Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions frontend/src/lib/components/content/ToolBlock.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
generateFallbackContent,
} from "../../utils/tool-params.js";

/** Returns true for tool names that represent a subagent call ("Task" or "Agent"). */
function isSubagentTool(name: string | undefined): boolean {
return name === "Task" || name === "Agent";
}

interface Props {
content: string;
label?: string;
Expand All @@ -33,7 +38,7 @@

/** For Task tool calls, extract key metadata fields */
let taskMeta = $derived.by(() => {
if (toolCall?.tool_name !== "Task" || !inputParams)
if (!isSubagentTool(toolCall?.tool_name) || !inputParams)
return null;
const meta: { label: string; value: string }[] = [];
if (inputParams.subagent_type) {
Expand Down Expand Up @@ -107,13 +112,13 @@
});

let taskPrompt = $derived(
toolCall?.tool_name === "Task"
isSubagentTool(toolCall?.tool_name)
? inputParams?.prompt ?? null
: null,
);

let subagentSessionId = $derived(
toolCall?.tool_name === "Task"
isSubagentTool(toolCall?.tool_name)
? toolCall?.subagent_session_id ?? null
: null,
);
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/lib/utils/content-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ const THINKING_LEGACY_RE =
/\[Thinking\]\n?([\s\S]*?)(?=\n\[|\n\n|$)/g;

const TOOL_NAMES =
"Tool|Read|Write|Edit|Bash|Glob|Grep|Other|TaskCreate|TaskUpdate|TaskGet|TaskList|Task|Skill|" +
"Tool|Read|Write|Edit|Bash|Glob|Grep|Other|TaskCreate|TaskUpdate|TaskGet|TaskList|Task|Agent|Skill|" +
"SendMessage|Question|Todo List|Entering Plan Mode|" +
"Exiting Plan Mode|exec_command|shell_command|" +
"write_stdin|apply_patch|shell|parallel|view_image|" +
"request_user_input|update_plan";

const TOOL_ALIASES: Record<string, string> = {
Agent: "Task",
exec_command: "Bash",
shell_command: "Bash",
write_stdin: "Bash",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/utils/tool-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export function generateFallbackContent(
toolName: string,
params: Params,
): string | null {
if (toolName === "Task") return null;
if (toolName === "Task" || toolName === "Agent") return null;
if (toolName === "Edit") {
const lines: string[] = [];
if (params.old_string != null) {
Expand Down
15 changes: 14 additions & 1 deletion internal/parser/claude.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@ func ParseClaudeSession(
continue
}

// Collect agent_progress events for subagent mapping.
// Claude Code v2.1+ emits these instead of queue-operation for Agent tool calls.
if entryType == "progress" {
if gjson.Get(line, "data.type").Str == "agent_progress" {
tuid := gjson.Get(line, "parentToolUseID").Str
agentID := gjson.Get(line, "data.agentId").Str
if tuid != "" && agentID != "" {
subagentMap[tuid] = "agent-" + agentID
}
}
continue
}

if entryType != "user" && entryType != "assistant" {
continue
}
Expand Down Expand Up @@ -495,7 +508,7 @@ func annotateSubagentSessions(
for i := range messages {
for j := range messages[i].ToolCalls {
tc := &messages[i].ToolCalls[j]
if tc.ToolName == "Task" && tc.ToolUseID != "" {
if (tc.ToolName == "Task" || tc.ToolName == "Agent") && tc.ToolUseID != "" {
if sid, ok := subagentMap[tc.ToolUseID]; ok {
tc.SubagentSessionID = sid
}
Expand Down
25 changes: 25 additions & 0 deletions internal/parser/claude_subagent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ func TestSubagentSessionIDMapping(t *testing.T) {
{ToolUseID: "toolu_xyz789", ToolName: "Task", Category: "Task", SubagentSessionID: "agent-cafebabe"},
},
},
{
name: "Agent Tool Name",
lines: []string{
`{"type":"user","timestamp":"2024-01-01T10:00:00Z","uuid":"u1","message":{"content":"hello"},"cwd":"/tmp"}`,
`{"type":"assistant","timestamp":"2024-01-01T10:00:01Z","uuid":"u2","parentUuid":"u1","message":{"content":[{"type":"tool_use","id":"toolu_agent1","name":"Agent","input":{"description":"explore codebase","subagent_type":"general-purpose","prompt":"find all API endpoints"}}]}}`,
`{"type":"queue-operation","operation":"enqueue","timestamp":"2024-01-01T10:00:01Z","sessionId":"test-session","content":"{\"task_id\":\"agent789\",\"tool_use_id\":\"toolu_agent1\",\"description\":\"explore codebase\",\"task_type\":\"local_agent\"}"}`,
`{"type":"user","timestamp":"2024-01-01T10:00:05Z","uuid":"u3","parentUuid":"u2","message":{"content":[{"type":"tool_result","tool_use_id":"toolu_agent1","content":"found 5 endpoints"}]}}`,
},
wantTools: []ParsedToolCall{
{ToolUseID: "toolu_agent1", ToolName: "Agent", Category: "Task", SubagentSessionID: "agent-agent789"},
},
},
{
name: "Non-Task Tool Unchanged",
lines: []string{
Expand Down Expand Up @@ -98,6 +110,19 @@ func TestSubagentSessionIDMapping(t *testing.T) {
{ToolUseID: "toolu_01XYZ", ToolName: "Task", Category: "Task", SubagentSessionID: "agent-beef4567"},
},
},
{
name: "Agent Progress Events",
lines: []string{
`{"type":"user","timestamp":"2024-01-01T10:00:00Z","uuid":"u1","message":{"content":"hello"},"cwd":"/tmp"}`,
`{"type":"assistant","timestamp":"2024-01-01T10:00:01Z","uuid":"u2","parentUuid":"u1","message":{"content":[{"type":"tool_use","id":"toolu_bdrk_01Wt5","name":"Agent","input":{"description":"wiki search","subagent_type":"wiki-search","prompt":"search wiki"}}]}}`,
`{"type":"progress","timestamp":"2024-01-01T10:00:02Z","parentToolUseID":"toolu_bdrk_01Wt5","data":{"type":"agent_progress","agentId":"a78243c84a44ebcd4","message":{"type":"user","message":{"role":"user","content":[{"type":"text","text":"search wiki"}]}}}}`,
`{"type":"progress","timestamp":"2024-01-01T10:00:03Z","parentToolUseID":"toolu_bdrk_01Wt5","data":{"type":"agent_progress","agentId":"a78243c84a44ebcd4","message":{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"found it"}]}}}}`,
`{"type":"user","timestamp":"2024-01-01T10:00:05Z","uuid":"u3","parentUuid":"u2","message":{"content":[{"type":"tool_result","tool_use_id":"toolu_bdrk_01Wt5","content":"wiki results"}]}}`,
},
wantTools: []ParsedToolCall{
{ToolUseID: "toolu_bdrk_01Wt5", ToolName: "Agent", Category: "Task", SubagentSessionID: "agent-a78243c84a44ebcd4"},
},
},
{
name: "Multiple Subagents",
lines: []string{
Expand Down
2 changes: 1 addition & 1 deletion internal/parser/codex.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ func codexCategoryDetail(
return fmt.Sprintf("%s in %s", pattern, path)
}
return firstNonEmpty(pattern, path)
case "Task":
case "Task", "Agent":
desc := codexArgValue(args, "description")
agent := codexArgValue(args, "subagent_type")
if desc != "" && agent != "" {
Expand Down
16 changes: 16 additions & 0 deletions internal/parser/codex_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,22 @@ func TestParseCodexSession_FunctionCalls(t *testing.T) {
assertToolCalls(t, msgs[1].ToolCalls, []ParsedToolCall{{ToolName: "write_stdin", Category: "Bash"}})
})

t.Run("Agent function call normalizes to Task category", func(t *testing.T) {
content := testjsonl.JoinJSONL(
testjsonl.CodexSessionMetaJSON("fc-agent", "/tmp", "user", tsEarly),
testjsonl.CodexMsgJSON("user", "explore code", tsEarlyS1),
testjsonl.CodexFunctionCallArgsJSON("Agent", map[string]any{
"description": "explore codebase",
"subagent_type": "Explore",
}, tsEarlyS5),
)
sess, msgs := runCodexParserTest(t, "test.jsonl", content, false)
assert.Equal(t, "codex:fc-agent", sess.ID)
assert.Equal(t, 2, len(msgs))
assert.Contains(t, msgs[1].Content, "[Task: explore codebase (Explore)]")
assertToolCalls(t, msgs[1].ToolCalls, []ParsedToolCall{{ToolName: "Agent", Category: "Task"}})
})

t.Run("function call no name skipped", func(t *testing.T) {
content := testjsonl.JoinJSONL(
testjsonl.CodexSessionMetaJSON("fc-2", "/tmp", "user", tsEarly),
Expand Down
2 changes: 1 addition & 1 deletion internal/parser/content.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func formatToolUse(block gjson.Result) string {
skill = input.Get("name").Str
}
return fmt.Sprintf("[Skill: %s]", skill)
case "Task":
case "Task", "Agent":
return formatTask(input)
case "Skill":
return fmt.Sprintf("[Skill: %s]", input.Get("skill").Str)
Expand Down
5 changes: 5 additions & 0 deletions internal/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,11 @@ func TestFormatToolUseVariants(t *testing.T) {
`{"type":"tool_use","name":"Task","input":{"description":"explore","subagent_type":"Explore"}}`,
"[Task: explore (Explore)]",
},
{
"Agent",
`{"type":"tool_use","name":"Agent","input":{"description":"explore","subagent_type":"Explore"}}`,
"[Task: explore (Explore)]",
},
{
"EnterPlanMode",
`{"type":"tool_use","name":"EnterPlanMode","input":{}}`,
Expand Down
2 changes: 1 addition & 1 deletion internal/parser/taxonomy.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func NormalizeToolCategory(rawName string) string {
return "Grep"
case "Glob":
return "Glob"
case "Task":
case "Task", "Agent":
return "Task"
case "Skill":
return "Tool"
Expand Down
1 change: 1 addition & 0 deletions internal/parser/taxonomy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func TestNormalizeToolCategory(t *testing.T) {
{"Grep", "Grep"},
{"Glob", "Glob"},
{"Task", "Task"},
{"Agent", "Task"},
{"Skill", "Tool"},

// Codex tools
Expand Down
2 changes: 1 addition & 1 deletion internal/server/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ var (
thinkingLegacyRe = regexp.MustCompile(
`(?s)\[Thinking\]\n?(.*?)(?:\n\[|\n\n|$)`)
toolBlockRe = regexp.MustCompile(
`(?s)\[(Tool|Read|Write|Edit|Bash|Glob|Grep|Task|` +
`(?s)\[(Tool|Read|Write|Edit|Bash|Glob|Grep|Task|Agent|` +
`Question|Todo List|Entering Plan Mode|` +
`Exiting Plan Mode|exec_command|shell_command|` +
`write_stdin|apply_patch|shell|parallel|` +
Expand Down