Repository navigation
fix(tui): show real terminal status for background agents #197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
5337ccd
d9849cc
a80e8f7
22289b4
4057e7b
e0e190b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@moonshot-ai/kimi-code": patch | ||
| --- | ||
|
|
||
| Show the real terminal status of background agents in the transcript so lost, failed, and killed ones no longer appear as completed. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,6 +66,7 @@ export class SessionReplayRenderer { | |
|
|
||
| this.hydrateSnapshot(main); | ||
| this.renderRecords(main); | ||
| this.applyTerminalBackgroundAgentStatuses(main); | ||
| return true; | ||
| } catch (error) { | ||
| const message = formatErrorMessage(error); | ||
|
|
@@ -104,6 +105,36 @@ export class SessionReplayRenderer { | |
| this.host.streamingUI.setTodoList(todos); | ||
| } | ||
|
|
||
| /** | ||
| * Push real terminal status into each replayed `Agent` card whose | ||
| * backing background task is already in a terminal state. Runs AFTER | ||
| * `renderRecords` because the tool call components only exist once the | ||
| * replay has mounted them — `hydrateBackgroundState` runs too early to | ||
| * reach them. Without this, terminated bg agents (including ones that | ||
| * reconcile reclassified as `lost`) keep the spawn-success ToolResult's | ||
| * default of `✓ Completed`. | ||
| */ | ||
| private applyTerminalBackgroundAgentStatuses(agent: ResumedAgentState): void { | ||
| for (const info of agent.background) { | ||
| if (!info.taskId.startsWith('agent-')) continue; | ||
| if (!isTerminalBackgroundTask(info)) continue; | ||
| const status = info.status; | ||
| if ( | ||
| status !== 'completed' && | ||
| status !== 'failed' && | ||
| status !== 'killed' && | ||
| status !== 'lost' | ||
| ) { | ||
| continue; | ||
| } | ||
| this.host.streamingUI.applyBackgroundTaskTerminalStatus({ | ||
| agentId: info.agentId, | ||
| description: info.description, | ||
|
Comment on lines
+130
to
+132
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On resume this loops over every persisted terminal background task, but Useful? React with 👍 / 👎. |
||
| status, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| private hydrateBackgroundState(agent: ResumedAgentState): void { | ||
| const { state, sessionEventHandler } = this.host; | ||
| const projection = replayBackgroundProjection(agent.background); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -174,6 +174,74 @@ export class StreamingUIController { | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Push the actual terminal status of a background agent task into the | ||
| * matching `Agent` tool call component so its snapshot phase no longer | ||
| * trusts the spawn-success ToolResult (which would otherwise label every | ||
| * terminated bg agent — including `lost` ones — as `✓ Completed`). | ||
| * | ||
| * Resolution order, picked to handle both live and resume: | ||
| * 1. `agentId` → matches the subagent id recorded on `spawned` | ||
| * (live path; replayed tool calls currently do not carry a | ||
| * subagent id back, so this branch only catches live mismatches). | ||
| * 2. Description fallback — required on resume because reconcile's | ||
| * `BackgroundTaskInfo` is the only handle the TUI has to a | ||
| * replayed Agent card. Only used when exactly one Agent tool call | ||
| * shares the description; ambiguous matches are skipped to avoid | ||
| * corrupting an unrelated card. | ||
| * | ||
| * Search scope includes both in-flight components and already-mounted | ||
| * cards (some live in `transcriptContainer` standalone, others are | ||
| * borrowed by an `AgentGroupComponent` and reachable only via | ||
| * `getToolComponents()`). | ||
| * | ||
| * Returns true iff a component was found and updated. | ||
| */ | ||
| applyBackgroundTaskTerminalStatus(args: { | ||
| agentId?: string | undefined; | ||
| description: string; | ||
| status: 'completed' | 'failed' | 'killed' | 'lost'; | ||
| }): boolean { | ||
| let agentIdMatch: ToolCallComponent | undefined; | ||
| let descMatch: ToolCallComponent | undefined; | ||
| let descAmbiguous = false; | ||
| const visit = (tc: ToolCallComponent): void => { | ||
| if (agentIdMatch !== undefined) return; | ||
| if (args.agentId !== undefined && tc.getSubagentAgentId() === args.agentId) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When two live background Useful? React with 👍 / 👎. |
||
| agentIdMatch = tc; | ||
| return; | ||
| } | ||
| if (tc.getAgentToolDescription() !== args.description) return; | ||
| if (descMatch !== undefined) { | ||
| descAmbiguous = true; | ||
| return; | ||
| } | ||
| descMatch = tc; | ||
| }; | ||
|
|
||
| for (const tc of this._pendingToolComponents.values()) { | ||
| visit(tc); | ||
| if (agentIdMatch !== undefined) break; | ||
| } | ||
| if (agentIdMatch === undefined) { | ||
| for (const child of this.host.state.transcriptContainer.children) { | ||
| if (child instanceof ToolCallComponent) { | ||
| visit(child); | ||
|
Comment on lines
+246
to
+248
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a background Agent terminates before its spawn-success Useful? React with 👍 / 👎. |
||
| } else if (child instanceof AgentGroupComponent) { | ||
| for (const tc of child.getToolComponents()) { | ||
| visit(tc); | ||
| if (agentIdMatch !== undefined) break; | ||
| } | ||
| } | ||
| if (agentIdMatch !== undefined) break; | ||
| } | ||
| } | ||
| const target = agentIdMatch ?? (descAmbiguous ? undefined : descMatch); | ||
| if (target === undefined) return false; | ||
| target.setBackgroundTaskTerminalStatus(args.status); | ||
| return true; | ||
| } | ||
|
|
||
| /** Registers a tool call that arrived via tool.call.started. | ||
| * Clears any pending streaming state for this id, updates or creates the | ||
| * component, and returns whether the call was new (no previous entry). */ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the matched Agent tool call is still a standalone
ToolCallComponent(for example, a replay with only one background Agent call in the step, so it was never upgraded toAgentGroupComponent), this method only updates the snapshot listener state. The standalone render path usesgetDerivedSubagentPhase(), which still derivesdonefrom the non-error spawn-successToolResult, and no header/content rebuild is requested here, so the visible card continues to showCompletedforlost/failed/killedtasks. The new status override only affects grouped Agent cards that consumegetSubagentSnapshot().Useful? React with 👍 / 👎.