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