@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
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `CursorTranslator` (`../translator.ts`): what each `@cursor/sdk` event and
|
|
3
|
+
* delta becomes on the canonical union, and — through the real
|
|
4
|
+
* `TranscriptBuilder` (`__test-utils__/fold.ts`) — what that does to the
|
|
5
|
+
* transcript. One arm per rule the translator's header states.
|
|
6
|
+
*
|
|
7
|
+
* Opened in #1097 with the arms re-keyed from `message-translator.test.ts`
|
|
8
|
+
* (the accumulator's status map, MCP unpacking, sub-agent name extraction and
|
|
9
|
+
* `conversationSteps` parsing), `delta-enricher.test.ts` (the buffering keyed
|
|
10
|
+
* by call id) and `todo-tracker.test.ts` (which events carry todos); the row
|
|
11
|
+
* rules those files also pinned — the by-id upsert, the monotonic merge, the
|
|
12
|
+
* dirty flag — are the builder's and live in
|
|
13
|
+
* `harness/transcript/__tests__/builder.test.ts`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
17
|
+
import { create } from "@bufbuild/protobuf";
|
|
18
|
+
import type { InteractionUpdate, SDKMessage } from "@cursor/sdk";
|
|
19
|
+
import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
20
|
+
import { ApprovalAction, MessageType, SubAgentStatus, ToolCallStatus, ToolKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
21
|
+
import { AgentMessageSchema, ToolCallSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
22
|
+
import type { TranscriptEvent } from "../../../harness/transcript/events.js";
|
|
23
|
+
import type { MergedToolPolicy } from "../../../shared/approval-policy.js";
|
|
24
|
+
import { CursorFold, foldCursorEvents } from "../__test-utils__/fold.js";
|
|
25
|
+
import { sdkEvents } from "../__test-utils__/scripted-agent.js";
|
|
26
|
+
import { grantToken } from "../approval-state.js";
|
|
27
|
+
import { CursorTranslator, extractSubagentName } from "../translator.js";
|
|
28
|
+
|
|
29
|
+
const RUN = "run-1";
|
|
30
|
+
const ev = sdkEvents("agent-1", RUN);
|
|
31
|
+
|
|
32
|
+
function translator(seeded = create(AgentExecutionStatusSchema, {})): CursorTranslator {
|
|
33
|
+
return new CursorTranslator({ policies: new Map(), leases: { global: false, categories: new Set() }, seeded: seeded.messages });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function kinds(events: readonly TranscriptEvent[]): string[] {
|
|
37
|
+
return events.map((e) => e.kind);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── The stream channel ────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
describe("CursorTranslator — text and thinking stream into segments; a tool call ends one", () => {
|
|
43
|
+
it("the first text of a run opens a segment (message_start) and streams into it; later text only streams", () => {
|
|
44
|
+
const t = translator();
|
|
45
|
+
expect(t.translate(ev.assistant("Hello, "))).toEqual([
|
|
46
|
+
{ kind: "message_start", runId: `${RUN}:0` },
|
|
47
|
+
{ kind: "text_delta", runId: `${RUN}:0`, text: "Hello, " },
|
|
48
|
+
]);
|
|
49
|
+
expect(t.translate(ev.assistant("world."))).toEqual([{ kind: "text_delta", runId: `${RUN}:0`, text: "world." }]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("thinking and text of one run share a segment; empty chunks are nothing", () => {
|
|
53
|
+
const t = translator();
|
|
54
|
+
expect(kinds(t.translate(ev.thinking("Let me think.")))).toEqual(["message_start", "reasoning_delta"]);
|
|
55
|
+
expect(kinds(t.translate(ev.assistant("Here.")))).toEqual(["text_delta"]);
|
|
56
|
+
expect(t.translate(ev.assistant(""))).toEqual([]);
|
|
57
|
+
expect(t.translate(ev.thinking(""))).toEqual([]);
|
|
58
|
+
expect(t.translate({ type: "assistant", agent_id: "a", run_id: RUN, message: { role: "assistant", content: [{ type: "tool_use", id: "x", name: "read", input: {} }] } })).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("a tool call finishes the open segment, and the text after it opens the next — Cursor's message boundary", () => {
|
|
62
|
+
const t = translator();
|
|
63
|
+
t.translate(ev.assistant("Reading."));
|
|
64
|
+
const atTool = t.translate(ev.toolCall("c1", "read", "running", { path: "a" }));
|
|
65
|
+
expect(kinds(atTool)).toEqual(["message_finish", "tool_started"]);
|
|
66
|
+
expect(atTool[0]).toEqual({ kind: "message_finish", runId: `${RUN}:0` });
|
|
67
|
+
const after = t.translate(ev.assistant("Done."));
|
|
68
|
+
expect(after[0]).toEqual({ kind: "message_start", runId: `${RUN}:1` });
|
|
69
|
+
// Through the builder: two messages, the row on the first.
|
|
70
|
+
const fold = foldCursorEvents([
|
|
71
|
+
ev.assistant("Reading."),
|
|
72
|
+
ev.toolCall("c1", "read", "running", { path: "a" }),
|
|
73
|
+
ev.toolCall("c1", "read", "completed", { path: "a" }, "text"),
|
|
74
|
+
ev.assistant("Done."),
|
|
75
|
+
]);
|
|
76
|
+
expect(fold.messages.map((m) => [m.content, m.toolCalls.map((tc) => tc.id)])).toEqual([["Reading.", ["c1"]], ["Done.", []]]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("a tool call with no segment open finishes nothing, and still turns the next text into a new message (tool-only-step)", () => {
|
|
80
|
+
const t = translator();
|
|
81
|
+
expect(kinds(t.translate(ev.toolCall("c1", "read", "running", { path: "a" })))).toEqual(["tool_started"]);
|
|
82
|
+
expect(t.translate(ev.assistant("After."))[0]).toEqual({ kind: "message_start", runId: `${RUN}:1` });
|
|
83
|
+
const fold = foldCursorEvents([ev.toolCall("c1", "read", "running", { path: "a" }), ev.assistant("After.")]);
|
|
84
|
+
expect(fold.messages.map((m) => [m.type, m.content, m.toolCalls.length])).toEqual([
|
|
85
|
+
[MessageType.MESSAGE_AI, "", 1],
|
|
86
|
+
[MessageType.MESSAGE_AI, "After.", 0],
|
|
87
|
+
]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("thinking after a tool call is a new THINKING row, not a continuation of the run's earlier one", () => {
|
|
91
|
+
const fold = foldCursorEvents([
|
|
92
|
+
ev.thinking("First thought."),
|
|
93
|
+
ev.assistant("Reading."),
|
|
94
|
+
ev.toolCall("c1", "read", "running", { path: "a" }),
|
|
95
|
+
ev.thinking("Second thought."),
|
|
96
|
+
ev.assistant("Done."),
|
|
97
|
+
]);
|
|
98
|
+
expect(fold.messages.map((m) => [m.type, m.content])).toEqual([
|
|
99
|
+
[MessageType.MESSAGE_THINKING, "First thought."],
|
|
100
|
+
[MessageType.MESSAGE_AI, "Reading."],
|
|
101
|
+
[MessageType.MESSAGE_THINKING, "Second thought."],
|
|
102
|
+
[MessageType.MESSAGE_AI, "Done."],
|
|
103
|
+
]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("runs are independent: a recovery retry's send has its own segments", () => {
|
|
107
|
+
const t = translator();
|
|
108
|
+
const other = sdkEvents("agent-1", "run-2");
|
|
109
|
+
expect(t.translate(ev.assistant("a"))[0]).toEqual({ kind: "message_start", runId: "run-1:0" });
|
|
110
|
+
expect(t.translate(other.assistant("b"))[0]).toEqual({ kind: "message_start", runId: "run-2:0" });
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("CursorTranslator — a tool_call event is the row's start and, when terminal, its outcome", () => {
|
|
115
|
+
it("running → tool_started with the stream's name, its args as input, no server, no gate for a read-only built-in", () => {
|
|
116
|
+
const [started] = translator().translate(ev.toolCall("c1", "read", "running", { path: "a.md" }));
|
|
117
|
+
// No provenance: an ungated built-in is governed by no layer (the `tool-call` golden's row carries none).
|
|
118
|
+
expect(started).toEqual({ kind: "tool_started", callId: "c1", name: "read", input: { path: "a.md" }, mcpServerSlug: "" });
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("a gated built-in carries the policy's word: the category's message over the args", () => {
|
|
122
|
+
const [started] = translator().translate(ev.toolCall("c1", "shell", "running", { command: "npm test" }));
|
|
123
|
+
expect(started).toMatchObject({ kind: "tool_started", gate: { message: "Run command: npm test" }, provenance: "builtin_category" });
|
|
124
|
+
const fold = foldCursorEvents([ev.toolCall("c1", "shell", "running", { command: "npm test" })]);
|
|
125
|
+
expect(fold.row("c1").requiresApproval).toBe(true);
|
|
126
|
+
expect(fold.row("c1").approvalMessage).toBe("Run command: npm test");
|
|
127
|
+
expect(fold.row("c1").status, "gated but RUNNING: the boundary parks it, never the stream").toBe(ToolCallStatus.TOOL_CALL_RUNNING);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("a leased category is still the policy's word (lease-blind, as the accumulator was; the harness difference is #1117)", () => {
|
|
131
|
+
const t = new CursorTranslator({ policies: new Map(), leases: { global: false, categories: new Set(["shell"]) }, seeded: [] });
|
|
132
|
+
const [started] = t.translate(ev.toolCall("c1", "shell", "running", { command: "ls" }));
|
|
133
|
+
expect(started).toMatchObject({ gate: { message: "Run command: ls" }, provenance: "approval_lease" });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("completed → tool_finished with the result rendered to the row's string; a string as-is, an envelope stringified", () => {
|
|
137
|
+
const t = translator();
|
|
138
|
+
t.translate(ev.toolCall("c1", "read", "running", {}));
|
|
139
|
+
expect(t.translate(ev.toolCall("c1", "read", "completed", {}, "plain"))).toEqual([
|
|
140
|
+
{ kind: "tool_started", callId: "c1", name: "read", input: {}, mcpServerSlug: "" },
|
|
141
|
+
{ kind: "tool_finished", callId: "c1", result: "plain" },
|
|
142
|
+
]);
|
|
143
|
+
const envelope = { status: "success", value: { exitCode: 0, stdout: "ok\n", stderr: "" } };
|
|
144
|
+
const [, finished] = t.translate(ev.toolCall("c2", "shell", "completed", { command: "make" }, envelope));
|
|
145
|
+
expect(finished).toEqual({ kind: "tool_finished", callId: "c2", result: JSON.stringify(envelope) });
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("error → tool_error with the failure text (or the generic text for a structured failure); the row carries it as error alone", () => {
|
|
149
|
+
const t = translator();
|
|
150
|
+
expect(t.translate(ev.toolCall("c1", "read", "error", {}, "ENOENT")).at(-1)).toEqual({ kind: "tool_error", callId: "c1", message: "ENOENT" });
|
|
151
|
+
expect(t.translate(ev.toolCall("c2", "read", "error", {}, { code: 1 })).at(-1)).toEqual({ kind: "tool_error", callId: "c2", message: "Tool call failed" });
|
|
152
|
+
const fold = foldCursorEvents([ev.toolCall("c1", "read", "running", { path: "x" }), ev.toolCall("c1", "read", "error", { path: "x" }, "ENOENT")]);
|
|
153
|
+
expect(fold.row("c1").status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
|
|
154
|
+
expect(fold.row("c1").error).toBe("ENOENT");
|
|
155
|
+
expect(fold.row("c1").result).toBe("");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("a completion with no running before it is a start and a finish in one — startedAt equals completedAt", () => {
|
|
159
|
+
const fold = foldCursorEvents([ev.assistant("Go."), ev.toolCall("c1", "read", "completed", { path: "x" }, "done")]);
|
|
160
|
+
const row = fold.row("c1");
|
|
161
|
+
expect(row.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
162
|
+
expect(row.startedAt).toBe(row.completedAt);
|
|
163
|
+
expect(fold.messages[0].toolCalls.map((tc) => tc.id)).toEqual(["c1"]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("a completion after later text lands on the row where it started, never on the new message (cross-message completion)", () => {
|
|
167
|
+
const fold = foldCursorEvents([
|
|
168
|
+
ev.assistant("Running two things."),
|
|
169
|
+
ev.toolCall("a", "read", "running", { path: "a" }),
|
|
170
|
+
ev.toolCall("b", "shell", "running", { command: "ls" }),
|
|
171
|
+
ev.assistant("Meanwhile…"),
|
|
172
|
+
ev.toolCall("b", "shell", "completed", { command: "ls" }, "files"),
|
|
173
|
+
ev.toolCall("a", "read", "completed", { path: "a" }, "text"),
|
|
174
|
+
ev.assistant("Both done."),
|
|
175
|
+
]);
|
|
176
|
+
expect(fold.messages.map((m) => [m.content, m.toolCalls.map((tc) => [tc.id, tc.status, tc.result])])).toEqual([
|
|
177
|
+
["Running two things.", [["a", ToolCallStatus.TOOL_CALL_COMPLETED, "text"], ["b", ToolCallStatus.TOOL_CALL_COMPLETED, "files"]]],
|
|
178
|
+
["Meanwhile…", []],
|
|
179
|
+
["Both done.", []],
|
|
180
|
+
]);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("a string args payload is parsed when it is JSON, and is nothing when it is not", () => {
|
|
184
|
+
const t = translator();
|
|
185
|
+
expect(t.translate(ev.toolCall("c1", "read", "running", '{"path":"a"}'))[0]).toMatchObject({ input: { path: "a" } });
|
|
186
|
+
expect(t.translate(ev.toolCall("c2", "read", "running", '{"path":"tru'))[0]).toMatchObject({ input: {} });
|
|
187
|
+
expect(t.translate(ev.toolCall("c3", "read", "running", undefined))[0]).toMatchObject({ input: {} });
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("a todo write is an ordinary tool call the builder keys on ToolKind.TODO and projects at completion", () => {
|
|
191
|
+
const TODOS = { todos: [{ content: "Step 1", status: "pending" }] };
|
|
192
|
+
const fold = foldCursorEvents([ev.assistant("Planning."), ev.toolCall("t1", "updateTodos", "running", TODOS)]);
|
|
193
|
+
expect(fold.row("t1").toolKind).toBe(ToolKind.TODO);
|
|
194
|
+
expect(fold.row("t1").requiresApproval).toBe(false);
|
|
195
|
+
expect(fold.status.todos, "a running write projects nothing: its args may be incomplete").toEqual({});
|
|
196
|
+
fold.event(ev.toolCall("t1", "updateTodos", "completed", TODOS, "ok"));
|
|
197
|
+
expect(Object.keys(fold.status.todos)).toEqual(["todo-0"]);
|
|
198
|
+
// The legacy name projects too; a write that errors, and a tool that is not a todo tool, never do.
|
|
199
|
+
fold.event(ev.toolCall("t2", "TodoWrite", "completed", { todos: [{ id: "x", content: "Legacy", status: "completed" }], merge: true }, "ok"));
|
|
200
|
+
expect(Object.keys(fold.status.todos).sort()).toEqual(["todo-0", "x"]);
|
|
201
|
+
fold.event(ev.toolCall("t3", "updateTodos", "error", { todos: [] }, "boom"));
|
|
202
|
+
fold.event(ev.toolCall("t4", "write_notes", "completed", { todos: [] }, "ok"));
|
|
203
|
+
expect(Object.keys(fold.status.todos).sort()).toEqual(["todo-0", "x"]);
|
|
204
|
+
// A JSON-string args payload (the SDK's other shape) projects like an object.
|
|
205
|
+
fold.event(ev.toolCall("t5", "updateTodos", "completed", JSON.stringify({ todos: [{ content: "From a string", status: "pending" }] }), "ok"));
|
|
206
|
+
expect(Object.keys(fold.status.todos)).toEqual(["todo-0"]);
|
|
207
|
+
expect(fold.status.todos["todo-0"].content).toBe("From a string");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("the task SDK event is a system_note; an empty one, the system/status/user/request/usage events, are nothing", () => {
|
|
211
|
+
const t = translator();
|
|
212
|
+
expect(t.translate(ev.task("Scanning the workspace."))).toEqual([{ kind: "system_note", text: "Scanning the workspace." }]);
|
|
213
|
+
expect(t.translate(ev.task(""))).toEqual([]);
|
|
214
|
+
expect(t.translate(ev.init())).toEqual([]);
|
|
215
|
+
expect(t.translate(ev.status("RUNNING"))).toEqual([]);
|
|
216
|
+
expect(t.translate({ type: "user", agent_id: "a", run_id: RUN, message: { role: "user", content: [{ type: "text", text: "hi" }] } })).toEqual([]);
|
|
217
|
+
expect(t.translate({ type: "request", agent_id: "a", run_id: RUN, request_id: "r" })).toEqual([]);
|
|
218
|
+
expect(t.translate({ type: "usage", agent_id: "a", run_id: RUN, usage: { inputTokens: 1, outputTokens: 1, cacheReadTokens: 0, cacheWriteTokens: 0, totalTokens: 2 } })).toEqual([]);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe("CursorTranslator — the MCP envelope is unwrapped to the inner tool", () => {
|
|
223
|
+
const MCP_ARGS = { providerIdentifier: "planton", toolName: "search_services", args: { query: "db" } };
|
|
224
|
+
const policies = new Map<string, MergedToolPolicy>([
|
|
225
|
+
["planton/search_services", { toolName: "search_services", mcpServerSlug: "planton", requiresApproval: true, approvalMessage: "Search {{args.query}} on {{tool_name}}", source: "pinned_override" }],
|
|
226
|
+
]);
|
|
227
|
+
|
|
228
|
+
it("name is the inner tool, the server its provider, the input the inner args; auto-approved without a policy (classifier_default)", () => {
|
|
229
|
+
const [started] = translator().translate(ev.toolCall("m1", "mcp", "running", MCP_ARGS));
|
|
230
|
+
expect(started).toEqual({ kind: "tool_started", callId: "m1", name: "search_services", input: { query: "db" }, mcpServerSlug: "planton", provenance: "classifier_default" });
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("a policy that requires approval is the gate, its message resolved over the inner args", () => {
|
|
234
|
+
const t = new CursorTranslator({ policies, leases: { global: false, categories: new Set() }, seeded: [] });
|
|
235
|
+
const [started] = t.translate(ev.toolCall("m1", "mcp", "running", MCP_ARGS));
|
|
236
|
+
expect(started).toMatchObject({ gate: { message: "Search db on search_services" }, provenance: "pinned_override" });
|
|
237
|
+
const fold = new CursorFold({ policies }).events(ev.toolCall("m1", "mcp", "running", MCP_ARGS));
|
|
238
|
+
expect(fold.row("m1").toolKind).toBe(ToolKind.MCP);
|
|
239
|
+
expect(fold.row("m1").argsPreview, "the row's own args, never the envelope").toBe(JSON.stringify({ query: "db" }));
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("an mcp event with no toolName is an ordinary tool named mcp, and is never gated by a built-in category", () => {
|
|
243
|
+
const [started] = translator().translate(ev.toolCall("m1", "mcp", "running", { providerIdentifier: "x" }));
|
|
244
|
+
expect(started).toMatchObject({ name: "mcp", mcpServerSlug: "", input: { providerIdentifier: "x" } });
|
|
245
|
+
expect((started as { gate?: unknown }).gate).toBeUndefined();
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// ── Sub-agents ────────────────────────────────────────────────────
|
|
250
|
+
|
|
251
|
+
describe("CursorTranslator — a task tool call is a sub-agent, its transcript delivered at completion", () => {
|
|
252
|
+
const TASK_ARGS = { subagentType: "helper", description: "Read README.md", prompt: "Read README.md and report." };
|
|
253
|
+
const STEPS = {
|
|
254
|
+
status: "success",
|
|
255
|
+
value: {
|
|
256
|
+
conversationSteps: [
|
|
257
|
+
{ assistantMessage: { text: "Looking." } },
|
|
258
|
+
{ toolCall: { toolCallId: "sub-read-1", readToolCall: { args: { path: "README.md" }, result: { success: { content: "# Hi" } } } } },
|
|
259
|
+
{ assistantMessage: { text: "It says hi." } },
|
|
260
|
+
],
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
it("running → sub_agent_started (name, subject, input from the args) BEFORE the row's tool_started; a duplicate running re-announces nothing", () => {
|
|
265
|
+
const t = translator();
|
|
266
|
+
expect(t.translate(ev.toolCall("task-1", "task", "running", TASK_ARGS))).toEqual([
|
|
267
|
+
{ kind: "sub_agent_started", subAgentId: "task-1", name: "helper", subject: "Read README.md", input: "Read README.md and report." },
|
|
268
|
+
{ kind: "tool_started", callId: "task-1", name: "task", input: TASK_ARGS, mcpServerSlug: "" },
|
|
269
|
+
]);
|
|
270
|
+
expect(kinds(t.translate(ev.toolCall("task-1", "task", "running", TASK_ARGS)))).toEqual(["tool_started"]);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("completed → the row's finish, the child's scoped events, then sub_agent_finished with the same output string", () => {
|
|
274
|
+
const t = translator();
|
|
275
|
+
t.translate(ev.toolCall("task-1", "task", "running", TASK_ARGS));
|
|
276
|
+
const out = t.translate(ev.toolCall("task-1", "task", "completed", TASK_ARGS, STEPS));
|
|
277
|
+
expect(kinds(out)).toEqual([
|
|
278
|
+
"tool_started", "tool_finished",
|
|
279
|
+
"message_start", "text_delta", "message_finish",
|
|
280
|
+
"tool_started", "tool_finished",
|
|
281
|
+
"message_start", "text_delta", "message_finish",
|
|
282
|
+
"sub_agent_finished",
|
|
283
|
+
]);
|
|
284
|
+
expect(out.slice(2, -1).every((e) => "subAgentId" in e && e.subAgentId === "task-1")).toBe(true);
|
|
285
|
+
expect(out.at(-1)).toEqual({ kind: "sub_agent_finished", subAgentId: "task-1", output: JSON.stringify(STEPS) });
|
|
286
|
+
expect(out[1]).toEqual({ kind: "tool_finished", callId: "task-1", result: JSON.stringify(STEPS) });
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("through the builder: the row is SUBAGENT kind, the sub-agent COMPLETED, and the child's tool joins the assistant step that proposed it", () => {
|
|
290
|
+
const fold = foldCursorEvents([
|
|
291
|
+
ev.assistant("Delegating."),
|
|
292
|
+
ev.toolCall("task-1", "task", "running", TASK_ARGS),
|
|
293
|
+
ev.toolCall("task-1", "task", "completed", TASK_ARGS, STEPS),
|
|
294
|
+
]).finalize();
|
|
295
|
+
expect(fold.row("task-1").toolKind).toBe(ToolKind.SUBAGENT);
|
|
296
|
+
const sub = fold.status.subAgentExecutions[0];
|
|
297
|
+
expect(sub.id).toBe("task-1");
|
|
298
|
+
expect(sub.status).toBe(SubAgentStatus.SUB_AGENT_COMPLETED);
|
|
299
|
+
expect(sub.output).toBe(JSON.stringify(STEPS));
|
|
300
|
+
expect(sub.messages.map((m) => [m.type, m.content, m.toolCalls.map((tc) => tc.id), m.isStreaming])).toEqual([
|
|
301
|
+
[MessageType.MESSAGE_AI, "Looking.", ["sub-read-1"], false],
|
|
302
|
+
[MessageType.MESSAGE_AI, "It says hi.", [], false],
|
|
303
|
+
]);
|
|
304
|
+
const read = sub.messages[0].toolCalls[0];
|
|
305
|
+
expect(read.name).toBe("read");
|
|
306
|
+
expect(read.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
307
|
+
expect(read.result).toBe(JSON.stringify({ content: "# Hi" }));
|
|
308
|
+
expect(read.argsPreview).toBe(JSON.stringify({ path: "README.md" }));
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("a completion with no running before it still opens the sub-agent and delivers its transcript", () => {
|
|
312
|
+
const fold = foldCursorEvents([ev.toolCall("task-1", "task", "completed", TASK_ARGS, STEPS)]);
|
|
313
|
+
expect(fold.status.subAgentExecutions[0].status).toBe(SubAgentStatus.SUB_AGENT_COMPLETED);
|
|
314
|
+
expect(fold.status.subAgentExecutions[0].messages).toHaveLength(2);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("error → the row's error then sub_agent_failed with the same text", () => {
|
|
318
|
+
const t = translator();
|
|
319
|
+
t.translate(ev.toolCall("task-1", "task", "running", TASK_ARGS));
|
|
320
|
+
expect(t.translate(ev.toolCall("task-1", "task", "error", TASK_ARGS, "child crashed")).slice(1)).toEqual([
|
|
321
|
+
{ kind: "tool_error", callId: "task-1", message: "child crashed" },
|
|
322
|
+
{ kind: "sub_agent_failed", subAgentId: "task-1", error: "child crashed" },
|
|
323
|
+
]);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("the child's steps: legacy type-envelope form, direct-keyed tool steps, failure oneofs, thinking, and skipped malformed or empty steps", () => {
|
|
327
|
+
const fold = foldCursorEvents([
|
|
328
|
+
ev.toolCall("task-1", "task", "completed", TASK_ARGS, {
|
|
329
|
+
status: "success",
|
|
330
|
+
value: {
|
|
331
|
+
conversationSteps: [
|
|
332
|
+
{ type: "thinkingMessage", message: { text: "Let me look." } },
|
|
333
|
+
{ type: "assistantMessage", message: { text: "" } },
|
|
334
|
+
{ toolCall: { toolCallId: "glob-1", globToolCall: { args: { pattern: "*.md" }, result: { success: { files: ["README.md"] } } } } },
|
|
335
|
+
{ toolCall: { toolCallId: "sh-1", shellToolCall: { args: { command: "rm -rf /" }, result: { permissionDenied: { error: "blocked by approval gate" } } } } },
|
|
336
|
+
{ toolCall: { toolCallId: "sh-2", shellToolCall: { args: {}, result: { rejected: { reason: "user rejected" } } } } },
|
|
337
|
+
{ toolCall: { toolCallId: "malformed" } },
|
|
338
|
+
{ toolCall: { readToolCall: { args: { path: "x" } } } },
|
|
339
|
+
{ unknownStep: {} },
|
|
340
|
+
{ type: "assistantMessage", message: { text: "Found it." } },
|
|
341
|
+
],
|
|
342
|
+
},
|
|
343
|
+
}),
|
|
344
|
+
]).finalize();
|
|
345
|
+
const sub = fold.status.subAgentExecutions[0];
|
|
346
|
+
expect(sub.messages.map((m) => [m.type, m.content, m.toolCalls.map((tc) => [tc.id, tc.status, tc.error])])).toEqual([
|
|
347
|
+
[MessageType.MESSAGE_THINKING, "Let me look.", []],
|
|
348
|
+
// Four tool steps with no assistant step before them share one empty message.
|
|
349
|
+
[MessageType.MESSAGE_AI, "", [
|
|
350
|
+
["glob-1", ToolCallStatus.TOOL_CALL_COMPLETED, ""],
|
|
351
|
+
["sh-1", ToolCallStatus.TOOL_CALL_FAILED, JSON.stringify({ error: "blocked by approval gate" })],
|
|
352
|
+
["sh-2", ToolCallStatus.TOOL_CALL_FAILED, JSON.stringify({ reason: "user rejected" })],
|
|
353
|
+
["sub-read-6", ToolCallStatus.TOOL_CALL_COMPLETED, ""],
|
|
354
|
+
]],
|
|
355
|
+
[MessageType.MESSAGE_AI, "Found it.", []],
|
|
356
|
+
]);
|
|
357
|
+
expect(sub.messages[1].toolCalls[0].toolKind).toBe(ToolKind.SEARCH);
|
|
358
|
+
expect(sub.messages[1].toolCalls[0].result).toBe(JSON.stringify({ files: ["README.md"] }));
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it("a result without conversationSteps, or not an object, yields an empty child transcript", () => {
|
|
362
|
+
for (const result of [undefined, "just text", { status: "success", value: {} }, { status: "success", value: { conversationSteps: [] } }]) {
|
|
363
|
+
const fold = foldCursorEvents([ev.toolCall("task-1", "task", "completed", TASK_ARGS, result)]);
|
|
364
|
+
expect(fold.status.subAgentExecutions[0].messages).toHaveLength(0);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("extractSubagentName: string, object with name, object with kind, unspecified kind, description fallback, generic", () => {
|
|
369
|
+
expect(extractSubagentName({ subagentType: "explorer" })).toBe("explorer");
|
|
370
|
+
expect(extractSubagentName({ subagentType: { kind: "generalPurpose", name: "researcher" } })).toBe("researcher");
|
|
371
|
+
expect(extractSubagentName({ subagentType: { kind: "generalPurpose" } })).toBe("generalPurpose");
|
|
372
|
+
expect(extractSubagentName({ subagentType: { kind: "unspecified" }, description: "Find the config" })).toBe("Find the config");
|
|
373
|
+
expect(extractSubagentName({ description: "Find the config" })).toBe("Find the config");
|
|
374
|
+
expect(extractSubagentName({})).toBe("task");
|
|
375
|
+
expect(extractSubagentName(null)).toBe("task");
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// ── The resumed agent's re-issued call ────────────────────────────
|
|
380
|
+
|
|
381
|
+
describe("CursorTranslator — a resumed agent's re-run lands on its seeded WAITING row by identity", () => {
|
|
382
|
+
function seededStatus(rows: Array<{ id: string; action?: ApprovalAction; path?: string }>) {
|
|
383
|
+
return create(AgentExecutionStatusSchema, {
|
|
384
|
+
messages: [
|
|
385
|
+
create(AgentMessageSchema, {
|
|
386
|
+
type: MessageType.MESSAGE_AI,
|
|
387
|
+
content: "I'll edit the notes.",
|
|
388
|
+
toolCalls: rows.map((r) => create(ToolCallSchema, {
|
|
389
|
+
id: r.id,
|
|
390
|
+
name: "edit",
|
|
391
|
+
status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
|
|
392
|
+
requiresApproval: true,
|
|
393
|
+
approvalAction: r.action ?? ApprovalAction.APPROVE,
|
|
394
|
+
args: { path: r.path ?? "notes.md", content: "A\n" },
|
|
395
|
+
argsPreview: JSON.stringify({ path: r.path ?? "notes.md", content: "A\n" }),
|
|
396
|
+
})),
|
|
397
|
+
}),
|
|
398
|
+
],
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
it("the fresh call id's start is emitted with the SEEDED id, and its later events follow the alias", () => {
|
|
403
|
+
const status = seededStatus([{ id: "seeded-1" }]);
|
|
404
|
+
const fold = new CursorFold({ status }).events(
|
|
405
|
+
ev.toolCall("fresh-1", "edit", "running", { path: "notes.md", content: "A\n" }),
|
|
406
|
+
ev.toolCall("fresh-1", "edit", "completed", { path: "notes.md", content: "A\n" }, "ok"),
|
|
407
|
+
);
|
|
408
|
+
expect(fold.rows().map((tc) => tc.id), "one row, the seeded one").toEqual(["seeded-1"]);
|
|
409
|
+
expect(fold.row("seeded-1").status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
410
|
+
expect(fold.row("seeded-1").result).toBe("ok");
|
|
411
|
+
expect(fold.row("seeded-1").approvalAction, "the decision is kept for audit").toBe(ApprovalAction.APPROVE);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it("a declined seeded row is a closed gate: a same-identity call is a NEW row", () => {
|
|
415
|
+
const status = seededStatus([{ id: "declined-1", action: ApprovalAction.REJECT }]);
|
|
416
|
+
const fold = new CursorFold({ status }).events(ev.toolCall("fresh-1", "edit", "running", { path: "notes.md", content: "A\n" }));
|
|
417
|
+
expect(fold.rows().map((tc) => tc.id)).toEqual(["declined-1", "fresh-1"]);
|
|
418
|
+
expect(fold.row("declined-1").status).toBe(ToolCallStatus.TOOL_CALL_WAITING_APPROVAL);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it("two co-pending seeded rows of one identity are consumed in transcript order; a third call is fresh", () => {
|
|
422
|
+
const status = seededStatus([{ id: "seeded-1" }, { id: "seeded-2" }]);
|
|
423
|
+
const fold = new CursorFold({ status }).events(
|
|
424
|
+
ev.toolCall("fresh-1", "edit", "running", { path: "notes.md", content: "A\n" }),
|
|
425
|
+
ev.toolCall("fresh-2", "edit", "running", { path: "notes.md", content: "A\n" }),
|
|
426
|
+
ev.toolCall("fresh-3", "edit", "running", { path: "notes.md", content: "A\n" }),
|
|
427
|
+
);
|
|
428
|
+
expect(fold.rows().map((tc) => [tc.id, tc.status])).toEqual([
|
|
429
|
+
["seeded-1", ToolCallStatus.TOOL_CALL_RUNNING],
|
|
430
|
+
["seeded-2", ToolCallStatus.TOOL_CALL_RUNNING],
|
|
431
|
+
["fresh-3", ToolCallStatus.TOOL_CALL_RUNNING],
|
|
432
|
+
]);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it("a different content for the same file is a different identity and never rides the seeded row", () => {
|
|
436
|
+
const status = seededStatus([{ id: "seeded-1" }]);
|
|
437
|
+
const fold = new CursorFold({ status }).events(ev.toolCall("fresh-1", "edit", "running", { path: "notes.md", content: "B\n" }));
|
|
438
|
+
expect(fold.rows().map((tc) => tc.id)).toEqual(["seeded-1", "fresh-1"]);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it("the identity is the hook's token space: a seeded shell row matches a re-run of the same command", () => {
|
|
442
|
+
const status = create(AgentExecutionStatusSchema, {
|
|
443
|
+
messages: [create(AgentMessageSchema, {
|
|
444
|
+
type: MessageType.MESSAGE_AI,
|
|
445
|
+
content: "Building.",
|
|
446
|
+
toolCalls: [create(ToolCallSchema, { id: "sh-seeded", name: "shell", status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL, args: { command: "make" }, approvalAction: ApprovalAction.APPROVE })],
|
|
447
|
+
})],
|
|
448
|
+
});
|
|
449
|
+
expect(grantToken("shell", "make")).toBeTruthy();
|
|
450
|
+
const fold = new CursorFold({ status }).events(ev.toolCall("sh-new", "shell", "running", { command: "make" }));
|
|
451
|
+
expect(fold.rows().map((tc) => tc.id)).toEqual(["sh-seeded"]);
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// ── The delta channel ─────────────────────────────────────────────
|
|
456
|
+
|
|
457
|
+
describe("CursorTranslator — the delta channel is queued, held for unannounced calls, and stamps its own instant", () => {
|
|
458
|
+
const MODEL_CALL = "mc-1";
|
|
459
|
+
const started = (callId: string): InteractionUpdate => ({ type: "tool-call-started", callId, modelCallId: MODEL_CALL, toolCall: { type: "shell", args: { command: "make" } } });
|
|
460
|
+
const output = (callId: string | undefined, data: string): InteractionUpdate => ({ type: "shell-output-delta", event: callId ? { callId, type: "stdout", data } : { type: "stdout", data } });
|
|
461
|
+
const completed = (callId: string): InteractionUpdate => ({
|
|
462
|
+
type: "tool-call-completed", callId, modelCallId: MODEL_CALL,
|
|
463
|
+
toolCall: { type: "shell", args: { command: "make" }, result: { status: "success", value: { exitCode: 0, signal: "", stdout: "ok", stderr: "", executionTime: 5 } } },
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
beforeEach(() => {
|
|
467
|
+
vi.useFakeTimers({ toFake: ["Date"] });
|
|
468
|
+
vi.setSystemTime(new Date("2026-01-01T00:00:00.000Z"));
|
|
469
|
+
});
|
|
470
|
+
afterEach(() => {
|
|
471
|
+
vi.useRealTimers();
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it("observeDelta applies nothing; drainDeltas returns what was queued, in order, and empties", () => {
|
|
475
|
+
const t = translator();
|
|
476
|
+
t.translate(ev.toolCall("c1", "shell", "running", { command: "make" }));
|
|
477
|
+
t.observeDelta(output("c1", "a"));
|
|
478
|
+
t.observeDelta(output("c1", "b"));
|
|
479
|
+
expect(t.drainDeltas()).toEqual([
|
|
480
|
+
{ kind: "tool_output_delta", callId: "c1", delta: "a" },
|
|
481
|
+
{ kind: "tool_output_delta", callId: "c1", delta: "b" },
|
|
482
|
+
]);
|
|
483
|
+
expect(t.drainDeltas()).toEqual([]);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it("an output delta before the stream announces its call is held and replayed after the tool_started", () => {
|
|
487
|
+
const t = translator();
|
|
488
|
+
t.observeDelta(output("c1", "early "));
|
|
489
|
+
expect(t.drainDeltas(), "nothing to apply: no row yet").toEqual([]);
|
|
490
|
+
t.translate(ev.toolCall("c1", "shell", "running", { command: "make" }));
|
|
491
|
+
expect(t.drainDeltas()).toEqual([{ kind: "tool_output_delta", callId: "c1", delta: "early " }]);
|
|
492
|
+
// Through the builder, in the loop's order: nothing is lost.
|
|
493
|
+
const fold = new CursorFold()
|
|
494
|
+
.delta(output("c1", "early "))
|
|
495
|
+
.event(ev.toolCall("c1", "shell", "running", { command: "make" }))
|
|
496
|
+
.delta(output("c1", "late"))
|
|
497
|
+
.event(ev.assistant("Done."));
|
|
498
|
+
expect(fold.row("c1").result).toBe("early late");
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it("an output delta naming no call belongs to the last shell the delta channel started", () => {
|
|
502
|
+
const t = translator();
|
|
503
|
+
t.translate(ev.toolCall("c1", "shell", "running", { command: "make" }));
|
|
504
|
+
t.observeDelta(started("c1"));
|
|
505
|
+
t.observeDelta(output(undefined, "out"));
|
|
506
|
+
expect(t.drainDeltas()).toEqual([{ kind: "tool_output_delta", callId: "c1", delta: "out" }]);
|
|
507
|
+
// No shell started yet and no call named: nowhere to put it.
|
|
508
|
+
const fresh = translator();
|
|
509
|
+
fresh.observeDelta(output(undefined, "orphan"));
|
|
510
|
+
expect(fresh.drainDeltas()).toEqual([]);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it("the completion delta is a tool_finished with no result and the instant it was OBSERVED, not applied", () => {
|
|
514
|
+
const t = translator();
|
|
515
|
+
t.translate(ev.toolCall("c1", "shell", "running", { command: "make" }));
|
|
516
|
+
vi.setSystemTime(new Date("2026-01-01T00:00:07.000Z"));
|
|
517
|
+
t.observeDelta(completed("c1"));
|
|
518
|
+
vi.setSystemTime(new Date("2026-01-01T00:00:20.000Z"));
|
|
519
|
+
expect(t.drainDeltas()).toEqual([{ kind: "tool_finished", callId: "c1", result: "", observedAt: "2026-01-01T00:00:07.000Z" }]);
|
|
520
|
+
// Through the builder: completedAt is the delta's instant; the stream's later completion supplies the result and moves no stamp.
|
|
521
|
+
const fold = new CursorFold().event(ev.toolCall("c2", "shell", "running", { command: "make" }));
|
|
522
|
+
vi.setSystemTime(new Date("2026-01-01T00:00:07.000Z"));
|
|
523
|
+
fold.delta(completed("c2"));
|
|
524
|
+
vi.setSystemTime(new Date("2026-01-01T00:00:20.000Z"));
|
|
525
|
+
fold.event(ev.thinking("Hmm."));
|
|
526
|
+
expect(fold.row("c2").status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
527
|
+
expect(fold.row("c2").completedAt).toBe("2026-01-01T00:00:07.000Z");
|
|
528
|
+
fold.event(ev.toolCall("c2", "shell", "completed", { command: "make" }, "ok"));
|
|
529
|
+
expect(fold.row("c2").result).toBe("ok");
|
|
530
|
+
expect(fold.row("c2").completedAt).toBe("2026-01-01T00:00:07.000Z");
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it("a completion delta whose result status is error is a tool_error at the observed instant; the stream's text follows", () => {
|
|
534
|
+
const failedDelta: InteractionUpdate = {
|
|
535
|
+
type: "tool-call-completed", callId: "c1", modelCallId: MODEL_CALL,
|
|
536
|
+
toolCall: { type: "shell", args: { command: "make" }, result: { status: "error", error: "command not found" } },
|
|
537
|
+
};
|
|
538
|
+
const t = translator();
|
|
539
|
+
t.translate(ev.toolCall("c1", "shell", "running", { command: "make" }));
|
|
540
|
+
vi.setSystemTime(new Date("2026-01-01T00:00:07.000Z"));
|
|
541
|
+
t.observeDelta(failedDelta);
|
|
542
|
+
expect(t.drainDeltas()).toEqual([{ kind: "tool_error", callId: "c1", message: "", observedAt: "2026-01-01T00:00:07.000Z" }]);
|
|
543
|
+
// Through the builder: FAILED from the delta's instant; a completion delta
|
|
544
|
+
// whose result is absent is a success.
|
|
545
|
+
const fold = new CursorFold().event(ev.toolCall("c2", "shell", "running", { command: "make" }));
|
|
546
|
+
vi.setSystemTime(new Date("2026-01-01T00:00:07.000Z"));
|
|
547
|
+
fold.delta({ ...failedDelta, callId: "c2" });
|
|
548
|
+
vi.setSystemTime(new Date("2026-01-01T00:00:20.000Z"));
|
|
549
|
+
fold.event(ev.thinking("It failed."));
|
|
550
|
+
expect(fold.row("c2").status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
|
|
551
|
+
expect(fold.row("c2").completedAt).toBe("2026-01-01T00:00:07.000Z");
|
|
552
|
+
expect(fold.row("c2").approvalRequestedAt, "a gated shell").toBe("2026-01-01T00:00:07.000Z");
|
|
553
|
+
fold.event(ev.toolCall("c2", "shell", "error", { command: "make" }, "sh: make: command not found"));
|
|
554
|
+
expect(fold.row("c2").error).toBe("sh: make: command not found");
|
|
555
|
+
expect(fold.row("c2").status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
|
|
556
|
+
const noResult = new CursorFold().event(ev.toolCall("c3", "read", "running", { path: "x" }));
|
|
557
|
+
noResult.delta({ type: "tool-call-completed", callId: "c3", modelCallId: MODEL_CALL, toolCall: { type: "read", args: { path: "x" } } }).drain();
|
|
558
|
+
expect(noResult.row("c3").status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it("a held completion for a resumed agent's re-run is re-targeted at the seeded row it aliases to", () => {
|
|
562
|
+
const status = create(AgentExecutionStatusSchema, {
|
|
563
|
+
messages: [create(AgentMessageSchema, {
|
|
564
|
+
type: MessageType.MESSAGE_AI, content: "Building.",
|
|
565
|
+
toolCalls: [create(ToolCallSchema, { id: "sh-seeded", name: "shell", status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL, args: { command: "make" }, approvalAction: ApprovalAction.APPROVE })],
|
|
566
|
+
})],
|
|
567
|
+
});
|
|
568
|
+
const fold = new CursorFold({ status })
|
|
569
|
+
.delta(output("sh-new", "building…"))
|
|
570
|
+
.event(ev.toolCall("sh-new", "shell", "running", { command: "make" }));
|
|
571
|
+
expect(fold.rows().map((tc) => tc.id)).toEqual(["sh-seeded"]);
|
|
572
|
+
expect(fold.row("sh-seeded").result).toBe("building…");
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it("turn-ended, thinking-completed, text and thinking deltas carry no transcript fact", () => {
|
|
576
|
+
const t = translator();
|
|
577
|
+
t.translate(ev.toolCall("c1", "shell", "running", { command: "make" }));
|
|
578
|
+
t.observeDelta({ type: "turn-ended", usage: { inputTokens: 1, outputTokens: 1, cacheReadTokens: 0, cacheWriteTokens: 0 } });
|
|
579
|
+
t.observeDelta({ type: "thinking-completed", thinkingDurationMs: 12 });
|
|
580
|
+
t.observeDelta({ type: "text-delta", text: "tok" });
|
|
581
|
+
t.observeDelta({ type: "thinking-delta", text: "tok" });
|
|
582
|
+
expect(t.drainDeltas()).toEqual([]);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
it("shell output text is read from data, output or text; anything else is nothing", () => {
|
|
586
|
+
const t = translator();
|
|
587
|
+
t.translate(ev.toolCall("c1", "shell", "running", { command: "make" }));
|
|
588
|
+
t.observeDelta({ type: "shell-output-delta", event: { callId: "c1", output: "via output" } });
|
|
589
|
+
t.observeDelta({ type: "shell-output-delta", event: { call_id: "c1", text: "via text" } });
|
|
590
|
+
t.observeDelta({ type: "shell-output-delta", event: { id: "c1", data: "via data" } });
|
|
591
|
+
t.observeDelta({ type: "shell-output-delta", event: { callId: "c1", bytes: 12 } });
|
|
592
|
+
expect(t.drainDeltas().map((e) => (e as { delta: string }).delta)).toEqual(["via output", "via text", "via data"]);
|
|
593
|
+
});
|
|
594
|
+
});
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* row that hung inside the harness on a completing turn is settled and
|
|
7
7
|
* disclosed (#965).
|
|
8
8
|
*
|
|
9
|
-
* The turn's FILE CHANGES are not the boundary's since
|
|
9
|
+
* The turn's FILE CHANGES are not the boundary's since #1096: the runtime
|
|
10
10
|
* captures them once after `runTurn` returns (`harness/capture.ts`, proven
|
|
11
11
|
* in `harness/__tests__/capture.test.ts` and the kit's runtime arm), so the
|
|
12
12
|
* five capture arms this file carried until then — the candidate, the clean
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* runtime's status-based rules. One arm changed meaning with the lift and is
|
|
16
16
|
* pinned below: a turn whose only pause would have been a file review is a
|
|
17
17
|
* COMPLETING turn to this boundary, so its hung rows are settled before the
|
|
18
|
-
* runtime's review, not after it
|
|
18
|
+
* runtime's review, not after it.
|
|
19
19
|
*
|
|
20
20
|
* Runs against a temp workspace dir with in-memory transcript + status
|
|
21
21
|
* protos; the gate reads a denied file's `before` from that dir.
|
|
@@ -36,8 +36,10 @@ import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/ag
|
|
|
36
36
|
import { MessageType, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
37
37
|
import { isToolCallRowHidden } from "../../../shared/tool-row.js";
|
|
38
38
|
import { denialLedgerPath } from "../approval-state.js";
|
|
39
|
-
import { toolCallIdentityToken
|
|
39
|
+
import { toolCallIdentityToken } from "../approval-state.js";
|
|
40
|
+
import { UNRESOLVED_TOOL_CALL_ERROR } from "../boundary-rows.js";
|
|
40
41
|
import { runTurnBoundary, type TurnBoundaryOptions } from "../turn-boundary.js";
|
|
42
|
+
import { TranscriptBuilder } from "../../../harness/transcript/builder.js";
|
|
41
43
|
|
|
42
44
|
const EXEC_ID = "exec-boundary-1";
|
|
43
45
|
|
|
@@ -71,6 +73,7 @@ function streamedEdit(id: string, path: string, content: string): AgentMessage {
|
|
|
71
73
|
function boundaryOpts(status: AgentExecutionStatus, overrides?: Partial<TurnBoundaryOptions>): TurnBoundaryOptions {
|
|
72
74
|
return {
|
|
73
75
|
status,
|
|
76
|
+
transcript: new TranscriptBuilder(EXEC_ID, status),
|
|
74
77
|
executionId: EXEC_ID,
|
|
75
78
|
hitlDir,
|
|
76
79
|
primaryWorkspaceDir: repo,
|
|
@@ -324,9 +327,9 @@ describe("runTurnBoundary — unresolved tool calls on a completing turn (issue
|
|
|
324
327
|
expect(isToolCallRowHidden(hung), "collapsed by the reconcile, not settled by the sweep").toBe(true);
|
|
325
328
|
});
|
|
326
329
|
|
|
327
|
-
it("a turn whose only change is a flowed file edit is COMPLETING here: the hung row is settled before the runtime's review
|
|
330
|
+
it("a turn whose only change is a flowed file edit is COMPLETING here: the hung row is settled before the runtime's review", async () => {
|
|
328
331
|
const status = newStatus();
|
|
329
|
-
// The edit flowed and the tree changed — until
|
|
332
|
+
// The edit flowed and the tree changed — until #1096 that made the turn
|
|
330
333
|
// `waiting` and hid the hung row behind the review; the runtime captures
|
|
331
334
|
// the edit after this boundary, so the boundary now tells the truth
|
|
332
335
|
// about the row first.
|