@stigmer/runner 3.0.9-dev.20260616060535 → 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 +10 -0
- package/dist/activities/execute-deep-agent/hitl.js +5 -1
- 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 +370 -56
- 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 +11 -5
- package/dist/activities/execute-deep-agent/status-builder.js +6 -2
- 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 +6 -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 +2 -5
- 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 +12 -11
- 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 +15 -1
- package/src/activities/execute-deep-agent/index.ts +434 -64
- 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 +19 -7
- 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 +8 -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
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @regression file-hitl-phase0 — pins file-edit HITL fix #3 (see _projects/2026-06/20260630.01.file-change-hitl-redesign/tasks/T01_3_regression-manifest.md)
|
|
3
|
+
*
|
|
2
4
|
* Unit tests for MessageAccumulator tool call status transitions.
|
|
3
5
|
*
|
|
4
6
|
* Validates the indexed tool call tracking that prevents the
|
|
@@ -9,12 +11,16 @@
|
|
|
9
11
|
|
|
10
12
|
import { describe, it, expect } from "vitest";
|
|
11
13
|
import { create } from "@bufbuild/protobuf";
|
|
12
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
AgentMessageSchema,
|
|
16
|
+
ToolCallSchema,
|
|
17
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
13
18
|
import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
14
19
|
import {
|
|
15
20
|
MessageType,
|
|
16
21
|
ToolCallStatus,
|
|
17
22
|
SubAgentStatus,
|
|
23
|
+
ToolKind,
|
|
18
24
|
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
19
25
|
import type { SDKMessage } from "@cursor/sdk";
|
|
20
26
|
import {
|
|
@@ -106,6 +112,40 @@ describe("MessageAccumulator tool call status transitions", () => {
|
|
|
106
112
|
expect(tc.completedAt).toBeTruthy();
|
|
107
113
|
});
|
|
108
114
|
|
|
115
|
+
it("persists the Cursor shell {status,value} envelope verbatim on ToolCall.result", () => {
|
|
116
|
+
// The Cursor SDK's built-in Shell tool returns a structured envelope object;
|
|
117
|
+
// the runner stores it as a JSON string (toResultString → JSON.stringify),
|
|
118
|
+
// and the SDK view layer (normalizeShell) unwraps it for display. This locks
|
|
119
|
+
// the persisted shape so the cross-language fixture (result-views.json's
|
|
120
|
+
// cursor_shell_envelope) is grounded in what the runner actually writes,
|
|
121
|
+
// not a guess — the two halves of the bug fix stay in sync.
|
|
122
|
+
const messages: AgentMessage[] = [];
|
|
123
|
+
const acc = new MessageAccumulator(messages);
|
|
124
|
+
|
|
125
|
+
const envelope = {
|
|
126
|
+
status: "success",
|
|
127
|
+
value: {
|
|
128
|
+
exitCode: 0,
|
|
129
|
+
signal: "",
|
|
130
|
+
stdout: "total 8\n",
|
|
131
|
+
stderr: "",
|
|
132
|
+
executionTime: 1176,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
acc.processEvent(assistantEvent("r1", "Running ls."));
|
|
137
|
+
acc.processEvent(toolCallEvent("tc-shell", "Shell", "running"));
|
|
138
|
+
acc.processEvent(
|
|
139
|
+
toolCallEvent("tc-shell", "Shell", "completed", "run-1", { result: envelope }),
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const tc = findToolCallById(messages, "tc-shell")!;
|
|
143
|
+
expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
144
|
+
expect(tc.result).toBe(JSON.stringify(envelope));
|
|
145
|
+
// Round-trips to the original structure (the SDK normalizer reads .value).
|
|
146
|
+
expect(JSON.parse(tc.result)).toEqual(envelope);
|
|
147
|
+
});
|
|
148
|
+
|
|
109
149
|
it("cross-message completion: tool call completes after new AI message is created", () => {
|
|
110
150
|
const messages: AgentMessage[] = [];
|
|
111
151
|
const acc = new MessageAccumulator(messages);
|
|
@@ -418,12 +458,18 @@ describe("MessageAccumulator tool call status transitions", () => {
|
|
|
418
458
|
expect(sub.messages[1].content).toBe("Here is a summary of AI.");
|
|
419
459
|
});
|
|
420
460
|
|
|
421
|
-
|
|
461
|
+
// Real Cursor task-result shape (verified against production agent_execution
|
|
462
|
+
// blobs): each step is a protobuf-oneof keyed DIRECTLY by kind, and a tool
|
|
463
|
+
// call is { toolCall: { toolCallId, <kind>ToolCall: { args, result } } } whose
|
|
464
|
+
// result is a oneof { success | error | permissionDenied | rejected }. This is
|
|
465
|
+
// the shape that exposed the dropped-tool-calls bug: the prior parser only
|
|
466
|
+
// matched a { type:"toolCall", message } envelope that never occurs.
|
|
467
|
+
it("sub-agent extracts real direct-keyed toolCall steps from conversationSteps", () => {
|
|
422
468
|
const messages: AgentMessage[] = [];
|
|
423
469
|
const acc = new MessageAccumulator(messages);
|
|
424
470
|
|
|
425
471
|
const runningEvent = toolCallEvent("tc-sub7", "task", "running", "r1", {
|
|
426
|
-
args: { description: "
|
|
472
|
+
args: { description: "Explore the repo", prompt: "Find the README" },
|
|
427
473
|
});
|
|
428
474
|
acc.processEvent(runningEvent);
|
|
429
475
|
acc.trackSubAgentExecution(runningEvent);
|
|
@@ -433,16 +479,36 @@ describe("MessageAccumulator tool call status transitions", () => {
|
|
|
433
479
|
status: "success",
|
|
434
480
|
value: {
|
|
435
481
|
conversationSteps: [
|
|
482
|
+
{ thinkingMessage: { text: "Let me look for the README." } },
|
|
436
483
|
{
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
484
|
+
toolCall: {
|
|
485
|
+
toolCallId: "glob-1",
|
|
486
|
+
globToolCall: {
|
|
487
|
+
args: { targetDirectory: "/repo", globPattern: "**/*.md" },
|
|
488
|
+
result: { success: { path: "/repo", files: ["README.md"] } },
|
|
489
|
+
},
|
|
442
490
|
},
|
|
443
491
|
},
|
|
444
|
-
{
|
|
492
|
+
{
|
|
493
|
+
toolCall: {
|
|
494
|
+
toolCallId: "read-1",
|
|
495
|
+
readToolCall: {
|
|
496
|
+
args: { path: "/repo/README.md" },
|
|
497
|
+
result: {
|
|
498
|
+
success: {
|
|
499
|
+
content: "# Hello",
|
|
500
|
+
path: "/repo/README.md",
|
|
501
|
+
totalLines: 1,
|
|
502
|
+
fileSize: 7,
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
{ assistantMessage: { text: "I found and read the README." } },
|
|
445
509
|
],
|
|
510
|
+
agentId: "sub-agent-xyz",
|
|
511
|
+
durationMs: 4200,
|
|
446
512
|
isBackground: false,
|
|
447
513
|
backgroundReason: "unspecified",
|
|
448
514
|
},
|
|
@@ -452,16 +518,31 @@ describe("MessageAccumulator tool call status transitions", () => {
|
|
|
452
518
|
acc.trackSubAgentExecution(completedEvent);
|
|
453
519
|
|
|
454
520
|
const sub = acc.subAgentExecutions[0];
|
|
455
|
-
|
|
521
|
+
// thinking + glob tool call + read tool call + assistant text
|
|
522
|
+
expect(sub.messages).toHaveLength(4);
|
|
456
523
|
|
|
457
|
-
|
|
458
|
-
expect(
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
expect(
|
|
462
|
-
|
|
463
|
-
expect(
|
|
464
|
-
expect(
|
|
524
|
+
expect(sub.messages[0].type).toBe(MessageType.MESSAGE_THINKING);
|
|
525
|
+
expect(sub.messages[0].content).toBe("Let me look for the README.");
|
|
526
|
+
|
|
527
|
+
const globMsg = sub.messages[1];
|
|
528
|
+
expect(globMsg.type).toBe(MessageType.MESSAGE_AI);
|
|
529
|
+
expect(globMsg.toolCalls).toHaveLength(1);
|
|
530
|
+
expect(globMsg.toolCalls[0].id).toBe("glob-1");
|
|
531
|
+
expect(globMsg.toolCalls[0].name).toBe("glob");
|
|
532
|
+
expect(globMsg.toolCalls[0].toolKind).toBe(ToolKind.SEARCH);
|
|
533
|
+
expect(globMsg.toolCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
534
|
+
|
|
535
|
+
const readMsg = sub.messages[2];
|
|
536
|
+
expect(readMsg.type).toBe(MessageType.MESSAGE_AI);
|
|
537
|
+
expect(readMsg.toolCalls).toHaveLength(1);
|
|
538
|
+
expect(readMsg.toolCalls[0].id).toBe("read-1");
|
|
539
|
+
expect(readMsg.toolCalls[0].name).toBe("read");
|
|
540
|
+
expect(readMsg.toolCalls[0].toolKind).toBe(ToolKind.FILE_READ);
|
|
541
|
+
expect(readMsg.toolCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
542
|
+
expect(readMsg.toolCalls[0].result).toContain("# Hello");
|
|
543
|
+
|
|
544
|
+
expect(sub.messages[3].type).toBe(MessageType.MESSAGE_AI);
|
|
545
|
+
expect(sub.messages[3].content).toBe("I found and read the README.");
|
|
465
546
|
});
|
|
466
547
|
|
|
467
548
|
it("sub-agent gracefully handles missing conversationSteps", () => {
|
|
@@ -563,26 +644,90 @@ describe("MessageAccumulator tool call status transitions", () => {
|
|
|
563
644
|
expect(out[0].content).toBe("Real content.");
|
|
564
645
|
});
|
|
565
646
|
|
|
566
|
-
it("
|
|
647
|
+
it("maps a tool result error oneof to a FAILED tool call", () => {
|
|
648
|
+
const out: AgentMessage[] = [];
|
|
649
|
+
extractConversationSteps({
|
|
650
|
+
status: "success",
|
|
651
|
+
value: {
|
|
652
|
+
conversationSteps: [
|
|
653
|
+
{
|
|
654
|
+
toolCall: {
|
|
655
|
+
toolCallId: "read-err",
|
|
656
|
+
readToolCall: {
|
|
657
|
+
args: { path: "/nope.txt" },
|
|
658
|
+
result: { error: { errorMessage: "file not found" } },
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
},
|
|
662
|
+
],
|
|
663
|
+
},
|
|
664
|
+
}, out);
|
|
665
|
+
|
|
666
|
+
expect(out).toHaveLength(1);
|
|
667
|
+
const tc = out[0].toolCalls[0];
|
|
668
|
+
expect(tc.name).toBe("read");
|
|
669
|
+
expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
|
|
670
|
+
expect(tc.error).toContain("file not found");
|
|
671
|
+
expect(tc.result).toBe("");
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
it("maps a gate-denied shell (permissionDenied / rejected) to a FAILED tool call", () => {
|
|
567
675
|
const out: AgentMessage[] = [];
|
|
568
676
|
extractConversationSteps({
|
|
569
677
|
status: "success",
|
|
570
678
|
value: {
|
|
571
679
|
conversationSteps: [
|
|
572
680
|
{
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
681
|
+
toolCall: {
|
|
682
|
+
toolCallId: "shell-denied",
|
|
683
|
+
shellToolCall: {
|
|
684
|
+
args: {},
|
|
685
|
+
result: {
|
|
686
|
+
permissionDenied: {
|
|
687
|
+
command: "rm -rf /",
|
|
688
|
+
error: "blocked by approval gate",
|
|
689
|
+
isReadonly: false,
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
},
|
|
693
|
+
},
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
toolCall: {
|
|
697
|
+
toolCallId: "shell-rejected",
|
|
698
|
+
shellToolCall: {
|
|
699
|
+
args: {},
|
|
700
|
+
result: { rejected: { command: "curl evil.sh", reason: "user rejected" } },
|
|
701
|
+
},
|
|
578
702
|
},
|
|
579
703
|
},
|
|
580
704
|
],
|
|
581
705
|
},
|
|
582
706
|
}, out);
|
|
583
707
|
|
|
708
|
+
expect(out).toHaveLength(2);
|
|
709
|
+
expect(out[0].toolCalls[0].name).toBe("shell");
|
|
710
|
+
expect(out[0].toolCalls[0].toolKind).toBe(ToolKind.SHELL);
|
|
711
|
+
expect(out[0].toolCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
|
|
712
|
+
expect(out[0].toolCalls[0].error).toContain("blocked by approval gate");
|
|
713
|
+
expect(out[1].toolCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
|
|
714
|
+
expect(out[1].toolCalls[0].error).toContain("user rejected");
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
it("skips a malformed toolCall step with no <kind>ToolCall key", () => {
|
|
718
|
+
const out: AgentMessage[] = [];
|
|
719
|
+
extractConversationSteps({
|
|
720
|
+
status: "success",
|
|
721
|
+
value: {
|
|
722
|
+
conversationSteps: [
|
|
723
|
+
{ toolCall: { toolCallId: "orphan" } },
|
|
724
|
+
{ assistantMessage: { text: "still works" } },
|
|
725
|
+
],
|
|
726
|
+
},
|
|
727
|
+
}, out);
|
|
728
|
+
|
|
584
729
|
expect(out).toHaveLength(1);
|
|
585
|
-
expect(out[0].
|
|
730
|
+
expect(out[0].content).toBe("still works");
|
|
586
731
|
});
|
|
587
732
|
});
|
|
588
733
|
|
|
@@ -859,4 +1004,5 @@ describe("MessageAccumulator tool call status transitions", () => {
|
|
|
859
1004
|
expect(cancelInProgressSubAgentProtos([])).toBe(false);
|
|
860
1005
|
});
|
|
861
1006
|
});
|
|
1007
|
+
|
|
862
1008
|
});
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @regression file-hitl-phase0 — pins file-edit HITL fix #2 (see _projects/2026-06/20260630.01.file-change-hitl-redesign/tasks/T01_3_regression-manifest.md)
|
|
3
|
+
*
|
|
4
|
+
* Cursor analog of execute-deep-agent/__tests__/sequential-gate-resume.test.ts:
|
|
5
|
+
* two SEQUENTIAL approval gates across a resume, on the Cursor deny-and-reconcile
|
|
6
|
+
* harness instead of the native in-process gate.
|
|
7
|
+
*
|
|
8
|
+
* The shared failure class both files guard: a resume that rebuilds its transcript
|
|
9
|
+
* from empty drops gate A's already-committed tool-call id, the backend's
|
|
10
|
+
* append-only-at-identity guard rejects the update, pending_approvals projects to
|
|
11
|
+
* 0, and the WAITING_FOR_APPROVAL + pending=0 state makes the workflow watchdog
|
|
12
|
+
* auto-resume — silently skipping (and looping on) gate B.
|
|
13
|
+
*
|
|
14
|
+
* The Cursor specifics this file pins, which the native one cannot:
|
|
15
|
+
* - the resume re-issues gate A with a BRAND-NEW call id (Cursor has no pause
|
|
16
|
+
* primitive; the model re-runs the approved tool), so the fix must reconcile
|
|
17
|
+
* the re-run onto gate A's committed id by canonical identity, not append a
|
|
18
|
+
* duplicate row;
|
|
19
|
+
* - "each tool gates exactly once": gate A, approved on turn 1, must come back
|
|
20
|
+
* COMPLETED on the resume (never WAITING_APPROVAL again), while gate B gates
|
|
21
|
+
* for the first time — and the REAL bash hook must ALLOW A's re-issue while
|
|
22
|
+
* DENYING B, the out-of-process half the native gate has no equivalent for.
|
|
23
|
+
*
|
|
24
|
+
* Exercises the MessageAccumulator + denial reconciliation (the unit that owns
|
|
25
|
+
* the Cursor resume emitter) plus the generated hook, keeping the reproduction
|
|
26
|
+
* hermetic and pinned to the exact code under change.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { describe, it, expect } from "vitest";
|
|
30
|
+
import { create, clone } from "@bufbuild/protobuf";
|
|
31
|
+
import {
|
|
32
|
+
AgentMessageSchema,
|
|
33
|
+
ToolCallSchema,
|
|
34
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
35
|
+
import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
36
|
+
import {
|
|
37
|
+
ApprovalAction,
|
|
38
|
+
MessageType,
|
|
39
|
+
ToolCallStatus,
|
|
40
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
41
|
+
import type { SDKMessage } from "@cursor/sdk";
|
|
42
|
+
|
|
43
|
+
import {
|
|
44
|
+
MessageAccumulator,
|
|
45
|
+
reconcileDeniedToolCalls,
|
|
46
|
+
toolCallIdentityToken,
|
|
47
|
+
} from "../message-translator.js";
|
|
48
|
+
import {
|
|
49
|
+
buildApprovalGrants,
|
|
50
|
+
buildApprovalState,
|
|
51
|
+
reconstructAdjudicatedApprovals,
|
|
52
|
+
} from "../approval-state.js";
|
|
53
|
+
import type { DeniedLedgerEntry } from "../approval-state.js";
|
|
54
|
+
import {
|
|
55
|
+
setupCursorHookHarness,
|
|
56
|
+
hasBash,
|
|
57
|
+
hookWrite,
|
|
58
|
+
hookShell,
|
|
59
|
+
} from "../__test-utils__/cursor-hook-harness.js";
|
|
60
|
+
|
|
61
|
+
const GATE_A_ID = "tool_edit_a";
|
|
62
|
+
const GATE_A_PATH = "/work/a.txt";
|
|
63
|
+
const GATE_B_ID = "tool_shell_b";
|
|
64
|
+
const GATE_B_CMD = "make build";
|
|
65
|
+
|
|
66
|
+
function builtInEvent(
|
|
67
|
+
callId: string,
|
|
68
|
+
name: string,
|
|
69
|
+
status: "running" | "completed",
|
|
70
|
+
args: Record<string, unknown>,
|
|
71
|
+
result?: unknown,
|
|
72
|
+
): Extract<SDKMessage, { type: "tool_call" }> {
|
|
73
|
+
return { type: "tool_call", agent_id: "agent-1", run_id: "r2", call_id: callId, name, status, args, result };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function assistantEvent(text: string): Extract<SDKMessage, { type: "assistant" }> {
|
|
77
|
+
return {
|
|
78
|
+
type: "assistant",
|
|
79
|
+
agent_id: "agent-1",
|
|
80
|
+
run_id: "r2",
|
|
81
|
+
message: { role: "assistant", content: [{ type: "text", text }] },
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Run-1 transcript: gate A (edit) committed and APPROVED by the user. */
|
|
86
|
+
function approvedGateA(): AgentMessage[] {
|
|
87
|
+
return [
|
|
88
|
+
create(AgentMessageSchema, {
|
|
89
|
+
type: MessageType.MESSAGE_AI,
|
|
90
|
+
content: "I'll write file A, then build.",
|
|
91
|
+
timestamp: "2026-06-20T00:00:01.000Z",
|
|
92
|
+
toolCalls: [
|
|
93
|
+
create(ToolCallSchema, {
|
|
94
|
+
id: GATE_A_ID,
|
|
95
|
+
name: "edit",
|
|
96
|
+
status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
|
|
97
|
+
requiresApproval: true,
|
|
98
|
+
approvalAction: ApprovalAction.APPROVE,
|
|
99
|
+
argsPreview: JSON.stringify({ path: GATE_A_PATH, content: "A\n" }),
|
|
100
|
+
}),
|
|
101
|
+
],
|
|
102
|
+
}),
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Resume events: gate A is re-issued with a FRESH id and completes (the approved
|
|
108
|
+
* tool finally runs), the agent advances and proposes gate B (shell), which is
|
|
109
|
+
* gated — emitted running, then cut off by the first-denial stop.
|
|
110
|
+
*/
|
|
111
|
+
function resumeIntoGateB(): SDKMessage[] {
|
|
112
|
+
return [
|
|
113
|
+
assistantEvent("File A written. Now building."),
|
|
114
|
+
builtInEvent(`${GATE_A_ID}_RESUME`, "edit", "running", { path: GATE_A_PATH, content: "A\n" }),
|
|
115
|
+
builtInEvent(`${GATE_A_ID}_RESUME`, "edit", "completed", { path: GATE_A_PATH, content: "A\n" }, "wrote a.txt"),
|
|
116
|
+
builtInEvent(`${GATE_B_ID}_RESUME`, "shell", "running", { command: GATE_B_CMD }),
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function allToolCalls(messages: AgentMessage[]) {
|
|
121
|
+
return messages.flatMap((m) => m.toolCalls);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Local mirror of the backend append-only-at-identity guard (see hitl-resume-history). */
|
|
125
|
+
function guardRejectionReason(existing: AgentMessage[], incoming: AgentMessage[]): string | undefined {
|
|
126
|
+
if (incoming.length < existing.length) return "would shrink the message transcript";
|
|
127
|
+
const incomingIds = new Set<string>();
|
|
128
|
+
for (const m of incoming) for (const tc of m.toolCalls) if (tc.id) incomingIds.add(tc.id);
|
|
129
|
+
for (const m of existing) {
|
|
130
|
+
for (const tc of m.toolCalls) {
|
|
131
|
+
if (tc.id && !incomingIds.has(tc.id)) return "would drop a previously-committed tool call";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
describe("Cursor sequential gates A->B across resume", () => {
|
|
138
|
+
it("preserves gate A (completed) and surfaces gate B exactly once — a superset the guard accepts", async () => {
|
|
139
|
+
const committed = approvedGateA();
|
|
140
|
+
const seeded = committed.map((m) => clone(AgentMessageSchema, m));
|
|
141
|
+
|
|
142
|
+
const acc = new MessageAccumulator(seeded, { seededSubAgents: [] });
|
|
143
|
+
for (const event of resumeIntoGateB()) acc.processEvent(event);
|
|
144
|
+
acc.finalize();
|
|
145
|
+
|
|
146
|
+
// Gate B was denied this turn; its identity comes off the reconciled call.
|
|
147
|
+
const bCall = allToolCalls(seeded).find((tc) => tc.name === "shell")!;
|
|
148
|
+
const ledger: DeniedLedgerEntry[] = [
|
|
149
|
+
{ toolName: "shell", token: toolCallIdentityToken(bCall) },
|
|
150
|
+
];
|
|
151
|
+
await reconcileDeniedToolCalls(seeded, ledger);
|
|
152
|
+
|
|
153
|
+
const tools = allToolCalls(seeded);
|
|
154
|
+
const byId = new Map(tools.map((tc) => [tc.id, tc]));
|
|
155
|
+
|
|
156
|
+
// Superset: gate A's committed id survives the resume (no drop, no re-key).
|
|
157
|
+
expect(byId.has(GATE_A_ID)).toBe(true);
|
|
158
|
+
expect(tools.some((tc) => tc.id === `${GATE_A_ID}_RESUME`)).toBe(false);
|
|
159
|
+
|
|
160
|
+
// Each gates exactly once: A is now COMPLETED (approved + run, never gated
|
|
161
|
+
// again); B is WAITING_APPROVAL, and only once.
|
|
162
|
+
expect(byId.get(GATE_A_ID)!.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
163
|
+
const waiting = tools.filter((tc) => tc.status === ToolCallStatus.TOOL_CALL_WAITING_APPROVAL);
|
|
164
|
+
expect(waiting).toHaveLength(1);
|
|
165
|
+
expect(waiting[0].name).toBe("shell");
|
|
166
|
+
|
|
167
|
+
// No duplicate rows for either gate.
|
|
168
|
+
expect(tools.filter((tc) => tc.name === "edit")).toHaveLength(1);
|
|
169
|
+
expect(tools.filter((tc) => tc.name === "shell")).toHaveLength(1);
|
|
170
|
+
|
|
171
|
+
// The decisive invariant: the resumed transcript is a strict superset, so the
|
|
172
|
+
// backend guard accepts it and gate B's pending is actually persisted.
|
|
173
|
+
expect(guardRejectionReason(committed, seeded)).toBeUndefined();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("the hook ALLOWS gate A's re-issue and DENIES gate B (out-of-process closure)", () => {
|
|
177
|
+
if (!hasBash) return;
|
|
178
|
+
|
|
179
|
+
// Only gate A is approved going into the resume; its grant is what the hook
|
|
180
|
+
// reads. Gate B has no grant yet (it gates for the first time this turn).
|
|
181
|
+
const { pendingApprovals, decisions } = reconstructAdjudicatedApprovals(approvedGateA());
|
|
182
|
+
const grants = buildApprovalGrants(pendingApprovals, decisions);
|
|
183
|
+
const state = buildApprovalState(new Map(), false, new Set(), grants);
|
|
184
|
+
|
|
185
|
+
const harness = setupCursorHookHarness({ grants: state.approvedGrants });
|
|
186
|
+
expect(harness.decide(hookWrite(GATE_A_PATH)).permission).toBe("allow");
|
|
187
|
+
expect(harness.decide(hookShell(GATE_B_CMD)).permission).toBe("deny");
|
|
188
|
+
});
|
|
189
|
+
});
|
|
@@ -2,12 +2,25 @@
|
|
|
2
2
|
* Unit tests for Cursor MCP image-result normalization.
|
|
3
3
|
*
|
|
4
4
|
* The Cursor SDK wraps an MCP tool result as
|
|
5
|
-
* { status, value: { content: [ { text:{text} }, { image:{ data, mimeType } } ] } }
|
|
6
|
-
* where image `data` is a Node Buffer-JSON ({ type:"Buffer", data:number[] })
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* { status, value: { content: [ { text:{text} }, { image:{ data, mimeType? } } ] } }
|
|
6
|
+
* where image `data` is a Node Buffer-JSON ({ type:"Buffer", data:number[] }) at
|
|
7
|
+
* RUNTIME — confirmed verbatim from a real cursor-harness get_app_state result
|
|
8
|
+
* (a PNG screenshot, no mimeType field). The SDK's .d.ts types `data` as a
|
|
9
|
+
* string, but the runtime serialization is Buffer-JSON, so the canonical fixture
|
|
10
|
+
* below uses Buffer-JSON; base64 / data:-URL string `data` is also covered.
|
|
11
|
+
*
|
|
12
|
+
* The translator must re-emit the envelope as the canonical top-level
|
|
13
|
+
* content-block array the shared persist-time offload consumes, so a screenshot
|
|
14
|
+
* lands as a renderable image ToolCallOutputRef instead of text/plain. These
|
|
15
|
+
* tests pin that normalization and confirm it flows end-to-end through the
|
|
16
|
+
* offload.
|
|
17
|
+
*
|
|
18
|
+
* Note: this normalization only runs when the SDK delivers `result` as an
|
|
19
|
+
* OBJECT. When it arrives already-serialized as a STRING (observed in
|
|
20
|
+
* production: the whole envelope nested under value.content), the translator
|
|
21
|
+
* passes it through and the shared offload's detectImagePayload is what must
|
|
22
|
+
* recognize it — that string path (including the Buffer-JSON + no-mimeType
|
|
23
|
+
* shape) is guarded in shared/__tests__/status-offload.
|
|
11
24
|
*/
|
|
12
25
|
|
|
13
26
|
import { describe, it, expect, vi } from "vitest";
|
|
@@ -15,7 +28,7 @@ import { create } from "@bufbuild/protobuf";
|
|
|
15
28
|
import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
16
29
|
import { AgentMessageSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
17
30
|
import type { SDKMessage } from "@cursor/sdk";
|
|
18
|
-
import
|
|
31
|
+
import { makeInMemoryArtifactStorage } from "../../../__test-utils__/fake-artifact-storage.js";
|
|
19
32
|
import {
|
|
20
33
|
offloadOversizedToolOutputs,
|
|
21
34
|
detectImagePayload,
|
|
@@ -28,17 +41,22 @@ import {
|
|
|
28
41
|
} from "../message-translator.js";
|
|
29
42
|
import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
30
43
|
|
|
31
|
-
// PNG signature + a little payload
|
|
44
|
+
// PNG signature + a little payload. PNG_BYTES is how the Cursor SDK delivers
|
|
45
|
+
// image data at runtime (Node Buffer-JSON); PNG_BASE64 is its base64 form, used
|
|
46
|
+
// by the string-`data` variant tests.
|
|
32
47
|
const PNG_BYTES = [137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82];
|
|
33
48
|
const PNG_BASE64 = Buffer.from(PNG_BYTES).toString("base64");
|
|
34
49
|
|
|
50
|
+
// The canonical envelope, matching the real runtime shape: image.data is Node
|
|
51
|
+
// Buffer-JSON and there is no mimeType. This is the shape the translator and
|
|
52
|
+
// offload must handle.
|
|
35
53
|
function cursorImageEnvelope(text = "App=com.example") {
|
|
36
54
|
return {
|
|
37
55
|
status: "success",
|
|
38
56
|
value: {
|
|
39
57
|
content: [
|
|
40
58
|
{ text: { text } },
|
|
41
|
-
{ image: { data: { type: "Buffer", data: PNG_BYTES }
|
|
59
|
+
{ image: { data: { type: "Buffer", data: PNG_BYTES } } },
|
|
42
60
|
],
|
|
43
61
|
isError: false,
|
|
44
62
|
},
|
|
@@ -46,7 +64,7 @@ function cursorImageEnvelope(text = "App=com.example") {
|
|
|
46
64
|
}
|
|
47
65
|
|
|
48
66
|
describe("canonicalizeImageResult", () => {
|
|
49
|
-
it("converts
|
|
67
|
+
it("converts the canonical Cursor image envelope (Buffer-JSON) to the canonical array", () => {
|
|
50
68
|
const out = canonicalizeImageResult(cursorImageEnvelope("App=Slack"));
|
|
51
69
|
expect(out).toBeDefined();
|
|
52
70
|
expect(JSON.parse(out!)).toEqual([
|
|
@@ -152,14 +170,12 @@ describe("buildToolCallProto image normalization", () => {
|
|
|
152
170
|
describe("cursor image flows through the persist-time offload", () => {
|
|
153
171
|
it("offloads the screenshot as an image ref with no inline bytes", async () => {
|
|
154
172
|
const uploads: { key: string; contentType?: string }[] = [];
|
|
155
|
-
const storage
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
exists: vi.fn(async () => true),
|
|
162
|
-
};
|
|
173
|
+
const { storage, blobs } = makeInMemoryArtifactStorage({ urlBase: "https://artifacts.local/" });
|
|
174
|
+
storage.upload.mockImplementation(async (key: string, content: Buffer, contentType?: string) => {
|
|
175
|
+
uploads.push({ key, contentType });
|
|
176
|
+
blobs.set(key, Buffer.from(content));
|
|
177
|
+
return key;
|
|
178
|
+
});
|
|
163
179
|
|
|
164
180
|
const event = {
|
|
165
181
|
type: "tool_call",
|
|
@@ -219,13 +235,18 @@ describe("sub-agent image normalization (extractConversationSteps)", () => {
|
|
|
219
235
|
result: {
|
|
220
236
|
status: "success",
|
|
221
237
|
value: {
|
|
238
|
+
// Real Cursor task-result shape: a conversation step is keyed directly
|
|
239
|
+
// by kind, and a tool call is { toolCall: { toolCallId, <kind>ToolCall:
|
|
240
|
+
// { args, result } } } whose result is a oneof { success | error | ... }.
|
|
241
|
+
// The screenshot rides the `success` branch as the tool's result value.
|
|
222
242
|
conversationSteps: [
|
|
223
243
|
{
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
244
|
+
toolCall: {
|
|
245
|
+
toolCallId: "sub-img-1",
|
|
246
|
+
mcpToolCall: {
|
|
247
|
+
args: {},
|
|
248
|
+
result: { success: cursorImageEnvelope("App=SubAgent").value },
|
|
249
|
+
},
|
|
229
250
|
},
|
|
230
251
|
},
|
|
231
252
|
],
|