Skip to content

fix(chat): keep a subagent's generated files visible when its card collapses - #356

Merged
garfiec merged 2 commits into
developfrom
fix/subagent-attachment-hoist
Aug 13, 2026
Merged

fix(chat): keep a subagent's generated files visible when its card collapses#356
garfiec merged 2 commits into
developfrom
fix/subagent-attachment-hoist

Conversation

@garfiec

@garfiec garfiec commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Summary

Follow-up to #348. That fix hoisted a tool group's attachments out of the collapsible so generated images stopped disappearing, but it could not reach files produced inside a subagent: a group collects only the ids of its own parts, and a nested call's id is not among them. A subagent's trace card renders its nested parts in its own collapsible and is itself groupable, so those images sat behind two folds and vanished on reopen.

Changes

Two commits, deliberately separable:

  • parity — The subagent branch of ToolCallDispatcher returned before reaching the attachment render, so files the subagent call itself produced rendered nowhere when the call wasn't grouped, and were hoisted when it was. Behaviour depended on whether grouping happened to kick in, which the user can neither see nor predict. They now render below the card under the same hideAttachments guard every other branch uses, so an enclosing activity group still hoists them instead. Mirrors upstream SubagentCall.tsx:577.

  • the nested caseoutputToolCallIds walks each call's own id and then the ids of the calls it ran nested inside it, so every id whose inline render is suppressed is already claimed by a hoist. Only then is it safe to draw the nested parts with hideAttachments. Both halves are one commit on purpose: the forward without the deeper walk reintroduces fix(chat): keep generated images visible when their tool block collapses #348's bug one level down, and the walk without the forward renders each file twice. The dispatcher's hoist and the card's render now resolve the run through one shared subagentTraceParts() helper — resolving it twice by hand is how the two would drift into hoisting one run's ids while rendering another's.

Depth asymmetry, deliberate: the id walk is uncapped while the render stops at depth 1. A depth-2 subagent falls back to the generic card and draws no nested parts at all, but its output still has to reach the surface.

Testing

  • ./gradlew test detektMetadataCommonMain detekt :app:lint :app:assembleDebug — green.
  • ContentSegmentsTest covers the id walk, including a depth-2 nesting case (4 new tests).
  • Reverting the second commit returns the tree to upstream parity, where coverage is carried by two tests that predate this work and are deliberately left untouched: groupedToolCallIdsReturnsToolCallsInOrderAndSkipsReasoning and groupedToolCallIdsSkipsBlankIds.
  • No device verification. This has not been run on a device or emulator.

Notes

  • Deliberate divergence from the web client. Upstream builds a group's attachment set from group.parts alone (ContentParts.tsx:365-369) and passes its subagent dialog no attachments, so a file generated inside a subagent is surfaced nowhere on web. We hoist it instead, on the same reasoning as fix(chat): keep generated images visible when their tool block collapses #348's choice to render every image rather than only the first: output the user cannot reach is the same class of defect as capping an image-gen render at the first attachment. The KDoc on outputToolCallIds records the prohibition and the upstream anchor, so a future /sync-upstream pass does not read this as accidental drift and "correct" it back.
  • Known limitation, widened but not introduced. Provider tool-call ids such as call_0 repeat across agents in a handoff run, and Attachment carries no agentId to scope on (web guards with filterAttachmentsForPart). Nested ids now join that set, so one attachment can hoist under two groups in a handoff run. Fixing it needs the DTO field; worth its own issue.
  • Composable declaration order is untestable here — no Compose harness — so the sibling-after-the-collapsible contract stays in KDoc and the id walk carries the tests.

The subagent branch of the dispatcher ended in a bare return before reaching ToolCallAttachments, so the files a `subagent` call itself produced rendered nowhere. #348 half-covered it by accident: once a group hoists its members' attachments, a subagent that happened to be grouped got its files back, while an ungrouped one still dropped them. Behaviour depended on whether grouping kicked in, which is not something the user can see or predict.

Render them below the card under the same `hideAttachments` guard every other branch uses, so an enclosing activity group still hoists them instead. This is parity, not a new idea: upstream gates its own AttachmentGroup identically at `SubagentCall.tsx:577`, outside the dialog.

Nested parts are untouched here — the following commit widens the hoist to cover them.

No test: the change is composable declaration order, which this module has no Compose harness to assert.
An image generated inside a subagent sat behind TWO folds and vanished on
reopen. The subagent trace card renders its nested parts inside its own
AnimatedVisibility, and the card is itself groupable, so #348's hoist could not
reach the images: a group collects the ids of its OWN parts, and a nested call's
id is not among them.

Widen the hoist set rather than narrow the suppression. outputToolCallIds now
descends through `subagent_content`, so every id whose inline render is
suppressed is already claimed by a hoist — the group's when the call is grouped,
the dispatcher's own sibling slot when it is not. Only then is it safe to draw
the nested parts with hideAttachments, which is why both halves land in one
commit: the forward without the deeper walk is exactly the bug #348 fixed, one
level down, and the walk without the forward renders the same file twice.

The walk descends without a depth cap, unlike the render, which stops at 1. A
depth-2 subagent falls back to the generic card and draws no nested parts at
all, but its output still has to reach the surface.

DELIBERATE DIVERGENCE from the web client. Upstream builds a group's attachment
set from `group.parts` alone (ContentParts.tsx:360-367) and hands its subagent
dialog no attachments, so a file generated inside a subagent is surfaced nowhere
on web. Burying output the user cannot reach is the same class of defect as
capping an image-gen render at the first attachment. The KDoc on
outputToolCallIds says so, because a sync pass would otherwise read this as
accidental drift and "correct" it back.

Known limitation, widened but not introduced: provider tool-call ids like
`call_0` repeat across agents in a handoff run, and Attachment carries no
agentId to scope on (web guards with filterAttachmentsForPart). Nested ids now
join that set, so one attachment can hoist under two groups in a handoff run.
Fixing it needs the DTO field.

Declaration order stays untestable — no Compose harness — so the
sibling-after-the-collapsible contract remains KDoc and the id walk carries the
tests. Reverting this commit returns the tree to upstream parity, and parity
stays covered there by two tests that predate it and are deliberately left
untouched: groupedToolCallIdsReturnsToolCallsInOrderAndSkipsReasoning and
groupedToolCallIdsSkipsBlankIds, both asserting the top-level-only id set.
@garfiec
garfiec merged commit 01e56b7 into develop Aug 13, 2026
5 checks passed
@garfiec
garfiec deleted the fix/subagent-attachment-hoist branch August 13, 2026 07:09
@github-actions

Copy link
Copy Markdown
Contributor

Android debug APK

Artifact: switchboard-android-debug-356
Download: switchboard-android-debug-356.zip
Retention: 90 days
Commit: 824b5ee240035c1c70f39295e03ae3f7cdfa46b4

Download requires a GitHub login. Installs over previous debug builds without uninstalling (stable signing key).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant