@deksden-com/dd-flow-cli 0.9.0-beta.14 → 0.9.0-beta.140
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +733 -0
- package/README.md +143 -1
- package/dist/build-info.json +8 -8
- package/dist/cli/argument-values.js +38 -0
- package/dist/cli/command-inputs.js +376 -0
- package/dist/cli/command-metadata.js +197 -0
- package/dist/cli/command-option-descriptions.js +191 -0
- package/dist/cli/completion-cli.js +70 -0
- package/dist/cli/completion.js +57 -0
- package/dist/cli/error-output.js +52 -0
- package/dist/cli/executor-onboarding.js +76 -0
- package/dist/cli/flows.js +29 -0
- package/dist/cli/harnesses.js +84 -0
- package/dist/cli/help.js +328 -18
- package/dist/cli/hook-ingress.js +78 -0
- package/dist/cli/input-preparation.js +165 -0
- package/dist/cli/memory-flow-admission.js +44 -0
- package/dist/cli/memory-init-invocation.js +78 -0
- package/dist/cli/memory-init.js +240 -0
- package/dist/cli/memory-storage-admission.js +53 -0
- package/dist/cli/models.js +39 -0
- package/dist/cli/native-hook-ingress.js +80 -0
- package/dist/cli/operator-profiles.js +61 -0
- package/dist/cli/output-options.js +35 -0
- package/dist/cli/response-file.js +71 -0
- package/dist/cli/run-cli.js +1689 -545
- package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
- package/dist/cli/runtime-maintenance-ingress.js +52 -0
- package/dist/cli/runtime-process.js +153 -0
- package/dist/cli/setup.js +149 -0
- package/dist/cli.js +71 -2
- package/dist/domain/stage-catalog.js +1 -1
- package/dist/domain/stage-handler-identities.js +20 -0
- package/dist/harness-runtime/DD-ZCODE.md +112 -0
- package/dist/harness-runtime/OPENCODE-LICENSE.txt +24 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +56 -0
- package/dist/harness-runtime/bin/dd-codex.mjs +27 -0
- package/dist/harness-runtime/bin/dd-droid.mjs +34 -0
- package/dist/harness-runtime/bin/dd-grok.mjs +40 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +22 -0
- package/dist/harness-runtime/bin/dd-zcode.mjs +87 -0
- package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
- package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
- package/dist/harness-runtime/lib/agy-hooks.d.mts +1 -0
- package/dist/harness-runtime/lib/agy-hooks.mjs +9 -0
- package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
- package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
- package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +371 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +724 -0
- package/dist/harness-runtime/lib/dd-agy.mjs +70 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +196 -0
- package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +817 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +129 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +584 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +498 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +177 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +507 -0
- package/dist/harness-runtime/lib/dd-opencode.mjs +354 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +750 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +1145 -0
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +14 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +157 -0
- package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +139 -0
- package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
- package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
- package/dist/harness-runtime/lib/lease-renewal.mjs +37 -0
- package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
- package/dist/harness-runtime/lib/lifecycle-command.mjs +273 -0
- package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
- package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
- package/dist/harness-runtime/lib/managed-daemon.d.mts +13 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +654 -0
- package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
- package/dist/harness-runtime/lib/model-observations.mjs +101 -0
- package/dist/harness-runtime/lib/native-children.d.mts +16 -0
- package/dist/harness-runtime/lib/native-children.mjs +113 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +7 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +251 -0
- package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
- package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
- package/dist/harness-runtime/lib/native-streams.mjs +45 -0
- package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
- package/dist/harness-runtime/lib/observation-clock.mjs +92 -0
- package/dist/harness-runtime/lib/opencode-plugin.mjs +85 -0
- package/dist/harness-runtime/lib/operation-context.mjs +5 -0
- package/dist/harness-runtime/lib/operation-errors.d.mts +14 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +116 -0
- package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
- package/dist/harness-runtime/lib/operation-progress.mjs +114 -0
- package/dist/harness-runtime/lib/process-json.d.mts +5 -0
- package/dist/harness-runtime/lib/process-json.mjs +201 -0
- package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
- package/dist/harness-runtime/lib/runner-events.mjs +200 -0
- package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
- package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
- package/dist/harness-runtime/lib/setup-progress.d.mts +9 -0
- package/dist/harness-runtime/lib/setup-progress.mjs +51 -0
- package/dist/harness-runtime/lib/tool-observations.d.mts +21 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +228 -0
- package/dist/harness-runtime/lib/zcode-usage.mjs +55 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-1y4a697q.js +81 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-2ny4vxct.js +92 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-6mptkzbc.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-7x67cf1q.js +52 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9rqn6x4v.js +16 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9vdz0zwn.js +1661 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ecs5ftka.js +1466 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-frbwqjmf.js +145 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gne7h7mq.js +50 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gx9wnkzr.js +105 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-m2eywc8w.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-nwcpakzx.js +12 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-q4g3afnw.js +623 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-qav6mfdz.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-sxt88q69.js +12 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-t1xa31vm.js +39 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ve4aws36.js +103 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vmytr4q8.js +4 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vt9jbch8.js +36 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vyas3vgt.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-wjyeq25y.js +30 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-xgnj2w4v.js +20 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-y2etg5bs.js +9 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ydhz1zrh.js +238 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-z6qdwcsf.js +62 -0
- package/dist/harness-runtime/vendor/opencode-client/contract.d.ts +1 -0
- package/dist/harness-runtime/vendor/opencode-client/contract.js +12 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/api/api.d.ts +2806 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/api/api.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/api.d.ts +4 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/api.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/client.d.ts +2530 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/client.js +15 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.d.ts +7 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.js +6 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.d.ts +2529 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.js +8 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.d.ts +2 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.js +12 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/index.d.ts +35 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/index.js +75 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/rpc.d.ts +20 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/rpc.js +11 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/service.d.ts +90 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/service.js +187 -0
- package/dist/harness-runtime/vendor/opencode-client/package.json +84 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/api.d.ts +21 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/api.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/client.d.ts +252 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/client.js +14 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.d.ts +8 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.js +6 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.d.ts +259 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.js +7 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.d.ts +3 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.js +79 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.d.ts +8932 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.js +70 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/index.d.ts +6 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/index.js +83 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/rpc.d.ts +32 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/rpc.js +7 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/service.d.ts +24 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/service.js +187 -0
- package/dist/harness-runtime/vendor/opencode-client/pty-handoff.d.ts +12 -0
- package/dist/harness-runtime/vendor/opencode-client/pty-handoff.js +14 -0
- package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.d.ts +20 -0
- package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.js +14 -0
- package/dist/harness-runtime/vendor/opencode-client/service-contender.d.ts +37 -0
- package/dist/harness-runtime/vendor/opencode-client/service-contender.js +10 -0
- package/dist/harness-runtime/vendor/opencode-client/service-probe.d.ts +70 -0
- package/dist/harness-runtime/vendor/opencode-client/service-probe.js +16 -0
- package/dist/harness-runtime/vendor/opencode-client/service-timing.d.ts +12 -0
- package/dist/harness-runtime/vendor/opencode-client/service-timing.js +10 -0
- package/dist/harness-runtime/vendor/opencode-client/service-version.d.ts +2 -0
- package/dist/harness-runtime/vendor/opencode-client/service-version.js +6 -0
- package/dist/harness-runtime/vendor/opencode-client/service.d.ts +52 -0
- package/dist/harness-runtime/vendor/opencode-client/service.js +1 -0
- package/dist/harness-runtime/vendor/opencode-client/shared-events.d.ts +11 -0
- package/dist/harness-runtime/vendor/opencode-client/shared-events.js +8 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/connection.d.ts +37 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/connection.js +10 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/data.d.ts +211 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/data.js +18 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/index.d.ts +3 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/index.js +32 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/pty.d.ts +22 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/pty.js +8 -0
- package/dist/harness-runtime/vendor/opencode-plugin.mjs +3 -0
- package/dist/protocol/local-files.js +18 -9
- package/dist/runtime/context.js +8 -4
- package/dist/schemas/agent-profile.schema.json +1 -1
- package/dist/schemas/code-review-result.schema.json +1 -1
- package/dist/schemas/code-verification.schema.json +7 -5
- package/dist/schemas/code-work-batch.schema.json +2 -2
- package/dist/schemas/discovery-result.schema.json +184 -0
- package/dist/schemas/execution-bundle.schema.json +205 -0
- package/dist/schemas/execution-settings.schema.json +162 -0
- package/dist/schemas/flow-descriptor.schema.json +54 -0
- package/dist/schemas/flow-run.schema.json +28 -0
- package/dist/schemas/harness-catalog.schema.json +80 -0
- package/dist/schemas/mb-init-compose-result.schema.json +457 -0
- package/dist/schemas/mb-init-discover-result.schema.json +500 -0
- package/dist/schemas/mb-init-finish-result.schema.json +422 -0
- package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
- package/dist/schemas/mb-init-review-result.schema.json +396 -0
- package/dist/schemas/mb-init-verify-result.schema.json +391 -0
- package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
- package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
- package/dist/schemas/memory-composition-work-result.schema.json +38 -0
- package/dist/schemas/memory-repair-work-result.schema.json +49 -0
- package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
- package/dist/schemas/memory-review-work-result.schema.json +43 -0
- package/dist/schemas/model-catalog.schema.json +153 -0
- package/dist/schemas/plan-review-decision.schema.json +3 -3
- package/dist/schemas/plan-review-result.schema.json +2 -2
- package/dist/schemas/profile-settings.schema.json +122 -0
- package/dist/schemas/release-manifest.schema.json +291 -0
- package/dist/schemas/run-control-receipt.schema.json +118 -0
- package/dist/schemas/run-control-request.schema.json +36 -0
- package/dist/schemas/run-engine-binding.schema.json +1 -1
- package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
- package/dist/services/branch-context.js +3 -3
- package/dist/services/canon.js +11 -8
- package/dist/services/check-runner-evidence.js +168 -0
- package/dist/services/cleanup.js +133 -40
- package/dist/services/cli-operation-classifier.js +25 -62
- package/dist/services/cli-upgrade-allowlist.js +27 -0
- package/dist/services/code-checks.js +1156 -107
- package/dist/services/codex-hook-delivery.js +32 -0
- package/dist/services/command-context.js +80 -0
- package/dist/services/compatibility-preflight.js +13 -3
- package/dist/services/config.js +11 -8
- package/dist/services/continuation-outcome.js +20 -0
- package/dist/services/controller-child-capacity.js +13 -0
- package/dist/services/controller-child-observer.js +115 -0
- package/dist/services/controller-execution.js +88 -0
- package/dist/services/controller-fanout.js +202 -0
- package/dist/services/dashboard.js +143 -58
- package/dist/services/descriptor-coordinator-session.js +35 -0
- package/dist/services/descriptor-root-work.js +53 -0
- package/dist/services/descriptor-run-settlement.js +105 -0
- package/dist/services/descriptor-run-start.js +449 -0
- package/dist/services/descriptor-stage-coordinator.js +197 -0
- package/dist/services/descriptor-stage-settlement.js +222 -0
- package/dist/services/descriptor-stage-start.js +139 -0
- package/dist/services/discovery/agy-discovery.js +2 -0
- package/dist/services/discovery/codex-discovery.js +2 -0
- package/dist/services/discovery/droid-discovery.js +2 -0
- package/dist/services/discovery/grok-discovery.js +2 -0
- package/dist/services/discovery/opencode-discovery.js +2 -0
- package/dist/services/discovery/static.js +31 -0
- package/dist/services/discovery/zcode-discovery.js +2 -0
- package/dist/services/engines.js +294 -153
- package/dist/services/eval-snapshots.js +1215 -116
- package/dist/services/execution-bundles.js +263 -0
- package/dist/services/execution-policy.js +185 -0
- package/dist/services/executor-onboarding.js +56 -0
- package/dist/services/external-work-launch.js +308 -0
- package/dist/services/flow-catalog.js +21 -0
- package/dist/services/flow-descriptors.js +82 -0
- package/dist/services/frozen-engine.js +250 -0
- package/dist/services/generic-execution.js +164 -0
- package/dist/services/harness-adapter.js +355 -36
- package/dist/services/harness-config.js +2 -0
- package/dist/services/harness-discovery.js +29 -0
- package/dist/services/harness-doctor.js +75 -0
- package/dist/services/harness-probe-effects.js +51 -0
- package/dist/services/harness-version-probe.js +100 -0
- package/dist/services/hooks.js +771 -325
- package/dist/services/hybrid-contracts.js +483 -0
- package/dist/services/ids.js +29 -1
- package/dist/services/installation-lock.js +95 -0
- package/dist/services/installed-flow-source.js +183 -0
- package/dist/services/lanes.js +78 -65
- package/dist/services/lifecycle-command.js +1 -288
- package/dist/services/lifecycle-contract.js +1 -0
- package/dist/services/lifecycle-invocations.js +2007 -0
- package/dist/services/managed-daemon-binding.js +38 -0
- package/dist/services/managed-lease-monitor.js +133 -0
- package/dist/services/managed-processes.js +475 -55
- package/dist/services/memory-accepted-composition.js +77 -0
- package/dist/services/memory-baseline-preservation.js +65 -0
- package/dist/services/memory-completed-correction-wave.js +320 -0
- package/dist/services/memory-compose-handler.js +258 -0
- package/dist/services/memory-composition-acceptance.js +199 -0
- package/dist/services/memory-composition-graph.js +134 -0
- package/dist/services/memory-composition-registration.js +146 -0
- package/dist/services/memory-composition-work-custody.js +151 -0
- package/dist/services/memory-composition-work-results.js +96 -0
- package/dist/services/memory-corrected-review-acceptance.js +129 -0
- package/dist/services/memory-corrected-review-batch.js +30 -0
- package/dist/services/memory-corrected-review-findings.js +106 -0
- package/dist/services/memory-corrected-review-inputs.js +210 -0
- package/dist/services/memory-corrected-review-projection.js +78 -0
- package/dist/services/memory-corrected-review-receipt.js +170 -0
- package/dist/services/memory-corrected-review-registration.js +191 -0
- package/dist/services/memory-correction-check-receipt.js +219 -0
- package/dist/services/memory-correction-input-owner.js +6 -0
- package/dist/services/memory-discover-handler.js +104 -0
- package/dist/services/memory-finish-evidence.js +435 -0
- package/dist/services/memory-finish-handler.js +215 -0
- package/dist/services/memory-finish-integration.js +280 -0
- package/dist/services/memory-finish-readiness.js +122 -0
- package/dist/services/memory-git-integration.js +151 -0
- package/dist/services/memory-historical-corrected-review.js +206 -0
- package/dist/services/memory-historical-original-review.js +130 -0
- package/dist/services/memory-historical-repair-registration.js +99 -0
- package/dist/services/memory-historical-second-review.js +128 -0
- package/dist/services/memory-init-bootstrap-readiness.js +210 -0
- package/dist/services/memory-init-contexts.js +89 -0
- package/dist/services/memory-init-controller-start.js +84 -0
- package/dist/services/memory-init-engine-release.js +223 -0
- package/dist/services/memory-init-git-policy.js +136 -0
- package/dist/services/memory-init-lint-capability.js +185 -0
- package/dist/services/memory-init-native-capacity.js +296 -0
- package/dist/services/memory-init-preflight.js +330 -0
- package/dist/services/memory-init-project-bootstrap.js +599 -0
- package/dist/services/memory-init-request-candidate.js +157 -0
- package/dist/services/memory-init-request-input.js +66 -0
- package/dist/services/memory-init-resource-storage.js +125 -0
- package/dist/services/memory-init-run-allocation.js +81 -0
- package/dist/services/memory-init-run-bundle.js +98 -0
- package/dist/services/memory-init-start.js +181 -0
- package/dist/services/memory-init-startup-custody.js +80 -0
- package/dist/services/memory-init-startup-input.js +240 -0
- package/dist/services/memory-init-storage.js +81 -0
- package/dist/services/memory-init-stores.js +359 -0
- package/dist/services/memory-init-worktrunk-capability.js +178 -0
- package/dist/services/memory-layer-publication.js +611 -0
- package/dist/services/memory-layer.js +156 -0
- package/dist/services/memory-lint-config.js +85 -0
- package/dist/services/memory-native-review-copy.js +165 -0
- package/dist/services/memory-normal-flow-entry.js +75 -0
- package/dist/services/memory-ordinary-defaults.js +112 -0
- package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
- package/dist/services/memory-preflight-handler.js +259 -0
- package/dist/services/memory-repair-current-view.js +299 -0
- package/dist/services/memory-repair-document-delta.js +451 -0
- package/dist/services/memory-repair-history.js +491 -0
- package/dist/services/memory-repair-physical-binding.js +312 -0
- package/dist/services/memory-repair-publication-files.js +152 -0
- package/dist/services/memory-repair-publication-intent.js +27 -0
- package/dist/services/memory-repair-publication-plan.js +160 -0
- package/dist/services/memory-repair-publication-source.js +225 -0
- package/dist/services/memory-repair-publication.js +794 -0
- package/dist/services/memory-repair-registration.js +239 -0
- package/dist/services/memory-repair-work-baseline.js +413 -0
- package/dist/services/memory-repair-work-completion.js +97 -0
- package/dist/services/memory-repair-work-custody.js +73 -0
- package/dist/services/memory-repair-work-results.js +89 -0
- package/dist/services/memory-repair-workspace.js +252 -0
- package/dist/services/memory-repair-writer-copy.js +735 -0
- package/dist/services/memory-result-binding.js +173 -0
- package/dist/services/memory-retained-review-authority.js +129 -0
- package/dist/services/memory-retained-review-results.js +70 -0
- package/dist/services/memory-retained-second-baseline.js +102 -0
- package/dist/services/memory-retained-second-repairs.js +156 -0
- package/dist/services/memory-review-acceptance.js +214 -0
- package/dist/services/memory-review-correction-plan.js +171 -0
- package/dist/services/memory-review-handler.js +585 -0
- package/dist/services/memory-review-inputs.js +200 -0
- package/dist/services/memory-review-receipt.js +180 -0
- package/dist/services/memory-review-registration.js +108 -0
- package/dist/services/memory-review-work-custody.js +124 -0
- package/dist/services/memory-review-work-input.js +72 -0
- package/dist/services/memory-review-work-results.js +73 -0
- package/dist/services/memory-second-corrected-review-inputs.js +165 -0
- package/dist/services/memory-second-correction-plan.js +76 -0
- package/dist/services/memory-second-repair-baseline.js +91 -0
- package/dist/services/memory-second-repair-launch.js +614 -0
- package/dist/services/memory-second-repair-registration.js +88 -0
- package/dist/services/memory-second-repair-source.js +187 -0
- package/dist/services/memory-second-review-acceptance.js +115 -0
- package/dist/services/memory-source-observations.js +117 -0
- package/dist/services/memory-stage-dispatch.js +28 -0
- package/dist/services/memory-stage-results.js +199 -0
- package/dist/services/memory-verification-acceptance.js +135 -0
- package/dist/services/memory-verification-checks.js +252 -0
- package/dist/services/memory-verification-receipt.js +159 -0
- package/dist/services/memory-verify-handler.js +135 -0
- package/dist/services/memory-work-result-preparation.js +63 -0
- package/dist/services/merge-queue.js +228 -149
- package/dist/services/merge-server.js +161 -35
- package/dist/services/migrations.js +37 -16
- package/dist/services/model-catalog.js +109 -0
- package/dist/services/model-discovery.js +46 -0
- package/dist/services/native-daemon-history.js +49 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/operator-profiles.js +236 -0
- package/dist/services/plan-runtime.js +26 -11
- package/dist/services/plans.js +28 -25
- package/dist/services/projects.js +37 -17
- package/dist/services/prompts.js +28 -17
- package/dist/services/protocols.js +163 -87
- package/dist/services/provider-capacity.js +385 -0
- package/dist/services/recovery-observation-budget.js +121 -0
- package/dist/services/recovery-snapshot-database.js +560 -0
- package/dist/services/release-archive-producer.js +52 -0
- package/dist/services/release-archive.js +143 -0
- package/dist/services/release-cache.js +142 -0
- package/dist/services/release-compatibility.js +18 -0
- package/dist/services/release-download.js +67 -0
- package/dist/services/release-envelope.js +104 -0
- package/dist/services/release-manifest.js +45 -0
- package/dist/services/release-payload.js +114 -0
- package/dist/services/release-producer.js +63 -0
- package/dist/services/release-provenance.js +160 -0
- package/dist/services/release-registry.js +66 -0
- package/dist/services/release-source.js +194 -0
- package/dist/services/repair-intents.js +42 -0
- package/dist/services/review-copy.js +165 -0
- package/dist/services/review-deferral.js +23 -0
- package/dist/services/run-control-receipt.js +56 -0
- package/dist/services/run-control-worker.js +411 -0
- package/dist/services/run-control.js +1056 -0
- package/dist/services/run-controller-adapter.js +399 -0
- package/dist/services/run-controller-capture.js +186 -0
- package/dist/services/run-controller-process.js +208 -0
- package/dist/services/run-controller-recovery.js +289 -0
- package/dist/services/run-controller-state.js +97 -0
- package/dist/services/run-controller.js +1205 -0
- package/dist/services/run-engine-bindings.js +1 -1
- package/dist/services/run-fork.js +147 -0
- package/dist/services/run-health.js +149 -0
- package/dist/services/run-observations.js +152 -0
- package/dist/services/run-projection.js +22 -64
- package/dist/services/run-recovery-guard.js +77 -0
- package/dist/services/run-recovery-runtime.js +112 -0
- package/dist/services/run-recovery.js +489 -0
- package/dist/services/run-start-custody.js +177 -0
- package/dist/services/runs.js +1019 -247
- package/dist/services/runtime-budget.js +430 -0
- package/dist/services/runtime-command.js +43 -0
- package/dist/services/runtime-scope-capture.js +52 -0
- package/dist/services/runtime-scope-control.js +468 -0
- package/dist/services/runtime-scope-resume.js +599 -0
- package/dist/services/runtime-scope-stop.js +210 -0
- package/dist/services/runtime-scope-worker.js +287 -0
- package/dist/services/runtime-service.js +96 -0
- package/dist/services/schema-validation.js +10 -8
- package/dist/services/scope-writer-cleanup.js +27 -0
- package/dist/services/sessions.js +35 -69
- package/dist/services/setup.js +377 -0
- package/dist/services/stage-blocker.js +17 -7
- package/dist/services/stage-context.js +193 -19
- package/dist/services/stage-handlers.js +74 -0
- package/dist/services/stage-lifecycle.js +100 -108
- package/dist/services/stage-pause.js +89 -75
- package/dist/services/stage-report-renderer.js +234 -6
- package/dist/services/stage-work-graph.js +52 -0
- package/dist/services/tooling.js +32 -23
- package/dist/services/usage.js +49 -101
- package/dist/services/verification-matrix.js +374 -0
- package/dist/services/vnext-code-review.js +223 -88
- package/dist/services/vnext-code.js +327 -226
- package/dist/services/vnext-contracts.js +10 -1
- package/dist/services/vnext-execution-profile.js +80 -5
- package/dist/services/vnext-fanout.js +311 -19
- package/dist/services/vnext-merge.js +332 -111
- package/dist/services/vnext-plan-review.js +279 -127
- package/dist/services/vnext-plan.js +229 -104
- package/dist/services/vnext-protocolize.js +411 -86
- package/dist/services/vnext-specify.js +119 -81
- package/dist/services/vnext-workspace-policy.js +7 -2
- package/dist/services/work-registry.js +1627 -199
- package/dist/services/workspace-bootstrap.js +52 -15
- package/dist/services/workspace-files.js +85 -0
- package/dist/services/worktrees.js +98 -44
- package/dist/shared/entity-references.js +8 -0
- package/dist/shared/errors.js +17 -0
- package/dist/storage/agent-output.js +26 -0
- package/dist/storage/database.js +891 -39
- package/dist/storage/foreign-key-transaction.js +39 -0
- package/dist/storage/immutable-input.js +45 -0
- package/dist/storage/import-guard.js +102 -0
- package/dist/storage/native-cleanup.js +52 -0
- package/dist/storage/paths.js +17 -4
- package/dist/storage/projection-lock.js +73 -0
- package/dist/storage/work-references.js +23 -0
- package/dist/storage/writer-contract.js +89 -0
- package/dist/storage/writer-migration.js +85 -0
- package/package.json +43 -13
- package/tools/create-canon-release.mjs +33 -0
- package/tools/produce-hybrid-release.mjs +41 -0
- package/tools/repair-paused-run-status.mjs +84 -0
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import { appendFile, mkdir, readFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import readline from "node:readline";
|
|
6
|
+
import { closeBridgeProcess } from "./managed-daemon.mjs";
|
|
7
|
+
import { errorRecord, isProvenShellCorrection } from "./operation-errors.mjs";
|
|
8
|
+
import { reportedFailure, commandText } from "./process-json.mjs";
|
|
9
|
+
import { ObservationClock, observedTimeout, validateObservationDuration } from "./observation-clock.mjs";
|
|
10
|
+
import { observeModel } from "./model-observations.mjs";
|
|
11
|
+
import { classifyCodexItem } from "./tool-observations.mjs";
|
|
12
|
+
import { readNativeLines, streamNativeDiagnostics, nativeObservationError } from "./native-streams.mjs";
|
|
13
|
+
import { operationProgressReporter } from "./operation-progress.mjs";
|
|
14
|
+
import { publishSetup } from './setup-progress.mjs';
|
|
15
|
+
|
|
16
|
+
export const CODEX_REQUEST_TIMEOUT_MS = 120_000;
|
|
17
|
+
export const LATE_RESPONSE_GRACE_MS = 5_000;
|
|
18
|
+
const CODEX_SESSION_CONFIG = Object.freeze({
|
|
19
|
+
bypass_hook_trust: true,
|
|
20
|
+
"features.plugins": false,
|
|
21
|
+
"features.multi_agent_v2.enabled": true,
|
|
22
|
+
"features.multi_agent_v2.tool_namespace": "collaboration",
|
|
23
|
+
"features.multi_agent_v2.wait_agent_enabled": true
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Minimal JSON-RPC wrapper around the public Codex app-server protocol. It is
|
|
27
|
+
// intentionally one-shot: the provider owns durable Threads, while the caller
|
|
28
|
+
// owns the runner journal and can safely resume after a process restart.
|
|
29
|
+
function fail(message, code, details) { const error = new Error(message); if (code) error.code = code; if (details) error.details = details; throw error; }
|
|
30
|
+
function required(value, label) { if (typeof value !== "string" || !value.trim()) fail(`${label} is required`); return value.trim(); }
|
|
31
|
+
function absolute(value, label) { const resolved = path.resolve(required(value, label)); if (!path.isAbsolute(resolved)) fail(`${label} must be absolute`); return resolved; }
|
|
32
|
+
function now() { return new Date().toISOString(); }
|
|
33
|
+
function responseSummary(message) {
|
|
34
|
+
const result = message?.result;
|
|
35
|
+
return { request_id: message?.id ?? null, has_result: result !== undefined,
|
|
36
|
+
result_keys: result && typeof result === "object" && !Array.isArray(result) ? Object.keys(result).slice(0, 32) : [], error: message?.error ?? null };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function parseCodexCliVersion(value) {
|
|
40
|
+
const match = String(value).trim().match(/^codex-cli\s+(\S+)$/m);
|
|
41
|
+
if (!match) fail("Codex CLI did not report its version", "codex_version_unavailable");
|
|
42
|
+
return match[1];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Journal {
|
|
48
|
+
constructor(file) { this.file = file ? absolute(file, "--journal") : null; this.pending = this.file ? mkdir(path.dirname(this.file), { recursive: true }) : Promise.resolve(); this.order = 0; }
|
|
49
|
+
write(kind, payload) { if (!this.file) return; this.pending = this.pending.then(() => appendFile(this.file, `${JSON.stringify({ order: ++this.order, observed_at: now(), kind, payload })}\n`)); void this.pending.catch(() => {}); return this.pending; }
|
|
50
|
+
flush() { return this.pending; }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class CodexBridge {
|
|
54
|
+
constructor(options = {}) { this.options = options; this.nextId = 1; this.pending = new Map(); this.lateResponses = new Map(); this.turns = new Map(); this.nativeErrors = new Map(); this.finalMessages = new Map(); this.descendants = new Map(); this.journal = new Journal(options.journal); this.stderr = ""; this.requestedProfiles = new Map(); this.modelObservations = Promise.resolve(); }
|
|
55
|
+
|
|
56
|
+
async restoreItemPhases() {
|
|
57
|
+
if (this.options.journal) {
|
|
58
|
+
const source = createReadStream(this.options.journal, { encoding: "utf8" });
|
|
59
|
+
try {
|
|
60
|
+
for await (const line of readline.createInterface({ input: source })) {
|
|
61
|
+
let record; try { record = JSON.parse(line); } catch { continue; }
|
|
62
|
+
if (record.direction === "inbound" || record.kind === "inbound") this.observeItemPhase(record.payload ?? record.message ?? record);
|
|
63
|
+
}
|
|
64
|
+
} catch (error) { if (error.code !== "ENOENT") throw error; }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async start() {
|
|
69
|
+
await this.restoreItemPhases();
|
|
70
|
+
const bin = this.options.bin ?? process.env.DD_CODEX_BIN ?? "codex";
|
|
71
|
+
// Thread overrides arrive too late to prevent app-server's background
|
|
72
|
+
// marketplace clones. Apply the same isolation policy before startup.
|
|
73
|
+
this.child = spawn(bin, ["-c", `features.plugins=${CODEX_SESSION_CONFIG["features.plugins"]}`, "app-server"], { cwd: this.options.cwd ? absolute(this.options.cwd, "--cwd") : process.cwd(), env: { ...process.env, ...(this.options.env ?? {}) }, detached: process.platform !== "win32", stdio: ["pipe", "pipe", "pipe"] });
|
|
74
|
+
this.diagnostics = streamNativeDiagnostics(this.child.stderr, async chunk => { this.stderr = (this.stderr + chunk).slice(-65_536); await this.journal.write("stderr", { text: chunk }); }, error => this.rejectAll(error));
|
|
75
|
+
this.child.stdin?.on("error", (error) => this.rejectAll(nativeObservationError(error)));
|
|
76
|
+
this.child.on("error", (error) => this.rejectAll(error));
|
|
77
|
+
this.child.on("close", code => { void Promise.all([this.nativeDrain, this.diagnostics]).then(() => this.rejectAll(Object.assign(new Error(`Codex app-server exited ${code}${this.stderr.trim() ? `: ${this.stderr.trim()}` : ""}`), { code: "bridge_exited" }))); });
|
|
78
|
+
this.nativeDrain = readNativeLines(this.child.stdout, async line => { this.receive(line); await this.journal.flush(); }, error => this.rejectAll(error));
|
|
79
|
+
if (this.options.onSpawn) await this.options.onSpawn(this.child);
|
|
80
|
+
publishSetup('probe_started', 'codex-initialize');
|
|
81
|
+
await this.request("initialize", { clientInfo: { name: "dd-codex", version: "0.1.0" }, capabilities: { optOutNotificationMethods: [], experimentalApi: true } });
|
|
82
|
+
publishSetup('native_initialized', 'codex-initialize');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
receive(line) {
|
|
86
|
+
let message; try { message = JSON.parse(line); } catch { this.journal.write("malformed", { line }); return; }
|
|
87
|
+
this.observeItemPhase(message);
|
|
88
|
+
try { this.observeTopology(message); }
|
|
89
|
+
catch (error) { this.topologyError = error; this.rejectAll(error); return; }
|
|
90
|
+
this.observeHookFailure(message);
|
|
91
|
+
this.observeNativeHook(message);
|
|
92
|
+
if (["model/rerouted", "thread/settings/updated"].includes(message.method)) {
|
|
93
|
+
const params = message.params ?? {}, id = params.threadId;
|
|
94
|
+
let root = id; const seen = new Set();
|
|
95
|
+
while (root && !this.requestedProfiles.has(root) && !seen.has(root)) { seen.add(root); root = this.descendants.get(root)?.parent_provider_session_id; }
|
|
96
|
+
if (root && this.requestedProfiles.has(root)) {
|
|
97
|
+
const settings = params.threadSettings ?? {};
|
|
98
|
+
const rerouted = message.method === "model/rerouted";
|
|
99
|
+
this.modelObservations = this.modelObservations.then(() => observeModel({ journal: this.options.journal, harness: "codex-desktop", sessionId: id,
|
|
100
|
+
parentSessionId: this.descendants.get(id)?.parent_provider_session_id ?? null, requested: this.requestedProfiles.get(root),
|
|
101
|
+
observed: { model: rerouted ? params.toModel : settings.model, provider: settings.modelProvider, reasoning: settings.effort },
|
|
102
|
+
source: message.method, evidence: rerouted ? "response" : "configured", reason: params.reason ?? null, previousModel: params.fromModel ?? null
|
|
103
|
+
})).catch(cause => { const error = nativeObservationError(cause); this.modelObservationError = error; this.rejectAll(error); });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (message.method === "turn/started") {
|
|
107
|
+
this.currentTurns ??= new Map(); const id = message.params?.turn?.id, thread = message.params?.threadId;
|
|
108
|
+
const pendingStart = [...this.pending.values()].find(value => value.method === "turn/start" && value.threadId === thread);
|
|
109
|
+
const dispatching = pendingStart && !pendingStart.turnFloor?.has(id);
|
|
110
|
+
const child = this.descendants.get(thread), parentCurrent = child?.parent_turn_id && child.parent_turn_id === this.currentTurns.get(child.parent_provider_session_id);
|
|
111
|
+
if (id && this.turns.get(id)?.status !== "completed" && (dispatching || parentCurrent && child.status === "running" || !this.currentTurns.has(thread) && !this.requestedProfiles.has(thread))) {
|
|
112
|
+
if (this.currentTurns.get(thread) !== id) this.usageMarkers?.delete(thread);
|
|
113
|
+
this.currentTurns.set(thread, id);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const params = message.params ?? {}, currentTurn = this.currentTurns?.get(params.threadId);
|
|
117
|
+
let productive = message.method === "turn/started" || /^item\/(started|completed|[^/]+\/(delta|outputDelta|summaryTextDelta|textDelta))$/.test(message.method ?? "");
|
|
118
|
+
if (productive && currentTurn && (params.turnId ?? params.turn?.id) === currentTurn && ["turn/started", "item/started", "item/completed"].includes(message.method)) {
|
|
119
|
+
this.progressPhases ??= new Map(); const key = JSON.stringify([params.threadId, currentTurn, message.method, params.item?.id ?? null]);
|
|
120
|
+
if (this.progressPhases.has(key)) productive = false;
|
|
121
|
+
else if (this.progressPhases.size < 1_024) this.progressPhases.set(key, true);
|
|
122
|
+
else productive = false;
|
|
123
|
+
}
|
|
124
|
+
if (productive && /(?:Delta|delta)$/.test(message.method ?? "") && !params.delta && !params.outputDelta && !params.text) productive = false;
|
|
125
|
+
let usageProgress = false;
|
|
126
|
+
if (message.method === "thread/tokenUsage/updated" && params.turnId === currentTurn) {
|
|
127
|
+
this.usageMarkers ??= new Map(); const count = params.tokenUsage?.last?.totalTokens ?? params.tokenUsage?.total?.totalTokens;
|
|
128
|
+
const prior = this.usageMarkers.get(params.threadId) ?? 0;
|
|
129
|
+
if (Number.isFinite(count) && count > prior) { usageProgress = true; this.usageMarkers.set(params.threadId, count); }
|
|
130
|
+
}
|
|
131
|
+
if (message.id === undefined && currentTurn && (params.turnId ?? params.turn?.id) === currentTurn && (productive || usageProgress)) {
|
|
132
|
+
this.activity ??= new Map();
|
|
133
|
+
this.activityObservations ??= new Map();
|
|
134
|
+
const visited = new Set();
|
|
135
|
+
let id = message.params?.threadId;
|
|
136
|
+
while (id && !visited.has(id)) {
|
|
137
|
+
visited.add(id); const cursor = (this.activity.get(id) ?? 0) + 1; this.activity.set(id, cursor); this.activityObservations.set(id, { cursor, observed_at: Date.now() });
|
|
138
|
+
const observer = this.turnObservers?.get(id), turnId = this.currentTurns?.get(id);
|
|
139
|
+
if (observer && turnId && !observer.turnFloor.has(turnId)) {
|
|
140
|
+
const pending = this.pending.get(observer.id);
|
|
141
|
+
if (pending) pending.progress = this.activityObservations.get(id);
|
|
142
|
+
const session = id;
|
|
143
|
+
void observer.publish("native_progress", { native_request_id: String(observer.id), session_id: id, observed_at: new Date(this.activityObservations.get(id).observed_at).toISOString() }).catch(cause => {
|
|
144
|
+
// Pipe publications can finish after this Turn's terminal response.
|
|
145
|
+
// Never attribute an old operation's storage failure to its successor.
|
|
146
|
+
if (this.turnObservers?.get(session) !== observer) return;
|
|
147
|
+
const error = nativeObservationError(cause); observer.progressError ??= error;
|
|
148
|
+
const current = this.pending.get(observer.id);
|
|
149
|
+
if (current === pending && current) { clearTimeout(current.timer); this.pending.delete(observer.id); current.reject(error); }
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const child = this.descendants.get(id), parent = child?.parent_provider_session_id;
|
|
153
|
+
id = parent && child.parent_turn_id && child.parent_turn_id === this.currentTurns?.get(parent) ? parent : null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// app-server currently returns complete turns even for `includeTurns:
|
|
157
|
+
// false`. The individual item notifications are already journalled, so
|
|
158
|
+
// retain a compact read receipt instead of writing the same transcript on
|
|
159
|
+
// every status poll.
|
|
160
|
+
const request = message.id !== undefined ? this.pending.get(message.id) : null;
|
|
161
|
+
if (request?.method === "thread/read") {
|
|
162
|
+
const thread = message.result?.thread ?? message.result ?? {};
|
|
163
|
+
this.journal.write("inbound", { jsonrpc: message.jsonrpc, id: message.id, result: { thread: {
|
|
164
|
+
id: thread.id ?? null, status: thread.status ?? null, model: thread.model ?? null, modelProvider: thread.modelProvider ?? null, reasoningEffort: thread.reasoningEffort ?? null, updatedAt: thread.updatedAt ?? null,
|
|
165
|
+
turns: Array.isArray(thread.turns) ? thread.turns.map((turn) => ({ id: turn?.id ?? null, status: turn?.status ?? null, startedAt: turn?.startedAt ?? null, completedAt: turn?.completedAt ?? null })) : []
|
|
166
|
+
} } });
|
|
167
|
+
} else this.journal.write("inbound", message);
|
|
168
|
+
if (message.id !== undefined && message.method) { void this.answerRequest(message); return; }
|
|
169
|
+
if (message.id !== undefined) {
|
|
170
|
+
const pending = this.pending.get(message.id);
|
|
171
|
+
if (!pending) {
|
|
172
|
+
this.lateResponses.set(message.id, message);
|
|
173
|
+
while (this.lateResponses.size > 128) this.lateResponses.delete(this.lateResponses.keys().next().value);
|
|
174
|
+
this.journal.write("late_response", responseSummary(message));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
clearTimeout(pending.timer); this.pending.delete(message.id);
|
|
178
|
+
if (pending.method === "turn/start" && pending.threadId && message.result?.turn?.id) {
|
|
179
|
+
this.currentTurns ??= new Map();
|
|
180
|
+
if (this.currentTurns.get(pending.threadId) !== message.result.turn.id) this.usageMarkers?.delete(pending.threadId);
|
|
181
|
+
this.currentTurns.set(pending.threadId, message.result.turn.id);
|
|
182
|
+
}
|
|
183
|
+
if (pending.timedOut) this.journal.write("late_response", { ...responseSummary(message), method: pending.method, grace_ms: LATE_RESPONSE_GRACE_MS });
|
|
184
|
+
message.error ? pending.reject(Object.assign(new Error(message.error.message ?? JSON.stringify(message.error)), { code: "rpc_error", details: message.error })) : pending.resolve(message.result ?? {});
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (message.method === "error" && message.params?.threadId && message.params?.turnId) {
|
|
188
|
+
const key = `${message.params.threadId}:${message.params.turnId}`;
|
|
189
|
+
this.nativeErrors.set(key, { error: message.params.error, willRetry: message.params.willRetry });
|
|
190
|
+
while (this.nativeErrors.size > 128) this.nativeErrors.delete(this.nativeErrors.keys().next().value);
|
|
191
|
+
}
|
|
192
|
+
if (message.method === "turn/completed") {
|
|
193
|
+
this.turns.set(message.params?.turn?.id ?? message.params?.threadId, { status: "completed", value: message.params, threadId: message.params?.threadId });
|
|
194
|
+
if (this.currentTurns?.get(message.params?.threadId) === message.params?.turn?.id) this.turnObservers?.delete(message.params.threadId);
|
|
195
|
+
}
|
|
196
|
+
if (message.method === "turn/started" && this.turns.get(message.params?.turn?.id)?.status !== "completed") this.turns.set(message.params?.turn?.id ?? message.params?.threadId, { status: "running", value: message.params, threadId: message.params?.threadId });
|
|
197
|
+
const threadId = message.params?.threadId;
|
|
198
|
+
if (threadId && message.method === "item/started") this.finalMessages.delete(threadId);
|
|
199
|
+
if (threadId && message.method === "item/completed" && message.params?.item?.type === "agentMessage") this.finalMessages.set(threadId, { item: message.params.item, turnId: message.params?.turnId ?? null, completedAt: Date.now() });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
isManagedHook(run) {
|
|
203
|
+
const home = this.options.env?.CODEX_HOME;
|
|
204
|
+
return Boolean(home && run?.eventName === "preToolUse" && run.handlerType === "command"
|
|
205
|
+
&& run.executionMode === "sync" && run.statusMessage === "dd-flow runtime observation"
|
|
206
|
+
&& run.sourcePath === path.join(home, "hooks.json"));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
observeNativeHook(message) {
|
|
210
|
+
if (message.method === "turn/started") {
|
|
211
|
+
const { threadId, turn } = message.params ?? {};
|
|
212
|
+
if (threadId && turn?.id && this.turns.get(turn.id)?.status !== "completed") {
|
|
213
|
+
this.nativeHookTurns ??= new Map(); this.nativeHookTurns.set(threadId, turn.id);
|
|
214
|
+
for (const [id, hook] of this.activeHooks ?? []) if (hook.threadId === threadId && hook.turnId !== turn.id) this.activeHooks.delete(id);
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (message.method === "turn/completed") {
|
|
219
|
+
const { threadId, turn } = message.params ?? {};
|
|
220
|
+
for (const [id, hook] of this.activeHooks ?? []) {
|
|
221
|
+
if (hook.threadId === threadId && hook.turnId === turn?.id) this.activeHooks.delete(id);
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (!["hook/started", "hook/completed"].includes(message.method)) return;
|
|
226
|
+
const { threadId, turnId, run } = message.params ?? {};
|
|
227
|
+
if (!this.isManagedHook(run) || typeof run.id !== "string" || !threadId) return;
|
|
228
|
+
this.activeHooks ??= new Map();
|
|
229
|
+
if (message.method === "hook/started") {
|
|
230
|
+
if (this.nativeHookTurns?.has(threadId) && this.nativeHookTurns.get(threadId) !== turnId) return;
|
|
231
|
+
let root = threadId; const seen = new Set();
|
|
232
|
+
while (this.descendants.has(root) && !seen.has(root)) { seen.add(root); root = this.descendants.get(root).parent_provider_session_id; }
|
|
233
|
+
this.activeHooks.set(run.id, { threadId, turnId, root, rootTurnId: this.nativeHookTurns?.get(root) ?? null, rootGeneration: this.hookPromptGenerations?.get(root) ?? 0 });
|
|
234
|
+
} else if (this.activeHooks.get(run.id)?.turnId === turnId) this.activeHooks.delete(run.id);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
verifyNativeHook(input) {
|
|
238
|
+
const { rootSessionId, sessionId, toolCallId, turnId } = input;
|
|
239
|
+
const home = this.options.env?.CODEX_HOME;
|
|
240
|
+
const id = `pre-tool-use:0:${path.join(home ?? "", "hooks.json")}:${toolCallId}`;
|
|
241
|
+
const active = this.activeHooks?.get(id);
|
|
242
|
+
let current = sessionId;
|
|
243
|
+
const seen = new Set();
|
|
244
|
+
while (current && current !== rootSessionId && !seen.has(current)) {
|
|
245
|
+
seen.add(current); current = this.descendants.get(current)?.parent_provider_session_id;
|
|
246
|
+
}
|
|
247
|
+
if (!active || active.threadId !== sessionId || active.turnId !== turnId || !toolCallId
|
|
248
|
+
|| current !== rootSessionId || !this.requestedProfiles.has(rootSessionId)
|
|
249
|
+
|| active.rootGeneration !== (this.hookPromptGenerations?.get(rootSessionId) ?? 0)) {
|
|
250
|
+
fail("No matching active managed native hook", "native_hook_unproven", { provider_session_id: sessionId, tool_call_id: toolCallId });
|
|
251
|
+
}
|
|
252
|
+
return { rootSessionId, sessionId, toolCallId, parentSessionId: sessionId === rootSessionId ? null : this.descendants.get(sessionId)?.parent_provider_session_id };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
observeHookFailure(message) {
|
|
256
|
+
const { threadId, turnId, run } = message.params ?? {};
|
|
257
|
+
const home = this.options.env?.CODEX_HOME;
|
|
258
|
+
if (message.method !== "hook/completed" || run?.status !== "failed" ||
|
|
259
|
+
run.eventName !== "preToolUse" || run.handlerType !== "command" ||
|
|
260
|
+
run.statusMessage !== "dd-flow runtime observation" || !home ||
|
|
261
|
+
run.sourcePath !== path.join(home, "hooks.json")) return;
|
|
262
|
+
const active = this.activeHooks?.get(run.id);
|
|
263
|
+
if (!active || active.threadId !== threadId || active.turnId !== turnId) return;
|
|
264
|
+
if (this.nativeHookTurns?.has(threadId) && this.nativeHookTurns.get(threadId) !== turnId
|
|
265
|
+
|| active.rootTurnId !== null && this.nativeHookTurns?.get(active.root) !== active.rootTurnId
|
|
266
|
+
|| active.rootGeneration !== (this.hookPromptGenerations?.get(active.root) ?? 0)) return;
|
|
267
|
+
const ancestors = new Set();
|
|
268
|
+
let root;
|
|
269
|
+
let current = threadId;
|
|
270
|
+
while (current && !ancestors.has(current)) {
|
|
271
|
+
ancestors.add(current);
|
|
272
|
+
if (this.requestedProfiles.has(current)) root = current;
|
|
273
|
+
current = this.descendants.get(current)?.parent_provider_session_id;
|
|
274
|
+
}
|
|
275
|
+
if (!root) return;
|
|
276
|
+
this.hookFailures ??= new Map();
|
|
277
|
+
if (this.hookFailures.has(root)) return;
|
|
278
|
+
const entries = Array.isArray(run.entries) ? run.entries : [];
|
|
279
|
+
const reason = entries.filter(entry => entry.kind === "error").map(entry => entry.text).join("; ");
|
|
280
|
+
const error = reportedFailure(entries.filter(entry => entry.kind === "error").map(entry => entry.text).join("\n"), "", reason || "Managed Codex hook failed", /timed out/i.test(reason) ? "native_hook_timeout" : "native_hook_failed");
|
|
281
|
+
const correction = isProvenShellCorrection(error);
|
|
282
|
+
error.details = { ...error.details, native_hook_binding: { provider_session_id: threadId, root_provider_session_id: root, turn_id: turnId,
|
|
283
|
+
hook_run_id: run.id, source: "codex.hook/completed" }, provider_session_id: threadId, root_provider_session_id: root, turn_id: turnId,
|
|
284
|
+
hook_run_id: run.id, effect: error.details?.effect ?? "unknown", native_turn_items: { observed: false, possible_effects: true, pending: true }, duration_ms: run.durationMs };
|
|
285
|
+
if (correction) {
|
|
286
|
+
try { void Promise.resolve(this.journal.write("native_hook_correction", errorRecord(error))).catch(storage => { error.cleanup_error ??= errorRecord(storage); }); }
|
|
287
|
+
catch (storage) { error.cleanup_error ??= errorRecord(storage); }
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
this.hookFailures.set(root, error);
|
|
291
|
+
for (const [id, pending] of this.pending) {
|
|
292
|
+
if (ancestors.has(pending.threadId) && pending.method !== "turn/interrupt") {
|
|
293
|
+
clearTimeout(pending.timer); this.pending.delete(id); pending.reject(error);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
try { void Promise.resolve(this.journal.write("native_hook_failure", errorRecord(error))).catch(storage => { error.cleanup_error ??= errorRecord(storage); }); }
|
|
297
|
+
catch (storage) { error.cleanup_error ??= errorRecord(storage); }
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
assertHookHealthy(sessionId) {
|
|
301
|
+
if (this.topologyError) throw this.topologyError;
|
|
302
|
+
const visited = new Set();
|
|
303
|
+
while (sessionId && !visited.has(sessionId)) {
|
|
304
|
+
visited.add(sessionId);
|
|
305
|
+
if (this.turnObservers?.get(sessionId)?.progressError) throw this.turnObservers.get(sessionId).progressError;
|
|
306
|
+
const error = this.hookFailures?.get(sessionId);
|
|
307
|
+
if (error) throw error;
|
|
308
|
+
sessionId = this.descendants.get(sessionId)?.parent_provider_session_id;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
observeItemPhase(message) {
|
|
313
|
+
const { threadId, turnId, item } = message?.params ?? {};
|
|
314
|
+
if (message?.method !== "item/completed" || !threadId || !turnId || !item?.id || classifyCodexItem(item.type) !== "tool") return;
|
|
315
|
+
this.itemPhases ??= new Map();
|
|
316
|
+
const key = JSON.stringify([threadId, turnId, item.id]);
|
|
317
|
+
const previous = this.itemPhases.get(key);
|
|
318
|
+
if (previous && (previous.type !== item.type || previous.status !== (item.status ?? null))) {
|
|
319
|
+
this.topologyError = Object.assign(new Error("Conflicting native item completion evidence"), { code: "native_item_completion_conflict", details: { provider_session_id: threadId, turn_id: turnId, item_id: item.id } });
|
|
320
|
+
this.rejectAll(this.topologyError); return;
|
|
321
|
+
}
|
|
322
|
+
this.itemPhases.set(key, { type: item.type, status: item.status ?? null });
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
bindChild(child, parent, facts) {
|
|
326
|
+
const previous = this.descendants.get(child);
|
|
327
|
+
if (child === parent || this.requestedProfiles.has(child) || previous?.parent_provider_session_id && previous.parent_provider_session_id !== parent) {
|
|
328
|
+
fail("Conflicting native child parent identity", "native_topology_conflict", { child_session_id: child, parent_session_id: parent, retained_parent: previous?.parent_provider_session_id ?? null });
|
|
329
|
+
}
|
|
330
|
+
const seen = new Set([child]);
|
|
331
|
+
for (let ancestor = parent; ancestor; ancestor = this.descendants.get(ancestor)?.parent_provider_session_id) {
|
|
332
|
+
if (seen.has(ancestor)) fail("Native child ancestry contains a cycle", "native_topology_cycle", { child_session_id: child, parent_session_id: parent });
|
|
333
|
+
seen.add(ancestor);
|
|
334
|
+
}
|
|
335
|
+
this.descendants.set(child, { ...previous, ...facts, parent_turn_id: facts.parent_turn_id ?? previous?.parent_turn_id ?? null, provider_session_id: child, parent_provider_session_id: parent });
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Collaboration child Threads are provider facts. Keep only objects that
|
|
339
|
+
// explicitly carry a parent relationship, including native spawn receipts.
|
|
340
|
+
// A wait/send receiver is not necessarily a child of its sender.
|
|
341
|
+
observeTopology(value, depth = 0, parentTurn = null) {
|
|
342
|
+
if (!value || typeof value !== "object" || depth > 8) return;
|
|
343
|
+
if (Array.isArray(value)) { for (const item of value) this.observeTopology(item, depth + 1, parentTurn); return; }
|
|
344
|
+
if (depth === 0 && ["turn/started", "turn/completed"].includes(value.method)) {
|
|
345
|
+
const { threadId, turn } = value.params ?? {};
|
|
346
|
+
const previous = this.descendants.get(threadId) ?? (threadId && !this.requestedProfiles.has(threadId) ? { provider_session_id: threadId, status: "unknown" } : null);
|
|
347
|
+
if (previous && typeof turn?.id === "string" && turn.id) {
|
|
348
|
+
if (value.method === "turn/started" && previous.active_turn_id !== turn.id && this.turns.get(turn.id)?.status !== "completed") {
|
|
349
|
+
this.descendants.set(threadId, { ...previous, status: "running", active_turn_id: turn.id, terminal_turn_id: null, provider_error: null });
|
|
350
|
+
} else if (value.method === "turn/completed" && (!previous.active_turn_id || previous.active_turn_id === turn.id)) {
|
|
351
|
+
this.descendants.set(threadId, { ...previous, status: turn.status === "completed" ? "completed" : turn.status ?? "unknown",
|
|
352
|
+
active_turn_id: turn.id, terminal_turn_id: turn.id, terminal_observed_at: previous.terminal_turn_id === turn.id ? previous.terminal_observed_at ?? now() : now(), provider_error: nativeTurnError(this, threadId, turn), native_turn_items: turnItemEvidence(turn, this, threadId) });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const activity = depth === 0 && ["item/started", "item/completed"].includes(value.method) ? value.params?.item : null;
|
|
357
|
+
const activityParent = value.params?.threadId;
|
|
358
|
+
if (activity?.type === "subAgentActivity" && typeof activity.agentThreadId === "string" && activity.agentThreadId &&
|
|
359
|
+
typeof activityParent === "string" && activityParent) {
|
|
360
|
+
const previous = this.descendants.get(activity.agentThreadId);
|
|
361
|
+
const observed = activity.kind === "completed" ? "completed" : activity.kind === "interrupted" ? "interrupted" : "running";
|
|
362
|
+
// Parent activity has no child Turn identity on some app-server builds.
|
|
363
|
+
// It can discover a child, but cannot reopen or close a later known Turn.
|
|
364
|
+
const status = previous?.active_turn_id || previous?.status === "completed" ? previous.status : observed;
|
|
365
|
+
if (activity.kind === "started") this.bindChild(activity.agentThreadId, activityParent, { status, parent_turn_id: value.params?.turnId === this.currentTurns?.get(activityParent) ? value.params.turnId : null, agent_path: activity.agentPath ?? previous?.agent_path ?? null, source: "codex.app_server.subAgentActivity" });
|
|
366
|
+
else if (["completed", "interrupted"].includes(activity.kind) && activity.agentThreadId !== activityParent && !this.requestedProfiles.has(activity.agentThreadId)) {
|
|
367
|
+
this.descendants.set(activity.agentThreadId, { ...previous, provider_session_id: activity.agentThreadId, status });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (value.type === "collabAgentToolCall" && value.tool === "spawnAgent" &&
|
|
371
|
+
typeof value.senderThreadId === "string" && value.senderThreadId && Array.isArray(value.receiverThreadIds)) {
|
|
372
|
+
for (const child of value.receiverThreadIds) {
|
|
373
|
+
if (typeof child !== "string" || !child) continue;
|
|
374
|
+
const previous = this.descendants.get(child);
|
|
375
|
+
this.bindChild(child, value.senderThreadId, {
|
|
376
|
+
parent_turn_id: parentTurn === this.currentTurns?.get(value.senderThreadId) ? parentTurn : null,
|
|
377
|
+
status: previous?.status === "completed" || previous?.active_turn_id ? previous.status : value.agentsStates?.[child]?.status ?? previous?.status ?? "unknown", source: "codex.app_server" });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const thread = depth === 0 && ["thread/started", "thread/status/changed"].includes(value.method) ? value.params?.thread : null;
|
|
381
|
+
const id = thread?.id ?? null;
|
|
382
|
+
const parent = thread?.parentThreadId ?? thread?.parent_thread_id ?? null;
|
|
383
|
+
if (typeof id === "string" && id && typeof parent === "string" && parent) {
|
|
384
|
+
const previous = this.descendants.get(id);
|
|
385
|
+
this.bindChild(id, parent, {
|
|
386
|
+
parent_turn_id: !previous ? this.currentTurns?.get(parent) ?? null : previous.parent_turn_id,
|
|
387
|
+
status: previous?.status === "completed" || previous?.active_turn_id ? previous.status : thread.status?.type ?? thread.status ?? previous?.status ?? "unknown", source: "codex.app_server" });
|
|
388
|
+
}
|
|
389
|
+
if (depth === 0) {
|
|
390
|
+
if (["item/started", "item/completed"].includes(value.method)) this.observeTopology(value.params?.item, depth + 1, value.params?.turnId);
|
|
391
|
+
const nativeThread = this.pending.get(value.id)?.method === "thread/read" ? value.result?.thread : null;
|
|
392
|
+
if (nativeThread && Array.isArray(nativeThread.turns)) for (const turn of nativeThread.turns) this.observeTopology(turn.items, depth + 1, turn.id);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
directChildren(sessionId) {
|
|
397
|
+
return [...this.descendants.values()].filter((child) => child.parent_provider_session_id === sessionId);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async answerRequest(message) {
|
|
401
|
+
// Eval runs are non-interactive. A request is evidence for the runner, not
|
|
402
|
+
// permission to continue. Do not silently approve a tool or elicitation.
|
|
403
|
+
// A JSON-RPC error is deliberately used instead of inventing a generic
|
|
404
|
+
// `{ decision: "deny" }` payload: app-server request methods have distinct
|
|
405
|
+
// result schemas, while every one must fail closed in a non-interactive
|
|
406
|
+
// eval. The runner can classify this journalled request as infrastructure
|
|
407
|
+
// or unexpected interaction without corrupting the provider protocol.
|
|
408
|
+
const error = { code: -32001, message: `dd-codex refuses interactive request: ${message.method}` };
|
|
409
|
+
this.journal.write("request_refused", { method: message.method, params: message.params, error });
|
|
410
|
+
this.send({ jsonrpc: "2.0", id: message.id, error });
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
request(method, params = {}, timeoutMs = this.options.timeoutMs ?? CODEX_REQUEST_TIMEOUT_MS) {
|
|
414
|
+
const turnFloor = method === "turn/start" ? new Set([this.currentTurns?.get(params.threadId), ...[...this.turns].filter(([, value]) => value.threadId === params.threadId).map(([id]) => id)].filter(Boolean)) : null;
|
|
415
|
+
if (method === "turn/start" && params.threadId) {
|
|
416
|
+
// Only one productive root Turn is admitted by the daemon fence. Drop
|
|
417
|
+
// lifecycle replay keys at the next dispatch, not at arbitrary reads.
|
|
418
|
+
this.progressPhases?.clear();
|
|
419
|
+
this.hookPromptGenerations ??= new Map();
|
|
420
|
+
this.hookPromptGenerations.set(params.threadId, (this.hookPromptGenerations.get(params.threadId) ?? 0) + 1);
|
|
421
|
+
this.currentTurns?.delete(params.threadId);
|
|
422
|
+
}
|
|
423
|
+
const id = this.nextId++;
|
|
424
|
+
return new Promise((resolve, reject) => {
|
|
425
|
+
const timer = observedTimeout(() => {
|
|
426
|
+
const pending = this.pending.get(id); if (!pending || pending.timedOut) return;
|
|
427
|
+
pending.timedOut = true;
|
|
428
|
+
const timeoutError = Object.assign(new Error(`${method} timed out`), { code: "rpc_timeout", details: { method, request_id: id, timeout_ms: timeoutMs, grace_ms: LATE_RESPONSE_GRACE_MS } });
|
|
429
|
+
this.journal.write("request_deadline", timeoutError.details);
|
|
430
|
+
pending.timer = observedTimeout(() => {
|
|
431
|
+
if (this.pending.get(id) !== pending) return;
|
|
432
|
+
this.pending.delete(id); reject(timeoutError);
|
|
433
|
+
}, LATE_RESPONSE_GRACE_MS, { progress: () => pending.progress });
|
|
434
|
+
}, timeoutMs, { progress: () => this.pending.get(id)?.progress, onGap: gap => this.journal.write("observation_gap", { ...gap, method, request_id: id }) });
|
|
435
|
+
const publish = operationProgressReporter();
|
|
436
|
+
this.pending.set(id, { id, resolve, reject, timer, method, threadId: params.threadId, turnFloor });
|
|
437
|
+
if (method === "turn/start" && params.threadId) {
|
|
438
|
+
this.turnObservers ??= new Map(); this.turnObservers.set(params.threadId, { id, turnFloor, publish });
|
|
439
|
+
}
|
|
440
|
+
this.send({ jsonrpc: "2.0", id, method, params });
|
|
441
|
+
if (method === "turn/start") this.journal.write("native_dispatch", { operation_id: this.dispatchOperationId ?? process.env.DD_EVAL_OPERATION_ID ?? null, provider_session_id: params.threadId, native_request_id: id });
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
send(value) {
|
|
445
|
+
this.journal.write("outbound", value);
|
|
446
|
+
const stdin = this.child?.stdin;
|
|
447
|
+
if (!stdin || stdin.destroyed) {
|
|
448
|
+
const error = Object.assign(new Error("Codex app-server stdin is closed"), { code: "bridge_pipe_broken" });
|
|
449
|
+
this.rejectAll(error);
|
|
450
|
+
throw error;
|
|
451
|
+
}
|
|
452
|
+
try { stdin.write(`${JSON.stringify(value)}\n`); }
|
|
453
|
+
catch (cause) { const error = nativeObservationError(cause); this.rejectAll(error); throw error; }
|
|
454
|
+
}
|
|
455
|
+
rejectAll(error) { for (const pending of this.pending.values()) { clearTimeout(pending.timer); pending.reject(error); } this.pending.clear(); this.turnObservers?.clear(); }
|
|
456
|
+
async close() {
|
|
457
|
+
return await closeBridgeProcess(this, async () => { await Promise.all([this.nativeDrain, this.diagnostics]); await this.modelObservations; await this.journal.flush(); });
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async function withBridge(options, action) {
|
|
462
|
+
const bridge = new CodexBridge(options); let primary, result;
|
|
463
|
+
try { await bridge.start(); result = await action(bridge); return result; }
|
|
464
|
+
catch (error) { primary = error; throw error; }
|
|
465
|
+
finally { try { await bridge.close(); } catch (error) { if (!primary) throw nativeObservationError(error, result === undefined ? {} : { result }); primary.cleanup_error = errorRecord(error); primary.details = { ...primary.details, cleanup_error: primary.cleanup_error }; } }
|
|
466
|
+
}
|
|
467
|
+
function profile(options) { return { harness: "codex-desktop", model: options.model ?? null, reasoning: options.reasoning ?? null }; }
|
|
468
|
+
function threadId(result) { const id = result?.thread?.id ?? result?.threadId; return required(id, "Codex thread id"); }
|
|
469
|
+
function directChildren(bridge, sessionId) { return typeof bridge?.directChildren === "function" ? bridge.directChildren(sessionId) : []; }
|
|
470
|
+
async function observedChildren(bridge, sessionId) {
|
|
471
|
+
const children = directChildren(bridge, sessionId);
|
|
472
|
+
let reads = 0;
|
|
473
|
+
return await Promise.all(children.map(async child => {
|
|
474
|
+
if (child.status !== "failed" || !child.terminal_turn_id || reads++ >= 16) return child;
|
|
475
|
+
const observed = await readTurn(bridge, child.provider_session_id, child.terminal_turn_id, true);
|
|
476
|
+
const turn = observed?.turn;
|
|
477
|
+
if (turn?.id !== child.terminal_turn_id || turn.status !== child.status) return child;
|
|
478
|
+
return { ...child, provider_error: nativeTurnError(bridge, child.provider_session_id, turn) ?? child.provider_error,
|
|
479
|
+
native_turn_items: turnItemEvidence(turn, bridge, child.provider_session_id) };
|
|
480
|
+
}));
|
|
481
|
+
}
|
|
482
|
+
function codexSessionConfig() { return { ...CODEX_SESSION_CONFIG }; }
|
|
483
|
+
|
|
484
|
+
async function requestConfiguredThread(bridge, method, params, timeoutMs) {
|
|
485
|
+
try { return await bridge.request(method, { ...params, config: codexSessionConfig() }, timeoutMs); }
|
|
486
|
+
catch (error) {
|
|
487
|
+
const data = error?.details?.data;
|
|
488
|
+
const key = data?.configKey ?? data?.config_key ?? null;
|
|
489
|
+
if (error?.code === "rpc_error" && (data?.reason === "config" || String(key ?? "").startsWith("features.multi_agent_v2"))) {
|
|
490
|
+
throw Object.assign(new Error("Codex runtime rejected the required multi-agent v2 session configuration"), {
|
|
491
|
+
code: "codex_multi_agent_v2_unavailable", cause: error, details: { method, provider_error: error.details }
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
throw error;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function threadReceipt(thread) {
|
|
499
|
+
// Thread reads can include the whole transcript even with includeTurns:false.
|
|
500
|
+
// Item evidence lives in the native history/journal, not each command reply.
|
|
501
|
+
return { ...thread, ...(Array.isArray(thread.turns) ? { turns: thread.turns.map(turn => ({
|
|
502
|
+
id: turn?.id ?? null, status: turn?.status ?? null,
|
|
503
|
+
startedAt: turn?.startedAt ?? null, completedAt: turn?.completedAt ?? null
|
|
504
|
+
})) } : {}) };
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export async function doctor(options = {}) {
|
|
508
|
+
const codex_cli = parseCodexCliVersion(await commandText(options.bin ?? process.env.DD_CODEX_BIN ?? "codex", ["--version"], { phase: 'native-version' }));
|
|
509
|
+
return await withBridge(options, async () => ({ harness: "codex-desktop", app_server: "available", observed_runtime: { codex_cli, dd_harness_contract: "dd-codex-harness@1" }, requested_profile: profile(options), observed_at: now() }));
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
async function observeCodexProfile(bridge, options, id, observed, source) {
|
|
513
|
+
bridge.requestedProfiles?.set(id, profile(options));
|
|
514
|
+
await bridge.modelObservations;
|
|
515
|
+
if (bridge.modelObservationError) throw bridge.modelObservationError;
|
|
516
|
+
await observeModel({ journal: options.journal ?? bridge.options?.journal, harness: "codex-desktop", sessionId: id,
|
|
517
|
+
parentSessionId: bridge.descendants?.get(id)?.parent_provider_session_id ?? null, requested: profile(options), observed, source });
|
|
518
|
+
return observed;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export async function createSession(options) {
|
|
522
|
+
return await withBridge(options, async (bridge) => await createSessionWithBridge(bridge, options));
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export async function createSessionWithBridge(bridge, options) {
|
|
526
|
+
const cwd = absolute(options.cwd, "--cwd");
|
|
527
|
+
// dd-codex is the eval-only adapter. Its CODEX_HOME is generated by
|
|
528
|
+
// dd-flow, so trust only that managed PreToolUse hook for this thread and
|
|
529
|
+
// do not load user plugin hooks into the isolated evaluator.
|
|
530
|
+
const result = await requestConfiguredThread(bridge, "thread/start", {
|
|
531
|
+
cwd,
|
|
532
|
+
model: options.model ?? null,
|
|
533
|
+
approvalPolicy: "never",
|
|
534
|
+
sandbox: "danger-full-access",
|
|
535
|
+
ephemeral: false
|
|
536
|
+
});
|
|
537
|
+
// ThreadStartParams has no reasoning-effort field. The first turn applies
|
|
538
|
+
// it, so reporting the Desktop default here would be a false profile drift.
|
|
539
|
+
return { harness: "codex-desktop", provider_session_id: threadId(result), adapter_session_id: threadId(result), requested_profile: profile(options), observed_profile: await observeCodexProfile(bridge, options, threadId(result), { model: result.model ?? null, reasoning: result.reasoningEffort ?? null, provider: result.modelProvider ?? null }, "thread/start.response"), cwd: result.cwd ?? cwd };
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export async function promptSession(options) {
|
|
543
|
+
return await withBridge(options, async (bridge) => await promptSessionWithBridge(bridge, options));
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export async function startSession(options) {
|
|
547
|
+
return await withBridge(options, async (bridge) => await startSessionWithBridge(bridge, options));
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export async function startSessionWithBridge(bridge, options) {
|
|
551
|
+
const sessionId = required(options.sessionId, "--session-id"); const cwd = absolute(options.cwd, "--cwd"); const prompt = required(options.prompt, "--prompt");
|
|
552
|
+
await ensureThreadLoaded(bridge, sessionId, options, cwd);
|
|
553
|
+
bridge.requestedProfiles?.set(sessionId, profile(options));
|
|
554
|
+
bridge.finalMessages?.delete(sessionId);
|
|
555
|
+
options.assertDispatch?.();
|
|
556
|
+
bridge.assertHookHealthy?.(sessionId);
|
|
557
|
+
bridge.dispatchOperationId = options.operationId;
|
|
558
|
+
const started = await bridge.request("turn/start", { threadId: sessionId, input: [{ type: "text", text: prompt }], cwd, model: options.model ?? null, effort: options.reasoning ?? null, approvalPolicy: "never", sandboxPolicy: { type: "dangerFullAccess" } }, options.timeoutMs ?? 30_000);
|
|
559
|
+
const turn = started?.turn ?? started; const turnId = turn?.id ?? null;
|
|
560
|
+
if (typeof turnId !== "string" || !turnId) throw new Error("Codex did not return a Turn id");
|
|
561
|
+
return { harness: "codex-desktop", provider_session_id: sessionId, adapter_session_id: sessionId, turn_id: turnId, status: "started", requested_profile: profile(options), observed_profile: { model: null, reasoning: null } };
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export async function promptSessionWithBridge(bridge, options) {
|
|
565
|
+
const sessionId = required(options.sessionId, "--session-id"); const cwd = absolute(options.cwd, "--cwd"); const prompt = required(options.prompt, "--prompt");
|
|
566
|
+
validateObservationDuration(options.livenessTimeoutMs ?? options.timeoutMs ?? 1_800_000);
|
|
567
|
+
await ensureThreadLoaded(bridge, sessionId, options, cwd);
|
|
568
|
+
// A final-message fallback belongs only to the Turn that produced it. A
|
|
569
|
+
// new Turn may start before app-server emits its first item notification;
|
|
570
|
+
// clearing here prevents the previous receipt from completing the new Turn.
|
|
571
|
+
bridge.requestedProfiles?.set(sessionId, profile(options));
|
|
572
|
+
bridge.finalMessages?.delete(sessionId);
|
|
573
|
+
options.assertDispatch?.();
|
|
574
|
+
bridge.assertHookHealthy?.(sessionId);
|
|
575
|
+
bridge.dispatchOperationId = options.operationId;
|
|
576
|
+
const clock = new ObservationClock({ timeoutMs: options.livenessTimeoutMs ?? options.timeoutMs ?? 1_800_000, onGap: gap => bridge.journal?.write("observation_gap", { ...gap, session_id: sessionId }) });
|
|
577
|
+
const started = await bridge.request("turn/start", { threadId: sessionId, input: [{ type: "text", text: prompt }], cwd, model: options.model ?? null, effort: options.reasoning ?? null, approvalPolicy: "never", sandboxPolicy: { type: "dangerFullAccess" } }, 30_000);
|
|
578
|
+
const turn = started?.turn ?? started; const turnId = turn?.id ?? null;
|
|
579
|
+
let completed;
|
|
580
|
+
try { completed = isTerminalTurnStatus(turn?.status) ? terminalTurn({ turn }, sessionId, turnId, bridge)
|
|
581
|
+
: await waitForTurn(bridge, sessionId, turnId, options.livenessTimeoutMs ?? options.timeoutMs ?? 1_800_000, options, cwd, clock, turn?.status); }
|
|
582
|
+
catch (error) {
|
|
583
|
+
const retained = bridge.turns?.get(turnId);
|
|
584
|
+
if (retained?.value?.threadId === sessionId && retained.value.turn?.id === turnId && retained.value.turn.status === "completed") {
|
|
585
|
+
throw Object.assign(new Error("Codex completed natively but mandatory observation failed"), { code: "native_outcome_observation_failed", retryable: false,
|
|
586
|
+
details: { native_outcome: { harness: "codex-desktop", provider_session_id: sessionId, turn_id: turnId, status: "completed", turn: retained.value.turn }, settled: false, observation_error: errorRecord(error) }, cause: error });
|
|
587
|
+
}
|
|
588
|
+
if (error?.code === "turn_interrupted") {
|
|
589
|
+
try {
|
|
590
|
+
const observed = await readTurn(bridge, sessionId, turnId, true);
|
|
591
|
+
if (observed?.turn?.id === turnId && observed.turn.status === error.details?.terminal_status) {
|
|
592
|
+
error.details.native_turn_items = turnItemEvidence(observed.turn, bridge, sessionId);
|
|
593
|
+
error.details.provider_error ??= observed.turn.error ?? null;
|
|
594
|
+
}
|
|
595
|
+
} catch { /* The primary native terminal failure remains authoritative. */ }
|
|
596
|
+
try { await bridge.journal?.flush?.(); }
|
|
597
|
+
catch (observationError) {
|
|
598
|
+
throw Object.assign(new Error("Codex terminal failure lacks durable native item evidence"), { code: "native_outcome_observation_failed", retryable: false,
|
|
599
|
+
details: { native_outcome: { provider_session_id: sessionId, turn_id: turnId, status: "failed", provider_error: error.details?.provider_error }, settled: false, observation_error: errorRecord(observationError) }, cause: error });
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
throw error;
|
|
603
|
+
}
|
|
604
|
+
const nativeOutcome = { harness: "codex-desktop", provider_session_id: sessionId, adapter_session_id: sessionId, turn_id: turnId, status: "completed", turn: completed };
|
|
605
|
+
try {
|
|
606
|
+
const thread = await readCompletedThread(bridge, sessionId);
|
|
607
|
+
bridge.assertHookHealthy?.(sessionId);
|
|
608
|
+
const assistantText = latestAssistantText(thread?.turns?.find(item => item?.id === turnId)) ?? latestAssistantText(completed);
|
|
609
|
+
return { ...nativeOutcome, ...(thread ? { thread: threadReceipt(thread) } : {}), descendants: await observedChildren(bridge, sessionId), ...(assistantText ? { assistant_text: assistantText } : {}), requested_profile: profile(options), observed_profile: await observeCodexProfile(bridge, options, sessionId, { model: thread?.model ?? null, reasoning: thread?.reasoningEffort ?? null, provider: thread?.modelProvider ?? null }, "thread/read.after_turn") };
|
|
610
|
+
} catch (error) {
|
|
611
|
+
throw Object.assign(new Error("Codex completed natively but mandatory observation failed"), { code: "native_outcome_observation_failed", retryable: false,
|
|
612
|
+
details: { native_outcome: nativeOutcome, settled: false, observation_error: errorRecord(error) }, cause: error });
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
async function ensureThreadLoaded(bridge, sessionId, options, cwd) {
|
|
617
|
+
const value = await bridge.request("thread/read", { threadId: sessionId, includeTurns: false }, 10_000);
|
|
618
|
+
const thread = value?.thread ?? value;
|
|
619
|
+
if (thread?.status?.type !== "notLoaded") return;
|
|
620
|
+
await requestConfiguredThread(bridge, "thread/resume", {
|
|
621
|
+
threadId: sessionId,
|
|
622
|
+
cwd,
|
|
623
|
+
model: options.model ?? null,
|
|
624
|
+
approvalPolicy: "never",
|
|
625
|
+
sandbox: "danger-full-access"
|
|
626
|
+
}, 30_000);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
async function waitForTurn(bridge, sessionId, turnId, timeoutMs, options, cwd, clock = new ObservationClock({ timeoutMs, onGap: gap => bridge.journal?.write("observation_gap", { ...gap, session_id: sessionId, turn_id: turnId }) }), startStatus) {
|
|
630
|
+
// app-server can briefly expose the new Turn as `interrupted` between the
|
|
631
|
+
// turn/start response and its turn/started notification. Give that snapshot
|
|
632
|
+
// one event-loop grace window before treating failure as authoritative.
|
|
633
|
+
const failureNotBefore = Date.now() + 1_000;
|
|
634
|
+
const reconcileSnapshot = async (status, threadStatus) => {
|
|
635
|
+
if (!isAuthoritativeTerminal(bridge, turnId, status, threadStatus, failureNotBefore)) return false;
|
|
636
|
+
if (status === "completed" || startStatus !== "inProgress" || bridge.turns.get(turnId)?.status === "completed") return true;
|
|
637
|
+
// A provisional failure can contradict the accepted start before its
|
|
638
|
+
// delayed notification arrives. Recheck this Turn, never start another.
|
|
639
|
+
const current = await readTurn(bridge, sessionId, turnId, true);
|
|
640
|
+
return bridge.turns.get(turnId)?.status === "completed" || current?.turn?.status === status
|
|
641
|
+
&& isAuthoritativeTerminal(bridge, turnId, status, current.threadStatus, failureNotBefore);
|
|
642
|
+
};
|
|
643
|
+
let nextStoredReadAt = Date.now();
|
|
644
|
+
while (true) {
|
|
645
|
+
bridge.assertHookHealthy?.(sessionId);
|
|
646
|
+
const known = bridge.turns.get(turnId) ?? bridge.turns.get(sessionId); if (known?.status === "completed" && (!known.threadId || known.threadId === sessionId)) return terminalTurn(known.value, sessionId, turnId, bridge);
|
|
647
|
+
const expired = clock.sample(bridge.activityObservations?.get(sessionId) ?? bridge.activity?.get(sessionId));
|
|
648
|
+
if (expired || Date.now() >= nextStoredReadAt) {
|
|
649
|
+
const stored = await readTurn(bridge, sessionId, turnId, false);
|
|
650
|
+
if (stored?.turn && await reconcileSnapshot(stored.turn.status, stored.threadStatus)) return terminalTurn({ turn: stored.turn }, sessionId, turnId, bridge);
|
|
651
|
+
// A loaded app-server can retain a stale `active` Thread state after a
|
|
652
|
+
// provider-side interruption. The persisted turn page is independent of
|
|
653
|
+
// that live subscription and is the compact authority for its terminal
|
|
654
|
+
// status; unlike `includeTurns`, it never hydrates the whole history.
|
|
655
|
+
const persisted = await readPersistedTurn(bridge, sessionId, turnId);
|
|
656
|
+
if (persisted && await reconcileSnapshot(persisted.status, stored?.threadStatus)) return terminalTurn({ turn: persisted }, sessionId, turnId, bridge);
|
|
657
|
+
// Desktop may unload an idle Thread immediately after an interrupted
|
|
658
|
+
// Turn. Reload it once before polling again: otherwise the compact
|
|
659
|
+
// status remains `notLoaded` forever and hides the terminal Turn.
|
|
660
|
+
if (stored?.threadStatus === "notLoaded") {
|
|
661
|
+
await ensureThreadLoaded(bridge, sessionId, options, cwd);
|
|
662
|
+
const hydrated = await readTurn(bridge, sessionId, turnId, true);
|
|
663
|
+
if (hydrated?.turn && await reconcileSnapshot(hydrated.turn.status, hydrated.threadStatus)) return terminalTurn({ turn: hydrated.turn }, sessionId, turnId, bridge);
|
|
664
|
+
}
|
|
665
|
+
// Some app-server versions correctly omit turns for a compact read.
|
|
666
|
+
// Once the thread is idle, hydrate exactly once to recover the terminal
|
|
667
|
+
// turn instead of waiting for the global timeout.
|
|
668
|
+
if (stored?.threadStatus === "idle") {
|
|
669
|
+
const hydrated = await readTurn(bridge, sessionId, turnId, true);
|
|
670
|
+
// "idle" only describes the Thread. Some app-server builds retain
|
|
671
|
+
// an inProgress Turn after emitting the final agent message; treating
|
|
672
|
+
// that stale Turn as terminal incorrectly aborts an otherwise usable
|
|
673
|
+
// worker session.
|
|
674
|
+
if (hydrated?.turn && await reconcileSnapshot(hydrated.turn.status, hydrated.threadStatus)) return terminalTurn({ turn: hydrated.turn }, sessionId, turnId, bridge);
|
|
675
|
+
}
|
|
676
|
+
// Some app-server builds emit a final agent message but omit both the
|
|
677
|
+
// terminal event and the compact turn record. Wait briefly, hydrate once,
|
|
678
|
+
// then preserve that message as the terminal receipt if still omitted.
|
|
679
|
+
const final = bridge.finalMessages?.get(sessionId);
|
|
680
|
+
// Agent messages are streamed as completed items while a Turn is still
|
|
681
|
+
// reasoning and calling tools. They are terminal evidence only after
|
|
682
|
+
// app-server reports the Thread idle.
|
|
683
|
+
if (final?.turnId === turnId && stored?.threadStatus === "idle" && Date.now() - final.completedAt >= 1_000) {
|
|
684
|
+
const hydrated = await readTurn(bridge, sessionId, turnId, true);
|
|
685
|
+
if (hydrated?.turn && await reconcileSnapshot(hydrated.turn.status, hydrated.threadStatus)) return terminalTurn({ turn: hydrated.turn }, sessionId, turnId, bridge);
|
|
686
|
+
if (hydrated?.threadStatus === "idle" && !isTerminalTurnStatus(hydrated.turn?.status))
|
|
687
|
+
return terminalTurn({ turn: { id: turnId, status: "completed", synthetic: true, final_message: final.item } }, sessionId, turnId, bridge);
|
|
688
|
+
}
|
|
689
|
+
nextStoredReadAt = Date.now() < failureNotBefore ? failureNotBefore : Date.now() + 15_000;
|
|
690
|
+
}
|
|
691
|
+
const terminal = bridge.turns.get(turnId); if (terminal?.status === "completed" && (!terminal.threadId || terminal.threadId === sessionId)) return terminalTurn(terminal.value, sessionId, turnId, bridge);
|
|
692
|
+
if (expired && clock.sample(bridge.activityObservations?.get(sessionId) ?? bridge.activity?.get(sessionId))) fail("Codex native outcome remains unknown after its silence window", "operation_observation_lost", { provider_session_id: sessionId, turn_id: turnId, native_outcome: "unknown" });
|
|
693
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function terminalTurn(value, sessionId, turnId, bridge) {
|
|
698
|
+
// A terminal event may arrive while an older snapshot RPC is in flight.
|
|
699
|
+
const observed = bridge.turns.get(turnId);
|
|
700
|
+
if (observed?.status === "completed" && (!observed.threadId || observed.threadId === sessionId)) value = observed.value;
|
|
701
|
+
const turn = value?.turn ?? value;
|
|
702
|
+
if (turn?.id && turn.id !== turnId || value?.threadId && value.threadId !== sessionId) fail("Codex terminal Turn identity does not match the requested Session/Turn", "turn_identity_mismatch", { provider_session_id: sessionId, turn_id: turnId, native_thread_id: value?.threadId ?? null, native_turn_id: turn?.id ?? null });
|
|
703
|
+
const providerError = nativeTurnError(bridge, sessionId, turn);
|
|
704
|
+
if (turn?.status && turn.status !== "completed") fail(`Codex turn ${turnId ?? "unknown"} ended as ${turn.status}`, "turn_interrupted", { provider_session_id: sessionId, turn_id: turnId, native_turn_id: turn.id ?? null, terminal_status: turn.status, observed_at: now(),
|
|
705
|
+
provider_error: providerError, native_turn_items: turnItemEvidence(turn, bridge, sessionId) });
|
|
706
|
+
return value;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function nativeTurnError(bridge, sessionId, turn) {
|
|
710
|
+
const native = bridge.nativeErrors?.get(`${sessionId}:${turn?.id}`);
|
|
711
|
+
const error = turn?.error && typeof turn.error === "object" && native?.error && typeof native.error === "object"
|
|
712
|
+
? { ...native.error, ...turn.error } : turn?.error ?? native?.error ?? (turn?.codexErrorInfo ? { codexErrorInfo: turn.codexErrorInfo } : null);
|
|
713
|
+
return error && native?.willRetry !== undefined && typeof error === "object" ? { ...error, willRetry: native.willRetry } : error;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
export function turnItemEvidence(turn, bridge, sessionId) {
|
|
717
|
+
const items = turn?.items;
|
|
718
|
+
const pending = item => {
|
|
719
|
+
const kind = classifyCodexItem(item?.type);
|
|
720
|
+
if (kind === "non_tool") return false;
|
|
721
|
+
if (kind === "unknown") return true;
|
|
722
|
+
const phase = bridge?.itemPhases?.get(JSON.stringify([sessionId, turn?.id, item?.id]));
|
|
723
|
+
if (phase && (phase.type !== item?.type || phase.status !== null && item?.status !== undefined && phase.status !== item.status)) return true;
|
|
724
|
+
if (phase && ![null, "completed", "failed", "declined", "interrupted"].includes(phase.status)) return true;
|
|
725
|
+
return !["completed", "failed", "declined", "interrupted"].includes(item?.status) && !(phase && phase.type === item?.type && (item?.status === undefined || item?.status === null));
|
|
726
|
+
};
|
|
727
|
+
return Array.isArray(items) && turn?.itemsView !== "notLoaded" ? { observed: true, count: items.length, types: items.map(item => item?.type ?? "unknown"),
|
|
728
|
+
items: items.map(item => {
|
|
729
|
+
const phase = bridge?.itemPhases?.get(JSON.stringify([sessionId, turn?.id, item?.id]));
|
|
730
|
+
return { id: item?.id ?? null, type: item?.type ?? "unknown", status: item?.status ?? null,
|
|
731
|
+
...(phase && !pending(item) ? { completion_phase: { session_id: sessionId, turn_id: turn.id, item_id: item.id, status: phase.status, source: "codex.item/completed" } } : {}) };
|
|
732
|
+
}),
|
|
733
|
+
pending: items.some(pending),
|
|
734
|
+
possible_effects: items.some(item => classifyCodexItem(item?.type) !== "non_tool") } : { observed: false };
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
export { terminalProviderOverload as terminalCodexOverload } from "./provider-overload-policy.mjs";
|
|
738
|
+
|
|
739
|
+
function isTerminalTurnStatus(status) { return ["completed", "failed", "interrupted", "cancelled"].includes(status); }
|
|
740
|
+
function isAuthoritativeTerminal(bridge, turnId, status, threadStatus, failureNotBefore) {
|
|
741
|
+
if (!isTerminalTurnStatus(status)) return false;
|
|
742
|
+
if (status === "completed") return true;
|
|
743
|
+
if (Date.now() < failureNotBefore) return false;
|
|
744
|
+
return !(bridge.turns.get(turnId)?.status === "running" && threadStatus === "active");
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
async function readTurn(bridge, sessionId, turnId, includeTurns) {
|
|
748
|
+
if (!turnId) return null;
|
|
749
|
+
try {
|
|
750
|
+
const result = await bridge.request("thread/read", { threadId: sessionId, includeTurns }, 10_000);
|
|
751
|
+
const thread = result?.thread ?? result;
|
|
752
|
+
return { turn: Array.isArray(thread?.turns) ? thread.turns.find((turn) => turn?.id === turnId) ?? null : null, threadStatus: thread?.status?.type ?? null };
|
|
753
|
+
} catch { bridge.assertHookHealthy?.(sessionId); return null; }
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
async function readPersistedTurn(bridge, sessionId, turnId) {
|
|
757
|
+
try {
|
|
758
|
+
const result = await bridge.request("thread/turns/list", { threadId: sessionId, limit: 20, sortDirection: "desc", itemsView: "summary" }, 10_000);
|
|
759
|
+
return Array.isArray(result?.data) ? result.data.find((turn) => turn?.id === turnId) ?? null : null;
|
|
760
|
+
} catch { bridge.assertHookHealthy?.(sessionId); return null; }
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
async function readCompletedThread(bridge, sessionId) {
|
|
764
|
+
let lastError;
|
|
765
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
766
|
+
try { const result = await bridge.request("thread/read", { threadId: sessionId, includeTurns: true }, 10_000); return result?.thread ?? result; }
|
|
767
|
+
catch (error) { lastError = error; bridge.assertHookHealthy?.(sessionId); bridge.journal.write("thread_read_retry", { session_id: sessionId, attempt, error: errorRecord(error) }); await new Promise((resolve) => setTimeout(resolve, 150)); }
|
|
768
|
+
}
|
|
769
|
+
throw lastError;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function latestAssistantText(value) {
|
|
773
|
+
const messages = value?.messages ?? value?.items ?? value?.turn?.items ?? (value?.turn?.final_message ? [value.turn.final_message] : []);
|
|
774
|
+
if (!Array.isArray(messages)) return null;
|
|
775
|
+
for (const message of [...messages].reverse()) {
|
|
776
|
+
if (message?.role !== "assistant" && message?.type !== "agentMessage") continue;
|
|
777
|
+
const parts = message.content ?? message.parts ?? [];
|
|
778
|
+
if (typeof message.text === "string") return message.text;
|
|
779
|
+
if (Array.isArray(parts)) {
|
|
780
|
+
const text = parts.map((part) => typeof part?.text === "string" ? part.text : typeof part?.content === "string" ? part.content : "").filter(Boolean).join("\n");
|
|
781
|
+
if (text) return text;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
export async function inspectSession(options) {
|
|
788
|
+
return await withBridge(options, async (bridge) => await inspectSessionWithBridge(bridge, options));
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
export async function inspectSessionWithBridge(bridge, options) {
|
|
792
|
+
const sessionId = required(options.sessionId, "--session-id"); const value = await bridge.request("thread/read", { threadId: sessionId, includeTurns: false }); const thread = value?.thread ?? value;
|
|
793
|
+
const terminal = options.turnId ? (await readTurn(bridge, sessionId, options.turnId, true))?.turn : null;
|
|
794
|
+
const exact = terminal?.id === options.turnId && terminal?.status === "failed" ? { terminal_turn_id: terminal.id, terminal_status: terminal.status, provider_error: nativeTurnError(bridge, sessionId, terminal), native_turn_items: turnItemEvidence(terminal, bridge, sessionId) } : {};
|
|
795
|
+
return { harness: "codex-desktop", provider_session_id: sessionId, adapter_session_id: sessionId, status: thread.status ?? null, thread: threadReceipt(thread), ...exact, descendants: await observedChildren(bridge, sessionId), requested_profile: profile(options), observed_profile: await observeCodexProfile(bridge, options, sessionId, { model: thread.model ?? null, reasoning: thread.reasoningEffort ?? null, provider: thread.modelProvider ?? null }, "thread/read") };
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
export async function cancelSession(options) {
|
|
799
|
+
return await withBridge(options, async (bridge) => await cancelSessionWithBridge(bridge, options));
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
export async function cancelSessionWithBridge(bridge, options) {
|
|
803
|
+
const sessionId = required(options.sessionId, "--session-id");
|
|
804
|
+
let turnId = options.turnId ?? null;
|
|
805
|
+
if (!turnId) {
|
|
806
|
+
const result = await bridge.request("thread/read", { threadId: sessionId, includeTurns: true }, 10_000);
|
|
807
|
+
const thread = result?.thread ?? result;
|
|
808
|
+
turnId = [...(thread?.turns ?? [])].reverse().find((turn) => turn?.status === "inProgress")?.id ?? null;
|
|
809
|
+
}
|
|
810
|
+
if (!turnId) fail("Codex Session has no active Turn to interrupt", "turn_not_active");
|
|
811
|
+
await bridge.request("turn/interrupt", { threadId: sessionId, turnId });
|
|
812
|
+
// Interrupt acknowledgement is not a provider terminal receipt. Reconcile
|
|
813
|
+
// observes the Thread before the runner records terminal cancellation.
|
|
814
|
+
return { harness: "codex-desktop", provider_session_id: sessionId, adapter_session_id: sessionId, turn_id: turnId, cancelled: true, settled: false };
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
export async function promptFromFile(file) { return await readFile(absolute(file, "--prompt-file"), "utf8"); }
|