diff --git a/src/main/pi/agent-status-extension-async-subagents.test.ts b/src/main/pi/agent-status-extension-async-subagents.test.ts index 00e375060ea..5eb9196163c 100644 --- a/src/main/pi/agent-status-extension-async-subagents.test.ts +++ b/src/main/pi/agent-status-extension-async-subagents.test.ts @@ -180,6 +180,40 @@ describe('Pi child rows', () => { ]) }) + it('names workflow children that share an agent by their workflow step key', async () => { + const harness = createAgentStatusExtensionHarness({ kind: 'pi' }) + startWorkflow(harness) + // pi-subagents 0.76.1 tags each scripted-workflow child with the key it was launched under. + for (const key of ['review-code', 'review-rollout']) { + harness.emitPiEvent('subagent:async-started', { + id: `run-${key}`, + mode: 'single', + agent: 'reviewer', + pid: 4000, + parentWorkflowRunId: WORKFLOW, + workflowKey: key + }) + } + await vi.advanceTimersByTimeAsync(0) + + expect(posts(harness).at(-1)?.subagents).toEqual([ + { + id: 'run-review-code', + state: 'working', + startedAt: expect.any(Number), + agentType: 'reviewer', + description: 'review-code' + }, + { + id: 'run-review-rollout', + state: 'working', + startedAt: expect.any(Number), + agentType: 'reviewer', + description: 'review-rollout' + } + ]) + }) + it('posts an OMP task child with its description', async () => { const harness = createAgentStatusExtensionHarness({ kind: 'omp' }) await harness.callHook('session_start') diff --git a/src/main/pi/agent-status-subagent-roster-source.ts b/src/main/pi/agent-status-subagent-roster-source.ts index ac3d294542b..a78cceec874 100644 --- a/src/main/pi/agent-status-subagent-roster-source.ts +++ b/src/main/pi/agent-status-subagent-roster-source.ts @@ -107,15 +107,16 @@ export function getPiSubagentRosterEventSourceLines(): string[] { " const readLabel = (value: unknown): string | undefined => typeof value === 'string' && value.trim() ? value : undefined", ' lifecycleState.onEvent = (event: unknown, forcedStatus?: string): void => {', " if (!event || typeof event !== 'object') return", - ' const record = event as { id?: unknown; runId?: unknown; agent?: unknown; description?: unknown; mode?: unknown; parentWorkflowRunId?: unknown }', + ' const record = event as { id?: unknown; runId?: unknown; agent?: unknown; description?: unknown; workflowKey?: unknown; mode?: unknown; parentWorkflowRunId?: unknown }', " const id = typeof record.id === 'string' && record.id ? record.id : typeof record.runId === 'string' ? record.runId : ''", ' const status = forcedStatus ?? (event as { status?: unknown }).status', ' if (!id) return', ' if (isOmpRuntime() && !lifecycleState.ownsPane) return', " if (status === 'started') {", ' lifecycleState.active.add(id)', - // Why: pi-subagents redacts task prompts, so only the agent name and OMP's short label are shown. - " if (!subagentDetails.has(id)) subagentDetails.set(id, { agentType: readLabel(record.agent), description: readLabel(record.description), startedAt: Date.now(), workflow: record.mode === 'workflow', parent: readLabel(record.parentWorkflowRunId), registration })", + // Why: pi-subagents redacts task prompts, so only the agent name, OMP's short label and a + // workflow child's step key are shown. The key tells apart siblings that share an agent. + " if (!subagentDetails.has(id)) subagentDetails.set(id, { agentType: readLabel(record.agent), description: readLabel(record.description) ?? readLabel(record.workflowKey), startedAt: Date.now(), workflow: record.mode === 'workflow', parent: readLabel(record.parentWorkflowRunId), registration })", // Why: Pi re-opens only a posted completion; an earlier child must leave the idle check intact. ' if (!isTurnInFlight() && (isOmpRuntime() || lifecycleState.runGeneration === 0 || lifecycleState.completionPostedGeneration === lifecycleState.runGeneration)) {', ' lifecycleState.waiting = true',