@stigmer/runner 3.0.9-dev.20260615153829 → 3.1.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/dist/.build-fingerprint +1 -1
- package/dist/__test-utils__/approval-contract/types.d.ts +174 -0
- package/dist/__test-utils__/approval-contract/types.js +24 -0
- package/dist/__test-utils__/approval-contract/types.js.map +1 -0
- package/dist/activities/call-agent-status.d.ts +19 -1
- package/dist/activities/call-agent-status.js +64 -5
- package/dist/activities/call-agent-status.js.map +1 -1
- package/dist/activities/call-llm.js +19 -53
- package/dist/activities/call-llm.js.map +1 -1
- package/dist/activities/classify-tool-approvals.d.ts +24 -0
- package/dist/activities/classify-tool-approvals.js +69 -17
- package/dist/activities/classify-tool-approvals.js.map +1 -1
- package/dist/activities/discover-mcp-server.d.ts +7 -0
- package/dist/activities/discover-mcp-server.js +11 -1
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +140 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +130 -0
- package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -0
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.d.ts +18 -0
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +123 -0
- package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -0
- package/dist/activities/execute-cursor/approval-policy.d.ts +17 -61
- package/dist/activities/execute-cursor/approval-policy.js +8 -120
- package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
- package/dist/activities/execute-cursor/approval-state.d.ts +219 -9
- package/dist/activities/execute-cursor/approval-state.js +233 -17
- package/dist/activities/execute-cursor/approval-state.js.map +1 -1
- package/dist/activities/execute-cursor/capture-flow.d.ts +127 -0
- package/dist/activities/execute-cursor/capture-flow.js +234 -0
- package/dist/activities/execute-cursor/capture-flow.js.map +1 -0
- package/dist/activities/execute-cursor/cas-observations.d.ts +96 -0
- package/dist/activities/execute-cursor/cas-observations.js +184 -0
- package/dist/activities/execute-cursor/cas-observations.js.map +1 -0
- package/dist/activities/execute-cursor/command-provenance.d.ts +62 -0
- package/dist/activities/execute-cursor/command-provenance.js +128 -0
- package/dist/activities/execute-cursor/command-provenance.js.map +1 -0
- package/dist/activities/execute-cursor/exact-apply.d.ts +110 -0
- package/dist/activities/execute-cursor/exact-apply.js +204 -0
- package/dist/activities/execute-cursor/exact-apply.js.map +1 -0
- package/dist/activities/execute-cursor/hook-script.d.ts +53 -24
- package/dist/activities/execute-cursor/hook-script.js +310 -47
- package/dist/activities/execute-cursor/hook-script.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +8 -1
- package/dist/activities/execute-cursor/index.js +500 -64
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/message-translator.d.ts +258 -11
- package/dist/activities/execute-cursor/message-translator.js +836 -90
- package/dist/activities/execute-cursor/message-translator.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +20 -23
- package/dist/activities/execute-cursor/prompt-builder.js +74 -15
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/workspace-setup.d.ts +17 -0
- package/dist/activities/execute-cursor/workspace-setup.js +212 -33
- package/dist/activities/execute-cursor/workspace-setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.d.ts +19 -0
- package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js +143 -0
- package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js.map +1 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +88 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +81 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -0
- package/dist/activities/execute-deep-agent/approval-file-change.d.ts +47 -0
- package/dist/activities/execute-deep-agent/approval-file-change.js +68 -0
- package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -0
- package/dist/activities/execute-deep-agent/attachment-injector.d.ts +8 -1
- package/dist/activities/execute-deep-agent/attachment-injector.js +7 -7
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
- package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +42 -0
- package/dist/activities/execute-deep-agent/cas-capture-backend.js +47 -0
- package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.d.ts +79 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.js +112 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.js.map +1 -0
- package/dist/activities/execute-deep-agent/hitl.d.ts +16 -3
- package/dist/activities/execute-deep-agent/hitl.js +11 -3
- package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.d.ts +2 -1
- package/dist/activities/execute-deep-agent/index.js +393 -33
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/inline-publisher.d.ts +7 -1
- package/dist/activities/execute-deep-agent/inline-publisher.js +23 -2
- package/dist/activities/execute-deep-agent/inline-publisher.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +53 -2
- package/dist/activities/execute-deep-agent/setup.js +149 -92
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.d.ts +36 -0
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.js +56 -0
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.js.map +1 -0
- package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +34 -1
- package/dist/activities/execute-deep-agent/status-builder-shared.js +26 -25
- package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
- package/dist/activities/execute-deep-agent/status-builder.d.ts +17 -5
- package/dist/activities/execute-deep-agent/status-builder.js +44 -3
- package/dist/activities/execute-deep-agent/status-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/streaming-side-effects.js +2 -19
- package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
- package/dist/activities/execute-deep-agent/streaming.js +3 -15
- package/dist/activities/execute-deep-agent/streaming.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +25 -7
- package/dist/activities/execute-deep-agent/subagent-transformer.js +23 -7
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +30 -3
- package/dist/activities/execute-deep-agent/subagent-wiring.js +29 -3
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/activities/execute-deep-agent/v3-status-builder.js +32 -2
- package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
- package/dist/claimcheck/payload-codec.js +9 -5
- package/dist/claimcheck/payload-codec.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +2 -0
- package/dist/client/stigmer-client.js +2 -0
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/middleware/approval-gate.d.ts +85 -4
- package/dist/middleware/approval-gate.js +165 -38
- package/dist/middleware/approval-gate.js.map +1 -1
- package/dist/middleware/types.d.ts +2 -5
- package/dist/shared/activity-input.d.ts +43 -0
- package/dist/shared/activity-input.js +17 -0
- package/dist/shared/activity-input.js.map +1 -0
- package/dist/shared/approval-canonicalize.d.ts +19 -0
- package/dist/shared/approval-canonicalize.js +119 -0
- package/dist/shared/approval-canonicalize.js.map +1 -0
- package/dist/shared/approval-fingerprint.d.ts +106 -0
- package/dist/shared/approval-fingerprint.js +113 -0
- package/dist/shared/approval-fingerprint.js.map +1 -0
- package/dist/shared/approval-policy.d.ts +182 -12
- package/dist/shared/approval-policy.js +213 -27
- package/dist/shared/approval-policy.js.map +1 -1
- package/dist/shared/args-preview.d.ts +52 -0
- package/dist/shared/args-preview.js +93 -0
- package/dist/shared/args-preview.js.map +1 -0
- package/dist/shared/artifact-storage.d.ts +19 -1
- package/dist/shared/artifact-storage.js +48 -11
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/file-change.d.ts +44 -0
- package/dist/shared/file-change.js +57 -0
- package/dist/shared/file-change.js.map +1 -0
- package/dist/shared/file-tools.d.ts +107 -0
- package/dist/shared/file-tools.js +168 -0
- package/dist/shared/file-tools.js.map +1 -0
- package/dist/shared/filereview/capture.d.ts +202 -0
- package/dist/shared/filereview/capture.js +498 -0
- package/dist/shared/filereview/capture.js.map +1 -0
- package/dist/shared/filereview/cas-substrate.d.ts +190 -0
- package/dist/shared/filereview/cas-substrate.js +284 -0
- package/dist/shared/filereview/cas-substrate.js.map +1 -0
- package/dist/shared/filereview/digest.d.ts +40 -0
- package/dist/shared/filereview/digest.js +66 -0
- package/dist/shared/filereview/digest.js.map +1 -0
- package/dist/shared/filereview/events.d.ts +170 -0
- package/dist/shared/filereview/events.js +298 -0
- package/dist/shared/filereview/events.js.map +1 -0
- package/dist/shared/filereview/git-substrate.d.ts +175 -0
- package/dist/shared/filereview/git-substrate.js +439 -0
- package/dist/shared/filereview/git-substrate.js.map +1 -0
- package/dist/shared/filereview/index.d.ts +11 -0
- package/dist/shared/filereview/index.js +12 -0
- package/dist/shared/filereview/index.js.map +1 -0
- package/dist/shared/filereview/secret-paths.d.ts +63 -0
- package/dist/shared/filereview/secret-paths.js +105 -0
- package/dist/shared/filereview/secret-paths.js.map +1 -0
- package/dist/shared/fingerprint-secret.d.ts +26 -0
- package/dist/shared/fingerprint-secret.js +47 -0
- package/dist/shared/fingerprint-secret.js.map +1 -0
- package/dist/shared/model-client.d.ts +51 -0
- package/dist/shared/model-client.js +77 -0
- package/dist/shared/model-client.js.map +1 -0
- package/dist/shared/plan-artifact.js +0 -2
- package/dist/shared/plan-artifact.js.map +1 -1
- package/dist/shared/status-offload.d.ts +83 -9
- package/dist/shared/status-offload.js +399 -79
- package/dist/shared/status-offload.js.map +1 -1
- package/dist/shared/status.js +14 -1
- package/dist/shared/status.js.map +1 -1
- package/dist/shared/tool-kind.d.ts +19 -0
- package/dist/shared/tool-kind.js +13 -0
- package/dist/shared/tool-kind.js.map +1 -1
- package/dist/shared/tool-row.d.ts +88 -0
- package/dist/shared/tool-row.js +127 -0
- package/dist/shared/tool-row.js.map +1 -0
- package/dist/shared/workspace/platform-dir.d.ts +25 -0
- package/dist/shared/workspace/platform-dir.js +38 -2
- package/dist/shared/workspace/platform-dir.js.map +1 -1
- package/dist/workflows/call-agent-orchestrator.js +56 -7
- package/dist/workflows/call-agent-orchestrator.js.map +1 -1
- package/dist/workflows/connect-mcp-server.d.ts +50 -0
- package/dist/workflows/connect-mcp-server.js +136 -15
- package/dist/workflows/connect-mcp-server.js.map +1 -1
- package/dist/workflows/types.d.ts +8 -0
- package/package.json +2 -2
- package/src/__test-utils__/approval-contract/contract.ts +224 -0
- package/src/__test-utils__/approval-contract/types.ts +179 -0
- package/src/__test-utils__/fake-artifact-storage.ts +72 -0
- package/src/__tests__/approval-gateway-contract.test.ts +29 -0
- package/src/__tests__/claimcheck-codec.test.ts +16 -53
- package/src/__tests__/golden-e2e.test.ts +2 -0
- package/src/__tests__/runner-token-coordinator.test.ts +3 -3
- package/src/activities/__tests__/call-agent-status.test.ts +135 -0
- package/src/activities/__tests__/call-llm.test.ts +1 -1
- package/src/activities/__tests__/classify-tool-approvals.test.ts +208 -1
- package/src/activities/__tests__/discover-mcp-server.test.ts +30 -0
- package/src/activities/__tests__/workflow-event-activities.test.ts +2 -1
- package/src/activities/call-agent-status.ts +74 -4
- package/src/activities/call-llm.ts +18 -63
- package/src/activities/classify-tool-approvals.ts +101 -19
- package/src/activities/discover-mcp-server.ts +29 -1
- package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +216 -0
- package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +148 -0
- package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +41 -9
- package/src/activities/execute-cursor/__tests__/approval-state.test.ts +292 -0
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +68 -1
- package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +1005 -0
- package/src/activities/execute-cursor/__tests__/cas-observations.test.ts +187 -0
- package/src/activities/execute-cursor/__tests__/coarse-fingerprint.test.ts +97 -0
- package/src/activities/execute-cursor/__tests__/command-provenance.test.ts +240 -0
- package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +203 -0
- package/src/activities/execute-cursor/__tests__/exact-apply.test.ts +375 -0
- package/src/activities/execute-cursor/__tests__/hitl-ledger.test.ts +1294 -24
- package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +446 -0
- package/src/activities/execute-cursor/__tests__/hook-script.test.ts +384 -110
- package/src/activities/execute-cursor/__tests__/message-translator.test.ts +171 -25
- package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +189 -0
- package/src/activities/execute-cursor/__tests__/tool-result-image.test.ts +44 -23
- package/src/activities/execute-cursor/__tests__/workspace-setup.test.ts +190 -10
- package/src/activities/execute-cursor/approval-policy.ts +28 -159
- package/src/activities/execute-cursor/approval-state.ts +366 -18
- package/src/activities/execute-cursor/capture-flow.ts +323 -0
- package/src/activities/execute-cursor/cas-observations.ts +204 -0
- package/src/activities/execute-cursor/command-provenance.ts +168 -0
- package/src/activities/execute-cursor/exact-apply.ts +253 -0
- package/src/activities/execute-cursor/hook-script.ts +317 -51
- package/src/activities/execute-cursor/index.ts +575 -67
- package/src/activities/execute-cursor/message-translator.ts +963 -89
- package/src/activities/execute-cursor/prompt-builder.ts +80 -14
- package/src/activities/execute-cursor/workspace-setup.ts +257 -42
- package/src/activities/execute-deep-agent/__test-utils__/gateway-substrate.ts +180 -0
- package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +134 -0
- package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +84 -0
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +11 -24
- package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +64 -0
- package/src/activities/execute-deep-agent/__tests__/cas-capture-observer.test.ts +163 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-integration.test.ts +8 -5
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +342 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +284 -0
- package/src/activities/execute-deep-agent/__tests__/hitl.test.ts +14 -36
- package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +31 -13
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +349 -0
- package/src/activities/execute-deep-agent/__tests__/stamp-flowed-rows.test.ts +119 -0
- package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +91 -10
- package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +9 -9
- package/src/activities/execute-deep-agent/__tests__/subagent-approval-propagation.test.ts +160 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-gitignored-capture.test.ts +213 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +3 -6
- package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +84 -1
- package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +4 -1
- package/src/activities/execute-deep-agent/approval-file-change.ts +80 -0
- package/src/activities/execute-deep-agent/attachment-injector.ts +20 -11
- package/src/activities/execute-deep-agent/cas-capture-backend.ts +66 -0
- package/src/activities/execute-deep-agent/cas-capture-observer.ts +125 -0
- package/src/activities/execute-deep-agent/hitl.ts +23 -7
- package/src/activities/execute-deep-agent/index.ts +463 -40
- package/src/activities/execute-deep-agent/inline-publisher.ts +27 -4
- package/src/activities/execute-deep-agent/setup.ts +223 -125
- package/src/activities/execute-deep-agent/stamp-flowed-rows.ts +64 -0
- package/src/activities/execute-deep-agent/status-builder-shared.ts +62 -23
- package/src/activities/execute-deep-agent/status-builder.ts +63 -8
- package/src/activities/execute-deep-agent/streaming-side-effects.ts +2 -16
- package/src/activities/execute-deep-agent/streaming.ts +3 -13
- package/src/activities/execute-deep-agent/subagent-transformer.ts +53 -13
- package/src/activities/execute-deep-agent/subagent-wiring.ts +50 -3
- package/src/activities/execute-deep-agent/v3-status-builder.ts +36 -2
- package/src/claimcheck/payload-codec.ts +8 -8
- package/src/client/stigmer-client.ts +9 -1
- package/src/middleware/__tests__/approval-gate.test.ts +488 -4
- package/src/middleware/approval-gate.ts +247 -38
- package/src/middleware/types.ts +5 -5
- package/src/shared/__tests__/activity-input.test.ts +78 -0
- package/src/shared/__tests__/approval-canonicalize.test.ts +106 -0
- package/src/shared/__tests__/approval-fingerprint.test.ts +115 -0
- package/src/shared/__tests__/approval-policy.test.ts +274 -40
- package/src/shared/__tests__/args-preview.test.ts +78 -0
- package/src/shared/__tests__/artifact-storage-extended.test.ts +62 -10
- package/src/shared/__tests__/artifact-storage.test.ts +123 -11
- package/src/shared/__tests__/file-change.test.ts +85 -0
- package/src/shared/__tests__/file-tools.test.ts +90 -0
- package/src/shared/__tests__/fingerprint-secret.test.ts +51 -0
- package/src/shared/__tests__/lease-scope-corpus.test.ts +56 -0
- package/src/shared/__tests__/model-client.test.ts +162 -0
- package/src/shared/__tests__/plan-artifact.test.ts +11 -26
- package/src/shared/__tests__/policy-source-corpus.test.ts +58 -0
- package/src/shared/__tests__/status-offload.test.ts +573 -16
- package/src/shared/__tests__/status.test.ts +4 -5
- package/src/shared/__tests__/tool-kind.test.ts +24 -1
- package/src/shared/__tests__/tool-row.test.ts +221 -0
- package/src/shared/activity-input.ts +57 -0
- package/src/shared/approval-canonicalize.ts +159 -0
- package/src/shared/approval-fingerprint.ts +148 -0
- package/src/shared/approval-policy.ts +303 -27
- package/src/shared/args-preview.ts +98 -0
- package/src/shared/artifact-storage.ts +62 -11
- package/src/shared/checkpointer/__tests__/http-saver.test.ts +1 -2
- package/src/shared/file-change.ts +64 -0
- package/src/shared/file-tools.ts +169 -0
- package/src/shared/filereview/__tests__/capture.test.ts +856 -0
- package/src/shared/filereview/__tests__/cas-substrate.test.ts +404 -0
- package/src/shared/filereview/__tests__/digest.test.ts +100 -0
- package/src/shared/filereview/__tests__/events.test.ts +245 -0
- package/src/shared/filereview/__tests__/git-substrate.test.ts +362 -0
- package/src/shared/filereview/__tests__/proxy-reconcile.test.ts +286 -0
- package/src/shared/filereview/__tests__/secret-paths.test.ts +121 -0
- package/src/shared/filereview/capture.ts +727 -0
- package/src/shared/filereview/cas-substrate.ts +401 -0
- package/src/shared/filereview/digest.ts +83 -0
- package/src/shared/filereview/events.ts +449 -0
- package/src/shared/filereview/git-substrate.ts +555 -0
- package/src/shared/filereview/index.ts +60 -0
- package/src/shared/filereview/secret-paths.ts +121 -0
- package/src/shared/fingerprint-secret.ts +53 -0
- package/src/shared/model-client.ts +122 -0
- package/src/shared/plan-artifact.ts +0 -2
- package/src/shared/status-offload.ts +433 -77
- package/src/shared/status.ts +13 -0
- package/src/shared/tool-kind.ts +33 -0
- package/src/shared/tool-row.ts +135 -0
- package/src/shared/workspace/platform-dir.ts +41 -2
- package/src/workflow-engine/__tests__/golden-execution.test.ts +35 -18
- package/src/workflow-engine/__tests__/tasks/try.test.ts +1 -1
- package/src/workflows/__tests__/connect-mcp-server.test.ts +304 -29
- package/src/workflows/call-agent-orchestrator.ts +53 -6
- package/src/workflows/connect-mcp-server.ts +179 -24
- package/src/workflows/types.ts +8 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression: two SEQUENTIAL approval gates under the ephemeral memory
|
|
3
|
+
* checkpointer (the OSS local / desktop default — config.ts resolves
|
|
4
|
+
* `checkpointerType: "memory"`).
|
|
5
|
+
*
|
|
6
|
+
* Unlike the durable (http) checkpointer — which resumes via Command(resume) and
|
|
7
|
+
* re-emits only post-checkpoint events — the memory checkpointer is recreated
|
|
8
|
+
* empty every invocation (factory.ts), so the Temporal workflow's post-approval
|
|
9
|
+
* re-invocation REPLAYS the graph from scratch. The langgraph input is just the
|
|
10
|
+
* original user message (setup.ts), and the blind-FIFO mock LLM advances exactly
|
|
11
|
+
* one gate per invocation, so the replay reaches the NEXT gate (gate B) directly.
|
|
12
|
+
*
|
|
13
|
+
* The defect this pins: on that gate-B re-invocation the live checkpoint is
|
|
14
|
+
* empty, so a status rebuilt from an empty proto emits a transcript that holds
|
|
15
|
+
* ONLY gate B and silently drops gate A's already-committed tool-call id. The
|
|
16
|
+
* server's append-only-at-identity guard (update_status.go
|
|
17
|
+
* nonTerminalTranscriptRegression) then rejects the update and keeps the old
|
|
18
|
+
* messages, but projects pending_approvals from those kept messages — gate A is
|
|
19
|
+
* already decided, so pending=0, and WAITING_FOR_APPROVAL + pending=0 makes the
|
|
20
|
+
* workflow auto-resume, skipping gate B. The fix seeds the status from the
|
|
21
|
+
* persisted transcript whenever the execution already has history, so gate B's
|
|
22
|
+
* update is a superset of gate A rather than a replacement.
|
|
23
|
+
*
|
|
24
|
+
* This test exercises BOTH invocations through the real activity, asserting the
|
|
25
|
+
* gate-A tool call survives into the gate-B update — the single behavior that,
|
|
26
|
+
* if regressed, reintroduces the silent skip.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
30
|
+
import { makeInMemoryArtifactStorage } from "../../../__test-utils__/fake-artifact-storage.js";
|
|
31
|
+
import { create, clone } from "@bufbuild/protobuf";
|
|
32
|
+
import {
|
|
33
|
+
AgentExecutionStatusSchema,
|
|
34
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
35
|
+
import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
36
|
+
import {
|
|
37
|
+
AgentMessageSchema,
|
|
38
|
+
ToolCallSchema,
|
|
39
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
40
|
+
import {
|
|
41
|
+
ApprovalAction,
|
|
42
|
+
ExecutionPhase,
|
|
43
|
+
MessageType,
|
|
44
|
+
ToolCallStatus,
|
|
45
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
46
|
+
import type { V3ProtocolEvent } from "../v3-event-recorder.js";
|
|
47
|
+
import type { Config } from "../../../config.js";
|
|
48
|
+
import type { SetupResult } from "../setup.js";
|
|
49
|
+
|
|
50
|
+
// The Temporal activity context is unavailable outside a worker.
|
|
51
|
+
vi.mock("@temporalio/activity", () => ({
|
|
52
|
+
Context: {
|
|
53
|
+
current: () => ({
|
|
54
|
+
cancellationSignal: { aborted: false },
|
|
55
|
+
heartbeat: vi.fn(),
|
|
56
|
+
}),
|
|
57
|
+
},
|
|
58
|
+
CancelledFailure: class CancelledFailure extends Error {},
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
vi.mock("../../../idle-watchdog.js", () => ({
|
|
62
|
+
activityStarted: vi.fn(),
|
|
63
|
+
activityFinished: vi.fn(),
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
// Capture every persisted status (the gRPC chokepoint) for assertions.
|
|
67
|
+
const { persistedStatuses } = vi.hoisted(() => ({
|
|
68
|
+
persistedStatuses: [] as AgentExecutionStatus[],
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
vi.mock("../../../client/stigmer-client.js", () => ({
|
|
72
|
+
StigmerClient: vi.fn().mockImplementation(() => ({
|
|
73
|
+
updateStatus: vi.fn(async (_id: string, status: AgentExecutionStatus) => {
|
|
74
|
+
// Snapshot point-in-time: persistStatus mutates the same object in place.
|
|
75
|
+
persistedStatuses.push(clone(AgentExecutionStatusSchema, status));
|
|
76
|
+
return { signal: 0 };
|
|
77
|
+
}),
|
|
78
|
+
getExecution: vi.fn(),
|
|
79
|
+
})),
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
// performSetup is mocked per-invocation to return a fake memory-replay SetupResult.
|
|
83
|
+
vi.mock("../setup.js", () => ({ performSetup: vi.fn() }));
|
|
84
|
+
|
|
85
|
+
import { createDeepAgentActivities } from "../index.js";
|
|
86
|
+
import { performSetup } from "../setup.js";
|
|
87
|
+
|
|
88
|
+
const GATE_A_ID = "toolu_gate_a";
|
|
89
|
+
const GATE_B_ID = "toolu_gate_b";
|
|
90
|
+
|
|
91
|
+
function v3Event(
|
|
92
|
+
seq: number,
|
|
93
|
+
method: string,
|
|
94
|
+
data: unknown,
|
|
95
|
+
namespace: string[] = [],
|
|
96
|
+
): V3ProtocolEvent {
|
|
97
|
+
return {
|
|
98
|
+
type: "event",
|
|
99
|
+
seq,
|
|
100
|
+
method,
|
|
101
|
+
params: { namespace, timestamp: Date.now(), data },
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Events for a replayed turn that proposes a gated tool but pauses BEFORE
|
|
107
|
+
* execution. The model turn commits no transcript message (no text/tool deltas);
|
|
108
|
+
* the gated tool call is authored downstream by the activity from the pending
|
|
109
|
+
* interrupt, matching the live e2e's single-tool gate update. Two events keep the
|
|
110
|
+
* v3 loop from erroring on an empty stream.
|
|
111
|
+
*/
|
|
112
|
+
function gateTurnEvents(): V3ProtocolEvent[] {
|
|
113
|
+
return [
|
|
114
|
+
v3Event(0, "messages", { event: "message-start", run_id: "g1" }),
|
|
115
|
+
v3Event(1, "messages", { event: "message-finish", run_id: "g1" }),
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function mockV3Run(events: V3ProtocolEvent[]) {
|
|
120
|
+
return {
|
|
121
|
+
[Symbol.asyncIterator]: async function* () {
|
|
122
|
+
for (const e of events) yield e;
|
|
123
|
+
},
|
|
124
|
+
output: Promise.resolve({ messages: [] }),
|
|
125
|
+
abort: vi.fn(),
|
|
126
|
+
signal: new AbortController().signal,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The empty live checkpoint a freshly-recreated MemorySaver reports pre-stream. */
|
|
131
|
+
const EMPTY_REPLAY_STATE = { values: { messages: [] as unknown[] }, tasks: [] };
|
|
132
|
+
|
|
133
|
+
/** The paused graph state after the replayed stream reaches the next gate. */
|
|
134
|
+
function gatedGraphState(toolCallId: string, toolName: string, filePath: string) {
|
|
135
|
+
return {
|
|
136
|
+
values: {
|
|
137
|
+
messages: [{
|
|
138
|
+
role: "assistant",
|
|
139
|
+
content: "",
|
|
140
|
+
tool_calls: [{ id: toolCallId, name: toolName, args: { file_path: filePath, content: "x\n" } }],
|
|
141
|
+
}],
|
|
142
|
+
},
|
|
143
|
+
tasks: [{
|
|
144
|
+
id: "task-gate",
|
|
145
|
+
interrupts: [{
|
|
146
|
+
value: {
|
|
147
|
+
tool_call_id: toolCallId,
|
|
148
|
+
tool_name: toolName,
|
|
149
|
+
message: `Execute ${toolName}`,
|
|
150
|
+
},
|
|
151
|
+
resumeValue: undefined,
|
|
152
|
+
}],
|
|
153
|
+
}],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* A memory-checkpointer invocation that replays from scratch and re-pauses at a
|
|
159
|
+
* single gate: getState reports an empty live checkpoint before the stream and a
|
|
160
|
+
* pending interrupt after it.
|
|
161
|
+
*/
|
|
162
|
+
function fakeMemoryGateSetup(opts: {
|
|
163
|
+
gateId: string;
|
|
164
|
+
gateName: string;
|
|
165
|
+
filePath: string;
|
|
166
|
+
executionStatus: AgentExecutionStatus;
|
|
167
|
+
}): SetupResult {
|
|
168
|
+
let streamed = false;
|
|
169
|
+
const gated = gatedGraphState(opts.gateId, opts.gateName, opts.filePath);
|
|
170
|
+
|
|
171
|
+
const agentGraph = {
|
|
172
|
+
getState: vi.fn(async () => (streamed ? gated : EMPTY_REPLAY_STATE)),
|
|
173
|
+
streamEvents: vi.fn(async () => {
|
|
174
|
+
streamed = true;
|
|
175
|
+
return mockV3Run(gateTurnEvents());
|
|
176
|
+
}),
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
agentGraph,
|
|
181
|
+
langgraphConfig: { configurable: { thread_id: "thread-seq" } },
|
|
182
|
+
langgraphInput: { messages: [{ role: "user", content: "Write A then B" }] },
|
|
183
|
+
execution: {
|
|
184
|
+
spec: { message: "Write file A, then write file B." },
|
|
185
|
+
status: opts.executionStatus,
|
|
186
|
+
},
|
|
187
|
+
agent: {},
|
|
188
|
+
session: { spec: { workspaceEntries: [] } },
|
|
189
|
+
workspaceBackend: {
|
|
190
|
+
rootDir: "/tmp/ws",
|
|
191
|
+
exists: vi.fn(async () => false),
|
|
192
|
+
readFile: vi.fn(async () => ""),
|
|
193
|
+
},
|
|
194
|
+
mcpConnection: null,
|
|
195
|
+
mergedEnvVars: {},
|
|
196
|
+
secretKeys: new Set<string>(),
|
|
197
|
+
modelName: "claude-sonnet",
|
|
198
|
+
gracefulStop: undefined,
|
|
199
|
+
artifactStorage: makeInMemoryArtifactStorage({ urlBase: "https://artifacts.local/" }).storage,
|
|
200
|
+
provisionResults: [],
|
|
201
|
+
approvalPolicies: new Map(),
|
|
202
|
+
toolServerMap: new Map(),
|
|
203
|
+
leasedCategories: new Set(),
|
|
204
|
+
globalBypass: false,
|
|
205
|
+
hasStructuredOutput: false,
|
|
206
|
+
streamVersion: "v3",
|
|
207
|
+
} as unknown as SetupResult;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Run-1 transcript persisted on the execution, with the user's APPROVE recorded. */
|
|
211
|
+
function approvedGateAStatus(): AgentExecutionStatus {
|
|
212
|
+
return create(AgentExecutionStatusSchema, {
|
|
213
|
+
phase: ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL,
|
|
214
|
+
startedAt: "2026-06-20T00:00:00.000Z",
|
|
215
|
+
messages: [
|
|
216
|
+
create(AgentMessageSchema, {
|
|
217
|
+
type: MessageType.MESSAGE_AI,
|
|
218
|
+
content: "",
|
|
219
|
+
timestamp: "2026-06-20T00:00:01.000Z",
|
|
220
|
+
toolCalls: [
|
|
221
|
+
create(ToolCallSchema, {
|
|
222
|
+
id: GATE_A_ID,
|
|
223
|
+
name: "write_file",
|
|
224
|
+
status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
|
|
225
|
+
requiresApproval: true,
|
|
226
|
+
approvalMessage: "Execute write_file",
|
|
227
|
+
// The user approved gate A; SubmitApproval records the decision on
|
|
228
|
+
// the persisted transcript (status stays WAITING_APPROVAL until the
|
|
229
|
+
// resumed tool runs), which is what re-invokes the activity.
|
|
230
|
+
approvalAction: ApprovalAction.APPROVE,
|
|
231
|
+
}),
|
|
232
|
+
],
|
|
233
|
+
}),
|
|
234
|
+
],
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const memoryConfig: Config = {
|
|
239
|
+
taskQueue: "test-queue",
|
|
240
|
+
temporalAddress: "localhost:7233",
|
|
241
|
+
temporalNamespace: "default",
|
|
242
|
+
stigmerBackendEndpoint: "http://localhost:7234",
|
|
243
|
+
stigmerToken: null,
|
|
244
|
+
cursorApiKey: "",
|
|
245
|
+
workspaceRootDir: "/tmp/test",
|
|
246
|
+
mode: "local",
|
|
247
|
+
proxyEndpoint: null,
|
|
248
|
+
maxConcurrentActivities: 5,
|
|
249
|
+
idleTimeoutSeconds: null,
|
|
250
|
+
cloudModeEnabled: false,
|
|
251
|
+
checkpointerType: "memory",
|
|
252
|
+
checkpointerProxyEndpoint: null,
|
|
253
|
+
primaryModel: "claude-sonnet",
|
|
254
|
+
cursorStreamStallTimeoutMs: 180000,
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
function toolCallIds(status: AgentExecutionStatus): string[] {
|
|
258
|
+
return status.messages.flatMap(m => m.toolCalls).map(tc => tc.id);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
describe("ExecuteDeepAgent — sequential gates under the memory checkpointer", () => {
|
|
262
|
+
beforeEach(() => {
|
|
263
|
+
persistedStatuses.length = 0;
|
|
264
|
+
vi.mocked(performSetup).mockReset();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("preserves gate A's committed tool call when resuming into gate B", async () => {
|
|
268
|
+
const activities = createDeepAgentActivities(memoryConfig);
|
|
269
|
+
|
|
270
|
+
// ── Invocation 1: first run reaches gate A (no prior history to seed). ──
|
|
271
|
+
vi.mocked(performSetup).mockResolvedValueOnce(
|
|
272
|
+
fakeMemoryGateSetup({
|
|
273
|
+
gateId: GATE_A_ID,
|
|
274
|
+
gateName: "write_file",
|
|
275
|
+
filePath: "a.txt",
|
|
276
|
+
executionStatus: create(AgentExecutionStatusSchema, {}),
|
|
277
|
+
}),
|
|
278
|
+
);
|
|
279
|
+
await activities.ExecuteDeepAgent("exec-seq", "thread-seq");
|
|
280
|
+
|
|
281
|
+
const afterGateA = persistedStatuses.at(-1)!;
|
|
282
|
+
expect(afterGateA.phase).toBe(ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL);
|
|
283
|
+
expect(toolCallIds(afterGateA)).toContain(GATE_A_ID);
|
|
284
|
+
|
|
285
|
+
// ── User approves gate A; the server records the decision on the persisted
|
|
286
|
+
// transcript. The next invocation re-invocation reads this status. ──
|
|
287
|
+
const approved = clone(AgentExecutionStatusSchema, afterGateA);
|
|
288
|
+
for (const m of approved.messages) {
|
|
289
|
+
for (const tc of m.toolCalls) {
|
|
290
|
+
if (tc.id === GATE_A_ID) tc.approvalAction = ApprovalAction.APPROVE;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ── Invocation 2: re-invoked after approval. The memory checkpointer replays
|
|
295
|
+
// from scratch (empty live checkpoint) and the mock serves gate B. ──
|
|
296
|
+
persistedStatuses.length = 0;
|
|
297
|
+
vi.mocked(performSetup).mockResolvedValueOnce(
|
|
298
|
+
fakeMemoryGateSetup({
|
|
299
|
+
gateId: GATE_B_ID,
|
|
300
|
+
gateName: "write_file",
|
|
301
|
+
filePath: "b.txt",
|
|
302
|
+
executionStatus: approved,
|
|
303
|
+
}),
|
|
304
|
+
);
|
|
305
|
+
await activities.ExecuteDeepAgent("exec-seq", "thread-seq");
|
|
306
|
+
|
|
307
|
+
const afterGateB = persistedStatuses.at(-1)!;
|
|
308
|
+
const ids = toolCallIds(afterGateB);
|
|
309
|
+
|
|
310
|
+
// Gate B is the new pending approval.
|
|
311
|
+
expect(afterGateB.phase).toBe(ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL);
|
|
312
|
+
expect(ids.filter(id => id === GATE_B_ID)).toHaveLength(1);
|
|
313
|
+
|
|
314
|
+
// The regression guard: gate A's committed tool call survives the resume.
|
|
315
|
+
// Dropping it lets the server guard reject the update and project pending=0,
|
|
316
|
+
// which auto-resumes the workflow and silently skips gate B.
|
|
317
|
+
expect(ids).toContain(GATE_A_ID);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
/** A status that is a strict superset of the persisted transcript is what the
|
|
321
|
+
* server's append-only-at-identity guard accepts (no committed id dropped). */
|
|
322
|
+
it("emits a gate-B transcript that is a superset of the persisted gate-A transcript", async () => {
|
|
323
|
+
const activities = createDeepAgentActivities(memoryConfig);
|
|
324
|
+
|
|
325
|
+
const approved = approvedGateAStatus();
|
|
326
|
+
const priorIds = toolCallIds(approved);
|
|
327
|
+
|
|
328
|
+
vi.mocked(performSetup).mockResolvedValueOnce(
|
|
329
|
+
fakeMemoryGateSetup({
|
|
330
|
+
gateId: GATE_B_ID,
|
|
331
|
+
gateName: "write_file",
|
|
332
|
+
filePath: "b.txt",
|
|
333
|
+
executionStatus: approved,
|
|
334
|
+
}),
|
|
335
|
+
);
|
|
336
|
+
await activities.ExecuteDeepAgent("exec-seq", "thread-seq");
|
|
337
|
+
|
|
338
|
+
const afterGateB = persistedStatuses.at(-1)!;
|
|
339
|
+
const ids = new Set(toolCallIds(afterGateB));
|
|
340
|
+
|
|
341
|
+
// Every previously-committed tool-call id is still present (no drop), and the
|
|
342
|
+
// transcript did not shrink — exactly the two regressions the server guard
|
|
343
|
+
// rejects (update_status.go nonTerminalTranscriptRegression).
|
|
344
|
+
for (const id of priorIds) {
|
|
345
|
+
expect(ids.has(id)).toBe(true);
|
|
346
|
+
}
|
|
347
|
+
expect(afterGateB.messages.length).toBeGreaterThanOrEqual(approved.messages.length);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the deep-agent turn-boundary flowed-row stamping
|
|
3
|
+
* (`stamp-flowed-rows.ts`), with emphasis on the sub-agent path added by the
|
|
4
|
+
* DD-24 follow-up: sub-agent edit rows fold into the parent turn's change set,
|
|
5
|
+
* so they carry the parent change set id, scoped to this turn by tool-call-id
|
|
6
|
+
* novelty (they lack the already-stamped/hidden shields the top-level pass has).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect } from "vitest";
|
|
10
|
+
import { create } from "@bufbuild/protobuf";
|
|
11
|
+
import {
|
|
12
|
+
AgentMessageSchema,
|
|
13
|
+
ToolCallSchema,
|
|
14
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
15
|
+
import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
16
|
+
import { SubAgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
|
|
17
|
+
import { ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
18
|
+
import { collectSubAgentToolCallIds } from "../../../shared/tool-row.js";
|
|
19
|
+
import {
|
|
20
|
+
stampFlowedFileEditRows,
|
|
21
|
+
stampFlowedSubAgentFileEditRows,
|
|
22
|
+
} from "../stamp-flowed-rows.js";
|
|
23
|
+
|
|
24
|
+
const CHANGE_SET_ID = "exec-1:0";
|
|
25
|
+
|
|
26
|
+
/** A streamed (COMPLETED) file-edit tool call, as the deep-agent would record. */
|
|
27
|
+
function editMessage(id: string, path: string, name = "write_file"): AgentMessage {
|
|
28
|
+
return create(AgentMessageSchema, {
|
|
29
|
+
type: 1, // MESSAGE_AI
|
|
30
|
+
toolCalls: [
|
|
31
|
+
create(ToolCallSchema, {
|
|
32
|
+
id,
|
|
33
|
+
name,
|
|
34
|
+
status: ToolCallStatus.TOOL_CALL_COMPLETED,
|
|
35
|
+
args: { file_path: path, content: "x" },
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function subAgent(subId: string, ...messages: AgentMessage[]) {
|
|
42
|
+
return create(SubAgentExecutionSchema, { id: subId, name: "code_editor", messages });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe("stampFlowedFileEditRows (deep-agent)", () => {
|
|
46
|
+
it("stamps a flowed write/delete row and leaves a non-file row untouched", () => {
|
|
47
|
+
const write = editMessage("tc-1", "src/a.ts", "write_file");
|
|
48
|
+
const shell = create(AgentMessageSchema, {
|
|
49
|
+
type: 1,
|
|
50
|
+
toolCalls: [create(ToolCallSchema, { id: "tc-2", name: "shell", status: ToolCallStatus.TOOL_CALL_COMPLETED })],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
stampFlowedFileEditRows([write, shell], CHANGE_SET_ID);
|
|
54
|
+
|
|
55
|
+
expect(write.toolCalls[0].fileChangeSetId).toBe(CHANGE_SET_ID);
|
|
56
|
+
expect(shell.toolCalls[0].fileChangeSetId).toBe("");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("skips a tool-call id in skipToolCallIds (current-turn scoping)", () => {
|
|
60
|
+
const prior = editMessage("tc-prior", "src/old.ts");
|
|
61
|
+
const current = editMessage("tc-current", "src/new.ts");
|
|
62
|
+
|
|
63
|
+
stampFlowedFileEditRows([prior, current], CHANGE_SET_ID, new Set(["tc-prior"]));
|
|
64
|
+
|
|
65
|
+
expect(prior.toolCalls[0].fileChangeSetId).toBe(""); // skipped
|
|
66
|
+
expect(current.toolCalls[0].fileChangeSetId).toBe(CHANGE_SET_ID);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("never overwrites an already-stamped row", () => {
|
|
70
|
+
const row = editMessage("tc-1", "src/a.ts");
|
|
71
|
+
row.toolCalls[0].fileChangeSetId = "exec-1:prior";
|
|
72
|
+
|
|
73
|
+
stampFlowedFileEditRows([row], CHANGE_SET_ID);
|
|
74
|
+
|
|
75
|
+
expect(row.toolCalls[0].fileChangeSetId).toBe("exec-1:prior");
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("stampFlowedSubAgentFileEditRows (deep-agent)", () => {
|
|
80
|
+
it("stamps a current-turn sub-agent's edit row with the parent change set id", () => {
|
|
81
|
+
const sub = subAgent("sa-1", editMessage("sa-tc-1", "src/sub.ts"));
|
|
82
|
+
|
|
83
|
+
stampFlowedSubAgentFileEditRows([sub], CHANGE_SET_ID, new Set());
|
|
84
|
+
|
|
85
|
+
expect(sub.messages[0].toolCalls[0].fileChangeSetId).toBe(CHANGE_SET_ID);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("stamps a fresh continuation row of a spanning sub-agent, not its prior row", () => {
|
|
89
|
+
// A sub-agent that paused on an internal tool gate spans invocations: its
|
|
90
|
+
// sub-agent id and its earlier row pre-exist on resume, but the continuation
|
|
91
|
+
// edit row carries a fresh tool-call id and belongs to the completing turn.
|
|
92
|
+
const priorRow = editMessage("sa-tc-old", "src/before.ts");
|
|
93
|
+
const spanning = subAgent("sa-span", priorRow);
|
|
94
|
+
const priorToolCallIds = collectSubAgentToolCallIds([spanning]); // {sa-tc-old}
|
|
95
|
+
|
|
96
|
+
// On resume the same sub-agent appends a new edit row.
|
|
97
|
+
spanning.messages.push(editMessage("sa-tc-new", "src/after.ts"));
|
|
98
|
+
|
|
99
|
+
stampFlowedSubAgentFileEditRows([spanning], "exec-1:1", priorToolCallIds);
|
|
100
|
+
|
|
101
|
+
expect(spanning.messages[0].toolCalls[0].fileChangeSetId).toBe(""); // prior row untouched
|
|
102
|
+
expect(spanning.messages[1].toolCalls[0].fileChangeSetId).toBe("exec-1:1"); // continuation stamped
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("withholds content for a sub-agent write to a tracked secret-like path (DD-12 D4 inherited)", () => {
|
|
106
|
+
const sub = subAgent("sa-1", editMessage("sa-tc-secret", "config/credentials.json"));
|
|
107
|
+
|
|
108
|
+
stampFlowedSubAgentFileEditRows([sub], CHANGE_SET_ID, new Set());
|
|
109
|
+
|
|
110
|
+
const row = sub.messages[0].toolCalls[0];
|
|
111
|
+
expect(row.fileChangeSetId).toBe(CHANGE_SET_ID); // stamped (path visible)
|
|
112
|
+
// stampFileEditRow normalizes the withheld row to `{ path }` (see tool-row.ts).
|
|
113
|
+
expect(row.args).toEqual({ path: "config/credentials.json" }); // body withheld
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("does nothing for no sub-agents", () => {
|
|
117
|
+
expect(() => stampFlowedSubAgentFileEditRows([], CHANGE_SET_ID, new Set())).not.toThrow();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -2,6 +2,11 @@ import { describe, it, expect, vi } from "vitest";
|
|
|
2
2
|
import { create } from "@bufbuild/protobuf";
|
|
3
3
|
import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
4
4
|
import {
|
|
5
|
+
AgentMessageSchema,
|
|
6
|
+
ToolCallSchema,
|
|
7
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
8
|
+
import {
|
|
9
|
+
ApprovalAction,
|
|
5
10
|
ExecutionPhase,
|
|
6
11
|
MessageType,
|
|
7
12
|
ToolCallStatus,
|
|
@@ -379,7 +384,6 @@ describe("StatusBuilder", () => {
|
|
|
379
384
|
kind: 0,
|
|
380
385
|
sizeBytes: 100n,
|
|
381
386
|
storageKey: "artifacts/exec-1/report.txt",
|
|
382
|
-
downloadUrl: "http://localhost/dl",
|
|
383
387
|
createdAt: "2026-05-19T12:00:00Z",
|
|
384
388
|
expiresAt: "",
|
|
385
389
|
entries: [],
|
|
@@ -400,7 +404,6 @@ describe("StatusBuilder", () => {
|
|
|
400
404
|
kind: 0,
|
|
401
405
|
sizeBytes: 10n,
|
|
402
406
|
storageKey: "k",
|
|
403
|
-
downloadUrl: "u",
|
|
404
407
|
createdAt: "",
|
|
405
408
|
expiresAt: "",
|
|
406
409
|
entries: [],
|
|
@@ -424,7 +427,6 @@ describe("StatusBuilder", () => {
|
|
|
424
427
|
kind: 0,
|
|
425
428
|
sizeBytes: 10n,
|
|
426
429
|
storageKey: "k",
|
|
427
|
-
downloadUrl: "u",
|
|
428
430
|
createdAt: "",
|
|
429
431
|
expiresAt: "",
|
|
430
432
|
entries: [],
|
|
@@ -542,7 +544,7 @@ describe("StatusBuilder", () => {
|
|
|
542
544
|
return {
|
|
543
545
|
policies: new Map(),
|
|
544
546
|
toolServerMap: new Map(),
|
|
545
|
-
|
|
547
|
+
globalBypass: false,
|
|
546
548
|
...overrides,
|
|
547
549
|
};
|
|
548
550
|
}
|
|
@@ -559,6 +561,7 @@ describe("StatusBuilder", () => {
|
|
|
559
561
|
mcpServerSlug: serverSlug,
|
|
560
562
|
requiresApproval: true,
|
|
561
563
|
approvalMessage: message,
|
|
564
|
+
source: "classifier_default",
|
|
562
565
|
},
|
|
563
566
|
];
|
|
564
567
|
}
|
|
@@ -595,13 +598,13 @@ describe("StatusBuilder", () => {
|
|
|
595
598
|
expect(sb.currentStatus.phase).toBe(ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL);
|
|
596
599
|
});
|
|
597
600
|
|
|
598
|
-
it("does not require approval
|
|
601
|
+
it("does not require approval under the global bypass (spec.auto_approve_all)", () => {
|
|
599
602
|
const sb = makeBuilder();
|
|
600
603
|
const [key, policy] = policyFor("github", "delete_repo");
|
|
601
604
|
sb.setApprovalProvider(makeApprovalProvider({
|
|
602
605
|
policies: new Map([[key, policy]]),
|
|
603
606
|
toolServerMap: new Map([["delete_repo", "github"]]),
|
|
604
|
-
|
|
607
|
+
globalBypass: true,
|
|
605
608
|
}));
|
|
606
609
|
|
|
607
610
|
sb.processEvent(chatStreamEvent("run-1", "deleting"));
|
|
@@ -612,11 +615,11 @@ describe("StatusBuilder", () => {
|
|
|
612
615
|
expect(tc.requiresApproval).toBe(false);
|
|
613
616
|
});
|
|
614
617
|
|
|
615
|
-
it("sets mcpServerSlug even
|
|
618
|
+
it("sets mcpServerSlug even under the global bypass (spec.auto_approve_all)", () => {
|
|
616
619
|
const sb = makeBuilder();
|
|
617
620
|
sb.setApprovalProvider(makeApprovalProvider({
|
|
618
621
|
toolServerMap: new Map([["echo", "test-mcp-server"]]),
|
|
619
|
-
|
|
622
|
+
globalBypass: true,
|
|
620
623
|
}));
|
|
621
624
|
|
|
622
625
|
sb.processEvent(chatStreamEvent("run-1", "echoing"));
|
|
@@ -713,10 +716,11 @@ describe("StatusBuilder", () => {
|
|
|
713
716
|
mcpServerSlug: serverSlug,
|
|
714
717
|
requiresApproval: true,
|
|
715
718
|
approvalMessage: "Approve?",
|
|
719
|
+
source: "classifier_default",
|
|
716
720
|
}],
|
|
717
721
|
]),
|
|
718
722
|
toolServerMap: new Map([[toolName, serverSlug]]),
|
|
719
|
-
|
|
723
|
+
globalBypass: false,
|
|
720
724
|
});
|
|
721
725
|
return sb;
|
|
722
726
|
}
|
|
@@ -1566,10 +1570,11 @@ describe("StatusBuilder", () => {
|
|
|
1566
1570
|
mcpServerSlug: "github",
|
|
1567
1571
|
requiresApproval: true,
|
|
1568
1572
|
approvalMessage: "Create PR '{{args.title}}' in {{args.repo}}?",
|
|
1573
|
+
source: "classifier_default",
|
|
1569
1574
|
}],
|
|
1570
1575
|
]),
|
|
1571
1576
|
toolServerMap: new Map([["create_pull_request", "github"]]),
|
|
1572
|
-
|
|
1577
|
+
globalBypass: false,
|
|
1573
1578
|
});
|
|
1574
1579
|
|
|
1575
1580
|
// Model streams text then calls the approval-gated tool
|
|
@@ -1771,4 +1776,80 @@ describe("StatusBuilder", () => {
|
|
|
1771
1776
|
});
|
|
1772
1777
|
});
|
|
1773
1778
|
});
|
|
1779
|
+
|
|
1780
|
+
// Durable-checkpoint resume on the legacy v2 path. The activity seeds the
|
|
1781
|
+
// builder from the persisted transcript (see seedStatusFromExecution in
|
|
1782
|
+
// index.ts); the resumed stream re-emits the gated tool's lifecycle with a
|
|
1783
|
+
// FRESH LangGraph run_id that does not match the seeded tool_call_id. The
|
|
1784
|
+
// builder must reconcile by name so the existing call is resolved in place,
|
|
1785
|
+
// preserving history with no duplicate (the documented v2 fallback; v3, the
|
|
1786
|
+
// default, matches by the exact tool_call_id).
|
|
1787
|
+
describe("resume reconciliation (durable checkpoint)", () => {
|
|
1788
|
+
const GATED_ID = "toolu_seeded_01";
|
|
1789
|
+
|
|
1790
|
+
function seededBuilder(): StatusBuilder {
|
|
1791
|
+
const status = create(AgentExecutionStatusSchema, {
|
|
1792
|
+
phase: ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL,
|
|
1793
|
+
messages: [
|
|
1794
|
+
create(AgentMessageSchema, {
|
|
1795
|
+
type: MessageType.MESSAGE_AI,
|
|
1796
|
+
content: "I'll call the gated tool.",
|
|
1797
|
+
toolCalls: [
|
|
1798
|
+
create(ToolCallSchema, {
|
|
1799
|
+
id: GATED_ID,
|
|
1800
|
+
name: "dangerous_tool",
|
|
1801
|
+
status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
|
|
1802
|
+
requiresApproval: true,
|
|
1803
|
+
approvalAction: ApprovalAction.APPROVE,
|
|
1804
|
+
mcpServerSlug: "my-server",
|
|
1805
|
+
}),
|
|
1806
|
+
],
|
|
1807
|
+
}),
|
|
1808
|
+
],
|
|
1809
|
+
});
|
|
1810
|
+
const sb = new StatusBuilder("exec-resume", status);
|
|
1811
|
+
sb.setApprovalProvider({
|
|
1812
|
+
policies: new Map([
|
|
1813
|
+
["my-server/dangerous_tool", {
|
|
1814
|
+
toolName: "dangerous_tool",
|
|
1815
|
+
mcpServerSlug: "my-server",
|
|
1816
|
+
requiresApproval: true,
|
|
1817
|
+
approvalMessage: "Execute dangerous_tool",
|
|
1818
|
+
source: "classifier_default",
|
|
1819
|
+
}],
|
|
1820
|
+
]),
|
|
1821
|
+
toolServerMap: new Map([["dangerous_tool", "my-server"]]),
|
|
1822
|
+
globalBypass: false,
|
|
1823
|
+
});
|
|
1824
|
+
return sb;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
it("resolves the seeded gated tool call in place — no duplicate, history kept", () => {
|
|
1828
|
+
const sb = seededBuilder();
|
|
1829
|
+
|
|
1830
|
+
// Resumed lifecycle: fresh run_id, no tool_call_id available in v2.
|
|
1831
|
+
sb.processEvent(toolStartEvent("run-fresh-99", "dangerous_tool", { target: "prod" }));
|
|
1832
|
+
sb.processEvent(toolEndEvent("run-fresh-99", "tool executed ok"));
|
|
1833
|
+
|
|
1834
|
+
// Following assistant turn appends after the seeded message.
|
|
1835
|
+
sb.processEvent(chatStreamEvent("llm-after", "All done."));
|
|
1836
|
+
sb.processEvent(chatEndEvent("llm-after"));
|
|
1837
|
+
|
|
1838
|
+
const status = sb.currentStatus;
|
|
1839
|
+
|
|
1840
|
+
const gated = status.messages
|
|
1841
|
+
.flatMap(m => m.toolCalls)
|
|
1842
|
+
.filter(tc => tc.id === GATED_ID);
|
|
1843
|
+
expect(gated).toHaveLength(1);
|
|
1844
|
+
expect(gated[0].status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
1845
|
+
expect(gated[0].result).toContain("tool executed ok");
|
|
1846
|
+
|
|
1847
|
+
const allText = status.messages.map(m => m.content).join("\n");
|
|
1848
|
+
expect(allText).toContain("I'll call the gated tool.");
|
|
1849
|
+
expect(allText).toContain("All done.");
|
|
1850
|
+
|
|
1851
|
+
// No re-gate: the resumed tool must not flip the run back to waiting.
|
|
1852
|
+
expect(status.phase).not.toBe(ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL);
|
|
1853
|
+
});
|
|
1854
|
+
});
|
|
1774
1855
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { makeInMemoryArtifactStorage } from "../../../__test-utils__/fake-artifact-storage.js";
|
|
2
3
|
import { create } from "@bufbuild/protobuf";
|
|
3
4
|
import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
4
5
|
import { streamExecutionV3 } from "../streaming-v3.js";
|
|
@@ -531,14 +532,13 @@ describe("streamExecutionV3", () => {
|
|
|
531
532
|
// faithfully (no truncation), and the persist chokepoint offloads the image
|
|
532
533
|
// to artifact storage so the persisted ToolCall carries outputRef.isImage.
|
|
533
534
|
const uploads: { key: string; size: number; contentType?: string }[] = [];
|
|
534
|
-
const artifactStorage = {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
} as any;
|
|
535
|
+
const { storage: artifactStorage, blobs } = makeInMemoryArtifactStorage({ urlBase: "https://artifacts.local/" });
|
|
536
|
+
artifactStorage.exists.mockResolvedValue(true);
|
|
537
|
+
artifactStorage.upload.mockImplementation(async (key: string, content: Buffer, contentType?: string) => {
|
|
538
|
+
uploads.push({ key, size: content.length, contentType });
|
|
539
|
+
blobs.set(key, Buffer.from(content));
|
|
540
|
+
return key;
|
|
541
|
+
});
|
|
542
542
|
|
|
543
543
|
const base64 = Buffer.from("PNGBYTES".repeat(64)).toString("base64");
|
|
544
544
|
const imageEnvelope = {
|
|
@@ -587,7 +587,7 @@ describe("streamExecutionV3", () => {
|
|
|
587
587
|
expect(refTc).toBeDefined();
|
|
588
588
|
expect(refTc.outputRef.isImage).toBe(true);
|
|
589
589
|
expect(refTc.outputRef.mimeType).toBe("image/png");
|
|
590
|
-
expect(refTc.outputRef.
|
|
590
|
+
expect(refTc.outputRef.storageKey).toBe("artifacts/exec-v3-test/toolcalls/toolu_1.png");
|
|
591
591
|
// The base64 must not survive inline on the persisted result.
|
|
592
592
|
expect(refTc.result).not.toContain(base64);
|
|
593
593
|
expect(uploads).toHaveLength(1);
|