@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
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runner's file-review event producer: maps a harness-agnostic capture into
|
|
3
|
+
* the proto {@code CapturedFileChange} + {@code FileReviewEvent} ledger entries
|
|
4
|
+
* the server folds (append-only, by event_id) into the server-owned stream.
|
|
5
|
+
*
|
|
6
|
+
* This is the runner half of the apply-then-review subsystem. It is intentionally
|
|
7
|
+
* harness-agnostic — it takes per-file before/after content and a kind, not a
|
|
8
|
+
* git-specific shape — so the Cursor (git substrate) and deep-agent (mutation
|
|
9
|
+
* buffer) harnesses author IDENTICAL ledger entries through one seam. Event ids
|
|
10
|
+
* are deterministic ({@link eventId}) so a re-sent heartbeat or a Temporal retry
|
|
11
|
+
* never duplicates an event.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { create } from "@bufbuild/protobuf";
|
|
15
|
+
import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
16
|
+
import {
|
|
17
|
+
CapturedFileChangeSchema,
|
|
18
|
+
FileReviewBaselineCapturedSchema,
|
|
19
|
+
FileReviewCandidateCapturedSchema,
|
|
20
|
+
FileReviewEventSchema,
|
|
21
|
+
FileReviewEventStreamSchema,
|
|
22
|
+
FileReviewFailureSchema,
|
|
23
|
+
FileReviewReconciledSchema,
|
|
24
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
|
|
25
|
+
import type {
|
|
26
|
+
CapturedFileChange,
|
|
27
|
+
FileReviewEvent,
|
|
28
|
+
SnapshotRef,
|
|
29
|
+
TurnCommandProvenance,
|
|
30
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
|
|
31
|
+
import {
|
|
32
|
+
FileContentSchema,
|
|
33
|
+
ToolCallOutputRefSchema,
|
|
34
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
35
|
+
import type { FileContent } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
36
|
+
import {
|
|
37
|
+
DiffCompleteness,
|
|
38
|
+
FileCaptureClass,
|
|
39
|
+
FileChangeKind,
|
|
40
|
+
FileReviewBlockReason,
|
|
41
|
+
FileReviewEventType,
|
|
42
|
+
FileReviewFailureKind,
|
|
43
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
44
|
+
import { aggregateDigest, fileDigest, sha256Bytes } from "./digest.js";
|
|
45
|
+
import { looksBinary } from "../file-change.js";
|
|
46
|
+
|
|
47
|
+
// The runner is the author of capture/reconcile/failure events. "runner" (not
|
|
48
|
+
// the generic "system") matches the cross-edition corpus fixtures and the
|
|
49
|
+
// proto's actor convention ("runner" for capture/reconcile, "user" for
|
|
50
|
+
// decisions). The projection ignores actor, but keeping it canonical keeps the
|
|
51
|
+
// ledger self-describing and the dual-edition corpus honest.
|
|
52
|
+
const ACTOR_RUNNER = "runner";
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The proto enum value NAME for a {@link FileReviewEventType}, e.g.
|
|
56
|
+
* `FILE_REVIEW_EVENT_TYPE_BASELINE_CAPTURED`. {@link eventId} hashes this name so
|
|
57
|
+
* the runner's ids are byte-identical to the Go `author.EventID` / Java
|
|
58
|
+
* `FileReviewStreamAuthor.eventId` (which use the proto name).
|
|
59
|
+
*/
|
|
60
|
+
const FILE_REVIEW_EVENT_TYPE_NAME: Readonly<Record<FileReviewEventType, string>> = {
|
|
61
|
+
[FileReviewEventType.UNSPECIFIED]: "FILE_REVIEW_EVENT_TYPE_UNSPECIFIED",
|
|
62
|
+
[FileReviewEventType.BASELINE_CAPTURED]: "FILE_REVIEW_EVENT_TYPE_BASELINE_CAPTURED",
|
|
63
|
+
[FileReviewEventType.CANDIDATE_CAPTURED]: "FILE_REVIEW_EVENT_TYPE_CANDIDATE_CAPTURED",
|
|
64
|
+
[FileReviewEventType.FILE_DECIDED]: "FILE_REVIEW_EVENT_TYPE_FILE_DECIDED",
|
|
65
|
+
[FileReviewEventType.RECONCILED]: "FILE_REVIEW_EVENT_TYPE_RECONCILED",
|
|
66
|
+
[FileReviewEventType.FAILED]: "FILE_REVIEW_EVENT_TYPE_FAILED",
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The deterministic idempotency key for a file-review event:
|
|
71
|
+
* `changeSetId:scopeId:eventTypeName`. Capture/reconcile/failure events are
|
|
72
|
+
* change-set-scoped (scopeId == changeSetId); the per-file decision scope is the
|
|
73
|
+
* server's concern. Identical to the Go/Java EventID.
|
|
74
|
+
*/
|
|
75
|
+
export function eventId(
|
|
76
|
+
changeSetId: string,
|
|
77
|
+
scopeId: string,
|
|
78
|
+
type: FileReviewEventType,
|
|
79
|
+
): string {
|
|
80
|
+
return `${changeSetId}:${scopeId}:${FILE_REVIEW_EVENT_TYPE_NAME[type]}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* One side (before/after) of a captured file's content, in one of three shapes:
|
|
85
|
+
*
|
|
86
|
+
* - `inline` — the raw text bytes, carried on the wire (the git substrate reads
|
|
87
|
+
* them from the tree for text files). A plain string is accepted as shorthand.
|
|
88
|
+
* - `ref` — a reference to an already-stored, content-addressed blob (the CAS
|
|
89
|
+
* substrate offloads ignored / non-git bodies to artifact storage and carries
|
|
90
|
+
* the ref, so the body is never stored twice).
|
|
91
|
+
* - `binary` — a git-tracked binary side: NO body is carried (a binary has no
|
|
92
|
+
* reviewable text diff, and reconcile sources its bytes byte-exact from the
|
|
93
|
+
* git ref, never from the wire), only the byte-true content address. This is
|
|
94
|
+
* what keeps the enforcement digest honest for binaries — hashing a UTF-8
|
|
95
|
+
* decode of binary bytes would be lossy.
|
|
96
|
+
*/
|
|
97
|
+
export type CapturedContent =
|
|
98
|
+
| {
|
|
99
|
+
readonly kind: "inline";
|
|
100
|
+
readonly text: string;
|
|
101
|
+
/**
|
|
102
|
+
* Byte-true SHA-256 of the RAW bytes, when the producer computed it at the
|
|
103
|
+
* source (the git substrate does). Preferred over hashing `text`, which for
|
|
104
|
+
* a non-UTF-8 blob (e.g. latin-1 with no NUL) would hash a lossy decode.
|
|
105
|
+
* Absent for the plain-string shorthand, where `text` IS the exact content.
|
|
106
|
+
*/
|
|
107
|
+
readonly sha256?: string;
|
|
108
|
+
}
|
|
109
|
+
| {
|
|
110
|
+
readonly kind: "ref";
|
|
111
|
+
/** Content address of the referenced blob (the enforcement digest). */
|
|
112
|
+
readonly sha256: string;
|
|
113
|
+
readonly storageKey: string;
|
|
114
|
+
readonly sizeBytes: number;
|
|
115
|
+
readonly isBinary: boolean;
|
|
116
|
+
readonly mimeType?: string;
|
|
117
|
+
}
|
|
118
|
+
| {
|
|
119
|
+
readonly kind: "binary";
|
|
120
|
+
/** Byte-true SHA-256 of the raw bytes (the enforcement digest). */
|
|
121
|
+
readonly sha256: string;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** A single captured file delta, harness-agnostic (no git/CAS specifics). */
|
|
125
|
+
export interface CapturedChangeInput {
|
|
126
|
+
/** Stable id of this file change within its set (correlation key, never a hash). */
|
|
127
|
+
readonly id: string;
|
|
128
|
+
/** Path before the change (workspace-root-relative). Empty for ADD. */
|
|
129
|
+
readonly pathBefore: string;
|
|
130
|
+
/** Path after the change. Empty for DELETE; differs from pathBefore for RENAME. */
|
|
131
|
+
readonly pathAfter: string;
|
|
132
|
+
readonly kind: FileChangeKind;
|
|
133
|
+
/** Which substrate captured this file (git-tracked, ignored, CAS, ...). */
|
|
134
|
+
readonly captureClass: FileCaptureClass;
|
|
135
|
+
/** Pre-edit content (inline string, or a blob ref). Omit for ADD. */
|
|
136
|
+
readonly before?: string | CapturedContent;
|
|
137
|
+
/** Post-edit content (inline string, or a blob ref). Omit for DELETE. */
|
|
138
|
+
readonly after?: string | CapturedContent;
|
|
139
|
+
/**
|
|
140
|
+
* False when this file's diff could not be captured completely (too large to
|
|
141
|
+
* persist inline before CAS exists, truncated, or binary-only). A change set
|
|
142
|
+
* with any incomplete file cannot be approved as complete.
|
|
143
|
+
*/
|
|
144
|
+
readonly diffComplete?: boolean;
|
|
145
|
+
/**
|
|
146
|
+
* The honest cause the diff is not fully reviewable, set only alongside
|
|
147
|
+
* `diffComplete === false` (defaults to UNSPECIFIED). Informational provenance
|
|
148
|
+
* for the review UI — never an enforcement input and never folded into the
|
|
149
|
+
* digests. Binary is conveyed by FileContent.is_binary, so binary files leave
|
|
150
|
+
* this UNSPECIFIED. See {@link FileReviewBlockReason}.
|
|
151
|
+
*/
|
|
152
|
+
readonly blockedReason?: FileReviewBlockReason;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Normalize the string-shorthand to a {@link CapturedContent}. */
|
|
156
|
+
function normalizeContent(v: string | CapturedContent | undefined): CapturedContent | undefined {
|
|
157
|
+
if (v === undefined) return undefined;
|
|
158
|
+
return typeof v === "string" ? { kind: "inline", text: v } : v;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The enforcement digest of one content side. Inline text is hashed as UTF-8
|
|
163
|
+
* bytes; a `ref`/`binary` side already carries its byte-true content address, so
|
|
164
|
+
* it is reused verbatim. This single function is the sole sha authority — used
|
|
165
|
+
* both at capture (in {@link buildCapturedFileChange}) and at the resume-time
|
|
166
|
+
* reconcile check — so the two are consistent by construction across substrates.
|
|
167
|
+
*/
|
|
168
|
+
export function contentSha256(content: CapturedContent): string {
|
|
169
|
+
switch (content.kind) {
|
|
170
|
+
case "inline":
|
|
171
|
+
return content.sha256 ?? sha256Bytes(Buffer.from(content.text, "utf8"));
|
|
172
|
+
case "ref":
|
|
173
|
+
case "binary":
|
|
174
|
+
return content.sha256;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Build the proto {@link FileContent} for one content side (inline, ref, or binary). */
|
|
179
|
+
function toFileContent(content: CapturedContent): FileContent {
|
|
180
|
+
if (content.kind === "inline") {
|
|
181
|
+
return create(FileContentSchema, {
|
|
182
|
+
body: { case: "inline", value: content.text },
|
|
183
|
+
isBinary: looksBinary(content.text),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
// A git-tracked binary carries no body: its bytes reconcile from the git ref,
|
|
187
|
+
// and a text diff of binary content is meaningless. Consumers key off the
|
|
188
|
+
// is_binary flag (the SDK renders "binary file changed"; the backend gates
|
|
189
|
+
// approval on it), never a body.
|
|
190
|
+
if (content.kind === "binary") {
|
|
191
|
+
return create(FileContentSchema, { isBinary: true });
|
|
192
|
+
}
|
|
193
|
+
return create(FileContentSchema, {
|
|
194
|
+
body: {
|
|
195
|
+
case: "ref",
|
|
196
|
+
value: create(ToolCallOutputRefSchema, {
|
|
197
|
+
storageKey: content.storageKey,
|
|
198
|
+
sizeBytes: BigInt(content.sizeBytes),
|
|
199
|
+
contentHash: content.sha256,
|
|
200
|
+
mimeType: content.mimeType ?? "application/octet-stream",
|
|
201
|
+
isImage: false,
|
|
202
|
+
truncatedPreview: "",
|
|
203
|
+
}),
|
|
204
|
+
},
|
|
205
|
+
isBinary: content.isBinary,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Build a proto {@link CapturedFileChange} from harness-agnostic input,
|
|
211
|
+
* computing the enforcement digests (`before_sha256`, `after_sha256`,
|
|
212
|
+
* `file_digest`) over the captured bytes. Content may be inline (git) or a blob
|
|
213
|
+
* ref (CAS) — the digests are identical either way, so the aggregate digest and
|
|
214
|
+
* the reconcile enforcement compose across both substrates.
|
|
215
|
+
*/
|
|
216
|
+
export function buildCapturedFileChange(input: CapturedChangeInput): CapturedFileChange {
|
|
217
|
+
const before = normalizeContent(input.before);
|
|
218
|
+
const after = normalizeContent(input.after);
|
|
219
|
+
const beforeSha256 = before ? contentSha256(before) : "";
|
|
220
|
+
const afterSha256 = after ? contentSha256(after) : "";
|
|
221
|
+
|
|
222
|
+
const fc = create(CapturedFileChangeSchema, {
|
|
223
|
+
id: input.id,
|
|
224
|
+
pathBefore: input.pathBefore,
|
|
225
|
+
pathAfter: input.pathAfter,
|
|
226
|
+
kind: input.kind,
|
|
227
|
+
captureClass: input.captureClass,
|
|
228
|
+
beforeSha256,
|
|
229
|
+
afterSha256,
|
|
230
|
+
diffComplete: input.diffComplete ?? true,
|
|
231
|
+
blockedReason: input.blockedReason ?? FileReviewBlockReason.UNSPECIFIED,
|
|
232
|
+
fileDigest: fileDigest({
|
|
233
|
+
pathBefore: input.pathBefore,
|
|
234
|
+
pathAfter: input.pathAfter,
|
|
235
|
+
kind: input.kind,
|
|
236
|
+
beforeSha256,
|
|
237
|
+
afterSha256,
|
|
238
|
+
}),
|
|
239
|
+
});
|
|
240
|
+
if (before) {
|
|
241
|
+
fc.before = toFileContent(before);
|
|
242
|
+
}
|
|
243
|
+
if (after) {
|
|
244
|
+
fc.after = toFileContent(after);
|
|
245
|
+
}
|
|
246
|
+
return fc;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Coordinates a change set's lifecycle so every event carries one identity. */
|
|
250
|
+
export interface ChangeSetContext {
|
|
251
|
+
readonly changeSetId: string;
|
|
252
|
+
readonly turnId: string;
|
|
253
|
+
/** "cursor" | "deep-agent". */
|
|
254
|
+
readonly harnessId: string;
|
|
255
|
+
/** ISO 8601 timestamp authored onto each event. */
|
|
256
|
+
readonly timestamp: string;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* The BASELINE_CAPTURED event — the first event of a change set's lifecycle,
|
|
261
|
+
* authored at turn start so the projection can materialize the set before any
|
|
262
|
+
* candidate exists.
|
|
263
|
+
*/
|
|
264
|
+
export function buildBaselineCapturedEvent(
|
|
265
|
+
ctx: ChangeSetContext,
|
|
266
|
+
baselineSnapshot: SnapshotRef | undefined,
|
|
267
|
+
): FileReviewEvent {
|
|
268
|
+
return create(FileReviewEventSchema, {
|
|
269
|
+
eventId: eventId(ctx.changeSetId, ctx.changeSetId, FileReviewEventType.BASELINE_CAPTURED),
|
|
270
|
+
changeSetId: ctx.changeSetId,
|
|
271
|
+
eventType: FileReviewEventType.BASELINE_CAPTURED,
|
|
272
|
+
timestamp: ctx.timestamp,
|
|
273
|
+
actor: ACTOR_RUNNER,
|
|
274
|
+
payload: {
|
|
275
|
+
case: "baselineCaptured",
|
|
276
|
+
value: create(FileReviewBaselineCapturedSchema, {
|
|
277
|
+
changeSetId: ctx.changeSetId,
|
|
278
|
+
turnId: ctx.turnId,
|
|
279
|
+
harnessId: ctx.harnessId,
|
|
280
|
+
...(baselineSnapshot ? { baselineSnapshot } : {}),
|
|
281
|
+
}),
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Derive a change set's {@link DiffCompleteness} rollup from its per-file
|
|
288
|
+
* signals — the single source of the three-way rule, shared by the turn-boundary
|
|
289
|
+
* capture ({@link buildCandidateCapturedEvent}) and the size backstop
|
|
290
|
+
* (status-offload), so the value can never be computed two ways:
|
|
291
|
+
*
|
|
292
|
+
* - every file complete → `COMPLETE`
|
|
293
|
+
* - else the ONLY incompleteness is binary (every incomplete file has a binary
|
|
294
|
+
* side) → `BINARY_SUMMARY_ONLY` — keepable in one acknowledged action, its
|
|
295
|
+
* exact bytes reconcile from the git ref / CAS blob
|
|
296
|
+
* - else (≥1 non-binary incomplete: secret-withheld / size-elided /
|
|
297
|
+
* uncapturable — no keepable bytes) → `PARTIAL_BLOCKED`
|
|
298
|
+
*
|
|
299
|
+
* Binary is proven by `FileContent.is_binary` on either side (DD-15 D2), never
|
|
300
|
+
* by the block reason. This mirrors the backend gate's `isBinaryChange`, so the
|
|
301
|
+
* runner's rollup and the gate's per-file re-derivation agree by construction.
|
|
302
|
+
*/
|
|
303
|
+
export function deriveDiffCompleteness(
|
|
304
|
+
changes: readonly CapturedFileChange[],
|
|
305
|
+
): DiffCompleteness {
|
|
306
|
+
let sawIncomplete = false;
|
|
307
|
+
for (const c of changes) {
|
|
308
|
+
if (c.diffComplete) continue;
|
|
309
|
+
sawIncomplete = true;
|
|
310
|
+
if (!(c.before?.isBinary || c.after?.isBinary)) {
|
|
311
|
+
// A non-binary incompleteness has no keepable bytes — it blocks the set
|
|
312
|
+
// outright, regardless of any binaries alongside it.
|
|
313
|
+
return DiffCompleteness.PARTIAL_BLOCKED;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (!sawIncomplete) return DiffCompleteness.COMPLETE;
|
|
317
|
+
return DiffCompleteness.BINARY_SUMMARY_ONLY;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* The CANDIDATE_CAPTURED event — authored at the turn boundary, carrying the
|
|
322
|
+
* authoritative per-file diff and the aggregate digest. `diff_completeness` is
|
|
323
|
+
* the {@link deriveDiffCompleteness} rollup over the changes.
|
|
324
|
+
*
|
|
325
|
+
* `commandProvenance` (optional, DD-28) is the harness's approved-command turn
|
|
326
|
+
* facts: when present, the backend verifies the cited consent rows against its
|
|
327
|
+
* server-authored approval record and — on success — auto-keeps the set with a
|
|
328
|
+
* policy-origin decision instead of arming the review gate. Never folded into
|
|
329
|
+
* the aggregate digest (provenance, not content).
|
|
330
|
+
*/
|
|
331
|
+
export function buildCandidateCapturedEvent(
|
|
332
|
+
ctx: ChangeSetContext,
|
|
333
|
+
candidateSnapshot: SnapshotRef | undefined,
|
|
334
|
+
changes: readonly CapturedFileChange[],
|
|
335
|
+
commandProvenance?: TurnCommandProvenance,
|
|
336
|
+
): FileReviewEvent {
|
|
337
|
+
return create(FileReviewEventSchema, {
|
|
338
|
+
eventId: eventId(ctx.changeSetId, ctx.changeSetId, FileReviewEventType.CANDIDATE_CAPTURED),
|
|
339
|
+
changeSetId: ctx.changeSetId,
|
|
340
|
+
eventType: FileReviewEventType.CANDIDATE_CAPTURED,
|
|
341
|
+
timestamp: ctx.timestamp,
|
|
342
|
+
actor: ACTOR_RUNNER,
|
|
343
|
+
payload: {
|
|
344
|
+
case: "candidateCaptured",
|
|
345
|
+
value: create(FileReviewCandidateCapturedSchema, {
|
|
346
|
+
changeSetId: ctx.changeSetId,
|
|
347
|
+
...(candidateSnapshot ? { candidateSnapshot } : {}),
|
|
348
|
+
changes: [...changes],
|
|
349
|
+
aggregateDigest: aggregateDigest(
|
|
350
|
+
changes.map((c) => ({
|
|
351
|
+
pathBefore: c.pathBefore,
|
|
352
|
+
pathAfter: c.pathAfter,
|
|
353
|
+
kind: c.kind,
|
|
354
|
+
beforeSha256: c.beforeSha256,
|
|
355
|
+
afterSha256: c.afterSha256,
|
|
356
|
+
})),
|
|
357
|
+
),
|
|
358
|
+
diffCompleteness: deriveDiffCompleteness(changes),
|
|
359
|
+
...(commandProvenance ? { commandProvenance } : {}),
|
|
360
|
+
}),
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** The RECONCILED event — authored after decisions are applied and verified. */
|
|
366
|
+
export function buildReconciledEvent(
|
|
367
|
+
ctx: ChangeSetContext,
|
|
368
|
+
approvedSnapshot: SnapshotRef | undefined,
|
|
369
|
+
): FileReviewEvent {
|
|
370
|
+
return create(FileReviewEventSchema, {
|
|
371
|
+
eventId: eventId(ctx.changeSetId, ctx.changeSetId, FileReviewEventType.RECONCILED),
|
|
372
|
+
changeSetId: ctx.changeSetId,
|
|
373
|
+
eventType: FileReviewEventType.RECONCILED,
|
|
374
|
+
timestamp: ctx.timestamp,
|
|
375
|
+
actor: ACTOR_RUNNER,
|
|
376
|
+
payload: {
|
|
377
|
+
case: "reconciled",
|
|
378
|
+
value: create(FileReviewReconciledSchema, {
|
|
379
|
+
changeSetId: ctx.changeSetId,
|
|
380
|
+
...(approvedSnapshot ? { approvedSnapshot } : {}),
|
|
381
|
+
}),
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** A terminal FAILED event in the file-review lifecycle (audit + resume). */
|
|
387
|
+
export function buildFailedEvent(
|
|
388
|
+
ctx: ChangeSetContext,
|
|
389
|
+
kind: FileReviewFailureKind,
|
|
390
|
+
detail: string,
|
|
391
|
+
): FileReviewEvent {
|
|
392
|
+
return create(FileReviewEventSchema, {
|
|
393
|
+
eventId: eventId(ctx.changeSetId, ctx.changeSetId, FileReviewEventType.FAILED),
|
|
394
|
+
changeSetId: ctx.changeSetId,
|
|
395
|
+
eventType: FileReviewEventType.FAILED,
|
|
396
|
+
timestamp: ctx.timestamp,
|
|
397
|
+
actor: ACTOR_RUNNER,
|
|
398
|
+
payload: {
|
|
399
|
+
case: "failed",
|
|
400
|
+
value: create(FileReviewFailureSchema, { changeSetId: ctx.changeSetId, kind, detail }),
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Whether a CANDIDATE_CAPTURED event exists for `changeSetId` on the status's
|
|
407
|
+
* file_review stream — i.e. this turn actually produced a reviewable change
|
|
408
|
+
* set. The capture seam authors no event for a no-op turn (an edit fully
|
|
409
|
+
* reverted before the boundary), so this is the single signal both harnesses
|
|
410
|
+
* gate on before opening a review or stamping transcript rows: a row must
|
|
411
|
+
* never reference a change set that does not exist.
|
|
412
|
+
*/
|
|
413
|
+
export function hasCandidateCaptured(
|
|
414
|
+
status: AgentExecutionStatus,
|
|
415
|
+
changeSetId: string,
|
|
416
|
+
): boolean {
|
|
417
|
+
return (status.fileReviewEventStream?.events ?? []).some(
|
|
418
|
+
(e) =>
|
|
419
|
+
e.changeSetId === changeSetId &&
|
|
420
|
+
e.eventType === FileReviewEventType.CANDIDATE_CAPTURED,
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Append the runner-authored events onto the status's file_review stream
|
|
426
|
+
* (seeding it if absent), idempotently by event_id. The server re-applies the
|
|
427
|
+
* same append-only/by-id discipline on receipt, so re-sending across throttled
|
|
428
|
+
* persists is safe; this keeps the in-runner status consistent between persists.
|
|
429
|
+
*/
|
|
430
|
+
export function appendFileReviewEvents(
|
|
431
|
+
status: AgentExecutionStatus,
|
|
432
|
+
executionId: string,
|
|
433
|
+
events: readonly FileReviewEvent[],
|
|
434
|
+
): void {
|
|
435
|
+
if (events.length === 0) return;
|
|
436
|
+
if (!status.fileReviewEventStream) {
|
|
437
|
+
status.fileReviewEventStream = create(FileReviewEventStreamSchema, {
|
|
438
|
+
executionId,
|
|
439
|
+
events: [],
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
const stream = status.fileReviewEventStream;
|
|
443
|
+
const seen = new Set(stream.events.map((e) => e.eventId));
|
|
444
|
+
for (const ev of events) {
|
|
445
|
+
if (seen.has(ev.eventId)) continue;
|
|
446
|
+
seen.add(ev.eventId);
|
|
447
|
+
stream.events.push(ev);
|
|
448
|
+
}
|
|
449
|
+
}
|