fix(parser): harden Agent subagent state machine, file matching, query performance and streaming child grouping

- Codex: decouple active_agent_count decrement from close_agent target
  parsing and reset counter on turn_context to prevent main assistant
  messages from being swallowed when close_agent events are malformed
- Codex: use exact filename match with separator-aware fallback and
  sorted candidates for deterministic subagent session file resolution
- Codex/OpenCode: truncate subagent tool call previews to 500 chars
- OpenCode: batch-load all subagent tool calls in a single SQL query
  instead of per-task N+1 queries to avoid slow detail page loads
- Streaming: restrict positional child grouping fallback to in-progress
  agents only, preventing top-level tool calls from being incorrectly
  folded into completed Agent cards
- Tests: update Claude context window assertions to match 1M default
This commit is contained in:
xintaofei
2026-04-17 09:38:52 +08:00
parent 73a910bb62
commit 3e30ab7d60
4 changed files with 143 additions and 66 deletions

View File

@@ -295,18 +295,20 @@ function buildStreamingTurnsFromLiveMessage(
}
}
// Second pass: assign children using parentToolUseId or position fallback
// Second pass: assign children using parentToolUseId or position fallback.
// Positional fallback only captures while the agent is still in-progress;
// once it completes/fails, subsequent tool calls are treated as top-level.
let positionalAgentId: string | null = null
let positionalAgentCompleted = false
for (const block of liveMessage.content) {
if (block.type === "tool_call") {
const toolName = getToolName(block.info)
if (toolName === "agent") {
positionalAgentId = block.info.tool_call_id
positionalAgentCompleted =
const isFinal =
block.info.status === "completed" || block.info.status === "failed"
// Only capture children while the agent is still running
positionalAgentId = isFinal ? null : block.info.tool_call_id
} else {
// Extract parentToolUseId from ACP meta (Claude Code embeds this
// under meta.claudeCode.parentToolUseId). Guard each access level
@@ -321,21 +323,22 @@ function buildStreamingTurnsFromLiveMessage(
}
}
// Use explicit parentToolUseId when available, positional fallback
// only for in-progress agents
const resolvedParent =
parentId && agentIds.has(parentId) ? parentId : positionalAgentId // fallback
parentId && agentIds.has(parentId) ? parentId : positionalAgentId
if (resolvedParent) {
childToolCallIds.add(block.info.tool_call_id)
agentChildren
.get(resolvedParent)!
.push({ info: block.info, toolName })
.get(resolvedParent)
?.push({ info: block.info, toolName })
}
}
} else if (positionalAgentId && positionalAgentCompleted) {
// A text/thinking/plan block after a completed agent means the main
// agent is producing new content — stop position-based capture.
} else if (positionalAgentId) {
// A non-tool block (text/thinking/plan) means the main agent is
// producing new content — stop position-based capture.
positionalAgentId = null
positionalAgentCompleted = false
}
}