Unified · Split
| @@ -2790,11 +2790,11 @@ export function makeClaudeAdapterV2( · 2789 unmodified lines | |||
| 2790 | status: input.status, | 2790 | status: input.status, |
| 2791 | title: "Subagent progress", | 2791 | title: "Subagent progress", |
| 2792 | startedAt: progressStartedAt, | 2792 | startedAt: progressStartedAt, |
| 2793 | completedAt: input.status === "running" ? null : now, | 2793 | completedAt: settled ? now : null, |
| 2794 | updatedAt: now, | 2794 | updatedAt: now, |
| 2795 | type: "reasoning", | 2795 | type: "reasoning", |
| 2796 | text: progress, | 2796 | text: progress, |
| 2797 | streaming: input.status === "running", | 2797 | streaming: !settled, |
| 2798 | }, | 2798 | }, |
| 2799 | }); | 2799 | }); |
| 2800 | } | 2800 | } |
| @@ -3577,6 +3577,21 @@ describe("CodexAdapterV2 post-settle continuation", () => { · 3576 unmodified lines | |||
| 3577 | assert.equal(firstCompletion?.subagent.activationCount, 1); | 3577 | assert.equal(firstCompletion?.subagent.activationCount, 1); |
| 3578 | assert.equal(firstCompletion?.subagent.usage?.totalTokens, 100); | 3578 | assert.equal(firstCompletion?.subagent.usage?.totalTokens, 100); |
| 3579 | assert.isNull(firstCompletion?.subagent.currentActivationId); | 3579 | assert.isNull(firstCompletion?.subagent.currentActivationId); |
| 3580 | const firstActivationUpdates = harness | ||
| 3581 | .subagentActivationUpdates() | ||
| 3582 | .filter((event) => event.activation.ordinal === 1); | ||
| 3583 | const firstActivation = firstActivationUpdates.at(-1)?.activation; | ||
| 3584 | assert.equal(firstActivation?.ordinal, 1); | ||
| 3585 | assert.equal(firstActivation?.status, "completed"); | ||
| 3586 | assert.equal(firstActivation?.usage?.totalTokens, 100); | ||
| 3587 | assert.equal( | ||
| 3588 | firstActivation?.completedAt, | ||
| 3589 | firstActivationUpdates.at(-2)?.activation.completedAt, | ||
| 3590 | ); | ||
| 3591 | assert.equal( | ||
| 3592 | firstActivation?.updatedAt, | ||
| 3593 | firstActivationUpdates.at(-2)?.activation.updatedAt, | ||
| 3594 | ); | ||
| 3580 | assert.lengthOf( | 3595 | assert.lengthOf( |
| 3581 | new Set( | 3596 | new Set( |
| 3582 | settledUpdates | 3597 | settledUpdates |
| @@ -3629,6 +3644,15 @@ describe("CodexAdapterV2 post-settle continuation", () => { · 46 unmodified lines | |||
| 3629 | [RESUME_CHILD_TURN_1, RESUME_CHILD_TURN_2], | 3644 | [RESUME_CHILD_TURN_1, RESUME_CHILD_TURN_2], |
| 3630 | ); | 3645 | ); |
| 3631 | assert.equal(activations.at(-1)?.activation.status, "completed"); | 3646 | assert.equal(activations.at(-1)?.activation.status, "completed"); |
| 3647 | assert.equal(activations.at(-1)?.activation.usage?.totalTokens, 80); | ||
| 3648 | assert.equal( | ||
| 3649 | activations.at(-1)?.activation.completedAt, | ||
| 3650 | activations.at(-2)?.activation.completedAt, | ||
| 3651 | ); | ||
| 3652 | assert.equal( | ||
| 3653 | activations.at(-1)?.activation.updatedAt, | ||
| 3654 | activations.at(-2)?.activation.updatedAt, | ||
| 3655 | ); | ||
| 3632 | assert.isFalse(yield* harness.hasPendingBackgroundWork); | 3656 | assert.isFalse(yield* harness.hasPendingBackgroundWork); |
| 3633 | assert.lengthOf(harness.terminalEvents(), 1); | 3657 | assert.lengthOf(harness.terminalEvents(), 1); |
| 3634 | assert.lengthOf(harness.continuationRequests, 0); | 3658 | assert.lengthOf(harness.continuationRequests, 0); |
| @@ -1517,6 +1517,9 @@ export function makeCodexAdapterV2(adapterOptions: CodexAdapterV2Options): Provi · 1516 unmodified lines | |||
| 1517 | const pendingRootTurns = yield* Ref.make(new Map<string, ProviderAdapterV2TurnInput>()); | 1517 | const pendingRootTurns = yield* Ref.make(new Map<string, ProviderAdapterV2TurnInput>()); |
| 1518 | const turnWaiters = yield* Ref.make(new Map<string, Deferred.Deferred<void, never>>()); | 1518 | const turnWaiters = yield* Ref.make(new Map<string, Deferred.Deferred<void, never>>()); |
| 1519 | const subagentThreads = yield* Ref.make(new Map<string, CodexSubagentThreadContext>()); | 1519 | const subagentThreads = yield* Ref.make(new Map<string, CodexSubagentThreadContext>()); |
| 1520 | const subagentActivationsByNativeTurnId = yield* Ref.make( | ||
| 1521 | new Map<string, OrchestrationV2SubagentActivation>(), | ||
| 1522 | ); | ||
| 1520 | const pendingSubagentTurns = yield* Ref.make( | 1523 | const pendingSubagentTurns = yield* Ref.make( |
| 1521 | new Map<string, ReadonlyArray<PendingCodexSubagentTurnStarted>>(), | 1524 | new Map<string, ReadonlyArray<PendingCodexSubagentTurnStarted>>(), |
| 1522 | ); | 1525 | ); |
| @@ -1958,6 +1961,9 @@ export function makeCodexAdapterV2(adapterOptions: CodexAdapterV2Options): Provi · 435 unmodified lines | |||
| 1958 | updatedAt: turn.startedAt, | 1961 | updatedAt: turn.startedAt, |
| 1959 | } satisfies OrchestrationV2SubagentActivation; | 1962 | } satisfies OrchestrationV2SubagentActivation; |
| 1960 | activeContext.subagentActivation = activation; | 1963 | activeContext.subagentActivation = activation; |
| 1964 | yield* Ref.update(subagentActivationsByNativeTurnId, (current) => | ||
| 1965 | new Map(current).set(turn.nativeTurnId, activation), | ||
| 1966 | ); | ||
| 1961 | yield* Ref.update(activeTurns, (current) => { | 1967 | yield* Ref.update(activeTurns, (current) => { |
| 1962 | const updated = new Map(current); | 1968 | const updated = new Map(current); |
| 1963 | updated.set(turn.nativeTurnId, activeContext); | 1969 | updated.set(turn.nativeTurnId, activeContext); |
| @@ -3380,30 +3386,36 @@ export function makeCodexAdapterV2(adapterOptions: CodexAdapterV2Options): Provi · 1416 unmodified lines | |||
| 3380 | }); | 3386 | }); |
| 3381 | 3387 | ||
| 3382 | const activeContext = (yield* Ref.get(activeTurns)).get(payload.turnId); | 3388 | const activeContext = (yield* Ref.get(activeTurns)).get(payload.turnId); |
| 3383 | if ( | 3389 | const activation = |
| 3384 | activeContext === undefined || | 3390 | activeContext?.subagent === subagent |
| 3385 | activeContext.subagent !== subagent || | 3391 | ? activeContext.subagentActivation |
| 3386 | activeContext.subagentActivation === null | 3392 | : ((yield* Ref.get(subagentActivationsByNativeTurnId)).get(payload.turnId) ?? null); |
| 3387 | ) { | 3393 | if (activation === null) { |
| 3388 | return; | 3394 | return; |
| 3389 | } | 3395 | } |
| 3390 | const last = payload.tokenUsage.last; | 3396 | const last = payload.tokenUsage.last; |
| 3391 | const activation = { | 3397 | const now = yield* DateTime.now; |
| 3392 | ...activeContext.subagentActivation, | 3398 | const updatedActivation = { |
| 3393 | usage: { | 3399 | ...activation, |
| 3400 | usage: mergeCumulativeSubagentUsage(activation.usage, { | ||
| 3394 | totalTokens: last.totalTokens, | 3401 | totalTokens: last.totalTokens, |
| 3395 | inputTokens: last.inputTokens, | 3402 | inputTokens: last.inputTokens, |
| 3396 | cachedInputTokens: last.cachedInputTokens, | 3403 | cachedInputTokens: last.cachedInputTokens, |
| 3397 | outputTokens: last.outputTokens, | 3404 | outputTokens: last.outputTokens, |
| 3398 | reasoningOutputTokens: last.reasoningOutputTokens, | 3405 | reasoningOutputTokens: last.reasoningOutputTokens, |
| 3399 | }, | 3406 | }), |
| 3400 | updatedAt: yield* DateTime.now, | 3407 | updatedAt: activation.completedAt === null ? now : activation.updatedAt, |
| 3401 | } satisfies OrchestrationV2SubagentActivation; | 3408 | } satisfies OrchestrationV2SubagentActivation; |
| 3402 | activeContext.subagentActivation = activation; | 3409 | if (activeContext?.subagent === subagent) { |
| 3410 | activeContext.subagentActivation = updatedActivation; | ||
| 3411 | } | ||
| 3412 | yield* Ref.update(subagentActivationsByNativeTurnId, (current) => | ||
| 3413 | new Map(current).set(payload.turnId, updatedActivation), | ||
| 3414 | ); | ||
| 3403 | yield* emitProviderEvent({ | 3415 | yield* emitProviderEvent({ |
| 3404 | type: "subagent_activation.updated", | 3416 | type: "subagent_activation.updated", |
| 3405 | driver: CODEX_PROVIDER, | 3417 | driver: CODEX_PROVIDER, |
| 3406 | activation, | 3418 | activation: updatedActivation, |
| 3407 | }); | 3419 | }); |
| 3408 | }).pipe(Effect.orDie), | 3420 | }).pipe(Effect.orDie), |
| 3409 | ); | 3421 | ); |
| @@ -3435,6 +3447,9 @@ export function makeCodexAdapterV2(adapterOptions: CodexAdapterV2Options): Provi · 25 unmodified lines | |||
| 3435 | updatedAt: now, | 3447 | updatedAt: now, |
| 3436 | } satisfies OrchestrationV2SubagentActivation; | 3448 | } satisfies OrchestrationV2SubagentActivation; |
| 3437 | activeContext.subagentActivation = activation; | 3449 | activeContext.subagentActivation = activation; |
| 3450 | yield* Ref.update(subagentActivationsByNativeTurnId, (current) => | ||
| 3451 | new Map(current).set(activeContext.nativeTurnId, activation), | ||
| 3452 | ); | ||
| 3438 | yield* emitProviderEvent({ | 3453 | yield* emitProviderEvent({ |
| 3439 | type: "subagent_activation.updated", | 3454 | type: "subagent_activation.updated", |
| 3440 | driver: CODEX_PROVIDER, | 3455 | driver: CODEX_PROVIDER, |
| @@ -4392,6 +4407,9 @@ export function makeCodexAdapterV2(adapterOptions: CodexAdapterV2Options): Provi · 951 unmodified lines | |||
| 4392 | updatedAt: input.completedAt, | 4407 | updatedAt: input.completedAt, |
| 4393 | } satisfies OrchestrationV2SubagentActivation; | 4408 | } satisfies OrchestrationV2SubagentActivation; |
| 4394 | input.context.subagentActivation = activation; | 4409 | input.context.subagentActivation = activation; |
| 4410 | yield* Ref.update(subagentActivationsByNativeTurnId, (current) => | ||
| 4411 | new Map(current).set(input.nativeTurnId, activation), | ||
| 4412 | ); | ||
| 4395 | yield* emitProviderEvent({ | 4413 | yield* emitProviderEvent({ |
| 4396 | type: "subagent_activation.updated", | 4414 | type: "subagent_activation.updated", |
| 4397 | driver: CODEX_PROVIDER, | 4415 | driver: CODEX_PROVIDER, |
| @@ -4,7 +4,7 @@ · 3 unmodified lines | |||
| 4 | {"type": "emit_inbound", "label": "system", "frame": {"type": "system", "subtype": "init", "agents": [], "apiKeySource": "none", "claude_code_version": "2.1.183", "cwd": "/tmp/claude-replay-claude_workflow", "tools": [], "mcp_servers": [], "model": "claude-sonnet-4-6", "permissionMode": "bypassPermissions", "slash_commands": [], "output_style": "default", "skills": [], "plugins": [], "fast_mode_state": "off", "uuid": "af3bf4cc-1385-4e19-a5fa-fcbe2a0ca201", "session_id": "3c7f21a0-9d4e-4b21-8f60-000000000201"}} | 4 | {"type": "emit_inbound", "label": "system", "frame": {"type": "system", "subtype": "init", "agents": [], "apiKeySource": "none", "claude_code_version": "2.1.183", "cwd": "/tmp/claude-replay-claude_workflow", "tools": [], "mcp_servers": [], "model": "claude-sonnet-4-6", "permissionMode": "bypassPermissions", "slash_commands": [], "output_style": "default", "skills": [], "plugins": [], "fast_mode_state": "off", "uuid": "af3bf4cc-1385-4e19-a5fa-fcbe2a0ca201", "session_id": "3c7f21a0-9d4e-4b21-8f60-000000000201"}} |
| 5 | {"type": "emit_inbound", "label": "assistant:commentary", "frame": {"type": "assistant", "message": {"model": "claude-sonnet-4-6", "id": "msg_01claudeworkflow1", "type": "message", "role": "assistant", "content": [{"type": "text", "text": "Starting the two-phase workflow."}], "stop_reason": null, "stop_sequence": null, "stop_details": null, "usage": {"input_tokens": 1, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 1, "service_tier": "standard", "inference_geo": "not_available"}, "context_management": null}, "parent_tool_use_id": null, "session_id": "3c7f21a0-9d4e-4b21-8f60-000000000201", "uuid": "97898e28-ee4e-4fcf-80c3-5c790c502201"}} | 5 | {"type": "emit_inbound", "label": "assistant:commentary", "frame": {"type": "assistant", "message": {"model": "claude-sonnet-4-6", "id": "msg_01claudeworkflow1", "type": "message", "role": "assistant", "content": [{"type": "text", "text": "Starting the two-phase workflow."}], "stop_reason": null, "stop_sequence": null, "stop_details": null, "usage": {"input_tokens": 1, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 1, "service_tier": "standard", "inference_geo": "not_available"}, "context_management": null}, "parent_tool_use_id": null, "session_id": "3c7f21a0-9d4e-4b21-8f60-000000000201", "uuid": "97898e28-ee4e-4fcf-80c3-5c790c502201"}} |
| 6 | {"type": "emit_inbound", "label": "task_started:local_workflow", "frame": {"type": "system", "subtype": "task_started", "task_id": "wf7ka2mq1", "tool_use_id": "toolu_01ClaudeWorkflowFixture01", "description": "Research and implement", "task_type": "local_workflow", "prompt": "Research the parser, then implement the fix.", "uuid": "4873c95b-1aa9-44a5-9783-dc56dac73201", "session_id": "3c7f21a0-9d4e-4b21-8f60-000000000201"}} | 6 | {"type": "emit_inbound", "label": "task_started:local_workflow", "frame": {"type": "system", "subtype": "task_started", "task_id": "wf7ka2mq1", "tool_use_id": "toolu_01ClaudeWorkflowFixture01", "description": "Research and implement", "task_type": "local_workflow", "prompt": "Research the parser, then implement the fix.", "uuid": "4873c95b-1aa9-44a5-9783-dc56dac73201", "session_id": "3c7f21a0-9d4e-4b21-8f60-000000000201"}} |
| 7 | {"type": "emit_inbound", "label": "task_progress:phase-1", "frame": {"type": "system", "subtype": "task_progress", "task_id": "wf7ka2mq1", "tool_use_id": "toolu_01ClaudeWorkflowFixture01", "description": "Research phase running", "usage": {"input_tokens": 300, "output_tokens": 200, "total_tokens": 500, "tool_uses": 4}, "workflow_progress": [{"type": "workflow_phase", "index": 0, "title": "Research"}, {"type": "workflow_phase", "index": 1, "title": "Implement"}, {"type": "workflow_agent", "index": 0, "label": "Researcher", "state": "active", "phaseIndex": 0, "attempt": 0, "model": "claude-sonnet-4-6", "lastToolName": "Read", "tokens": 200, "toolCalls": 2}], "uuid": "4873c95b-1aa9-44a5-9783-dc56dac73202", "session_id": "3c7f21a0-9d4e-4b21-8f60-000000000201"}} | 7 | {"type": "emit_inbound", "label": "task_progress:phase-1", "frame": {"type": "system", "subtype": "task_progress", "task_id": "wf7ka2mq1", "tool_use_id": "toolu_01ClaudeWorkflowFixture01", "description": "Research phase running", "usage": {"input_tokens": 300, "output_tokens": 200, "total_tokens": 500, "tool_uses": 4}, "workflow_progress": [{"type": "workflow_phase", "index": 0, "title": "Research"}, {"type": "workflow_phase", "index": 1, "title": "Implement"}, {"type": "workflow_agent", "index": 0, "label": "Researcher", "state": "waiting", "phaseIndex": 0, "attempt": 0, "model": "claude-sonnet-4-6", "lastToolName": "Read", "tokens": 200, "toolCalls": 2}], "uuid": "4873c95b-1aa9-44a5-9783-dc56dac73202", "session_id": "3c7f21a0-9d4e-4b21-8f60-000000000201"}} |
| 8 | {"type": "emit_inbound", "label": "task_progress:phase-2", "frame": {"type": "system", "subtype": "task_progress", "task_id": "wf7ka2mq1", "tool_use_id": "toolu_01ClaudeWorkflowFixture01", "description": "Implement phase running", "usage": {"input_tokens": 700, "output_tokens": 500, "total_tokens": 1200, "tool_uses": 9}, "workflow_progress": [{"type": "workflow_phase", "index": 0, "title": "Research"}, {"type": "workflow_phase", "index": 1, "title": "Implement"}, {"type": "workflow_agent", "index": 0, "label": "Researcher", "state": "done", "phaseIndex": 0, "attempt": 0, "model": "claude-sonnet-4-6", "lastToolName": "Read", "tokens": 400, "toolCalls": 3}, {"type": "workflow_agent", "index": 1, "label": "Implementer", "state": "active", "phase_index": 1, "attempt": 0, "model": "claude-opus-4-1", "last_tool_name": "Edit", "tokens": 500, "tool_calls": 4}], "uuid": "4873c95b-1aa9-44a5-9783-dc56dac73203", "session_id": "3c7f21a0-9d4e-4b21-8f60-000000000201"}} | 8 | {"type": "emit_inbound", "label": "task_progress:phase-2", "frame": {"type": "system", "subtype": "task_progress", "task_id": "wf7ka2mq1", "tool_use_id": "toolu_01ClaudeWorkflowFixture01", "description": "Implement phase running", "usage": {"input_tokens": 700, "output_tokens": 500, "total_tokens": 1200, "tool_uses": 9}, "workflow_progress": [{"type": "workflow_phase", "index": 0, "title": "Research"}, {"type": "workflow_phase", "index": 1, "title": "Implement"}, {"type": "workflow_agent", "index": 0, "label": "Researcher", "state": "done", "phaseIndex": 0, "attempt": 0, "model": "claude-sonnet-4-6", "lastToolName": "Read", "tokens": 400, "toolCalls": 3}, {"type": "workflow_agent", "index": 1, "label": "Implementer", "state": "active", "phase_index": 1, "attempt": 0, "model": "claude-opus-4-1", "last_tool_name": "Edit", "tokens": 500, "tool_calls": 4}], "uuid": "4873c95b-1aa9-44a5-9783-dc56dac73203", "session_id": "3c7f21a0-9d4e-4b21-8f60-000000000201"}} |
| 9 | {"type": "emit_inbound", "label": "task_notification:local_workflow", "frame": {"type": "system", "subtype": "task_notification", "task_id": "wf7ka2mq1", "tool_use_id": "toolu_01ClaudeWorkflowFixture01", "status": "completed", "output_file": "", "summary": "Workflow finished both phases.", "usage": {"input_tokens": 900, "output_tokens": 600, "total_tokens": 1500, "tool_uses": 11}, "uuid": "4873c95b-1aa9-44a5-9783-dc56dac73204", "session_id": "3c7f21a0-9d4e-4b21-8f60-000000000201"}} | 9 | {"type": "emit_inbound", "label": "task_notification:local_workflow", "frame": {"type": "system", "subtype": "task_notification", "task_id": "wf7ka2mq1", "tool_use_id": "toolu_01ClaudeWorkflowFixture01", "status": "completed", "output_file": "", "summary": "Workflow finished both phases.", "usage": {"input_tokens": 900, "output_tokens": 600, "total_tokens": 1500, "tool_uses": 11}, "uuid": "4873c95b-1aa9-44a5-9783-dc56dac73204", "session_id": "3c7f21a0-9d4e-4b21-8f60-000000000201"}} |
| 10 | {"type": "emit_inbound", "label": "assistant:commentary", "frame": {"type": "assistant", "message": {"model": "claude-sonnet-4-6", "id": "msg_01claudeworkflow2", "type": "message", "role": "assistant", "content": [{"type": "text", "text": "claude workflow fixture complete"}], "stop_reason": null, "stop_sequence": null, "stop_details": null, "usage": {"input_tokens": 1, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 1, "service_tier": "standard", "inference_geo": "not_available"}, "context_management": null}, "parent_tool_use_id": null, "session_id": "3c7f21a0-9d4e-4b21-8f60-000000000201", "uuid": "89be9e65-097b-47e8-849b-be723386b201"}} | 10 | {"type": "emit_inbound", "label": "assistant:commentary", "frame": {"type": "assistant", "message": {"model": "claude-sonnet-4-6", "id": "msg_01claudeworkflow2", "type": "message", "role": "assistant", "content": [{"type": "text", "text": "claude workflow fixture complete"}], "stop_reason": null, "stop_sequence": null, "stop_details": null, "usage": {"input_tokens": 1, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 1, "service_tier": "standard", "inference_geo": "not_available"}, "context_management": null}, "parent_tool_use_id": null, "session_id": "3c7f21a0-9d4e-4b21-8f60-000000000201", "uuid": "89be9e65-097b-47e8-849b-be723386b201"}} |
| @@ -72,6 +72,20 @@ export function assertClaudeWorkflowOutput( · 71 unmodified lines | |||
| 72 | 0, | 72 | 0, |
| 73 | "a member without membership would render outside its workflow", | 73 | "a member without membership would render outside its workflow", |
| 74 | ); | 74 | ); |
| 75 | const waitingProgressItem = result.domainEvents.find( | ||
| 76 | (event) => | ||
| 77 | event.type === "turn-item.updated" && | ||
| 78 | event.payload.type === "reasoning" && | ||
| 79 | event.payload.text === "Read" && | ||
| 80 | event.payload.status === "waiting", | ||
| 81 | ); | ||
| 82 | assert.isDefined(waitingProgressItem); | ||
| 83 | if (waitingProgressItem?.type === "turn-item.updated") { | ||
| 84 | assert.isNull(waitingProgressItem.payload.completedAt); | ||
| 85 | assert.isTrue( | ||
| 86 | waitingProgressItem.payload.type === "reasoning" && waitingProgressItem.payload.streaming, | ||
| 87 | ); | ||
| 88 | } | ||
| 75 | // The coordinator's usage already covers its members, so the panel subtracts | 89 | // The coordinator's usage already covers its members, so the panel subtracts |
| 76 | // theirs from it. That only stays non-negative if the provider's totals are | 90 | // theirs from it. That only stays non-negative if the provider's totals are |
| 77 | // carried through unmodified. | 91 | // carried through unmodified. |