Skip to content

Commit e284656

Browse files
Label Codex collaboration groups as subagents
Read canonical tool metadata in the grouped conversation header so Codex spawn batches no longer fall back to the generic Other label. Generated-By: PostHog Code Task-Id: 971044c5-e622-4f07-b1b5-dca9b06d040c
1 parent 40c16f8 commit e284656

2 files changed

Lines changed: 52 additions & 6 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { posthogToolMeta } from "@posthog/shared";
2+
import type { ConversationItem } from "@posthog/ui/features/sessions/components/buildConversationItems";
3+
import { Theme } from "@radix-ui/themes";
4+
import { render, screen } from "@testing-library/react";
5+
import { describe, expect, it } from "vitest";
6+
import { ToolGroup } from "./ToolGroup";
7+
8+
function subagentItem(
9+
id: string,
10+
): Extract<ConversationItem, { type: "session_update" }> {
11+
return {
12+
type: "session_update",
13+
id,
14+
update: {
15+
sessionUpdate: "tool_call",
16+
toolCallId: id,
17+
title: "Subagent",
18+
kind: "other",
19+
status: "completed",
20+
_meta: posthogToolMeta({ toolName: "spawn_agent" }),
21+
},
22+
turnContext: {
23+
toolCalls: new Map(),
24+
childItems: new Map(),
25+
turnCancelled: false,
26+
turnComplete: true,
27+
},
28+
} as Extract<ConversationItem, { type: "session_update" }>;
29+
}
30+
31+
describe("ToolGroup", () => {
32+
it("labels Codex spawn batches as subagents", () => {
33+
render(
34+
<Theme>
35+
<ToolGroup tools={[subagentItem("spawn-1"), subagentItem("spawn-2")]} />
36+
</Theme>,
37+
);
38+
39+
expect(screen.getByText("Used Subagents")).toBeInTheDocument();
40+
});
41+
});

packages/ui/src/features/sessions/components/chat-thread/ToolGroup.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import {
66
cn,
77
Spinner,
88
} from "@posthog/quill";
9+
import { readAgentToolName } from "@posthog/shared";
910
import type { ToolCall } from "@posthog/ui/features/sessions/types";
1011
import { memo } from "react";
1112
import type { ConversationItem } from "../buildConversationItems";
13+
import { grouping } from "../new-thread/conversationThreadConfig";
1214
import { SessionUpdateView } from "../session-update/SessionUpdateView";
1315
import { iconForToolCall } from "../session-update/toolCallUtils";
1416

@@ -38,10 +40,10 @@ function resolveTool(item: ToolGroupItem["tools"][number]): {
3840
...(update as unknown as ToolCall),
3941
status: (update as unknown as ToolCall).status ?? "in_progress",
4042
};
41-
const meta = fromMap._meta as
42-
| { claudeCode?: { toolName?: string } }
43-
| undefined;
44-
return { toolCall: fromMap, toolName: meta?.claudeCode?.toolName };
43+
return {
44+
toolCall: fromMap,
45+
toolName: readAgentToolName(fromMap._meta),
46+
};
4547
}
4648

4749
/** Identity used to decide if a group is "all the same tool". */
@@ -52,9 +54,12 @@ function toolKey(item: ToolGroupItem["tools"][number]): string {
5254

5355
/** Human label for a uniform group, e.g. `ToolSearch` → "Tool search", `mcp__x__run` → "Run". */
5456
function friendlyName(key: string): string {
57+
if (grouping.subagentToolNames.has(key)) return "Subagents";
5558
const last = key.includes("__") ? (key.split("__").pop() ?? key) : key;
56-
// Split PascalCase/camelCase into words so `ToolSearch` reads "Tool search" rather than "Toolsearch".
57-
const spaced = last.replace(/([a-z\d])([A-Z])/g, "$1 $2");
59+
// Split separators and PascalCase/camelCase so tool identifiers read naturally.
60+
const spaced = last
61+
.replace(/[_-]+/g, " ")
62+
.replace(/([a-z\d])([A-Z])/g, "$1 $2");
5863
return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
5964
}
6065

0 commit comments

Comments
 (0)