@deksden-com/dd-flow-cli 0.9.0-beta.15 → 0.9.0-beta.151
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 +907 -0
- package/README.md +139 -10
- package/dist/build-info.json +8 -8
- package/dist/cli/argument-values.js +38 -0
- package/dist/cli/command-inputs.js +386 -0
- package/dist/cli/command-metadata.js +232 -0
- package/dist/cli/command-option-descriptions.js +192 -0
- package/dist/cli/completion-cli.js +70 -0
- package/dist/cli/completion.js +57 -0
- package/dist/cli/diagnostic-invocation.js +46 -0
- package/dist/cli/diagnostic.js +120 -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 +396 -19
- package/dist/cli/hook-ingress.js +102 -0
- package/dist/cli/input-preparation.js +165 -0
- package/dist/cli/memory-flow-admission.js +50 -0
- package/dist/cli/memory-init-invocation.js +85 -0
- package/dist/cli/memory-init.js +249 -0
- package/dist/cli/memory-storage-admission.js +54 -0
- package/dist/cli/memory-storage-qualification.js +105 -0
- package/dist/cli/memory-storage-transition.js +123 -0
- package/dist/cli/memory-upgrade.js +189 -0
- package/dist/cli/models.js +39 -0
- package/dist/cli/native-hook-ingress.js +90 -0
- package/dist/cli/native-hook-preparation.js +55 -0
- package/dist/cli/operator-profiles.js +61 -0
- package/dist/cli/output-options.js +35 -0
- package/dist/cli/response-file.js +72 -0
- package/dist/cli/run-cli.js +1734 -550
- 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 +150 -0
- package/dist/cli.js +89 -2
- package/dist/domain/diagnostic-aspects.js +103 -0
- package/dist/domain/diagnostic-flows.js +8 -0
- package/dist/domain/stage-catalog.js +1 -1
- package/dist/domain/stage-handler-identities.js +28 -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-error-boundary.mjs +79 -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/agy-native-inspection.mjs +216 -0
- package/dist/harness-runtime/lib/agy-native_pb.js +117 -0
- package/dist/harness-runtime/lib/agy-terminal-envelope.mjs +67 -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 +2 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +462 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +1092 -0
- package/dist/harness-runtime/lib/dd-agy.mjs +86 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +231 -0
- package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +954 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +141 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +626 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +555 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +180 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +627 -0
- package/dist/harness-runtime/lib/dd-opencode.mjs +390 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +752 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +1200 -0
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +14 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +166 -0
- package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +140 -0
- package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
- package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
- package/dist/harness-runtime/lib/entity-references.mjs +9 -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 +16 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +698 -0
- package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
- package/dist/harness-runtime/lib/model-observations.mjs +113 -0
- package/dist/harness-runtime/lib/native-children.d.mts +34 -0
- package/dist/harness-runtime/lib/native-children.mjs +265 -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 +95 -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 +16 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +127 -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 +23 -0
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +242 -0
- package/dist/harness-runtime/lib/runner-events.mjs +202 -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 +53 -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-decision.schema.json +9 -0
- 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/diagnostic-stage-result.schema.json +354 -0
- package/dist/schemas/diagnostic-work-result.schema.json +296 -0
- package/dist/schemas/discovery-result.schema.json +184 -0
- package/dist/schemas/execution-bundle.schema.json +211 -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 +29 -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/mb-upgrade-stage-result.schema.json +863 -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 +56 -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 +12 -3
- package/dist/schemas/plan-review-result.schema.json +2 -2
- package/dist/schemas/profile-settings.schema.json +122 -0
- package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
- package/dist/schemas/release-impact.schema.json +3 -3
- 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/stage-start-response.schema.json +4 -0
- package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
- package/dist/services/accepted-intent.js +139 -0
- package/dist/services/branch-context.js +3 -3
- package/dist/services/canon.js +54 -15
- package/dist/services/check-environment.js +21 -0
- package/dist/services/check-runner-evidence.js +277 -0
- package/dist/services/check-runner-proof.js +305 -0
- package/dist/services/cleanup.js +133 -40
- package/dist/services/cli-operation-classifier.js +26 -63
- package/dist/services/cli-upgrade-allowlist.js +27 -0
- package/dist/services/code-checks.js +1401 -119
- package/dist/services/codex-hook-delivery.js +32 -0
- package/dist/services/command-context.js +80 -0
- package/dist/services/compatibility-preflight.js +16 -3
- package/dist/services/config.js +11 -8
- package/dist/services/continuation-outcome.js +20 -0
- package/dist/services/controller-child-capacity.js +71 -0
- package/dist/services/controller-child-observer.js +121 -0
- package/dist/services/controller-execution.js +94 -0
- package/dist/services/controller-fanout.js +226 -0
- package/dist/services/controller-memory-task.js +45 -0
- package/dist/services/controller-memory-worker.js +49 -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 +107 -0
- package/dist/services/descriptor-run-start.js +475 -0
- package/dist/services/descriptor-stage-coordinator.js +250 -0
- package/dist/services/descriptor-stage-settlement.js +223 -0
- package/dist/services/descriptor-stage-start.js +175 -0
- package/dist/services/diagnostic-evidence.js +73 -0
- package/dist/services/diagnostic-report-publication.js +72 -0
- package/dist/services/diagnostic-report.js +67 -0
- package/dist/services/diagnostic-run-bundle.js +74 -0
- package/dist/services/diagnostic-snapshot.js +159 -0
- package/dist/services/diagnostic-stage-handlers.js +108 -0
- package/dist/services/diagnostic-stage-results.js +132 -0
- package/dist/services/diagnostic-start.js +84 -0
- package/dist/services/diagnostic-work-batch.js +34 -0
- package/dist/services/diagnostic-work-collection.js +97 -0
- package/dist/services/diagnostic-work-registration.js +101 -0
- package/dist/services/diagnostic-work-results.js +93 -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 +354 -163
- package/dist/services/eval-snapshots.js +1316 -150
- package/dist/services/execution-bundles.js +275 -0
- package/dist/services/execution-policy.js +185 -0
- package/dist/services/executor-onboarding.js +56 -0
- package/dist/services/external-work-launch.js +310 -0
- package/dist/services/flow-catalog.js +30 -0
- package/dist/services/flow-descriptors.js +82 -0
- package/dist/services/frozen-engine.js +387 -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/hook-memory-routing.js +188 -0
- package/dist/services/hook-recovery-guard.js +14 -0
- package/dist/services/hook-session-observation.js +24 -0
- package/dist/services/hooks.js +791 -326
- package/dist/services/hybrid-contracts.js +474 -0
- package/dist/services/hybrid-digest.js +14 -0
- package/dist/services/ids.js +29 -1
- package/dist/services/installation-lock.js +95 -0
- package/dist/services/installed-flow-source.js +226 -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 +2148 -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 +449 -69
- package/dist/services/memory-accepted-composition.js +78 -0
- package/dist/services/memory-baseline-preservation.js +65 -0
- package/dist/services/memory-completed-correction-wave.js +328 -0
- package/dist/services/memory-compose-handler.js +400 -0
- package/dist/services/memory-composition-acceptance.js +375 -0
- package/dist/services/memory-composition-graph.js +165 -0
- package/dist/services/memory-composition-registration.js +161 -0
- package/dist/services/memory-composition-work-custody.js +156 -0
- package/dist/services/memory-composition-work-results.js +108 -0
- package/dist/services/memory-corrected-review-acceptance.js +129 -0
- package/dist/services/memory-corrected-review-batch.js +37 -0
- package/dist/services/memory-corrected-review-findings.js +108 -0
- package/dist/services/memory-corrected-review-inputs.js +215 -0
- package/dist/services/memory-corrected-review-projection.js +78 -0
- package/dist/services/memory-corrected-review-receipt.js +178 -0
- package/dist/services/memory-corrected-review-registration.js +192 -0
- package/dist/services/memory-correction-check-receipt.js +235 -0
- package/dist/services/memory-correction-input-owner.js +6 -0
- package/dist/services/memory-custody-snapshot.js +36 -0
- package/dist/services/memory-discover-handler.js +104 -0
- package/dist/services/memory-finish-evidence.js +457 -0
- package/dist/services/memory-finish-handler.js +229 -0
- package/dist/services/memory-finish-integration.js +450 -0
- package/dist/services/memory-finish-readiness.js +169 -0
- package/dist/services/memory-git-integration.js +220 -0
- package/dist/services/memory-historical-corrected-review.js +212 -0
- package/dist/services/memory-historical-original-review.js +143 -0
- package/dist/services/memory-historical-repair-registration.js +105 -0
- package/dist/services/memory-historical-second-review.js +147 -0
- package/dist/services/memory-init-bootstrap-readiness.js +210 -0
- package/dist/services/memory-init-contexts.js +131 -0
- package/dist/services/memory-init-controller-start.js +103 -0
- package/dist/services/memory-init-engine-release.js +223 -0
- package/dist/services/memory-init-git-policy.js +165 -0
- package/dist/services/memory-init-lint-capability.js +203 -0
- package/dist/services/memory-init-native-capacity.js +309 -0
- package/dist/services/memory-init-preflight.js +334 -0
- package/dist/services/memory-init-project-bootstrap.js +599 -0
- package/dist/services/memory-init-request-candidate.js +213 -0
- package/dist/services/memory-init-request-input.js +82 -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 +135 -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 +91 -0
- package/dist/services/memory-init-stores.js +359 -0
- package/dist/services/memory-init-worktrunk-capability.js +174 -0
- package/dist/services/memory-layer-publication.js +611 -0
- package/dist/services/memory-layer.js +221 -0
- package/dist/services/memory-lint-config.js +194 -0
- package/dist/services/memory-native-review-copy.js +103 -0
- package/dist/services/memory-normal-flow-entry.js +87 -0
- package/dist/services/memory-ordinary-defaults.js +129 -0
- package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
- package/dist/services/memory-preflight-handler.js +274 -0
- package/dist/services/memory-repair-current-view.js +299 -0
- package/dist/services/memory-repair-document-delta.js +488 -0
- package/dist/services/memory-repair-history.js +496 -0
- package/dist/services/memory-repair-physical-binding.js +194 -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 +162 -0
- package/dist/services/memory-repair-publication-source.js +225 -0
- package/dist/services/memory-repair-publication.js +795 -0
- package/dist/services/memory-repair-registration.js +240 -0
- package/dist/services/memory-repair-work-baseline.js +415 -0
- package/dist/services/memory-repair-work-completion.js +117 -0
- package/dist/services/memory-repair-work-custody.js +73 -0
- package/dist/services/memory-repair-work-results.js +108 -0
- package/dist/services/memory-repair-workspace.js +261 -0
- package/dist/services/memory-repair-writer-copy.js +736 -0
- package/dist/services/memory-result-binding.js +239 -0
- package/dist/services/memory-retained-review-authority.js +138 -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 +162 -0
- package/dist/services/memory-review-acceptance.js +214 -0
- package/dist/services/memory-review-correction-plan.js +206 -0
- package/dist/services/memory-review-handler.js +585 -0
- package/dist/services/memory-review-inputs.js +246 -0
- package/dist/services/memory-review-limits.js +10 -0
- package/dist/services/memory-review-receipt.js +180 -0
- package/dist/services/memory-review-registration.js +117 -0
- package/dist/services/memory-review-work-custody.js +126 -0
- package/dist/services/memory-review-work-input.js +91 -0
- package/dist/services/memory-review-work-results.js +95 -0
- package/dist/services/memory-second-corrected-review-inputs.js +167 -0
- package/dist/services/memory-second-correction-plan.js +77 -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 +65 -0
- package/dist/services/memory-stage-results.js +290 -0
- package/dist/services/memory-tool-probe.js +25 -0
- package/dist/services/memory-upgrade-analysis-handler.js +243 -0
- package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
- package/dist/services/memory-upgrade-evidence.js +100 -0
- package/dist/services/memory-upgrade-inventory.js +442 -0
- package/dist/services/memory-upgrade-publication-custody.js +73 -0
- package/dist/services/memory-upgrade-publication.js +413 -0
- package/dist/services/memory-upgrade-selection.js +430 -0
- package/dist/services/memory-upgrade-start.js +60 -0
- package/dist/services/memory-upgrade-storage-readiness.js +340 -0
- package/dist/services/memory-verification-acceptance.js +156 -0
- package/dist/services/memory-verification-checks.js +317 -0
- package/dist/services/memory-verification-receipt.js +160 -0
- package/dist/services/memory-verify-handler.js +137 -0
- package/dist/services/memory-work-result-json.js +12 -0
- package/dist/services/memory-work-result-preparation.js +64 -0
- package/dist/services/merge-queue.js +228 -149
- package/dist/services/merge-server.js +163 -35
- package/dist/services/migrations.js +164 -114
- package/dist/services/model-catalog.js +109 -0
- package/dist/services/model-discovery.js +46 -0
- package/dist/services/native-daemon-history.js +50 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/operator-profiles.js +236 -0
- package/dist/services/physical-startup.js +45 -0
- package/dist/services/plan-runtime.js +26 -11
- package/dist/services/plans.js +28 -25
- package/dist/services/project-registry.js +97 -0
- package/dist/services/project-summary.js +1 -1
- package/dist/services/projects.js +17 -87
- package/dist/services/prompt-delivery.js +71 -0
- package/dist/services/prompts.js +28 -17
- package/dist/services/protocols.js +163 -87
- package/dist/services/provider-capacity.js +463 -0
- package/dist/services/recovery-observation-budget.js +121 -0
- package/dist/services/recovery-snapshot-database.js +588 -0
- package/dist/services/release-archive-producer.js +52 -0
- package/dist/services/release-archive.js +143 -0
- package/dist/services/release-cache.js +150 -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 +31 -0
- package/dist/services/repair-receipt.js +13 -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 +428 -0
- package/dist/services/run-control.js +1092 -0
- package/dist/services/run-controller-adapter.js +400 -0
- package/dist/services/run-controller-capture.js +186 -0
- package/dist/services/run-controller-process.js +205 -0
- package/dist/services/run-controller-recovery.js +295 -0
- package/dist/services/run-controller-state.js +97 -0
- package/dist/services/run-controller.js +1395 -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 +107 -0
- package/dist/services/run-recovery-runtime.js +112 -0
- package/dist/services/run-recovery.js +456 -0
- package/dist/services/run-start-custody.js +189 -0
- package/dist/services/runs.js +1023 -246
- package/dist/services/runtime-budget.js +446 -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 +605 -0
- package/dist/services/runtime-scope-stop.js +211 -0
- package/dist/services/runtime-scope-worker.js +314 -0
- package/dist/services/runtime-service.js +19 -14
- package/dist/services/schema-validation.js +10 -8
- package/dist/services/scope-writer-cleanup.js +27 -0
- package/dist/services/sessions.js +36 -91
- package/dist/services/setup.js +427 -0
- package/dist/services/stage-blocker.js +17 -7
- package/dist/services/stage-context.js +240 -18
- package/dist/services/stage-handlers.js +89 -0
- package/dist/services/stage-lifecycle.js +152 -116
- package/dist/services/stage-pause.js +202 -91
- package/dist/services/stage-report-renderer.js +234 -6
- package/dist/services/stage-work-graph.js +52 -0
- package/dist/services/tooling.js +41 -30
- package/dist/services/usage.js +49 -101
- package/dist/services/verification-matrix.js +393 -0
- package/dist/services/version-status.js +1 -1
- package/dist/services/vnext-code-review.js +235 -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 +355 -21
- package/dist/services/vnext-merge.js +332 -111
- package/dist/services/vnext-plan-review.js +287 -132
- package/dist/services/vnext-plan.js +228 -104
- package/dist/services/vnext-protocolize.js +533 -95
- package/dist/services/vnext-specify.js +123 -81
- package/dist/services/vnext-workspace-policy.js +7 -2
- package/dist/services/work-registry.js +1706 -200
- package/dist/services/workspace-bootstrap.js +53 -15
- package/dist/services/workspace-files.js +85 -0
- package/dist/services/worktrees.js +105 -48
- package/dist/shared/entity-references.js +1 -0
- package/dist/shared/errors.js +27 -0
- package/dist/storage/agent-output.js +26 -0
- package/dist/storage/data-preservation.js +65 -0
- package/dist/storage/database.js +930 -40
- package/dist/storage/foreign-key-transaction.js +39 -0
- package/dist/storage/immutable-input.js +45 -0
- package/dist/storage/import-guard.js +159 -0
- package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
- package/dist/storage/native-cleanup.js +52 -0
- package/dist/storage/paired-writer-migration.js +424 -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 +96 -0
- package/dist/storage/writer-migration.js +346 -0
- package/dist/storage/writer-restore.js +217 -0
- package/dist/verification-components.json +299 -0
- package/package.json +51 -13
- package/tools/create-canon-release.mjs +33 -0
- package/tools/probe-codex-history.mjs +37 -0
- package/tools/produce-hybrid-release.mjs +41 -0
- package/tools/repair-paused-run-status.mjs +84 -0
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
import { managedFlowEnv } from './managed-flow-env.mjs';
|
|
2
|
+
import { SETUP_STARTUP_MS, startupObserver, observationDetails } from './setup-progress.mjs';
|
|
3
|
+
import { callDaemonObserved } from './daemon-observation.mjs';
|
|
4
|
+
import { admitNativeRequestBuffer } from "./native-streams.mjs";
|
|
5
|
+
import { prepareDaemonRequest } from "./native-request-proof.mjs";
|
|
6
|
+
import { captureDispatchGuard, cancelPendingDispatch } from "./dispatch-fence.mjs";
|
|
7
|
+
import { openCodeToolObservation, journalToolObservations } from "./tool-observations.mjs";
|
|
8
|
+
import { ObservationClock } from "./observation-clock.mjs";
|
|
9
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
10
|
+
import { appendFile, chmod, lstat, mkdir, open, readFile, realpath, unlink, writeFile } from "node:fs/promises";
|
|
11
|
+
import { spawn } from "node:child_process";
|
|
12
|
+
import net from "node:net";
|
|
13
|
+
import { durableDaemonDispatch, completeDaemonShutdown, commitDaemonShutdown, trackDaemonConnections, retireDaemonEndpoint, retireFailedDaemon, assertDaemonDispatch, assertDaemonRestartable } from "./daemon-operations.mjs";
|
|
14
|
+
import { waitForSettlement } from "./session-settlement.mjs";
|
|
15
|
+
import { nativeExecutionReceipt } from "./native-children.mjs";
|
|
16
|
+
import { errorRecord, isProvenShellCorrection, reportedError } from "./operation-errors.mjs";
|
|
17
|
+
import { checkObservedProfile, observeModel } from "./model-observations.mjs";
|
|
18
|
+
import os from "node:os";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
import { parse as parseJsonc } from "jsonc-parser";
|
|
21
|
+
import { writeJsonAtomic } from "./runner-events.mjs";
|
|
22
|
+
import { OpenCodeClient, OpenCodeError, historyDigest, usageSnapshot, nativeModel } from "./dd-opencode.mjs";
|
|
23
|
+
import { safeProviderError } from "./provider-overload-policy.mjs";
|
|
24
|
+
import { acceptNativeHookRequest, hookBudget, readNativeHookFailures } from "./native-hook-command.mjs";
|
|
25
|
+
import { assertStartupChild, retainStartupFailure, prepareRuntimeOwner, cleanupFailedStart, cleanupFailedDaemonStart, confirmDaemonProcess, confirmDaemonStopped, finishDaemonProcess, scheduleDaemonProcessHeartbeat, registerDaemonProcess, stopProcessGroup } from "./managed-daemon.mjs";
|
|
26
|
+
|
|
27
|
+
const REQUEST_SCHEMA = "dd-opencode/daemon-request@1", RESPONSE_SCHEMA = "dd-opencode/daemon-response@1", STATE_SCHEMA = "dd-opencode/daemon-state@2", ARCHIVE_SCHEMA = "dd-opencode/session-archive@2";
|
|
28
|
+
class DaemonError extends Error { constructor(code, message, retryable = false, details) { super(message); this.code = code; this.retryable = retryable; this.details = details; } }
|
|
29
|
+
const absolute = (value, label) => { if (!value || !path.isAbsolute(value)) throw new DaemonError("invalid_path", `${label} must be an absolute path`); return path.resolve(value); };
|
|
30
|
+
export function openCodeProfileOptions(options) {
|
|
31
|
+
for (const [native, shared] of [["variant", "reasoning"], ["agent", "mode"]]) {
|
|
32
|
+
if (options[native] !== undefined && options[shared] !== undefined && options[native] !== options[shared]) throw new DaemonError("profile_conflict", `--${native} and --${shared} disagree`);
|
|
33
|
+
}
|
|
34
|
+
return { variant: options.variant ?? options.reasoning, agent: options.agent ?? options.mode };
|
|
35
|
+
}
|
|
36
|
+
export async function doctor(options) {
|
|
37
|
+
if (options.stateDir) return await callDaemon(options.stateDir, "daemon.doctor");
|
|
38
|
+
const output = (await run(options.bin ?? process.env.DD_OPENCODE_BIN ?? "opencode", ["--version"], { cwd: options.cwd ?? process.cwd() })).trim();
|
|
39
|
+
const version = output.match(/(?:^|\s)v?(2\.\d+\.\d+(?:[-+][\w.-]+)?)(?:\s|$)/)?.[1];
|
|
40
|
+
if (!version) throw new DaemonError("unsupported_version", "dd-opencode supports OpenCode v2 only");
|
|
41
|
+
return { compatible: true, native_protocol: 2, observed_runtime: { opencode: version }, productive_hooks: false, profile_observation: "pending_native_session" };
|
|
42
|
+
}
|
|
43
|
+
function locations(stateDir) { const dir = absolute(stateDir, "--state-dir"), local = path.join(dir, "daemon.sock"); return { dir, socket: Buffer.byteLength(local) < 100 ? local : `/tmp/dd-opencode-${createHash("sha256").update(dir).digest("hex").slice(0, 20)}.sock`, state: path.join(dir, "daemon.json"), journal: path.join(dir, "events.jsonl"), log: path.join(dir, "daemon.log"), data: path.join(dir, "xdg-data"), config: path.join(dir, "xdg-config"), cache: path.join(dir, "xdg-cache"), archive: path.join(dir, "import.json"), db: path.join(dir, "native.db"), xdgState: path.join(dir, "xdg-state") }; }
|
|
44
|
+
const writeJson = writeJsonAtomic;
|
|
45
|
+
async function readState(dir) { try { return JSON.parse(await readFile(locations(dir).state, "utf8")); } catch (error) { if (error.code === "ENOENT") return null; throw error; } }
|
|
46
|
+
async function removeSocket(paths) { try { if (!(await lstat(paths.socket)).isSocket()) throw new DaemonError("unsafe_socket", "refusing to replace non-socket daemon path"); await unlink(paths.socket); } catch (error) { if (error.code !== "ENOENT") throw error; } }
|
|
47
|
+
function executable(bin, args = []) { return /\.[cm]?js$/.test(bin) ? { command: process.execPath, args: [bin, ...args] } : { command: bin, args }; }
|
|
48
|
+
export function isolatedEnv(config) {
|
|
49
|
+
const env = { ...process.env };
|
|
50
|
+
for (const name of ["OPENCODE_CONFIG", "OPENCODE_CONFIG_CONTENT", "OPENCODE_PASSWORD", "OPENCODE_PTY_HANDOFF", "OPENCODE_SERVER", "OPENCODE_SIMULATE"]) delete env[name];
|
|
51
|
+
return { ...env, XDG_DATA_HOME: config.dataHome, XDG_CONFIG_HOME: config.configHome, XDG_CACHE_HOME: config.cacheHome,
|
|
52
|
+
XDG_STATE_HOME: config.stateHome, OPENCODE_DB: config.database, OPENCODE_CONFIG_DIR: config.adapterDir,
|
|
53
|
+
OPENCODE_CONFIG_PROJECT_DISABLE: "1", OPENCODE_SERVER_PASSWORD: config.password,
|
|
54
|
+
...managedFlowEnv(config),
|
|
55
|
+
DD_OPENCODE_PLUGIN_SHA256: config.pluginSha256 ?? "none" };
|
|
56
|
+
}
|
|
57
|
+
export async function run(bin, args, config, options = {}) {
|
|
58
|
+
const { stdin, timeoutMs = 30_000, startupMs = options.timeoutMs ?? SETUP_STARTUP_MS } = options;
|
|
59
|
+
return await commandText(bin, args, { cwd: config.cwd, env: isolatedEnv(config), input: stdin ?? null, timeoutMs, startupMs, phase: args.includes('--version') ? 'native-version' : 'native-utility' });
|
|
60
|
+
}
|
|
61
|
+
async function forwardUsage(config, sessionId, snapshot) {
|
|
62
|
+
if (!config.ddFlowBin) return;
|
|
63
|
+
const payload = { provider_session_id: sessionId, daemon_id: config.daemonId, observed_at: new Date().toISOString(), usage_scope: "physical_session", completeness: snapshot.completeness, usage: { inputTokens: snapshot.input_tokens, outputTokens: snapshot.output_tokens, reasoningTokens: snapshot.reasoning_tokens, cacheReadInputTokens: snapshot.cached_input_tokens, cacheWriteInputTokens: snapshot.cache_write_tokens, totalTokens: snapshot.total_tokens, ...(snapshot.cost_usd === null ? {} : { costUsd: snapshot.cost_usd }) }, tool_calls: snapshot.tool_calls };
|
|
64
|
+
await run(config.ddFlowBin, ["opencode", "usage", "ingest", "--project-root", config.projectRoot, "--json"], config, { stdin: `${JSON.stringify(payload)}\n` });
|
|
65
|
+
}
|
|
66
|
+
async function freePort() { return await new Promise((resolve, reject) => { const server = net.createServer(); server.once("error", reject); server.listen(0, "127.0.0.1", () => { const port = server.address().port; server.close(() => resolve(port)); }); }); }
|
|
67
|
+
const errorPayload = errorRecord;
|
|
68
|
+
|
|
69
|
+
async function writePlugin(paths, config) {
|
|
70
|
+
const directory = path.join(paths.config, "opencode", "plugins"); await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
71
|
+
const source = `import { makeOpenCodePlugin } from ${JSON.stringify(new URL("./opencode-plugin.mjs", import.meta.url).href)};\nexport default makeOpenCodePlugin(${JSON.stringify({ stateDir: paths.dir, daemonId: config.daemonId, version: config.version, ddFlowBin: config.ddFlowBin, nodeBin: process.execPath, ddFlowHome: config.ddFlowHome, projectRoot: config.projectRoot, resourceHome: config.resourceHome })});\n`;
|
|
72
|
+
const checksum = createHash("sha256").update(source).digest("hex"); await writeFile(path.join(directory, "dd-flow.js"), source, { mode: 0o600 }); return checksum;
|
|
73
|
+
}
|
|
74
|
+
async function prepare(paths, config, authSource) {
|
|
75
|
+
for (const dir of [paths.data, paths.config, paths.cache, paths.xdgState, path.join(paths.config, "opencode")]) { await mkdir(dir, { recursive: true, mode: 0o700 }); await chmod(dir, 0o700); }
|
|
76
|
+
const authTarget = path.join(paths.data, "opencode", "auth.json"); await mkdir(path.dirname(authTarget), { recursive: true, mode: 0o700 });
|
|
77
|
+
try { const source = JSON.parse(await readFile(authSource, "utf8")); const selected = !Array.isArray(source) && source[config.provider];
|
|
78
|
+
if (selected) { await writeJson(authTarget, { [config.provider]: selected }); config.authStatus = "selected_portable"; }
|
|
79
|
+
else config.authStatus = "absent";
|
|
80
|
+
} catch (error) { if (error.code !== "ENOENT") throw error; config.authStatus = "absent"; }
|
|
81
|
+
let portable = {}, policies, permissions = [{ action: "*", resource: "*", effect: "allow" }], agentRestrictions = {};
|
|
82
|
+
try { const errors = [], source = parseJsonc(await readFile(config.configSource, "utf8"), errors, { allowTrailingComma: true }); if (errors.length || !source || typeof source !== "object" || Array.isArray(source)) throw new Error("invalid config");
|
|
83
|
+
portable = Object.fromEntries(["provider", "providers", "permission", "tools"].filter(key => source[key] !== undefined).map(key => [key, source[key]]));
|
|
84
|
+
// Explicit operator policies outrank our permissive empty-home defaults.
|
|
85
|
+
// Let v2 normalize legacy permission fields instead of guessing mappings.
|
|
86
|
+
if (source.permissions !== undefined || source.permission !== undefined || source.tools !== undefined) permissions = source.permissions ?? [];
|
|
87
|
+
policies = source.experimental?.policies;
|
|
88
|
+
for (const [name, agents] of [["agent", source.agent], ["agents", source.agents]]) {
|
|
89
|
+
const selected = agents?.[config.agent];
|
|
90
|
+
if (selected) agentRestrictions[name] = { [config.agent]: Object.fromEntries(["permission", "permissions", "tools"].filter(key => selected[key] !== undefined).map(key => [key, selected[key]])) };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (error) { if (error.code !== "ENOENT") throw new DaemonError("opencode_config_invalid", "Portable OpenCode provider config must be valid JSON"); }
|
|
94
|
+
const generatedConfig = { ...portable, ...agentRestrictions, permissions,
|
|
95
|
+
agents: { ...agentRestrictions.agents, "dd-flow-worker": { ...agentRestrictions.agent?.[config.agent], ...agentRestrictions.agents?.[config.agent], mode: "subagent", system: "Execute the assigned dd-flow Work. Follow the issued lifecycle instructions. Do not spawn descendants.", model: `${config.provider}/${config.model}${config.variant !== "default" ? `#${config.variant}` : ""}` } },
|
|
96
|
+
experimental: { subagent_depth: 1, ...(policies ? { policies } : {}) } };
|
|
97
|
+
const configPath = path.join(paths.config, "opencode", "opencode.json");
|
|
98
|
+
await writeJson(configPath, generatedConfig);
|
|
99
|
+
config.workerLaunchConfig = { locator: configPath, sha256: createHash('sha256').update(await readFile(configPath)).digest('hex'), primary_agent: config.agent, subagent: 'dd-flow-worker', max_depth: 1 };
|
|
100
|
+
config.pluginSha256 = await writePlugin(paths, config);
|
|
101
|
+
}
|
|
102
|
+
async function bootstrapCredentials(config, client) {
|
|
103
|
+
let source;
|
|
104
|
+
try { source = JSON.parse(await readFile(config.authSource, "utf8")); }
|
|
105
|
+
catch (error) { if (error.code === "ENOENT") return "absent"; throw new DaemonError("opencode_credentials_invalid", "Portable credentials are not valid JSON"); }
|
|
106
|
+
const entry = Array.isArray(source) ? source.find(item => item.integrationID === config.provider && item.active === true) : null;
|
|
107
|
+
const legacy = !Array.isArray(source) ? source[config.provider] : null;
|
|
108
|
+
const value = entry?.value ?? (legacy?.type === "api" ? { type: "key", key: legacy.key } : null);
|
|
109
|
+
// OAuth/external entries imported by the native v1 migration are retained;
|
|
110
|
+
// v2 exports use native credential values, never a copied user database.
|
|
111
|
+
let selected = (await client.call("credential.list")).find(item => item.integrationID === config.provider && item.active);
|
|
112
|
+
if (value && JSON.stringify(selected?.value) !== JSON.stringify(value)) {
|
|
113
|
+
if (selected) await client.call("credential.update", { id: selected.id, value }, { productive: true });
|
|
114
|
+
else await client.call("credential.create", { integrationID: config.provider, label: "dd-flow portable", value, activate: true }, { productive: true });
|
|
115
|
+
selected = (await client.call("credential.list")).find(item => item.integrationID === config.provider && item.active);
|
|
116
|
+
if (!selected || JSON.stringify(selected.value) !== JSON.stringify(value)) throw new DaemonError("opencode_credentials_unproven", "Private credential readback did not confirm the selected entry");
|
|
117
|
+
}
|
|
118
|
+
if ((entry || legacy) && !selected) throw new DaemonError("opencode_credentials_unproven", "Selected portable credential is not active in the private server");
|
|
119
|
+
return selected ? "active_private" : "absent";
|
|
120
|
+
}
|
|
121
|
+
async function importArchive(config, client, onProgress = async () => {}) {
|
|
122
|
+
if (!config.sessionArchive) return null;
|
|
123
|
+
const manifest = JSON.parse(await readFile(config.sessionArchive, "utf8"));
|
|
124
|
+
if (manifest.schema_id !== ARCHIVE_SCHEMA || !manifest.payload || !/^2\./.test(manifest.opencode_version)) throw new DaemonError("invalid_session_archive", "Use the archived v1 engine for legacy OpenCode archives");
|
|
125
|
+
const payload = JSON.parse(Buffer.from(manifest.payload, "base64").toString("utf8"));
|
|
126
|
+
const transfers = payload.sessions, root = manifest.provider_session_id;
|
|
127
|
+
if (!Array.isArray(transfers) || !transfers.length || transfers[0]?.info?.id !== root || new Set(transfers.map(item => item.info?.id)).size !== transfers.length)
|
|
128
|
+
throw new DaemonError("invalid_session_archive", "Archive does not contain a unique native Session tree");
|
|
129
|
+
const records = [], imported = new Set();
|
|
130
|
+
for (const transfer of transfers) {
|
|
131
|
+
if (!transfer.info?.id || !Array.isArray(transfer.messages) || transfer.info.id !== root && !imported.has(transfer.info.parentID))
|
|
132
|
+
throw new DaemonError("invalid_session_archive", "Archive child is not in the native parent-first tree");
|
|
133
|
+
imported.add(transfer.info.id);
|
|
134
|
+
}
|
|
135
|
+
for (const transfer of transfers) {
|
|
136
|
+
const info = await client.import({ ...transfer, info: { ...transfer.info, location: { directory: config.cwd } } });
|
|
137
|
+
if (info?.id !== transfer.info.id || info.location?.directory !== config.cwd || (info.parentID ?? null) !== (transfer.info.parentID ?? null))
|
|
138
|
+
throw new DaemonError("session_import_unproven", "OpenCode import did not preserve native identity/location");
|
|
139
|
+
records.push({ provider_session_id: info.id, parent_provider_session_id: info.id === root ? null : info.parentID, root_provider_session_id: root, cwd: config.cwd });
|
|
140
|
+
await onProgress(info.id);
|
|
141
|
+
}
|
|
142
|
+
return records;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function callDaemon(stateDir, operation, params = {}, timeoutMs = 30_000) {
|
|
146
|
+
const state = await readState(stateDir);
|
|
147
|
+
if (state && state.schema_id !== STATE_SCHEMA) throw new DaemonError("legacy_opencode_state", "Use archive/opencode-v1-final-beta134 for v1 states; the v2 adapter cannot dispatch to a legacy daemon", false, { effect: "no_effect" });
|
|
148
|
+
const request = prepareDaemonRequest({ schema_id: REQUEST_SCHEMA, id: process.env.DD_EVAL_OPERATION_ID ?? randomUUID(), operation, params });
|
|
149
|
+
return await callDaemonObserved({ stateDir, socket: locations(stateDir).socket, request, responseSchema: RESPONSE_SCHEMA, harness: 'opencode', timeoutMs });
|
|
150
|
+
}
|
|
151
|
+
function sameConfig(state, config) { return ["cwd","journal","bin","provider","model","variant","agent","projectRoot","ddFlowBin","ddFlowHome","sessionArchive","resourceHome","noFlow","configSource","authSource"].every(key => (state.config?.[key] ?? null) === (config[key] ?? null)); }
|
|
152
|
+
export async function startDaemon(options) {
|
|
153
|
+
const paths = locations(options.stateDir);
|
|
154
|
+
const previous = await readState(paths.dir);
|
|
155
|
+
if (previous && previous.schema_id !== STATE_SCHEMA) throw new DaemonError("legacy_opencode_state", "Use archive/opencode-v1-final-beta134 for v1 states; start v2 in a fresh state directory");
|
|
156
|
+
await mkdir(paths.dir, { recursive: true, mode: 0o700 }); await chmod(paths.dir, 0o700);
|
|
157
|
+
const daemonId = randomUUID(), noFlow = options.noFlow === true, cwd = await realpath(absolute(options.cwd, "--cwd")); const config = { cwd, journal: options.journal ? absolute(options.journal, "--journal") : paths.journal, bin: options.bin ?? process.env.DD_OPENCODE_BIN ?? "opencode", provider: String(options.provider ?? ""), model: String(options.model ?? ""), variant: String(options.variant ?? "default"), agent: String(options.agent ?? "build"), projectRoot: noFlow ? cwd : await realpath(absolute(options.projectRoot, "--project-root")), ddFlowBin: noFlow ? null : absolute(options.ddFlowBin, "--dd-flow-bin"), ddFlowHome: noFlow ? null : absolute(options.ddFlowHome, "--dd-flow-home"), resourceHome: noFlow ? null : (process.env.DD_FLOW_RESOURCE_HOME ? absolute(process.env.DD_FLOW_RESOURCE_HOME, "DD_FLOW_RESOURCE_HOME") : null), env: noFlow ? {} : { DD_FLOW_HOME: absolute(options.ddFlowHome, "--dd-flow-home") }, sessionArchive: options.sessionArchive ? absolute(options.sessionArchive, "--session-archive") : null, dataHome: paths.data, configHome: paths.config, cacheHome: paths.cache, stateHome: paths.xdgState, database: paths.db, adapterDir: path.join(paths.config, "opencode"), configSource: options.configPath ?? process.env.DD_OPENCODE_CONFIG_SOURCE ?? path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"), "opencode", "opencode.json"), password: randomBytes(24).toString("hex"), daemonId, noFlow };
|
|
158
|
+
await prepareRuntimeOwner(config, paths.dir);
|
|
159
|
+
config.authSource = options.authPath ? absolute(options.authPath, "--auth-path") : path.join(os.homedir(), ".local", "share", "opencode", "auth.json");
|
|
160
|
+
if (!options.configPath && !process.env.DD_OPENCODE_CONFIG_SOURCE) {
|
|
161
|
+
try { await lstat(config.configSource); } catch (error) { if (error.code === "ENOENT") config.configSource += "c"; else throw error; }
|
|
162
|
+
}
|
|
163
|
+
config.configSource = absolute(config.configSource, "--config-path");
|
|
164
|
+
assertDaemonRestartable(previous);
|
|
165
|
+
if (!config.provider || !config.model) throw new DaemonError("profile_required", "--provider and --model are required");
|
|
166
|
+
try { const status = await callDaemon(paths.dir, "daemon.status", {}, 1000); if (!sameConfig(status, config)) throw new DaemonError("daemon_config_mismatch", "a daemon is already running with different configuration"); return { ...status, already_running: true }; } catch (error) { if (!["daemon_not_running","daemon_timeout"].includes(error.code)) throw error; }
|
|
167
|
+
assertDaemonRestartable(await readState(paths.dir));
|
|
168
|
+
if (previous?.shutdown_state === "clean") await authorizeRetainedDaemonResume(paths.dir, previous, options.sessionId, config); if (previous?.shutdown_state === "running" && previous.active_tree) throw new DaemonError("invalid_harness_crash", "previous daemon died with an active or unproven Session tree");
|
|
169
|
+
await removeSocket(paths); const authSource = config.authSource;
|
|
170
|
+
const version = (await run(config.bin, ["--version"], config)).trim(); config.version = version.match(/(?:^|\s)v?(2\.\d+\.\d+(?:[-+][\w.-]+)?)(?:\s|$)/)?.[1];
|
|
171
|
+
if (!config.version) throw new DaemonError("unsupported_version", "dd-opencode supports OpenCode v2 only; v1 remains at archive/opencode-v1-final-beta134");
|
|
172
|
+
await prepare(paths, config, authSource);
|
|
173
|
+
const resourceProcess = await registerDaemonProcess(config, { kind: "opencode-daemon", owner: `opencode:${path.basename(paths.dir)}`, operation: `opencode-daemon:${path.basename(paths.dir)}`, stdout: paths.log, stderr: paths.log });
|
|
174
|
+
const state = { schema_id: STATE_SCHEMA, native_protocol: 2, daemon_id: daemonId, shutdown_state: "starting", active_tree: false, auth_status: config.authStatus, versions: { opencode: config.version }, config, sessions: previous?.sessions ?? [], operations: previous?.operations ?? {}, resource_process: resourceProcess }; await writeJson(paths.state, state);
|
|
175
|
+
const log = await open(paths.log, "a", 0o600), entry = absolute(options.entryPath, "dd-opencode entry"); const child = spawn(process.execPath, [entry, "daemon", "serve", "--state-dir", paths.dir], { cwd: config.cwd, env: isolatedEnv(config), detached: true, stdio: ["ignore", log.fd, log.fd] }); child.unref(); await log.close();
|
|
176
|
+
try { await confirmDaemonProcess(config, resourceProcess, child); const startupClock = new ObservationClock({ timeoutMs: SETUP_STARTUP_MS }); const startupProgress = startupObserver(paths.dir, state.daemon_id); while (!startupClock.sample(await startupProgress())) {
|
|
177
|
+
assertStartupChild(child, state, await readState(paths.dir), paths.log);
|
|
178
|
+
await new Promise(resolve => setTimeout(resolve, 100)); try { return await callDaemon(paths.dir, "daemon.status", {}, 1000); } catch (error) { if (!["daemon_not_running","daemon_timeout"].includes(error.code)) throw error; }
|
|
179
|
+
} throw Object.assign(new DaemonError("daemon_start_failed", "daemon did not become ready", true), { details: observationDetails(startupClock, 'daemon-start', child.pid) }); }
|
|
180
|
+
catch (error) { await cleanupFailedDaemonStart(config, resourceProcess, child, error, () => readState(paths.dir), daemonId); throw error; }
|
|
181
|
+
}
|
|
182
|
+
export const stopDaemon = async options => { const timeoutMs = options.timeoutMs ?? 30_000; return await confirmDaemonStopped(() => callDaemon(options.stateDir, "daemon.stop", { cancelTree: options.cancelTree === true }, timeoutMs), locations(options.stateDir).socket, timeoutMs, { readState: () => readState(options.stateDir) }); };
|
|
183
|
+
|
|
184
|
+
export class Runtime {
|
|
185
|
+
constructor(paths, state, client, child, providerProcess) { this.paths = paths; this.state = state; this.client = client; this.child = child; this.providerProcess = providerProcess; this.sessions = new Map();
|
|
186
|
+
for (const record of state.sessions ?? []) {
|
|
187
|
+
if (typeof record?.provider_session_id !== "string" || !record.provider_session_id || this.sessions.has(record.provider_session_id))
|
|
188
|
+
throw new DaemonError("session_identity_mismatch", "OpenCode retained Session identity is invalid or duplicated");
|
|
189
|
+
this.sessions.set(record.provider_session_id, record);
|
|
190
|
+
}
|
|
191
|
+
this.active = null; this.persisting = Promise.resolve(); }
|
|
192
|
+
persist(patch = {}) { Object.assign(this.state, patch, { pid: process.pid, sessions: [...this.sessions.values()] }); const snapshot = structuredClone(this.state); this.persisting = this.persisting.catch(() => {}).then(() => writeJson(this.paths.state, snapshot)); return this.persisting; }
|
|
193
|
+
retainSession(info, root) {
|
|
194
|
+
const id = info.id, parent = info.fork ? null : info.parentID ?? null, retained = this.sessions.get(id);
|
|
195
|
+
if (typeof id !== "string" || !id || typeof info.directory !== "string" || !path.isAbsolute(info.directory)
|
|
196
|
+
|| retained && ((retained.parent_provider_session_id ?? null) !== parent
|
|
197
|
+
|| retained.adapter_session_id && retained.adapter_session_id !== id
|
|
198
|
+
|| retained.root_provider_session_id && retained.root_provider_session_id !== root))
|
|
199
|
+
throw new DaemonError("session_identity_mismatch", "OpenCode retained Session identity changed");
|
|
200
|
+
// Cwd is observed routing authority, not ownership identity: a moved owned
|
|
201
|
+
// Session must remain observable/cancellable. requireSettled fences dispatch.
|
|
202
|
+
const record = { provider_session_id: id, adapter_session_id: id, parent_provider_session_id: parent, root_provider_session_id: root, cwd: info.directory };
|
|
203
|
+
this.sessions.set(id, record); return record;
|
|
204
|
+
}
|
|
205
|
+
async journal(operation, result) { const line = `${JSON.stringify({ schema_id: "dd-opencode/daemon-event@1", event_id: randomUUID(), observed_at: new Date().toISOString(), operation, active_tree: this.state.active_tree, session_id: result?.provider_session_id ?? null })}\n`; for (const journal of new Set([this.paths.journal, this.state.config.journal].filter(Boolean))) { await mkdir(path.dirname(journal), { recursive: true, mode: 0o700 }); await appendFile(journal, line, { mode: 0o600 }); } }
|
|
206
|
+
async childTree(id, seen = new Set()) {
|
|
207
|
+
if (seen.has(id)) throw new DaemonError("session_identity_mismatch", "OpenCode native topology contains a cycle");
|
|
208
|
+
seen.add(id); const direct = await this.ownedChildren(id), nested = [];
|
|
209
|
+
for (const child of direct) {
|
|
210
|
+
if (!child.id || child.parentID !== id) throw new DaemonError("session_identity_mismatch", "OpenCode child has a foreign native parent");
|
|
211
|
+
nested.push(child, ...await this.childTree(child.id, seen));
|
|
212
|
+
}
|
|
213
|
+
return nested;
|
|
214
|
+
}
|
|
215
|
+
async ownedChildren(id) {
|
|
216
|
+
const direct = await this.client.children(id), children = new Map();
|
|
217
|
+
if (!Array.isArray(direct)) throw new DaemonError("native_child_observation_unavailable", "OpenCode child inventory is unavailable");
|
|
218
|
+
for (const child of direct) children.set(child.id, child);
|
|
219
|
+
for (const record of this.sessions.values()) if (record.parent_provider_session_id === id && !children.has(record.provider_session_id)) {
|
|
220
|
+
let child;
|
|
221
|
+
try { child = await this.client.inspect(record.provider_session_id); }
|
|
222
|
+
catch (cause) { throw new DaemonError("native_child_observation_unavailable", "Retained OpenCode child disappeared without completion proof", false, { provider_session_id: record.provider_session_id, cause: errorPayload(cause) }); }
|
|
223
|
+
if (child?.id !== record.provider_session_id) throw new DaemonError("session_identity_mismatch", "OpenCode retained child read returned another Session");
|
|
224
|
+
children.set(child.id, child);
|
|
225
|
+
}
|
|
226
|
+
for (const child of children.values()) if (!child.id || child.parentID !== id || child.fork) throw new DaemonError("session_identity_mismatch", "OpenCode child has a foreign native parent");
|
|
227
|
+
return [...children.values()];
|
|
228
|
+
}
|
|
229
|
+
async revision(id) {
|
|
230
|
+
if (!this.client.logs) return null;
|
|
231
|
+
const { watermark } = await this.client.logs(id);
|
|
232
|
+
return JSON.stringify([id, watermark]);
|
|
233
|
+
}
|
|
234
|
+
async describe(id, refreshes = 1) {
|
|
235
|
+
const before = new Map([[id, await this.revision(id)]]);
|
|
236
|
+
const [info, messages, children] = await Promise.all([this.client.inspect(id), this.client.messages(id), this.childTree(id)]);
|
|
237
|
+
const parent = info.fork ? null : info.parentID ?? null, existing = this.sessions.get(id);
|
|
238
|
+
if (info.id !== id || !existing && (!parent || !this.sessions.has(parent) || info.fork)
|
|
239
|
+
|| existing && (existing.parent_provider_session_id ?? null) !== parent) throw new DaemonError("foreign_session", "OpenCode Session is not in the retained owned tree");
|
|
240
|
+
const record = this.retainSession(info, parent ? this.sessions.get(parent)?.root_provider_session_id ?? parent : existing?.root_provider_session_id ?? id);
|
|
241
|
+
for (const child of children) {
|
|
242
|
+
const retained = this.sessions.get(child.id);
|
|
243
|
+
if (child.fork || retained && retained.parent_provider_session_id !== child.parentID) throw new DaemonError("session_identity_mismatch", "OpenCode child ancestry changed");
|
|
244
|
+
this.retainSession(child, record.root_provider_session_id);
|
|
245
|
+
}
|
|
246
|
+
const pending = new Map(), histories = new Map([[id, messages]]);
|
|
247
|
+
for (const child of children) before.set(child.id, await this.revision(child.id));
|
|
248
|
+
const statuses = await this.client.statuses();
|
|
249
|
+
for (const session of [id, ...children.map(child => child.id)]) {
|
|
250
|
+
if (session !== id) histories.set(session, await this.client.messages(session));
|
|
251
|
+
const value = this.client.pending ? await this.client.pending(session) : { inbox: [], forms: [], permissions: [] };
|
|
252
|
+
pending.set(session, { ...value, shells: this.client.shells ? (await this.client.shells(session)).filter(shell => shell.status === "running") : [] });
|
|
253
|
+
}
|
|
254
|
+
const changed = new Set(), unavailable = new Set();
|
|
255
|
+
for (const [session, seq] of before) {
|
|
256
|
+
const after = await this.revision(session);
|
|
257
|
+
if (seq === null || after === null) { unavailable.add(session); changed.add(session); }
|
|
258
|
+
else if (after !== seq) changed.add(session);
|
|
259
|
+
}
|
|
260
|
+
// Re-read one raced snapshot inside this request, without a new clock or
|
|
261
|
+
// quiet sleep. An absent fence has no reconciliation authority.
|
|
262
|
+
if (changed.size && !unavailable.size && refreshes > 0) return this.describe(id, refreshes - 1);
|
|
263
|
+
const currentMessages = session => { const history = histories.get(session); return history.slice(history.findLastIndex(message => message.info.role === "user") + 1); };
|
|
264
|
+
const busy = session => changed.has(session) || Boolean(statuses[session] && statuses[session].type !== "idle") || Object.values(pending.get(session)).some(items => items.length > 0)
|
|
265
|
+
|| currentMessages(session).some(message => (message.parts ?? []).some(part => part.type === "tool" && ["pending", "running", "streaming"].includes(part.state?.status)));
|
|
266
|
+
for (const child of children) {
|
|
267
|
+
const history = histories.get(child.id);
|
|
268
|
+
const index = history.findLastIndex(message => message.info.role === "user" && message.info.sessionID === child.id);
|
|
269
|
+
const terminal = index >= 0 && history.slice(index + 1).findLast(message => message.info.role === "idle" && message.info.sessionID === child.id);
|
|
270
|
+
if (!terminal || busy(child.id)) { Object.assign(child, { status: changed.has(child.id) ? "unknown" : busy(child.id) ? "running" : "idle", native_turn_items: { observed: Boolean(terminal) && !changed.has(child.id), possible_effects: true, pending: busy(child.id) } }); continue; }
|
|
271
|
+
const outcome = terminal.info.outcome;
|
|
272
|
+
if (!["succeeded", "failed", "interrupted"].includes(outcome)) continue;
|
|
273
|
+
Object.assign(child, { status: outcome === "succeeded" ? "completed" : outcome === "failed" ? "failed" : "cancelled",
|
|
274
|
+
terminal_turn_id: history[index].info.id, native_source: "opencode.v2.session.execution",
|
|
275
|
+
native_turn_items: { observed: true, possible_effects: true, pending: false },
|
|
276
|
+
...(terminal.info.error ? { provider_error: safeProviderError(terminal.info.error) } : {}) });
|
|
277
|
+
}
|
|
278
|
+
const unsettledChildren = children.filter(child => busy(child.id)).map(child => child.id), status = changed.has(id) ? "unknown" : busy(id) ? "busy" : "idle";
|
|
279
|
+
const lastAssistant = messages.findLast(message => message.info?.role === "assistant");
|
|
280
|
+
const inputIndex = messages.findLastIndex(message => message.info.role === "user");
|
|
281
|
+
const idle = messages.slice(inputIndex + 1).findLast(message => message.info.role === "idle");
|
|
282
|
+
const treeIds = [id, ...children.map(child => child.id)];
|
|
283
|
+
await this.observeModels(id, messages, parent);
|
|
284
|
+
this.journaledTools ??= new Map();
|
|
285
|
+
if (this.state.config.journal) journalToolObservations(this.state.config.journal, messages.flatMap(message => (message.parts ?? []).map(part => openCodeToolObservation(part, id, message.info?.id))), this.journaledTools);
|
|
286
|
+
const receipt = { ...record, observed_profile: { provider: lastAssistant?.info?.providerID ?? info.model?.providerID ?? null, model: lastAssistant?.info?.modelID ?? info.model?.id ?? null, reasoning: lastAssistant?.info?.variant ?? info.model?.variant ?? "default", mode: info.agent ?? null },
|
|
287
|
+
assistant_text: (lastAssistant?.parts ?? []).filter(part => part.type === "text").map(part => part.text ?? "").join(""), info, status,
|
|
288
|
+
route_conformance: { matches: [info, ...children].every(session => session.directory && path.resolve(session.directory) === path.resolve(this.state.config.cwd)), expected: this.state.config.cwd, actual: info.directory ?? null, actual_provenance: "native_session_readback" },
|
|
289
|
+
route_violations: [info, ...children].filter(session => !session.directory || path.resolve(session.directory) !== path.resolve(this.state.config.cwd)).map(session => ({ provider_session_id: session.id, expected: this.state.config.cwd, actual: session.directory ?? null, actual_provenance: "native_session_readback" })),
|
|
290
|
+
settled: status === "idle" && !unsettledChildren.length, unsettled_children: unsettledChildren,
|
|
291
|
+
tree_session_ids: treeIds, unsettled_sessions: treeIds.filter(busy),
|
|
292
|
+
tree_observations: treeIds.map(session => ({ provider_session_id: session, status: changed.has(session) ? "unknown" : busy(session) ? "running" : "idle", settled: !busy(session) })),
|
|
293
|
+
...(changed.size ? { native_settlement: { confirmed: false, state: "unproven", reason: "native_revision_fence_unavailable_or_changed" } } : {}),
|
|
294
|
+
terminal_turn_id: idle && inputIndex >= 0 ? messages[inputIndex].info.id : null,
|
|
295
|
+
terminal_status: idle?.info.outcome === "failed" ? "failed" : idle?.info.outcome === "succeeded" ? "completed" : idle?.info.outcome ?? null,
|
|
296
|
+
native_turn_items: { observed: Boolean(idle), possible_effects: messages.slice(inputIndex + 1).some(message => message.parts.some(part => part.type === "tool")),
|
|
297
|
+
pending: busy(id) },
|
|
298
|
+
children, pending: pending.get(id), usage: usageSnapshot(messages, id), history_sha256: historyDigest(messages), message_count: messages.length };
|
|
299
|
+
return nativeExecutionReceipt(receipt, { daemonId: this.state.daemon_id, revision: before.get(id) ?? 0 });
|
|
300
|
+
}
|
|
301
|
+
async observeModels(id, messages, parent = null) {
|
|
302
|
+
this.modelMessages ??= new Set();
|
|
303
|
+
for (const message of messages ?? []) {
|
|
304
|
+
const info = message.info;
|
|
305
|
+
if (info?.role !== "assistant" || !info.id || this.modelMessages.has(info.id)) continue;
|
|
306
|
+
if (info.sessionID && info.sessionID !== id) throw new DaemonError("session_identity_mismatch", "native message belongs to another Session");
|
|
307
|
+
await observeModel({ journal: this.state.config.journal ?? this.paths.journal, harness: "opencode-server", sessionId: id, parentSessionId: parent,
|
|
308
|
+
requested: { ...this.state.config, reasoning: this.state.config.variant, mode: parent ? "dd-flow-worker" : this.state.config.agent },
|
|
309
|
+
nativeAgentRole: this.state.config.workerLaunchConfig ? { name: info.mode ?? null, kind: parent ? 'subagent' : 'primary', parent_session_id: parent,
|
|
310
|
+
launch: { ...this.state.config.workerLaunchConfig, root_session_id: this.sessions.get(id)?.root_provider_session_id ?? null } } : null,
|
|
311
|
+
observed: { provider: info.providerID, model: info.modelID, reasoning: info.variant, mode: info.mode }, source: { method: "session/message", message_id: info.id, event_sha256: createHash("sha256").update(`${id}:${info.id}:${info.modelID}`).digest("hex") }, evidence: "response" });
|
|
312
|
+
this.modelMessages.add(info.id);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async requireSettled(id) {
|
|
316
|
+
const value = await this.describe(id);
|
|
317
|
+
if (!value.route_conformance.matches) throw new DaemonError("invocation_directory_mismatch", "Owned OpenCode Session has an unexpected workspace", false, { effect: "no_effect", ...value.route_violations[0], native_outcome: value.terminal_status, settled: value.settled });
|
|
318
|
+
if (!value.settled) throw new DaemonError("tree_not_settled", "OpenCode Session is not idle", false, { session_id: id, status: value.status });
|
|
319
|
+
const config = this.state.config;
|
|
320
|
+
if (config.provider && config.model) checkObservedProfile({ ...config, reasoning: config.variant, mode: value.parent_provider_session_id ? "dd-flow-worker" : config.agent }, value.observed_profile);
|
|
321
|
+
return value;
|
|
322
|
+
}
|
|
323
|
+
async reconcileUsage(observed) { const errors = []; try { await forwardUsage(this.state.config, observed.provider_session_id, observed.usage); } catch (error) { errors.push({ session_id: observed.provider_session_id, phase: "usage", ...errorPayload(error) }); } for (const child of observed.children.filter(item => (item.parentID ?? observed.provider_session_id) === observed.provider_session_id)) { try { errors.push(...await this.reconcileUsage(await this.describe(child.id))); } catch (error) { errors.push({ session_id: child.id, phase: "usage_child", ...errorPayload(error) }); } } return errors; }
|
|
324
|
+
async productive(name, task) {
|
|
325
|
+
assertDaemonDispatch(this.paths?.dir);
|
|
326
|
+
if (this.active) throw new DaemonError("operation_busy", `${this.active} is already running`, true);
|
|
327
|
+
this.active = name; let result, completed = false; const priorActive = this.state.active_tree;
|
|
328
|
+
try {
|
|
329
|
+
await this.persist({ active_tree: true, active_operation: name });
|
|
330
|
+
assertDaemonDispatch(this.paths?.dir);
|
|
331
|
+
result = await task(); completed = true;
|
|
332
|
+
await this.persist({ active_tree: !result?.settled, active_operation: null });
|
|
333
|
+
await this.journal(name, result); return result;
|
|
334
|
+
} catch (cause) {
|
|
335
|
+
const error = completed ? new DaemonError("operation_observation_lost", "OpenCode returned but its durable observation could not be saved; do not resend", false, { result, cause: errorPayload(cause) }) : cause;
|
|
336
|
+
const failures = [];
|
|
337
|
+
try { await this.persist({ active_tree: error.details?.effect === "no_effect" && error.details?.native_turn_items?.possible_effects !== true ? priorActive : true, active_operation: null }); } catch (secondary) { failures.push(errorPayload(secondary)); }
|
|
338
|
+
try { await this.journal(`${name}.failed`, null); } catch (secondary) { failures.push(errorPayload(secondary)); }
|
|
339
|
+
if (failures.length) error.details = { ...error.details, observation_errors: failures };
|
|
340
|
+
throw error;
|
|
341
|
+
} finally { this.active = null; }
|
|
342
|
+
}
|
|
343
|
+
async cancelTree(id, seen = new Set()) {
|
|
344
|
+
if (!this.sessions.has(id)) throw new DaemonError("foreign_session", "cannot cancel a Session without daemon ownership");
|
|
345
|
+
if (seen.has(id)) return [];
|
|
346
|
+
const retained = this.sessions.get(id), errors = [];
|
|
347
|
+
let native;
|
|
348
|
+
try { native = await this.client.inspect(id); }
|
|
349
|
+
catch (error) { errors.push({ session_id: id, phase: "inspect", ...errorPayload(error) }); }
|
|
350
|
+
if (native) {
|
|
351
|
+
if (native.id !== id || (retained.parent_provider_session_id ?? null) !== (native.fork ? null : native.parentID ?? null)) throw new DaemonError("session_identity_mismatch", "OpenCode cancellation Session ancestry changed");
|
|
352
|
+
try { this.retainSession(native, retained.root_provider_session_id ?? id); }
|
|
353
|
+
catch (error) { errors.push({ session_id: id, phase: "retain", ...errorPayload(error) }); }
|
|
354
|
+
}
|
|
355
|
+
seen.add(id);
|
|
356
|
+
let children = [];
|
|
357
|
+
try { children = await this.ownedChildren(id); } catch (error) { errors.push({ session_id: id, phase: "children", ...errorPayload(error) });
|
|
358
|
+
children = [...this.sessions.values()].filter(record => record.parent_provider_session_id === id).map(record => ({ id: record.provider_session_id, parentID: id, directory: record.cwd })); }
|
|
359
|
+
for (const child of children) {
|
|
360
|
+
if (child.parentID !== id || child.fork || this.sessions.has(child.id) && this.sessions.get(child.id).parent_provider_session_id !== id) {
|
|
361
|
+
errors.push({ session_id: child.id, phase: "identity", code: "session_identity_mismatch" }); continue;
|
|
362
|
+
}
|
|
363
|
+
try { this.retainSession(child, this.sessions.get(id)?.root_provider_session_id ?? id); errors.push(...await this.cancelTree(child.id, seen)); }
|
|
364
|
+
catch (error) { errors.push({ session_id: child.id, phase: "cancel_child", ...errorPayload(error) }); }
|
|
365
|
+
}
|
|
366
|
+
try { await this.client.abort(id); } catch (error) { errors.push({ session_id: id, phase: "abort", ...errorPayload(error) }); }
|
|
367
|
+
return errors;
|
|
368
|
+
}
|
|
369
|
+
async observeHookFailures(current = this.nativeHookOperation) {
|
|
370
|
+
if (!current || this.nativeHookOperation !== current) return;
|
|
371
|
+
if (current.reading) return await current.reading;
|
|
372
|
+
current.reading = readNativeHookFailures(this.paths.dir, current.requests, current, error => this.retainHookFailure(current, error));
|
|
373
|
+
try { await current.reading; } finally { current.reading = null; }
|
|
374
|
+
}
|
|
375
|
+
retainHookFailure(current, error) {
|
|
376
|
+
if (this.nativeHookOperation !== current || isProvenShellCorrection(error)) return;
|
|
377
|
+
error.details = { ...error.details, provider_session_id: current.sessionId, native_outcome: "unknown", native_turn_items: { observed: true, possible_effects: true, pending: true } };
|
|
378
|
+
current.failure ??= error;
|
|
379
|
+
}
|
|
380
|
+
async dispatch(operation, params, operationId = null) {
|
|
381
|
+
const assertDispatch = captureDispatchGuard(this);
|
|
382
|
+
if (["session.create", "session.prompt", "session.start", "session.fork", "session.resume", "session.archive"].includes(operation)) assertDaemonDispatch(this.paths.dir);
|
|
383
|
+
if (operation === "daemon.status") { const { password, ...config } = this.state.config; return { daemon_id: this.state.daemon_id, pid: process.pid, socket: this.paths.socket, versions: this.state.versions, auth_status: this.state.auth_status, shutdown_state: this.state.shutdown_state, active_tree: this.state.active_tree, active_operation: this.active, sessions: [...this.sessions.values()], config }; }
|
|
384
|
+
if (operation === "hook.resolve") {
|
|
385
|
+
const current = this.nativeHookOperation, sessionId = params.sessionId, payload = params.payload;
|
|
386
|
+
if (!current || !sessionId || typeof payload?.input?.command !== "string" || typeof payload.tool_call_id !== "string" || !payload.tool_call_id || typeof payload.message_id !== "string" || !payload.message_id || !["before", "after"].includes(payload.phase))
|
|
387
|
+
throw new DaemonError("native_hook_unproven", "OpenCode hook has no exact active operation");
|
|
388
|
+
let owned = sessionId === current.sessionId || this.sessions.get(sessionId)?.root_provider_session_id === current.sessionId;
|
|
389
|
+
try {
|
|
390
|
+
const tree = await this.childTree(current.sessionId);
|
|
391
|
+
if (sessionId !== current.sessionId && !tree.some(child => child.id === sessionId && !child.fork)) throw new DaemonError("foreign_session", "OpenCode hook Session is outside the owned native tree");
|
|
392
|
+
owned = true;
|
|
393
|
+
const native = await this.client.inspect(sessionId), retained = this.sessions.get(sessionId), child = tree.find(item => item.id === sessionId);
|
|
394
|
+
if (native.id !== sessionId || child && (native.parentID !== child.parentID || native.fork) || retained && (retained.parent_provider_session_id ?? null) !== (native.fork ? null : native.parentID ?? null)) throw new DaemonError("native_hook_identity_conflict", "OpenCode hook Session ancestry changed");
|
|
395
|
+
if (!native.directory) throw new DaemonError("native_hook_unproven", "OpenCode hook Session directory is unavailable");
|
|
396
|
+
if (payload.input.workdir !== undefined && typeof payload.input.workdir !== "string") throw new DaemonError("native_hook_unproven", "OpenCode hook workdir must be a string");
|
|
397
|
+
if (this.nativeHookOperation !== current) throw new DaemonError("native_hook_unproven", "OpenCode operation changed during hook identity observation");
|
|
398
|
+
const accepted = await acceptNativeHookRequest(this.paths.dir, { daemon_id: this.state.daemon_id, operation_id: current.operationId, turn_generation: current.generation,
|
|
399
|
+
provider_session_id: sessionId, root_provider_session_id: current.sessionId, tool_call_id: `${payload.message_id}:${payload.tool_call_id}`, event: payload.phase === "after" ? "PostToolUse" : "PreToolUse",
|
|
400
|
+
command_sha256: createHash("sha256").update(payload.input.command).digest("hex"), native_cwd: path.resolve(native.directory, payload.input.workdir ?? ".") }, current);
|
|
401
|
+
if (this.nativeHookOperation !== current) throw new DaemonError("native_hook_unproven", "OpenCode operation changed during hook acceptance");
|
|
402
|
+
current.requests.set(accepted.request_id, { current, binding: accepted }); return { nativeHookBinding: accepted, nativeSession: {
|
|
403
|
+
id: native.id, directory: native.directory, parentID: native.parentID, projectID: native.projectID,
|
|
404
|
+
model: { providerID: native.model?.providerID, id: native.model?.id, variant: native.model?.variant }
|
|
405
|
+
} };
|
|
406
|
+
} catch (error) {
|
|
407
|
+
if (owned) this.retainHookFailure(current, error);
|
|
408
|
+
throw error;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (operation === "hook.verify") {
|
|
412
|
+
const current = this.nativeHookOperation, binding = params.binding;
|
|
413
|
+
if (!current || binding?.operation_id !== current.operationId || binding?.turn_generation !== current.generation
|
|
414
|
+
|| JSON.stringify(current.requests.get(binding?.request_id)?.binding) !== JSON.stringify(binding)) throw new DaemonError("native_hook_unproven", "OpenCode hook generation is no longer current");
|
|
415
|
+
return { current: true };
|
|
416
|
+
}
|
|
417
|
+
if (operation === "hook.failed") {
|
|
418
|
+
if (params.failure) {
|
|
419
|
+
await this.dispatch("hook.verify", { binding: params.binding });
|
|
420
|
+
if (JSON.stringify(params.failure.details?.native_hook_binding) !== JSON.stringify(params.binding) || !params.failure.details?.receipt_error || typeof params.failure.code !== "string" || typeof params.failure.message !== "string") throw new DaemonError("native_hook_receipt_invalid", "OpenCode fallback hook error is not bound");
|
|
421
|
+
this.retainHookFailure(this.nativeHookOperation, reportedError(params.failure, "Native hook failed"));
|
|
422
|
+
}
|
|
423
|
+
await this.observeHookFailures(); return { observed: true };
|
|
424
|
+
}
|
|
425
|
+
if (operation === "daemon.doctor") {
|
|
426
|
+
const info = await this.client.health();
|
|
427
|
+
if (info.pid !== this.child?.pid || info.version !== this.state.versions.opencode) throw new DaemonError("foreign_server", "OpenCode endpoint is not the owned process");
|
|
428
|
+
await this.client.call("model.list", { location: { directory: this.state.config.cwd } });
|
|
429
|
+
const plugins = await this.client.call("plugin.list", { location: { directory: this.state.config.cwd } });
|
|
430
|
+
const pluginID = this.state.config.noFlow ? "dd-flow-security" : "dd-flow";
|
|
431
|
+
if (!plugins.data.some(plugin => plugin.id === pluginID && plugin.state?.status === "active")) throw new DaemonError("opencode_plugin_missing", "Owned v2 plugin is not active");
|
|
432
|
+
return { compatible: true, native_protocol: 2, observed_runtime: this.state.versions, productive_hooks: !this.state.config.noFlow, versions: this.state.versions, plugin_sha256: this.state.config.pluginSha256, auth_status: this.state.auth_status };
|
|
433
|
+
}
|
|
434
|
+
if (operation === "session.create") return this.productive(operation, async () => {
|
|
435
|
+
const info = await this.client.create(params.title, this.state.config);
|
|
436
|
+
const created = { provider_session_id: info.id, adapter_session_id: info.id, parent_provider_session_id: info.parentID ?? null, root_provider_session_id: info.id, cwd: info.directory };
|
|
437
|
+
this.sessions.set(info.id, created);
|
|
438
|
+
try {
|
|
439
|
+
const result = await this.describe(info.id);
|
|
440
|
+
result.usage_ingest_errors = await this.reconcileUsage(result);
|
|
441
|
+
return result;
|
|
442
|
+
} catch (error) {
|
|
443
|
+
// The Session exists even if its optional post-create observation is
|
|
444
|
+
// temporarily unavailable. Preserve the identity; a later inspect
|
|
445
|
+
// owns any profile/topology proof required before a prompt.
|
|
446
|
+
return { ...created, settled: false, settlement_observation_error: errorPayload(error) };
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
if (["session.inspect", "session.resume", "session.prompt", "session.fork", "session.archive"].includes(operation) && !this.sessions.has(params.sessionId)) {
|
|
450
|
+
const native = await this.client.inspect(params.sessionId);
|
|
451
|
+
if (!native.parentID || !this.sessions.has(native.parentID) || native.fork) throw new DaemonError("foreign_session", "Session is not a member of the retained owned tree");
|
|
452
|
+
}
|
|
453
|
+
if (operation === "session.inspect" || operation === "session.resume") { const result = await this.describe(params.sessionId); await this.persist(); return result; }
|
|
454
|
+
if (operation === "session.prompt") { await this.requireSettled(params.sessionId); return this.productive(operation, async () => {
|
|
455
|
+
this.state.operations ??= {}; const key = operationId ?? randomUUID(), retained = this.state.operations[key];
|
|
456
|
+
const current = { operationId: key, sessionId: params.sessionId, generation: (this.state.hook_generation ?? 0) + 1, requests: new Map(), failure: null };
|
|
457
|
+
this.state.hook_generation = current.generation; this.nativeHookOperation = current;
|
|
458
|
+
const checkHookFailures = async () => { await this.observeHookFailures(current); if (this.nativeHookOperation !== current) throw new DaemonError("native_hook_unproven", "OpenCode prompt generation changed"); if (current.failure) throw current.failure; };
|
|
459
|
+
let reply;
|
|
460
|
+
try { assertDispatch(); reply = await this.client.prompt(params.sessionId, { prompt: params.prompt, messageId: retained?.message_id ?? `msg_${randomUUID().replaceAll("-", "")}`, retained,
|
|
461
|
+
save: async receipt => { this.state.operations[key] = receipt; await this.persist(); },
|
|
462
|
+
assertDispatch, checkHookFailures, livenessTimeoutMs: params.livenessTimeoutMs ?? this.client.timeoutMs,
|
|
463
|
+
onDispatched: () => this.journal("native_dispatch", { operation_id: operationId, provider_session_id: params.sessionId }),
|
|
464
|
+
onModelMessages: (id, messages, parent) => this.observeModels(id, messages, parent),
|
|
465
|
+
onObservationError: error => observeModel({ journal: this.state.config.journal ?? this.paths.journal, harness: "opencode-server", sessionId: params.sessionId, requested: this.state.config, source: "native.message_observer", evidence: "unavailable", reason: error.code ?? "observation_unavailable" })
|
|
466
|
+
}); await checkHookFailures(); }
|
|
467
|
+
finally { current.requests.clear(); if (this.nativeHookOperation === current) this.nativeHookOperation = null; }
|
|
468
|
+
const assistant_text = reply.parts.filter(part => part.type === "text").map(part => part.text ?? "").join("");
|
|
469
|
+
const expectedAgent = this.sessions.get(params.sessionId)?.parent_provider_session_id ? "dd-flow-worker" : this.state.config.agent;
|
|
470
|
+
const reply_profile = { provider: reply.info.providerID ?? null, model: reply.info.modelID ?? null, reasoning: reply.info.variant ?? null, mode: reply.info.mode ?? null };
|
|
471
|
+
checkObservedProfile({ ...this.state.config, reasoning: this.state.config.variant, mode: expectedAgent }, reply_profile);
|
|
472
|
+
let result;
|
|
473
|
+
try { result = await this.describe(params.sessionId); }
|
|
474
|
+
catch (error) { return { provider_session_id: params.sessionId, adapter_session_id: params.sessionId, assistant_text, response_message_id: reply.info.id, terminal_turn_id: reply.admission_message_id, terminal_status: "completed", native_outcome: "succeeded", observed_profile: reply_profile, settled: false, settlement_observation_error: errorPayload(error) }; }
|
|
475
|
+
result.assistant_text = assistant_text;
|
|
476
|
+
if (!result.route_conformance.matches) throw new DaemonError("invocation_directory_mismatch", "Owned OpenCode Session moved during execution", false, { ...result.route_violations[0], native_outcome: result.terminal_status, settled: result.settled, result });
|
|
477
|
+
result.response_message_id = reply.info.id;
|
|
478
|
+
result.observed_profile = { ...reply_profile, mode: result.info.agent ?? reply_profile.mode };
|
|
479
|
+
checkObservedProfile({ ...this.state.config, reasoning: this.state.config.variant, mode: expectedAgent }, result.observed_profile); result.usage_ingest_errors = await this.reconcileUsage(result); return result; }); }
|
|
480
|
+
if (operation === "session.fork") { const source = await this.requireSettled(params.sessionId); return this.productive(operation, async () => {
|
|
481
|
+
const info = await this.client.fork(params.sessionId, params.messageId);
|
|
482
|
+
const created = { provider_session_id: info.id, adapter_session_id: info.id, parent_provider_session_id: info.fork ? null : info.parentID ?? params.sessionId, root_provider_session_id: info.fork ? info.id : this.sessions.get(params.sessionId)?.root_provider_session_id ?? params.sessionId, cwd: info.directory };
|
|
483
|
+
this.sessions.set(info.id, created);
|
|
484
|
+
try {
|
|
485
|
+
const fork = await this.describe(info.id);
|
|
486
|
+
if (info.fork?.sessionID !== params.sessionId || params.messageId && info.fork?.boundary?.messageID !== params.messageId) throw new DaemonError("fork_continuity_failed", "OpenCode did not confirm the requested native fork boundary");
|
|
487
|
+
return { ...fork, fork_receipt: { source_provider_session_id: params.sessionId, source_history_sha256: source.history_sha256, fork_history_sha256: fork.history_sha256, boundary: info.fork.boundary, native_boundary_proven: true } };
|
|
488
|
+
} catch (error) {
|
|
489
|
+
// A native fork is materialized already. Do not lose its identity or
|
|
490
|
+
// retry it, but never let an unproven history prefix become usable.
|
|
491
|
+
return { ...created, settled: false, fork_receipt: { source_provider_session_id: params.sessionId, source_history_sha256: source.history_sha256, history_prefix_proven: false }, fork_continuity_error: errorPayload(error) };
|
|
492
|
+
}
|
|
493
|
+
}); }
|
|
494
|
+
if (operation === "session.cancel") {
|
|
495
|
+
if (!this.sessions.has(params.sessionId)) throw new DaemonError("foreign_session", "cannot cancel a Session without daemon ownership");
|
|
496
|
+
cancelPendingDispatch(this);
|
|
497
|
+
const cancellation_errors = await this.cancelTree(params.sessionId); let result;
|
|
498
|
+
try { result = await this.describe(params.sessionId); }
|
|
499
|
+
catch (error) { result = { provider_session_id: params.sessionId, settled: false, inspection_error: errorPayload(error) }; }
|
|
500
|
+
result = { ...result, cancellation_errors };
|
|
501
|
+
await this.persist({ active_tree: Boolean(this.active) || !result.settled }); await this.journal(operation, result); return result;
|
|
502
|
+
}
|
|
503
|
+
if (operation === "session.archive") { const observed = await this.requireSettled(params.sessionId); const sessions = [];
|
|
504
|
+
for (const id of [params.sessionId, ...observed.children.map(child => child.id)]) sessions.push(await this.client.export(id));
|
|
505
|
+
const payload = JSON.stringify({ sessions }); const manifest = { schema_id: ARCHIVE_SCHEMA, native_protocol: 2, provider_session_id: params.sessionId, source_cwd: observed.cwd, opencode_version: this.state.versions.opencode, history_sha256: observed.history_sha256, exported_at: new Date().toISOString(), payload: Buffer.from(payload).toString("base64") }; const file = absolute(params.archiveFile, "--archive-file"); await mkdir(path.dirname(file), { recursive: true, mode: 0o700 }); await writeJson(file, manifest); return { archive_file: file, ...manifest, payload: undefined, sha256: createHash("sha256").update(await readFile(file)).digest("hex") }; }
|
|
506
|
+
if (operation === "daemon.stop") {
|
|
507
|
+
return await completeDaemonShutdown(this.paths.dir, this.state, patch => this.persist(patch), async () => {
|
|
508
|
+
if (params.cancelTree) cancelPendingDispatch(this);
|
|
509
|
+
await waitForSettlement({
|
|
510
|
+
knownSessions: () => [...this.sessions.keys()],
|
|
511
|
+
observe: async () => {
|
|
512
|
+
const busy = [];
|
|
513
|
+
for (const session of this.sessions.values()) if (!(await this.describe(session.provider_session_id)).settled) busy.push(session.provider_session_id);
|
|
514
|
+
return { sessions: busy.reverse(), active: Boolean(this.active), active_operation: this.active };
|
|
515
|
+
},
|
|
516
|
+
...(params.cancelTree ? { cancel: id => this.cancelTree(id) } : {})
|
|
517
|
+
});
|
|
518
|
+
// Fresh native observation, not a prior productive result, owns the idle proof.
|
|
519
|
+
commitDaemonShutdown(this.paths.dir);
|
|
520
|
+
await this.persist({ active_tree: false });
|
|
521
|
+
return { clean: true, settled: true, native_settled: true };
|
|
522
|
+
}, [
|
|
523
|
+
["provider", () => stopProcessGroup(this.child)],
|
|
524
|
+
["provider_resource", () => finishDaemonProcess(this.state.config, this.providerProcess)],
|
|
525
|
+
["daemon_resource", () => finishDaemonProcess(this.state.config, this.state.resource_process)]
|
|
526
|
+
]);
|
|
527
|
+
}
|
|
528
|
+
throw new DaemonError("unknown_operation", `unknown daemon operation: ${operation}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/** Read-only readiness probes share the startup quiet window. A repeated
|
|
533
|
+
* ServiceUnavailable response is not activation progress. */
|
|
534
|
+
export async function awaitOpenCodeStartup(client, state, child, { timeoutMs = SETUP_STARTUP_MS, publish = async () => {} } = {}) {
|
|
535
|
+
const clock = new ObservationClock({ timeoutMs });
|
|
536
|
+
let phase = "server", attempt = 0; const observedPhases = new Set();
|
|
537
|
+
const mark = async value => {
|
|
538
|
+
if (state.startup_phase === value) return;
|
|
539
|
+
state.startup_phase = value; await publish(state);
|
|
540
|
+
if (!observedPhases.has(value)) { observedPhases.add(value); clock.sample(value); }
|
|
541
|
+
};
|
|
542
|
+
const remaining = () => {
|
|
543
|
+
if (clock.sample()) throw new DaemonError("daemon_start_failed", "OpenCode startup made no further progress", false, observationDetails(clock, "native-readiness", child?.pid));
|
|
544
|
+
if (child && (child.exitCode !== null || child.signalCode !== null)) throw new DaemonError("opencode_unavailable", "Owned OpenCode process exited during startup", false, { exit_code: child.exitCode, signal: child.signalCode });
|
|
545
|
+
return Math.max(1, Math.ceil(clock.timeoutMs - clock.elapsed));
|
|
546
|
+
};
|
|
547
|
+
client.readObservation = () => ({ timeoutMs: remaining() });
|
|
548
|
+
try {
|
|
549
|
+
while (true) {
|
|
550
|
+
remaining();
|
|
551
|
+
try {
|
|
552
|
+
if (phase === "server") {
|
|
553
|
+
const identity = await client.health({ timeoutMs: remaining() });
|
|
554
|
+
if (identity.pid !== child?.pid || identity.version !== state.versions.opencode) throw new DaemonError("foreign_server", "OpenCode startup returned another server");
|
|
555
|
+
await mark("owned_server_ready"); phase = "location";
|
|
556
|
+
}
|
|
557
|
+
if (phase === "location") {
|
|
558
|
+
await client.call("model.list", { location: { directory: state.config.cwd } });
|
|
559
|
+
await mark("location_models_ready"); phase = "plugin";
|
|
560
|
+
}
|
|
561
|
+
const plugins = await client.call("plugin.list", { location: { directory: state.config.cwd } });
|
|
562
|
+
if (!Array.isArray(plugins?.data)) throw new DaemonError("opencode_configuration_invalid", "OpenCode returned invalid plugin readiness");
|
|
563
|
+
const id = state.config.noFlow ? "dd-flow-security" : "dd-flow", plugin = plugins.data.find(value => value.id === id);
|
|
564
|
+
if (["failed", "error", "missing", "disabled"].includes(plugin?.state?.status)) throw new DaemonError("opencode_plugin_failed", "Required owned plugin failed activation", false, { plugin_id: id, status: plugin.state.status });
|
|
565
|
+
if (plugin?.state?.status === "active") { await mark("required_plugin_active"); return { remaining, clock }; }
|
|
566
|
+
// A status transition of this required plugin is real new evidence.
|
|
567
|
+
if (["loading", "activating", "registered"].includes(plugin?.state?.status)) await mark(`required_plugin_${plugin.state.status}`);
|
|
568
|
+
} catch (error) {
|
|
569
|
+
const transient = error.code === "opencode_unavailable" && error.retryable === true
|
|
570
|
+
// Safe pre-identity reads use the normalized observation category,
|
|
571
|
+
// not a version-specific SDK error wrapper. No productive RPC here.
|
|
572
|
+
&& (phase === "server" || error.details?.native_type === "ServiceUnavailableError");
|
|
573
|
+
if (!transient) throw error;
|
|
574
|
+
}
|
|
575
|
+
const pause = Math.min([250, 500, 1000, 2000][Math.min(attempt++, 3)], remaining());
|
|
576
|
+
await new Promise(resolve => setTimeout(resolve, pause));
|
|
577
|
+
}
|
|
578
|
+
} catch (error) { delete client.readObservation; throw error; }
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
export async function serveDaemon(stateDir) {
|
|
582
|
+
const paths = locations(stateDir), state = await readState(paths.dir); if (state?.schema_id !== STATE_SCHEMA) throw new DaemonError("daemon_state_missing", "daemon state is missing");
|
|
583
|
+
let child, providerProcess;
|
|
584
|
+
try {
|
|
585
|
+
const port = await freePort(), target = executable(state.config.bin, ["serve", "--hostname", "127.0.0.1", "--port", String(port)]);
|
|
586
|
+
providerProcess = await registerDaemonProcess(state.config, { kind: "opencode-provider", owner: `opencode:${path.basename(paths.dir)}`, operation: `opencode-provider:${path.basename(paths.dir)}`, stdout: paths.log, stderr: paths.log });
|
|
587
|
+
child = spawn(target.command, target.args, { cwd: state.config.cwd, env: isolatedEnv(state.config), detached: true, stdio: ["ignore", "inherit", "inherit"] });
|
|
588
|
+
await confirmDaemonProcess(state.config, providerProcess, child);
|
|
589
|
+
scheduleDaemonProcessHeartbeat(state.config, state.resource_process);
|
|
590
|
+
scheduleDaemonProcessHeartbeat(state.config, providerProcess);
|
|
591
|
+
state.provider_process = providerProcess; state.provider_pid = child.pid; await writeJson(paths.state, state);
|
|
592
|
+
const client = new OpenCodeClient({ baseUrl: `http://127.0.0.1:${port}`, password: state.config.password, directory: state.config.cwd });
|
|
593
|
+
const startup = await awaitOpenCodeStartup(client, state, child, { publish: snapshot => writeJson(paths.state, snapshot) });
|
|
594
|
+
state.auth_status = await bootstrapCredentials(state.config, client);
|
|
595
|
+
state.startup_phase = "credentials_ready"; await writeJson(paths.state, state); startup.clock.sample(state.startup_phase);
|
|
596
|
+
const imported = await importArchive(state.config, client, async id => { state.startup_phase = `session_imported:${id}`; await writeJson(paths.state, state); startup.clock.sample(state.startup_phase); }); if (imported) state.sessions.push(...imported);
|
|
597
|
+
startup.remaining(); delete client.readObservation;
|
|
598
|
+
await chmod(state.config.database, 0o600);
|
|
599
|
+
const runtime = new Runtime(paths, state, client, child, providerProcess); await removeSocket(paths);
|
|
600
|
+
const server = net.createServer(connection => {
|
|
601
|
+
connection.setEncoding("utf8"); let buffer = "", consumed = false;
|
|
602
|
+
connection.on("data", chunk => {
|
|
603
|
+
if (consumed) return;
|
|
604
|
+
buffer += chunk; if (!admitNativeRequestBuffer(buffer, connection, RESPONSE_SCHEMA)) { consumed = true; return; } const at = buffer.indexOf("\n"); if (at < 0) return; consumed = true;
|
|
605
|
+
void (async () => {
|
|
606
|
+
let request;
|
|
607
|
+
try {
|
|
608
|
+
request = JSON.parse(buffer.slice(0, at));
|
|
609
|
+
const result = await durableDaemonDispatch(paths.dir, request, () => runtime.dispatch(request.operation, request.params ?? {}, request.id), () => runtime.dispatch("session.inspect", request.params ?? {}));
|
|
610
|
+
if (result._shutdown) retireDaemonEndpoint(server);
|
|
611
|
+
connection.end(`${JSON.stringify({ schema_id: RESPONSE_SCHEMA, id: request.id, ok: true, result: { ...result, _shutdown: undefined } })}\n`);
|
|
612
|
+
} catch (error) {
|
|
613
|
+
await retireFailedDaemon(server, state, error, patch => runtime.persist(patch));
|
|
614
|
+
connection.end(`${JSON.stringify({ schema_id: RESPONSE_SCHEMA, id: request?.id ?? null, ok: false, error: errorPayload(error) })}\n`);
|
|
615
|
+
}
|
|
616
|
+
})();
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
trackDaemonConnections(server); runtime.server = server;
|
|
620
|
+
await new Promise((resolve, reject) => { server.once("error", reject); server.listen(paths.socket, resolve); }); await chmod(paths.socket, 0o600); await runtime.persist({ shutdown_state: "running", active_tree: false, server_port: port }); return await new Promise(() => {});
|
|
621
|
+
} catch (error) {
|
|
622
|
+
await cleanupFailedStart(state.config, providerProcess, child, error);
|
|
623
|
+
await retainStartupFailure({ state, publish: snapshot => writeJson(paths.state, snapshot), log: paths.log, error }); throw error;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
import { authorizeRetainedDaemonResume } from "./driver-recovery.mjs";
|
|
627
|
+
import { commandText } from "./process-json.mjs";
|