Merge nucleic/upbeat-cedar-weasel-1orz into dev

This commit is contained in:
2026-07-17 22:22:13 -07:00
parent 8dd1e65ae0
commit 9af5019bf7
@@ -451,6 +451,12 @@ enum TranscriptProjection {
/// aside and re-emitted right after the block, so the calls stay one contiguous block. /// aside and re-emitted right after the block, so the calls stay one contiguous block.
/// Mirrors the desktop projection. /// Mirrors the desktop projection.
/// ///
/// A subagent spawn (`Task`/`Agent`) only groups with *other* subagent spawns: a run of them
/// is a multi-agent fan-out (the gold orchestration card), which the view gates on the block
/// being all-orchestration. So a subagent-ness switch breaks the run — a wave of `Task`s
/// followed by the agent's own `Bash` calls yields a fan-out card *and* a tool block, not one
/// generic block titled by the first `Task` that would swallow the fan-out.
///
/// `flatItems` already drops the only kind the phone has (empty redacted thinking), so the /// `flatItems` already drops the only kind the phone has (empty redacted thinking), so the
/// hold path is a guard for a caller passing unfiltered items — not a live case. /// hold path is a guard for a caller passing unfiltered items — not a live case.
static func coalesceToolRuns(_ flat: [TranscriptItem]) -> [TranscriptItem] { static func coalesceToolRuns(_ flat: [TranscriptItem]) -> [TranscriptItem] {
@@ -474,7 +480,14 @@ enum TranscriptProjection {
} }
for item in flat { for item in flat {
if case .tool = item.kind { if case .tool(let group) = item.kind {
// A subagent spawn and an ordinary tool call never share a run (see above):
// switching kind flushes the run in progress so each side forms its own card.
// `isOrchestration` mirrors the view's fan-out gate (`groups.allSatisfy(\.isOrchestration)`)
// exactly — worker spawns included — so no mixed block can ever reach it.
if case .tool(let first)? = run.first?.kind, first.isOrchestration != group.isOrchestration {
flush()
}
run.append(item) run.append(item)
} else if item.rendersNothing, !run.isEmpty { } else if item.rendersNothing, !run.isEmpty {
held.append(item) held.append(item)