Add ask_human and the inbox with batched responses and delivery to agents
This commit is contained in:
+55
-3
@@ -78,6 +78,41 @@ CREATE TABLE agent_sessions (
|
||||
|
||||
CREATE INDEX agent_sessions_task ON agent_sessions(task_id, created_at);
|
||||
CREATE INDEX agent_sessions_state ON agent_sessions(state);
|
||||
`, `
|
||||
CREATE TABLE inbox_items (
|
||||
id TEXT PRIMARY KEY,
|
||||
project_id TEXT NOT NULL REFERENCES projects(id),
|
||||
task_id TEXT NOT NULL REFERENCES tasks(id),
|
||||
agent_session_id TEXT NOT NULL REFERENCES agent_sessions(id),
|
||||
thread_id TEXT NOT NULL,
|
||||
kind TEXT NOT NULL,
|
||||
priority INTEGER NOT NULL,
|
||||
blocking INTEGER NOT NULL,
|
||||
payload TEXT NOT NULL,
|
||||
state TEXT NOT NULL,
|
||||
snoozed_until TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
resolved_by TEXT REFERENCES users(login),
|
||||
resolved_at TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX inbox_items_open ON inbox_items(state, priority, created_at);
|
||||
CREATE INDEX inbox_items_session ON inbox_items(agent_session_id, state);
|
||||
|
||||
CREATE TABLE inbox_responses (
|
||||
id TEXT PRIMARY KEY,
|
||||
item_id TEXT NOT NULL REFERENCES inbox_items(id),
|
||||
user_login TEXT NOT NULL REFERENCES users(login),
|
||||
type TEXT NOT NULL,
|
||||
option TEXT,
|
||||
text TEXT,
|
||||
attachments TEXT NOT NULL,
|
||||
snooze_until TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
delivered_at TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX inbox_responses_item ON inbox_responses(item_id, created_at);
|
||||
`}
|
||||
|
||||
type Store struct {
|
||||
@@ -384,13 +419,24 @@ func (s *Store) querySessions(ctx context.Context, query string, args ...any) ([
|
||||
return sessions, rows.Err()
|
||||
}
|
||||
|
||||
var ErrStateChanged = errors.New("session state changed")
|
||||
|
||||
func (s *Store) SetSessionState(ctx context.Context, id string, state api.AgentSessionState, actor string, payload map[string]any) error {
|
||||
return s.transitionSession(ctx, id, nil, state, actor, payload)
|
||||
}
|
||||
|
||||
func (s *Store) TransitionSession(ctx context.Context, id string, from, to api.AgentSessionState, actor string) error {
|
||||
return s.transitionSession(ctx, id, &from, to, actor, nil)
|
||||
}
|
||||
|
||||
func (s *Store) transitionSession(ctx context.Context, id string, from *api.AgentSessionState, state api.AgentSessionState, actor string, payload map[string]any) error {
|
||||
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
taskID := ""
|
||||
projectID := ""
|
||||
current := ""
|
||||
err := tx.QueryRowContext(ctx, `
|
||||
SELECT t.id, t.project_id FROM agent_sessions s JOIN tasks t ON t.id = s.task_id WHERE s.id = ?
|
||||
`, id).Scan(&taskID, &projectID)
|
||||
SELECT t.id, t.project_id, s.state FROM agent_sessions s JOIN tasks t ON t.id = s.task_id WHERE s.id = ?
|
||||
`, id).Scan(&taskID, &projectID, ¤t)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return ErrNotFound
|
||||
}
|
||||
@@ -398,6 +444,10 @@ func (s *Store) SetSessionState(ctx context.Context, id string, state api.AgentS
|
||||
return err
|
||||
}
|
||||
|
||||
if from != nil && current != string(*from) {
|
||||
return fmt.Errorf("%w: session %s is %s, not %s", ErrStateChanged, id, current, *from)
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, `UPDATE agent_sessions SET state = ? WHERE id = ?`, string(state), id)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -560,8 +610,10 @@ func now() string {
|
||||
return formatTime(time.Now())
|
||||
}
|
||||
|
||||
const timeLayout = "2006-01-02T15:04:05.000000000Z07:00"
|
||||
|
||||
func formatTime(t time.Time) string {
|
||||
return t.UTC().Format(time.RFC3339Nano)
|
||||
return t.UTC().Format(timeLayout)
|
||||
}
|
||||
|
||||
func parseTime(s string) (time.Time, error) {
|
||||
|
||||
Reference in New Issue
Block a user