Add team tasks: several agents in one worktree sharing a board
This commit is contained in:
+21
-7
@@ -476,6 +476,9 @@ ALTER TABLE inbox_responses DROP COLUMN client;
|
||||
ALTER TABLE inbox_responses DROP COLUMN exit_code;
|
||||
`}, {"add-user-default-model", `
|
||||
ALTER TABLE users ADD COLUMN default_model TEXT NOT NULL DEFAULT 'claude-opus-5-5';
|
||||
`}, {"add-team-members", `
|
||||
ALTER TABLE agent_sessions ADD COLUMN member TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE task_inputs ADD COLUMN member TEXT NOT NULL DEFAULT '';
|
||||
`}}
|
||||
|
||||
const (
|
||||
@@ -1305,7 +1308,7 @@ func (s *Store) Participants(ctx context.Context, taskID string) ([]api.Particip
|
||||
rows, err := s.db.QueryContext(ctx, `
|
||||
SELECT u.login, u.display_name, u.git_name, u.git_email
|
||||
FROM events e JOIN users u ON u.login = e.actor
|
||||
WHERE e.task_id = ? AND (e.kind = 'user.message' OR (e.kind = 'task.created' AND json_extract(e.payload, '$.forked_from') IS NOT NULL))
|
||||
WHERE e.task_id = ? AND (e.kind IN ('user.message', 'board.posted') OR (e.kind = 'task.created' AND json_extract(e.payload, '$.forked_from') IS NOT NULL))
|
||||
GROUP BY u.login ORDER BY min(e.seq)
|
||||
`, taskID)
|
||||
if err != nil {
|
||||
@@ -1349,6 +1352,17 @@ func (s *Store) TaskEvents(ctx context.Context, taskID string, after int64, limi
|
||||
return scanEvents(rows)
|
||||
}
|
||||
|
||||
func (s *Store) BoardPosts(ctx context.Context, taskID string, after int64) ([]api.Event, error) {
|
||||
rows, err := s.db.QueryContext(ctx, `
|
||||
SELECT `+eventColumns+`
|
||||
FROM events WHERE task_id = ? AND kind = 'board.posted' AND seq > ? ORDER BY seq
|
||||
`, taskID, after)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return scanEvents(rows)
|
||||
}
|
||||
|
||||
func (s *Store) TaskEventsTail(ctx context.Context, taskID string, limit int) ([]api.Event, error) {
|
||||
rows, err := s.db.QueryContext(ctx, `
|
||||
SELECT * FROM (SELECT `+eventColumns+` FROM events WHERE task_id = ? ORDER BY seq DESC LIMIT ?) ORDER BY seq
|
||||
@@ -1478,9 +1492,9 @@ func (s *Store) CreateSession(ctx context.Context, sess api.AgentSession, projec
|
||||
}
|
||||
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
_, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO agent_sessions (id, task_id, owner, vendor, model, vendor_session_id, state, role, created_by, created_at, forked_from, forked_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, sess.Id, nullable(sess.TaskId), sess.Owner, string(sess.Vendor), sess.Model, sess.VendorSessionId, string(sess.State), string(sess.Role), sess.CreatedBy, formatTime(sess.CreatedAt), forkedFrom, forkedAt)
|
||||
INSERT INTO agent_sessions (id, task_id, owner, vendor, model, vendor_session_id, state, role, member, created_by, created_at, forked_from, forked_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, sess.Id, nullable(sess.TaskId), sess.Owner, string(sess.Vendor), sess.Model, sess.VendorSessionId, string(sess.State), string(sess.Role), sess.Member, sess.CreatedBy, formatTime(sess.CreatedAt), forkedFrom, forkedAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1836,7 +1850,7 @@ const taskStateExpr = `CASE
|
||||
END
|
||||
FROM agent_sessions s
|
||||
WHERE s.task_id = tasks.id AND s.role != 'reviewer' AND s.state != 'completed'
|
||||
ORDER BY s.created_at DESC LIMIT 1
|
||||
ORDER BY s.state = 'running' DESC, s.subagents > 0 DESC, s.created_at DESC LIMIT 1
|
||||
), 'no_agent')
|
||||
END`
|
||||
|
||||
@@ -1895,7 +1909,7 @@ const projectColumns = `id, name, repo_path, default_branch, remote, created_by,
|
||||
|
||||
const taskColumns = `id, project_id, title, merge_title, branch, worktree_path, base_ref, port_base, color, status, created_by, created_at, closed_at, merge_commit, todo_item, forked_from, forked_at, merge_queued_at, workflow_name, workflow_run, workflow_stage, parent_task_id, parent_merge_by, merge_after_review_by != '', state, merging_by != '' OR finalizing != '', discard_state != '' AND status = 'active', (SELECT coalesce(max(seq), 0) FROM events WHERE events.task_id = tasks.id AND kind = 'turn.ended' AND json_extract(payload, '$.summary') = 1)`
|
||||
|
||||
const sessionColumns = `id, coalesce(task_id, ''), owner, vendor, model, vendor_session_id, state, role, created_by, created_at, forked_from, forked_at, subagents`
|
||||
const sessionColumns = `id, coalesce(task_id, ''), owner, vendor, model, vendor_session_id, state, role, member, created_by, created_at, forked_from, forked_at, subagents`
|
||||
|
||||
type scanner interface {
|
||||
Scan(dest ...any) error
|
||||
@@ -1985,7 +1999,7 @@ func scanSession(row scanner) (api.AgentSession, error) {
|
||||
createdAt := ""
|
||||
forkedFrom := ""
|
||||
forkedAt := ""
|
||||
err := row.Scan(&sess.Id, &sess.TaskId, &sess.Owner, &vendor, &sess.Model, &sess.VendorSessionId, &state, &role, &sess.CreatedBy, &createdAt, &forkedFrom, &forkedAt, &sess.Subagents)
|
||||
err := row.Scan(&sess.Id, &sess.TaskId, &sess.Owner, &vendor, &sess.Model, &sess.VendorSessionId, &state, &role, &sess.Member, &sess.CreatedBy, &createdAt, &forkedFrom, &forkedAt, &sess.Subagents)
|
||||
if err != nil {
|
||||
return sess, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user