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.
This commit is contained in:
ata
2026-08-17 17:09:20 +10:00
parent aacf42c7eb
commit 54ba7fa4ab
2 changed files with 20 additions and 5 deletions
@@ -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))}`;
}
}
@@ -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/);