create-walle 0.10.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -41
- package/lib/owner-write-safety.js +135 -0
- package/lib/public-runtime.js +13 -5
- package/lib/release-schema.json +3 -2
- package/lib/runtime-closure-policy.json +7 -5
- package/package.json +10 -4
- package/template/bin/deps-doctor.js +1 -1
- package/template/bin/node-bin.sh +11 -0
- package/template/coding-task-manager/api-prompts.js +10 -22
- package/template/coding-task-manager/bin/capture-codex-rollout-records.js +417 -0
- package/template/coding-task-manager/bin/census-codex-lead-history.js +325 -0
- package/template/coding-task-manager/bin/claude-hook.js +7 -3
- package/template/coding-task-manager/bin/claude-table-parity.js +307 -0
- package/template/coding-task-manager/bin/conversation-storage-auto-migration-lib.js +16 -1
- package/template/coding-task-manager/bin/conversation-storage-generation-evidence.js +9 -2
- package/template/coding-task-manager/bin/conversation-storage-service-entry-lib.js +16 -1
- package/template/coding-task-manager/bin/conversation-storage-walle-owner.js +247 -0
- package/template/coding-task-manager/bin/ctm-machine-pair.js +160 -0
- package/template/coding-task-manager/bin/lead-wait-census.js +1348 -0
- package/template/coding-task-manager/bin/repair-foreign-clone.js +564 -0
- package/template/coding-task-manager/bin/repair-resume-echo-titles.js +248 -0
- package/template/coding-task-manager/bin/seam-replay-codex.js +301 -0
- package/template/coding-task-manager/bin/write-lock-bench.js +111 -0
- package/template/coding-task-manager/db.js +705 -286
- package/template/coding-task-manager/git-utils.js +44 -0
- package/template/coding-task-manager/lib/adopted-host-redial.js +4 -0
- package/template/coding-task-manager/lib/agent-presets.js +15 -0
- package/template/coding-task-manager/lib/agent-title-apply.js +88 -0
- package/template/coding-task-manager/lib/agent-title-backfill-read.js +92 -0
- package/template/coding-task-manager/lib/api/host-stats.js +98 -0
- package/template/coding-task-manager/lib/api/ingress.js +35 -12
- package/template/coding-task-manager/lib/api/machine-pairing.js +285 -0
- package/template/coding-task-manager/lib/api/machine-pool.js +307 -0
- package/template/coding-task-manager/lib/api/machines.js +461 -0
- package/template/coding-task-manager/lib/api/misc-ops.js +20 -5
- package/template/coding-task-manager/lib/api/providers.js +53 -6
- package/template/coding-task-manager/lib/api/resume-from-store.js +25 -0
- package/template/coding-task-manager/lib/api/sessions.js +160 -8
- package/template/coding-task-manager/lib/api/transcript-history.js +17 -4
- package/template/coding-task-manager/lib/api/worktrees.js +38 -37
- package/template/coding-task-manager/lib/approval-drift.js +16 -1
- package/template/coding-task-manager/lib/approval-logical-frame.js +5 -1
- package/template/coding-task-manager/lib/approval-scope.js +68 -4
- package/template/coding-task-manager/lib/auth-context.js +32 -2
- package/template/coding-task-manager/lib/auth-rate-limit.js +41 -1
- package/template/coding-task-manager/lib/auth-rules.js +186 -45
- package/template/coding-task-manager/lib/broadcast-serialize.js +2 -1
- package/template/coding-task-manager/lib/browser-session-broker.js +9 -1
- package/template/coding-task-manager/lib/browser-use-adapter.js +33 -2
- package/template/coding-task-manager/lib/claude-markdown-ansi.js +27 -85
- package/template/coding-task-manager/lib/claude-session-title.js +6 -30
- package/template/coding-task-manager/lib/claude-table-layout.js +322 -0
- package/template/coding-task-manager/lib/client-diag-handler.js +25 -0
- package/template/coding-task-manager/lib/codex-config-guard.js +33 -3
- package/template/coding-task-manager/lib/codex-harness-qualification.js +12 -0
- package/template/coding-task-manager/lib/codex-history-terminal-renderer.js +33 -33
- package/template/coding-task-manager/lib/codex-launch-health.js +29 -2
- package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
- package/template/coding-task-manager/lib/codex-local-links.js +267 -0
- package/template/coding-task-manager/lib/codex-markdown-lists.js +160 -0
- package/template/coding-task-manager/lib/codex-native-history-prefix.js +4 -0
- package/template/coding-task-manager/lib/codex-paths.js +2 -1
- package/template/coding-task-manager/lib/codex-source-handoff.js +186 -0
- package/template/coding-task-manager/lib/coding-agent-models.js +21 -30
- package/template/coding-task-manager/lib/coding-session-model-adapters.js +27 -2
- package/template/coding-task-manager/lib/coding-session-model-projection.js +5 -0
- package/template/coding-task-manager/lib/coding-session-model-runtime.js +1 -2
- package/template/coding-task-manager/lib/coding-session-model-state.js +10 -0
- package/template/coding-task-manager/lib/coding-session-model-sync-state.js +0 -1
- package/template/coding-task-manager/lib/conversation-import/cursor-guard-owner.js +10 -0
- package/template/coding-task-manager/lib/create/resolve-launch-params.js +8 -1
- package/template/coding-task-manager/lib/ctm-boot-lifecycle.js +17 -1
- package/template/coding-task-manager/lib/db-domain-tables.js +7 -0
- package/template/coding-task-manager/lib/db-domains.js +21 -2
- package/template/coding-task-manager/lib/db-owner-op-registry.js +7 -0
- package/template/coding-task-manager/lib/db-owner-worker-client.js +22 -0
- package/template/coding-task-manager/lib/db-schema-source-files.js +1 -0
- package/template/coding-task-manager/lib/feature-manifest.js +1 -3
- package/template/coding-task-manager/lib/fs-watcher.js +3 -2
- package/template/coding-task-manager/lib/harness-history-transition.js +41 -14
- package/template/coding-task-manager/lib/headless-inflight-ops.js +203 -0
- package/template/coding-task-manager/lib/headless-missing-reply.js +42 -0
- package/template/coding-task-manager/lib/headless-term-service.js +60 -23
- package/template/coding-task-manager/lib/history-transition-read-op.js +179 -23
- package/template/coding-task-manager/lib/host-control-dial.js +167 -0
- package/template/coding-task-manager/lib/host-handoff-orchestrator.js +16 -2
- package/template/coding-task-manager/lib/host-handoff.js +51 -13
- package/template/coding-task-manager/lib/host-health-ledger.js +164 -12
- package/template/coding-task-manager/lib/host-self-truth-watchdog.js +4 -0
- package/template/coding-task-manager/lib/host-stats.js +328 -0
- package/template/coding-task-manager/lib/host-threadpool-evacuation.js +160 -0
- package/template/coding-task-manager/lib/host-threadpool-monitor.js +14 -1
- package/template/coding-task-manager/lib/hosted-input-dispatch.js +203 -0
- package/template/coding-task-manager/lib/hosted-sync-worker.js +113 -5
- package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +4 -0
- package/template/coding-task-manager/lib/instance-manifest-runtime.js +3 -0
- package/template/coding-task-manager/lib/instance-manifest.js +13 -1
- package/template/coding-task-manager/lib/instance-sync-status.js +15 -0
- package/template/coding-task-manager/lib/kernel/diag-log.js +12 -0
- package/template/coding-task-manager/lib/launch-model-override.js +160 -1
- package/template/coding-task-manager/lib/launch-plan-worker.js +59 -2
- package/template/coding-task-manager/lib/lead-model-observer.js +17 -2
- package/template/coding-task-manager/lib/lead-model-switch-runtime.js +57 -4
- package/template/coding-task-manager/lib/lead-model-switch.js +11 -2
- package/template/coding-task-manager/lib/lead-model-transition-runtime.js +42 -4
- package/template/coding-task-manager/lib/lead-model-transition-store.js +61 -3
- package/template/coding-task-manager/lib/legacy-draft-cancellation-recovery.js +111 -0
- package/template/coding-task-manager/lib/login-tripwire.js +18 -0
- package/template/coding-task-manager/lib/machine-pairing.js +238 -0
- package/template/coding-task-manager/lib/machine-pool.js +409 -0
- package/template/coding-task-manager/lib/machine-registry.js +749 -0
- package/template/coding-task-manager/lib/machine-self.js +66 -0
- package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +56 -4
- package/template/coding-task-manager/lib/media-journal-control-reader.js +473 -0
- package/template/coding-task-manager/lib/media-journal.js +84 -27
- package/template/coding-task-manager/lib/media-store-options.js +43 -15
- package/template/coding-task-manager/lib/migrations/v56-host-samples-and-machine-pool.js +76 -0
- package/template/coding-task-manager/lib/mirror-feed-sanitize.js +12 -3
- package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +16 -3
- package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
- package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
- package/template/coding-task-manager/lib/model-proxy/collaboration-codec.js +94 -0
- package/template/coding-task-manager/lib/model-proxy/handoff.js +494 -0
- package/template/coding-task-manager/lib/model-proxy/lead-worker-picks.js +25 -0
- package/template/coding-task-manager/lib/model-proxy/native-lead-listener.js +61 -7
- package/template/coding-task-manager/lib/model-proxy/proxy-cli.js +200 -0
- package/template/coding-task-manager/lib/model-proxy/proxy-instance-record.js +257 -0
- package/template/coding-task-manager/lib/model-proxy/responses-face.js +24 -2
- package/template/coding-task-manager/lib/model-proxy/stream-accounting.js +205 -0
- package/template/coding-task-manager/lib/model-proxy/tokens.js +78 -0
- package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
- package/template/coding-task-manager/lib/native-history-control.js +62 -14
- package/template/coding-task-manager/lib/native-lead-settings-client.js +2 -1
- package/template/coding-task-manager/lib/native-lead-tls.js +18 -4
- package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +628 -64
- package/template/coding-task-manager/lib/owner-action-manifest-data.json +14 -0
- package/template/coding-task-manager/lib/painted-row-recorder.js +9 -1
- package/template/coding-task-manager/lib/prompt-admission-trace.js +69 -0
- package/template/coding-task-manager/lib/prompt-submission-contract.js +21 -1
- package/template/coding-task-manager/lib/provider-login-broker.js +192 -9
- package/template/coding-task-manager/lib/provider-login-capture.js +153 -0
- package/template/coding-task-manager/lib/queue-draft-status-overlay.js +36 -0
- package/template/coding-task-manager/lib/raw-codex-history-window.js +5 -3
- package/template/coding-task-manager/lib/raw-history-block.js +3 -1
- package/template/coding-task-manager/lib/read-pool-client.js +36 -24
- package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
- package/template/coding-task-manager/lib/read-pool-telemetry.js +14 -5
- package/template/coding-task-manager/lib/recent-catalog-query.js +26 -0
- package/template/coding-task-manager/lib/recent-session-catalog-response.js +9 -0
- package/template/coding-task-manager/lib/remote-command-context.js +12 -0
- package/template/coding-task-manager/lib/remote-command-runtime.js +18 -5
- package/template/coding-task-manager/lib/remote-machine-catalog.js +1062 -0
- package/template/coding-task-manager/lib/remote-machine-create.js +693 -0
- package/template/coding-task-manager/lib/remote-machine-link.js +939 -0
- package/template/coding-task-manager/lib/remote-machine-proxy.js +520 -0
- package/template/coding-task-manager/lib/remote-session-forward.js +306 -0
- package/template/coding-task-manager/lib/remote-submission-status.js +6 -0
- package/template/coding-task-manager/lib/restart-outcome-truth.js +20 -3
- package/template/coding-task-manager/lib/restart-preflight.js +9 -2
- package/template/coding-task-manager/lib/restore-backoff-gate.js +10 -2
- package/template/coding-task-manager/lib/restore-prepare-retry.js +12 -5
- package/template/coding-task-manager/lib/restore-resume-batch.js +22 -1
- package/template/coding-task-manager/lib/resume-blocked-wire.js +62 -1
- package/template/coding-task-manager/lib/resume-cwd.js +13 -0
- package/template/coding-task-manager/lib/resume-from-store.js +12 -1
- package/template/coding-task-manager/lib/resume-origin-facts.js +38 -1
- package/template/coding-task-manager/lib/resume-preflight.js +21 -16
- package/template/coding-task-manager/lib/review-repo-admission.js +39 -6
- package/template/coding-task-manager/lib/review-repo-registry.js +17 -0
- package/template/coding-task-manager/lib/root-delivery-runtime.js +72 -15
- package/template/coding-task-manager/lib/root-delivery-selection.js +37 -1
- package/template/coding-task-manager/lib/root-prompt-admission.js +43 -11
- package/template/coding-task-manager/lib/root-prompt-flow.js +175 -38
- package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
- package/template/coding-task-manager/lib/server-memory-sampler-runtime.js +10 -0
- package/template/coding-task-manager/lib/server-memory-sampler.js +41 -2
- package/template/coding-task-manager/lib/session-analysis-memory.js +2 -0
- package/template/coding-task-manager/lib/session-create/codex-native-provider-gate.js +218 -0
- package/template/coding-task-manager/lib/session-create/codex-resume-exit-classify.js +52 -0
- package/template/coding-task-manager/lib/session-create/delegate-worker.js +95 -6
- package/template/coding-task-manager/lib/session-create/identity.js +20 -1
- package/template/coding-task-manager/lib/session-create/prepared.js +69 -1
- package/template/coding-task-manager/lib/session-create/provider-launch-context.js +77 -0
- package/template/coding-task-manager/lib/session-create/provider-process.js +38 -39
- package/template/coding-task-manager/lib/session-create/response.js +30 -12
- package/template/coding-task-manager/lib/session-create/transcript.js +13 -4
- package/template/coding-task-manager/lib/session-create/worker-continuation.js +43 -1
- package/template/coding-task-manager/lib/session-create/worker-notices.js +94 -5
- package/template/coding-task-manager/lib/session-create/worker-observability.js +99 -7
- package/template/coding-task-manager/lib/session-create/worker-stop.js +11 -3
- package/template/coding-task-manager/lib/session-create/worker-wait-watcher.js +315 -0
- package/template/coding-task-manager/lib/session-geometry-authority-handler.js +10 -1
- package/template/coding-task-manager/lib/session-host-manager.js +152 -206
- package/template/coding-task-manager/lib/session-jobs.js +17 -9
- package/template/coding-task-manager/lib/session-lifecycle.js +20 -50
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +68 -3
- package/template/coding-task-manager/lib/session-resource-sampler.js +67 -9
- package/template/coding-task-manager/lib/session-standup.js +67 -4
- package/template/coding-task-manager/lib/session-status-ledger.js +42 -3
- package/template/coding-task-manager/lib/session-status-runtime.js +45 -1
- package/template/coding-task-manager/lib/session-status.js +2 -2
- package/template/coding-task-manager/lib/session-title-mirror.js +25 -57
- package/template/coding-task-manager/lib/session-transcript-trash.js +8 -1
- package/template/coding-task-manager/lib/startup-pointer-write.js +57 -0
- package/template/coding-task-manager/lib/state-sync/painted-frame-field.js +11 -0
- package/template/coding-task-manager/lib/storage/coding-session-model-store.js +21 -22
- package/template/coding-task-manager/lib/surviving-owner-bootstrap.js +7 -2
- package/template/coding-task-manager/lib/surviving-owner-recovery.js +2 -1
- package/template/coding-task-manager/lib/terminal-draft-http-status.js +25 -0
- package/template/coding-task-manager/lib/transcript-archiver.js +229 -229
- package/template/coding-task-manager/lib/transcript-frame-cache.js +12 -2
- package/template/coding-task-manager/lib/transcript-guard-job.js +55 -4
- package/template/coding-task-manager/lib/transcript-link.js +77 -0
- package/template/coding-task-manager/lib/transcript-manifest-store.js +8 -4
- package/template/coding-task-manager/lib/transcript-source.js +8 -3
- package/template/coding-task-manager/lib/transcript-store.js +40 -0
- package/template/coding-task-manager/lib/tunnel-lane-governor.js +6 -3
- package/template/coding-task-manager/lib/typing-interaction-trace.js +136 -2
- package/template/coding-task-manager/lib/typing-worker-stages.js +208 -1
- package/template/coding-task-manager/lib/usage-ledger/claude-parse.js +53 -2
- package/template/coding-task-manager/lib/usage-ledger/ingest.js +13 -4
- package/template/coding-task-manager/lib/usage-ledger/read.js +14 -0
- package/template/coding-task-manager/lib/usage-ledger/schema.js +49 -1
- package/template/coding-task-manager/lib/usage-ledger/store.js +337 -6
- package/template/coding-task-manager/lib/usage-ledger/task-read.js +123 -5
- package/template/coding-task-manager/lib/visible-revision-watchdog.js +8 -12
- package/template/coding-task-manager/lib/wake-skip-reasons.js +46 -0
- package/template/coding-task-manager/lib/walle-supervisor.js +98 -14
- package/template/coding-task-manager/lib/worker-failure-classify.js +13 -1
- package/template/coding-task-manager/lib/ws/input.js +1 -0
- package/template/coding-task-manager/lib/ws/state-sync.js +19 -2
- package/template/coding-task-manager/lib/ws/terminal-attach.js +32 -1
- package/template/coding-task-manager/lib/ws-router.js +68 -1
- package/template/coding-task-manager/lib/ws-send-admission.js +22 -4
- package/template/coding-task-manager/oauth-proxy.js +99 -55
- package/template/coding-task-manager/package-lock.json +24 -0
- package/template/coding-task-manager/package.json +3 -0
- package/template/coding-task-manager/protocol/message-table.json +0 -10
- package/template/coding-task-manager/protocol/messages.js +0 -10
- package/template/coding-task-manager/protocol/ws-schema.json +0 -20
- package/template/coding-task-manager/protocol/ws-schema.md +0 -2
- package/template/coding-task-manager/providers/codex-mcp-extract.js +121 -19
- package/template/coding-task-manager/providers/codex-mcp.js +14 -5
- package/template/coding-task-manager/public/css/app.css +279 -2
- package/template/coding-task-manager/public/css/reviews.css +4 -0
- package/template/coding-task-manager/public/index.html +297 -240
- package/template/coding-task-manager/public/js/app/machines-page.js +1252 -0
- package/template/coding-task-manager/public/js/app/models-tab.js +546 -36
- package/template/coding-task-manager/public/js/app/worktrees-panel.js +7 -5
- package/template/coding-task-manager/public/js/background-work.js +13 -7
- package/template/coding-task-manager/public/js/client-health-core.js +81 -0
- package/template/coding-task-manager/public/js/client-health-main.js +61 -0
- package/template/coding-task-manager/public/js/coding-session-model-presentation.js +44 -4
- package/template/coding-task-manager/public/js/core/session-commands.js +676 -0
- package/template/coding-task-manager/public/js/core/session-selectors.js +5 -1
- package/template/coding-task-manager/public/js/history-app-seam.js +26 -2
- package/template/coding-task-manager/public/js/history-painted-rows.js +17 -2
- package/template/coding-task-manager/public/js/history-reader-park.js +13 -1
- package/template/coding-task-manager/public/js/history-scroller.js +187 -9
- package/template/coding-task-manager/public/js/history-transcript-pages.js +5 -5
- package/template/coding-task-manager/public/js/machine-pool-shared.js +436 -0
- package/template/coding-task-manager/public/js/private-state-scope.js +31 -3
- package/template/coding-task-manager/public/js/prompt-submission-client.js +173 -4
- package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
- package/template/coding-task-manager/public/js/protocol.js +0 -10
- package/template/coding-task-manager/public/js/queue-panel-submission-reconcile.js +174 -1
- package/template/coding-task-manager/public/js/reviews.js +7 -3
- package/template/coding-task-manager/public/js/root-draft-lifetime.js +139 -2
- package/template/coding-task-manager/public/js/root-queue-builder-lifetime.js +16 -0
- package/template/coding-task-manager/public/js/row-join.js +18 -3
- package/template/coding-task-manager/public/js/session-model-picker.js +1 -1
- package/template/coding-task-manager/public/js/session-search-utils.js +46 -19
- package/template/coding-task-manager/public/js/session-usage-view.js +1 -0
- package/template/coding-task-manager/public/js/state-sync-client.js +4 -0
- package/template/coding-task-manager/public/js/terminal/runtime-02.js +16 -7
- package/template/coding-task-manager/public/js/terminal/runtime-03.js +41 -0
- package/template/coding-task-manager/public/js/terminal/runtime-04.js +41 -4
- package/template/coding-task-manager/public/js/terminal/runtime-12.js +52 -2
- package/template/coding-task-manager/public/js/terminal/runtime-16.js +12 -12
- package/template/coding-task-manager/public/js/terminal/runtime-18.js +14 -0
- package/template/coding-task-manager/public/js/terminal/runtime-19.js +8 -4
- package/template/coding-task-manager/public/js/terminal/runtime-20.js +210 -73
- package/template/coding-task-manager/public/js/terminal/runtime-21.js +19 -6
- package/template/coding-task-manager/public/js/terminal/runtime-22.js +86 -0
- package/template/coding-task-manager/public/js/terminal-history-viewport.js +1 -3
- package/template/coding-task-manager/public/js/terminal-wheel.js +39 -2
- package/template/coding-task-manager/public/js/walle/chat-conversation.js +11 -3
- package/template/coding-task-manager/public/js/walle-session.js +30 -7
- package/template/coding-task-manager/public/m/app.css +226 -8
- package/template/coding-task-manager/public/m/app.js +502 -9
- package/template/coding-task-manager/public/m/index.html +38 -1
- package/template/coding-task-manager/public/m/machines.js +1313 -0
- package/template/coding-task-manager/public/m/pool-failover.js +391 -0
- package/template/coding-task-manager/public/m/private-queue-actions.js +26 -7
- package/template/coding-task-manager/public/m/sw.js +5 -0
- package/template/coding-task-manager/queue-engine.js +13 -0
- package/template/coding-task-manager/server/approvals.js +5 -6
- package/template/coding-task-manager/server/boot.js +136 -17
- package/template/coding-task-manager/server.js +964 -425
- package/template/coding-task-manager/session-integrity.js +7 -9
- package/template/coding-task-manager/workers/db-owner-worker.js +53 -7
- package/template/coding-task-manager/workers/read-pool-worker.js +17 -6
- package/template/coding-task-manager/workers/session-host-pool-process.js +18 -4
- package/template/coding-task-manager/workers/session-host-process.js +14 -3
- package/template/coding-task-manager/workers/transcript-archive-worker.js +1 -1
- package/template/coding-task-manager/workers/worktree-worker.js +34 -0
- package/template/package-lock.json +2 -2
- package/template/package.json +1 -1
- package/template/scripts/conversation-storage-v2-migration.js +78 -11
- package/template/shared/conversation-storage-v2-contract.js +42 -6
- package/template/shared/fixtures/worker-status-payloads.json +392 -0
- package/template/shared/git-remote-key.js +83 -0
- package/template/shared/host-identity.js +98 -2
- package/template/shared/owner-write-safety.js +135 -0
- package/template/shared/public-runtime.js +13 -5
- package/template/shared/release-schema.json +3 -2
- package/template/shared/report-citations.js +14 -0
- package/template/shared/sqlite-write-lock.js +361 -223
- package/template/shared/storage/contract.js +35 -3
- package/template/wall-e/agent.js +21 -0
- package/template/wall-e/api-walle.js +5 -0
- package/template/wall-e/bin/brain-purge-legacy-ctm.js +3 -2
- package/template/wall-e/bin/walle-mcp-stdio.js +12 -6
- package/template/wall-e/brain/helper-rules-schema.js +129 -0
- package/template/wall-e/brain/migrations.js +51 -22
- package/template/wall-e/brain/model-deployment-facts.js +31 -1
- package/template/wall-e/brain/owner-call-whitelist.js +16 -0
- package/template/wall-e/brain/schema-ensures.js +22 -0
- package/template/wall-e/brain.js +1 -1
- package/template/wall-e/coding/host-capabilities.js +2 -2
- package/template/wall-e/coding/journal-dispatcher.js +51 -0
- package/template/wall-e/coding/session-journal.js +28 -40
- package/template/wall-e/coding-orchestrator.js +23 -26
- package/template/wall-e/fly.toml +1 -1
- package/template/wall-e/http/auth.js +18 -1
- package/template/wall-e/http/blog-views-api.js +318 -0
- package/template/wall-e/http/helper-rules-api.js +321 -0
- package/template/wall-e/http/model-proxy-providers.js +3 -1
- package/template/wall-e/http/telemetry-api.js +12 -0
- package/template/wall-e/lib/blog-view-policy.js +88 -0
- package/template/wall-e/lib/blog-view-store.js +169 -0
- package/template/wall-e/lib/client-config-lock.js +2 -1
- package/template/wall-e/lib/coding-journal-writer-child-client.js +67 -18
- package/template/wall-e/lib/error-kinds.js +10 -1
- package/template/wall-e/lib/feature-manifest.js +1 -1
- package/template/wall-e/lib/helper-rules/cell-state.js +123 -0
- package/template/wall-e/lib/helper-rules/evaluate.js +357 -0
- package/template/wall-e/lib/helper-rules/explore-rubric.md +12 -0
- package/template/wall-e/lib/helper-rules/grade.js +277 -0
- package/template/wall-e/lib/helper-rules/label.js +247 -0
- package/template/wall-e/lib/helper-rules/lead-call-hook.js +522 -0
- package/template/wall-e/lib/helper-rules/store.js +667 -0
- package/template/wall-e/lib/mcp-integration.js +5 -6
- package/template/wall-e/lib/runtime-worker-pool.js +1 -0
- package/template/wall-e/lib/scheduler-worker-jobs.js +6 -0
- package/template/wall-e/lib/telemetry-db-schema.js +37 -0
- package/template/wall-e/lib/telemetry-process-client.js +76 -1
- package/template/wall-e/llm/default-fallback.js +4 -0
- package/template/wall-e/llm/model-catalog.js +13 -4
- package/template/wall-e/llm/model-roles.js +6 -0
- package/template/wall-e/llm/portkey-http.js +150 -0
- package/template/wall-e/llm/portkey-sync.js +15 -145
- package/template/wall-e/llm/worker-mcp-routing.js +61 -0
- package/template/wall-e/llm/worker-route.js +43 -1
- package/template/wall-e/loops/brain-custom-writes.js +45 -0
- package/template/wall-e/mcp-protocol-contract.js +41 -1
- package/template/wall-e/mcp-server.js +13 -2
- package/template/wall-e/mcp-worker-schemas.js +20 -2
- package/template/wall-e/memory/ctm-context-client.js +25 -5
- package/template/wall-e/shared/release-schema.json +3 -2
- package/template/wall-e/shared/sqlite-write-lock.js +361 -223
- package/template/wall-e/shared/storage/contract.js +35 -3
- package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
- package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +6 -0
- package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +7 -2
- package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +7 -2
- package/template/wall-e/tools/local-tools.js +12 -5
- package/template/wall-e/workers/brain-owner-worker.js +9 -0
- package/template/wall-e/workers/coding-journal-writer-child.js +8 -1
- package/template/wall-e/workers/telemetry-worker.js +8 -0
- package/template/coding-task-manager/lib/giant-freeze-park.js +0 -31
|
@@ -16,10 +16,11 @@ const {
|
|
|
16
16
|
} = require('./lifecycle-wake-experiment');
|
|
17
17
|
const projectConfig = require('./project-config');
|
|
18
18
|
const { resolveResumeCwdFallback } = require('./resume-cwd');
|
|
19
|
-
const { validateResumeTarget, withTrashLocation } = require('./resume-preflight');
|
|
19
|
+
const { validateResumeTarget, withTrashLocation, findClaudeResumeTranscript, transcriptHasConversation } = require('./resume-preflight');
|
|
20
20
|
const { findTrashedTranscript } = require('./session-transcript-trash');
|
|
21
21
|
const sessionHistory = require('./session-history');
|
|
22
22
|
const { extractResumeTarget, getCodexThreadResumeCwdInfo, locateCodexRollout } = sessionHistory;
|
|
23
|
+
const { ensureClaudeResumeTarget } = require('./launch-model-override');
|
|
23
24
|
const walleMcpAutoConfig = require('./walle-mcp-auto-config');
|
|
24
25
|
const { ensureWalleMcpForAgentSession } = walleMcpAutoConfig;
|
|
25
26
|
const {
|
|
@@ -228,7 +229,15 @@ async function prepareLaunchPlan(spec = {}, progressPort = parentPort) {
|
|
|
228
229
|
cwd = fallback.cwd || cwd;
|
|
229
230
|
|
|
230
231
|
let agentType = agentTypeForCommand(cmd);
|
|
231
|
-
|
|
232
|
+
// WP-0 D-2: for the PRIVATE Claude model-target operation below, the recorded source argv must reach
|
|
233
|
+
// the native-history classification UNTOUCHED. resolveResumeAlias is a naive
|
|
234
|
+
// `indexOf('--resume')` + metadata rewrite, and an argument that merely SPELLS `--resume` while being
|
|
235
|
+
// another option's operand (e.g. `--append-system-prompt --resume`) would be rewritten before the
|
|
236
|
+
// canonical normalizer ever sees it. Ordinary create/restore callers and Codex keep today's order.
|
|
237
|
+
const defersClaudeResumeAlias = agentType === 'claude' && spec.claudeResumeThreadId !== undefined;
|
|
238
|
+
let resumeResolution = defersClaudeResumeAlias
|
|
239
|
+
? { args, resume: extractResumeTarget(agentType, args) }
|
|
240
|
+
: resolveResumeAlias(agentType, args, metadata);
|
|
232
241
|
args = resumeResolution.args;
|
|
233
242
|
let resume = resumeResolution.resume;
|
|
234
243
|
|
|
@@ -283,6 +292,54 @@ async function prepareLaunchPlan(spec = {}, progressPort = parentPort) {
|
|
|
283
292
|
const cliResolution = resolvePublishedCliCommand(cmd, spec, env);
|
|
284
293
|
cmd = cliResolution.cmd;
|
|
285
294
|
agentType = agentTypeForCommand(cmd);
|
|
295
|
+
// WP-0 D-2 (same-harness, early): the project config above may have just rewritten this launch to a
|
|
296
|
+
// DIFFERENT harness, and the Codex-specific MCP/profile rewrites and prepareCodexLaunch() run long
|
|
297
|
+
// before a plan is returned. The private Claude model-target field names a Claude thread, so a launch
|
|
298
|
+
// that resolved to another harness is refused HERE -- before any of that preparation -- instead of
|
|
299
|
+
// executing Codex preparation and only then refusing the produced plan.
|
|
300
|
+
// Ordinary callers never set the private field, and Codex callers never set it either, so their
|
|
301
|
+
// behavior is unchanged. The planner's post-plan same-harness guard remains as the outer defence.
|
|
302
|
+
if (spec.claudeResumeThreadId !== undefined && agentType !== 'claude') {
|
|
303
|
+
const error = new TypeError('spec.claudeResumeThreadId names a Claude thread but the launch resolved to another harness');
|
|
304
|
+
error.code = 'launch_claude_target_harness_changed';
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
// WP-0 (docs/claude-routed-native-history-resume-2026-10-24.html §5 D-2, §6): a Claude
|
|
308
|
+
// MODEL-TARGET preparation names the canonical source thread through the PRIVATE spec field
|
|
309
|
+
// `claudeResumeThreadId`, which only the lead-transition target planner sets after its canonical
|
|
310
|
+
// source proof. Claude keeps its own native transcript, so the launch must RESUME that exact
|
|
311
|
+
// thread instead of starting the recorded `--session-id` again (which would collide with the
|
|
312
|
+
// conversation already on disk). The classification below is the EXISTING native preflight --
|
|
313
|
+
// findClaudeResumeTranscript + transcriptHasConversation -- never a second reader, and nothing is
|
|
314
|
+
// written, moved, deleted or materialized here.
|
|
315
|
+
//
|
|
316
|
+
// A source with no recorded conversation is left EXACTLY as it is (today's fresh-launch
|
|
317
|
+
// behavior): forcing `--resume` onto a metadata-only stub is what the CLI answers with
|
|
318
|
+
// "No conversation found". The field is private to this worker spec, so an ordinary
|
|
319
|
+
// create/restore caller -- which never sets it -- cannot reach this block at all.
|
|
320
|
+
if (agentType === 'claude' && spec.claudeResumeThreadId !== undefined) {
|
|
321
|
+
const canonicalThreadId = String(spec.claudeResumeThreadId || '').trim();
|
|
322
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(canonicalThreadId)) {
|
|
323
|
+
const error = new TypeError('spec.claudeResumeThreadId must be the canonical Claude thread id');
|
|
324
|
+
error.code = 'launch_claude_resume_target_invalid';
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
327
|
+
const canonicalTranscript = findClaudeResumeTranscript({
|
|
328
|
+
resumeId: canonicalThreadId,
|
|
329
|
+
jsonlPath: metadata.resumeAgent?.jsonl_path || metadata.resolved?.jsonl_path || '',
|
|
330
|
+
homeDir,
|
|
331
|
+
claudeConfigDir: env.CLAUDE_CONFIG_DIR || spec.claudeConfigDir,
|
|
332
|
+
});
|
|
333
|
+
if (canonicalTranscript && transcriptHasConversation(canonicalTranscript.path)) {
|
|
334
|
+
const normalized = ensureClaudeResumeTarget(args, canonicalThreadId);
|
|
335
|
+
if (!normalized.ok) {
|
|
336
|
+
const error = new TypeError(`the recorded Claude launch arguments cannot name the canonical thread: ${normalized.reason}`);
|
|
337
|
+
error.code = normalized.reason;
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
args = normalized.args;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
286
343
|
resumeResolution = resolveResumeAlias(agentType, args, metadata);
|
|
287
344
|
args = resumeResolution.args;
|
|
288
345
|
resume = resumeResolution.resume;
|
|
@@ -172,15 +172,30 @@ function wrapResolver(inner) {
|
|
|
172
172
|
const results = await inner(outgoing);
|
|
173
173
|
if (current && wanted.size && Array.isArray(results)) {
|
|
174
174
|
const itemsById = new Map(list.map((it) => [String(it.id), it]));
|
|
175
|
+
const answered = new Set();
|
|
175
176
|
for (const result of results) {
|
|
176
177
|
const id = String(result && result.id);
|
|
177
178
|
if (!wanted.has(id)) continue;
|
|
179
|
+
answered.add(id);
|
|
178
180
|
const obs = turnObservationFrom(itemsById.get(id), result);
|
|
181
|
+
// A pending switch settles only on this observation; every drop point is otherwise silent.
|
|
182
|
+
const trace = (verdict) => console.warn(`[lead-model-observer] ${id.slice(0, 8)} ${JSON.stringify({
|
|
183
|
+
partial: result?.partial ?? null, coverage: result?.scanCoverage ?? result?.modelScanCheckpoint?.coverage ?? null,
|
|
184
|
+
turnContext: result?.turnContext ? { model: result.turnContext.model, outcome: result.turnContext.outcome ?? null } : null,
|
|
185
|
+
observations: Array.isArray(result?.observations) ? result.observations.map((o) => `${o?.kind}:${o?.source}`) : null,
|
|
186
|
+
obs: obs ? { model: obs.model, outcome: obs.outcome ?? null, turnId: obs.turnId ?? null,
|
|
187
|
+
processGeneration: obs.processGeneration ?? null, providerThreadId: obs.providerThreadId ?? null,
|
|
188
|
+
routeId: obs.routeId ?? null, hasProfile: Object.hasOwn(obs, 'profileFingerprint'),
|
|
189
|
+
hasFacts: Object.hasOwn(obs, 'factsRevision') } : null,
|
|
190
|
+
verdict })}`);
|
|
179
191
|
if (obs) {
|
|
180
192
|
const report = (error) => console.warn(`[lead-model-observer] observe failed for ${id.slice(0, 8)}: ${error && error.message || error}`);
|
|
181
|
-
try { Promise.resolve(current.observe(id, obs)).
|
|
193
|
+
try { Promise.resolve(current.observe(id, obs)).then((verdict) => trace(verdict), report); } // REPORT: an observer failure must never break model sync
|
|
182
194
|
catch (error) { report(error); }
|
|
183
|
-
}
|
|
195
|
+
} else trace({ settled: false, ignored: 'no_turn_observation' });
|
|
196
|
+
}
|
|
197
|
+
for (const id of wanted) {
|
|
198
|
+
if (!answered.has(id)) console.warn(`[lead-model-observer] ${id.slice(0, 8)} {"verdict":"no_result"}`);
|
|
184
199
|
}
|
|
185
200
|
}
|
|
186
201
|
return results;
|
|
@@ -217,6 +217,13 @@ function createLeadTransitionTargetPlanner(deps = {}) {
|
|
|
217
217
|
const cwd = task.cwd || session.cwd || '';
|
|
218
218
|
const agentType = detectAgentType(cmd);
|
|
219
219
|
const resume = extractResumeTarget(agentType, args);
|
|
220
|
+
// D-2 (docs/claude-routed-native-history-resume-2026-10-24.html §5 D-2, §6): a Claude target
|
|
221
|
+
// launch must name the canonical SOURCE thread -- never an old startup/parent selector still
|
|
222
|
+
// sitting in the recorded argv -- so the canonical owner's own provider thread is what the
|
|
223
|
+
// metadata read is keyed by, and the private spec field below is what the launch-plan worker
|
|
224
|
+
// classifies/normalizes against. Both are derived ONLY here, after the canonical source proof
|
|
225
|
+
// below; no client/API caller and no ordinary create/restore caller sets either.
|
|
226
|
+
const canonicalThreadId = String(source?.providerThreadId || '');
|
|
220
227
|
// The canonical SOURCE proof: the injected model runtime's owner must name THIS session, agent and
|
|
221
228
|
// provider thread at exactly the submitted generation — generation 0 included. A missing owner is
|
|
222
229
|
// refused here, BEFORE any reservation, metadata read or plan.
|
|
@@ -241,12 +248,19 @@ function createLeadTransitionTargetPlanner(deps = {}) {
|
|
|
241
248
|
const generation = reserved.generation;
|
|
242
249
|
const launchMetadata = await runReadPoolOp('readLaunchMetadata', {
|
|
243
250
|
dbPath: dbModule.getDbPath(), baseDbPath: dbModule.getDbPath(), sessionId,
|
|
244
|
-
resumeId:
|
|
251
|
+
resumeId: agentType === 'claude' ? canonicalThreadId : (resume?.sessionId || ''),
|
|
252
|
+
telemetryReason: 'lead-transition-target-preflight',
|
|
245
253
|
}, { timeoutMs: 10_000, signal });
|
|
246
254
|
if (identityGone()) return { ok: false, reason: 'lead_source_generation_changed' };
|
|
247
255
|
cacheSessionOwnershipProjection(sessionId, launchMetadata?.resolved || launchMetadata || null);
|
|
248
256
|
const mcpProfile = sessionMcpProfileSetting(task);
|
|
249
|
-
|
|
257
|
+
// Only Codex gathers warm-plan inputs. A Claude model-target preparation opts out of that cache
|
|
258
|
+
// entirely, because its key (transcript freshness, cwd, codex config, CLI stamp) cannot describe
|
|
259
|
+
// the same-thread resume operation below -- and a value that is computed only to be discarded
|
|
260
|
+
// would still cost the read.
|
|
261
|
+
const warmPlanInputs = agentType === 'codex'
|
|
262
|
+
? await launchPlanWarmInputs({ agentType, cwd, launchMetadata, mcpProfile })
|
|
263
|
+
: null;
|
|
250
264
|
if (identityGone()) return { ok: false, reason: 'lead_source_generation_changed' };
|
|
251
265
|
const capturedLaunch = session._modelTransitionLaunchContext || {};
|
|
252
266
|
const launchHomeDir = session._launchEnvOverrides?.HOME || capturedLaunch.homeDir || serverConfig.HOME;
|
|
@@ -262,11 +276,36 @@ function createLeadTransitionTargetPlanner(deps = {}) {
|
|
|
262
276
|
env: targetPlanEnv,
|
|
263
277
|
homeDir: launchHomeDir, cliCacheFile: agentCliCacheFile, codexHome: launchCodexHome,
|
|
264
278
|
codexTransportGuard: serverConfig.CTM_CODEX_MCP_TRANSPORT_GUARD !== '0', wallePort,
|
|
265
|
-
isRestore: true, launchMetadata, mcpProfile,
|
|
279
|
+
isRestore: true, launchMetadata, mcpProfile,
|
|
280
|
+
warmPlanInputs,
|
|
281
|
+
lane: 'interactive',
|
|
266
282
|
transcriptTrashRoot: sessionTranscriptTrashRoot,
|
|
283
|
+
// PRIVATE internal argument (never persisted, never published, never accepted from a client):
|
|
284
|
+
// the canonical source thread the produced plan must resume. Only this planner sets it.
|
|
285
|
+
...(agentType === 'claude' ? { claudeResumeThreadId: canonicalThreadId } : {}),
|
|
267
286
|
});
|
|
268
287
|
if (identityGone()) return { ok: false, reason: 'lead_source_generation_changed' };
|
|
269
288
|
if (!result?.plan || Number(result.plan.generation) !== generation) return { ok: false, reason: 'lead_target_plan_unavailable' };
|
|
289
|
+
// D-2/D-3: the produced plan must still name the canonical thread in its argv AND in its resume
|
|
290
|
+
// metadata, and the two must AGREE about whether there is a resume target at all.
|
|
291
|
+
// `resolveResumeAlias` may re-resolve a Claude resume id out of the metadata row, so a plan that
|
|
292
|
+
// ends up on ANOTHER thread is refused here -- before the controller ever parks the source. Both
|
|
293
|
+
// absent is the legitimate native no-history fresh launch; exactly one absent is an inconsistent
|
|
294
|
+
// plan (an argv the launcher will act on that the metadata denies, or the reverse).
|
|
295
|
+
if (agentType === 'claude') {
|
|
296
|
+
// Same-harness guard: project config's applyToSpec may substitute the live Claude `cmd`, so a
|
|
297
|
+
// plan for ANOTHER harness (or a plan that does not name its harness at all) is refused here
|
|
298
|
+
// rather than being accepted as this planner's fresh/both-absent result.
|
|
299
|
+
if (result.plan.agentType !== 'claude') return { ok: false, reason: 'lead_target_harness_changed' };
|
|
300
|
+
const planArgs = Array.isArray(result.plan.args) ? result.plan.args : [];
|
|
301
|
+
const argvResume = extractResumeTarget('claude', planArgs);
|
|
302
|
+
const argvId = argvResume ? String(argvResume.sessionId || '') : '';
|
|
303
|
+
const metadataId = String(result.plan.resume?.sessionId || '');
|
|
304
|
+
if (!!argvId !== !!metadataId) return { ok: false, reason: 'lead_target_resume_inconsistent' };
|
|
305
|
+
if (argvId && (argvId !== canonicalThreadId || metadataId !== canonicalThreadId)) {
|
|
306
|
+
return { ok: false, reason: 'lead_target_resume_changed' };
|
|
307
|
+
}
|
|
308
|
+
}
|
|
270
309
|
noteLaunchPlanReadinessGeneration(result.plan);
|
|
271
310
|
return { ok: true, generation, launchPlan: result.plan, sourceGeneration };
|
|
272
311
|
};
|
|
@@ -670,7 +709,10 @@ function createLeadModelSwitchRuntime(host) {
|
|
|
670
709
|
const targetRecipe = {
|
|
671
710
|
id: targetRecipeId, sessionId, providerThreadId: source.providerThreadId, harness,
|
|
672
711
|
providerId: target.routeId || 'unknown', origin: target.routeId ? 'routed-proxy' : 'unknown',
|
|
673
|
-
|
|
712
|
+
// The marker names the Codex app-server conversion adapter. A Claude target -- routed or
|
|
713
|
+
// native -- has no conversion adapter at all, so recording the Codex one there would claim a
|
|
714
|
+
// conversion this operation never performs (same-harness native resume).
|
|
715
|
+
controlAdapterVersion: harness === 'codex-cli' && target.routeId ? CODEX_CONTROL_ADAPTER : null,
|
|
674
716
|
programVersion: null, cyberAccessProgram: null,
|
|
675
717
|
routeId: target.routeId || null,
|
|
676
718
|
wireModelId: target.routeId ? (target.modelId || null) : null, processGeneration: targetPlan.generation,
|
|
@@ -758,6 +800,17 @@ function createLeadModelSwitchRuntime(host) {
|
|
|
758
800
|
const rebound = require('./launch-model-override').ensureCodexResumeTarget(recoveryArgs, source.providerThreadId);
|
|
759
801
|
if (!rebound.ok) return { ok: false, reason: rebound.reason };
|
|
760
802
|
recoveryArgs = rebound.args;
|
|
803
|
+
} else if (source.agent === 'claude') {
|
|
804
|
+
// The SAME normalization for a native Claude source. A captured Claude argv may select its
|
|
805
|
+
// thread through `--resume` OR through an explicit `--session-id` (a fresh identity CTM
|
|
806
|
+
// injected at startup), and it may still carry that launch's own initial positional prompt.
|
|
807
|
+
// The existing pure normalizer produces exactly one canonical `--resume <source thread>`
|
|
808
|
+
// prefix: every permission/settings operand survives, while the selector that named the
|
|
809
|
+
// thread and the one-shot startup prompt do not -- so a rollback resumes the SAME
|
|
810
|
+
// conversation instead of replaying the original task as a new turn.
|
|
811
|
+
const rebound = require('./launch-model-override').ensureClaudeResumeTarget(recoveryArgs, source.providerThreadId);
|
|
812
|
+
if (!rebound.ok) return { ok: false, reason: rebound.reason };
|
|
813
|
+
recoveryArgs = rebound.args;
|
|
761
814
|
}
|
|
762
815
|
const selectedSourceThread = extractResumeTarget(source.agent, recoveryArgs)?.sessionId || '';
|
|
763
816
|
if (selectedSourceThread !== sourceThreadId) return { ok: false, reason: 'source_launch_context_unavailable' };
|
|
@@ -393,6 +393,7 @@ function createLeadModelSwitch(deps) {
|
|
|
393
393
|
entry.recovered = !!result?.ok;
|
|
394
394
|
if (!entry.recovered) {
|
|
395
395
|
entry.req.recovery_reason = 'source_recovery_failed';
|
|
396
|
+
diagnostic(entry.req.ctm_session_id, 'lead-transition-source-recovery-failed', { reason: String(result?.reason || 'unknown').slice(0, 120) });
|
|
396
397
|
const cause = entry.failureOutcome?.detail || 'The requested target was not applied.';
|
|
397
398
|
entry.req.detail = `${cause} The captured source provider could not be restored.`;
|
|
398
399
|
} else {
|
|
@@ -480,7 +481,7 @@ function createLeadModelSwitch(deps) {
|
|
|
480
481
|
return refuse(409, 'client_request_conflict');
|
|
481
482
|
}
|
|
482
483
|
if (existing?.confirmationPromise) return refuse(409, 'lead_transition_committing', { request: publicRequest(existing.req) });
|
|
483
|
-
if (existing?.handoffStarted && !existing.recovered && ['cancelled', 'failed'].includes(existing.req.state)) {
|
|
484
|
+
if (existing?.handoffStarted && (existing.sourceParked || existing.targetStarted) && !existing.recovered && ['cancelled', 'failed'].includes(existing.req.state)) {
|
|
484
485
|
return refuse(409, 'lead_switch_recovery_in_flight', { request: publicRequest(existing.req) });
|
|
485
486
|
}
|
|
486
487
|
if (existing?.req.state === 'awaiting_turn' && existing.handoffStarted && !existing.recovered) {
|
|
@@ -767,7 +768,7 @@ function createLeadModelSwitch(deps) {
|
|
|
767
768
|
return refuse(500, 'lead_transition_persist_failed', { message: String(error?.message || error).slice(0, 200) });
|
|
768
769
|
}
|
|
769
770
|
}
|
|
770
|
-
if (providerTransition) {
|
|
771
|
+
if (providerTransition && info.agent !== 'claude') {
|
|
771
772
|
if (!entry.transition) { requestControllers.delete(id); fail(entry, 'lead_transition_owner_unavailable', 'The transition owner is not available.'); return refuse(409, 'lead_transition_owner_unavailable'); }
|
|
772
773
|
try {
|
|
773
774
|
historyPreparation = await prepareHistory({
|
|
@@ -783,6 +784,14 @@ function createLeadModelSwitch(deps) {
|
|
|
783
784
|
targetRecipe: entry.transition.targetRecipe,
|
|
784
785
|
preparedProfile: routed?.preparedProfile || targetPlan?.preparedProfile || null,
|
|
785
786
|
signal: controller.signal,
|
|
787
|
+
// D-5/R-1: the source model is writing the handoff summary, which spends a GPT turn and
|
|
788
|
+
// can take minutes. The journal records the phase (preparing -> compacting -> verified)
|
|
789
|
+
// and the public request carries the bounded detail line the client already renders.
|
|
790
|
+
onPhase: async (phase, detail) => {
|
|
791
|
+
if (phase !== 'compacting' || !ownsRequest()) return;
|
|
792
|
+
await advanceJournal(entry, 'compacting');
|
|
793
|
+
transition(entry, 'preparing', { phase: 'compacting', detail: String(detail).slice(0, 200) });
|
|
794
|
+
},
|
|
786
795
|
});
|
|
787
796
|
} catch (error) { historyPreparation = { ok: false, reason: error?.code || 'history_preparation_failed' }; }
|
|
788
797
|
if (!ownsRequest()) return superseded();
|
|
@@ -4,11 +4,13 @@ const fs = require('node:fs');
|
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
const crypto = require('node:crypto');
|
|
6
6
|
const { inspectTransition } = require('./harness-history-transition');
|
|
7
|
-
const { createCodexNativeHistoryControl } = require('./native-history-control');
|
|
7
|
+
const { createCodexNativeHistoryControl, handoffMessage } = require('./native-history-control');
|
|
8
|
+
const { writeSourceHandoff } = require('./codex-source-handoff');
|
|
8
9
|
const { effectiveOutputCap } = require('./model-proxy/output-cap');
|
|
9
10
|
const { workerCapabilityForModel } = require('./model-proxy/routes');
|
|
10
11
|
const { rebindCodexForkLaunchPlan } = require('./launch-model-override');
|
|
11
12
|
const codexPaths = require('./codex-paths');
|
|
13
|
+
const { _probeVersion } = require('./harness-model-profile');
|
|
12
14
|
// S-6: the receipt id and the default control-adapter id are qualified-table constants.
|
|
13
15
|
const { CODEX_COMPACTION_SCHEMA_RECEIPT, CODEX_CONTROL_ADAPTER, isQualifiedCodexVersion } =
|
|
14
16
|
require('./codex-harness-qualification');
|
|
@@ -71,6 +73,9 @@ function createLeadModelTransitionRuntime(host = {}) {
|
|
|
71
73
|
// The GENUINE native-config receipt checker. Existing callers omit it and keep the real behavior;
|
|
72
74
|
// a test may inject a wrapper that delegates to it (never an always-true verifier).
|
|
73
75
|
configReceiptMatches = codexConfigReceiptMatches,
|
|
76
|
+
// The measured `--version` of the source executable the fork runs. A test whose executable is
|
|
77
|
+
// not a Codex binary injects the version that binary would report.
|
|
78
|
+
probeSourceProgramVersion = _probeVersion,
|
|
74
79
|
transitionControlAdapterVersion = CODEX_CONTROL_ADAPTER } = host;
|
|
75
80
|
if (typeof getSession !== 'function' || typeof runReadPoolOp !== 'function'
|
|
76
81
|
|| typeof dbModule?.getDbPath !== 'function') throw new TypeError('lead history runtime requires session, read-pool, and DB-owner access');
|
|
@@ -218,6 +223,13 @@ function createLeadModelTransitionRuntime(host = {}) {
|
|
|
218
223
|
)) ? { ok: true } : { ok: false, reason: 'source_config_policy_unqualified' };
|
|
219
224
|
try { sourceLaunch.executablePath = await fs.promises.realpath(sourceLaunch.executablePath); }
|
|
220
225
|
catch { return refuse('source_binary_unavailable'); }
|
|
226
|
+
// The fork and the handoff run THIS binary, so its measured version is what the adapter
|
|
227
|
+
// qualifies. The rollout's session_meta cli_version names the binary that CREATED the thread
|
|
228
|
+
// (resumes append none), e.g. 0.147.0 for a thread still resumed today by 0.163.0.
|
|
229
|
+
let sourceProgramVersion;
|
|
230
|
+
try { sourceProgramVersion = await probeSourceProgramVersion(sourceLaunch.executablePath, signal); }
|
|
231
|
+
catch { return refuse('source_binary_unavailable'); }
|
|
232
|
+
effectiveSource.programVersion = sourceProgramVersion;
|
|
221
233
|
let policyOverrides;
|
|
222
234
|
try {
|
|
223
235
|
const argvPolicyOverrides = extractCodexPolicyOverrides(captured.args, {
|
|
@@ -242,17 +254,35 @@ function createLeadModelTransitionRuntime(host = {}) {
|
|
|
242
254
|
try {
|
|
243
255
|
nativeControl = createCodexNativeHistoryControl({
|
|
244
256
|
sourceRecipe: { controlAdapterVersion: transitionControlAdapterVersion,
|
|
245
|
-
programVersion:
|
|
257
|
+
programVersion: sourceProgramVersion,
|
|
246
258
|
cyberAccessProgram: snapshot.previousSettings.cyberAccessProgram,
|
|
247
259
|
approvalPolicy: snapshot.previousSettings.approvalPolicy,
|
|
248
260
|
sandboxPolicy: snapshot.previousSettings.sandboxPolicy, ...sourceLaunch },
|
|
249
261
|
targetRecipe: targetControlRecipe,
|
|
250
262
|
});
|
|
251
263
|
} catch (error) { return refuse(error?.code || 'native_adapter_unqualified'); }
|
|
264
|
+
// D-5/D-6: the source model writes the plaintext handoff that will cover its sealed compaction.
|
|
265
|
+
// It runs here -- after the source launch context and config receipt were validated and before
|
|
266
|
+
// anything is parked or staged -- so a refusal costs one GPT turn and no state change.
|
|
267
|
+
let handoff = null;
|
|
268
|
+
if (decision.handoff?.required) {
|
|
269
|
+
const sourceModel = nonempty(snapshot.previousSettings?.model) || nonempty(source.modelId);
|
|
270
|
+
if (!sourceModel) return refuse('source_model_changed');
|
|
271
|
+
await input.onPhase?.('compacting', `Writing a handoff summary with ${sourceModel}`);
|
|
272
|
+
const written = await writeSourceHandoff(sourceLaunch, { sourceThreadId: source.providerThreadId,
|
|
273
|
+
lastTurnId: snapshot.checkpoint.id }, { signal });
|
|
274
|
+
if (!sourceIdentityHolds()) return refuse('lead_source_changed');
|
|
275
|
+
if (!written.ok) return refuse(written.reason);
|
|
276
|
+
const message = handoffMessage({ compactionSha256: decision.handoff.compactionSha256,
|
|
277
|
+
text: written.text, sourceModel });
|
|
278
|
+
handoff = { compactionSha256: decision.handoff.compactionSha256, text: written.text,
|
|
279
|
+
textSha256: crypto.createHash('sha256').update(message.content[0].text).digest('hex'),
|
|
280
|
+
sourceModel };
|
|
281
|
+
}
|
|
252
282
|
return { ok: true, action: 'fork', planAction: decision.action, reason: decision.reason, decision, snapshot,
|
|
253
283
|
sourceRecipe: effectiveSource, targetRecipe: effectiveTarget,
|
|
254
284
|
nativeControl, sourceLaunch, targetControlRecipe, revalidateTargetProfile, revalidateSourceConfig,
|
|
255
|
-
storageRootReceipt,
|
|
285
|
+
storageRootReceipt, handoff,
|
|
256
286
|
controlPolicy: { approvalPolicy: snapshot.previousSettings.approvalPolicy,
|
|
257
287
|
sandboxPolicy: snapshot.previousSettings.sandboxPolicy,
|
|
258
288
|
cyberAccessProgram: snapshot.previousSettings.cyberAccessProgram } };
|
|
@@ -306,7 +336,8 @@ function createLeadModelTransitionRuntime(host = {}) {
|
|
|
306
336
|
try {
|
|
307
337
|
nativeReceipt = await historyPreparation.nativeControl.forkCheckpoint({ sourceThreadId: source.providerThreadId,
|
|
308
338
|
lastTurnId: historyPreparation.snapshot.checkpoint.id,
|
|
309
|
-
sourceGeneration: historyPreparation.snapshot.generation },
|
|
339
|
+
sourceGeneration: historyPreparation.snapshot.generation },
|
|
340
|
+
{ signal, targetAuthToken: token, handoff: historyPreparation.handoff || undefined });
|
|
310
341
|
} catch (error) { return refuse(error?.code || 'native_history_control_failed'); }
|
|
311
342
|
if (signal?.aborted) return refuse('transition_cancelled');
|
|
312
343
|
if (parkedGone(getSession(sessionId))) return refuse('source_owner_changed_after_read');
|
|
@@ -317,6 +348,12 @@ function createLeadModelTransitionRuntime(host = {}) {
|
|
|
317
348
|
targetGeneration: transition.targetGeneration, sourceCheckpointId: historyPreparation.snapshot.checkpoint.id,
|
|
318
349
|
sourceContentDigest: historyPreparation.snapshot.contentDigest, targetThreadId: nativeReceipt.targetThreadId,
|
|
319
350
|
storageRootReceipt: historyPreparation.storageRootReceipt,
|
|
351
|
+
// D-8: the readback must prove the EXACT handoff that was injected, so the controller hands
|
|
352
|
+
// the reader the two digests it wrote instead of trusting the inject call's own result.
|
|
353
|
+
expectedHandoff: historyPreparation.handoff
|
|
354
|
+
? { compactionSha256: historyPreparation.handoff.compactionSha256,
|
|
355
|
+
textSha256: historyPreparation.handoff.textSha256 }
|
|
356
|
+
: null,
|
|
320
357
|
}, { timeoutMs: 30_000, signal });
|
|
321
358
|
if (signal?.aborted) return refuse('transition_cancelled');
|
|
322
359
|
if (parkedGone(getSession(sessionId))) return refuse('source_owner_changed_after_read');
|
|
@@ -385,6 +422,7 @@ function createLeadModelTransitionRuntime(host = {}) {
|
|
|
385
422
|
}
|
|
386
423
|
|
|
387
424
|
function refuse(reason) { return { ok: false, reason }; }
|
|
425
|
+
function nonempty(value) { return typeof value === 'string' && value.trim() ? value.trim() : ''; }
|
|
388
426
|
|
|
389
427
|
function extractCodexPolicyOverrides(argv, expected = {}) {
|
|
390
428
|
if (!Array.isArray(argv)) throw Object.assign(new Error('source launch args unavailable'), { code: 'source_policy_override_unqualified' });
|
|
@@ -261,11 +261,41 @@ function createLeadModelTransitionStore({
|
|
|
261
261
|
const getActiveForSession = (ctmSessionId) => {
|
|
262
262
|
const sessionId = String(ctmSessionId || '').trim();
|
|
263
263
|
if (!sessionId || sessionId.length > 128) return null;
|
|
264
|
-
const
|
|
264
|
+
const db = getCoreDb();
|
|
265
|
+
// An explicit CTM_DB_DOMAIN_SPLIT=0 boot never stamps v18, so the v49 journal is never
|
|
266
|
+
// created there. A journal that does not exist holds no active transition: answer that
|
|
267
|
+
// truthfully instead of throwing, which made every deferred restore on such a boot refuse
|
|
268
|
+
// with lead-transition-reconciliation-unavailable.
|
|
269
|
+
if (!db.prepare("SELECT 1 AS present FROM sqlite_master WHERE type='table' AND name='coding_model_transitions'").get()) return null;
|
|
270
|
+
const row = db.prepare(`SELECT transition_id FROM coding_model_transitions
|
|
265
271
|
WHERE ctm_session_id=? AND state IN ('queued','preparing','compacting','verified','handing_off','awaiting_turn')
|
|
266
272
|
ORDER BY updated_at DESC, transition_id DESC LIMIT 1`).get(sessionId);
|
|
267
273
|
return row ? get(row.transition_id) : null;
|
|
268
274
|
};
|
|
275
|
+
// WP-1 (docs/codex-routed-fork-cold-restore-guard-2026-10-22.html §5 D-3, §9 S-6): the journal
|
|
276
|
+
// reader the launch-time route reconciliation needs. `getActiveForSession` excludes terminal
|
|
277
|
+
// states, but the repair must find the fork a transition ACTUALLY launched even after it failed, so
|
|
278
|
+
// this reads ANY state, newest revision first, with the parsed target recipe beside the row
|
|
279
|
+
// identity. Read-only and bounded; a malformed recipe JSON is skipped, never surfaced.
|
|
280
|
+
const listForThread = (ctmSessionId, providerThreadId) => {
|
|
281
|
+
const sessionId = String(ctmSessionId || '').trim();
|
|
282
|
+
const threadId = String(providerThreadId || '').trim();
|
|
283
|
+
if (!sessionId || sessionId.length > 128 || !threadId || threadId.length > 512) return [];
|
|
284
|
+
const rows = getCoreDb().prepare(`SELECT transition_id, revision, state, target_recipe_json
|
|
285
|
+
FROM coding_model_transitions WHERE ctm_session_id = ? ORDER BY revision DESC, updated_at DESC, transition_id DESC`)
|
|
286
|
+
.all(sessionId);
|
|
287
|
+
const out = [];
|
|
288
|
+
for (const row of rows) {
|
|
289
|
+
let targetRecipe;
|
|
290
|
+
try { targetRecipe = JSON.parse(row.target_recipe_json); } catch { continue; /* expected: a corrupt recipe is not a route source */ }
|
|
291
|
+
out.push({
|
|
292
|
+
transition_id: row.transition_id, revision: Number(row.revision), state: row.state, target_recipe: targetRecipe,
|
|
293
|
+
provider_thread_id: targetRecipe?.providerThreadId || null, route_id: targetRecipe?.routeId || null,
|
|
294
|
+
model: targetRecipe?.wireModelId || null, harness: targetRecipe?.harness || null,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return out;
|
|
298
|
+
};
|
|
269
299
|
function saveProfile(profile = {}) {
|
|
270
300
|
const allowed = new Set(['fingerprint', 'harness', 'cliVersion', 'adapterVersion', 'deployment', 'artifactPath', 'evidenceReceipt']);
|
|
271
301
|
if (Object.keys(profile).some((key) => !allowed.has(key))) throw new TypeError('profile contains an unapproved field');
|
|
@@ -690,11 +720,39 @@ function createLeadModelTransitionStore({
|
|
|
690
720
|
if (!row) return null;
|
|
691
721
|
try { return { ...row, bindings: JSON.parse(row.bindings_json) }; } catch { return null; }
|
|
692
722
|
}
|
|
693
|
-
return { get, getActiveForSession, getStagedToken, saveProfile, begin, advance, commit, stageToken, recordFork, clearStagedToken };
|
|
723
|
+
return { get, getActiveForSession, listForThread, getStagedToken, saveProfile, begin, advance, commit, stageToken, recordFork, clearStagedToken };
|
|
694
724
|
}
|
|
695
725
|
|
|
696
726
|
// `validateRecipe` is exported so the approved-intent store can reuse the ONE complete bounded
|
|
697
727
|
// journal recipe validation instead of keeping a parallel whitelist that silently drops (or refuses)
|
|
698
728
|
// the real producer's provider/context metadata. This module imports only the PURE intent contract,
|
|
699
729
|
// so the store importing this export introduces no construction cycle.
|
|
700
|
-
|
|
730
|
+
// The provider thread of a FORK the session's active transition is still trying, or null. Until
|
|
731
|
+
// commit() moves the startup pointer, that thread is a candidate: the startup row keeps the source so
|
|
732
|
+
// a cold start before the first confirmed target turn resumes the source. Every startup-pointer writer
|
|
733
|
+
// (db.js updateStartupTaskAgentSession) asks this on the same handle, inside its own transaction.
|
|
734
|
+
function activeCandidateForkThread(db, ctmSessionId) {
|
|
735
|
+
// A core schema without the journal (pre-migration fixtures) has no transitions, so no candidate.
|
|
736
|
+
if (!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='coding_model_transitions'").get()) return null;
|
|
737
|
+
const row = db.prepare(`SELECT source_recipe_json, target_recipe_json FROM coding_model_transitions
|
|
738
|
+
WHERE ctm_session_id=? AND state IN (${[...ACTIVE_STATES].map(() => '?').join(',')})
|
|
739
|
+
ORDER BY updated_at DESC, transition_id DESC LIMIT 1`).get(String(ctmSessionId || ''), ...ACTIVE_STATES);
|
|
740
|
+
if (!row) return null;
|
|
741
|
+
let source, target;
|
|
742
|
+
try { source = JSON.parse(row.source_recipe_json); target = JSON.parse(row.target_recipe_json); }
|
|
743
|
+
catch { return null; } // expected: a malformed journal row is refused by every transition reader; it names no candidate
|
|
744
|
+
const thread = String(target?.providerThreadId || '');
|
|
745
|
+
return thread && thread !== String(source?.providerThreadId || '') ? thread : null;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// The startup-pointer writers' guard (db.js updateStartupTaskAgentSession). Live: a fork relaunch wrote
|
|
749
|
+
// `resume <target>` and the commit then refused "startup task does not point at the captured source
|
|
750
|
+
// provider thread".
|
|
751
|
+
function resumesCandidateFork(db, ctmSessionId, resumeArgs) {
|
|
752
|
+
const candidate = Array.isArray(resumeArgs) ? activeCandidateForkThread(db, ctmSessionId) : null;
|
|
753
|
+
if (!candidate || !resumeArgs.some((arg) => String(arg) === candidate)) return false;
|
|
754
|
+
console.warn(`[startup-task] kept the source pointer for ${String(ctmSessionId).slice(0, 8)}: ${candidate.slice(0, 8)} is an uncommitted fork`);
|
|
755
|
+
return true;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
module.exports = { ACTIVE_STATES, createLeadModelTransitionStore, validateRecipe, activeCandidateForkThread, resumesCandidateFork };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { createMediaJournalControlReader } = require('./media-journal-control-reader');
|
|
5
|
+
|
|
6
|
+
function unavailable(cause) {
|
|
7
|
+
return Object.assign(new Error('Pending image cancellation recovery is unavailable', { cause }), {
|
|
8
|
+
code: 'CTM_MEDIA_JOURNAL_UNAVAILABLE',
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// One recovery scope per logical DB handle and journal directory. No new SQL owner: the caller
|
|
13
|
+
// supplies the same worker projector used by the write-behind store. Neither journal nor cursor
|
|
14
|
+
// is advanced, because unrelated content must remain available to its ordinary replay owner.
|
|
15
|
+
function createLegacyDraftCancellationRecovery({ getDirectory, getContext, projectEntry }) {
|
|
16
|
+
for (const callback of [getDirectory, getContext, projectEntry]) {
|
|
17
|
+
if (typeof callback !== 'function') throw new TypeError('Cancellation recovery requires callbacks');
|
|
18
|
+
}
|
|
19
|
+
let scope = null;
|
|
20
|
+
let operation = null;
|
|
21
|
+
let retired = false;
|
|
22
|
+
let closing = null;
|
|
23
|
+
|
|
24
|
+
function binding() {
|
|
25
|
+
const context = getContext();
|
|
26
|
+
const directory = getDirectory();
|
|
27
|
+
if (!context || !directory) throw new Error('Cancellation recovery has no open storage context');
|
|
28
|
+
return { context, directory: path.resolve(directory) };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function assertCurrent(owner) {
|
|
32
|
+
if (retired || owner !== scope) throw new Error('Cancellation recovery scope is retired');
|
|
33
|
+
const current = binding();
|
|
34
|
+
if (current.context !== owner.context || current.directory !== owner.directory) {
|
|
35
|
+
throw new Error('Cancellation recovery storage context changed');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function closeReader(owner) {
|
|
40
|
+
if (!owner?.reader) return;
|
|
41
|
+
await owner.reader.close();
|
|
42
|
+
owner.reader = null; // Retain an unconfirmed resource owner on failure, not just its descriptor.
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function recover(current) {
|
|
46
|
+
await closeReader(scope);
|
|
47
|
+
const owner = scope = { ...current, reader: null, ready: false };
|
|
48
|
+
assertCurrent(owner);
|
|
49
|
+
const reader = owner.reader = createMediaJournalControlReader({
|
|
50
|
+
directory: owner.directory, assertCurrent: () => assertCurrent(owner),
|
|
51
|
+
});
|
|
52
|
+
let records;
|
|
53
|
+
let failed = false;
|
|
54
|
+
let failure;
|
|
55
|
+
try { records = await reader.read(); }
|
|
56
|
+
catch (error) { failed = true; failure = error; }
|
|
57
|
+
try { await closeReader(owner); }
|
|
58
|
+
catch (error) {
|
|
59
|
+
throw failed
|
|
60
|
+
? new AggregateError([failure, error], 'Cancellation recovery and reader cleanup failed', { cause: failure })
|
|
61
|
+
: error;
|
|
62
|
+
}
|
|
63
|
+
if (failed) throw failure;
|
|
64
|
+
// Close every read handle BEFORE the first SQL projection. A partial projection remains
|
|
65
|
+
// idempotently retryable through applyMediaJournalEntry; it is never a completed recovery.
|
|
66
|
+
for (const { entry } of records) {
|
|
67
|
+
assertCurrent(owner);
|
|
68
|
+
const result = await projectEntry(entry, { urgent: true });
|
|
69
|
+
assertCurrent(owner);
|
|
70
|
+
if (typeof result?.cancelled !== 'boolean' || typeof result?.sessionEnded !== 'boolean'
|
|
71
|
+
|| result.cancelled === result.sessionEnded) {
|
|
72
|
+
throw new TypeError('Cancellation projection did not confirm a durable cancellation or ended session');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
assertCurrent(owner);
|
|
76
|
+
owner.ready = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function prepare() {
|
|
80
|
+
try {
|
|
81
|
+
if (retired) throw new Error('Cancellation recovery is closed');
|
|
82
|
+
const current = binding();
|
|
83
|
+
if (operation) return operation;
|
|
84
|
+
if (scope?.ready && scope.context === current.context && scope.directory === current.directory) {
|
|
85
|
+
return Promise.resolve();
|
|
86
|
+
}
|
|
87
|
+
operation = recover(current).catch(error => { throw unavailable(error); })
|
|
88
|
+
.finally(() => { operation = null; });
|
|
89
|
+
return operation;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return Promise.reject(unavailable(error));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function close() {
|
|
96
|
+
retired = true;
|
|
97
|
+
if (closing) return closing;
|
|
98
|
+
closing = (async () => {
|
|
99
|
+
// Already delivered operational errors are not shutdown failures. Unconfirmed handles
|
|
100
|
+
// remain owned and closeReader must still report them. No race against a timeout here.
|
|
101
|
+
if (operation) await operation.catch(() => { /* expected: admitted caller receives the failure */ });
|
|
102
|
+
await closeReader(scope);
|
|
103
|
+
scope = null;
|
|
104
|
+
})().finally(() => { closing = null; });
|
|
105
|
+
return closing;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { prepare, close };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = { createLegacyDraftCancellationRecovery };
|
|
@@ -13,6 +13,20 @@
|
|
|
13
13
|
// claude-not-logged-in transcript 95d10514 (16 Oct 23:31 -> 17 Oct 22:06): 37 results.
|
|
14
14
|
// github-device-login ~/.claude/plugins/cache/doordash-agentskills/core/5.3.67/bin/_run_cli.py
|
|
15
15
|
// url_patterns:62 -- the wrapper's `gh auth login --web` URL shape.
|
|
16
|
+
// codex-login-picker strings /opt/homebrew/Caskroom/codex/0.163.0/bin/codex (measured
|
|
17
|
+
// 2026-10-20) holds the literal "Sign in with Device Code" -- the
|
|
18
|
+
// picker's second label, D-2.
|
|
19
|
+
// codex-browser-login the same binary holds "Finish signing in via your browser", the banner
|
|
20
|
+
// the default (browser) flow prints while it waits.
|
|
21
|
+
// codex-device-login the same binary holds "Enter this one-time code" and the path fragment
|
|
22
|
+
// "/codex/device" (the full URL is composed at runtime, D-2); the device
|
|
23
|
+
// URL https://auth.openai.com/codex/device is cited in
|
|
24
|
+
// https://developers.openai.com/codex/cli/reference.
|
|
25
|
+
//
|
|
26
|
+
// NOT matched on purpose: the bare literal "Sign in with ChatGPT". The same 0.163.0 binary prints
|
|
27
|
+
// it in status lines such as "Sign in with ChatGPT to use /usage." and "Sign in with ChatGPT and
|
|
28
|
+
// retry", which are not a login screen -- matching them would kill an authenticated session. Only
|
|
29
|
+
// the picker's device-code label, the browser-flow banner and the device URL/code are matched.
|
|
16
30
|
//
|
|
17
31
|
// The window is a rolling tail of ANSI-stripped output so a pattern split across two
|
|
18
32
|
// chunks (or wrapped in colour codes) still matches. Correctness only needs
|
|
@@ -28,6 +42,9 @@ const LOGIN_PATTERNS = Object.freeze([
|
|
|
28
42
|
{ id: 'claude-oauth-url', re: /claude\.(?:com\/cai|ai)\/oauth/ },
|
|
29
43
|
{ id: 'claude-not-logged-in', re: /Not logged in\s*·\s*Please run \/login/ },
|
|
30
44
|
{ id: 'github-device-login', re: /github\.com\/login(?:\/device|\/oauth)/ },
|
|
45
|
+
{ id: 'codex-login-picker', re: /Sign in with Device Code/ },
|
|
46
|
+
{ id: 'codex-browser-login', re: /Finish signing in via your browser/ },
|
|
47
|
+
{ id: 'codex-device-login', re: /auth\.openai\.com\/codex\/device|Enter this one-time code/ },
|
|
31
48
|
]);
|
|
32
49
|
|
|
33
50
|
const WINDOW_CHARS = 4096;
|
|
@@ -170,6 +187,7 @@ function armSessionLoginTripwire({ proc, env, onBlocked } = {}) {
|
|
|
170
187
|
module.exports = {
|
|
171
188
|
LOGIN_PATTERNS,
|
|
172
189
|
WINDOW_CHARS,
|
|
190
|
+
stripAnsi,
|
|
173
191
|
createLoginTripwire,
|
|
174
192
|
attachToPty,
|
|
175
193
|
armSessionLoginTripwire,
|