fix(web): dedupe background subagent rows in the agents dock - #1754
Conversation
🦋 Changeset detectedLatest commit: 6efe145 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8a0986f9c2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| outputPreview: live.outputPreview ?? rest.outputPreview, | ||
| outputBytes: live.outputBytes ?? rest.outputBytes, |
There was a problem hiding this comment.
Preserve REST output updates when folding subagents
When a background subagent has been folded once, the live row can already have outputPreview/outputBytes from the first REST poll. Later polls build a fresh REST row with newer tail or final output, but this merge keeps the old live values whenever they are set, so the agent detail Result stops updating after the first folded poll and may never show the final output. Prefer the folded REST output when it is present (or compare bytes/status) while still preserving WS-only fields.
Useful? React with 👍 / 👎.
| id: taskId, | ||
| sessionId, | ||
| kind: 'subagent', | ||
| description, |
There was a problem hiding this comment.
Link late task registrations to the agent id
In the missed-subagent.spawned path, when task.started includes an agentId, this creates only a row keyed by the background task id and does not seed subagentMeta/backgroundTaskId for the agent id. The next agent-scoped progress frame is routed by agentId into patchSubagent, creating a second subagent row keyed by the agent id, so late subscribers can still see duplicate dock rows with split progress. Key this fallback by agentId when available and set backgroundTaskId, or otherwise seed both identities.
Useful? React with 👍 / 👎.
…ST output in task fold
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4ed2a28f43
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const rest = restBased.filter((t) => !foldedRestIds.has(t.id)); | ||
| return [...rest, ...merged]; |
There was a problem hiding this comment.
Preserve terminal output after folding REST rows
When loadTasksForSession folds a REST background-subagent row into the live agent-id row, the one-off terminal-output backfill still records fetched output by the REST task id and then updates existing rows by t.id. Because this code removes the REST-id row here, a completed background subagent that is loaded/refreshed with an existing live row can end up with status folded correctly but no final Result/output attached, and the fetched id is marked as already fetched so later backfills skip it.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8828d8a662
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| ...live, | ||
| // Terminal-stickiness: never let a lagging poll flip a finished row back | ||
| // to running, but let REST complete a row whose finish event was missed. | ||
| status: live.status === 'running' ? rest.status : live.status, |
There was a problem hiding this comment.
Update the phase when REST folds a terminal subagent
When a background subagent misses its subagent.completed/subagent.failed event but the REST poll returns a terminal status, this fold updates status while leaving the live row's stale subagentPhase (usually working or queued). The detail panel path (toAgentMember) prefers task.subagentPhase over terminal status, so clicking a completed dock row can still show it as Working/Suspended; set or clear the phase when the fold makes the row terminal.
Useful? React with 👍 / 👎.
|
Codex Review: Didn't find any major issues. Swish! Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
Related Issue
No linked issue — the problem is explained below.
Problem
During streaming, while a background subagent (
Agenttool withrun_in_background: true) is running, opening the agents panel in the bottom dock shows two identical rows for the same agent.Root cause: the server gives a background subagent two different ids — the agent id (delivered by the WS
subagent.spawnedevent) and a fresh task id in the main agent's background-task store (served by REST/tasks, and broadcast in thetask.startedregistration event). The web client kept one row per id: the WS projector added a row keyed by agent id, then the 1-second REST poll (active while tasks run, i.e. during streaming) returned the same agent keyed by task id. The merge only dedupes by exact id, so both rows survived — and both pass the dock'skind === 'subagent' && runInBackgroundfilter. The snapshot-roster path already avoids this duplication (it deliberately excludes background subagents); the live WS + poll path had no equivalent reconciliation.What changed
task.startedevent forkind: 'agent'carries both ids. The projector now records the task id on the existing WS-owned row asbackgroundTaskIdinstead of projecting a second row (which was also mis-typed askind: 'bash').keepLiveSubagents) folds a REST row whose id matches a live row'sbackgroundTaskIdinto that row. REST can still correct a terminal status the WS row missed, but a lagging poll never flips a finished row back to running.backgroundTaskIdfirst, so the stop button keeps targeting the id REST actually knows.Tests: projector fold + late-subscribe fallback + unchanged process-task behavior; merge fold + terminal-stickiness cases. Full kimi-web suite (534 tests) and
vue-tscpass.Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.