You've already forked agentic-coding-workflow
主流程
This commit is contained in:
@@ -89,10 +89,10 @@ func (f *fakeAgentKindRepo) InsertSession(context.Context, *acp.Session) (*acp.S
|
||||
func (f *fakeAgentKindRepo) GetSessionByID(context.Context, uuid.UUID) (*acp.Session, error) {
|
||||
panic("n/a")
|
||||
}
|
||||
func (f *fakeAgentKindRepo) ListSessionsByUser(context.Context, uuid.UUID) ([]*acp.Session, error) {
|
||||
func (f *fakeAgentKindRepo) ListSessionsByUser(context.Context, uuid.UUID, *uuid.UUID) ([]*acp.Session, error) {
|
||||
panic("n/a")
|
||||
}
|
||||
func (f *fakeAgentKindRepo) ListAllSessions(context.Context) ([]*acp.Session, error) {
|
||||
func (f *fakeAgentKindRepo) ListAllSessions(context.Context, *uuid.UUID) ([]*acp.Session, error) {
|
||||
panic("n/a")
|
||||
}
|
||||
func (f *fakeAgentKindRepo) UpdateSessionRunning(context.Context, uuid.UUID, string, int32) error {
|
||||
|
||||
@@ -120,10 +120,17 @@ type AgentKindService interface {
|
||||
type SessionService interface {
|
||||
Create(ctx context.Context, c Caller, in CreateSessionInput) (*Session, error)
|
||||
Get(ctx context.Context, c Caller, id uuid.UUID) (*Session, error)
|
||||
List(ctx context.Context, c Caller, all bool) ([]*Session, error)
|
||||
List(ctx context.Context, c Caller, f SessionListFilter) ([]*Session, error)
|
||||
Terminate(ctx context.Context, c Caller, id uuid.UUID) error
|
||||
}
|
||||
|
||||
// SessionListFilter 控制 sessions 列表的过滤维度。
|
||||
// All 仅 admin 可用(查看全部用户);RequirementID nil 表示不按需求过滤。
|
||||
type SessionListFilter struct {
|
||||
All bool
|
||||
RequirementID *uuid.UUID
|
||||
}
|
||||
|
||||
// CreateSessionInput 是创建 session 的入参。
|
||||
// branch / issue_number / requirement_number 三选一或全空(spec §8.1)。
|
||||
type CreateSessionInput struct {
|
||||
|
||||
+10
-2
@@ -300,8 +300,16 @@ func (h *Handler) listSessions(w http.ResponseWriter, r *http.Request) {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
all := r.URL.Query().Get("all") == "true"
|
||||
list, err := h.sessSvc.List(r.Context(), c, all)
|
||||
f := SessionListFilter{All: r.URL.Query().Get("all") == "true"}
|
||||
if v := r.URL.Query().Get("requirement_id"); v != "" {
|
||||
id, err := uuid.Parse(v)
|
||||
if err != nil {
|
||||
writeErr(w, r, errs.New(errs.CodeInvalidInput, "requirement_id 非法"))
|
||||
return
|
||||
}
|
||||
f.RequirementID = &id
|
||||
}
|
||||
list, err := h.sessSvc.List(r.Context(), c, f)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
|
||||
@@ -23,6 +23,7 @@ SELECT id, workspace_id, project_id, agent_kind_id, user_id,
|
||||
pid, exit_code, last_error, started_at, ended_at
|
||||
FROM acp_sessions
|
||||
WHERE user_id = $1
|
||||
AND ($2::uuid IS NULL OR requirement_id = $2)
|
||||
ORDER BY started_at DESC;
|
||||
|
||||
-- name: ListAllSessions :many
|
||||
@@ -31,6 +32,7 @@ SELECT id, workspace_id, project_id, agent_kind_id, user_id,
|
||||
branch, cwd_path, is_main_worktree, status,
|
||||
pid, exit_code, last_error, started_at, ended_at
|
||||
FROM acp_sessions
|
||||
WHERE ($1::uuid IS NULL OR requirement_id = $1)
|
||||
ORDER BY started_at DESC;
|
||||
|
||||
-- name: UpdateSessionRunning :exec
|
||||
|
||||
@@ -81,10 +81,10 @@ func (f *fakeEventRepo) InsertSession(context.Context, *acp.Session) (*acp.Sessi
|
||||
func (f *fakeEventRepo) GetSessionByID(context.Context, uuid.UUID) (*acp.Session, error) {
|
||||
panic("n/a")
|
||||
}
|
||||
func (f *fakeEventRepo) ListSessionsByUser(context.Context, uuid.UUID) ([]*acp.Session, error) {
|
||||
func (f *fakeEventRepo) ListSessionsByUser(context.Context, uuid.UUID, *uuid.UUID) ([]*acp.Session, error) {
|
||||
panic("n/a")
|
||||
}
|
||||
func (f *fakeEventRepo) ListAllSessions(context.Context) ([]*acp.Session, error) {
|
||||
func (f *fakeEventRepo) ListAllSessions(context.Context, *uuid.UUID) ([]*acp.Session, error) {
|
||||
panic("n/a")
|
||||
}
|
||||
func (f *fakeEventRepo) UpdateSessionRunning(context.Context, uuid.UUID, string, int32) error {
|
||||
|
||||
@@ -31,8 +31,9 @@ type Repository interface {
|
||||
// Session
|
||||
InsertSession(ctx context.Context, s *Session) (*Session, error)
|
||||
GetSessionByID(ctx context.Context, id uuid.UUID) (*Session, error)
|
||||
ListSessionsByUser(ctx context.Context, userID uuid.UUID) ([]*Session, error)
|
||||
ListAllSessions(ctx context.Context) ([]*Session, error)
|
||||
// requirementID 非 nil 时仅返回关联该需求的 sessions。
|
||||
ListSessionsByUser(ctx context.Context, userID uuid.UUID, requirementID *uuid.UUID) ([]*Session, error)
|
||||
ListAllSessions(ctx context.Context, requirementID *uuid.UUID) ([]*Session, error)
|
||||
UpdateSessionRunning(ctx context.Context, id uuid.UUID, agentSessionID string, pid int32) error
|
||||
UpdateSessionFinished(ctx context.Context, id uuid.UUID, status SessionStatus, exitCode *int32, lastErr *string) error
|
||||
CountActiveSessions(ctx context.Context) (int64, error)
|
||||
@@ -288,8 +289,11 @@ func (r *pgRepo) GetSessionByID(ctx context.Context, id uuid.UUID) (*Session, er
|
||||
return rowToSession(row), nil
|
||||
}
|
||||
|
||||
func (r *pgRepo) ListSessionsByUser(ctx context.Context, userID uuid.UUID) ([]*Session, error) {
|
||||
rows, err := r.q.ListSessionsByUser(ctx, toPgUUID(userID))
|
||||
func (r *pgRepo) ListSessionsByUser(ctx context.Context, userID uuid.UUID, requirementID *uuid.UUID) ([]*Session, error) {
|
||||
rows, err := r.q.ListSessionsByUser(ctx, acpsqlc.ListSessionsByUserParams{
|
||||
UserID: toPgUUID(userID),
|
||||
Column2: toPgUUIDPtr(requirementID),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err, errs.CodeInternal, "list sessions by user")
|
||||
}
|
||||
@@ -300,8 +304,8 @@ func (r *pgRepo) ListSessionsByUser(ctx context.Context, userID uuid.UUID) ([]*S
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *pgRepo) ListAllSessions(ctx context.Context) ([]*Session, error) {
|
||||
rows, err := r.q.ListAllSessions(ctx)
|
||||
func (r *pgRepo) ListAllSessions(ctx context.Context, requirementID *uuid.UUID) ([]*Session, error) {
|
||||
rows, err := r.q.ListAllSessions(ctx, toPgUUIDPtr(requirementID))
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err, errs.CodeInternal, "list all sessions")
|
||||
}
|
||||
|
||||
@@ -335,14 +335,14 @@ func (s *sessionService) Get(ctx context.Context, c Caller, id uuid.UUID) (*Sess
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
func (s *sessionService) List(ctx context.Context, c Caller, all bool) ([]*Session, error) {
|
||||
if all && !c.IsAdmin {
|
||||
func (s *sessionService) List(ctx context.Context, c Caller, f SessionListFilter) ([]*Session, error) {
|
||||
if f.All && !c.IsAdmin {
|
||||
return nil, errs.New(errs.CodeForbidden, "admin only")
|
||||
}
|
||||
if all {
|
||||
return s.repo.ListAllSessions(ctx)
|
||||
if f.All {
|
||||
return s.repo.ListAllSessions(ctx, f.RequirementID)
|
||||
}
|
||||
return s.repo.ListSessionsByUser(ctx, c.UserID)
|
||||
return s.repo.ListSessionsByUser(ctx, c.UserID, f.RequirementID)
|
||||
}
|
||||
|
||||
func (s *sessionService) Terminate(ctx context.Context, c Caller, id uuid.UUID) error {
|
||||
|
||||
@@ -212,10 +212,33 @@ func (r *fakeAcpRepo) DeleteAgentKind(_ context.Context, _ uuid.UUID) error { re
|
||||
func (r *fakeAcpRepo) CountAgentKindUsage(_ context.Context, _ uuid.UUID) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
func (r *fakeAcpRepo) ListSessionsByUser(_ context.Context, _ uuid.UUID) ([]*acp.Session, error) {
|
||||
return nil, nil
|
||||
func (r *fakeAcpRepo) ListSessionsByUser(_ context.Context, userID uuid.UUID, reqID *uuid.UUID) ([]*acp.Session, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
var out []*acp.Session
|
||||
for _, s := range r.sessions {
|
||||
if s.UserID != userID {
|
||||
continue
|
||||
}
|
||||
if reqID != nil && (s.RequirementID == nil || *s.RequirementID != *reqID) {
|
||||
continue
|
||||
}
|
||||
out = append(out, s)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (r *fakeAcpRepo) ListAllSessions(_ context.Context, reqID *uuid.UUID) ([]*acp.Session, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
var out []*acp.Session
|
||||
for _, s := range r.sessions {
|
||||
if reqID != nil && (s.RequirementID == nil || *s.RequirementID != *reqID) {
|
||||
continue
|
||||
}
|
||||
out = append(out, s)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (r *fakeAcpRepo) ListAllSessions(_ context.Context) ([]*acp.Session, error) { return nil, nil }
|
||||
func (r *fakeAcpRepo) UpdateSessionRunning(_ context.Context, _ uuid.UUID, _ string, _ int32) error {
|
||||
return nil
|
||||
}
|
||||
@@ -440,3 +463,35 @@ func (f *fakeAcpRepo) ExpirePendingPermissionRequestsBySession(context.Context,
|
||||
func (f *fakeAcpRepo) ExpireStalePermissionRequests(context.Context, time.Time) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func TestSessionService_List_RequirementFilter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
uid := uuid.New()
|
||||
reqID := uuid.New()
|
||||
repo := &fakeAcpRepo{}
|
||||
repo.sessions = []*acp.Session{
|
||||
{ID: uuid.New(), UserID: uid, RequirementID: &reqID},
|
||||
{ID: uuid.New(), UserID: uid},
|
||||
{ID: uuid.New(), UserID: uuid.New(), RequirementID: &reqID},
|
||||
}
|
||||
|
||||
svc := acp.NewSessionService(
|
||||
repo, nil, nil, nil, nil, nil, nil,
|
||||
acp.SessionServiceConfig{}, nil,
|
||||
)
|
||||
|
||||
// 普通用户:仅本人 + requirement 过滤
|
||||
list, err := svc.List(context.Background(), acp.Caller{UserID: uid}, acp.SessionListFilter{RequirementID: &reqID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 1)
|
||||
|
||||
// 非 admin 请求 all → 403
|
||||
_, err = svc.List(context.Background(), acp.Caller{UserID: uid}, acp.SessionListFilter{All: true})
|
||||
require.Error(t, err)
|
||||
|
||||
// admin all + requirement 过滤:跨用户 2 条
|
||||
list, err = svc.List(context.Background(), acp.Caller{UserID: uid, IsAdmin: true}, acp.SessionListFilter{All: true, RequirementID: &reqID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 2)
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ type Conversation struct {
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
DeletedAt pgtype.Timestamptz `json:"deleted_at"`
|
||||
RequirementID pgtype.UUID `json:"requirement_id"`
|
||||
}
|
||||
|
||||
type GitCredential struct {
|
||||
@@ -276,6 +277,18 @@ type Requirement struct {
|
||||
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
||||
}
|
||||
|
||||
type RequirementArtifact struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
RequirementID pgtype.UUID `json:"requirement_id"`
|
||||
Phase string `json:"phase"`
|
||||
Version int32 `json:"version"`
|
||||
Content string `json:"content"`
|
||||
Note string `json:"note"`
|
||||
SourceMessageID *int64 `json:"source_message_id"`
|
||||
CreatedBy pgtype.UUID `json:"created_by"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
Email string `json:"email"`
|
||||
|
||||
@@ -154,11 +154,12 @@ SELECT id, workspace_id, project_id, agent_kind_id, user_id,
|
||||
branch, cwd_path, is_main_worktree, status,
|
||||
pid, exit_code, last_error, started_at, ended_at
|
||||
FROM acp_sessions
|
||||
WHERE ($1::uuid IS NULL OR requirement_id = $1)
|
||||
ORDER BY started_at DESC
|
||||
`
|
||||
|
||||
func (q *Queries) ListAllSessions(ctx context.Context) ([]AcpSession, error) {
|
||||
rows, err := q.db.Query(ctx, listAllSessions)
|
||||
func (q *Queries) ListAllSessions(ctx context.Context, dollar_1 pgtype.UUID) ([]AcpSession, error) {
|
||||
rows, err := q.db.Query(ctx, listAllSessions, dollar_1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -202,11 +203,17 @@ SELECT id, workspace_id, project_id, agent_kind_id, user_id,
|
||||
pid, exit_code, last_error, started_at, ended_at
|
||||
FROM acp_sessions
|
||||
WHERE user_id = $1
|
||||
AND ($2::uuid IS NULL OR requirement_id = $2)
|
||||
ORDER BY started_at DESC
|
||||
`
|
||||
|
||||
func (q *Queries) ListSessionsByUser(ctx context.Context, userID pgtype.UUID) ([]AcpSession, error) {
|
||||
rows, err := q.db.Query(ctx, listSessionsByUser, userID)
|
||||
type ListSessionsByUserParams struct {
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
Column2 pgtype.UUID `json:"column_2"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListSessionsByUser(ctx context.Context, arg ListSessionsByUserParams) ([]AcpSession, error) {
|
||||
rows, err := q.db.Query(ctx, listSessionsByUser, arg.UserID, arg.Column2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user