diff --git a/src/workflow/__tests__/WorkflowsPanel.test.tsx b/src/workflow/__tests__/WorkflowsPanel.test.tsx index 4306cf72a..0f4d0be27 100644 --- a/src/workflow/__tests__/WorkflowsPanel.test.tsx +++ b/src/workflow/__tests__/WorkflowsPanel.test.tsx @@ -6,6 +6,7 @@ import { SentryErrorBoundary } from '../../components/SentryErrorBoundary.js'; import type { RunProgress } from '../progress/store.js'; import { call as panelCall } from '../panel/panelCall.js'; import { clampSelected, WorkflowsPanel } from '../panel/WorkflowsPanel.js'; +import { truncateLabel } from '../panel/AgentList.js'; import { STATUS_DOT } from '../panel/status.js'; import { __resetWorkflowServiceForTests, getWorkflowService } from '../service.js'; @@ -20,6 +21,23 @@ test('clampSelected:空列表→0;越界→末位;负/NaN→0;正常→ expect(clampSelected(Number.NaN, 3)).toBe(0); }); +// truncateLabel:短 label 原样;含 `#数字` 后缀时保留后缀,前缀截断 + 省略号; +// 无后缀则从右切。让 audit workflow 的 verify:${dim}#${idx} 多 finding 仍可区分。 +test('truncateLabel:短 label 原样;含 #数字 后缀保留后缀前缀截断;无后缀从右切', () => { + // 短 label 原样 + expect(truncateLabel('agent-1', 18)).toBe('agent-1'); + expect(truncateLabel('review:bugs', 18)).toBe('review:bugs'); + // 刚好 max 长度(边界) + expect(truncateLabel('review:correctness', 18)).toBe('review:correctness'); + // 超 max + 含 #数字 后缀:保留后缀,前缀截断 + 省略号 + expect(truncateLabel('verify:correctness#0', 18)).toBe('verify:correctn…#0'); + expect(truncateLabel('verify:architecture#15', 18)).toBe('verify:archite…#15'); + // 多位 #idx 也能区分 + expect(truncateLabel('verify:correctness#2', 18)).toBe('verify:correctn…#2'); + // 无 #数字 后缀:从右切(旧行为) + expect(truncateLabel('a-very-long-label-no-suffix', 18)).toBe('a-very-long-label-'); +}); + // STATUS_DOT 覆盖四种状态,且均为可见圆点字符。 test('STATUS_DOT 覆盖 running/completed/failed/killed 且为非空字符', () => { const statuses = ['running', 'completed', 'failed', 'killed'] as const; diff --git a/src/workflow/panel/AgentList.tsx b/src/workflow/panel/AgentList.tsx index 8afcd352d..20b956454 100644 --- a/src/workflow/panel/AgentList.tsx +++ b/src/workflow/panel/AgentList.tsx @@ -8,6 +8,22 @@ const SPINNER_FRAMES = ['·', '✢', '✱', '✶', '✻', '✽']; const FRAME_MS = 120; const LABEL_MAX = 18; +/** + * 截断 label 到 max 字符。保留尾部 `#数字` 后缀(audit workflow 的 + * `verify:${dim}#${findingIdx}` 格式)——同 dimension 多 finding 的 verify + * agent label 仍可区分(前缀用 `…` 省略)。无后缀则从右切(旧行为)。 + * 已导出便于单测覆盖。 + */ +export function truncateLabel(raw: string, max: number): string { + if (raw.length <= max) return raw; + const m = raw.match(/#\d+$/); + if (!m) return raw.slice(0, max); + const suffix = m[0]; // 含 # 号 + const prefix = raw.slice(0, raw.length - suffix.length); + const available = max - suffix.length - 1; // -1 留给 … + return `${prefix.slice(0, available)}…${suffix}`; +} + /** * 右 agent 列表(已按选中 phase 过滤)。 * 选中行:仅在本列聚焦(focused=true)时铺 selectionBg 底(保留 fg,非反色); @@ -39,7 +55,7 @@ export function AgentList({ const highlighted = selected && focused; const running = a.status === 'running'; const mark = running ? frame : v.mark; - const label = (a.label ?? `agent-${a.id}`).slice(0, LABEL_MAX); + const label = truncateLabel(a.label ?? `agent-${a.id}`, LABEL_MAX); return (