Repository navigation
feat(protocol,gui-app): durable background cards for backgrounded commands - #918
Conversation
Summary by CodeRabbit
WalkthroughBackground command state now flows through runtime events, persistence, chat state, activity grouping, and command rendering. Detached command completions update their owning settled assistant message. ChangesBackground command lifecycle
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant AgentRuntime
participant RuntimeAccumulator
participant ChatSessionStore
participant ActivityGroups
participant CommandSegment
AgentRuntime->>RuntimeAccumulator: emit command.started or command.completed
RuntimeAccumulator->>ChatSessionStore: persist command metadata and ownership
ChatSessionStore->>ActivityGroups: provide rendered command segment
ActivityGroups->>CommandSegment: promote and render background or stopped state
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
|
Pairs with the internal-repo host PR: traycerai/traycer-internal#4788 (Codex background work — helper cards, background panel, and backgrounded commands). That PR's gitlink currently pins this branch head and will be re-bumped to the merge commit once this lands, so this one should merge first. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 38289bb062
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
38289bb to
fe06702
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fe067025c9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…mands
Codex decides on its own to yield a long-running exec to a background
terminal, and keeps it alive past the turn that started it - through the
next turn, and through everything short of the app-server dying. Until
now a `command` block had no way to say so: it was folded into the
activity group like any other command, force-finalized when its turn
ended, and its real terminal - which can land minutes later - arrived to
a card that had already claimed an ending.
`commandBlockSchema` gains the same two markers `toolCallBlockSchema`
already carries, with the same semantics:
- `backgroundTask` (nullable, defaulted false): sticky, survives every
terminal path and reload, so the GUI keeps the card standalone for
the whole lifecycle instead of collapsing it back into the group.
- `stopped`: the terminal outcome was an explicit stop, not a failure.
The provider reports its own kill as a synthetic exit code, and
rendering that as an error blames the command for something the host
did.
`command.started` gains an optional `backgroundTask`, and the
accumulator now UPSERTS on it rather than blindly appending - a harness
that learns about backgrounding only after the card is open (Codex
decides at the parent turn's end) re-emits the event to stamp the
marker, and appending would duplicate the card. The open block's
`timestamp` (its elapsed anchor) and `status` are left alone.
`command.completed` gains `terminationReason` and `backgroundTask`,
mirroring `tool_call.errored`/`tool_call.completed`.
`streamingDetachedBlockIds` now treats a background-marked `command`
like a background-marked `tool_call`, so a clean `turn.completed` leaves
it streaming instead of force-finalizing it - the block waits for its
own terminal, which is the only frame that knows how it ended.
GUI: `shouldPromoteCommandSegment` mirrors the tool-call promotion gate
(durable marker, plus the transient host `backgroundItems` set for a
card whose marker hasn't arrived yet), and `CommandSegment` grows a real
card variant - elapsed heartbeat as the collapsed preview, a "Stopped"
badge in place of the exit code when the host stopped it.
Deliberately NOT included: a per-item `stoppable` flag on the wire.
`chat.subscribe` is frozen through 1.5, so adding a field to
`backgroundItemBaseFields` is a breaking host->client change at every
released version; it needs a new minor with frozen copies, not an
additive default.
Signed-off-by: Amiteshwar Randhawa <amiteshwar04@gmail.com>
…rminal arrives A codex backgrounded command reports its real ending minutes after its row settled - the host routes that `command.completed` to the old row and persists it, but the renderer's detached-owner router only recognized `subagent.*` and terminal `tool_call.*` events (Claude's background shapes). A parentless `command.completed` returned null, fell through toward the active turn, and was dropped: the card's timer ticked forever and only corrected on the next send, when the store re-derived from the host snapshot (live-repro on a 20s sleep that ticked past 2 minutes). Route `command.completed` exactly like a terminal `tool_call.*`: to its `parentBlockId` when it is a subagent child, otherwise to its own block's settled row, mandatory when the terminal carries the background marker so it can never mint a duplicate card on an unrelated live turn. Signed-off-by: Amiteshwar Randhawa <amiteshwar04@gmail.com>
…mand fields, and name only abnormal endings The frozen `chat.subscribe@1.0-1.3` runtime-event unions bind the live `command.*` schemas directly, so `backgroundTask` and `terminationReason` leaked onto lines that shipped without them. Bind hand-frozen copies there instead, and note on `runtimeEventSchemaV12` that it is the live union's base - not the union `subscribe.ts` actually pins to 1.2 - so the next per-event freeze lands in the right copy. `terminationReason` also drops its `"error"` default and becomes optional: it exists only when the ending was abnormal. A clean exit carries no reason at all, which is also what an emitter that predates the field sends - so absence reads the same either way, instead of every clean command claiming an error the renderer has to ignore. Signed-off-by: Amiteshwar Randhawa <amiteshwar04@gmail.com>
…nt fields Rebase fallout: the fixture predates `backgroundTask`/`stopped` becoming part of `CommandSegment`, so it stopped type-checking against the merged type. Signed-off-by: Amiteshwar Randhawa <amiteshwar04@gmail.com>
cf2b139 to
3f1e876
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3f1e8766d1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
protocol/src/host/agent/gui/agent-runtime-accumulator.ts (1)
1508-1541: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winPropagate
terminationReason: "error"to the command UI.When
exitCodeis absent or zero, this event producesstatus: "completed"andstopped: false. The GUI derives command failure only from a non-zeroexitCode, so it renders this genuine failure as a normal completion. Propagate an explicit failure signal and makeCommandSegmentconsume it; setting only the block status is insufficient because the projection does not pass that status through.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@protocol/src/host/agent/gui/agent-runtime-accumulator.ts` around lines 1508 - 1541, Update the command completion handling in the command.completed case to propagate terminationReason: "error" as an explicit failure signal on both updated and newly created command blocks, including when exitCode is absent or zero. Then update CommandSegment to consume this signal when deriving the rendered command result, rather than relying only on a non-zero exitCode; preserve normal completion and stopped behavior for other termination reasons.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@protocol/src/host/agent/gui/agent-runtime-accumulator.ts`:
- Around line 1508-1541: Update the command completion handling in the
command.completed case to propagate terminationReason: "error" as an explicit
failure signal on both updated and newly created command blocks, including when
exitCode is absent or zero. Then update CommandSegment to consume this signal
when deriving the rendered command result, rather than relying only on a
non-zero exitCode; preserve normal completion and stopped behavior for other
termination reasons.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 68792861-9adf-4247-a4f4-563219ecb230
📒 Files selected for processing (19)
clients/gui-app/src/components/chat/__tests__/chat-activity-groups.test.tsclients/gui-app/src/components/chat/__tests__/chat-find-projection.test.tsclients/gui-app/src/components/chat/__tests__/chat-message-fixtures.tsclients/gui-app/src/components/chat/__tests__/chat-messages.test.tsxclients/gui-app/src/components/chat/chat-activity-groups.tsclients/gui-app/src/components/chat/chat-message-assistant-body.tsxclients/gui-app/src/components/chat/segments/__tests__/activity-group-segment.test.tsxclients/gui-app/src/components/chat/segments/activity-group-segment.tsxclients/gui-app/src/components/chat/segments/command-segment.tsxclients/gui-app/src/stores/chats/__tests__/chat-session-store.test.tsclients/gui-app/src/stores/chats/__tests__/rendered-messages.test.tsxclients/gui-app/src/stores/chats/chat-session-store.tsclients/gui-app/src/stores/chats/rendered-messages.tsclients/gui-app/src/stores/composer/chat-store.tsprotocol/src/host/agent/gui/__tests__/agent-runtime-accumulator.test.tsprotocol/src/host/agent/gui/agent-runtime-accumulator.tsprotocol/src/host/agent/gui/agent-runtime.tsprotocol/src/persistence/epic/__tests__/__fixtures__/epic-schema-surface.tsprotocol/src/persistence/epic/content-blocks.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7b0a787d4c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
What / Why
Codex decides on its own to yield a long-running exec to a background terminal, and keeps it alive past the turn that started it — through the next turn, and through everything short of the app-server dying. The real terminal can land minutes later. Until now a
commandblock had no way to say any of that: it was folded into the activity group like any other command, force-finalized when its turn ended, and its terminal arrived to a card that had already claimed an ending.This adds the wire markers and the GUI rendering for a backgrounded command's own durable card, and fixes the renderer routing that dropped its late terminal.
Protocol
commandBlockSchemagains the same two markerstoolCallBlockSchemaalready carries, with the same semantics:backgroundTask(nullable, defaults false) — sticky: survives every terminal path and reload, so the card stays standalone for the whole lifecycle instead of collapsing back into the activity group.stopped— the terminal outcome was an explicit stop, not a failure. The provider reports its own kill as a synthetic exit code, and rendering that as an error blames the command for something the host did.command.startedgains an optionalbackgroundTask, and the accumulator now upserts on it rather than blindly appending — a harness that learns about backgrounding only after the card is open (Codex decides at the parent turn's end) re-emits the event to stamp the marker, and appending would duplicate the card. The open block'stimestamp(its elapsed anchor) andstatusare left alone.command.completedgainsterminationReasonandbackgroundTask, mirroringtool_call.errored/tool_call.completed.streamingDetachedBlockIdsnow treats a background-markedcommandlike a background-markedtool_call, so a cleanturn.completedleaves it streaming instead of force-finalizing it — the block waits for its own terminal, which is the only frame that knows how it ended.GUI
shouldPromoteCommandSegmentmirrors the tool-call promotion gate (durable marker, plus the transient hostbackgroundItemsset for a card whose marker hasn't arrived yet).CommandSegmentgrows a real card variant — elapsed heartbeat as the collapsed preview, a "Stopped" badge in place of the exit code when the host stopped it.subagent.*and terminaltool_call.*events (Claude's background shapes). A parentlesscommand.completedreturned null, fell through toward the active turn, and was dropped — the card's timer ticked forever and only corrected on the next send, when the store re-derived from the host snapshot (live-repro on a 20s sleep that ticked past 2 minutes).command.completedis now routed exactly like a terminaltool_call.*: to itsparentBlockIdwhen it is a subagent child, otherwise to its own block's settled row, and mandatory when the terminal carries the background marker so it can never mint a duplicate card on an unrelated live turn.Deliberately not included
A per-item
stoppableflag on the wire.chat.subscribeis frozen through 1.5, so adding a field tobackgroundItemBaseFieldsis a breaking host→client change at every released version; it needs a new minor with frozen copies, not an additive default. Consequence today: on a Codex build below the stop-API floor the host lists the command but declines the stop, and the GUI cannot yet hide the button for it.Pairs with
This is the client half of the Codex background-work feature; the host half lives in an internal-repo PR (linked in a follow-up comment). Both are needed for the cards to appear.
🤖 Generated with Claude Code