From 293c21ca8c99f0f2eb63921eecf5fc11dfa2a743 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Thu, 22 Oct 2026 21:55:42 -0700 Subject: [PATCH] Add a pause that holds an agent after its current step --- api/openapi.yaml | 33 +- docs/antigravity-protocol.md | 4 +- docs/claude-protocol.md | 4 +- docs/codex-protocol.md | 4 + internal/agents/antigravity.go | 13 +- internal/agents/codex.go | 20 +- internal/agents/compact.go | 8 + internal/agents/guidance.go | 8 +- internal/agents/inbox.go | 25 + internal/agents/manager.go | 146 ++-- internal/agents/pause.go | 177 ++++ internal/agents/process.go | 10 +- internal/agents/queue.go | 2 +- internal/agents/snapshots.go | 14 +- internal/antigravity/session.go | 39 +- internal/api/api.gen.go | 757 ++++++++++-------- internal/claude/session.go | 10 +- internal/codex/session.go | 64 +- internal/hookcmd/hookcmd.go | 40 + internal/server/server.go | 18 + .../migrations/104-add-session-paused.sql | 1 + internal/store/store.go | 30 +- mac/Sources/Byre/Chat.swift | 40 + mac/Sources/Byre/Composer.swift | 17 +- mac/Sources/Byre/Store.swift | 2 +- 25 files changed, 1014 insertions(+), 472 deletions(-) create mode 100644 internal/agents/pause.go create mode 100644 internal/hookcmd/hookcmd.go create mode 100644 internal/store/migrations/104-add-session-paused.sql diff --git a/api/openapi.yaml b/api/openapi.yaml index 57aba36..354b558 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -1196,6 +1196,36 @@ paths: "404": { $ref: "#/components/responses/Error" } "409": { $ref: "#/components/responses/Error" } + /api/sessions/{sessionId}/pause: + parameters: + - { name: sessionId, in: path, required: true, schema: { type: string } } + post: + operationId: pauseSession + description: Pauses a task agent without interrupting it (see AgentSession.paused). + responses: + "200": + description: The agent is paused. + content: + application/json: + schema: { $ref: "#/components/schemas/AgentSession" } + "404": { $ref: "#/components/responses/Error" } + "409": { $ref: "#/components/responses/Error" } + + /api/sessions/{sessionId}/resume: + parameters: + - { name: sessionId, in: path, required: true, schema: { type: string } } + post: + operationId: resumeSession + description: Ends a pause; messages that queued during it reach the agent now. + responses: + "200": + description: The agent is running again. + content: + application/json: + schema: { $ref: "#/components/schemas/AgentSession" } + "404": { $ref: "#/components/responses/Error" } + "409": { $ref: "#/components/responses/Error" } + /api/blobs: post: operationId: uploadBlob @@ -2046,7 +2076,7 @@ components: AgentSession: type: object - required: [id, task_id, owner, vendor, model, vendor_session_id, state, role, member, created_by, created_at, subagents] + required: [id, task_id, owner, vendor, model, vendor_session_id, state, role, member, created_by, created_at, subagents, paused] properties: id: { type: string } task_id: { type: string, description: Empty for a lead session. } @@ -2065,6 +2095,7 @@ components: forked_from: { type: string, description: "A fork's first start resumes this vendor session, cut at forked_at, under a new vendor_session_id; absent for other sessions." } forked_at: { type: string, description: "With forked_from: the vendor's id of the last thing the fork keeps, a message for Claude Code, a turn for Codex." } subagents: { type: integer, description: "How many of the agent's subagents are running, background ones included after the turn that started them has ended." } + paused: { type: boolean, description: "A task agent's pause: mid-turn it holds at its next step (after the tool call or model call in progress finishes); idle, no turn starts and messages queue until resumed." } SessionCreate: type: object required: [prompt] diff --git a/docs/antigravity-protocol.md b/docs/antigravity-protocol.md index 31cab5e..5ff5347 100644 --- a/docs/antigravity-protocol.md +++ b/docs/antigravity-protocol.md @@ -22,7 +22,7 @@ There is no flag for a system prompt or MCP servers, so byre hands them over as - `plugin.json`: `{"name": "byre"}`. - `rules/AGENTS.md`: always-on guidance. `rules/notes.md` uses `trigger: always_on` frontmatter. The CLI cuts each rule file at 24,000 bytes, so longer guidance or notes must be refused before launch. - `mcp_config.json`: one entry per server, with a Streamable HTTP `url` or a stdio `command` and `args`. The CLI namespaces plugin servers as `_`, so byre's own server is `byre_byre`. -- `hooks.json`: handlers keyed by hook event, each a command that reads the hook's JSON from stdin and prints its reply. Only flat events (`SessionStart`, `PreInvocation`, `PostInvocation`, `Stop`) fit this shape. +- `hooks.json`: handlers keyed by hook event, each a command that reads the hook's JSON from stdin and prints its reply, with a `timeout` in seconds (default 30). Flat events (`SessionStart`, `PreInvocation`, `PostInvocation`, `Stop`) list handlers directly; tool events (`PreToolUse`, `PostToolUse`) wrap them in groups, `[{"matcher": "*", "hooks": [...]}]`, where `matcher` is a regex on the tool name. ## The session's listener @@ -52,7 +52,7 @@ There is no way to switch the model of a running process. Close the idle process ## Hooks -Hook input and output are camelCase JSON. byre uses `PreInvocation`, which runs before every model call with `invocationNum` (0 for a turn's first call) and `initialNumSteps`; an output of `{"injectSteps":[{"userMessage":"..."}]}` adds a user message the model reads on that call. The `Stop` hook gets no final message, so byre doesn't use it. +Hook input and output are camelCase JSON. byre uses `PreInvocation`, which runs before every model call with `invocationNum` (0 for a turn's first call) and `initialNumSteps`; an output of `{"injectSteps":[{"userMessage":"..."}]}` adds a user message the model reads on that call. byre also uses `PreToolUse` (input `toolCall` with `name` and `args`), whose output needs `decision`: `allow`, or `deny` with a `reason` the agent reads in place of the tool's result. The `Stop` hook gets no final message, so byre doesn't use it. ## Forking diff --git a/docs/claude-protocol.md b/docs/claude-protocol.md index b8166de..acd563c 100644 --- a/docs/claude-protocol.md +++ b/docs/claude-protocol.md @@ -38,14 +38,14 @@ Either side sends `{"type":"control_request","request_id":ID,"request":{"subtype Host → CLI: -- `initialize` must be first. It carries hook registrations: `{"hooks":{"Stop":[{"matcher":null,"hookCallbackIds":["hook_0"]}],...}}`. +- `initialize` must be first. It carries hook registrations: `{"hooks":{"Stop":[{"matcher":null,"hookCallbackIds":["hook_0"],"timeout":}],...}}`. Without `timeout` the CLI abandons a hook callback after its default; the timer is a JavaScript `setTimeout`, so values past about 24 days (2^31 ms) fire at once. - `interrupt` ends the current turn; the turn's `result` follows almost immediately and a running Bash tool is killed. The session stays usable. - `set_model` (`{"subtype":"set_model","model":M}`) switches the running process's model for later turns. - `set_permission_mode`, `mcp_status`, `get_context_usage`, `rewind_files`, `stop_task` exist and are not used yet. CLI → host: -- `hook_callback` with `callback_id` and `input` (the hook's input JSON). The response body is the hook output. A Stop hook returning `{"decision":"block","reason":"..."}` keeps the agent going with the reason fed back to it. +- `hook_callback` with `callback_id` and `input` (the hook's input JSON). The response body is the hook output. A Stop hook returning `{"decision":"block","reason":"..."}` keeps the agent going with the reason fed back to it. A PreToolUse hook returning `{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"..."}}` skips the call, and the model reads the reason in its place. The model treats that reason as tool output and won't follow instructions in it; an `additionalContext` beside `permissionDecision` reaches it as context it does follow. A hook's callback can take as long as it likes within its timeout; the agent waits. - `mcp_message` with `server_name` and a JSON-RPC `message` for an `sdk`-type MCP server; answer with `{"mcp_response": }`. Notifications still need a response. - `can_use_tool` only arrives when a permission prompt would show; under `bypassPermissions` it should not. - `control_cancel_request` abandons an in-flight request. diff --git a/docs/codex-protocol.md b/docs/codex-protocol.md index a84f9d6..c7b42ea 100644 --- a/docs/codex-protocol.md +++ b/docs/codex-protocol.md @@ -33,6 +33,10 @@ A `dynamicTools` entry of type `namespace` holds tools as `function`s with their Closing stdin ends the process. +## Hooks + +The `config` on `thread/start`, `thread/resume` and `thread/fork` takes hooks in Claude Code's shape: `{"hooks":{"PreToolUse":[{"hooks":[{"type":"command","command":...,"timeout":}]}]}}`. They are ignored unless `config` also sets `bypass_hook_trust` true: hooks otherwise need trust recorded in `config.toml` (the `--dangerously-bypass-hook-trust` of `codex exec`, which `app-server` lacks as a flag). The command reads the hook's JSON on stdin and prints its reply on stdout; a sync hook holds the turn until it exits, and `hook/started` and `hook/completed` notifications report each run. `PostToolUse` runs after the tool's `item/completed`. A `PreToolUse` reply of `{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":...}}` skips the call, and the model reads "Command blocked by PreToolUse hook:" and the reason in its place; an `additionalContext` beside it reaches the model too. Output that isn't valid JSON fails the hook (`hook returned invalid pre-tool-use JSON output`) and the call runs. + ## Events - `turn/started`, `turn/completed` (`turn.status` `completed`, `interrupted` or `failed` with `turn.error`). A failed request to the model also sends an `error` notification first, with `willRetry`. diff --git a/internal/agents/antigravity.go b/internal/agents/antigravity.go index bd0378f..f4361a0 100644 --- a/internal/agents/antigravity.go +++ b/internal/agents/antigravity.go @@ -11,6 +11,7 @@ import ( "g.fc.run/ai/byre/internal/antigravity" "g.fc.run/ai/byre/internal/api" + "g.fc.run/ai/byre/internal/hookcmd" ) type agySteps struct { @@ -192,7 +193,7 @@ func agyUsage(groups []antigravity.UsageGroup) api.Usage { return u } -func (m *Manager) agyBeforeModel(l *live) antigravity.HookFunc { +func (m *Manager) agyBeforeModel(l *live) hookcmd.Func { return func(ctx context.Context, input json.RawMessage) (map[string]any, error) { in := struct { InvocationNum int `json:"invocationNum"` @@ -204,6 +205,7 @@ func (m *Manager) agyBeforeModel(l *live) antigravity.HookFunc { if in.InvocationNum == 0 || l.reviewID != "" { return map[string]any{}, nil } + m.holdWhilePaused(ctx, l) mt := m.deliverMidTurn(ctx, l) if mt.text == "" { return map[string]any{}, nil @@ -211,3 +213,12 @@ func (m *Manager) agyBeforeModel(l *live) antigravity.HookFunc { return map[string]any{"injectSteps": []map[string]any{{"userMessage": mt.text}}}, nil } } + +func (m *Manager) agyBeforeTool(l *live) hookcmd.Func { + return func(ctx context.Context, input json.RawMessage) (map[string]any, error) { + if !m.heldBeforeTool(ctx, l) || !m.midTurnWaiting(ctx, l) { + return map[string]any{"decision": "allow"}, nil + } + return map[string]any{"decision": "deny", "reason": pausedCallNotice}, nil + } +} diff --git a/internal/agents/codex.go b/internal/agents/codex.go index 4b4c180..fb4e7b4 100644 --- a/internal/agents/codex.go +++ b/internal/agents/codex.go @@ -11,6 +11,7 @@ import ( "g.fc.run/ai/byre/internal/api" "g.fc.run/ai/byre/internal/codex" + "g.fc.run/ai/byre/internal/hookcmd" ) var codexToolItems = map[string]bool{ @@ -181,15 +182,20 @@ func (m *Manager) codexItemCompleted(ctx context.Context, l *live, s *codex.Sess if l.reviewID != "" { return nil } + m.steerMidTurn(ctx, l, s) + return nil +} + +func (m *Manager) steerMidTurn(ctx context.Context, l *live, s *codex.Session) { mt := m.deliverMidTurn(ctx, l) if mt.text == "" { - return nil + return } reply, err := s.Steer(mt.text) if err != nil { slog.Error("steer mid-turn delivery", "session", l.id, "err", err) m.restoreMidTurn(ctx, l, mt) - return nil + return } go func() { ctx := context.Background() @@ -199,7 +205,15 @@ func (m *Manager) codexItemCompleted(ctx context.Context, l *live, s *codex.Sess m.restoreMidTurn(ctx, l, mt) } }() - return nil +} + +func (m *Manager) codexAfterTool(l *live) hookcmd.Func { + return func(ctx context.Context, input json.RawMessage) (map[string]any, error) { + if m.holdWhilePaused(ctx, l) && ctx.Err() == nil { + m.steerMidTurn(context.WithoutCancel(ctx), l, l.sess.(codexProcess).s) + } + return map[string]any{}, nil + } } func imageAlt(prompt string) string { diff --git a/internal/agents/compact.go b/internal/agents/compact.go index c55a538..fda4531 100644 --- a/internal/agents/compact.go +++ b/internal/agents/compact.go @@ -3,6 +3,7 @@ package agents import ( "context" "errors" + "fmt" "log/slog" "strings" @@ -33,7 +34,13 @@ func (m *Manager) Compact(ctx context.Context, sessionID, model string) (api.Age if err != nil { return api.AgentSession{}, err } + unlock := m.lockDelivery(sessionID) + if m.isPaused(sessionID) { + unlock() + return api.AgentSession{}, fmt.Errorf("%w: session %s is paused; resume it first", ErrConflict, sessionID) + } err = m.startRunning(ctx, sess, "byre") + unlock() if err != nil { return api.AgentSession{}, err } @@ -72,6 +79,7 @@ func isContinuation(actor, text string) bool { func (m *Manager) continueFromHandoff(sessionID, handoff string) { ctx := context.Background() + m.waitResumed(sessionID) err := m.handoff(ctx, sessionID, handoff) if err == nil { return diff --git a/internal/agents/guidance.go b/internal/agents/guidance.go index f9e9d1a..31a1269 100644 --- a/internal/agents/guidance.go +++ b/internal/agents/guidance.go @@ -9,7 +9,7 @@ import ( "unicode/utf8" "g.fc.run/ai/byre/internal/api" - "g.fc.run/ai/byre/internal/claude" + "g.fc.run/ai/byre/internal/hookcmd" "g.fc.run/ai/byre/internal/store" "g.fc.run/ai/byre/internal/todo" ) @@ -49,7 +49,7 @@ func asksQuestion(text string) bool { return endsQuestion.MatchString(paras[len(paras)-1]) } -func (m *Manager) bounceProseQuestion(l *live) claude.HookFunc { +func (m *Manager) bounceProseQuestion(l *live) hookcmd.Func { return func(ctx context.Context, input json.RawMessage) (map[string]any, error) { in := struct { LastAssistantMessage string `json:"last_assistant_message"` @@ -68,7 +68,7 @@ func (m *Manager) bounceProseQuestion(l *live) claude.HookFunc { } func (m *Manager) takeBounce(ctx context.Context, l *live, final string) (bool, error) { - if !asksQuestion(final) { + if !asksQuestion(final) || m.isPaused(l.id) { return false, nil } first, err := m.store.FirstPrompt(ctx, l.taskID) @@ -159,7 +159,7 @@ func forkGuidance(task, src api.Task) string { src.Title, *task.ForkedAt, task.Branch, task.WorktreePath, src.WorktreePath, task.WorktreePath) } -func remindGuidance(text string) claude.HookFunc { +func remindGuidance(text string) hookcmd.Func { return func(ctx context.Context, input json.RawMessage) (map[string]any, error) { return map[string]any{ "hookSpecificOutput": map[string]any{ diff --git a/internal/agents/inbox.go b/internal/agents/inbox.go index 120cf9f..fbcb422 100644 --- a/internal/agents/inbox.go +++ b/internal/agents/inbox.go @@ -304,6 +304,11 @@ func (m *Manager) Deliver(ctx context.Context, sessionID string) error { unlock := m.lockDelivery(sessionID) defer unlock() + if m.isPaused(sessionID) { + slog.Info("deliver held: session paused", "session", sessionID) + return nil + } + sess, err := m.store.GetSession(ctx, sessionID) if err != nil { return err @@ -400,6 +405,9 @@ func (m *Manager) deliverMidTurn(ctx context.Context, l *live) midTurn { unlock := m.lockDelivery(l.id) defer unlock() + if m.isPaused(l.id) { + return midTurn{} + } discard, err := m.store.GetDiscard(ctx, l.taskID) if err != nil { slog.Error("read discard state", "task", l.taskID, "err", err) @@ -428,6 +436,23 @@ func (m *Manager) deliverMidTurn(ctx context.Context, l *live) midTurn { return mt } +func (m *Manager) midTurnWaiting(ctx context.Context, l *live) bool { + inputs, err := m.store.TaskInputs(ctx, l.taskID, l.member) + if err != nil { + slog.Error("list queued messages", "session", l.id, "err", err) + return false + } + if len(inputs) > 0 { + return true + } + pending, err := m.store.Undelivered(ctx, l.id) + if err != nil { + slog.Error("list undelivered responses", "session", l.id, "err", err) + return false + } + return len(pending) > 0 +} + func (m *Manager) restoreMidTurn(ctx context.Context, l *live, mt midTurn) { if len(mt.taskInputs) > 0 { err := m.store.RestoreTaskInputs(ctx, mt.taskInputs) diff --git a/internal/agents/manager.go b/internal/agents/manager.go index e79ead2..04ddceb 100644 --- a/internal/agents/manager.go +++ b/internal/agents/manager.go @@ -62,6 +62,7 @@ type Manager struct { startHolds map[string]showHold handoffModel map[string]string mergeTurns map[string]mergeTurn + paused map[string]chan struct{} deliverMu sync.Mutex deliverLocks map[string]*sessionLock @@ -165,6 +166,7 @@ func New(st *store.Store, worktreeRoot, leadRoot, handsRoot, codexHome, antigrav startHolds: map[string]showHold{}, handoffModel: map[string]string{}, mergeTurns: map[string]mergeTurn{}, + paused: map[string]chan struct{}{}, signals: make(chan []api.Event, 1024), snaps: map[string]*snapState{}, pokes: make(chan struct{}, 1), @@ -326,7 +328,9 @@ func (m *Manager) StopTask(ctx context.Context, taskID, actor string) error { if sess.State == api.AgentSessionStateCompleted { continue } + unlock := m.lockDelivery(sess.Id) err = m.retire(ctx, sess, actor) + unlock() if err != nil { return err } @@ -337,7 +341,9 @@ func (m *Manager) StopTask(ctx context.Context, taskID, actor string) error { func (m *Manager) retire(ctx context.Context, sess api.AgentSession, actor string) error { m.stopLive(sess.Id) - return m.store.SetSessionState(ctx, sess.Id, api.AgentSessionStateCompleted, actor, nil) + err := m.store.SetSessionState(ctx, sess.Id, api.AgentSessionStateCompleted, actor, nil) + m.dropPause(sess.Id) + return err } func (m *Manager) stopLive(sessionID string) { @@ -357,12 +363,79 @@ func (m *Manager) stopLive(sessionID string) { } } +func (m *Manager) recoverSession(ctx context.Context, sess api.AgentSession) error { + task, err := m.taskFor(ctx, sess) + if err != nil { + slog.Error("resume session: load task", "session", sess.Id, "task", sess.TaskId, "err", err) + return nil + } + + pending, err := m.store.SessionPending(ctx, sess.Id) + if err != nil { + return err + } + started, err := m.store.SessionStarted(ctx, sess.Id) + if err != nil { + return err + } + finished, err := m.store.StageFinished(ctx, sess.Id) + if err != nil { + return err + } + if finished || !pending && !started { + m.endMergeTurn(sess.Id, true) + err = m.store.SetSessionState(ctx, sess.Id, api.AgentSessionStateIdle, "byre", nil) + if err != nil { + return err + } + slog.Info("session left idle at startup: its stage finished or it never got its first message", "session", sess.Id, "task", task.Id) + return nil + } + if pending { + found, err := m.deliverRecorded(ctx, sess, task) + if err != nil { + slog.Error("resume session: deliver its recorded message", "session", sess.Id, "task", task.Id, "err", err) + } + if found { + return nil + } + } + + l, err := m.ensureLive(ctx, sess, task) + if err != nil { + return m.failResume(ctx, sess, task, err) + } + + notice := restartNotice + if sess.Role == api.Implementer { + discard, err := m.store.GetDiscard(ctx, task.Id) + if err != nil { + return err + } + if discard.State == store.DiscardRunning && discard.Session == sess.Id { + notice = discardRestartPrompt + } + } + err = m.message(ctx, sess, task, l, "byre", notice, nil) + if err != nil { + return m.failResume(ctx, sess, task, err) + } + slog.Info("resumed session", "session", sess.Id, "task", task.Id, "owner", sess.Owner) + m.logUndelivered(ctx, sess.Id, "resumed with restart notice") + return nil +} + func (m *Manager) Recover(ctx context.Context) error { err := m.store.ClearMainPushes(ctx) if err != nil { return err } + err = m.loadPauses(ctx) + if err != nil { + return err + } + err = m.recoverPreviews(ctx) if err != nil { return err @@ -390,72 +463,18 @@ func (m *Manager) Recover(ctx context.Context) error { } for _, sess := range sessions { - task, err := m.taskFor(ctx, sess) - if err != nil { - slog.Error("resume session: load task", "session", sess.Id, "task", sess.TaskId, "err", err) + m.mu.Lock() + resumed := m.paused[sess.Id] + m.mu.Unlock() + if resumed != nil { + slog.Info("session resume held: paused", "session", sess.Id, "task", sess.TaskId) + go m.recoverAfterPause(sess.Id) continue } - - pending, err := m.store.SessionPending(ctx, sess.Id) + err = m.recoverSession(ctx, sess) if err != nil { return err } - started, err := m.store.SessionStarted(ctx, sess.Id) - if err != nil { - return err - } - finished, err := m.store.StageFinished(ctx, sess.Id) - if err != nil { - return err - } - if finished || !pending && !started { - m.endMergeTurn(sess.Id, true) - err = m.store.SetSessionState(ctx, sess.Id, api.AgentSessionStateIdle, "byre", nil) - if err != nil { - return err - } - slog.Info("session left idle at startup: its stage finished or it never got its first message", "session", sess.Id, "task", task.Id) - continue - } - if pending { - found, err := m.deliverRecorded(ctx, sess, task) - if err != nil { - slog.Error("resume session: deliver its recorded message", "session", sess.Id, "task", task.Id, "err", err) - } - if found { - continue - } - } - - l, err := m.ensureLive(ctx, sess, task) - if err != nil { - ferr := m.failResume(ctx, sess, task, err) - if ferr != nil { - return ferr - } - continue - } - - notice := restartNotice - if sess.Role == api.Implementer { - discard, err := m.store.GetDiscard(ctx, task.Id) - if err != nil { - return err - } - if discard.State == store.DiscardRunning && discard.Session == sess.Id { - notice = discardRestartPrompt - } - } - err = m.message(ctx, sess, task, l, "byre", notice, nil) - if err != nil { - ferr := m.failResume(ctx, sess, task, err) - if ferr != nil { - return ferr - } - continue - } - slog.Info("resumed session", "session", sess.Id, "task", task.Id, "owner", sess.Owner) - m.logUndelivered(ctx, sess.Id, "resumed with restart notice") } idle, err := m.store.SessionsInState(ctx, api.AgentSessionStateIdle) @@ -576,6 +595,9 @@ func (m *Manager) Usage(ctx context.Context) (Usage, error) { } func (m *Manager) turn(ctx context.Context, sess api.AgentSession, task api.Task, actor, text, sendID, client string, images []string) (api.AgentSession, error) { + if m.isPaused(sess.Id) { + return api.AgentSession{}, fmt.Errorf("%w: session %s is paused; resume it first", ErrConflict, sess.Id) + } err := m.startRunning(ctx, sess, actor) if err != nil { return api.AgentSession{}, err diff --git a/internal/agents/pause.go b/internal/agents/pause.go new file mode 100644 index 0000000..c1be8ca --- /dev/null +++ b/internal/agents/pause.go @@ -0,0 +1,177 @@ +package agents + +import ( + "context" + "encoding/json" + "fmt" + "log/slog" + + "g.fc.run/ai/byre/internal/api" + "g.fc.run/ai/byre/internal/hookcmd" +) + +const pausedCallNotice = "Not run: the human paused you just before this call and sent you a message while you were paused. Read it, then carry on, making this call again only if it still fits." + +func (m *Manager) Pause(ctx context.Context, sessionID, actor string) (api.AgentSession, error) { + sess, err := m.store.GetSession(ctx, sessionID) + if err != nil { + return api.AgentSession{}, err + } + if sess.Role != api.Implementer || sess.State == api.AgentSessionStateCompleted { + return api.AgentSession{}, fmt.Errorf("%w: session %s is not an active task agent", ErrConflict, sess.Id) + } + + unlock := m.lockDelivery(sessionID) + defer unlock() + if m.isPaused(sessionID) { + return sess, nil + } + m.mu.Lock() + m.paused[sessionID] = make(chan struct{}) + m.mu.Unlock() + err = m.store.SetSessionPaused(context.WithoutCancel(ctx), sessionID, true, actor) + if err != nil { + m.mu.Lock() + delete(m.paused, sessionID) + m.mu.Unlock() + return api.AgentSession{}, err + } + slog.Info("session paused", "session", sessionID, "task", sess.TaskId, "actor", actor) + return m.store.GetSession(ctx, sessionID) +} + +func (m *Manager) Resume(ctx context.Context, sessionID, actor string) (api.AgentSession, error) { + sess, err := m.store.GetSession(ctx, sessionID) + if err != nil { + return api.AgentSession{}, err + } + + unlock := m.lockDelivery(sessionID) + if !m.isPaused(sessionID) { + unlock() + return sess, nil + } + err = m.store.SetSessionPaused(context.WithoutCancel(ctx), sessionID, false, actor) + if err != nil { + unlock() + return api.AgentSession{}, err + } + m.dropPause(sessionID) + unlock() + slog.Info("session resumed", "session", sessionID, "task", sess.TaskId, "actor", actor) + + go func() { + err := m.Deliver(context.Background(), sessionID) + if err != nil { + slog.Error("deliver after resume", "session", sessionID, "err", err) + } + }() + return m.store.GetSession(ctx, sessionID) +} + +func (m *Manager) loadPauses(ctx context.Context) error { + sessions, err := m.store.PausedSessions(ctx) + if err != nil { + return err + } + m.mu.Lock() + defer m.mu.Unlock() + for _, sess := range sessions { + m.paused[sess.Id] = make(chan struct{}) + } + return nil +} + +func (m *Manager) dropPause(sessionID string) { + m.mu.Lock() + defer m.mu.Unlock() + ch := m.paused[sessionID] + if ch != nil { + close(ch) + delete(m.paused, sessionID) + } +} + +func (m *Manager) waitResumed(sessionID string) { + for { + m.mu.Lock() + ch := m.paused[sessionID] + m.mu.Unlock() + if ch == nil { + return + } + <-ch + } +} + +func (m *Manager) recoverAfterPause(sessionID string) { + ctx := context.Background() + for { + m.waitResumed(sessionID) + unlock := m.lockDelivery(sessionID) + if m.isPaused(sessionID) { + unlock() + continue + } + sess, err := m.store.GetSession(ctx, sessionID) + if err == nil && sess.State == api.AgentSessionStateRunning { + err = m.recoverSession(ctx, sess) + } + unlock() + if err != nil { + slog.Error("resume session after pause", "session", sessionID, "err", err) + } + return + } +} + +func (m *Manager) isPaused(sessionID string) bool { + m.mu.Lock() + defer m.mu.Unlock() + return m.paused[sessionID] != nil +} + +func (m *Manager) holdWhilePaused(ctx context.Context, l *live) bool { + held := false + for { + m.mu.Lock() + ch := m.paused[l.id] + m.mu.Unlock() + if ch == nil { + return held + } + if !held { + slog.Info("agent held by pause", "session", l.id) + } + held = true + select { + case <-ch: + case <-ctx.Done(): + return held + } + } +} + +func (m *Manager) heldBeforeTool(ctx context.Context, l *live) bool { + return m.holdWhilePaused(ctx, l) && ctx.Err() == nil +} + +func (m *Manager) beforeTool(l *live) hookcmd.Func { + return func(ctx context.Context, input json.RawMessage) (map[string]any, error) { + if !m.heldBeforeTool(ctx, l) { + return map[string]any{}, nil + } + mt := m.deliverMidTurn(ctx, l) + if mt.text == "" { + return map[string]any{}, nil + } + return map[string]any{ + "hookSpecificOutput": map[string]any{ + "hookEventName": "PreToolUse", + "permissionDecision": "deny", + "permissionDecisionReason": pausedCallNotice, + "additionalContext": mt.text, + }, + }, nil + } +} diff --git a/internal/agents/process.go b/internal/agents/process.go index c903bf1..b10c28a 100644 --- a/internal/agents/process.go +++ b/internal/agents/process.go @@ -15,6 +15,7 @@ import ( "g.fc.run/ai/byre/internal/api" "g.fc.run/ai/byre/internal/claude" "g.fc.run/ai/byre/internal/codex" + "g.fc.run/ai/byre/internal/hookcmd" "g.fc.run/ai/byre/internal/mcp" ) @@ -74,8 +75,12 @@ func (m *Manager) startClaude(ctx context.Context, sess api.AgentSession, l *liv hooks := map[string][]claude.HookMatcher{ "UserPromptSubmit": {{Func: remindGuidance(g)}}, } + if l.implementer() { + hooks["PreToolUse"] = []claude.HookMatcher{{Func: m.beforeTool(l)}} + } if l.reviewID == "" { hooks["PostToolUse"] = []claude.HookMatcher{{Func: m.afterTool(l)}} + hooks["PostToolUseFailure"] = []claude.HookMatcher{{Func: m.afterTool(l)}} if l.owner == "" { hooks["Stop"] = []claude.HookMatcher{{Func: m.bounceProseQuestion(l)}} } @@ -166,6 +171,9 @@ func (m *Manager) startCodex(ctx context.Context, sess api.AgentSession, l *live StdioMCP: stdio, OnBadLine: func(line []byte, reason string) { m.recordBadLine(l, line, reason) }, } + if l.implementer() { + opts.Hooks = map[string]hookcmd.Func{"PreToolUse": m.beforeTool(l), "PostToolUse": m.codexAfterTool(l)} + } switch { case sess.VendorSessionId != "": opts.Resume = sess.VendorSessionId @@ -237,7 +245,7 @@ func (m *Manager) startAntigravity(ctx context.Context, sess api.AgentSession, l Reminder: g, MCPServers: servers, StdioMCP: stdio, - Hooks: map[string]antigravity.HookFunc{"PreInvocation": m.agyBeforeModel(l)}, + Hooks: map[string]hookcmd.Func{"PreInvocation": m.agyBeforeModel(l), "PreToolUse": m.agyBeforeTool(l)}, OnBadLine: func(line []byte, reason string) { m.recordBadLine(l, line, reason) }, } diff --git a/internal/agents/queue.go b/internal/agents/queue.go index 9dbeeed..4858af1 100644 --- a/internal/agents/queue.go +++ b/internal/agents/queue.go @@ -62,7 +62,7 @@ func (m *Manager) Message(ctx context.Context, sessionID, actor, text, model, se if err != nil { return api.AgentSession{}, false, err } - held := sess.State == api.AgentSessionStateIdle && sess.Role == api.Implementer && (automatedActor(actor) && m.interrupted(sess.Id) || landing) + held := sess.State == api.AgentSessionStateIdle && sess.Role == api.Implementer && (automatedActor(actor) && m.interrupted(sess.Id) || landing || m.isPaused(sess.Id)) if !held && (sess.State != api.AgentSessionStateRunning || sess.Role != api.Implementer) { sess, err = m.Send(ctx, sessionID, actor, text, model, sendID, client, images...) return sess, false, err diff --git a/internal/agents/snapshots.go b/internal/agents/snapshots.go index 87e9bc9..7922de9 100644 --- a/internal/agents/snapshots.go +++ b/internal/agents/snapshots.go @@ -10,8 +10,8 @@ import ( "time" "g.fc.run/ai/byre/internal/api" - "g.fc.run/ai/byre/internal/claude" "g.fc.run/ai/byre/internal/git" + "g.fc.run/ai/byre/internal/hookcmd" "g.fc.run/ai/byre/internal/ids" "g.fc.run/ai/byre/internal/mcp" "g.fc.run/ai/byre/internal/store" @@ -282,15 +282,23 @@ func (m *Manager) dropSnapshots(repo, taskID string) error { return git.DeleteSnapshotRefs(context.Background(), repo, taskID) } -func (m *Manager) afterTool(l *live) claude.HookFunc { +func (m *Manager) afterTool(l *live) hookcmd.Func { return func(ctx context.Context, input json.RawMessage) (map[string]any, error) { + in := struct { + HookEventName string `json:"hook_event_name"` + }{} + err := json.Unmarshal(input, &in) + if err != nil { + return nil, err + } + m.holdWhilePaused(ctx, l) mt := m.deliverMidTurn(ctx, l) if mt.text == "" { return map[string]any{}, nil } return map[string]any{ "hookSpecificOutput": map[string]any{ - "hookEventName": "PostToolUse", + "hookEventName": in.HookEventName, "additionalContext": mt.text, }, }, nil diff --git a/internal/antigravity/session.go b/internal/antigravity/session.go index 2dc3dee..13a4a7f 100644 --- a/internal/antigravity/session.go +++ b/internal/antigravity/session.go @@ -21,14 +21,13 @@ import ( "time" "g.fc.run/ai/byre/internal/agentinput" + "g.fc.run/ai/byre/internal/hookcmd" "g.fc.run/ai/byre/internal/mcp" "g.fc.run/ai/byre/internal/procgroup" ) const maxRuleBytes = 24000 -type HookFunc func(ctx context.Context, input json.RawMessage) (map[string]any, error) - type Options struct { Dir string Cwd string @@ -40,7 +39,7 @@ type Options struct { Reminder string MCPServers []*mcp.Server StdioMCP []mcp.Stdio - Hooks map[string]HookFunc + Hooks map[string]hookcmd.Func OnBadLine func(line []byte, reason string) } @@ -167,7 +166,7 @@ func Start(ctx context.Context, opts Options) (*Session, error) { mux := http.NewServeMux() mux.HandleFunc("POST /"+token+"/mcp/{server}", s.serveMCP) - mux.HandleFunc("POST /"+token+"/hook/{event}", s.serveHook) + mux.HandleFunc("POST /"+token+"/hook/{event}", hookcmd.Handler(opts.Hooks)) s.srv = &http.Server{Handler: mux} go s.srv.Serve(ln) @@ -217,11 +216,16 @@ func (s *Session) writePlugin() error { hooks := map[string]any{} for event := range s.opts.Hooks { - hooks[event] = []map[string]any{{ + handlers := []map[string]any{{ "type": "command", - "command": fmt.Sprintf("curl -sS --max-time 25 --data-binary @- %s/hook/%s", s.base, event), - "timeout": 30, + "command": hookcmd.Command(s.base + "/hook/" + event), + "timeout": hookcmd.Timeout, }} + if event == "PreToolUse" || event == "PostToolUse" { + hooks[event] = []map[string]any{{"matcher": "*", "hooks": handlers}} + continue + } + hooks[event] = handlers } files := map[string]any{ @@ -282,27 +286,6 @@ func (s *Session) serveMCP(w http.ResponseWriter, r *http.Request) { w.Write(resp) } -func (s *Session) serveHook(w http.ResponseWriter, r *http.Request) { - event := r.PathValue("event") - fn := s.opts.Hooks[event] - if fn == nil { - http.NotFound(w, r) - return - } - body, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - out, err := fn(r.Context(), body) - if err != nil { - slog.Error("agy hook failed", "event", event, "err", err) - out = map[string]any{} - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(out) -} - func (s *Session) spawnLocked() error { args := []string{ "--input-format", "stream-json", diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 8cb0ee2..7c21f9d 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -694,6 +694,9 @@ type AgentSession struct { // Owner The login whose lead agent this is; empty for a task's session. Owner string `json:"owner"` + // Paused A task agent's pause: mid-turn it holds at its next step (after the tool call or model call in progress finishes); idle, no turn starts and messages queue until resumed. + Paused bool `json:"paused"` + // Role reviewer: a review's session (see Review); a task's sessions list leaves these out. Role AgentSessionRole `json:"role"` State AgentSessionState `json:"state"` @@ -2241,6 +2244,12 @@ type ServerInterface interface { // (POST /api/sessions/{sessionId}/messages) SendMessage(w http.ResponseWriter, r *http.Request, sessionId string) + // (POST /api/sessions/{sessionId}/pause) + PauseSession(w http.ResponseWriter, r *http.Request, sessionId string) + + // (POST /api/sessions/{sessionId}/resume) + ResumeSession(w http.ResponseWriter, r *http.Request, sessionId string) + // (GET /api/tailnet) GetTailnet(w http.ResponseWriter, r *http.Request) @@ -3698,6 +3707,58 @@ func (siw *ServerInterfaceWrapper) SendMessage(w http.ResponseWriter, r *http.Re handler.ServeHTTP(w, r) } +// PauseSession operation middleware +func (siw *ServerInterfaceWrapper) PauseSession(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "sessionId" ------------- + var sessionId string + + err = runtime.BindStyledParameterWithOptions("simple", "sessionId", r.PathValue("sessionId"), &sessionId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "", ValueIsUnescaped: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "sessionId", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.PauseSession(w, r, sessionId) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ResumeSession operation middleware +func (siw *ServerInterfaceWrapper) ResumeSession(w http.ResponseWriter, r *http.Request) { + + var err error + _ = err + + // ------------- Path parameter "sessionId" ------------- + var sessionId string + + err = runtime.BindStyledParameterWithOptions("simple", "sessionId", r.PathValue("sessionId"), &sessionId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "", ValueIsUnescaped: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "sessionId", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ResumeSession(w, r, sessionId) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // GetTailnet operation middleware func (siw *ServerInterfaceWrapper) GetTailnet(w http.ResponseWriter, r *http.Request) { @@ -4980,6 +5041,8 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/sessions/{sessionId}/messages", wrapper.SendMessage) m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/sessions/{sessionId}/compact", wrapper.CompactSession) m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/sessions/{sessionId}/interrupt", wrapper.InterruptSession) + m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/sessions/{sessionId}/pause", wrapper.PauseSession) + m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/sessions/{sessionId}/resume", wrapper.ResumeSession) m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/blobs", wrapper.UploadBlob) m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/blobs/{blobId}", wrapper.GetBlob) m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/inbox", wrapper.ListInbox) @@ -5024,351 +5087,355 @@ var swaggerSpec = []string{ "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", + "/3sA", } // decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, diff --git a/internal/claude/session.go b/internal/claude/session.go index b4df6d9..3e66437 100644 --- a/internal/claude/session.go +++ b/internal/claude/session.go @@ -18,15 +18,14 @@ import ( "time" "g.fc.run/ai/byre/internal/agentinput" + "g.fc.run/ai/byre/internal/hookcmd" "g.fc.run/ai/byre/internal/mcp" "g.fc.run/ai/byre/internal/procgroup" ) -type HookFunc func(ctx context.Context, input json.RawMessage) (map[string]any, error) - type HookMatcher struct { Matcher string - Func HookFunc + Func hookcmd.Func } type Options struct { @@ -207,7 +206,7 @@ type Session struct { inflightMu sync.Mutex inflight map[string]context.CancelFunc - hooks map[string]HookFunc + hooks map[string]hookcmd.Func servers map[string]*mcp.Server idMu sync.Mutex @@ -237,7 +236,7 @@ func Start(ctx context.Context, opts Options) (*Session, error) { done: make(chan struct{}), pending: map[string]chan controlResult{}, inflight: map[string]context.CancelFunc{}, - hooks: map[string]HookFunc{}, + hooks: map[string]hookcmd.Func{}, servers: map[string]*mcp.Server{}, id: initialID(opts), onBadLine: opts.OnBadLine, @@ -386,6 +385,7 @@ func (s *Session) hookConfig(hooks map[string][]HookMatcher) map[string]any { list = append(list, map[string]any{ "matcher": m.Matcher, "hookCallbackIds": []string{id}, + "timeout": hookcmd.Timeout, }) } cfg[event] = list diff --git a/internal/codex/session.go b/internal/codex/session.go index 6e1c6c5..d1f7032 100644 --- a/internal/codex/session.go +++ b/internal/codex/session.go @@ -4,12 +4,15 @@ import ( "bufio" "bytes" "context" + "crypto/rand" "encoding/base64" "encoding/json" "errors" "fmt" "io" "log/slog" + "net" + "net/http" "os" "strconv" "sync" @@ -17,6 +20,7 @@ import ( "time" "g.fc.run/ai/byre/internal/agentinput" + "g.fc.run/ai/byre/internal/hookcmd" "g.fc.run/ai/byre/internal/mcp" "g.fc.run/ai/byre/internal/procgroup" ) @@ -32,6 +36,7 @@ type Options struct { Reminder string MCPServers []*mcp.Server StdioMCP []mcp.Stdio + Hooks map[string]hookcmd.Func OnBadLine func(line []byte, reason string) } @@ -83,6 +88,7 @@ type Session struct { servers map[string]*mcp.Server reminder string + hookSrv *http.Server onBadLine func(line []byte, reason string) lastLine atomic.Int64 @@ -94,7 +100,10 @@ func Start(ctx context.Context, opts Options) (*Session, error) { return nil, err } - err = s.open(ctx, opts) + config, err := s.serveHooks(opts) + if err == nil { + err = s.open(ctx, opts, config) + } if err != nil { s.Close() return nil, err @@ -102,6 +111,38 @@ func Start(ctx context.Context, opts Options) (*Session, error) { return s, nil } +func (s *Session) serveHooks(opts Options) (map[string]any, error) { + config := map[string]any{} + if len(opts.StdioMCP) > 0 { + config["mcp_servers"] = stdioConfig(opts.StdioMCP) + } + if len(opts.Hooks) == 0 { + return config, nil + } + + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return nil, err + } + token := rand.Text() + mux := http.NewServeMux() + mux.HandleFunc("POST /"+token+"/hook/{event}", hookcmd.Handler(opts.Hooks)) + s.hookSrv = &http.Server{Handler: mux} + go s.hookSrv.Serve(ln) + + hooks := map[string]any{} + for event := range opts.Hooks { + hooks[event] = []map[string]any{{"hooks": []map[string]any{{ + "type": "command", + "command": hookcmd.Command("http://" + ln.Addr().String() + "/" + token + "/hook/" + event), + "timeout": hookcmd.Timeout, + }}}} + } + config["hooks"] = hooks + config["bypass_hook_trust"] = true + return config, nil +} + func ReadRateLimits(ctx context.Context, cwd string, env []string) (RateLimitSnapshot, error) { s, err := spawn(Options{Cwd: cwd, Env: env}) if err != nil { @@ -177,7 +218,7 @@ func newSession(opts Options, stdin *os.File, stdout io.Reader) *Session { return s } -func (s *Session) open(ctx context.Context, opts Options) error { +func (s *Session) open(ctx context.Context, opts Options, config map[string]any) error { err := s.initialize(ctx) if err != nil { return err @@ -192,7 +233,7 @@ func (s *Session) open(ctx context.Context, opts Options) error { ApprovalPolicy: json.RawMessage(`"never"`), Sandbox: SandboxModeDangerFullAccess, DeveloperInstructions: opts.Guidance, - Config: stdioConfig(opts.StdioMCP), + Config: config, }, &resp) if err != nil { return fmt.Errorf("resume thread: %w", err) @@ -211,7 +252,7 @@ func (s *Session) open(ctx context.Context, opts Options) error { ApprovalPolicy: json.RawMessage(`"never"`), Sandbox: SandboxModeDangerFullAccess, DeveloperInstructions: opts.Guidance, - Config: stdioConfig(opts.StdioMCP), + Config: config, }, &resp) if err != nil { return fmt.Errorf("fork thread: %w", err) @@ -228,7 +269,7 @@ func (s *Session) open(ctx context.Context, opts Options) error { Sandbox: SandboxModeDangerFullAccess, DeveloperInstructions: opts.Guidance, DynamicTools: dynamicTools(opts.MCPServers), - Config: stdioConfig(opts.StdioMCP), + Config: config, }, &resp) if err != nil { return fmt.Errorf("start thread: %w", err) @@ -251,14 +292,11 @@ func (s *Session) initialize(ctx context.Context) error { } func stdioConfig(servers []mcp.Stdio) map[string]any { - if len(servers) == 0 { - return nil - } cfg := map[string]any{} for _, srv := range servers { cfg[srv.Name] = map[string]any{"command": srv.Command, "args": srv.Args} } - return map[string]any{"mcp_servers": cfg} + return cfg } func dynamicTools(servers []*mcp.Server) []DynamicToolSpec { @@ -466,7 +504,14 @@ func (s *Session) notify(method string, params any) error { return s.write(m, time.Now().Add(rpcTimeout)) } +func (s *Session) closeHooks() { + if s.hookSrv != nil { + s.hookSrv.Close() + } +} + func (s *Session) Close() error { + defer s.closeHooks() s.writeLock <- struct{}{} s.stdin.Close() <-s.writeLock @@ -482,6 +527,7 @@ func (s *Session) Close() error { } func (s *Session) Kill() { + defer s.closeHooks() s.cmd.Kill() <-s.done s.cmd.Wait() diff --git a/internal/hookcmd/hookcmd.go b/internal/hookcmd/hookcmd.go new file mode 100644 index 0000000..f2978ab --- /dev/null +++ b/internal/hookcmd/hookcmd.go @@ -0,0 +1,40 @@ +package hookcmd + +import ( + "context" + "encoding/json" + "io" + "log/slog" + "net/http" +) + +const Timeout = 7 * 24 * 60 * 60 + +type Func func(ctx context.Context, input json.RawMessage) (map[string]any, error) + +func Command(url string) string { + return "curl -sS --data-binary @- " + url +} + +func Handler(hooks map[string]Func) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + event := r.PathValue("event") + fn := hooks[event] + if fn == nil { + http.NotFound(w, r) + return + } + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + out, err := fn(r.Context(), body) + if err != nil { + slog.Error("hook failed", "event", event, "err", err) + out = map[string]any{} + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(out) + } +} diff --git a/internal/server/server.go b/internal/server/server.go index 5cfea39..e4fade9 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -1034,6 +1034,24 @@ func (s *Server) InterruptSession(w http.ResponseWriter, r *http.Request, sessio writeJSON(w, http.StatusAccepted, sess) } +func (s *Server) PauseSession(w http.ResponseWriter, r *http.Request, sessionID string) { + sess, err := s.agents.Pause(r.Context(), sessionID, actor(r)) + if err != nil { + writeAgentError(w, err) + return + } + writeJSON(w, http.StatusOK, sess) +} + +func (s *Server) ResumeSession(w http.ResponseWriter, r *http.Request, sessionID string) { + sess, err := s.agents.Resume(r.Context(), sessionID, actor(r)) + if err != nil { + writeAgentError(w, err) + return + } + writeJSON(w, http.StatusOK, sess) +} + func writeAgentError(w http.ResponseWriter, err error) { switch { case errors.Is(err, store.ErrNotFound): diff --git a/internal/store/migrations/104-add-session-paused.sql b/internal/store/migrations/104-add-session-paused.sql new file mode 100644 index 0000000..f456fdf --- /dev/null +++ b/internal/store/migrations/104-add-session-paused.sql @@ -0,0 +1 @@ +ALTER TABLE agent_sessions ADD COLUMN paused INTEGER NOT NULL DEFAULT 0; diff --git a/internal/store/store.go b/internal/store/store.go index 73f89cc..567be16 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -1376,6 +1376,32 @@ func (s *Store) SessionsWithSubagents(ctx context.Context) ([]api.AgentSession, return s.querySessions(ctx, `SELECT `+sessionColumns+` FROM agent_sessions WHERE subagents > 0`) } +func (s *Store) PausedSessions(ctx context.Context) ([]api.AgentSession, error) { + return s.querySessions(ctx, `SELECT `+sessionColumns+` FROM agent_sessions WHERE paused AND state != ?`, string(api.AgentSessionStateCompleted)) +} + +func (s *Store) SetSessionPaused(ctx context.Context, id string, paused bool, actor string) error { + return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error { + taskID := "" + projectID := "" + owner := "" + err := tx.QueryRowContext(ctx, ` + SELECT coalesce(t.id, ''), coalesce(t.project_id, ''), s.owner FROM agent_sessions s LEFT JOIN tasks t ON t.id = s.task_id WHERE s.id = ? + `, id).Scan(&taskID, &projectID, &owner) + if errors.Is(err, sql.ErrNoRows) { + return ErrNotFound + } + if err != nil { + return err + } + _, err = tx.ExecContext(ctx, `UPDATE agent_sessions SET paused = ? WHERE id = ?`, paused, id) + if err != nil { + return err + } + return emit(sessionEvent("session.paused", projectID, taskID, owner, id, actor, map[string]any{"paused": paused})) + }) +} + func (s *Store) SetSubagents(ctx context.Context, id string, n int, actor string) error { return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error { taskID := "" @@ -1701,7 +1727,7 @@ const backgroundExpr = `(audit != '' OR EXISTS (SELECT 1 FROM tasks r WHERE r.id AND (kind != 'task.discard_suggested' OR (kind = 'task.discard_suggested' AND NOT EXISTS (SELECT 1 FROM tasks r JOIN workflow_definitions d ON d.id = r.workflow_definition_id WHERE r.id IN (tasks.id, tasks.parent_task_id) AND d.auto_discard))))` -const sessionColumns = `id, coalesce(task_id, ''), owner, vendor, model, vendor_session_id, state, role, member, 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, paused` type scanner interface { Scan(dest ...any) error @@ -1809,7 +1835,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.Member, &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, &sess.Paused) if err != nil { return sess, err } diff --git a/mac/Sources/Byre/Chat.swift b/mac/Sources/Byre/Chat.swift index b2b48c3..2e5b108 100644 --- a/mac/Sources/Byre/Chat.swift +++ b/mac/Sources/Byre/Chat.swift @@ -174,6 +174,10 @@ final class ChatModel { builder.open && session?.state == .running } + var paused: Bool { + liveSession?.paused ?? false + } + var reviewing: Bool { let state = taskId.flatMap { store.findTask($0) }?.state return state == .reviewing || state == .reviewPending || state == .mergeAfterReview @@ -559,6 +563,20 @@ final class ChatModel { } } } + + func setPaused(_ sessionId: String, _ paused: Bool, _ overlays: Overlays) { + Task { + do { + if paused { + _ = try await store.api.client.pauseSession(path: .init(sessionId: sessionId)).ok + } else { + _ = try await store.api.client.resumeSession(path: .init(sessionId: sessionId)).ok + } + } catch { + overlays.showAlert(errorText(error)) + } + } + } } struct ChatView: View { @@ -825,6 +843,24 @@ private struct ScrollMetrics: Equatable { let offset: CGFloat } +private struct PausedLine: View { + let running: Bool + + var body: some View { + HStack(spacing: 6) { + Image(systemName: "pause.fill") + .font(.system(size: 10)) + Text(running ? "Paused: finishes its current step, then holds until you resume" : "Paused: no turn starts until you resume") + .font(Fonts.font(13)) + } + .foregroundStyle(Palette.muted) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(Palette.surface, in: RoundedRectangle(cornerRadius: 6)) + .overlay(RoundedRectangle(cornerRadius: 6).stroke(Palette.border)) + } +} + private struct TranscriptScroll: View { let model: ChatModel @Environment(Store.self) private var store @@ -855,6 +891,10 @@ private struct TranscriptScroll: View { RowView(row: row, model: model, nested: false) .padding(.top, 12) } + if model.paused { + PausedLine(running: running) + .padding(.top, 12) + } ForEach(model.builder.pending) { p in PendingView(pending: p, model: model) .padding(.top, 12) diff --git a/mac/Sources/Byre/Composer.swift b/mac/Sources/Byre/Composer.swift index 5bb4288..2b08314 100644 --- a/mac/Sources/Byre/Composer.swift +++ b/mac/Sources/Byre/Composer.swift @@ -527,7 +527,7 @@ struct ComposerView: View { controls(m) .frame(maxWidth: .infinity, alignment: .leading) status - actions(live?.id, running: sessionState == .running) + actions(live?.id, running: sessionState == .running, paused: live?.paused ?? false) .frame(maxWidth: .infinity, alignment: .trailing) } } @@ -622,7 +622,7 @@ struct ComposerView: View { } } - private func actions(_ sessionId: String?, running: Bool) -> some View { + private func actions(_ sessionId: String?, running: Bool, paused: Bool) -> some View { HStack(spacing: 8) { Button("Interrupt") { if let sessionId { @@ -632,6 +632,19 @@ struct ComposerView: View { .buttonStyle(WebButtonStyle(danger: true)) .disabled(!running) .fixedSize() + if case .task = chat.kind, !chat.team, !chat.closed { + Button { + if let sessionId { + chat.setPaused(sessionId, !paused, overlays) + } + } label: { + Image(systemName: paused ? "play.fill" : "pause.fill") + .frame(width: 14, height: 14) + } + .buttonStyle(WebButtonStyle()) + .disabled(sessionId == nil) + .help(paused ? "Resume the agent" : "Pause the agent after its current step") + } Button(chat.closed ? "Continue" : "Send") { submit(modelState()) } diff --git a/mac/Sources/Byre/Store.swift b/mac/Sources/Byre/Store.swift index 59266dc..5d56a2a 100644 --- a/mac/Sources/Byre/Store.swift +++ b/mac/Sources/Byre/Store.swift @@ -858,7 +858,7 @@ final class Store { } notifyTask(self, ev) reloadTask(ev) - case "session.created", "session.state", "session.subagents", "session.model": + case "session.created", "session.state", "session.subagents", "session.model", "session.paused": if ev.owner != nil { reloadSessions(nil) } else if let taskId = ev.taskId {