diff --git a/src/ui/transcript/tool_group_projection.zig b/src/ui/transcript/tool_group_projection.zig index cfd2c4212..300317df3 100644 --- a/src/ui/transcript/tool_group_projection.zig +++ b/src/ui/transcript/tool_group_projection.zig @@ -151,6 +151,7 @@ const category_labels = [_][]const u8{ "browser", }; const command_category_index = 5; +const subagent_category_index = 6; const Summary = struct { total: usize = 0, @@ -182,7 +183,7 @@ fn categoryIndex(kind: types.ToolActivityKind) ?usize { .edit => 3, .open => 4, .command => command_category_index, - .subagent => 6, + .subagent => subagent_category_index, .ask => null, }; } @@ -531,10 +532,11 @@ fn formatGroupSummary(alloc: std.mem.Allocator, summary: Summary) ![]u8 { const index = next_index orelse break; emitted[index] = true; const count = summary.categories[index]; - const label = if (index == command_category_index and count != 1) - "commands" - else - category_labels[index]; + const label = if (count == 1) category_labels[index] else switch (index) { + command_category_index => "commands", + subagent_category_index => "subagents", + else => category_labels[index], + }; try appendSegment(&out.writer, count, label); } try appendSegment(&out.writer, summary.completion_unreported, "unreported"); @@ -1490,7 +1492,7 @@ test "grouped subagent status keeps the vertical continuation for middle rows" { defer projection.deinit(alloc); try std.testing.expectEqualStrings( - "● 4 tool calls · 4 subagent\n" ++ + "● 4 tool calls · 4 subagents\n" ++ "├ Subagent working · inspect auth\n│ glm-5.3-flash · max\n" ++ "├ Subagent working · inspect auth\n│ glm-5.3-flash · max\n" ++ "├ Subagent working · inspect auth\n│ glm-5.3-flash · max\n" ++ @@ -1499,6 +1501,23 @@ test "grouped subagent status keeps the vertical continuation for middle rows" { ); } +test "group summary uses singular and plural subagent labels" { + const alloc = std.testing.allocator; + var summary: Summary = .{ .total = 2 }; + summary.categories[command_category_index] = 1; + summary.categories[subagent_category_index] = 1; + const single = try formatGroupSummary(alloc, summary); + defer alloc.free(single); + try std.testing.expectEqualStrings("● 2 tool calls · 1 command · 1 subagent", single); + + summary = .{ .total = 5 }; + summary.categories[command_category_index] = 2; + summary.categories[subagent_category_index] = 3; + const plural = try formatGroupSummary(alloc, summary); + defer alloc.free(plural); + try std.testing.expectEqualStrings("● 5 tool calls · 3 subagents · 2 commands", plural); +} + test "small minimal tool groups surface canonical action targets" { const alloc = std.testing.allocator; const entries = [_]TranscriptEntry{ diff --git a/tests/e2e/tui-gateway-stream-lifecycle.test.ts b/tests/e2e/tui-gateway-stream-lifecycle.test.ts index a21f4bec4..2a580e043 100644 --- a/tests/e2e/tui-gateway-stream-lifecycle.test.ts +++ b/tests/e2e/tui-gateway-stream-lifecycle.test.ts @@ -6369,7 +6369,7 @@ describe.skipIf(!tmuxAvailable())("TUI gateway stream lifecycle", () => { } await session.waitForText(finalText, TIMEOUT); const compact = await session.captureFullScrollback(); - expect(compact).toContain("● 5 tool calls · 4 subagent · 1 read"); + expect(compact).toContain("● 5 tool calls · 4 subagents · 1 read"); expect(compact).toContain("Invalid Agent failed · Check failure visibility"); expect(compact).not.toContain("Invalid Agent replied"); for (let index = 0; index < tasks.length; index += 1) { @@ -6400,7 +6400,7 @@ describe.skipIf(!tmuxAvailable())("TUI gateway stream lifecycle", () => { session = await TmuxSession.create({ cmd: `${FX_BIN} --resume-last`, cwd: workspace, env, width: 110, height: 35, stderrPath: join(root, "resumed-stderr.log") }); await session.waitForText(finalText, TIMEOUT); const resumed = await session.captureFullScrollback(); - expect(resumed).toContain("● 5 tool calls · 4 subagent · 1 read"); + expect(resumed).toContain("● 5 tool calls · 4 subagents · 1 read"); expect(resumed).toContain("Invalid Agent failed · Check failure visibility"); expect(resumed).toContain("Subagent finished · Check one-off cleanup"); expect(resumed).toContain("reviewer replied · Check replay again");