From 54ba7fa4ab7a5ca2ccad3beb2d16e2eb189b0c1d Mon Sep 17 00:00:00 2001 From: ata <111813260+atacolak@users.noreply.github.com> Date: Mon, 17 Aug 2026 17:09:20 +1000 Subject: [PATCH] fix(tui): treat Name-N HUD labels as echoed spawn ids Collision suffixes such as HindsightMcpFunnel-3 were still shown as Name-3: Name because the first pass only compared the raw id. --- .../coding-agent/src/modes/interactive-mode.ts | 14 +++++++++----- .../coding-agent/test/subagent-hud-render.test.ts | 11 +++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/coding-agent/src/modes/interactive-mode.ts b/packages/coding-agent/src/modes/interactive-mode.ts index 2cb0b2487..02e08c6f7 100644 --- a/packages/coding-agent/src/modes/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive-mode.ts @@ -450,6 +450,13 @@ const MODEL_CYCLE_TRACK_CLEAR_MS = 4000; const SUBAGENT_HUD_VISIBLE_LIMIT = 8; const SUBAGENT_OBSERVER_UI_COALESCE_MS = 100; +/** True when `label` is the spawn handle, including collision suffixes (`Name-2`). */ +function hudLabelEchoesId(id: string, label: string): boolean { + if (label.localeCompare(id, undefined, { sensitivity: "accent" }) === 0) return true; + const suffix = id.startsWith(`${label}-`) ? id.slice(label.length + 1) : ""; + return /^\d+$/.test(suffix); +} + /** * Build the anchored subagent HUD block: a bold accent "Subagents" header plus * a bounded set of running-agent rows in the same `Id ⟨role⟩: description` shape @@ -480,10 +487,7 @@ export function renderSubagentHudLines(sessions: ObservableSession[], columns: n const badge = agentTypeBadge(role, theme); let line = `${dot} ${theme.fg("accent", theme.bold(displayId))}${badge}`; const description = session.description?.trim() || session.progress?.description?.trim(); - const distinctDescription = - description && description.localeCompare(session.id, undefined, { sensitivity: "accent" }) !== 0 - ? description - : undefined; + const distinctDescription = description && !hudLabelEchoesId(session.id, description) ? description : undefined; if (distinctDescription) { const budget = Math.max( TRUNCATE_LENGTHS.SHORT, @@ -494,7 +498,7 @@ export function renderSubagentHudLines(sessions: ObservableSession[], columns: n // No spawn description: fall back to a muted task preview, same as // the inline task rows when a row has no label. const taskPreview = session.progress?.task?.trim(); - if (taskPreview && taskPreview.localeCompare(session.id, undefined, { sensitivity: "accent" }) !== 0) { + if (taskPreview && !hudLabelEchoesId(session.id, taskPreview)) { line += ` ${theme.fg("muted", truncateToWidth(replaceTabs(taskPreview), TRUNCATE_LENGTHS.SHORT))}`; } } diff --git a/packages/coding-agent/test/subagent-hud-render.test.ts b/packages/coding-agent/test/subagent-hud-render.test.ts index 2157b5717..2543235e0 100644 --- a/packages/coding-agent/test/subagent-hud-render.test.ts +++ b/packages/coding-agent/test/subagent-hud-render.test.ts @@ -130,6 +130,17 @@ describe("subagent HUD lines", () => { expect(echoed).toMatch(/HindsightMcpFunnel.*scout/); expect(echoed).not.toContain("HindsightMcpFunnel: HindsightMcpFunnel"); + const collision = render([ + makeSession({ + id: "HindsightMcpFunnel-3", + agent: "scout", + description: "HindsightMcpFunnel", + }), + ]); + expect(collision).toContain("HindsightMcpFunnel-3"); + expect(collision).toMatch(/HindsightMcpFunnel-3.*scout/); + expect(collision).not.toContain("HindsightMcpFunnel-3: HindsightMcpFunnel"); + const defaultWorker = render([makeSession({ id: "AuthLoader", agent: "task", description: "Refactor auth" })]); expect(defaultWorker).toContain("AuthLoader: Refactor auth"); expect(defaultWorker).not.toMatch(/AuthLoader.*task/);