fix(chat): show why a settled subagent failed in its detail panel - #192
Conversation
A delegate that ended failed, timed_out, or aborted showed only an outcome
capsule and an elapsed time. The reason existed but had no reader: the runtime
reports `error: { code, message }` on the delegation roster entry, while a node
and its dock render the `Task` row's own result, which ADR 0089 fixes at
`running` the moment the delegate starts. A delegate that died before emitting
a message row therefore left a panel of steps and no explanation (issue vastsa#161).
Collect the failure from the lifecycle rows' `details.delegations[]` and
`details.stopped[]` with the same last-write-wins rule as the settled status,
and close the dock with an error card that reuses the transcript error card's
visual language and its `errors.<code>` vocabulary, falling back to the
localized `chat.subagentStatus.*` outcome when a code is not registered.
The card follows a non-success terminal outcome rather than the error field
alone, so a completed or still-running delegate never shows one. Its disclosure
control sits in the heading so it stays reachable while the details are
collapsed, and a code-only error stays a one-line card instead of opening onto
an empty box.
No runtime, IPC, storage, or tool-result change — the runtime already reported
the error and this stops discarding it — and no new i18n keys.
Verified: node --test test/subagent-topology.test.mjs test/subagent-panel.test.mjs
(23 passed, 0 failed).
Synchronize the surfaces that describe a settled delegate: - spec 04-ux 08-component-spec §5.7 gains the failure card, its summary fallback, and the non-success terminal-outcome gate. - E2E-198 gains step 6 and the matching expectation, and its status records the new unit coverage. - The decisions log records D381, and the zh-CN mirror of the decisions log carries the same entry. No behavior change beyond the code commit.
|
@Yi-111-a is attempting to deploy a commit to the vastsa's projects Team on Vercel. A member of the Team first needs to authorize it. |
D381 is now taken by the selectable Windows shell change (vastsa#191), so this entry moves to D382 and the two PRs stay distinct. Both decisions logs follow.
Keep D381 (PowerShell 7 catalog) and D382 (settled-subagent failure card) as consecutive decisions-log entries after vastsa#191 landed on main.
collectDelegationFailures skipped Task rows the same way collectDelegationStatuses does, but read item.message after the predicate. That guard narrows a tool item to never, so typecheck failed. Pull the message off first, matching the existing collector.
vastsa
left a comment
There was a problem hiding this comment.
Principle
Sound. Merging this PR.
Issue #161 is real on current main: a delegate that ends failed / timed_out / aborted shows a Failed capsule and a step stream, and nothing else. SubagentRunResult.error already travels on the lifecycle roster (delegationSummary spreads record.result?.error), but the dock renders the Task row, which ADR 0089 pins at running. This is a presentation gap, not a runtime/IPC/storage change.
The approach is the right one:
- Collect
error: { code, message }from the same lifecycle rows, with the same last-write-wins rule, ascollectDelegationStatuses. - Gate the card on a non-success terminal outcome, not on the error field alone.
- Reuse the transcript error card (summary, code, disclosure + copy) and existing i18n keys.
- Topology node and sidebar share
SubagentDetailvia the dock, so both entry points get the card.
Landing
main had moved (#191 / D381), so the decisions logs conflicted. I merged main into this branch (keep D381 then D382) and copied the tool-row message before the isDelegationActivityItem guard — the same never narrowing that collectDelegationStatuses already documents. Typecheck now passes.
Validation
node --test test/subagent-topology.test.mjs test/subagent-panel.test.mjs— 23 passednode scripts/check-style-tokens.mjs— OK- CI after the landing commits: Docs locale pair, JS typecheck/lint/test, Rust host-core — pass. Vercel is unrelated deploy-auth.
Follow-up (not merge blockers)
- Optional issue ask (failed step/tool name) is still out of scope.
- zh-CN is missing the pre-existing §5.7 / E2E-198 sections; correctly not invented here.
- A reconstructed leftover
abortedwith no rostererrorstill has no card.
Thank you — clean diagnosis and a small, matching collector.
|
Merged as |
Summary
When a delegate ends
failed,timed_out, oraborted, its detail panel shows a step stream that just stops. The status capsule says Failed, the elapsed time is there, and nothing says why. A delegate that dies before emitting any message row has no other carrier for its reason, so the panel is a list of steps and no explanation.Reported as #161.
This renders the reason as an error card at the foot of the dock.
Why the reason was missing
The reason already exists at runtime —
SubagentRunResult.error: { code, message }— anddelegationSummaryinpackages/agent-runtime/src/runtime.tsalready spreads it onto the delegation roster entry. It simply had no reader on this surface:Taskreturns the moment a delegate starts (ADR 0089), so its own tool result is pinned atrunningforever, no matter how the delegate ended.SubagentDetailrenders that row.SubagentRun(the delegate's own row collection) carries onlyagentName+items— no error field.TaskWait/TaskList/TaskStop), which are deliberately not topology nodes.So this is a presentation gap, not a runtime gap: the error was already being delivered to the renderer and then discarded. No runtime, IPC, storage, or tool-result change is needed.
What changed
collectDelegationFailures(items)insrc/lib/subagent-topology.tswalks the lifecycle rows'details.delegations[]anddetails.stopped[]and returnsdelegationId → { code, message }. It is deliberately the same shape as the existingcollectDelegationStatuses(same iteration, same last-write-wins rule, same "theTaskrow is not a carrier" exclusion), so the status and the reason can never disagree about which row wins.SubagentDetailgains an optionaldelegationFailuresprop and renders aSubagentFailureCardat the foot of the panel.SubagentPanelcomputes the map fromselected.turnActivityItems— the same array it already feeds tocollectDelegationStatusesandcollectDelegationTimings.error.codeuses the sameerrors.<code>lookup (AssistantErrorMessage), the summary falls back to the localizedchat.subagentStatus.*outcome when a code is not registered, and the raw provider message sits behind a disclosure with a copy control.No new i18n keys. All eight locales already carry
chat.subagentStatus.*(all eight outcomes),chat.showErrorDetails/chat.hideErrorDetails, andchat.copyErrorDetails— the card only reuses them.Two details worth calling out, since both would be easy to get wrong:
completed, and a still-running delegate has no terminal outcome to explain. The gate isfailure && outcome !== "completed" && outcome !== "running".message-error-actionsfor exactly this reason; hiding the details must not take away the control that brings them back.Documentation
spec/04-ux/08-component-spec.md§5.7 gains the failure card, the summary fallback, and the terminal-outcome gate.E2E-198(the subagent dock scenario) gains step 6 and the matching expectation, and its status records the new unit coverage.spec/08-meta/decisions-log.mdrecords D382, mirrored indocs/zh-CN/.Note on the zh-CN mirrors:
docs/zh-CN/spec/04-ux/08-component-spec.mdis missing the entire §5.7, anddocs/zh-CN/spec/06-delivery/04-e2e-test-plan.mddoes not have E2E-198 at all — both predate this change. I did not translate a whole pre-existing missing section here, since that would be an unrelated change (AGENTS.md R2); happy to do it as a separate PR if useful. The decisions log mirror is current, so its entry is synchronized.Validation
node --test test/subagent-topology.test.mjs test/subagent-panel.test.mjs— 23 passed, 0 failed.subagent-topology.test.mjs: a failure read from aTaskWaitroster entry, one read fromTaskStop'sstopped[], last-write-wins across a repeated row, entries that report no error at all (statusonly,error: {}, whitespace-onlymessage, missingdelegationId), and theTaskrow that must never carry one.subagent-panel.test.mjs, following that file's existing pattern for this panel: the prop is wired, the gate is the terminal outcome, and the toggle precedes the region it collapses.node scripts/check-style-tokens.mjs— style tokens OK.apps/desktop/test/*.test.mjsreports 24 failures on this machine, all of themERR_MODULE_NOT_FOUND: Cannot find package '@pi-desktop/shared'— this checkout has nonode_modulesbecausepnpm installcannot complete on this network (metadata timeouts; it has been retrying for several minutes). Those files are unrelated to this change and fail identically on unmodifiedmain. CI is the authoritative check forpnpm typecheck/ lint / the full workspace suite.If the toggle placement or the summary fallback should follow a different convention in this panel, say so and I will adjust.
Numbering note: the decision is D382. D381 is taken by #191 (the selectable Windows
shell change), which itself moved up from D380 after upstream
8ce8ed36claimed it.Both decisions logs follow. The sibling PRs are #191 at D381 and #193 at D383.