fix(tui): show subagent role and drop echoed HUD labels
The anchored Subagents list printed only `Id: description` and treated a label that repeated the spawn handle as a real description. Show the same ⟨role⟩ badge as inline task rows and omit descriptions that only echo the id.
This commit is contained in:
@@ -21,6 +21,10 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed `omp stats` and `/stats` dashboards being unreachable from container hosts by accepting an explicit `--host` bind address while preserving the `127.0.0.1` default.
|
||||
### Fixed
|
||||
|
||||
- Fixed the anchored Subagents HUD omitting the spawned agent role and repeating the spawn id as `Name: Name` when the generated label echoed the handle.
|
||||
|
||||
|
||||
## [17.3.5] - 2026-08-16
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ import { BUILTIN_SLASH_COMMAND_RESERVED_NAMES, buildTuiBuiltinSlashCommands } fr
|
||||
import { formatDuration } from "../slash-commands/helpers/format";
|
||||
import { STTController, type SttState } from "../stt";
|
||||
import { discoverTitleSystemPromptFile, resolvePromptInput } from "../system-prompt";
|
||||
import { formatTaskId } from "../task/render";
|
||||
import { agentTypeBadge, formatTaskId } from "../task/render";
|
||||
import type { ConfiguredThinkingLevel } from "../thinking";
|
||||
import { tinyTitleClient } from "../tiny/title-client";
|
||||
import type { LspStartupServerInfo } from "../tools";
|
||||
@@ -452,8 +452,8 @@ const SUBAGENT_OBSERVER_UI_COALESCE_MS = 100;
|
||||
|
||||
/**
|
||||
* Build the anchored subagent HUD block: a bold accent "Subagents" header plus
|
||||
* a bounded set of running-agent rows in the same `Id: description` shape the
|
||||
* inline task rows use (muted task preview when no description was given).
|
||||
* a bounded set of running-agent rows in the same `Id ⟨role⟩: description` shape
|
||||
* the inline task rows use (muted task preview when no description was given).
|
||||
* Layout mirrors the Todos HUD exactly: unindented header, then
|
||||
* `renderTreeList` rows (dim connectors) shifted right by one space.
|
||||
* Only detached background spawns are listed: a sync task call blocks the
|
||||
@@ -476,16 +476,25 @@ export function renderSubagentHudLines(sessions: ObservableSession[], columns: n
|
||||
expanded: true,
|
||||
renderItem: session => {
|
||||
const displayId = formatTaskId(session.id);
|
||||
let line = `${dot} ${theme.fg("accent", theme.bold(displayId))}`;
|
||||
const role = session.agent ?? session.progress?.agent;
|
||||
const badge = agentTypeBadge(role, theme);
|
||||
let line = `${dot} ${theme.fg("accent", theme.bold(displayId))}${badge}`;
|
||||
const description = session.description?.trim() || session.progress?.description?.trim();
|
||||
if (description) {
|
||||
const budget = Math.max(TRUNCATE_LENGTHS.SHORT, columns - visibleWidth(displayId) - 10);
|
||||
line += `${theme.fg("accent", ":")} ${theme.fg("accent", truncateToWidth(replaceTabs(description), budget))}`;
|
||||
const distinctDescription =
|
||||
description && description.localeCompare(session.id, undefined, { sensitivity: "accent" }) !== 0
|
||||
? description
|
||||
: undefined;
|
||||
if (distinctDescription) {
|
||||
const budget = Math.max(
|
||||
TRUNCATE_LENGTHS.SHORT,
|
||||
columns - visibleWidth(displayId) - visibleWidth(Bun.stripANSI(badge)) - 10,
|
||||
);
|
||||
line += `${theme.fg("accent", ":")} ${theme.fg("accent", truncateToWidth(replaceTabs(distinctDescription), budget))}`;
|
||||
} else {
|
||||
// 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) {
|
||||
if (taskPreview && taskPreview.localeCompare(session.id, undefined, { sensitivity: "accent" }) !== 0) {
|
||||
line += ` ${theme.fg("muted", truncateToWidth(replaceTabs(taskPreview), TRUNCATE_LENGTHS.SHORT))}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -710,7 +710,7 @@ function formatAgentHeaderLabel(args: Partial<TaskParams> | undefined): string |
|
||||
}
|
||||
|
||||
/** Dim `⟨agent⟩` badge for a non-default agent type; empty for the generic worker. */
|
||||
function agentTypeBadge(agent: string | undefined, theme: Theme): string {
|
||||
export function agentTypeBadge(agent: string | undefined, theme: Theme): string {
|
||||
const trimmed = agent?.trim();
|
||||
if (!trimmed || trimmed === "task") return "";
|
||||
return ` ${theme.fg("dim", `${theme.format.bracketLeft}${trimmed}${theme.format.bracketRight}`)}`;
|
||||
|
||||
@@ -107,6 +107,34 @@ describe("subagent HUD lines", () => {
|
||||
expect(out).toContain("SchemaMigrator: Migrating the users table");
|
||||
});
|
||||
|
||||
it("shows a non-default role badge and hides descriptions that only echo the id", () => {
|
||||
const withRole = render([
|
||||
makeSession({
|
||||
id: "HindsightMcpFunnel",
|
||||
agent: "scout",
|
||||
description: "Audit MCP funnel wiring",
|
||||
}),
|
||||
]);
|
||||
expect(withRole).toContain("HindsightMcpFunnel");
|
||||
expect(withRole).toMatch(/HindsightMcpFunnel.*scout/);
|
||||
expect(withRole).toContain("Audit MCP funnel wiring");
|
||||
|
||||
const echoed = render([
|
||||
makeSession({
|
||||
id: "HindsightMcpFunnel",
|
||||
agent: "scout",
|
||||
description: "HindsightMcpFunnel",
|
||||
}),
|
||||
]);
|
||||
expect(echoed).toContain("HindsightMcpFunnel");
|
||||
expect(echoed).toMatch(/HindsightMcpFunnel.*scout/);
|
||||
expect(echoed).not.toContain("HindsightMcpFunnel: HindsightMcpFunnel");
|
||||
|
||||
const defaultWorker = render([makeSession({ id: "AuthLoader", agent: "task", description: "Refactor auth" })]);
|
||||
expect(defaultWorker).toContain("AuthLoader: Refactor auth");
|
||||
expect(defaultWorker).not.toMatch(/AuthLoader.*task/);
|
||||
});
|
||||
|
||||
it("only shows active subagents and clears once everything finished", () => {
|
||||
const finishedStates = ["completed", "failed", "aborted"] as const;
|
||||
const sessions: ObservableSession[] = [
|
||||
|
||||
Reference in New Issue
Block a user