@deksden-com/dd-flow-cli 0.9.0-beta.14 → 0.9.0-beta.140
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/CHANGELOG.md +733 -0
- package/README.md +143 -1
- package/dist/build-info.json +8 -8
- package/dist/cli/argument-values.js +38 -0
- package/dist/cli/command-inputs.js +376 -0
- package/dist/cli/command-metadata.js +197 -0
- package/dist/cli/command-option-descriptions.js +191 -0
- package/dist/cli/completion-cli.js +70 -0
- package/dist/cli/completion.js +57 -0
- package/dist/cli/error-output.js +52 -0
- package/dist/cli/executor-onboarding.js +76 -0
- package/dist/cli/flows.js +29 -0
- package/dist/cli/harnesses.js +84 -0
- package/dist/cli/help.js +328 -18
- package/dist/cli/hook-ingress.js +78 -0
- package/dist/cli/input-preparation.js +165 -0
- package/dist/cli/memory-flow-admission.js +44 -0
- package/dist/cli/memory-init-invocation.js +78 -0
- package/dist/cli/memory-init.js +240 -0
- package/dist/cli/memory-storage-admission.js +53 -0
- package/dist/cli/models.js +39 -0
- package/dist/cli/native-hook-ingress.js +80 -0
- package/dist/cli/operator-profiles.js +61 -0
- package/dist/cli/output-options.js +35 -0
- package/dist/cli/response-file.js +71 -0
- package/dist/cli/run-cli.js +1689 -545
- package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
- package/dist/cli/runtime-maintenance-ingress.js +52 -0
- package/dist/cli/runtime-process.js +153 -0
- package/dist/cli/setup.js +149 -0
- package/dist/cli.js +71 -2
- package/dist/domain/stage-catalog.js +1 -1
- package/dist/domain/stage-handler-identities.js +20 -0
- package/dist/harness-runtime/DD-ZCODE.md +112 -0
- package/dist/harness-runtime/OPENCODE-LICENSE.txt +24 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +56 -0
- package/dist/harness-runtime/bin/dd-codex.mjs +27 -0
- package/dist/harness-runtime/bin/dd-droid.mjs +34 -0
- package/dist/harness-runtime/bin/dd-grok.mjs +40 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +22 -0
- package/dist/harness-runtime/bin/dd-zcode.mjs +87 -0
- package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
- package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
- package/dist/harness-runtime/lib/agy-hooks.d.mts +1 -0
- package/dist/harness-runtime/lib/agy-hooks.mjs +9 -0
- package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
- package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
- package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +371 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +724 -0
- package/dist/harness-runtime/lib/dd-agy.mjs +70 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +196 -0
- package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +817 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +129 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +584 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +498 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +177 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +507 -0
- package/dist/harness-runtime/lib/dd-opencode.mjs +354 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +750 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +1145 -0
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +14 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +157 -0
- package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +139 -0
- package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
- package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
- package/dist/harness-runtime/lib/lease-renewal.mjs +37 -0
- package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
- package/dist/harness-runtime/lib/lifecycle-command.mjs +273 -0
- package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
- package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
- package/dist/harness-runtime/lib/managed-daemon.d.mts +13 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +654 -0
- package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
- package/dist/harness-runtime/lib/model-observations.mjs +101 -0
- package/dist/harness-runtime/lib/native-children.d.mts +16 -0
- package/dist/harness-runtime/lib/native-children.mjs +113 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +7 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +251 -0
- package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
- package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
- package/dist/harness-runtime/lib/native-streams.mjs +45 -0
- package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
- package/dist/harness-runtime/lib/observation-clock.mjs +92 -0
- package/dist/harness-runtime/lib/opencode-plugin.mjs +85 -0
- package/dist/harness-runtime/lib/operation-context.mjs +5 -0
- package/dist/harness-runtime/lib/operation-errors.d.mts +14 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +116 -0
- package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
- package/dist/harness-runtime/lib/operation-progress.mjs +114 -0
- package/dist/harness-runtime/lib/process-json.d.mts +5 -0
- package/dist/harness-runtime/lib/process-json.mjs +201 -0
- package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
- package/dist/harness-runtime/lib/runner-events.mjs +200 -0
- package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
- package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
- package/dist/harness-runtime/lib/setup-progress.d.mts +9 -0
- package/dist/harness-runtime/lib/setup-progress.mjs +51 -0
- package/dist/harness-runtime/lib/tool-observations.d.mts +21 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +228 -0
- package/dist/harness-runtime/lib/zcode-usage.mjs +55 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-1y4a697q.js +81 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-2ny4vxct.js +92 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-6mptkzbc.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-7x67cf1q.js +52 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9rqn6x4v.js +16 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9vdz0zwn.js +1661 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ecs5ftka.js +1466 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-frbwqjmf.js +145 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gne7h7mq.js +50 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gx9wnkzr.js +105 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-m2eywc8w.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-nwcpakzx.js +12 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-q4g3afnw.js +623 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-qav6mfdz.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-sxt88q69.js +12 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-t1xa31vm.js +39 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ve4aws36.js +103 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vmytr4q8.js +4 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vt9jbch8.js +36 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vyas3vgt.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-wjyeq25y.js +30 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-xgnj2w4v.js +20 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-y2etg5bs.js +9 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ydhz1zrh.js +238 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-z6qdwcsf.js +62 -0
- package/dist/harness-runtime/vendor/opencode-client/contract.d.ts +1 -0
- package/dist/harness-runtime/vendor/opencode-client/contract.js +12 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/api/api.d.ts +2806 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/api/api.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/api.d.ts +4 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/api.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/client.d.ts +2530 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/client.js +15 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.d.ts +7 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.js +6 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.d.ts +2529 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.js +8 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.d.ts +2 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.js +12 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/index.d.ts +35 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/index.js +75 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/rpc.d.ts +20 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/rpc.js +11 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/service.d.ts +90 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/service.js +187 -0
- package/dist/harness-runtime/vendor/opencode-client/package.json +84 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/api.d.ts +21 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/api.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/client.d.ts +252 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/client.js +14 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.d.ts +8 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.js +6 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.d.ts +259 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.js +7 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.d.ts +3 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.js +79 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.d.ts +8932 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.js +70 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/index.d.ts +6 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/index.js +83 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/rpc.d.ts +32 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/rpc.js +7 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/service.d.ts +24 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/service.js +187 -0
- package/dist/harness-runtime/vendor/opencode-client/pty-handoff.d.ts +12 -0
- package/dist/harness-runtime/vendor/opencode-client/pty-handoff.js +14 -0
- package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.d.ts +20 -0
- package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.js +14 -0
- package/dist/harness-runtime/vendor/opencode-client/service-contender.d.ts +37 -0
- package/dist/harness-runtime/vendor/opencode-client/service-contender.js +10 -0
- package/dist/harness-runtime/vendor/opencode-client/service-probe.d.ts +70 -0
- package/dist/harness-runtime/vendor/opencode-client/service-probe.js +16 -0
- package/dist/harness-runtime/vendor/opencode-client/service-timing.d.ts +12 -0
- package/dist/harness-runtime/vendor/opencode-client/service-timing.js +10 -0
- package/dist/harness-runtime/vendor/opencode-client/service-version.d.ts +2 -0
- package/dist/harness-runtime/vendor/opencode-client/service-version.js +6 -0
- package/dist/harness-runtime/vendor/opencode-client/service.d.ts +52 -0
- package/dist/harness-runtime/vendor/opencode-client/service.js +1 -0
- package/dist/harness-runtime/vendor/opencode-client/shared-events.d.ts +11 -0
- package/dist/harness-runtime/vendor/opencode-client/shared-events.js +8 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/connection.d.ts +37 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/connection.js +10 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/data.d.ts +211 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/data.js +18 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/index.d.ts +3 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/index.js +32 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/pty.d.ts +22 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/pty.js +8 -0
- package/dist/harness-runtime/vendor/opencode-plugin.mjs +3 -0
- package/dist/protocol/local-files.js +18 -9
- package/dist/runtime/context.js +8 -4
- package/dist/schemas/agent-profile.schema.json +1 -1
- package/dist/schemas/code-review-result.schema.json +1 -1
- package/dist/schemas/code-verification.schema.json +7 -5
- package/dist/schemas/code-work-batch.schema.json +2 -2
- package/dist/schemas/discovery-result.schema.json +184 -0
- package/dist/schemas/execution-bundle.schema.json +205 -0
- package/dist/schemas/execution-settings.schema.json +162 -0
- package/dist/schemas/flow-descriptor.schema.json +54 -0
- package/dist/schemas/flow-run.schema.json +28 -0
- package/dist/schemas/harness-catalog.schema.json +80 -0
- package/dist/schemas/mb-init-compose-result.schema.json +457 -0
- package/dist/schemas/mb-init-discover-result.schema.json +500 -0
- package/dist/schemas/mb-init-finish-result.schema.json +422 -0
- package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
- package/dist/schemas/mb-init-review-result.schema.json +396 -0
- package/dist/schemas/mb-init-verify-result.schema.json +391 -0
- package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
- package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
- package/dist/schemas/memory-composition-work-result.schema.json +38 -0
- package/dist/schemas/memory-repair-work-result.schema.json +49 -0
- package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
- package/dist/schemas/memory-review-work-result.schema.json +43 -0
- package/dist/schemas/model-catalog.schema.json +153 -0
- package/dist/schemas/plan-review-decision.schema.json +3 -3
- package/dist/schemas/plan-review-result.schema.json +2 -2
- package/dist/schemas/profile-settings.schema.json +122 -0
- package/dist/schemas/release-manifest.schema.json +291 -0
- package/dist/schemas/run-control-receipt.schema.json +118 -0
- package/dist/schemas/run-control-request.schema.json +36 -0
- package/dist/schemas/run-engine-binding.schema.json +1 -1
- package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
- package/dist/services/branch-context.js +3 -3
- package/dist/services/canon.js +11 -8
- package/dist/services/check-runner-evidence.js +168 -0
- package/dist/services/cleanup.js +133 -40
- package/dist/services/cli-operation-classifier.js +25 -62
- package/dist/services/cli-upgrade-allowlist.js +27 -0
- package/dist/services/code-checks.js +1156 -107
- package/dist/services/codex-hook-delivery.js +32 -0
- package/dist/services/command-context.js +80 -0
- package/dist/services/compatibility-preflight.js +13 -3
- package/dist/services/config.js +11 -8
- package/dist/services/continuation-outcome.js +20 -0
- package/dist/services/controller-child-capacity.js +13 -0
- package/dist/services/controller-child-observer.js +115 -0
- package/dist/services/controller-execution.js +88 -0
- package/dist/services/controller-fanout.js +202 -0
- package/dist/services/dashboard.js +143 -58
- package/dist/services/descriptor-coordinator-session.js +35 -0
- package/dist/services/descriptor-root-work.js +53 -0
- package/dist/services/descriptor-run-settlement.js +105 -0
- package/dist/services/descriptor-run-start.js +449 -0
- package/dist/services/descriptor-stage-coordinator.js +197 -0
- package/dist/services/descriptor-stage-settlement.js +222 -0
- package/dist/services/descriptor-stage-start.js +139 -0
- package/dist/services/discovery/agy-discovery.js +2 -0
- package/dist/services/discovery/codex-discovery.js +2 -0
- package/dist/services/discovery/droid-discovery.js +2 -0
- package/dist/services/discovery/grok-discovery.js +2 -0
- package/dist/services/discovery/opencode-discovery.js +2 -0
- package/dist/services/discovery/static.js +31 -0
- package/dist/services/discovery/zcode-discovery.js +2 -0
- package/dist/services/engines.js +294 -153
- package/dist/services/eval-snapshots.js +1215 -116
- package/dist/services/execution-bundles.js +263 -0
- package/dist/services/execution-policy.js +185 -0
- package/dist/services/executor-onboarding.js +56 -0
- package/dist/services/external-work-launch.js +308 -0
- package/dist/services/flow-catalog.js +21 -0
- package/dist/services/flow-descriptors.js +82 -0
- package/dist/services/frozen-engine.js +250 -0
- package/dist/services/generic-execution.js +164 -0
- package/dist/services/harness-adapter.js +355 -36
- package/dist/services/harness-config.js +2 -0
- package/dist/services/harness-discovery.js +29 -0
- package/dist/services/harness-doctor.js +75 -0
- package/dist/services/harness-probe-effects.js +51 -0
- package/dist/services/harness-version-probe.js +100 -0
- package/dist/services/hooks.js +771 -325
- package/dist/services/hybrid-contracts.js +483 -0
- package/dist/services/ids.js +29 -1
- package/dist/services/installation-lock.js +95 -0
- package/dist/services/installed-flow-source.js +183 -0
- package/dist/services/lanes.js +78 -65
- package/dist/services/lifecycle-command.js +1 -288
- package/dist/services/lifecycle-contract.js +1 -0
- package/dist/services/lifecycle-invocations.js +2007 -0
- package/dist/services/managed-daemon-binding.js +38 -0
- package/dist/services/managed-lease-monitor.js +133 -0
- package/dist/services/managed-processes.js +475 -55
- package/dist/services/memory-accepted-composition.js +77 -0
- package/dist/services/memory-baseline-preservation.js +65 -0
- package/dist/services/memory-completed-correction-wave.js +320 -0
- package/dist/services/memory-compose-handler.js +258 -0
- package/dist/services/memory-composition-acceptance.js +199 -0
- package/dist/services/memory-composition-graph.js +134 -0
- package/dist/services/memory-composition-registration.js +146 -0
- package/dist/services/memory-composition-work-custody.js +151 -0
- package/dist/services/memory-composition-work-results.js +96 -0
- package/dist/services/memory-corrected-review-acceptance.js +129 -0
- package/dist/services/memory-corrected-review-batch.js +30 -0
- package/dist/services/memory-corrected-review-findings.js +106 -0
- package/dist/services/memory-corrected-review-inputs.js +210 -0
- package/dist/services/memory-corrected-review-projection.js +78 -0
- package/dist/services/memory-corrected-review-receipt.js +170 -0
- package/dist/services/memory-corrected-review-registration.js +191 -0
- package/dist/services/memory-correction-check-receipt.js +219 -0
- package/dist/services/memory-correction-input-owner.js +6 -0
- package/dist/services/memory-discover-handler.js +104 -0
- package/dist/services/memory-finish-evidence.js +435 -0
- package/dist/services/memory-finish-handler.js +215 -0
- package/dist/services/memory-finish-integration.js +280 -0
- package/dist/services/memory-finish-readiness.js +122 -0
- package/dist/services/memory-git-integration.js +151 -0
- package/dist/services/memory-historical-corrected-review.js +206 -0
- package/dist/services/memory-historical-original-review.js +130 -0
- package/dist/services/memory-historical-repair-registration.js +99 -0
- package/dist/services/memory-historical-second-review.js +128 -0
- package/dist/services/memory-init-bootstrap-readiness.js +210 -0
- package/dist/services/memory-init-contexts.js +89 -0
- package/dist/services/memory-init-controller-start.js +84 -0
- package/dist/services/memory-init-engine-release.js +223 -0
- package/dist/services/memory-init-git-policy.js +136 -0
- package/dist/services/memory-init-lint-capability.js +185 -0
- package/dist/services/memory-init-native-capacity.js +296 -0
- package/dist/services/memory-init-preflight.js +330 -0
- package/dist/services/memory-init-project-bootstrap.js +599 -0
- package/dist/services/memory-init-request-candidate.js +157 -0
- package/dist/services/memory-init-request-input.js +66 -0
- package/dist/services/memory-init-resource-storage.js +125 -0
- package/dist/services/memory-init-run-allocation.js +81 -0
- package/dist/services/memory-init-run-bundle.js +98 -0
- package/dist/services/memory-init-start.js +181 -0
- package/dist/services/memory-init-startup-custody.js +80 -0
- package/dist/services/memory-init-startup-input.js +240 -0
- package/dist/services/memory-init-storage.js +81 -0
- package/dist/services/memory-init-stores.js +359 -0
- package/dist/services/memory-init-worktrunk-capability.js +178 -0
- package/dist/services/memory-layer-publication.js +611 -0
- package/dist/services/memory-layer.js +156 -0
- package/dist/services/memory-lint-config.js +85 -0
- package/dist/services/memory-native-review-copy.js +165 -0
- package/dist/services/memory-normal-flow-entry.js +75 -0
- package/dist/services/memory-ordinary-defaults.js +112 -0
- package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
- package/dist/services/memory-preflight-handler.js +259 -0
- package/dist/services/memory-repair-current-view.js +299 -0
- package/dist/services/memory-repair-document-delta.js +451 -0
- package/dist/services/memory-repair-history.js +491 -0
- package/dist/services/memory-repair-physical-binding.js +312 -0
- package/dist/services/memory-repair-publication-files.js +152 -0
- package/dist/services/memory-repair-publication-intent.js +27 -0
- package/dist/services/memory-repair-publication-plan.js +160 -0
- package/dist/services/memory-repair-publication-source.js +225 -0
- package/dist/services/memory-repair-publication.js +794 -0
- package/dist/services/memory-repair-registration.js +239 -0
- package/dist/services/memory-repair-work-baseline.js +413 -0
- package/dist/services/memory-repair-work-completion.js +97 -0
- package/dist/services/memory-repair-work-custody.js +73 -0
- package/dist/services/memory-repair-work-results.js +89 -0
- package/dist/services/memory-repair-workspace.js +252 -0
- package/dist/services/memory-repair-writer-copy.js +735 -0
- package/dist/services/memory-result-binding.js +173 -0
- package/dist/services/memory-retained-review-authority.js +129 -0
- package/dist/services/memory-retained-review-results.js +70 -0
- package/dist/services/memory-retained-second-baseline.js +102 -0
- package/dist/services/memory-retained-second-repairs.js +156 -0
- package/dist/services/memory-review-acceptance.js +214 -0
- package/dist/services/memory-review-correction-plan.js +171 -0
- package/dist/services/memory-review-handler.js +585 -0
- package/dist/services/memory-review-inputs.js +200 -0
- package/dist/services/memory-review-receipt.js +180 -0
- package/dist/services/memory-review-registration.js +108 -0
- package/dist/services/memory-review-work-custody.js +124 -0
- package/dist/services/memory-review-work-input.js +72 -0
- package/dist/services/memory-review-work-results.js +73 -0
- package/dist/services/memory-second-corrected-review-inputs.js +165 -0
- package/dist/services/memory-second-correction-plan.js +76 -0
- package/dist/services/memory-second-repair-baseline.js +91 -0
- package/dist/services/memory-second-repair-launch.js +614 -0
- package/dist/services/memory-second-repair-registration.js +88 -0
- package/dist/services/memory-second-repair-source.js +187 -0
- package/dist/services/memory-second-review-acceptance.js +115 -0
- package/dist/services/memory-source-observations.js +117 -0
- package/dist/services/memory-stage-dispatch.js +28 -0
- package/dist/services/memory-stage-results.js +199 -0
- package/dist/services/memory-verification-acceptance.js +135 -0
- package/dist/services/memory-verification-checks.js +252 -0
- package/dist/services/memory-verification-receipt.js +159 -0
- package/dist/services/memory-verify-handler.js +135 -0
- package/dist/services/memory-work-result-preparation.js +63 -0
- package/dist/services/merge-queue.js +228 -149
- package/dist/services/merge-server.js +161 -35
- package/dist/services/migrations.js +37 -16
- package/dist/services/model-catalog.js +109 -0
- package/dist/services/model-discovery.js +46 -0
- package/dist/services/native-daemon-history.js +49 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/operator-profiles.js +236 -0
- package/dist/services/plan-runtime.js +26 -11
- package/dist/services/plans.js +28 -25
- package/dist/services/projects.js +37 -17
- package/dist/services/prompts.js +28 -17
- package/dist/services/protocols.js +163 -87
- package/dist/services/provider-capacity.js +385 -0
- package/dist/services/recovery-observation-budget.js +121 -0
- package/dist/services/recovery-snapshot-database.js +560 -0
- package/dist/services/release-archive-producer.js +52 -0
- package/dist/services/release-archive.js +143 -0
- package/dist/services/release-cache.js +142 -0
- package/dist/services/release-compatibility.js +18 -0
- package/dist/services/release-download.js +67 -0
- package/dist/services/release-envelope.js +104 -0
- package/dist/services/release-manifest.js +45 -0
- package/dist/services/release-payload.js +114 -0
- package/dist/services/release-producer.js +63 -0
- package/dist/services/release-provenance.js +160 -0
- package/dist/services/release-registry.js +66 -0
- package/dist/services/release-source.js +194 -0
- package/dist/services/repair-intents.js +42 -0
- package/dist/services/review-copy.js +165 -0
- package/dist/services/review-deferral.js +23 -0
- package/dist/services/run-control-receipt.js +56 -0
- package/dist/services/run-control-worker.js +411 -0
- package/dist/services/run-control.js +1056 -0
- package/dist/services/run-controller-adapter.js +399 -0
- package/dist/services/run-controller-capture.js +186 -0
- package/dist/services/run-controller-process.js +208 -0
- package/dist/services/run-controller-recovery.js +289 -0
- package/dist/services/run-controller-state.js +97 -0
- package/dist/services/run-controller.js +1205 -0
- package/dist/services/run-engine-bindings.js +1 -1
- package/dist/services/run-fork.js +147 -0
- package/dist/services/run-health.js +149 -0
- package/dist/services/run-observations.js +152 -0
- package/dist/services/run-projection.js +22 -64
- package/dist/services/run-recovery-guard.js +77 -0
- package/dist/services/run-recovery-runtime.js +112 -0
- package/dist/services/run-recovery.js +489 -0
- package/dist/services/run-start-custody.js +177 -0
- package/dist/services/runs.js +1019 -247
- package/dist/services/runtime-budget.js +430 -0
- package/dist/services/runtime-command.js +43 -0
- package/dist/services/runtime-scope-capture.js +52 -0
- package/dist/services/runtime-scope-control.js +468 -0
- package/dist/services/runtime-scope-resume.js +599 -0
- package/dist/services/runtime-scope-stop.js +210 -0
- package/dist/services/runtime-scope-worker.js +287 -0
- package/dist/services/runtime-service.js +96 -0
- package/dist/services/schema-validation.js +10 -8
- package/dist/services/scope-writer-cleanup.js +27 -0
- package/dist/services/sessions.js +35 -69
- package/dist/services/setup.js +377 -0
- package/dist/services/stage-blocker.js +17 -7
- package/dist/services/stage-context.js +193 -19
- package/dist/services/stage-handlers.js +74 -0
- package/dist/services/stage-lifecycle.js +100 -108
- package/dist/services/stage-pause.js +89 -75
- package/dist/services/stage-report-renderer.js +234 -6
- package/dist/services/stage-work-graph.js +52 -0
- package/dist/services/tooling.js +32 -23
- package/dist/services/usage.js +49 -101
- package/dist/services/verification-matrix.js +374 -0
- package/dist/services/vnext-code-review.js +223 -88
- package/dist/services/vnext-code.js +327 -226
- package/dist/services/vnext-contracts.js +10 -1
- package/dist/services/vnext-execution-profile.js +80 -5
- package/dist/services/vnext-fanout.js +311 -19
- package/dist/services/vnext-merge.js +332 -111
- package/dist/services/vnext-plan-review.js +279 -127
- package/dist/services/vnext-plan.js +229 -104
- package/dist/services/vnext-protocolize.js +411 -86
- package/dist/services/vnext-specify.js +119 -81
- package/dist/services/vnext-workspace-policy.js +7 -2
- package/dist/services/work-registry.js +1627 -199
- package/dist/services/workspace-bootstrap.js +52 -15
- package/dist/services/workspace-files.js +85 -0
- package/dist/services/worktrees.js +98 -44
- package/dist/shared/entity-references.js +8 -0
- package/dist/shared/errors.js +17 -0
- package/dist/storage/agent-output.js +26 -0
- package/dist/storage/database.js +891 -39
- package/dist/storage/foreign-key-transaction.js +39 -0
- package/dist/storage/immutable-input.js +45 -0
- package/dist/storage/import-guard.js +102 -0
- package/dist/storage/native-cleanup.js +52 -0
- package/dist/storage/paths.js +17 -4
- package/dist/storage/projection-lock.js +73 -0
- package/dist/storage/work-references.js +23 -0
- package/dist/storage/writer-contract.js +89 -0
- package/dist/storage/writer-migration.js +85 -0
- package/package.json +43 -13
- package/tools/create-canon-release.mjs +33 -0
- package/tools/produce-hybrid-release.mjs +41 -0
- package/tools/repair-paused-run-status.mjs +84 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { errorRecord, reportedError } from "./operation-errors.mjs";
|
|
3
|
+
import { ObservationClock, validateObservationDuration } from "./observation-clock.mjs";
|
|
4
|
+
import { terminateCommandTree } from "./native-hook-command.mjs";
|
|
5
|
+
import { SETUP_STARTUP_MS, publishSetup, setupObserver, observationDetails, isSetupPhase } from './setup-progress.mjs';
|
|
6
|
+
let probeSequence = 0;
|
|
7
|
+
|
|
8
|
+
function failure(message, code) {
|
|
9
|
+
return Object.assign(new Error(message), { code });
|
|
10
|
+
}
|
|
11
|
+
const phaseDiagnostic = value => isSetupPhase(value) || ["native_hook_phase", "process_maintenance_phase"].includes(value?.kind);
|
|
12
|
+
|
|
13
|
+
export function reportedFailure(stdout, stderr, fallback, fallbackCode = "flow_reconciliation_failed", retainedError = null) {
|
|
14
|
+
for (const text of [stdout, stderr]) {
|
|
15
|
+
// Bounded CLI transport may prepend JSONL phase records to the usual
|
|
16
|
+
// pretty-printed error envelope. Remove only our diagnostics, not arbitrary
|
|
17
|
+
// text or a substring that might masquerade as an authoritative receipt.
|
|
18
|
+
const envelope = text.split(/\n/).filter(line => {
|
|
19
|
+
try { return !phaseDiagnostic(JSON.parse(line)); }
|
|
20
|
+
catch { return true; }
|
|
21
|
+
}).join("\n").trim();
|
|
22
|
+
for (const line of [text.trim(), envelope, ...text.trim().split(/\n/).reverse()]) {
|
|
23
|
+
try {
|
|
24
|
+
const value = JSON.parse(line);
|
|
25
|
+
if (phaseDiagnostic(value)) continue;
|
|
26
|
+
const error = value?.error;
|
|
27
|
+
if (typeof error?.code === "string" && error.code) return reportedError(error, fallback);
|
|
28
|
+
if (typeof value?.code === "string" && value.code) return reportedError({ ...value, message: typeof error === "string" ? error : value.message }, fallback);
|
|
29
|
+
} catch { /* Not a structured CLI error line. */ }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (retainedError) return reportedError(retainedError, fallback);
|
|
33
|
+
return failure(stderr.trim() || stdout.trim() || fallback, fallbackCode);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function processTarget(executable, args) {
|
|
37
|
+
return /\.[cm]?js$/.test(executable) ? { command: process.execPath, args: [executable, ...args] } : { command: executable, args };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// These are utility/transport children, never a second productive-work owner.
|
|
41
|
+
async function command(bin, args, json, { cwd, env = {}, input = null, onProgress = null,
|
|
42
|
+
timeoutMs = 30_000, startupMs = null, phase = "utility", signal = null, onDiagnostic = null, result = false, progress = null } = {}) {
|
|
43
|
+
if (startupMs !== null) validateObservationDuration(startupMs);
|
|
44
|
+
if (timeoutMs !== null) validateObservationDuration(timeoutMs);
|
|
45
|
+
if (timeoutMs === null && phase !== "native-work") throw new RangeError("only native-work may omit the utility quiet timeout");
|
|
46
|
+
if (signal?.aborted) throw signal.reason ?? failure("command aborted", "command_aborted");
|
|
47
|
+
const setupProbe = ['native-version', 'native-doctor'].includes(phase);
|
|
48
|
+
const step = setupProbe ? `version-${++probeSequence}` : null;
|
|
49
|
+
if (setupProbe) publishSetup('probe_started', step);
|
|
50
|
+
const executable = bin === "dd-flow" && typeof env.DD_FLOW_BIN === "string" ? env.DD_FLOW_BIN : bin;
|
|
51
|
+
const target = processTarget(executable, json ? [...args, "--json"] : args);
|
|
52
|
+
return await new Promise((resolve, reject) => {
|
|
53
|
+
const child = spawn(target.command, target.args, { cwd, env: { ...process.env, ...env },
|
|
54
|
+
detached: process.platform !== "win32", stdio: [input === null ? "ignore" : "pipe", "pipe", "pipe"] });
|
|
55
|
+
const maxOutput = 8 * 1024 * 1024, maxTail = 64 * 1024, maxLine = 1024 * 1024;
|
|
56
|
+
let stdout = "", stderr = "", pendingStderr = "", settled = false, primary = null, cursor = 0, droppingStructured = false;
|
|
57
|
+
let outputBytes = 0, cleanupTimer = null, inputError = null, termination = null, retainedError = null, prettyError = "";
|
|
58
|
+
const phases = new Set();
|
|
59
|
+
const clock = timeoutMs === null ? null : new ObservationClock({ timeoutMs: startupMs ?? timeoutMs });
|
|
60
|
+
const observeSetup = setupObserver(env.DD_EVAL_OPERATION_ID ?? process.env.DD_EVAL_OPERATION_ID);
|
|
61
|
+
const productive = () => { cursor++; clock?.sample(cursor, Date.now()); };
|
|
62
|
+
const finish = (error, value) => {
|
|
63
|
+
if (settled) return; settled = true;
|
|
64
|
+
clearInterval(timer); clearTimeout(cleanupTimer); signal?.removeEventListener("abort", abort);
|
|
65
|
+
if (!error && setupProbe) publishSetup('probe_completed', step);
|
|
66
|
+
error ? reject(error) : resolve(value);
|
|
67
|
+
};
|
|
68
|
+
const stop = error => {
|
|
69
|
+
if (primary || settled) return;
|
|
70
|
+
primary = error;
|
|
71
|
+
termination = terminateCommandTree(child).catch(cause => { if (cause.code !== "ESRCH") primary.cleanup_error = cause; });
|
|
72
|
+
// A missing close after owned SIGKILL is cleanup uncertainty, not success.
|
|
73
|
+
cleanupTimer = setTimeout(() => {
|
|
74
|
+
primary.cleanup_error ??= failure("owned utility child did not close", "command_cleanup_unconfirmed");
|
|
75
|
+
finish(primary);
|
|
76
|
+
}, 5_000);
|
|
77
|
+
};
|
|
78
|
+
const abort = () => stop(signal.reason ?? failure("command aborted", "command_aborted"));
|
|
79
|
+
const deliverProgress = line => {
|
|
80
|
+
let value;
|
|
81
|
+
// Parse a bounded whole pretty envelope before its diagnostic bytes leave
|
|
82
|
+
// the tail. An oversized envelope cannot become plain-text keepalive.
|
|
83
|
+
if (prettyError === null) { if (line.trimEnd() === "}") prettyError = ""; return; }
|
|
84
|
+
const candidate = prettyError ? `${prettyError}\n${line}` : line;
|
|
85
|
+
if (prettyError && candidate.length > maxTail) { prettyError = null; return; }
|
|
86
|
+
try { value = JSON.parse(candidate); prettyError = ""; }
|
|
87
|
+
catch {
|
|
88
|
+
if (!prettyError && !line.trimStart().startsWith("{")) { productive(); return; }
|
|
89
|
+
prettyError = candidate.length > maxTail ? null : candidate; return;
|
|
90
|
+
}
|
|
91
|
+
const kind = value?.kind;
|
|
92
|
+
if (kind === 'setup_phase') {
|
|
93
|
+
if (observeSetup(value)) {
|
|
94
|
+
if (clock) clock.setupProgress = { phase: value.phase, sequence: value.sequence, step: value.step, operation_id: value.operation_id };
|
|
95
|
+
if (clock) clock.timeoutMs = ['ingress_entered', 'probe_started'].includes(value.phase) ? startupMs ?? timeoutMs : timeoutMs;
|
|
96
|
+
productive();
|
|
97
|
+
if (setupProbe || phase === 'engine-probe') publishSetup(value.phase, `${step ?? 'engine'}:${value.sequence}:${value.step ?? ''}`);
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const passive = ["heartbeat", "poll", "status", "elapsed", "wait", "waiting"].some(marker => [kind, value?.phase, value?.action].includes(marker));
|
|
102
|
+
if (!["native_hook_phase", "process_maintenance_phase"].includes(kind)) {
|
|
103
|
+
const error = typeof value?.error?.code === "string" && value.error.code ? value.error
|
|
104
|
+
: typeof value?.code === "string" && value.code ? { ...value, message: typeof value.error === "string" ? value.error : value.message } : null;
|
|
105
|
+
// Retain one bounded typed summary independently of the diagnostic tail.
|
|
106
|
+
// It is consulted only on failed exit, after authoritative stdout.
|
|
107
|
+
if (error) retainedError = errorRecord(error);
|
|
108
|
+
}
|
|
109
|
+
if (["native_hook_phase", "process_maintenance_phase"].includes(kind)) {
|
|
110
|
+
const marker = JSON.stringify([kind, value.phase, value.operation_id ?? value.process_id]);
|
|
111
|
+
if (typeof value.phase === "string" && !passive && !phases.has(marker)) { phases.add(marker); productive(); }
|
|
112
|
+
} else if (!passive) productive();
|
|
113
|
+
if (typeof onProgress !== "function" || primary) return;
|
|
114
|
+
try {
|
|
115
|
+
const returned = onProgress(value);
|
|
116
|
+
if (returned && typeof returned.then === "function") {
|
|
117
|
+
Promise.resolve(returned).catch(() => {});
|
|
118
|
+
throw failure("command onProgress must be synchronous", "progress_callback_async");
|
|
119
|
+
}
|
|
120
|
+
} catch (error) { stop(error); }
|
|
121
|
+
};
|
|
122
|
+
const diagnostic = (data, stream) => {
|
|
123
|
+
try {
|
|
124
|
+
const returned = onDiagnostic?.(data, stream);
|
|
125
|
+
if (returned && typeof returned.then === "function") {
|
|
126
|
+
Promise.resolve(returned).catch(() => {});
|
|
127
|
+
throw failure("command onDiagnostic must be synchronous", "progress_callback_async");
|
|
128
|
+
}
|
|
129
|
+
} catch (error) { stop(error); }
|
|
130
|
+
};
|
|
131
|
+
let observedProgress = 0;
|
|
132
|
+
const timer = clock ? setInterval(() => {
|
|
133
|
+
if (progress) {
|
|
134
|
+
try {
|
|
135
|
+
const next = progress();
|
|
136
|
+
if (!Number.isSafeInteger(next) || next < 0) throw new RangeError('progress must be a non-negative integer');
|
|
137
|
+
if (next > observedProgress) { observedProgress = next; productive(); }
|
|
138
|
+
} catch (error) { stop(error); return; }
|
|
139
|
+
}
|
|
140
|
+
if (clock.sample(cursor)) stop(Object.assign(failure("command observation became quiet", "command_observation_lost"),
|
|
141
|
+
{ details: observationDetails(clock, phase, child.pid) }));
|
|
142
|
+
}, Math.max(1, Math.min(1_000, timeoutMs / 4))) : null;
|
|
143
|
+
clock?.sample(cursor);
|
|
144
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
145
|
+
child.stdout.setEncoding("utf8").on("data", data => {
|
|
146
|
+
productive();
|
|
147
|
+
diagnostic(data, "stdout"); outputBytes += Buffer.byteLength(data);
|
|
148
|
+
if (outputBytes > maxOutput) stop(failure("command final output exceeds 8 MiB", "command_output_limit"));
|
|
149
|
+
else stdout += data;
|
|
150
|
+
});
|
|
151
|
+
child.stderr.setEncoding("utf8").on("data", data => {
|
|
152
|
+
diagnostic(data, "stderr"); stderr = (stderr + data).slice(-maxTail);
|
|
153
|
+
if (droppingStructured) {
|
|
154
|
+
const end = data.indexOf("\n");
|
|
155
|
+
if (end < 0) return;
|
|
156
|
+
data = data.slice(end + 1); droppingStructured = false;
|
|
157
|
+
}
|
|
158
|
+
pendingStderr += data;
|
|
159
|
+
for (;;) {
|
|
160
|
+
const end = pendingStderr.indexOf("\n"); if (end < 0) break;
|
|
161
|
+
const line = pendingStderr.slice(0, end); pendingStderr = pendingStderr.slice(end + 1);
|
|
162
|
+
if (line.length <= maxLine) deliverProgress(line);
|
|
163
|
+
}
|
|
164
|
+
if (pendingStderr && prettyError === "" && !pendingStderr.trimStart().startsWith("{")) productive();
|
|
165
|
+
// Plain streaming diagnostics are not the final reply and cannot kill a
|
|
166
|
+
// productive operation merely because their cumulative size is large.
|
|
167
|
+
if (pendingStderr.length > maxLine) {
|
|
168
|
+
droppingStructured = prettyError !== "" || pendingStderr.trimStart().startsWith("{");
|
|
169
|
+
pendingStderr = droppingStructured ? "" : pendingStderr.slice(-maxTail);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
child.once("error", error => { if (!child.pid) finish(primary ?? error); else stop(error); });
|
|
173
|
+
if (input !== null) {
|
|
174
|
+
child.stdin.on("error", error => { inputError = error; if (error.code !== "EPIPE") stop(error); });
|
|
175
|
+
child.stdin.end(input);
|
|
176
|
+
}
|
|
177
|
+
child.once("close", (status, signal) => {
|
|
178
|
+
if (pendingStderr) deliverProgress(pendingStderr);
|
|
179
|
+
if (primary) { void (termination ?? Promise.resolve()).then(() => finish(primary)); return; }
|
|
180
|
+
if (result) return finish(inputError, { exit_code: status, signal, stdout: stdout.trim(), stderr });
|
|
181
|
+
if (status !== 0) {
|
|
182
|
+
const error = reportedFailure(stdout, stderr, `${executable} exited ${status}`, json ? "flow_reconciliation_failed" : "command_failed", retainedError);
|
|
183
|
+
error.details = { reason: 'child_exit', exit_code: status, signal, phase, pid: child.pid, ...error.details };
|
|
184
|
+
return finish(error);
|
|
185
|
+
}
|
|
186
|
+
if (!json) return finish(inputError, stdout.trim());
|
|
187
|
+
try {
|
|
188
|
+
const value = JSON.parse(stdout.trim());
|
|
189
|
+
if (value?.ok === false) {
|
|
190
|
+
const error = value.error && typeof value.error === "object" ? value.error : value;
|
|
191
|
+
return finish(reportedError(error, `${bin} returned an error envelope`));
|
|
192
|
+
}
|
|
193
|
+
if (inputError) return finish(inputError);
|
|
194
|
+
finish(null, value);
|
|
195
|
+
} catch (error) { finish(failure(`${bin} returned invalid JSON: ${error.message}`, "flow_reconciliation_failed")); }
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
export async function commandJson(bin, args, options = {}) { return await command(bin, args, true, { startupMs: options.timeoutMs ?? SETUP_STARTUP_MS, ...options }); }
|
|
200
|
+
export async function commandText(bin, args, options = {}) { return await command(bin, args, false, { startupMs: options.timeoutMs ?? SETUP_STARTUP_MS, ...options }); }
|
|
201
|
+
export async function commandResult(bin, args, options = {}) { return await command(bin, args, false, { startupMs: options.timeoutMs ?? SETUP_STARTUP_MS, ...options, result: true }); }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const run = promisify(execFile);
|
|
4
|
+
|
|
5
|
+
/** Inspect OS descendants without using process names as ownership. A start-time
|
|
6
|
+
* identity prevents a later unrelated process with a recycled PID being killed. */
|
|
7
|
+
export async function processSnapshot() {
|
|
8
|
+
const { stdout } = await run("ps", ["-axo", "pid=,ppid=,pgid=,lstart=,stat="], { timeout: 5_000 });
|
|
9
|
+
return stdout.trim().split("\n").flatMap(line => { const parts = line.trim().split(/\s+/); return parts.length >= 9 ? [{ pid: Number(parts[0]), ppid: Number(parts[1]), pgid: Number(parts[2]), started: parts.slice(3, 8).join(" "), zombie: parts[8].startsWith("Z") }] : []; });
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const CAPACITY_POLICY: "provider-overload-episode@1";
|
|
2
|
+
export const MAX_OVERLOAD_RETRIES: 2;
|
|
3
|
+
export const MAX_RETRY_AFTER_MS: 30000;
|
|
4
|
+
export function classifyProviderError(native: unknown): string;
|
|
5
|
+
export function safeProviderError(native: unknown): Record<string, unknown>;
|
|
6
|
+
export type CapacityRefusal = { policy: string; ordinal: number; session_id: string; turn_id: string; first_observed_at: string; native_terminal_at?: string; native_terminal_basis?: string };
|
|
7
|
+
export function parseCapacityOperation(id: string): { root: string; ordinal: number };
|
|
8
|
+
export function normalizeProviderFailure(error: unknown, sessionId?: string, turnId?: string): null | { code: string; session_id: string; turn_id: string; native_error: Record<string, unknown>; observed_at: unknown; terminal_at: unknown; native_turn_items: unknown; primary_error: { details?: Record<string, unknown> } };
|
|
9
|
+
export function terminalProviderOverload(error: unknown, sessionId?: string, turnId?: string): boolean;
|
|
10
|
+
export function nativeItemsSettled(items: unknown): boolean;
|
|
11
|
+
export function capacityBackoff(error: unknown, ordinal: number, now?: number, random?: () => number): number;
|
|
12
|
+
export function refusalObservation(error: unknown, ordinal: number, firstObservedAt: string): CapacityRefusal;
|
|
13
|
+
export function overloadExhausted(refusals: CapacityRefusal[], maxRetries?: number): null | { policy: string; reason: string; attempts: number; refusals: CapacityRefusal[] };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/** Pure policy shared by the controller and pinned-engine EVAL consumers. */
|
|
2
|
+
export const CAPACITY_POLICY = "provider-overload-episode@1";
|
|
3
|
+
export const MAX_OVERLOAD_RETRIES = 2;
|
|
4
|
+
export const MAX_RETRY_AFTER_MS = 30_000;
|
|
5
|
+
|
|
6
|
+
function policyError(code, message, details = {}) { return Object.assign(new Error(message), { code, details }); }
|
|
7
|
+
|
|
8
|
+
/** Only call on a native error envelope, never prompt or tool output. */
|
|
9
|
+
export function classifyProviderError(native) {
|
|
10
|
+
if (!native || typeof native !== "object") return "provider_failed";
|
|
11
|
+
const code = [native.code, native.type, native.codexErrorInfo?.code ?? native.codexErrorInfo].filter(value => typeof value === "string").join(" ");
|
|
12
|
+
const message = String(native.message ?? "");
|
|
13
|
+
const text = `${code} ${message}`;
|
|
14
|
+
const status = Number(native.status ?? native.statusCode ?? native.status_code ?? native.http_status);
|
|
15
|
+
if (status === 402 || /quota|usageLimitExceeded|sessionBudgetExceeded|billing|payment.required|balance.exhausted|insufficient.credit|resource_exhausted.*limit/i.test(text)) return "provider_quota_exhausted";
|
|
16
|
+
if (status === 401 || status === 403 || /provider_auth_failed|invalid.api.key|authentication|unauthorized|eligib|forbidden|access.denied|rejected.this.account/i.test(text)) return "provider_auth_failed";
|
|
17
|
+
if (/provider_capacity_transient|serverOverloaded|model.?capacity|at capacity|server.overload|model.overload|server.?busy/i.test(text) || /(?:^|\s)overloaded_error(?:\s|$)/i.test(code) || status === 503 || status === 529) return "provider_capacity_transient";
|
|
18
|
+
if (/rateLimitExceeded|rate.limit/i.test(text) && (native.retryAfter !== undefined || native.retry_after !== undefined || Object.keys(native.headers ?? {}).some(key => key.toLowerCase() === "retry-after"))) return "provider_rate_limited";
|
|
19
|
+
return status === 429 || /provider_limit_unknown|\b429\b|rateLimitExceeded|rate.limit/i.test(text) ? "provider_limit_unknown" : "provider_failed";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Public retry evidence excludes response bodies, credentials and other headers. */
|
|
23
|
+
export function safeProviderError(native) {
|
|
24
|
+
const result = { code: classifyProviderError(native) };
|
|
25
|
+
if (!native || typeof native !== "object") return result;
|
|
26
|
+
const status = native.status ?? native.statusCode ?? native.status_code ?? native.http_status;
|
|
27
|
+
if (Number.isInteger(Number(status))) result.status = Number(status);
|
|
28
|
+
const header = Object.entries(native.headers ?? {}).find(([key]) => key.toLowerCase() === "retry-after")?.[1];
|
|
29
|
+
const retry = header ?? native.retryAfter ?? native.retry_after;
|
|
30
|
+
if (retry !== undefined && retry !== null) result.retryAfter = typeof retry === "string" && retry.length <= 128 || typeof retry === "number" && Number.isFinite(retry) ? retry : "invalid-retry-after";
|
|
31
|
+
if (typeof native.willRetry === "boolean") result.willRetry = native.willRetry;
|
|
32
|
+
for (const key of ["reset_at", "resets_at", "resetAt", "resetsAt", "reset_time"]) {
|
|
33
|
+
const value = native[key];
|
|
34
|
+
if (typeof value === "string" && value.length <= 128 || typeof value === "number" && Number.isFinite(value)) result[key] = value;
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function parseCapacityOperation(id) {
|
|
40
|
+
if (typeof id !== "string" || !id) throw policyError("capacity_continuation_conflict", "Missing capacity operation identity");
|
|
41
|
+
const match = /^(.*):capacity:(\d+):session\.prompt$/.exec(id);
|
|
42
|
+
if (!match) return { root: id, ordinal: 0 };
|
|
43
|
+
const ordinal = Number(match[2]);
|
|
44
|
+
if (!Number.isSafeInteger(ordinal) || ordinal < 1 || String(ordinal) !== match[2]) throw policyError("capacity_continuation_conflict", "Capacity ordinal is not a canonical positive safe integer", { operation_id: id });
|
|
45
|
+
return { root: match[1], ordinal };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function normalizeProviderFailure(error, expectedSessionId, expectedTurnId) {
|
|
49
|
+
let current = error;
|
|
50
|
+
for (let depth = 0; depth < 4 && current && typeof current === "object"; depth++) {
|
|
51
|
+
// The same continuation authority applies to a proven v2 execution
|
|
52
|
+
// refusal. Never classify transport loss or an active native retry here.
|
|
53
|
+
if (current.code === "provider_capacity_transient" || current.code === "provider_rate_limited") {
|
|
54
|
+
const details = current.details ?? {};
|
|
55
|
+
if (!["opencode.v2.session.execution", "agy.result", "acp.error_response", "droid.agent_turn_outcome", "grok.subagent.event"].includes(details.native_source) || details.terminal_status !== "failed"
|
|
56
|
+
|| details.will_retry === true || details.native_retry_active === true || details.provider_error?.willRetry === true
|
|
57
|
+
|| details.provider_error && !["provider_capacity_transient", "provider_rate_limited"].includes(classifyProviderError(details.provider_error))
|
|
58
|
+
|| typeof details.provider_session_id !== "string" || !details.provider_session_id
|
|
59
|
+
|| typeof details.turn_id !== "string" || !details.turn_id || details.native_turn_id !== details.turn_id
|
|
60
|
+
|| expectedSessionId !== undefined && details.provider_session_id !== expectedSessionId
|
|
61
|
+
|| expectedTurnId !== undefined && details.turn_id !== expectedTurnId) return null;
|
|
62
|
+
return { code: "serverOverloaded", session_id: details.provider_session_id, turn_id: details.turn_id,
|
|
63
|
+
native_error: details.provider_error ?? { type: details.native_error_type, status: details.http_status }, observed_at: details.observed_at ?? null,
|
|
64
|
+
terminal_at: null, terminal_basis: null, native_turn_items: details.native_turn_items ?? null, primary_error: current };
|
|
65
|
+
}
|
|
66
|
+
if (current.code === "turn_interrupted") {
|
|
67
|
+
const details = current.details ?? {}, native = details.provider_error;
|
|
68
|
+
const code = typeof native?.codexErrorInfo === "string" ? native.codexErrorInfo : native?.codexErrorInfo?.code;
|
|
69
|
+
if (native?.willRetry === true || details.terminal_status !== "failed" || typeof details.provider_session_id !== "string" || !details.provider_session_id
|
|
70
|
+
|| expectedSessionId !== undefined && details.provider_session_id !== expectedSessionId
|
|
71
|
+
|| typeof details.turn_id !== "string" || !details.turn_id || details.native_turn_id !== details.turn_id
|
|
72
|
+
|| expectedTurnId !== undefined && details.turn_id !== expectedTurnId || typeof code !== "string") return null;
|
|
73
|
+
return { code, session_id: details.provider_session_id, turn_id: details.turn_id, native_error: native,
|
|
74
|
+
observed_at: details.observed_at ?? null, terminal_at: details.terminal_at ?? null, terminal_basis: details.terminal_basis ?? null,
|
|
75
|
+
native_turn_items: details.native_turn_items ?? null, primary_error: current };
|
|
76
|
+
}
|
|
77
|
+
// No arbitrary recursive cause search: an ownership/hook/lifecycle error wins.
|
|
78
|
+
if (current.code !== "harness_adapter_failed") return null;
|
|
79
|
+
current = current.details?.cause ?? current.cause;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function terminalProviderOverload(error, expectedSessionId, expectedTurnId) {
|
|
85
|
+
const normalized = normalizeProviderFailure(error, expectedSessionId, expectedTurnId);
|
|
86
|
+
return normalized !== null && ["provider_capacity_transient", "provider_rate_limited"].includes(classifyProviderError(normalized.native_error));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function nativeItemsSettled(items) {
|
|
90
|
+
return items?.observed === true && typeof items.possible_effects === "boolean" && items.pending !== true
|
|
91
|
+
&& (items.possible_effects === false || items.pending === false);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function capacityBackoff(error, ordinal, now = Date.now(), random = Math.random) {
|
|
95
|
+
if (!Number.isSafeInteger(ordinal) || ordinal < 1 || !Number.isFinite(now)) throw policyError("capacity_continuation_conflict", "Invalid capacity timing identity");
|
|
96
|
+
const native = normalizeProviderFailure(error)?.native_error ?? error?.details?.provider_error ?? error;
|
|
97
|
+
const header = Object.entries(native?.headers ?? {}).find(([key]) => key.toLowerCase() === "retry-after")?.[1];
|
|
98
|
+
const value = header ?? native?.retryAfter ?? native?.retry_after;
|
|
99
|
+
let retry = 0;
|
|
100
|
+
if (value !== undefined && value !== null) {
|
|
101
|
+
retry = typeof value === "number" || typeof value === "string" && /^\d+(\.\d+)?$/.test(value.trim()) ? Number(value) * 1000 : typeof value === "string" && /^\w{3}, \d{2} \w{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/.test(value.trim()) ? Math.max(0, Date.parse(value) - now) : NaN;
|
|
102
|
+
if (!Number.isFinite(retry) || retry < 0 || !Number.isFinite(now + retry) || now + retry > 8.64e15)
|
|
103
|
+
throw policyError("capacity_retry_metadata_invalid", "Retry-After cannot be represented safely", { retry_after: value });
|
|
104
|
+
}
|
|
105
|
+
const jitter = random();
|
|
106
|
+
if (!Number.isFinite(jitter) || jitter < 0 || jitter >= 1) throw policyError("capacity_retry_metadata_invalid", "Invalid retry jitter");
|
|
107
|
+
const wait = Math.max((ordinal === 1 ? 5_000 : 15_000) + Math.floor(jitter * 251), retry);
|
|
108
|
+
if (wait > MAX_RETRY_AFTER_MS) throw policyError("provider_unavailable", "Provider requested a delay beyond the retry budget", { policy: CAPACITY_POLICY, reason: "retry_after_exceeds_budget", retry_after_ms: retry, suggested_retry_at: new Date(now + retry).toISOString() });
|
|
109
|
+
return wait;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function refusalObservation(error, ordinal, firstObservedAt) {
|
|
113
|
+
const failure = normalizeProviderFailure(error);
|
|
114
|
+
const observed = Date.parse(firstObservedAt);
|
|
115
|
+
if (!failure || !Number.isSafeInteger(ordinal) || ordinal < 0 || !Number.isFinite(observed))
|
|
116
|
+
throw policyError("capacity_continuation_conflict", "Refusal lacks an immutable native identity/time");
|
|
117
|
+
if (failure.observed_at !== null && (typeof failure.observed_at !== "string" || !Number.isFinite(Date.parse(failure.observed_at)) || Date.parse(failure.observed_at) !== observed))
|
|
118
|
+
throw policyError("capacity_continuation_conflict", "Native refusal observation time is invalid or changed");
|
|
119
|
+
const native = failure.terminal_at && Date.parse(failure.terminal_at);
|
|
120
|
+
return { policy: CAPACITY_POLICY, ordinal, session_id: failure.session_id, turn_id: failure.turn_id,
|
|
121
|
+
first_observed_at: firstObservedAt, ...(Number.isFinite(native) && typeof failure.terminal_basis === "string" && failure.terminal_basis ? { native_terminal_at: failure.terminal_at, native_terminal_basis: failure.terminal_basis } : {}) };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function overloadExhausted(refusals, maxRetries = MAX_OVERLOAD_RETRIES) {
|
|
125
|
+
if (!Number.isSafeInteger(maxRetries) || maxRetries < 0 || maxRetries > MAX_OVERLOAD_RETRIES) throw policyError("capacity_continuation_conflict", "Invalid retry budget");
|
|
126
|
+
for (const [index, refusal] of refusals.entries()) {
|
|
127
|
+
const previous = refusals[index - 1];
|
|
128
|
+
if (typeof refusal.first_observed_at !== "string" || !Number.isFinite(Date.parse(refusal.first_observed_at))
|
|
129
|
+
|| previous && Date.parse(refusal.first_observed_at) < Date.parse(previous.first_observed_at))
|
|
130
|
+
throw policyError("capacity_continuation_conflict", "Refusal observation clock reversed or is invalid");
|
|
131
|
+
}
|
|
132
|
+
for (const [index, refusal] of refusals.entries()) {
|
|
133
|
+
if (refusal.policy !== CAPACITY_POLICY) throw policyError("capacity_policy_mismatch", "Retained episode uses another retry policy");
|
|
134
|
+
const previous = refusals[index - 1];
|
|
135
|
+
if (refusal.ordinal !== index || previous && (refusal.session_id !== previous.session_id || refusals.slice(0, index).some(item => item.turn_id === refusal.turn_id))) throw policyError("capacity_continuation_conflict", "Refusal chain identity is inconsistent");
|
|
136
|
+
}
|
|
137
|
+
return refusals.length > maxRetries ? { policy: CAPACITY_POLICY, reason: "overload_retries_exhausted", attempts: refusals.length, refusals } : null;
|
|
138
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, open, readFile, rename, unlink } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { withRunnerLock as withLock } from "./runner-lock.mjs";
|
|
5
|
+
import { isObservationLoss, errorRecord } from "./operation-errors.mjs";
|
|
6
|
+
import { operationContext } from "./operation-context.mjs";
|
|
7
|
+
|
|
8
|
+
const appendQueues = new Map();
|
|
9
|
+
|
|
10
|
+
export function canonicalJson(value) {
|
|
11
|
+
const normalize = (item) => {
|
|
12
|
+
if (Array.isArray(item)) return item.map(normalize);
|
|
13
|
+
if (item && typeof item === "object") return Object.fromEntries(Object.keys(item).sort().map((key) => [key, normalize(item[key])]));
|
|
14
|
+
return item;
|
|
15
|
+
};
|
|
16
|
+
return `${JSON.stringify(normalize(value))}\n`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function sha256(value) {
|
|
20
|
+
return createHash("sha256").update(value).digest("hex");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function hashJson(value) { return sha256(canonicalJson(value)); }
|
|
24
|
+
|
|
25
|
+
export async function writeJsonAtomic(file, value) {
|
|
26
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
27
|
+
const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
28
|
+
try {
|
|
29
|
+
const payload = await open(temporary, "wx", 0o600);
|
|
30
|
+
try { await payload.writeFile(`${JSON.stringify(value, null, 2)}\n`); await payload.sync(); }
|
|
31
|
+
finally { await payload.close(); }
|
|
32
|
+
await rename(temporary, file);
|
|
33
|
+
const directory = await open(path.dirname(file), "r");
|
|
34
|
+
try { await directory.sync(); }
|
|
35
|
+
finally { await directory.close(); }
|
|
36
|
+
} finally { await unlink(temporary).catch(error => { if (error.code !== "ENOENT") throw error; }); }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function readJsonLines(file, label = "JSONL") {
|
|
40
|
+
try {
|
|
41
|
+
const source = await readFile(file, "utf8");
|
|
42
|
+
return source.split("\n").filter(Boolean).map((line, index) => {
|
|
43
|
+
try { return JSON.parse(line); }
|
|
44
|
+
catch { throw new Error(`invalid ${label} record at ${file}:${index + 1}`); }
|
|
45
|
+
});
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error?.code === "ENOENT") return [];
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function readEvents(file) {
|
|
53
|
+
const records = await readJsonLines(file, "runner event");
|
|
54
|
+
return records.map((record, index) => {
|
|
55
|
+
try { return validateRunnerEvent(record); }
|
|
56
|
+
catch { throw new Error(`invalid runner event at ${file}:${index + 1}`); }
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function validateRunnerEvent(value) {
|
|
61
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || value.specversion !== "1.0" || typeof value.id !== "string" || typeof value.source !== "string" || typeof value.type !== "string" || typeof value.time !== "string" || !value.data || typeof value.data !== "object" || !Number.isInteger(value.data.sequence) || value.data.sequence < 1) {
|
|
62
|
+
throw new Error("invalid normalized runner event");
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function appendEvent(file, input) {
|
|
68
|
+
const prior = appendQueues.get(file) ?? Promise.resolve();
|
|
69
|
+
const current = prior.then(() => withLock(file, async () => {
|
|
70
|
+
const events = await readEvents(file);
|
|
71
|
+
// Internal callers can validate a dispatch or bind its generation under
|
|
72
|
+
// the same cross-process lock that orders cancellation and completion.
|
|
73
|
+
if (input.deduplicate && input.id) { const existing = events.find(event => event.id === input.id); if (existing) return existing; }
|
|
74
|
+
if (input.beforeAppend?.(events) === false) return null;
|
|
75
|
+
const sequence = events.length + 1;
|
|
76
|
+
const event = {
|
|
77
|
+
specversion: "1.0",
|
|
78
|
+
id: input.id ?? `EVT-${randomUUID()}`,
|
|
79
|
+
source: input.source,
|
|
80
|
+
type: input.type,
|
|
81
|
+
time: input.time ?? new Date().toISOString(),
|
|
82
|
+
subject: input.subject ?? "runner",
|
|
83
|
+
datacontenttype: "application/json",
|
|
84
|
+
runid: input.runId,
|
|
85
|
+
executionid: input.executionId,
|
|
86
|
+
traceid: input.traceId,
|
|
87
|
+
spanid: input.spanId ?? randomUUID(),
|
|
88
|
+
...(input.parentSpanId ? { parentspanid: input.parentSpanId } : {}),
|
|
89
|
+
data: { sequence, ...input.data }
|
|
90
|
+
};
|
|
91
|
+
const context = operationContext.getStore();
|
|
92
|
+
if (context?.eventsFile === file && context.executionId === input.executionId && context.operationId && input.executionId) {
|
|
93
|
+
event.data.execution_operation_id ??= context.operationId;
|
|
94
|
+
}
|
|
95
|
+
validateRunnerEvent(event);
|
|
96
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
97
|
+
const handle = await open(file, "a");
|
|
98
|
+
try { await handle.write(`${JSON.stringify(event)}\n`); await handle.sync(); }
|
|
99
|
+
finally { await handle.close(); }
|
|
100
|
+
return event;
|
|
101
|
+
}));
|
|
102
|
+
const tail = current.catch(() => {}); appendQueues.set(file, tail);
|
|
103
|
+
try { return await current; }
|
|
104
|
+
finally { if (appendQueues.get(file) === tail) appendQueues.delete(file); }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function reduceEvents(events) {
|
|
108
|
+
const operations = new Map();
|
|
109
|
+
let state = "planned";
|
|
110
|
+
for (const event of events) {
|
|
111
|
+
const data = event?.data ?? {};
|
|
112
|
+
if (["dev.dd.eval.planned", "dev.dd.eval.completed"].includes(event.type) && typeof data.state === "string") state = data.state;
|
|
113
|
+
if (typeof data.operation_id !== "string") continue;
|
|
114
|
+
const prior = operations.get(data.operation_id) ?? { id: data.operation_id, requested: false, started: false, terminal: null };
|
|
115
|
+
if (event.type.endsWith(".requested")) prior.requested = true;
|
|
116
|
+
if (event.type.endsWith(".started")) prior.started = true;
|
|
117
|
+
if (event.type.endsWith(".observation_lost")) prior.observation_lost = data.error ?? { code: "observation_lost" };
|
|
118
|
+
if (event.type.endsWith(".completed") || event.type.endsWith(".failed") || event.type.endsWith(".cancelled")) {
|
|
119
|
+
const terminal = event.type.split(".").at(-1);
|
|
120
|
+
const result = terminal === "completed" ? data.result : { error: data.error ?? null };
|
|
121
|
+
const resultHash = hashJson({ terminal, result });
|
|
122
|
+
if (prior.terminal && (prior.terminal !== terminal || prior.result_hash !== resultHash)) {
|
|
123
|
+
throw Object.assign(new Error(`runner operation ${data.operation_id} has conflicting terminal events`), { code: "journal_conflict", operation_id: data.operation_id });
|
|
124
|
+
}
|
|
125
|
+
prior.terminal = terminal;
|
|
126
|
+
prior.result = result;
|
|
127
|
+
prior.result_hash = resultHash;
|
|
128
|
+
}
|
|
129
|
+
operations.set(data.operation_id, prior);
|
|
130
|
+
}
|
|
131
|
+
return { state, operations: Object.fromEntries([...operations].map(([id, value]) => [id, value])) };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function recordOperation({ eventsFile, source, runId, executionId, traceId, operationId, operation, subject = "runner", action }) {
|
|
135
|
+
const existing = await withLock(`${eventsFile}.${operationId.replace(/[^a-zA-Z0-9._-]/g, "_")}`, async () => {
|
|
136
|
+
const current = reduceEvents(await readEvents(eventsFile)).operations[operationId];
|
|
137
|
+
if (current?.terminal === "completed") return current;
|
|
138
|
+
if (current?.terminal) throw Object.assign(new Error(`operation ${operationId} is already ${current.terminal}; use explicit recovery instead of replaying it`), { code: "operation_terminal", operation_id: operationId, terminal: current.terminal });
|
|
139
|
+
if (current?.started && !current.terminal) {
|
|
140
|
+
const error = new Error(current.observation_lost
|
|
141
|
+
? `operation ${operationId} has no confirmed outcome; reconcile its provider state before retrying`
|
|
142
|
+
: `operation ${operationId} is already in progress; reconcile before retrying`);
|
|
143
|
+
error.code = current.observation_lost ? "operation_observation_lost" : "operation_in_progress";
|
|
144
|
+
error.operation_id = operationId;
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
await appendEvent(eventsFile, { source, runId, executionId, traceId, subject, type: "dev.dd.eval.operation.requested", data: { operation_id: operationId, operation, status: "requested" } });
|
|
148
|
+
await appendEvent(eventsFile, { source, runId, executionId, traceId, subject, type: "dev.dd.eval.operation.started", data: { operation_id: operationId, operation, status: "started" } });
|
|
149
|
+
return null;
|
|
150
|
+
});
|
|
151
|
+
if (existing) return { reused: true, operation_id: operationId, result: existing.result };
|
|
152
|
+
try {
|
|
153
|
+
const result = await operationContext.run({ operationId, eventsFile, runId, executionId }, action);
|
|
154
|
+
await completeOperation({ eventsFile, source, runId, executionId, traceId, subject, operationId, operation, result });
|
|
155
|
+
return { reused: false, operation_id: operationId, result };
|
|
156
|
+
} catch (error) {
|
|
157
|
+
if (isObservationLoss(error)) await observationLost({ eventsFile, source, runId, executionId, traceId, subject, operationId, operation, error });
|
|
158
|
+
else await failOperation({ eventsFile, source, runId, executionId, traceId, subject, operationId, operation, error });
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export async function completeOperation({ eventsFile, source, runId, executionId, traceId, subject = "runner", operationId, operation, result }) {
|
|
164
|
+
return await settleOperation({ eventsFile, source, runId, executionId, traceId, subject, operationId, operation, terminal: "completed", result });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function failOperation({ eventsFile, source, runId, executionId, traceId, subject = "runner", operationId, operation, error }) {
|
|
168
|
+
return await settleOperation({ eventsFile, source, runId, executionId, traceId, subject, operationId, operation, terminal: "failed", result: { error: errorRecord(error) } });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** A lost observer response is deliberately non-terminal: replay could duplicate a provider Turn. */
|
|
172
|
+
export async function observationLost({ eventsFile, source, runId, executionId, traceId, subject = "runner", operationId, operation, error }) {
|
|
173
|
+
return await withLock(`${eventsFile}.${operationId.replace(/[^a-zA-Z0-9._-]/g, "_")}`, async () => {
|
|
174
|
+
const existing = reduceEvents(await readEvents(eventsFile)).operations[operationId];
|
|
175
|
+
if (existing?.terminal) return { reused: true, operation_id: operationId, result: existing.result };
|
|
176
|
+
if (!existing?.started) throw Object.assign(new Error(`operation ${operationId} has no started receipt`), { code: "operation_not_started" });
|
|
177
|
+
if (existing.observation_lost) return { reused: true, operation_id: operationId, observation_lost: existing.observation_lost };
|
|
178
|
+
const recorded = errorRecord(error);
|
|
179
|
+
await appendEvent(eventsFile, { source, runId, executionId, traceId, subject, type: "dev.dd.eval.operation.observation_lost", data: { operation_id: operationId, operation, status: "observation_lost", error: recorded } });
|
|
180
|
+
return { reused: false, operation_id: operationId, observation_lost: recorded };
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function settleOperation({ eventsFile, source, runId, executionId, traceId, subject, operationId, operation, terminal, result }) {
|
|
185
|
+
return await withLock(`${eventsFile}.${operationId.replace(/[^a-zA-Z0-9._-]/g, "_")}`, async () => {
|
|
186
|
+
const existing = reduceEvents(await readEvents(eventsFile)).operations[operationId];
|
|
187
|
+
const expectedHash = hashJson({ terminal, result });
|
|
188
|
+
if (existing?.terminal) {
|
|
189
|
+
if (existing.terminal === terminal && existing.result_hash === expectedHash) return { reused: true, operation_id: operationId, result: existing.result };
|
|
190
|
+
throw Object.assign(new Error(`runner operation ${operationId} has conflicting terminal result`), { code: "journal_conflict", operation_id: operationId });
|
|
191
|
+
}
|
|
192
|
+
if (!existing?.started) throw Object.assign(new Error(`operation ${operationId} has no started receipt`), { code: "operation_not_started" });
|
|
193
|
+
const type = terminal === "completed" ? "dev.dd.eval.operation.completed" : "dev.dd.eval.operation.failed";
|
|
194
|
+
const data = terminal === "completed"
|
|
195
|
+
? { operation_id: operationId, operation, status: terminal, result }
|
|
196
|
+
: { operation_id: operationId, operation, status: terminal, error: result.error };
|
|
197
|
+
await appendEvent(eventsFile, { source, runId, executionId, traceId, subject, type, data });
|
|
198
|
+
return { reused: false, operation_id: operationId, result };
|
|
199
|
+
});
|
|
200
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync, rmdirSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
function release(lock, owner) {
|
|
6
|
+
try { unlinkSync(path.join(lock, owner)); } catch (error) { if (error.code === "ENOENT") return; throw error; }
|
|
7
|
+
// Never recursively remove a directory: another owner may already have acquired it.
|
|
8
|
+
try { rmdirSync(lock); } catch (error) { if (!["ENOENT", "ENOTEMPTY", "EEXIST"].includes(error.code)) throw error; }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function reclaimDeadOwner(lock) {
|
|
12
|
+
let entries;
|
|
13
|
+
try { entries = readdirSync(lock); } catch (error) { if (error.code === "ENOENT") return; throw error; }
|
|
14
|
+
if (entries.length !== 1 || !/^owner-.*\.json$/.test(entries[0])) return;
|
|
15
|
+
let owner;
|
|
16
|
+
try { owner = JSON.parse(readFileSync(path.join(lock, entries[0]), "utf8")); } catch { return; }
|
|
17
|
+
if (!Number.isInteger(owner.pid) || owner.pid <= 0) return;
|
|
18
|
+
try { process.kill(owner.pid, 0); }
|
|
19
|
+
catch (error) { if (error.code === "ESRCH") release(lock, entries[0]); }
|
|
20
|
+
// A live/reused PID or unknown owner is conservative: age never proves death.
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function withRunnerLock(file, action, { timeoutMs = 30_000 } = {}) {
|
|
24
|
+
const lock = `${file}.lock`;
|
|
25
|
+
const owner = `owner-${process.pid}-${randomUUID()}.json`;
|
|
26
|
+
const deadline = performance.now() + timeoutMs;
|
|
27
|
+
for (;;) {
|
|
28
|
+
let acquired = false;
|
|
29
|
+
try { mkdirSync(lock); acquired = true; }
|
|
30
|
+
catch (error) { if (error.code !== "EEXIST") throw error; }
|
|
31
|
+
if (acquired) {
|
|
32
|
+
// No await between acquisition and identifying the owner.
|
|
33
|
+
try { writeFileSync(path.join(lock, owner), JSON.stringify({ pid: process.pid }), { flag: "wx" }); }
|
|
34
|
+
catch (error) { release(lock, owner); throw error; }
|
|
35
|
+
try { return await action(); }
|
|
36
|
+
finally { release(lock, owner); }
|
|
37
|
+
}
|
|
38
|
+
reclaimDeadOwner(lock);
|
|
39
|
+
if (performance.now() >= deadline) throw Object.assign(new Error(`runner lock has a live or unconfirmed owner: ${lock}; inspect its owner before recovery`), { code: "runner_lock_timeout" });
|
|
40
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { errorRecord } from "./operation-errors.mjs";
|
|
2
|
+
|
|
3
|
+
/** Diagnostic failure never prevents cancellation of already owned Sessions.
|
|
4
|
+
* Only a fresh successful observation can establish settlement. */
|
|
5
|
+
export async function waitForSettlement({ observe, cancel, knownSessions = () => [], timeoutMs = 5_000 }) {
|
|
6
|
+
const deadline = performance.now() + timeoutMs;
|
|
7
|
+
const cancelled = new Set(), errors = [];
|
|
8
|
+
if (cancel) for (const id of knownSessions()) {
|
|
9
|
+
cancelled.add(id);
|
|
10
|
+
try { await cancel(id); } catch (error) { errors.push({ phase: "cancel", session_id: id, ...errorRecord(error) }); }
|
|
11
|
+
}
|
|
12
|
+
for (;;) {
|
|
13
|
+
let state;
|
|
14
|
+
try { state = await observe(); }
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (!cancel) throw error;
|
|
17
|
+
errors.push({ phase: "observe", ...errorRecord(error) });
|
|
18
|
+
state = { sessions: knownSessions(), active: true, observation_error: errorRecord(error) };
|
|
19
|
+
}
|
|
20
|
+
if (!state.active && state.sessions.length === 0) return { ...state, cleanup_errors: errors };
|
|
21
|
+
// The observation budget must not skip a newly discovered owned target.
|
|
22
|
+
// Attempt its cancellation once, then leave physical settlement pending.
|
|
23
|
+
for (const id of state.sessions) if (cancel && !cancelled.has(id)) {
|
|
24
|
+
cancelled.add(id);
|
|
25
|
+
try { await cancel(id); } catch (error) { errors.push({ phase: "cancel", session_id: id, ...errorRecord(error) }); }
|
|
26
|
+
}
|
|
27
|
+
if (!cancel || performance.now() >= deadline) throw Object.assign(new Error("daemon still owns an unsettled Session tree"), { code: "tree_not_settled", details: { ...state, cleanup_errors: errors } });
|
|
28
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
29
|
+
}
|
|
30
|
+
}
|