@stigmer/runner 3.15.1 → 3.15.2-dev.20260913223433
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -4
- package/dist/.build-fingerprint +1 -1
- package/dist/__test-utils__/execution-record-fixture.d.ts +14 -0
- package/dist/__test-utils__/execution-record-fixture.js +4 -0
- package/dist/__test-utils__/execution-record-fixture.js.map +1 -1
- package/dist/__test-utils__/file-review-projection.d.ts +40 -0
- package/dist/__test-utils__/file-review-projection.js +136 -0
- package/dist/__test-utils__/file-review-projection.js.map +1 -0
- package/dist/__test-utils__/harness-contract/types.d.ts +23 -1
- package/dist/__test-utils__/harness-contract/types.js +15 -0
- package/dist/__test-utils__/harness-contract/types.js.map +1 -1
- package/dist/__test-utils__/hermetic-activity.d.ts +44 -12
- package/dist/__test-utils__/hermetic-activity.js +52 -14
- package/dist/__test-utils__/hermetic-activity.js.map +1 -1
- package/dist/__test-utils__/model-registry-fixture.d.ts +32 -7
- package/dist/__test-utils__/model-registry-fixture.js +31 -7
- package/dist/__test-utils__/model-registry-fixture.js.map +1 -1
- package/dist/__test-utils__/turn-input-fixture.d.ts +1 -1
- package/dist/__test-utils__/turn-input-fixture.js +1 -1
- package/dist/__test-utils__/turn-input-fixture.js.map +1 -1
- package/dist/activities/call-agent.js +2 -2
- package/dist/activities/call-agent.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/contract-subject.js +45 -3
- package/dist/activities/execute-cursor/__test-utils__/contract-subject.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +0 -9
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +2 -41
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
- package/dist/activities/execute-cursor/adapter.d.ts +7 -5
- package/dist/activities/execute-cursor/adapter.js +7 -5
- package/dist/activities/execute-cursor/adapter.js.map +1 -1
- package/dist/activities/execute-cursor/approval-policy.d.ts +5 -2
- package/dist/activities/execute-cursor/approval-policy.js +5 -2
- package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
- package/dist/activities/execute-cursor/approval-state.d.ts +16 -8
- package/dist/activities/execute-cursor/approval-state.js +6 -3
- package/dist/activities/execute-cursor/approval-state.js.map +1 -1
- package/dist/activities/execute-cursor/cas-observations.d.ts +14 -2
- package/dist/activities/execute-cursor/cas-observations.js +19 -2
- package/dist/activities/execute-cursor/cas-observations.js.map +1 -1
- package/dist/activities/execute-cursor/cursor-capabilities.d.ts +8 -4
- package/dist/activities/execute-cursor/cursor-capabilities.js +21 -4
- package/dist/activities/execute-cursor/cursor-capabilities.js.map +1 -1
- package/dist/activities/execute-cursor/message-translator.d.ts +12 -20
- package/dist/activities/execute-cursor/message-translator.js +19 -32
- package/dist/activities/execute-cursor/message-translator.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +48 -58
- package/dist/activities/execute-cursor/prompt-builder.js +76 -95
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/turn-boundary.d.ts +27 -55
- package/dist/activities/execute-cursor/turn-boundary.js +30 -90
- package/dist/activities/execute-cursor/turn-boundary.js.map +1 -1
- package/dist/activities/execute-cursor/turn-settle.d.ts +11 -7
- package/dist/activities/execute-cursor/turn-settle.js +37 -49
- package/dist/activities/execute-cursor/turn-settle.js.map +1 -1
- package/dist/activities/execute-cursor/turn-setup.d.ts +16 -24
- package/dist/activities/execute-cursor/turn-setup.js +29 -59
- package/dist/activities/execute-cursor/turn-setup.js.map +1 -1
- package/dist/activities/execute-cursor/turn-stream.d.ts +3 -8
- package/dist/activities/execute-cursor/turn-stream.js +9 -22
- package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
- package/dist/activities/execute-cursor/turn.js +5 -1
- package/dist/activities/execute-cursor/turn.js.map +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.d.ts +93 -0
- package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.js +348 -0
- package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.js.map +1 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.d.ts +170 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js +225 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js.map +1 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.d.ts +24 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.js +41 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.js.map +1 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.d.ts +54 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.js +72 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.js.map +1 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +187 -23
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +238 -27
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -1
- package/dist/activities/execute-deep-agent/adapter.d.ts +41 -0
- package/dist/activities/execute-deep-agent/adapter.js +78 -0
- package/dist/activities/execute-deep-agent/adapter.js.map +1 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.d.ts +8 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.js +9 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.js.map +1 -1
- package/dist/activities/execute-deep-agent/deep-agent-capabilities.d.ts +31 -0
- package/dist/activities/execute-deep-agent/deep-agent-capabilities.js +40 -0
- package/dist/activities/execute-deep-agent/deep-agent-capabilities.js.map +1 -0
- package/dist/activities/execute-deep-agent/execution-state.d.ts +4 -11
- package/dist/activities/execute-deep-agent/execution-state.js +4 -12
- package/dist/activities/execute-deep-agent/execution-state.js.map +1 -1
- package/dist/activities/execute-deep-agent/hitl.d.ts +49 -47
- package/dist/activities/execute-deep-agent/hitl.js +77 -103
- package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +51 -23
- package/dist/activities/execute-deep-agent/prompt-builder.js +115 -76
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +18 -32
- package/dist/activities/execute-deep-agent/status-builder-shared.js +10 -65
- package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
- package/dist/activities/execute-deep-agent/streaming-side-effects.d.ts +10 -8
- package/dist/activities/execute-deep-agent/streaming-side-effects.js +12 -14
- package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-tracker.d.ts +36 -13
- package/dist/activities/execute-deep-agent/subagent-tracker.js +76 -39
- package/dist/activities/execute-deep-agent/subagent-tracker.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +25 -28
- package/dist/activities/execute-deep-agent/subagent-transformer.js +29 -82
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +3 -2
- package/dist/activities/execute-deep-agent/subagent-wiring.js +2 -2
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/activities/execute-deep-agent/turn-settle.d.ts +42 -0
- package/dist/activities/execute-deep-agent/turn-settle.js +146 -0
- package/dist/activities/execute-deep-agent/turn-settle.js.map +1 -0
- package/dist/activities/execute-deep-agent/turn-setup.d.ts +212 -0
- package/dist/activities/execute-deep-agent/turn-setup.js +430 -0
- package/dist/activities/execute-deep-agent/turn-setup.js.map +1 -0
- package/dist/activities/execute-deep-agent/turn-stream.d.ts +97 -0
- package/dist/activities/execute-deep-agent/turn-stream.js +242 -0
- package/dist/activities/execute-deep-agent/turn-stream.js.map +1 -0
- package/dist/activities/execute-deep-agent/turn.d.ts +42 -0
- package/dist/activities/execute-deep-agent/turn.js +116 -0
- package/dist/activities/execute-deep-agent/turn.js.map +1 -0
- package/dist/activities/execute-deep-agent/v3-event-recorder.d.ts +6 -5
- package/dist/activities/execute-deep-agent/v3-event-recorder.js +6 -5
- package/dist/activities/execute-deep-agent/v3-event-recorder.js.map +1 -1
- package/dist/activities/execute-deep-agent/v3-events.d.ts +1 -1
- package/dist/activities/execute-deep-agent/v3-events.js +1 -1
- package/dist/activities/execute-deep-agent/v3-status-builder.d.ts +60 -17
- package/dist/activities/execute-deep-agent/v3-status-builder.js +72 -57
- package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
- package/dist/harness/approval-decisions.d.ts +61 -0
- package/dist/harness/approval-decisions.js +102 -0
- package/dist/harness/approval-decisions.js.map +1 -0
- package/dist/harness/capabilities.d.ts +16 -5
- package/dist/harness/capabilities.js +9 -0
- package/dist/harness/capabilities.js.map +1 -1
- package/dist/harness/capture.d.ts +144 -0
- package/dist/harness/capture.js +219 -0
- package/dist/harness/capture.js.map +1 -0
- package/dist/harness/persist-chokepoint.d.ts +19 -4
- package/dist/harness/persist-chokepoint.js +17 -4
- package/dist/harness/persist-chokepoint.js.map +1 -1
- package/dist/harness/run-turn.js +115 -29
- package/dist/harness/run-turn.js.map +1 -1
- package/dist/harness/terminal-table.d.ts +40 -11
- package/dist/harness/terminal-table.js +58 -19
- package/dist/harness/terminal-table.js.map +1 -1
- package/dist/harness/turn-context.d.ts +141 -76
- package/dist/harness/turn-context.js +194 -99
- package/dist/harness/turn-context.js.map +1 -1
- package/dist/harness/types.d.ts +65 -12
- package/dist/harness/types.js +8 -2
- package/dist/harness/types.js.map +1 -1
- package/dist/harness-adapters.d.ts +12 -8
- package/dist/harness-adapters.js +17 -9
- package/dist/harness-adapters.js.map +1 -1
- package/dist/middleware/approval-gate.d.ts +1 -1
- package/dist/middleware/approval-gate.js +1 -1
- package/dist/middleware/approval-gate.js.map +1 -1
- package/dist/middleware/cost-advisory.d.ts +39 -0
- package/dist/middleware/{cost-cap.js → cost-advisory.js} +37 -55
- package/dist/middleware/cost-advisory.js.map +1 -0
- package/dist/middleware/index.d.ts +23 -12
- package/dist/middleware/index.js +26 -18
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/types.d.ts +3 -2
- package/dist/runner-manager.js +1 -5
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +1 -5
- package/dist/runner.js.map +1 -1
- package/dist/shared/attachment-resolver.d.ts +41 -18
- package/dist/shared/attachment-resolver.js +178 -46
- package/dist/shared/attachment-resolver.js.map +1 -1
- package/dist/shared/attachment-zip.d.ts +85 -0
- package/dist/shared/attachment-zip.js +185 -0
- package/dist/shared/attachment-zip.js.map +1 -0
- package/dist/shared/connect-backfill.js +5 -4
- package/dist/shared/connect-backfill.js.map +1 -1
- package/dist/shared/cost-guard.d.ts +3 -3
- package/dist/shared/cost-guard.js +3 -3
- package/dist/shared/execution-status-writer.d.ts +5 -5
- package/dist/shared/execution-status-writer.js +5 -5
- package/dist/shared/extract-structured-output.d.ts +8 -7
- package/dist/shared/extract-structured-output.js +8 -7
- package/dist/shared/extract-structured-output.js.map +1 -1
- package/dist/shared/filereview/cas-progress.d.ts +1 -18
- package/dist/shared/filereview/cas-progress.js +4 -0
- package/dist/shared/filereview/cas-progress.js.map +1 -1
- package/dist/shared/filereview/cas-touched.d.ts +56 -0
- package/dist/shared/filereview/cas-touched.js +63 -0
- package/dist/shared/filereview/cas-touched.js.map +1 -0
- package/dist/shared/filereview/index.d.ts +2 -1
- package/dist/shared/filereview/index.js +2 -1
- package/dist/shared/filereview/index.js.map +1 -1
- package/dist/shared/mcp-resolver.d.ts +4 -5
- package/dist/shared/mcp-resolver.js +4 -5
- package/dist/shared/mcp-resolver.js.map +1 -1
- package/dist/shared/persist-decision.d.ts +3 -4
- package/dist/shared/persist-decision.js +3 -4
- package/dist/shared/persist-decision.js.map +1 -1
- package/dist/shared/plan-mode-permissions.d.ts +2 -2
- package/dist/shared/plan-mode-permissions.js +2 -2
- package/dist/shared/plan-mode-prompt.d.ts +1 -1
- package/dist/shared/plan-mode-prompt.js +1 -1
- package/dist/shared/prompt-sections.d.ts +163 -0
- package/dist/shared/prompt-sections.js +164 -0
- package/dist/shared/prompt-sections.js.map +1 -0
- package/dist/shared/skill-resolver.d.ts +4 -2
- package/dist/shared/skill-resolver.js +4 -2
- package/dist/shared/skill-resolver.js.map +1 -1
- package/dist/shared/tool-rounds.d.ts +32 -0
- package/dist/shared/tool-rounds.js +43 -0
- package/dist/shared/tool-rounds.js.map +1 -1
- package/dist/shared/tool-row.d.ts +34 -0
- package/dist/shared/tool-row.js +52 -0
- package/dist/shared/tool-row.js.map +1 -1
- package/dist/shared/workspace/session-provision.d.ts +3 -3
- package/dist/shared/workspace/session-provision.js +3 -3
- package/dist/shared/workspace/stigmer-link.d.ts +3 -3
- package/dist/shared/workspace/stigmer-link.js +3 -3
- package/dist/shared/workspace/writeback-coordinator.d.ts +15 -22
- package/dist/shared/workspace/writeback-coordinator.js +16 -53
- package/dist/shared/workspace/writeback-coordinator.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +1 -1
- package/package.json +4 -4
- package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +1 -0
- package/src/__test-utils__/execution-record-fixture.ts +18 -2
- package/src/__test-utils__/file-review-projection.ts +156 -0
- package/src/__test-utils__/git-workspace-fixture.ts +58 -0
- package/src/__test-utils__/harness-boot-order-child.ts +7 -2
- package/src/__test-utils__/harness-contract/contract.ts +33 -20
- package/src/__test-utils__/harness-contract/recording-sink.ts +11 -1
- package/src/__test-utils__/harness-contract/runtime-contract.ts +328 -10
- package/src/__test-utils__/harness-contract/scripted-adapter.ts +38 -6
- package/src/__test-utils__/harness-contract/types.ts +27 -3
- package/src/__test-utils__/hermetic-activity.ts +52 -14
- package/src/__test-utils__/model-registry-fixture.ts +32 -7
- package/src/__test-utils__/module-specifiers.ts +24 -0
- package/src/__test-utils__/turn-input-fixture.ts +2 -2
- package/src/__tests__/harness-boot-order.test.ts +9 -2
- package/src/activities/call-agent.ts +2 -2
- package/src/activities/execute-cursor/__test-utils__/contract-subject.ts +47 -3
- package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +2 -48
- package/src/activities/execute-cursor/__tests__/approval-decisions-agree.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +6 -1
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +52 -27
- package/src/activities/execute-cursor/__tests__/cas-observations.test.ts +25 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.enhanced.everything.prompt.md +166 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.enhanced.plan-mode.prompt.md +186 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.hitl.recovery.prompt.md +191 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.hitl.reinvocation.prompt.md +13 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.resumed.prefixed.prompt.md +32 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.resumed.raw.prompt.md +1 -0
- package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +1 -2
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/sdk-error-at-create.status.json +4 -0
- package/src/activities/execute-cursor/__tests__/hermetic/harness-contract.test.ts +24 -4
- package/src/activities/execute-cursor/__tests__/hermetic/stream-self-stop-arms.test.ts +6 -5
- package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +9 -0
- package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +11 -8
- package/src/activities/execute-cursor/__tests__/prompt-goldens.test.ts +229 -0
- package/src/activities/execute-cursor/__tests__/same-identity-reproposal.test.ts +3 -3
- package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +2 -2
- package/src/activities/execute-cursor/__tests__/turn-boundary.test.ts +78 -234
- package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +1 -26
- package/src/activities/execute-cursor/adapter.ts +7 -5
- package/src/activities/execute-cursor/approval-policy.ts +5 -2
- package/src/activities/execute-cursor/approval-state.ts +16 -8
- package/src/activities/execute-cursor/cas-observations.ts +20 -2
- package/src/activities/execute-cursor/cursor-capabilities.ts +24 -5
- package/src/activities/execute-cursor/message-translator.ts +29 -41
- package/src/activities/execute-cursor/prompt-builder.ts +99 -157
- package/src/activities/execute-cursor/turn-boundary.ts +34 -140
- package/src/activities/execute-cursor/turn-settle.ts +37 -51
- package/src/activities/execute-cursor/turn-setup.ts +30 -70
- package/src/activities/execute-cursor/turn-stream.ts +8 -31
- package/src/activities/execute-cursor/turn.ts +5 -1
- package/src/activities/execute-deep-agent/__test-utils__/contract-subject.ts +391 -0
- package/src/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.ts +313 -0
- package/src/activities/execute-deep-agent/__test-utils__/hitl-script.ts +48 -0
- package/src/activities/execute-deep-agent/__test-utils__/scripted-model-module.ts +85 -0
- package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +383 -36
- package/src/activities/execute-deep-agent/__tests__/adapter-graph-is-sdk-free.test.ts +76 -0
- package/src/activities/execute-deep-agent/__tests__/adapter-is-temporal-free.test.ts +47 -0
- package/src/activities/execute-deep-agent/__tests__/adapter.test.ts +80 -0
- package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +1 -1
- package/src/activities/execute-deep-agent/__tests__/execution-state-extended.test.ts +0 -2
- package/src/activities/execute-deep-agent/__tests__/execution-state.test.ts +0 -3
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.build-from-plan.prompt.md +172 -0
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.everything.prompt.md +167 -0
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.minimal.prompt.md +36 -0
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.plan-mode.prompt.md +178 -0
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.skills-below-threshold.prompt.md +158 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/file-review-capture.test.ts +210 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve-all-lease.turn1.status.json +74 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve-all-lease.turn2.status.json +104 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve.turn2.status.json +78 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/file-review-capture.status.json +138 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/gate.turn1.status.json +68 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn1.status.json +154 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn2.status.json +116 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/pause.loop.after-tool.status.json +69 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/pause.loop.mid-tool.status.json +67 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/plain-turn.status.json +47 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/platform-stop.status.json +68 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/recursion-limit.persisted.status.json +280 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/reject.turn2.status.json +77 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/resolution-error.status.json +18 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sequential-gates.turn2.status.json +99 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sequential-gates.turn3.status.json +109 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/skip.turn2.status.json +76 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/structured-output.text-fallback.status.json +51 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/structured-output.tier2.status.json +51 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/structured-output.tool-strategy.status.json +56 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sub-agent-delegation.status.json +90 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/tool-call.status.json +70 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/worker-shutdown.status.json +71 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/harness-contract.test.ts +144 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-approve-all-lease.test.ts +184 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-round-trips.test.ts +203 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-sequential-gates.test.ts +170 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/inline-artifact-across-gate.test.ts +175 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/pause-vs-shutdown.test.ts +206 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/plain-turn.test.ts +142 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/platform-stop.test.ts +126 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/recursion-limit.test.ts +139 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/resolution-error.test.ts +122 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/structured-output.test.ts +236 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/sub-agent-delegation.test.ts +161 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/tool-call.test.ts +157 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/workspace-lock-timeout.test.ts +152 -0
- package/src/activities/execute-deep-agent/__tests__/hitl.test.ts +50 -122
- package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +8 -6
- package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +2 -2
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +35 -35
- package/src/activities/execute-deep-agent/__tests__/prompt-goldens.test.ts +247 -0
- package/src/activities/execute-deep-agent/__tests__/scripted-model.test.ts +329 -0
- package/src/activities/execute-deep-agent/__tests__/status-builder-shared.test.ts +1 -20
- package/src/activities/execute-deep-agent/__tests__/streaming-side-effects.test.ts +78 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-gitignored-capture.test.ts +1 -1
- package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +2 -2
- package/src/activities/execute-deep-agent/__tests__/subagent-tracker.test.ts +57 -31
- package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +19 -79
- package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +15 -15
- package/src/activities/execute-deep-agent/__tests__/summarization-verification.test.ts +1 -1
- package/src/activities/execute-deep-agent/__tests__/turn-stream.test.ts +391 -0
- package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +343 -38
- package/src/activities/execute-deep-agent/__tests__/vision-input.test.ts +5 -5
- package/src/activities/execute-deep-agent/adapter.ts +94 -0
- package/src/activities/execute-deep-agent/cas-capture-observer.ts +11 -0
- package/src/activities/execute-deep-agent/deep-agent-capabilities.ts +43 -0
- package/src/activities/execute-deep-agent/execution-state.ts +4 -13
- package/src/activities/execute-deep-agent/hitl.ts +100 -123
- package/src/activities/execute-deep-agent/prompt-builder.ts +141 -115
- package/src/activities/execute-deep-agent/status-builder-shared.ts +21 -84
- package/src/activities/execute-deep-agent/streaming-side-effects.ts +13 -19
- package/src/activities/execute-deep-agent/subagent-tracker.ts +77 -42
- package/src/activities/execute-deep-agent/subagent-transformer.ts +46 -105
- package/src/activities/execute-deep-agent/subagent-wiring.ts +5 -4
- package/src/activities/execute-deep-agent/turn-settle.ts +164 -0
- package/src/activities/execute-deep-agent/turn-setup.ts +541 -0
- package/src/activities/execute-deep-agent/turn-stream.ts +313 -0
- package/src/activities/execute-deep-agent/turn.ts +131 -0
- package/src/activities/execute-deep-agent/v3-event-recorder.ts +6 -5
- package/src/activities/execute-deep-agent/v3-events.ts +1 -1
- package/src/activities/execute-deep-agent/v3-status-builder.ts +84 -63
- package/src/harness/__tests__/approval-decisions.test.ts +116 -0
- package/src/harness/__tests__/capture.test.ts +268 -0
- package/src/harness/__tests__/mount-skills.test.ts +169 -0
- package/src/harness/__tests__/persist-chokepoint.test.ts +38 -2
- package/src/harness/__tests__/run-turn-deterministic.test.ts +94 -0
- package/src/harness/__tests__/run-turn.test.ts +39 -23
- package/src/harness/__tests__/turn-attachments-vision.test.ts +80 -0
- package/src/harness/__tests__/turn-context.test.ts +73 -26
- package/src/harness/approval-decisions.ts +104 -0
- package/src/harness/capabilities.ts +16 -5
- package/src/harness/capture.ts +301 -0
- package/src/harness/persist-chokepoint.ts +24 -4
- package/src/harness/run-turn.ts +120 -29
- package/src/harness/terminal-table.ts +61 -20
- package/src/harness/turn-context.ts +208 -111
- package/src/harness/types.ts +66 -12
- package/src/harness-adapters.ts +17 -9
- package/src/middleware/__tests__/approval-gate.test.ts +1 -1
- package/src/middleware/__tests__/cost-advisory.test.ts +113 -0
- package/src/middleware/approval-gate.ts +2 -2
- package/src/middleware/{cost-cap.ts → cost-advisory.ts} +40 -70
- package/src/middleware/index.ts +29 -29
- package/src/middleware/types.ts +3 -2
- package/src/runner-manager.ts +0 -8
- package/src/runner.ts +0 -8
- package/src/shared/__tests__/attachment-naming.test.ts +6 -19
- package/src/shared/__tests__/attachment-resolver.test.ts +158 -8
- package/src/shared/__tests__/attachment-zip.test.ts +236 -0
- package/src/shared/__tests__/bedrock-adapter.test.ts +1 -1
- package/src/shared/__tests__/blueprint-resolver.test.ts +43 -0
- package/src/shared/__tests__/cost-guard.test.ts +3 -3
- package/src/shared/__tests__/prompt-sections.test.ts +167 -0
- package/src/shared/__tests__/secret-leak-scan.test.ts +2 -2
- package/src/{activities/execute-deep-agent → shared}/__tests__/stamp-flowed-rows.test.ts +33 -10
- package/src/shared/__tests__/vertex-adapter.test.ts +1 -1
- package/src/shared/attachment-resolver.ts +218 -55
- package/src/shared/attachment-zip.ts +281 -0
- package/src/shared/connect-backfill.ts +9 -11
- package/src/shared/cost-guard.ts +3 -3
- package/src/shared/execution-status-writer.ts +5 -5
- package/src/shared/extract-structured-output.ts +8 -7
- package/src/shared/filereview/__tests__/capture.test.ts +86 -4
- package/src/shared/filereview/__tests__/cas-progress.test.ts +2 -1
- package/src/shared/filereview/cas-progress.ts +5 -19
- package/src/shared/filereview/cas-touched.ts +89 -0
- package/src/shared/filereview/index.ts +4 -2
- package/src/shared/mcp-resolver.ts +4 -5
- package/src/shared/persist-decision.ts +3 -4
- package/src/shared/plan-mode-permissions.ts +2 -2
- package/src/shared/plan-mode-prompt.ts +1 -1
- package/src/shared/prompt-sections.ts +256 -0
- package/src/shared/skill-resolver.ts +4 -2
- package/src/shared/tool-rounds.ts +48 -0
- package/src/shared/tool-row.ts +61 -0
- package/src/shared/workspace/__tests__/stigmer-link.test.ts +7 -22
- package/src/shared/workspace/__tests__/writeback-coordinator.test.ts +20 -61
- package/src/shared/workspace/session-provision.ts +3 -3
- package/src/shared/workspace/stigmer-link.ts +3 -3
- package/src/shared/workspace/writeback-coordinator.ts +16 -64
- package/src/tools/index.ts +1 -1
- package/dist/activities/execute-cursor/capture-flow.d.ts +0 -142
- package/dist/activities/execute-cursor/capture-flow.js +0 -285
- package/dist/activities/execute-cursor/capture-flow.js.map +0 -1
- package/dist/activities/execute-cursor/command-provenance.d.ts +0 -48
- package/dist/activities/execute-cursor/command-provenance.js +0 -38
- package/dist/activities/execute-cursor/command-provenance.js.map +0 -1
- package/dist/activities/execute-deep-agent/attachment-injector.d.ts +0 -134
- package/dist/activities/execute-deep-agent/attachment-injector.js +0 -386
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +0 -1
- package/dist/activities/execute-deep-agent/command-provenance.d.ts +0 -61
- package/dist/activities/execute-deep-agent/command-provenance.js +0 -72
- package/dist/activities/execute-deep-agent/command-provenance.js.map +0 -1
- package/dist/activities/execute-deep-agent/environment.d.ts +0 -24
- package/dist/activities/execute-deep-agent/environment.js +0 -58
- package/dist/activities/execute-deep-agent/environment.js.map +0 -1
- package/dist/activities/execute-deep-agent/event-recorder.d.ts +0 -21
- package/dist/activities/execute-deep-agent/event-recorder.js +0 -67
- package/dist/activities/execute-deep-agent/event-recorder.js.map +0 -1
- package/dist/activities/execute-deep-agent/index.d.ts +0 -15
- package/dist/activities/execute-deep-agent/index.js +0 -918
- package/dist/activities/execute-deep-agent/index.js.map +0 -1
- package/dist/activities/execute-deep-agent/mcp-gate.d.ts +0 -28
- package/dist/activities/execute-deep-agent/mcp-gate.js +0 -22
- package/dist/activities/execute-deep-agent/mcp-gate.js.map +0 -1
- package/dist/activities/execute-deep-agent/post-stream.d.ts +0 -23
- package/dist/activities/execute-deep-agent/post-stream.js +0 -71
- package/dist/activities/execute-deep-agent/post-stream.js.map +0 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +0 -136
- package/dist/activities/execute-deep-agent/setup.js +0 -767
- package/dist/activities/execute-deep-agent/setup.js.map +0 -1
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.d.ts +0 -36
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.js +0 -56
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.js.map +0 -1
- package/dist/activities/execute-deep-agent/status-builder.d.ts +0 -93
- package/dist/activities/execute-deep-agent/status-builder.js +0 -365
- package/dist/activities/execute-deep-agent/status-builder.js.map +0 -1
- package/dist/activities/execute-deep-agent/streaming-terminal.d.ts +0 -21
- package/dist/activities/execute-deep-agent/streaming-terminal.js +0 -80
- package/dist/activities/execute-deep-agent/streaming-terminal.js.map +0 -1
- package/dist/activities/execute-deep-agent/streaming-v3.d.ts +0 -13
- package/dist/activities/execute-deep-agent/streaming-v3.js +0 -174
- package/dist/activities/execute-deep-agent/streaming-v3.js.map +0 -1
- package/dist/activities/execute-deep-agent/streaming.d.ts +0 -81
- package/dist/activities/execute-deep-agent/streaming.js +0 -160
- package/dist/activities/execute-deep-agent/streaming.js.map +0 -1
- package/dist/middleware/cost-cap.d.ts +0 -22
- package/dist/middleware/cost-cap.js.map +0 -1
- package/dist/middleware/graceful-stop.d.ts +0 -17
- package/dist/middleware/graceful-stop.js +0 -63
- package/dist/middleware/graceful-stop.js.map +0 -1
- package/dist/shared/skill-writer.d.ts +0 -75
- package/dist/shared/skill-writer.js +0 -207
- package/dist/shared/skill-writer.js.map +0 -1
- package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +0 -1032
- package/src/activities/execute-cursor/__tests__/command-provenance.test.ts +0 -240
- package/src/activities/execute-cursor/__tests__/progress-substrate.test.ts +0 -169
- package/src/activities/execute-cursor/capture-flow.ts +0 -379
- package/src/activities/execute-cursor/command-provenance.ts +0 -73
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +0 -1161
- package/src/activities/execute-deep-agent/__tests__/command-provenance.test.ts +0 -252
- package/src/activities/execute-deep-agent/__tests__/environment.test.ts +0 -108
- package/src/activities/execute-deep-agent/__tests__/event-recorder.test.ts +0 -150
- package/src/activities/execute-deep-agent/__tests__/hitl-integration.test.ts +0 -226
- package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +0 -318
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +0 -351
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +0 -293
- package/src/activities/execute-deep-agent/__tests__/index.test.ts +0 -99
- package/src/activities/execute-deep-agent/__tests__/mcp-gate.test.ts +0 -44
- package/src/activities/execute-deep-agent/__tests__/post-stream.test.ts +0 -112
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +0 -358
- package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +0 -1933
- package/src/activities/execute-deep-agent/__tests__/streaming-terminal.test.ts +0 -40
- package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +0 -597
- package/src/activities/execute-deep-agent/__tests__/streaming.test.ts +0 -508
- package/src/activities/execute-deep-agent/attachment-injector.ts +0 -627
- package/src/activities/execute-deep-agent/command-provenance.ts +0 -102
- package/src/activities/execute-deep-agent/environment.ts +0 -76
- package/src/activities/execute-deep-agent/event-recorder.ts +0 -95
- package/src/activities/execute-deep-agent/index.ts +0 -1072
- package/src/activities/execute-deep-agent/mcp-gate.ts +0 -37
- package/src/activities/execute-deep-agent/post-stream.ts +0 -109
- package/src/activities/execute-deep-agent/setup.ts +0 -1081
- package/src/activities/execute-deep-agent/stamp-flowed-rows.ts +0 -64
- package/src/activities/execute-deep-agent/status-builder.ts +0 -481
- package/src/activities/execute-deep-agent/streaming-terminal.ts +0 -106
- package/src/activities/execute-deep-agent/streaming-v3.ts +0 -277
- package/src/activities/execute-deep-agent/streaming.ts +0 -313
- package/src/middleware/__tests__/cost-cap.test.ts +0 -192
- package/src/middleware/__tests__/graceful-stop.test.ts +0 -105
- package/src/middleware/graceful-stop.ts +0 -86
- package/src/shared/__tests__/skill-writer.test.ts +0 -372
- package/src/shared/skill-writer.ts +0 -266
|
@@ -36,12 +36,20 @@
|
|
|
36
36
|
* `awaiting_approval` resolves, and never executes in that turn.
|
|
37
37
|
* 3. Reinvoked with APPROVE for that id → executes exactly once and the row
|
|
38
38
|
* is carried to COMPLETED; with REJECT or SKIP → never executes and the
|
|
39
|
-
* turn continues (the row's
|
|
40
|
-
*
|
|
41
|
-
*
|
|
39
|
+
* turn continues (the row's SKIPPED status is the runtime's to write,
|
|
40
|
+
* `harness/approval-decisions.ts`, proven in the runtime half — the
|
|
41
|
+
* driver stands in for that write between turns, Q-M1-3); reinvoked again
|
|
42
|
+
* after the approval → still once, never re-gated.
|
|
42
43
|
* 4. Aborting `stopSignal` mid-hang settles `runTurn` as `interrupted` within
|
|
43
|
-
* {@link INTERRUPT_SETTLE_BOUND_MS} and nothing after the stop executes
|
|
44
|
-
*
|
|
44
|
+
* {@link INTERRUPT_SETTLE_BOUND_MS} and nothing after the stop executes or
|
|
45
|
+
* is even gated (a proposal after the hang has no row); a signal aborted
|
|
46
|
+
* BEFORE `runTurn` yields `interrupted` with nothing done. Stated over a
|
|
47
|
+
* FRESH proposal on purpose: WHEN an already-approved pending action
|
|
48
|
+
* executes on the resumed turn is the pause primitive's business — an
|
|
49
|
+
* interrupt engine completes the pending task before its model speaks,
|
|
50
|
+
* a deny-and-retry engine re-reaches it whenever its model does — so it
|
|
51
|
+
* is below the contract line (S3 M3 Q-M3-2; each harness's own kit file
|
|
52
|
+
* observes its order).
|
|
45
53
|
* 5. Usage reaches the sink as non-negative deltas summing to what the engine
|
|
46
54
|
* emitted.
|
|
47
55
|
* 6. Capability and behaviour agree on the state id: an `engine-minted`
|
|
@@ -74,7 +82,7 @@ import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/a
|
|
|
74
82
|
import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
75
83
|
import { ApprovalAction, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
76
84
|
|
|
77
|
-
import { approvalDecisionsOf } from "../../harness/
|
|
85
|
+
import { approvalDecisionsOf, terminalizeNonExecutingDecisions } from "../../harness/approval-decisions.js";
|
|
78
86
|
import type { TurnInput, TurnOutcome, UsageDelta } from "../../harness/types.js";
|
|
79
87
|
import type { ProposedAction } from "../approval-contract/types.js";
|
|
80
88
|
import { emptyStatus, findToolCallRow } from "../proto-helpers.js";
|
|
@@ -92,7 +100,7 @@ import type { EngineView, HarnessContractSubject, TurnScenario } from "./types.j
|
|
|
92
100
|
*/
|
|
93
101
|
export const INTERRUPT_SETTLE_BOUND_MS = 5_000;
|
|
94
102
|
|
|
95
|
-
const OUTCOME_KINDS = ["completed", "cancelled", "awaiting_approval", "failed", "interrupted"] as const satisfies readonly TurnOutcome["kind"][];
|
|
103
|
+
const OUTCOME_KINDS = ["completed", "cancelled", "awaiting_approval", "tool_call_limit", "failed", "interrupted"] as const satisfies readonly TurnOutcome["kind"][];
|
|
96
104
|
|
|
97
105
|
/** Representative gated action shared by the invariants. */
|
|
98
106
|
const WRITE_ALPHA: ProposedAction = { kind: "write", resource: "/work/alpha.txt" };
|
|
@@ -185,8 +193,14 @@ export class ExecutionDriver {
|
|
|
185
193
|
return { input, sink, outcome };
|
|
186
194
|
}
|
|
187
195
|
|
|
188
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* The runtime's post-turn bookkeeping: settle the SKIP and REJECT rows the
|
|
198
|
+
* adapter never executes (the runtime's write, `terminalizeNonExecutingDecisions`,
|
|
199
|
+
* so a decided row is not re-read as WAITING on the next turn), persist the
|
|
200
|
+
* status, adopt a bound id, advance the cycle.
|
|
201
|
+
*/
|
|
189
202
|
recordTurn(sink: RecordingTurnSink): void {
|
|
203
|
+
terminalizeNonExecutingDecisions(sink.status);
|
|
190
204
|
this.persisted = sink.status;
|
|
191
205
|
const bound = sink.boundStateIds.at(-1);
|
|
192
206
|
if (bound !== undefined) this.threadId = bound;
|
|
@@ -321,9 +335,10 @@ export async function assertProposalIsWaitingAndUnexecuted(subject: HarnessContr
|
|
|
321
335
|
* turn continues — the row's terminal status is NOT asserted here because
|
|
322
336
|
* it is not the adapter's to write (the decision is the server's field and
|
|
323
337
|
* the WAITING → SKIPPED transition follows from it with no engine knowledge,
|
|
324
|
-
* so it is the runtime's
|
|
325
|
-
*
|
|
326
|
-
*
|
|
338
|
+
* so it is the runtime's — `terminalizeNonExecutingDecisions`, landed at
|
|
339
|
+
* S3 M1 and pinned by the runtime half's REJECT / SKIP arms; S2 M4 Q-M4-1).
|
|
340
|
+
* The reinvocation sees a CLONE of the persisted status, as it would from
|
|
341
|
+
* the server.
|
|
327
342
|
*/
|
|
328
343
|
export async function assertDecisionsExecuteExactlyOnce(subject: HarnessContractSubject): Promise<void> {
|
|
329
344
|
const driver = new ExecutionDriver(subject, "inv3");
|
|
@@ -360,22 +375,20 @@ export async function assertDecisionsExecuteExactlyOnce(subject: HarnessContract
|
|
|
360
375
|
* Invariant 4: the stop signal is honoured promptly and completely. A turn
|
|
361
376
|
* stopped WHILE HANGING (the engine has reported it is parked, so the stop
|
|
362
377
|
* lands in the hang and not in the adapter's setup) settles `interrupted`
|
|
363
|
-
* within the bound and
|
|
364
|
-
*
|
|
365
|
-
* nothing at all.
|
|
378
|
+
* within the bound, and the proposal after the hang neither executes nor
|
|
379
|
+
* reaches the gate (no row of any status); a turn entered under an aborted
|
|
380
|
+
* signal does nothing at all. The proposal is FRESH — see the catalog on why
|
|
381
|
+
* an approved one would assert the pause primitive's order, not the contract.
|
|
366
382
|
*/
|
|
367
383
|
export async function assertStopSignalInterrupts(subject: HarnessContractSubject, boundMs = INTERRUPT_SETTLE_BOUND_MS): Promise<void> {
|
|
368
384
|
const driver = new ExecutionDriver(subject, "inv4");
|
|
369
|
-
const id = "inv4-
|
|
370
|
-
|
|
371
|
-
const proposed = await driver.turn([scenario.propose(id, WRITE_ALPHA)]);
|
|
372
|
-
expect(proposed.outcome.kind, `${subject.name}: proposal must end awaiting_approval`).toBe("awaiting_approval");
|
|
373
|
-
driver.decide(id, ApprovalAction.APPROVE);
|
|
385
|
+
const id = "inv4-proposed-after-hang";
|
|
374
386
|
|
|
375
387
|
const hanging = driver.begin([scenario.say("working"), scenario.hang(), scenario.propose(id, WRITE_ALPHA)], { stopWhenHanging: "kit: user pause" });
|
|
376
388
|
const outcome = await settleWithinBound(subject, hanging.settled, "after stopSignal aborted mid-hang", boundMs);
|
|
377
389
|
expect(outcome.kind, `${subject.name}: a turn stopped mid-hang must settle interrupted`).toBe("interrupted");
|
|
378
|
-
expect(subject.executionCount(id), `${subject.name}: nothing after the stop may execute
|
|
390
|
+
expect(subject.executionCount(id), `${subject.name}: nothing after the stop may execute`).toBe(0);
|
|
391
|
+
expect(findToolCallRow(hanging.sink.status, id), `${subject.name}: nothing after the stop may even reach the gate — the proposal after the hang has no row`).toBeUndefined();
|
|
379
392
|
driver.recordTurn(hanging.sink);
|
|
380
393
|
|
|
381
394
|
const preAborted = new RecordingTurnSink({ status: driver.seedStatus() });
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
22
22
|
|
|
23
23
|
import type { TurnSink, UsageDelta } from "../../harness/types.js";
|
|
24
|
+
import type { CasTouchedReader } from "../../shared/filereview/cas-touched.js";
|
|
24
25
|
import { TimingRecorder } from "../../shared/cold-start-timing.js";
|
|
25
26
|
import { emptyStatus } from "../proto-helpers.js";
|
|
26
27
|
|
|
@@ -30,7 +31,8 @@ export type SinkEvent =
|
|
|
30
31
|
| { readonly kind: "activity"; readonly detail: string | undefined }
|
|
31
32
|
| { readonly kind: "usage"; readonly delta: UsageDelta }
|
|
32
33
|
| { readonly kind: "progress"; readonly label: string }
|
|
33
|
-
| { readonly kind: "bind"; readonly harnessStateId: string }
|
|
34
|
+
| { readonly kind: "bind"; readonly harnessStateId: string }
|
|
35
|
+
| { readonly kind: "cas-observations" };
|
|
34
36
|
|
|
35
37
|
export interface RecordingTurnSinkOptions {
|
|
36
38
|
/** The status this turn folds into; a fresh empty one when omitted. */
|
|
@@ -80,6 +82,14 @@ export class RecordingTurnSink implements TurnSink {
|
|
|
80
82
|
this.log.push({ kind: "bind", harnessStateId });
|
|
81
83
|
}
|
|
82
84
|
|
|
85
|
+
bindCasObservations(read: CasTouchedReader): void {
|
|
86
|
+
this.casObservations = read;
|
|
87
|
+
this.log.push({ kind: "cas-observations" });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The reader the adapter bound, for a test that wants to pull what the engine observed. */
|
|
91
|
+
casObservations: CasTouchedReader | undefined;
|
|
92
|
+
|
|
83
93
|
/** Every call, in order. */
|
|
84
94
|
get events(): readonly SinkEvent[] {
|
|
85
95
|
return this.log;
|
|
@@ -44,21 +44,29 @@
|
|
|
44
44
|
*/
|
|
45
45
|
|
|
46
46
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
47
|
-
import { mkdirSync } from "node:fs";
|
|
47
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
48
48
|
import { realpath } from "node:fs/promises";
|
|
49
49
|
import { join } from "node:path";
|
|
50
|
-
import
|
|
51
|
-
import {
|
|
50
|
+
import { ApprovalAction, ExecutionControlSignal, ExecutionPhase, MessageType, SubAgentStatus, TodoStatus, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
51
|
+
import { create, type JsonObject } from "@bufbuild/protobuf";
|
|
52
|
+
import { RecalledMemoriesReportSchema, type AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
53
|
+
import { ExecutionArtifactSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
|
|
54
|
+
import { SubAgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
|
|
55
|
+
import { TodoItemSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/todo_pb";
|
|
56
|
+
import { StreamingUsageSummarySchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/usage_pb";
|
|
57
|
+
import { WorkspaceWriteBackPhase, WorkspaceWriteBackSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/writeback_pb";
|
|
52
58
|
|
|
53
59
|
import type { StigmerClient } from "../../client/stigmer-client.js";
|
|
54
60
|
import type { Config } from "../../config.js";
|
|
55
61
|
import { HARNESS_ACTIVITY_NAMES, createHarnessActivities } from "../../harness/registry.js";
|
|
56
62
|
import { TERMINAL_COPY } from "../../harness/terminal-table.js";
|
|
57
|
-
import { approvalDecisionsOf } from "../../harness/
|
|
63
|
+
import { REJECTED_BY_USER_ERROR, approvalDecisionsOf } from "../../harness/approval-decisions.js";
|
|
64
|
+
import { MIN_TOOL_ROUNDS, TOOL_CALL_LIMIT_ERROR_PREFIX, TOOL_CALL_LIMIT_USER_COPY, formatToolCallLimitError } from "../../shared/tool-rounds.js";
|
|
58
65
|
import type { FailureSurface } from "../../harness/types.js";
|
|
59
66
|
import type { ExecuteActivityInput } from "../../shared/activity-input.js";
|
|
60
67
|
import { acquireWorkspaceLock } from "../../shared/workspace/workspace-lock.js";
|
|
61
68
|
import { executionRecordFixture, type ExecutionRecordOptions } from "../execution-record-fixture.js";
|
|
69
|
+
import { initGitWorkspace } from "../git-workspace-fixture.js";
|
|
62
70
|
import {
|
|
63
71
|
ExecutionRecord,
|
|
64
72
|
ScriptedClock,
|
|
@@ -292,6 +300,256 @@ export async function assertApprovalRoundTrip(harness: RuntimeContractHarness, l
|
|
|
292
300
|
return { driver, invocations: [first, second], final: finalStatusOf(harness, driver) };
|
|
293
301
|
}
|
|
294
302
|
|
|
303
|
+
// ── Arm: file review — capture, decide, reconcile ───────────────────────────
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Apply-then-review across two invocations — the runtime's own act for every
|
|
307
|
+
* harness (`harness/capture.ts`; `turn-context.ts` `reconcileReinvocation`),
|
|
308
|
+
* proven on a REAL git work tree (the session's workspace directory IS the
|
|
309
|
+
* tree, so no harness needs a workspace entry to reach it).
|
|
310
|
+
*
|
|
311
|
+
* Turn 1: two writes FLOW (no gate row, each executed exactly once), and the
|
|
312
|
+
* runtime — not the adapter — pins the baseline before the engine, captures
|
|
313
|
+
* the candidate after it, badges the two flowed rows with the change set,
|
|
314
|
+
* carries the strip at the WAITING write, and pauses `WAITING_FOR_APPROVAL`
|
|
315
|
+
* with the ledger reading `[baseline, candidate]` under the harness's own
|
|
316
|
+
* file-review identity. Between turns the reviewer keeps one file and
|
|
317
|
+
* discards the other (the record stands in for the server's decision and
|
|
318
|
+
* projection). Turn 2: the reinvocation reconciles WITHOUT asking the engine
|
|
319
|
+
* — the kept file holds its bytes, the discarded one is back at its baseline,
|
|
320
|
+
* RECONCILED is authored, the execution settles COMPLETED with the runtime's
|
|
321
|
+
* discard row, and the engine's execution counts are unchanged.
|
|
322
|
+
*
|
|
323
|
+
* Content is the engine's own (a real `edit_file`, a real hook-allowed edit,
|
|
324
|
+
* the fake's one line), so the arm asserts PATHS and the reconcile's effect
|
|
325
|
+
* on disk, never bytes; the change kinds differ per engine (an engine that
|
|
326
|
+
* seeds its ledger file before the turn produces a MODIFY where another
|
|
327
|
+
* produces an ADD) and are not asserted either.
|
|
328
|
+
*/
|
|
329
|
+
export async function assertFileReviewRoundTrip(harness: RuntimeContractHarness, label = "rt-file-review"): Promise<RuntimeArmResult> {
|
|
330
|
+
const { subject } = harness;
|
|
331
|
+
const driver = new RuntimeExecutionDriver(harness, label);
|
|
332
|
+
initGitWorkspace(driver.workspaceDir, { "README.md": "# the kit's tree\n" });
|
|
333
|
+
const keepId = `${label}-keep`;
|
|
334
|
+
const dropId = `${label}-drop`;
|
|
335
|
+
const KEPT = "kept.md";
|
|
336
|
+
const DROPPED = "dropped.md";
|
|
337
|
+
const changeSetId = `${driver.record.executionId}:0`;
|
|
338
|
+
|
|
339
|
+
const first = await driver.turn([scenario.flowingWrite(keepId, KEPT), scenario.flowingWrite(dropId, DROPPED), scenario.say("Edited two files.")]);
|
|
340
|
+
expect(slimOf(subject, first).phase, `${subject.name}: a captured change pauses the turn for review`).toBe("EXECUTION_WAITING_FOR_APPROVAL");
|
|
341
|
+
expect(driver.record.waitingToolCalls(), `${subject.name}: a flowing write opens no gate row`).toHaveLength(0);
|
|
342
|
+
expect(subject.executionCount(keepId), `${subject.name}: a flowing write executes at once`).toBe(1);
|
|
343
|
+
expect(subject.executionCount(dropId)).toBe(1);
|
|
344
|
+
expect(existsSync(join(driver.workspaceDir, KEPT)) && existsSync(join(driver.workspaceDir, DROPPED)), `${subject.name}: both writes landed on the tree`).toBe(true);
|
|
345
|
+
|
|
346
|
+
const afterCapture = finalStatusOf(harness, driver);
|
|
347
|
+
const events = afterCapture.fileReviewEventStream?.events ?? [];
|
|
348
|
+
expect(events.map((e) => e.payload.case), `${subject.name}: the runtime authored the baseline and the candidate`).toEqual(["baselineCaptured", "candidateCaptured"]);
|
|
349
|
+
const candidate = events[1]!.payload;
|
|
350
|
+
if (candidate.case !== "candidateCaptured") throw new Error("unreachable");
|
|
351
|
+
expect(candidate.value.changeSetId).toBe(changeSetId);
|
|
352
|
+
expect([...candidate.value.changes.map((c) => c.pathAfter)].sort(), `${subject.name}: exactly the two written paths are reviewable`).toEqual([DROPPED, KEPT]);
|
|
353
|
+
const baseline = events[0]!.payload;
|
|
354
|
+
if (baseline.case !== "baselineCaptured") throw new Error("unreachable");
|
|
355
|
+
expect(baseline.value.harnessId, `${subject.name}: the ledger carries this harness's file-review identity`).toBe(subject.adapter.capabilities.fileReview.harnessId);
|
|
356
|
+
const rows = new Map(afterCapture.messages.flatMap((m) => m.toolCalls).map((tc) => [tc.id, tc]));
|
|
357
|
+
expect(rows.get(keepId)?.fileChangeSetId, `${subject.name}: a flowed row is badged with the change set`).toBe(changeSetId);
|
|
358
|
+
expect(rows.get(dropId)?.fileChangeSetId).toBe(changeSetId);
|
|
359
|
+
expect(rows.get(keepId)?.status, `${subject.name}: a flowed row is COMPLETED, never gated`).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
360
|
+
// The strip is the runtime's, attached on its writes (Q-M4-7). Its COUNT is
|
|
361
|
+
// the capture floor's: a real engine's first persist precedes its first
|
|
362
|
+
// write and both writes land within one floor interval, so the pause can
|
|
363
|
+
// honestly read "0 files" beside a two-file candidate (F-M4-3, recorded
|
|
364
|
+
// for S5: whether the terminal write should bypass the floor). The arm
|
|
365
|
+
// asserts the ownership, not the floor.
|
|
366
|
+
expect(afterCapture.fileChangeProgress?.changeSetId, `${subject.name}: the runtime carries this turn's strip`).toBe(changeSetId);
|
|
367
|
+
expect(afterCapture.completedAt, `${subject.name}: a review pause is not complete`).toBe("");
|
|
368
|
+
|
|
369
|
+
const keptBytes = readFileSync(join(driver.workspaceDir, KEPT), "utf-8");
|
|
370
|
+
expect(keptBytes).toContain(keepId);
|
|
371
|
+
harness.clock.tick();
|
|
372
|
+
expect(driver.record.decideCapturedFileChanges({ approve: [KEPT], reject: [DROPPED] }, new Date().toISOString()), `${subject.name}: two files to decide`).toBe(2);
|
|
373
|
+
|
|
374
|
+
const second = await driver.turn([]);
|
|
375
|
+
expect(slimOf(subject, second).phase, `${subject.name}: a pure file-review resume completes without the engine`).toBe("EXECUTION_COMPLETED");
|
|
376
|
+
// The kept file is the engine-not-re-run witness: a subject that counts
|
|
377
|
+
// executions by reading its ledger off disk (the native one, Q-M3-4) reads
|
|
378
|
+
// the DISCARDED file's count as 0 now, because the reconcile just restored
|
|
379
|
+
// that file to its baseline — which is the reconcile working, not the
|
|
380
|
+
// engine forgetting.
|
|
381
|
+
expect(subject.executionCount(keepId), `${subject.name}: the engine was not asked again`).toBe(1);
|
|
382
|
+
expect(readFileSync(join(driver.workspaceDir, KEPT), "utf-8"), `${subject.name}: the kept file holds its reviewed bytes`).toBe(keptBytes);
|
|
383
|
+
const droppedPath = join(driver.workspaceDir, DROPPED);
|
|
384
|
+
expect(!existsSync(droppedPath) || !readFileSync(droppedPath, "utf-8").includes(dropId), `${subject.name}: the discarded file is back at its baseline`).toBe(true);
|
|
385
|
+
const final = finalStatusOf(harness, driver);
|
|
386
|
+
expect(systemMessages(final), `${subject.name}: the discard row names the discarded path`).toContain(`${TERMINAL_COPY.fileReview.discardedPrefix}${DROPPED}.`);
|
|
387
|
+
expect(final.fileReviewEventStream?.events.map((e) => e.payload.case), `${subject.name}: the reconcile is authored`).toContain("reconciled");
|
|
388
|
+
expect(final.completedAt).not.toBe("");
|
|
389
|
+
return { driver, invocations: [first, second], final };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ── Arm: awaiting_approval then REJECT or SKIP ──────────────────────────────
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* A decision that never runs the tool: the user REJECTs (or SKIPs) the
|
|
396
|
+
* proposal; the reinvocation continues WITHOUT it — the adapter's next step
|
|
397
|
+
* runs, the turn COMPLETES, the tool executes zero times — and the runtime
|
|
398
|
+
* settles the row SKIPPED (a REJECT row carries `error: "Rejected by user"`),
|
|
399
|
+
* so no WAITING row survives on a finished execution. This is the proto
|
|
400
|
+
* contract as of stigmer#197 and the runtime's own write
|
|
401
|
+
* (`harness/approval-decisions.ts`); before S3 M1 the runtime FAILED a
|
|
402
|
+
* REJECTed reinvocation before any engine ran.
|
|
403
|
+
*/
|
|
404
|
+
export async function assertNonExecutingDecisionSettlesSkipped(
|
|
405
|
+
harness: RuntimeContractHarness,
|
|
406
|
+
action: ApprovalAction.REJECT | ApprovalAction.SKIP,
|
|
407
|
+
label = `rt-${ApprovalAction[action].toLowerCase()}`,
|
|
408
|
+
): Promise<RuntimeArmResult> {
|
|
409
|
+
const { subject } = harness;
|
|
410
|
+
const driver = new RuntimeExecutionDriver(harness, label);
|
|
411
|
+
const id = `${label}-shell`;
|
|
412
|
+
const propose = scenario.propose(id, { kind: "shell", resource: `rm -rf build # ${label}` });
|
|
413
|
+
const verdict = ApprovalAction[action];
|
|
414
|
+
|
|
415
|
+
const first = await driver.turn([propose]);
|
|
416
|
+
expect(slimOf(subject, first).phase, `${subject.name}: a proposal returns WAITING_FOR_APPROVAL`).toBe("EXECUTION_WAITING_FOR_APPROVAL");
|
|
417
|
+
expect(driver.decide(action), `${subject.name}: one row to decide`).toBe(1);
|
|
418
|
+
|
|
419
|
+
const second = await driver.turn([propose, scenario.say("Moving on.")]);
|
|
420
|
+
expect(slimOf(subject, second).phase, `${subject.name}: after ${verdict} the run continues to COMPLETED — it does not fail`).toBe("EXECUTION_COMPLETED");
|
|
421
|
+
expect(slimOf(subject, second).final_text, `${subject.name}: the step after the ${verdict}ed action ran`).toBe("Moving on.");
|
|
422
|
+
expect(subject.executionCount(id), `${subject.name}: a ${verdict}ed action never executes`).toBe(0);
|
|
423
|
+
|
|
424
|
+
const final = finalStatusOf(harness, driver);
|
|
425
|
+
const row = final.messages.flatMap((m) => m.toolCalls).find((tc) => tc.id === id);
|
|
426
|
+
expect(row, `${subject.name}: the decided row is carried on the transcript`).toBeDefined();
|
|
427
|
+
expect(row!.status, `${subject.name}: the runtime settles a ${verdict}ed row SKIPPED`).toBe(ToolCallStatus.TOOL_CALL_SKIPPED);
|
|
428
|
+
expect(row!.approvalAction, `${subject.name}: the decision stays legible on the settled row`).toBe(action);
|
|
429
|
+
expect(row!.error, `${subject.name}: a REJECT carries its reason, a SKIP carries none`).toBe(action === ApprovalAction.REJECT ? REJECTED_BY_USER_ERROR : "");
|
|
430
|
+
expect(driver.record.waitingToolCalls(), `${subject.name}: nothing waits on a finished execution`).toHaveLength(0);
|
|
431
|
+
expect(final.error, `${subject.name}: the execution carries no error`).toBe("");
|
|
432
|
+
return { driver, invocations: [first, second], final };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ── Arm: the tool-call limit ────────────────────────────────────────────────
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* The engine exhausted its tool-round budget: TERMINATED (not FAILED — the
|
|
439
|
+
* platform deliberately stopped the run and the conversation continues on
|
|
440
|
+
* the next message), `error` starting with the cross-repo prefix Stigmer
|
|
441
|
+
* Cloud's channel delivery matches on, the user copy as the one system row,
|
|
442
|
+
* `completedAt` stamped, RETURNED (a retry would spend the same budget), and
|
|
443
|
+
* the step after the limit never processed. Before S3 M1 the native harness
|
|
444
|
+
* RETURNED this terminal without persisting it (M0 finding F-M0-4); the
|
|
445
|
+
* runtime's arm is the one persisted terminal.
|
|
446
|
+
*
|
|
447
|
+
* The execution SETS the budget it exhausts: the record carries the smallest
|
|
448
|
+
* valid `max_tool_rounds` (an unbounded execution has no limit to reach — on
|
|
449
|
+
* native `resolveRecursionLimit(0)` is unlimited, and the first run of this
|
|
450
|
+
* arm against it spun until the test timed out; S3 M3 F-M3-3). The fake
|
|
451
|
+
* exhausts whatever budget it is given; a real engine spins its cheapest
|
|
452
|
+
* ungated tool until the graph stops it.
|
|
453
|
+
*/
|
|
454
|
+
export async function assertToolCallLimitTerminates(harness: RuntimeContractHarness, label = "rt-tool-call-limit"): Promise<RuntimeArmResult> {
|
|
455
|
+
const { subject } = harness;
|
|
456
|
+
const driver = new RuntimeExecutionDriver(harness, label, { maxToolRounds: MIN_TOOL_ROUNDS });
|
|
457
|
+
const invocation = await driver.turn([scenario.say("Working…"), scenario.limit(), scenario.say(NEVER_SEEN)]);
|
|
458
|
+
// `slimOf` is the RETURN assertion (a retry would spend the same budget).
|
|
459
|
+
expect(slimOf(subject, invocation).phase, `${subject.name}: the tool-call limit is TERMINATED, not FAILED`).toBe("EXECUTION_TERMINATED");
|
|
460
|
+
const final = finalStatusOf(harness, driver);
|
|
461
|
+
expect(final.phase).toBe(ExecutionPhase.EXECUTION_TERMINATED);
|
|
462
|
+
expect(final.error.startsWith(TOOL_CALL_LIMIT_ERROR_PREFIX), `${subject.name}: the error carries the cross-repo prefix; got "${final.error}"`).toBe(true);
|
|
463
|
+
expect(final.error, `${subject.name}: the copy is the table's, byte for byte`).toBe(formatToolCallLimitError());
|
|
464
|
+
expect(systemMessages(final), `${subject.name}: the user copy is the one system row`).toEqual([TOOL_CALL_LIMIT_USER_COPY]);
|
|
465
|
+
expect(final.completedAt, `${subject.name}: a TERMINATED turn completes`).not.toBe("");
|
|
466
|
+
expect(aiMessages(final), `${subject.name}: the step after the limit is never processed`).not.toContain(NEVER_SEEN);
|
|
467
|
+
expect(driver.record.persistedPhases.at(-1), `${subject.name}: the terminal is persisted, not merely returned`).toBe(ExecutionPhase.EXECUTION_TERMINATED);
|
|
468
|
+
return { driver, invocations: [invocation], final };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ── Arm: the seed's breadth on a reinvocation ───────────────────────────────
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* What a reinvocation carries forward. Every runner-owned collection and
|
|
475
|
+
* singleton the prior invocation persisted — the sub-agent rows, the to-dos,
|
|
476
|
+
* the artifacts, the write-back rows, the usage summary, the recalled-memories
|
|
477
|
+
* report, the structured output — is on the completed status of the resumed
|
|
478
|
+
* turn, because the server REPLACES each of them wholesale when the runner's
|
|
479
|
+
* write carries any (`update-status.ts`), so a write holding only the resumed
|
|
480
|
+
* turn's own would erase the prior turn's. A first turn (no transcript)
|
|
481
|
+
* carries none of them, whatever the record holds: the seed is gated on the
|
|
482
|
+
* persisted transcript, the one signal every harness shares.
|
|
483
|
+
*
|
|
484
|
+
* The record here REPLACES its held status with every full write (stricter
|
|
485
|
+
* than the server's presence guard), so a dropped collection is a failing
|
|
486
|
+
* assertion, not a masked one. The planted rows are the prior turn's own
|
|
487
|
+
* facts, written onto the record between invocations as the prior turn would
|
|
488
|
+
* have persisted them.
|
|
489
|
+
*/
|
|
490
|
+
export async function assertReinvocationCarriesThePersistedStatus(harness: RuntimeContractHarness, label = "rt-seed"): Promise<RuntimeArmResult> {
|
|
491
|
+
const { subject } = harness;
|
|
492
|
+
const driver = new RuntimeExecutionDriver(harness, label);
|
|
493
|
+
const id = `${label}-shell`;
|
|
494
|
+
const propose = scenario.propose(id, { kind: "shell", resource: `make release # ${label}` });
|
|
495
|
+
|
|
496
|
+
const first = await driver.turn([propose]);
|
|
497
|
+
expect(slimOf(subject, first).phase).toBe("EXECUTION_WAITING_FOR_APPROVAL");
|
|
498
|
+
const planted = plantPriorTurnFacts(driver.record.status!, label);
|
|
499
|
+
expect(driver.decide(ApprovalAction.APPROVE)).toBe(1);
|
|
500
|
+
|
|
501
|
+
const second = await driver.turn([propose, scenario.say("Done.")]);
|
|
502
|
+
expect(slimOf(subject, second).phase, `${subject.name}: the resumed turn completes`).toBe("EXECUTION_COMPLETED");
|
|
503
|
+
const final = finalStatusOf(harness, driver);
|
|
504
|
+
expect(final.subAgentExecutions.map((s) => s.id), `${subject.name}: the prior turn's sub-agent row survives the resume`).toContain(planted.subAgentId);
|
|
505
|
+
expect(Object.keys(final.todos), `${subject.name}: the prior turn's to-dos survive the resume`).toContain(planted.todoId);
|
|
506
|
+
expect(final.artifacts.map((a) => a.name), `${subject.name}: the prior turn's artifacts survive the resume`).toContain(planted.artifactName);
|
|
507
|
+
expect(final.workspaceWriteBacks.map((w) => w.workspaceEntryName), `${subject.name}: the prior turn's write-back rows survive the resume`).toContain(planted.writeBackEntry);
|
|
508
|
+
expect(final.recalledMemoriesReport?.injectedMemoryIds, `${subject.name}: the recalled-memories report survives the resume`).toEqual([planted.memoryId]);
|
|
509
|
+
expect(final.structuredOutput, `${subject.name}: a structured output already on the record survives the resume`).toEqual(planted.structuredOutput);
|
|
510
|
+
expect(final.streamingUsage?.inputTokens, `${subject.name}: the usage summary carries until the resumed turn reports its own`).toBe(planted.inputTokens);
|
|
511
|
+
expect(final.messages.flatMap((m) => m.toolCalls).filter((tc) => tc.id === id), `${subject.name}: the gated row is carried once, not duplicated`).toHaveLength(1);
|
|
512
|
+
return { driver, invocations: [first, second], final };
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/** The same record with the same planted facts but NO transcript: a first turn seeds nothing. */
|
|
516
|
+
export async function assertFirstTurnSeedsNothing(harness: RuntimeContractHarness, label = "rt-seed-first"): Promise<RuntimeArmResult> {
|
|
517
|
+
const { subject } = harness;
|
|
518
|
+
const driver = new RuntimeExecutionDriver(harness, label);
|
|
519
|
+
const planted = plantPriorTurnFacts(driver.record.status ?? (driver.record.execution.status = emptyStatus()), label);
|
|
520
|
+
const invocation = await driver.turn([scenario.say("Fresh start.")]);
|
|
521
|
+
expect(slimOf(subject, invocation).phase).toBe("EXECUTION_COMPLETED");
|
|
522
|
+
const final = finalStatusOf(harness, driver);
|
|
523
|
+
expect(final.subAgentExecutions.map((s) => s.id), `${subject.name}: a first turn seeds no sub-agent rows`).not.toContain(planted.subAgentId);
|
|
524
|
+
expect(Object.keys(final.todos), `${subject.name}: a first turn seeds no to-dos`).not.toContain(planted.todoId);
|
|
525
|
+
expect(final.artifacts.map((a) => a.name), `${subject.name}: a first turn seeds no artifacts`).not.toContain(planted.artifactName);
|
|
526
|
+
expect(final.workspaceWriteBacks, `${subject.name}: a first turn seeds no write-back rows`).toHaveLength(0);
|
|
527
|
+
expect(final.recalledMemoriesReport, `${subject.name}: a first turn seeds no report`).toBeUndefined();
|
|
528
|
+
expect(final.structuredOutput, `${subject.name}: a first turn seeds no structured output`).toBeUndefined();
|
|
529
|
+
return { driver, invocations: [invocation], final };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/** Write the prior turn's facts onto the server-held status, as its writes would have left them. */
|
|
533
|
+
function plantPriorTurnFacts(held: AgentExecutionStatus, label: string) {
|
|
534
|
+
const planted = {
|
|
535
|
+
subAgentId: `${label}-sub`,
|
|
536
|
+
todoId: `${label}-todo`,
|
|
537
|
+
artifactName: `${label}-report.md`,
|
|
538
|
+
writeBackEntry: `${label}-repo`,
|
|
539
|
+
memoryId: `${label}-memory`,
|
|
540
|
+
structuredOutput: { verdict: "carried" } as JsonObject,
|
|
541
|
+
inputTokens: 4_242n,
|
|
542
|
+
};
|
|
543
|
+
held.subAgentExecutions.push(create(SubAgentExecutionSchema, { id: planted.subAgentId, name: "researcher", status: SubAgentStatus.SUB_AGENT_COMPLETED }));
|
|
544
|
+
held.todos[planted.todoId] = create(TodoItemSchema, { id: planted.todoId, content: "carry me", status: TodoStatus.TODO_PENDING });
|
|
545
|
+
held.artifacts.push(create(ExecutionArtifactSchema, { name: planted.artifactName }));
|
|
546
|
+
held.workspaceWriteBacks.push(create(WorkspaceWriteBackSchema, { workspaceEntryName: planted.writeBackEntry, phase: WorkspaceWriteBackPhase.WORKSPACE_WRITE_BACK_COMMITTED }));
|
|
547
|
+
held.recalledMemoriesReport = create(RecalledMemoriesReportSchema, { selectionActive: true, injectedMemoryIds: [planted.memoryId] });
|
|
548
|
+
held.structuredOutput = planted.structuredOutput;
|
|
549
|
+
held.streamingUsage = create(StreamingUsageSummarySchema, { inputTokens: planted.inputTokens, totalTokens: planted.inputTokens });
|
|
550
|
+
return planted;
|
|
551
|
+
}
|
|
552
|
+
|
|
295
553
|
// ── Arms: the stop controller's producers ───────────────────────────────────
|
|
296
554
|
|
|
297
555
|
/** A hanging scenario with the never-seen text after the hang; the arms below stop it in three different ways. */
|
|
@@ -353,17 +611,42 @@ export async function assertWorkerShutdownPersistsAndThrows(harness: RuntimeCont
|
|
|
353
611
|
return { driver, invocations: [invocation], final };
|
|
354
612
|
}
|
|
355
613
|
|
|
614
|
+
/** How often the stall arm's clock advances once the engine is parked; real time between scripted windows. */
|
|
615
|
+
const STALL_CLOCK_INTERVAL_MS = 25;
|
|
616
|
+
|
|
356
617
|
/**
|
|
357
|
-
* A stall: the watchdog measures idle time on the scripted clock;
|
|
358
|
-
*
|
|
359
|
-
*
|
|
618
|
+
* A stall: the watchdog measures idle time on the scripted clock; while the
|
|
619
|
+
* engine hangs the clock KEEPS RUNNING — a stall window per interval until
|
|
620
|
+
* the turn settles — and the first window with no activity fails the turn
|
|
621
|
+
* (RETURNED — a retry would wedge again) with the watchdog's copy naming the
|
|
622
|
+
* idle seconds.
|
|
623
|
+
*
|
|
624
|
+
* Why the clock runs rather than ticks once: on a push-based engine the
|
|
625
|
+
* park is reported while the chunks the engine produced before parking are
|
|
626
|
+
* still in its event pipeline ahead of the adapter's loop, so activity marks
|
|
627
|
+
* land AFTER a single tick and a frozen clock reads idle 0 from then on
|
|
628
|
+
* (S3 M3 F-M3-5, measured on the native adapter: 35 marks at the park, 37
|
|
629
|
+
* within 10 ms). A pull-based double's park implies everything before it
|
|
630
|
+
* was seen; a real stall is wall time passing while the engine is silent,
|
|
631
|
+
* whichever shape the engine has. The interval advances a SCRIPTED clock —
|
|
632
|
+
* the kit's rule against timers is about stopping a turn on a guess, which
|
|
633
|
+
* this never does (Q-M3-7).
|
|
360
634
|
*/
|
|
361
635
|
export async function assertStallFailsTheTurn(harness: RuntimeContractHarness, label = "rt-stall"): Promise<RuntimeArmResult> {
|
|
362
636
|
const { subject, clock } = harness;
|
|
363
637
|
const driver = new RuntimeExecutionDriver(harness, label);
|
|
364
|
-
|
|
638
|
+
let running: NodeJS.Timeout | undefined;
|
|
639
|
+
void subject.whenHanging().then(() => {
|
|
640
|
+
clock.tick(STALL_TIMEOUT_MS);
|
|
641
|
+
running = setInterval(() => clock.tick(STALL_TIMEOUT_MS), STALL_CLOCK_INTERVAL_MS);
|
|
642
|
+
});
|
|
365
643
|
|
|
366
|
-
|
|
644
|
+
let invocation: ActivityInvocation;
|
|
645
|
+
try {
|
|
646
|
+
invocation = await driver.turn(hangingTurn("Running the suite."), { config: { cursorStreamStallTimeoutMs: STALL_TIMEOUT_MS } });
|
|
647
|
+
} finally {
|
|
648
|
+
if (running) clearInterval(running);
|
|
649
|
+
}
|
|
367
650
|
|
|
368
651
|
expect(slimOf(subject, invocation).phase, `${subject.name}: a stall RETURNS FAILED`).toBe("EXECUTION_FAILED");
|
|
369
652
|
expect(driver.record.persistedPhases.at(-1)).toBe(ExecutionPhase.EXECUTION_FAILED);
|
|
@@ -589,6 +872,21 @@ export interface RuntimeContractOptions {
|
|
|
589
872
|
* pass). Every surface by default.
|
|
590
873
|
*/
|
|
591
874
|
readonly failureSurfaces?: readonly FailureSurface[];
|
|
875
|
+
/**
|
|
876
|
+
* Whether this subject's engine can exhaust a tool-round budget and end a
|
|
877
|
+
* turn `tool_call_limit` (`scenario.limit()`); the arm is registered
|
|
878
|
+
* SKIPPED otherwise. On by default; the Cursor subject turns it off (its
|
|
879
|
+
* SDK has no such budget).
|
|
880
|
+
*/
|
|
881
|
+
readonly toolCallLimit?: boolean;
|
|
882
|
+
/**
|
|
883
|
+
* Whether this subject's engine can end its OWN run cancelled with nothing
|
|
884
|
+
* to wait for (`scenario.cancelled()`, an SDK-side cancel); the arm is
|
|
885
|
+
* registered SKIPPED otherwise. On by default; the native subject turns it
|
|
886
|
+
* off (no LangGraph path ends a turn `cancelled` — a stop is the runtime's
|
|
887
|
+
* and settles `interrupted`; S3 M3 F-M3-2).
|
|
888
|
+
*/
|
|
889
|
+
readonly engineCancel?: boolean;
|
|
592
890
|
}
|
|
593
891
|
|
|
594
892
|
const ALL_SURFACES: readonly FailureSurface[] = ["engine", "actionable", "internal"];
|
|
@@ -620,6 +918,22 @@ export function describeHarnessRuntimeContract(harness: RuntimeContractHarness,
|
|
|
620
918
|
clock.reset();
|
|
621
919
|
await assertApprovalRoundTrip(harness);
|
|
622
920
|
});
|
|
921
|
+
it("a reinvocation carries every runner-owned collection and singleton the prior turn persisted; a first turn seeds nothing", async () => {
|
|
922
|
+
clock.reset();
|
|
923
|
+
await assertReinvocationCarriesThePersistedStatus(harness);
|
|
924
|
+
clock.reset();
|
|
925
|
+
await assertFirstTurnSeedsNothing(harness);
|
|
926
|
+
});
|
|
927
|
+
it("file review: two writes flow, the runtime captures and pauses, the reviewer keeps one and discards one, the resume reconciles and completes", async () => {
|
|
928
|
+
clock.reset();
|
|
929
|
+
await assertFileReviewRoundTrip(harness);
|
|
930
|
+
});
|
|
931
|
+
for (const action of [ApprovalAction.REJECT, ApprovalAction.SKIP] as const) {
|
|
932
|
+
it(`awaiting_approval then ${ApprovalAction[action]}: the run continues without the tool and the runtime settles the row SKIPPED`, async () => {
|
|
933
|
+
clock.reset();
|
|
934
|
+
await assertNonExecutingDecisionSettlesSkipped(harness, action);
|
|
935
|
+
});
|
|
936
|
+
}
|
|
623
937
|
it("a user pause persists PAUSED with the pause row once and throws the pause CancelledFailure", async () => {
|
|
624
938
|
clock.reset();
|
|
625
939
|
await assertPausePersistsAndThrows(harness);
|
|
@@ -640,10 +954,14 @@ export function describeHarnessRuntimeContract(harness: RuntimeContractHarness,
|
|
|
640
954
|
clock.reset();
|
|
641
955
|
await assertPlatformStopCompletesEarly(harness);
|
|
642
956
|
});
|
|
643
|
-
it("an engine-cancelled run ends CANCELLED with no error and no copy", async () => {
|
|
957
|
+
it.skipIf(options.engineCancel === false)("an engine-cancelled run ends CANCELLED with no error and no copy", async () => {
|
|
644
958
|
clock.reset();
|
|
645
959
|
await assertEngineCancelledEndsCancelled(harness);
|
|
646
960
|
});
|
|
961
|
+
it.skipIf(options.toolCallLimit === false)("a tool-call limit terminates the turn with the cross-repo prefix and the user copy, persisted and returned", async () => {
|
|
962
|
+
clock.reset();
|
|
963
|
+
await assertToolCallLimitTerminates(harness);
|
|
964
|
+
});
|
|
647
965
|
for (const surface of ALL_SURFACES) {
|
|
648
966
|
it.skipIf(!surfaces.includes(surface))(`a failed turn on the ${surface} surface writes that surface's copy and returns`, async () => {
|
|
649
967
|
clock.reset();
|
|
@@ -33,9 +33,10 @@
|
|
|
33
33
|
* job: carry a REJECTed or SKIPped row to its terminal status. The decision
|
|
34
34
|
* is on the row (the server's field) and the transition follows from it with
|
|
35
35
|
* no engine knowledge, so it belongs to the runtime — one writer per field,
|
|
36
|
-
* as `approvalDecisionsOf` is the runtime's one reader
|
|
37
|
-
*
|
|
38
|
-
*
|
|
36
|
+
* as `approvalDecisionsOf` is the runtime's one reader and
|
|
37
|
+
* `terminalizeNonExecutingDecisions` its one writer (S2 M4, Q-M4-1; landed at
|
|
38
|
+
* S3 M1, `harness/approval-decisions.ts`). An adapter's whole duty for a
|
|
39
|
+
* non-executing decision is to not execute.
|
|
39
40
|
*
|
|
40
41
|
* Scenarios are arranged PER SESSION (the engine is per session in every real
|
|
41
42
|
* harness; a subject arranges that session's engine), and arranging replaces
|
|
@@ -45,6 +46,8 @@
|
|
|
45
46
|
* `TurnOutcome`.
|
|
46
47
|
*/
|
|
47
48
|
|
|
49
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
50
|
+
import { dirname, join } from "node:path";
|
|
48
51
|
import { ApprovalAction, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
49
52
|
import type { ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
50
53
|
|
|
@@ -217,6 +220,7 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
|
|
|
217
220
|
return undefined;
|
|
218
221
|
}
|
|
219
222
|
case "propose":
|
|
223
|
+
if (step.flows) return this.flowingWrite(step.toolCallId, step.action, input, sink);
|
|
220
224
|
return this.propose(step.toolCallId, step.action, input, sink);
|
|
221
225
|
case "read": {
|
|
222
226
|
// Ungated: the row lands COMPLETED at once, the effect counts as run,
|
|
@@ -246,6 +250,8 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
|
|
|
246
250
|
return { kind: "failed", surface: step.surface, message: step.message };
|
|
247
251
|
case "cancelled":
|
|
248
252
|
return { kind: "cancelled" };
|
|
253
|
+
case "limit":
|
|
254
|
+
return { kind: "tool_call_limit" };
|
|
249
255
|
default: {
|
|
250
256
|
const exhaustive: never = step;
|
|
251
257
|
throw new Error(`${this.name}: unknown scenario step ${JSON.stringify(exhaustive)}`);
|
|
@@ -303,6 +309,29 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
|
|
|
303
309
|
}
|
|
304
310
|
}
|
|
305
311
|
|
|
312
|
+
/**
|
|
313
|
+
* A write under apply-then-review capture: no gate — the engine writes the
|
|
314
|
+
* bytes into the workspace and reports a COMPLETED `write` row, and the
|
|
315
|
+
* RUNTIME captures the change for review after the turn. The bytes are the
|
|
316
|
+
* fake's own (the tool-call id, one line); an already-settled row on a
|
|
317
|
+
* reinvocation is a no-op, as `propose`'s is.
|
|
318
|
+
*/
|
|
319
|
+
private async flowingWrite(toolCallId: string, action: ProposedAction, input: TurnInput, sink: TurnSink): Promise<TurnOutcome | undefined> {
|
|
320
|
+
if (isSettled(findToolCallRow(sink.status, toolCallId))) return undefined;
|
|
321
|
+
const file = join(input.workspace.primaryDir, action.resource);
|
|
322
|
+
await mkdir(dirname(file), { recursive: true });
|
|
323
|
+
await writeFile(file, `${toolCallId}\n`);
|
|
324
|
+
const message = aiMessage("");
|
|
325
|
+
const row = toolCall(toolCallId, "write", ToolCallStatus.TOOL_CALL_COMPLETED);
|
|
326
|
+
row.args = { path: action.resource };
|
|
327
|
+
message.toolCalls.push(row);
|
|
328
|
+
sink.status.messages.push(message);
|
|
329
|
+
this.executions.set(toolCallId, this.executionCount(toolCallId) + 1);
|
|
330
|
+
sink.recordActivity("write");
|
|
331
|
+
await sink.requestPersist();
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
|
|
306
335
|
/**
|
|
307
336
|
* Report the executed action on its row, as a real engine's completion
|
|
308
337
|
* event does. The row normally exists (the runtime seeded the status with
|
|
@@ -331,9 +360,12 @@ export interface ScriptedSubject extends HarnessContractSubject {
|
|
|
331
360
|
}
|
|
332
361
|
|
|
333
362
|
/**
|
|
334
|
-
* The fake
|
|
335
|
-
*
|
|
336
|
-
*
|
|
363
|
+
* The fake declares the `deep-agent` row: in S2 the runtime served that row
|
|
364
|
+
* through this fake alone; since S3 M3 the real native adapter takes it over
|
|
365
|
+
* in the runtime half
|
|
366
|
+
* (`execute-deep-agent/__test-utils__/contract-subject.ts`), and the fake
|
|
367
|
+
* keeps declaring it so the kit's self-check runs a full subject under both
|
|
368
|
+
* pause primitives without an engine.
|
|
337
369
|
*/
|
|
338
370
|
export function scriptedSubject(options: ScriptedHarnessOptions): ScriptedSubject {
|
|
339
371
|
const adapter = new ScriptedHarnessAdapter(options);
|