mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 14:38:23 -10:00
fix(web-shell): count node-face agents from the same sources the rows render
R8-2: agentCount read only transcript subTools, while renderExecution also renders live child tasks from the task index, so a live child with no transcript entry rendered a row the tally never counted. Count the same deduped union the rows come from. Co-authored-by: Qwen-Coder <[email protected]>
This commit is contained in:
1 parent
5c4f1de9e8
commit
5f70a13866
2 files changed
+84
-9
No files matched your search
@@ -586,10 +586,14 @@ describe('PlanExecutionView', () => {
|
||||
// the dependency chips, and the agent count and elapsed carry the "is
|
||||
// this alive" signal onto the face.
|
||||
expect(buildNode?.textContent).toContain('Build');
|
||||
expect(buildNode?.textContent).toContain('1 agent');
|
||||
// Root agent plus the live child task below (parentAgentId set, no
|
||||
// transcript entry): the node renders two agent rows, so the face tally
|
||||
// must read 2 — counting only transcript subTools is the R8-2 divergence.
|
||||
expect(buildNode?.textContent).toContain('2 agents');
|
||||
expect(buildNode?.textContent).toContain('1m 5s');
|
||||
// The singular branch above and the plural branch here are separate
|
||||
// formatter paths; `1 agents` would ship green with only one of them.
|
||||
// The plural branch is pinned above; `1 agents` would ship green without
|
||||
// the negative assertion here. The singular branch is pinned in
|
||||
// 'the node-face agent tally matches the rows it renders'.
|
||||
const researchNode = container
|
||||
.querySelector('[data-plan-node-id="research"]')
|
||||
?.closest('article');
|
||||
@@ -1839,6 +1843,62 @@ describe('PlanExecutionView', () => {
|
||||
container.remove();
|
||||
});
|
||||
|
||||
it('the node-face agent tally matches the rows it renders', () => {
|
||||
// R8-2 witness: a live child task whose toolUseId matches no subTools
|
||||
// callId renders a row (via toolForNestedTask) while the transcript-only
|
||||
// tally never saw it — the node read "1 agent" beside two agent rows.
|
||||
const rootTool: ACPToolCall = {
|
||||
...agentTool('build'),
|
||||
subTools: [
|
||||
{
|
||||
callId: 'call-nested',
|
||||
toolName: 'Agent',
|
||||
title: 'Nested agent',
|
||||
status: 'in_progress',
|
||||
},
|
||||
],
|
||||
};
|
||||
const rootTask = task('running');
|
||||
const liveChild = task('running', {
|
||||
id: 'agent-live-child',
|
||||
label: 'Live child',
|
||||
toolUseId: 'call-live-child',
|
||||
parentAgentId: rootTask.id,
|
||||
});
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
const root = createRoot(container);
|
||||
act(() => {
|
||||
root.render(
|
||||
<I18nProvider language="en">
|
||||
<PlanExecutionView
|
||||
todos={todos}
|
||||
tools={[rootTool, agentTool('research')]}
|
||||
tasks={[rootTask, liveChild]}
|
||||
/>
|
||||
</I18nProvider>,
|
||||
);
|
||||
});
|
||||
|
||||
const buildNode = container
|
||||
.querySelector('[data-plan-node-id="build"]')
|
||||
?.closest('article');
|
||||
// Three rows render: root, the transcript nested agent, and the live
|
||||
// child — and the tally counts the same three.
|
||||
expect(buildNode?.textContent).toContain('3 agents');
|
||||
expect(buildNode?.textContent).toContain('Nested agent');
|
||||
expect(buildNode?.textContent).toContain('Live child');
|
||||
// The lone root on the research node keeps the singular path pinned.
|
||||
const researchNode = container
|
||||
.querySelector('[data-plan-node-id="research"]')
|
||||
?.closest('article');
|
||||
expect(researchNode?.textContent).toContain('1 agent');
|
||||
expect(researchNode?.textContent).not.toContain('1 agents');
|
||||
|
||||
act(() => root.unmount());
|
||||
container.remove();
|
||||
});
|
||||
|
||||
it('renders no agent tally for a non-agent tool tagged to the step', () => {
|
||||
// Declaring todo_id does not make a tool an agent: the tally hides
|
||||
// rather than printing "0 agents" beside the tool's row.
|
||||
|
||||
@@ -1293,14 +1293,29 @@ export function PlanExecutionView({
|
||||
// Agents, not bare executions: a nested subagent counts too,
|
||||
// matching the rows this node renders and the inspector's
|
||||
// Subagents list for the same step. The runtime above stays
|
||||
// on roots, so nested time is not summed twice.
|
||||
const agentCount = executions.reduce(
|
||||
(count, tool) =>
|
||||
// on roots, so nested time is not summed twice. Count from
|
||||
// the same two sources renderExecution draws the rows from —
|
||||
// live child tasks from the task index plus transcript
|
||||
// subTools, deduped by toolUseId exactly as the rows are —
|
||||
// otherwise a live child task with no transcript entry
|
||||
// renders a row the count never sees.
|
||||
const agentCount = executions.reduce((count, tool) => {
|
||||
const liveNested = nestedTasksFromIndex(tool, taskIndex);
|
||||
const liveCallIds = new Set(
|
||||
liveNested.flatMap(({ task }) =>
|
||||
task.toolUseId ? [task.toolUseId] : [],
|
||||
),
|
||||
);
|
||||
const transcriptOnly = nestedAgentToolsForTool(tool).filter(
|
||||
({ tool: nested }) => !liveCallIds.has(nested.callId),
|
||||
);
|
||||
return (
|
||||
count +
|
||||
(isSubAgentToolCall(tool) ? 1 : 0) +
|
||||
nestedAgentToolsForTool(tool).length,
|
||||
0,
|
||||
);
|
||||
liveNested.length +
|
||||
transcriptOnly.length
|
||||
);
|
||||
}, 0);
|
||||
// blockedBy is model-authored and can repeat an id — or name
|
||||
// the todo itself. Dedup and drop self-references like the
|
||||
// topology builder; ghost ids stay, because above the edge
|
||||
|
||||
Reference in new issue
Block a user