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
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-1 — docs/codex-routed-fork-cold-restore-guard-2026-10-22.html §5 D-1..D-4, §6.
|
|
4
|
+
//
|
|
5
|
+
// The incident this closes: a Codex LEAD is launched natively with startup args `["resume", T]` and
|
|
6
|
+
// NO `session_lead_route` row. Codex 0.163's `thread/resume` injects the thread's own stored
|
|
7
|
+
// provider (`$CODEX_HOME/state_5.sqlite` -> threads.model_provider, e.g. `ctm-lead`) into its config
|
|
8
|
+
// load, which then fails "Model provider `ctm-lead` not found" -> exit 1 -> the 3-restart budget
|
|
9
|
+
// burns 50 s and the session ends blocked with a message that says nothing about the cause.
|
|
10
|
+
//
|
|
11
|
+
// D-1: the provider a spawned Codex process loads is CTM's decision (the route row), never the
|
|
12
|
+
// string in the thread. D-2: that string is read ONLY to decide whether a NATIVE launch can
|
|
13
|
+
// succeed. So before a native spawn this gate asks: is the thread's stored provider one a native
|
|
14
|
+
// `codex` config load resolves (its built-ins ∪ the user `[model_providers]` table)? If not, the
|
|
15
|
+
// launch is either repaired from CTM's own transition journal (D-3, the same row the commit would
|
|
16
|
+
// have written) or refused deterministically before any PTY exists (D-4) — never spawned to die.
|
|
17
|
+
//
|
|
18
|
+
// Nothing here is flag-gated, and nothing here spawns a process: reading the state row is a single
|
|
19
|
+
// readonly SQLite lookup and the rollout fallback only reads the file's first line.
|
|
20
|
+
|
|
21
|
+
const path = require('node:path');
|
|
22
|
+
const { getCodexStateThreadById, readCodexRolloutMetadata } = require('../session-history');
|
|
23
|
+
const codexConfigGuard = require('../codex-config-guard');
|
|
24
|
+
|
|
25
|
+
// codex-rs/core/src/config/mod.rs built_in_model_providers(); `openai` is the default when unset.
|
|
26
|
+
const NATIVE_BUILTIN_PROVIDERS = Object.freeze(['openai', 'ollama', 'lmstudio', 'amazon-bedrock']);
|
|
27
|
+
|
|
28
|
+
// The route/model columns `session_lead_route` accepts (lib/storage/session-lead-route-store.js).
|
|
29
|
+
const MAX_ROUTE_FIELD_LENGTH = 200;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Read the provider a native `codex resume <threadId>` would load for this thread.
|
|
33
|
+
*
|
|
34
|
+
* Order is the authority order Codex itself uses (D-2 decision table): the state DB row wins — it
|
|
35
|
+
* is what `thread/resume` reads — then the rollout's own `session_meta.model_provider`, then the
|
|
36
|
+
* built-in default. `codexHome` is the CODEX_HOME directory the launch uses; the state DB lookup
|
|
37
|
+
* takes the HOME it derives `<HOME>/.codex` from, so `homeDir` may be passed explicitly and is
|
|
38
|
+
* otherwise taken to be `codexHome`'s parent.
|
|
39
|
+
*
|
|
40
|
+
* @returns {{ provider: string, source: 'state_db'|'rollout'|'default', rolloutPath: string|null }}
|
|
41
|
+
*/
|
|
42
|
+
function readCodexThreadProvider({ threadId, codexHome = null, homeDir, rolloutPath = null,
|
|
43
|
+
getStateThread = getCodexStateThreadById, readRolloutMeta = readCodexRolloutMetadata } = {}) {
|
|
44
|
+
const id = String(threadId || '').trim();
|
|
45
|
+
const fallbackPath = rolloutPath ? String(rolloutPath) : null;
|
|
46
|
+
if (!id) return { provider: 'openai', source: 'default', rolloutPath: fallbackPath };
|
|
47
|
+
const resolvedHome = homeDir !== undefined && homeDir !== null
|
|
48
|
+
? homeDir
|
|
49
|
+
: (codexHome ? path.dirname(String(codexHome)) : undefined);
|
|
50
|
+
let row = null;
|
|
51
|
+
try {
|
|
52
|
+
row = getStateThread(id, resolvedHome) || null;
|
|
53
|
+
} catch {
|
|
54
|
+
row = null; /* expected: a corrupt/unreadable state DB degrades to the rollout fallback (D-2) */
|
|
55
|
+
}
|
|
56
|
+
const stateProvider = String(row?.model_provider || '').trim();
|
|
57
|
+
const stateRolloutPath = String(row?.rollout_path || '').trim();
|
|
58
|
+
if (stateProvider) {
|
|
59
|
+
return { provider: stateProvider, source: 'state_db', rolloutPath: stateRolloutPath || fallbackPath };
|
|
60
|
+
}
|
|
61
|
+
const file = stateRolloutPath || fallbackPath;
|
|
62
|
+
if (file) {
|
|
63
|
+
let meta = null;
|
|
64
|
+
try {
|
|
65
|
+
meta = readRolloutMeta(file) || null;
|
|
66
|
+
} catch {
|
|
67
|
+
meta = null; /* expected: a missing/malformed rollout is not a provider source (D-2) */
|
|
68
|
+
}
|
|
69
|
+
const rolloutProvider = String(meta?.model_provider || '').trim();
|
|
70
|
+
if (rolloutProvider) return { provider: rolloutProvider, source: 'rollout', rolloutPath: file };
|
|
71
|
+
}
|
|
72
|
+
return { provider: 'openai', source: 'default', rolloutPath: file || null };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Decide whether a NATIVE spawn can serve this thread (D-2).
|
|
77
|
+
* native === true iff provider ∈ NATIVE_BUILTIN_PROVIDERS ∪ keys(config.toml[model_providers]).
|
|
78
|
+
*
|
|
79
|
+
* @returns {{ native: boolean, provider: string, source: string, configProviders: string[] }}
|
|
80
|
+
*/
|
|
81
|
+
function classifyCodexThreadProvider({ provider, source = '', codexHome = null,
|
|
82
|
+
readConfigToml = codexConfigGuard.readModelProviderKeys } = {}) {
|
|
83
|
+
const id = String(provider || '').trim();
|
|
84
|
+
let configProviders = [];
|
|
85
|
+
try {
|
|
86
|
+
const listed = readConfigToml(codexHome);
|
|
87
|
+
if (Array.isArray(listed)) configProviders = listed.map((key) => String(key));
|
|
88
|
+
} catch {
|
|
89
|
+
configProviders = []; /* expected: an unreadable config.toml declares no user providers (S-5) */
|
|
90
|
+
}
|
|
91
|
+
const native = !!id && (NATIVE_BUILTIN_PROVIDERS.includes(id) || configProviders.includes(id));
|
|
92
|
+
return { native, provider: id, source, configProviders };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* D-3. Find the newest `coding_model_transitions` row for `ctmSessionId` whose parsed target recipe
|
|
97
|
+
* names this thread on the Codex harness with a route, and write `session_lead_route` from it.
|
|
98
|
+
*
|
|
99
|
+
* The journal is CTM's own record of the fork it launched, so this never guesses: no matching row
|
|
100
|
+
* means no repair (`{ ok: false, reason: 'no_journal_route' }`) and D-4 refuses the launch. An
|
|
101
|
+
* EXISTING route row wins byte-for-byte: the read below short-circuits, and the store's write is
|
|
102
|
+
* INSERT … ON CONFLICT DO NOTHING through the DB-owner mutation lane (awaited), so two concurrent
|
|
103
|
+
* cold restores converge on one row (meta-test M-1).
|
|
104
|
+
*
|
|
105
|
+
* @returns {{ ok: true, route: { route_id: string, model: string, context_window: null, transition_id: string }, written: boolean }
|
|
106
|
+
* | { ok: false, reason: 'no_journal_route' }}
|
|
107
|
+
*/
|
|
108
|
+
async function reconcileLeadRouteFromJournal({ ctmSessionId, threadId, dbModule, recordSessionDiagnostic } = {}) {
|
|
109
|
+
const id = String(ctmSessionId || '').trim();
|
|
110
|
+
const thread = String(threadId || '').trim();
|
|
111
|
+
if (!id || !thread) return { ok: false, reason: 'no_journal_route' };
|
|
112
|
+
let rows = [];
|
|
113
|
+
try {
|
|
114
|
+
rows = typeof dbModule?.getCodingModelTransitionsForThread === 'function'
|
|
115
|
+
? (dbModule.getCodingModelTransitionsForThread(id, thread) || [])
|
|
116
|
+
: [];
|
|
117
|
+
} catch {
|
|
118
|
+
rows = []; /* expected: an unreadable journal is not a route source — refuse, never guess (D-3) */
|
|
119
|
+
}
|
|
120
|
+
const row = rows.find((candidate) => {
|
|
121
|
+
const recipe = candidate?.target_recipe || {};
|
|
122
|
+
return String(recipe.providerThreadId || '') === thread
|
|
123
|
+
&& String(recipe.harness || '') === 'codex-cli'
|
|
124
|
+
&& String(recipe.routeId || '').trim().length > 0;
|
|
125
|
+
}) || null;
|
|
126
|
+
if (!row) return { ok: false, reason: 'no_journal_route' };
|
|
127
|
+
const routeId = String(row.target_recipe.routeId || '').trim();
|
|
128
|
+
const model = String(row.target_recipe.wireModelId || '').trim();
|
|
129
|
+
const transitionId = String(row.transition_id || '').trim();
|
|
130
|
+
if (!routeId || !model || !transitionId
|
|
131
|
+
|| routeId.length > MAX_ROUTE_FIELD_LENGTH || model.length > MAX_ROUTE_FIELD_LENGTH) {
|
|
132
|
+
return { ok: false, reason: 'no_journal_route' };
|
|
133
|
+
}
|
|
134
|
+
let existing = null;
|
|
135
|
+
try {
|
|
136
|
+
existing = typeof dbModule?.getSessionLeadRoute === 'function' ? dbModule.getSessionLeadRoute(id) : null;
|
|
137
|
+
} catch {
|
|
138
|
+
existing = null; /* expected: an unreadable route row is treated as absent — the store's write is idempotent */
|
|
139
|
+
}
|
|
140
|
+
let written = false;
|
|
141
|
+
let route;
|
|
142
|
+
if (existing) {
|
|
143
|
+
route = {
|
|
144
|
+
route_id: existing.route_id, model: existing.model,
|
|
145
|
+
context_window: existing.context_window == null ? null : existing.context_window,
|
|
146
|
+
transition_id: existing.transition_id || null,
|
|
147
|
+
};
|
|
148
|
+
} else {
|
|
149
|
+
let stored = null;
|
|
150
|
+
if (typeof dbModule?.setSessionLeadRoute === 'function') {
|
|
151
|
+
stored = await dbModule.setSessionLeadRoute({
|
|
152
|
+
ctmSessionId: id, agent: 'codex', routeId, model, display: model,
|
|
153
|
+
contextWindow: null, transitionId,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
written = !!stored;
|
|
157
|
+
route = { route_id: routeId, model, context_window: null, transition_id: transitionId };
|
|
158
|
+
}
|
|
159
|
+
if (typeof recordSessionDiagnostic === 'function') {
|
|
160
|
+
recordSessionDiagnostic(id, 'lead-route-reconciled-from-journal', {
|
|
161
|
+
transition_id: route.transition_id, route_id: route.route_id, model: route.model,
|
|
162
|
+
provider_thread_id: thread, written,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return { ok: true, route, written };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The D-4 refusal message. ONE sentence, and the picker is the way out (server.js's
|
|
170
|
+
* RESUME_BLOCK_MESSAGES and the client's error map both render this same provider id).
|
|
171
|
+
*/
|
|
172
|
+
function codexProviderMissingMessage(provider) {
|
|
173
|
+
return `This Codex thread was created on gateway provider \`${String(provider || '')}\`; pick a model for this session to relaunch it.`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* The launch-time hook (§9 S-3), called by provider-process.js when a Codex lead is about to be
|
|
178
|
+
* spawned natively on a resume thread (no `_launchLeadRoute` object and no route row). Returns
|
|
179
|
+
* `{ native: true }` when the thread's stored provider is servable natively, `{ target }` when
|
|
180
|
+
* the journal repaired a route (the caller runs its routed branch unchanged), or `{ refused: true }`
|
|
181
|
+
* after the deterministic resume block was recorded and the client told (D-4).
|
|
182
|
+
*/
|
|
183
|
+
async function gateNativeCodexLaunch({ sessionId, threadId, env = {}, preparedLaunchPlan = null, dbModule,
|
|
184
|
+
recordSessionDiagnostic, markSessionResumeBlocked, ws = null, wsSend = null } = {}) {
|
|
185
|
+
const codexHome = env.CODEX_HOME || (env.HOME ? path.join(env.HOME, '.codex') : null);
|
|
186
|
+
const threadRead = readCodexThreadProvider({
|
|
187
|
+
threadId, codexHome, homeDir: env.HOME || null,
|
|
188
|
+
rolloutPath: preparedLaunchPlan?.resume?.rolloutInfo?.rolloutPath
|
|
189
|
+
|| preparedLaunchPlan?.resume?.preflight?.transcriptPath || null,
|
|
190
|
+
});
|
|
191
|
+
const threadClass = classifyCodexThreadProvider({ ...threadRead, codexHome });
|
|
192
|
+
if (threadClass.native) return { native: true };
|
|
193
|
+
const reconciled = await reconcileLeadRouteFromJournal({ ctmSessionId: sessionId, threadId, dbModule, recordSessionDiagnostic });
|
|
194
|
+
if (reconciled.ok) {
|
|
195
|
+
return { target: { routeId: reconciled.route.route_id, model: reconciled.route.model, contextWindow: null } };
|
|
196
|
+
}
|
|
197
|
+
if (typeof recordSessionDiagnostic === 'function') {
|
|
198
|
+
recordSessionDiagnostic(sessionId, 'codex-thread-provider-unservable', {
|
|
199
|
+
provider_thread_id: threadId, provider: threadClass.provider, source: threadRead.source,
|
|
200
|
+
config_providers: threadClass.configProviders.length,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const message = codexProviderMissingMessage(threadClass.provider);
|
|
204
|
+
if (typeof markSessionResumeBlocked === 'function') {
|
|
205
|
+
markSessionResumeBlocked(sessionId, { code: 'codex_provider_missing', message, agentType: 'codex', deterministic: true }, 'codex-provider-gate');
|
|
206
|
+
}
|
|
207
|
+
if (ws && typeof wsSend === 'function') wsSend(ws, JSON.stringify({ type: 'error', code: 'codex_provider_missing', message }));
|
|
208
|
+
return { refused: true };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
module.exports = {
|
|
212
|
+
NATIVE_BUILTIN_PROVIDERS,
|
|
213
|
+
readCodexThreadProvider,
|
|
214
|
+
classifyCodexThreadProvider,
|
|
215
|
+
reconcileLeadRouteFromJournal,
|
|
216
|
+
codexProviderMissingMessage,
|
|
217
|
+
gateNativeCodexLaunch,
|
|
218
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-2 — docs/codex-routed-fork-cold-restore-guard-2026-10-22.html §5 D-5, §9 S-9/S-10, §8 WP-2.
|
|
4
|
+
//
|
|
5
|
+
// The belt to WP-1's braces. WP-1 refuses a native Codex launch *before* it spawns when the
|
|
6
|
+
// thread's stored provider is not one a native config load resolves. This module covers the exits
|
|
7
|
+
// that get past any such gate: a thread whose provider changes between the decision and the spawn,
|
|
8
|
+
// and the D-8 paths that never consult the gate at all.
|
|
9
|
+
//
|
|
10
|
+
// Codex reports "Model provider `x` not found" as a plain anyhow error with exit 1 — the SAME exit
|
|
11
|
+
// code as a transient startup fault — so the exit code alone cannot discriminate: only the captured
|
|
12
|
+
// output text can. CTM already buffers that text per attempt (`session._resumeAttemptOutput`, 8 KiB,
|
|
13
|
+
// reset at the start of every attempt), which is what this classifier reads.
|
|
14
|
+
//
|
|
15
|
+
// A config-load failure is a precondition failure: no amount of retrying resolves it, so the
|
|
16
|
+
// caller blocks deterministically instead of spending the 3-restart budget (50 s) to reach the same
|
|
17
|
+
// state with a message that names nothing. Nothing here is flag-gated and nothing here spawns a
|
|
18
|
+
// process: the whole decision is one regex over the caller's string.
|
|
19
|
+
|
|
20
|
+
// The one Codex phrase this classifies, pinned deliberately narrow (§11: "do not widen the exit
|
|
21
|
+
// classifier beyond the one Codex phrase"; every other startup fault keeps the restart budget).
|
|
22
|
+
// The provider id is the backtick-quoted run Codex echoes back from the thread's stored
|
|
23
|
+
// `model_provider`.
|
|
24
|
+
const CODEX_PROVIDER_MISSING_RE = /Model provider `([^`]+)` not found/;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Classify one attempt's captured Codex output (the caller passes `session._resumeAttemptOutput`).
|
|
28
|
+
*
|
|
29
|
+
* @param {string} output the attempt buffer — never scrollback; a non-string degrades to ''
|
|
30
|
+
* @returns {{ code: 'codex_provider_missing', provider: string, line: string } | null}
|
|
31
|
+
* `null` for any output that does not carry the phrase, and for a phrase with no usable provider
|
|
32
|
+
* id (e.g. "Model provider `` not found"), so a malformed line can only ever fail OPEN into
|
|
33
|
+
* today's restart budget rather than blocking a thread it cannot name.
|
|
34
|
+
*/
|
|
35
|
+
function classifyCodexResumeExit(output) {
|
|
36
|
+
const text = typeof output === 'string' ? output : '';
|
|
37
|
+
if (!text) return null;
|
|
38
|
+
const match = CODEX_PROVIDER_MISSING_RE.exec(text);
|
|
39
|
+
if (!match) return null;
|
|
40
|
+
const provider = String(match[1] || '').trim();
|
|
41
|
+
if (!provider) return null;
|
|
42
|
+
// `detail` is the whole line the phrase landed on (Codex wraps the anyhow error across the
|
|
43
|
+
// "thread/resume failed: ..." prefix and the "(code -32600)" suffix); the line is the unit a
|
|
44
|
+
// human reads, and the provider id alone would drop the surrounding failure context.
|
|
45
|
+
const lineStart = text.lastIndexOf('\n', Math.max(0, match.index - 1)) + 1;
|
|
46
|
+
const lineEndRaw = text.indexOf('\n', match.index);
|
|
47
|
+
const lineEnd = lineEndRaw === -1 ? text.length : lineEndRaw;
|
|
48
|
+
const line = text.slice(lineStart, lineEnd).replace(/\r$/, '').trim();
|
|
49
|
+
return { code: 'codex_provider_missing', provider, line: line || match[0] };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { CODEX_PROVIDER_MISSING_RE, classifyCodexResumeExit };
|
|
@@ -23,7 +23,7 @@ const { armAt } = require('./clamped-timer');
|
|
|
23
23
|
const admission = require('../worker-admission');
|
|
24
24
|
const { createWorkerAdmissionExit } = require('./worker-admission-exit');
|
|
25
25
|
const { createWorkerContinuation, validCallerToolUseId } = require('./worker-continuation');
|
|
26
|
-
const { createWorkerObservability, safeToolCounts, progressForGeneration, baselineChanged, statusProgress, currentGenerationReceipts, currentReceiptState, computeExitFailure, boundedWorkerText, isColdDeferredSession, workerHideLogLine, probeCachedFileState, THROTTLED_WINDOW_MS, EVENT_RECEIPT_LIMIT } = require('./worker-observability');
|
|
26
|
+
const { createWorkerObservability, safeToolCounts, progressForGeneration, baselineChanged, statusProgress, currentGenerationReceipts, currentReceiptState, computeExitFailure, boundedWorkerText, isColdDeferredSession, workerHideLogLine, probeCachedFileState, THROTTLED_WINDOW_MS, EVENT_RECEIPT_LIMIT, WORKER_REPORT_TEXT_CAP_BYTES } = require('./worker-observability');
|
|
27
27
|
const { killSessionProcess } = require('../session-class'); // worker-session-tree WP-2: the one kill authority
|
|
28
28
|
const { loadWorkerBrief, validateWorkerCwd, validateBriefPath, validateBriefRealpathInsideCwd, renderedBriefPathForSession, renderedBriefWithBudget, ownedPaths: parseOwnedPaths, findScopeOverlap } = require('./worker-brief');
|
|
29
29
|
const { checkCallerAuthority } = require('./worker-launch-sandbox');
|
|
@@ -49,10 +49,27 @@ const EFFORT_VALUES = new Set(['low', 'medium', 'high', 'xhigh']);
|
|
|
49
49
|
const { createProxyRouteReadiness, DelegateError, setLeadRouteReadiness, DEFAULT_ROUTE_ALIAS, DEFAULT_MODEL, READINESS_BUDGET_MS } = require('./proxy-route-readiness');
|
|
50
50
|
const DEFAULT_TOKEN_TTL_MS = 24 * 60 * 60 * 1000 + 10 * 60 * 1000; // session lifetime + 10 min (D-4)
|
|
51
51
|
|
|
52
|
+
// D-3 (lead-cost-routing-round2-2026-10-24.html §5 D-3, §6.1/§6.2): the ONE citation counter,
|
|
53
|
+
// shared with wall-e/lib/helper-rules/label.js so the counter and the labeller cannot drift.
|
|
54
|
+
const { countReportCitations } = require('../../../shared/report-citations');
|
|
55
|
+
|
|
52
56
|
// Only generated UUIDs are accepted before report paths are constructed, preventing encoded
|
|
53
57
|
// path traversal through GET /api/sessions/:id/worker-status?include_report=1.
|
|
54
58
|
const SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
55
59
|
|
|
60
|
+
// The statuses whose payload carries a citation count; every other status reports null/null
|
|
61
|
+
// because no text has been selected yet (D-3's §6.2 table).
|
|
62
|
+
const WORKER_TERMINAL_STATUSES = new Set(['report_ready', 'exited', 'failed']);
|
|
63
|
+
|
|
64
|
+
// D-3: read at most WORKER_REPORT_TEXT_CAP_BYTES of a report/last-message file, decode as UTF-8
|
|
65
|
+
// and drop a trailing U+FFFD left by a split multi-byte sequence -- byte-for-byte the semantics
|
|
66
|
+
// lib/api/sessions.js:931 applies to an ?include_report=1 read. A throw (ENOENT/EACCES/EISDIR)
|
|
67
|
+
// is the caller's to classify: getWorkerStatus turns it into report_read_error, never a failure.
|
|
68
|
+
function _readBoundedReportText(filePath) {
|
|
69
|
+
const bytes = fs.readFileSync(filePath);
|
|
70
|
+
return bytes.subarray(0, WORKER_REPORT_TEXT_CAP_BYTES).toString('utf8').replace(/\uFFFD$/, '');
|
|
71
|
+
}
|
|
72
|
+
|
|
56
73
|
function _assertValidSessionId(id) {
|
|
57
74
|
if (!SESSION_ID_RE.test(String(id || ''))) {
|
|
58
75
|
throw new DelegateError('session_invalid', 400, 'ctm_session_id is not a valid session id');
|
|
@@ -791,6 +808,31 @@ function getWorkerStatus(ctmSessionId, { now = Date.now } = {}) {
|
|
|
791
808
|
status = 'starting';
|
|
792
809
|
}
|
|
793
810
|
|
|
811
|
+
// D-3 (lead-cost-routing-round2-2026-10-24.html §5 D-3, §6.2): CTM is the ONLY counter of a
|
|
812
|
+
// report's distinct `file:line` citations. Count whichever text the row's OWN report_source
|
|
813
|
+
// names -- the report file, the last-message fallback, or the final provider result -- on
|
|
814
|
+
// every read, independent of ?include_report=1, so the whole-set wait's nested `worker`
|
|
815
|
+
// payload (which never carries report_text) still answers with a row count. Non-terminal
|
|
816
|
+
// statuses have selected no text yet and answer null/null; a read failure degrades to 0/0
|
|
817
|
+
// plus report_read_error and never fails the status read (same posture as report_present).
|
|
818
|
+
const terminalStatus = WORKER_TERMINAL_STATUSES.has(status);
|
|
819
|
+
let citationText = '';
|
|
820
|
+
let reportReadError = null;
|
|
821
|
+
if (terminalStatus) {
|
|
822
|
+
if (reportSource === 'report' || reportSource === 'last_message') {
|
|
823
|
+
try {
|
|
824
|
+
citationText = _readBoundedReportText(reportSource === 'report' ? reportPath : lastMessagePath);
|
|
825
|
+
} catch (error) {
|
|
826
|
+
// expected: report/last-message file missing, unreadable or a directory -- the count
|
|
827
|
+
// degrades to 0 with the error code exposed, exactly like the include_report=1 reader.
|
|
828
|
+
reportReadError = (error && error.code) || 'read_failed';
|
|
829
|
+
citationText = '';
|
|
830
|
+
}
|
|
831
|
+
} else if (reportSource === 'final_result') {
|
|
832
|
+
citationText = typeof successfulFinalText === 'string' ? successfulFinalText : '';
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
794
836
|
const identity = workerRow ? 'marked' : 'unmarked';
|
|
795
837
|
|
|
796
838
|
return {
|
|
@@ -799,6 +841,12 @@ function getWorkerStatus(ctmSessionId, { now = Date.now } = {}) {
|
|
|
799
841
|
report_present: reportPresent,
|
|
800
842
|
report_source: reportSource,
|
|
801
843
|
report_text: reportText,
|
|
844
|
+
// D-3 (§6.2): the citation count and the character count of the text report_source names,
|
|
845
|
+
// never gated by include_report. report_read_error rides along ONLY when the file read
|
|
846
|
+
// threw (its error code, or 'read_failed'), in which case both counts are 0.
|
|
847
|
+
report_citation_rows: terminalStatus ? countReportCitations(citationText) : null,
|
|
848
|
+
report_chars: terminalStatus ? citationText.length : null,
|
|
849
|
+
...(reportReadError ? { report_read_error: reportReadError } : {}),
|
|
802
850
|
report_provisional: !!reportPresent && alive,
|
|
803
851
|
last_message_path: lastMessagePath,
|
|
804
852
|
turns: observer ? Number(observer.count || 0) : (Number.isInteger(runtime?.turns) ? runtime.turns : (Number.isInteger(session?._workerTurnCount) ? session._workerTurnCount : null)),
|
|
@@ -961,6 +1009,13 @@ function cancelRetirement(ctmSessionId) {
|
|
|
961
1009
|
// that never exits doesn't spam the log every 60s forever.
|
|
962
1010
|
const HIDE_LIVE_PROCESS_RETRY_MS = 60_000;
|
|
963
1011
|
|
|
1012
|
+
// WP-1 (durable terminal stamp, worker-terminal-stamp-durable-2026-10-26): the ceiling for
|
|
1013
|
+
// markWorkerTerminal's transient-lock retry backoff. The write lock is held by the db-owner core
|
|
1014
|
+
// writer's transactions (observed 259-1210 ms back-to-back for 19 s on 2026-10-26), so
|
|
1015
|
+
// the retry has NO attempt ceiling -- it re-arms until the stamp lands or the row is already
|
|
1016
|
+
// terminal / hidden / gone -- and the geometric backoff stops growing here.
|
|
1017
|
+
const TERMINAL_STAMP_RETRY_CAP_MS = 5000;
|
|
1018
|
+
|
|
964
1019
|
async function _hideWorker(ctmSessionId, { now = Date.now } = {}) {
|
|
965
1020
|
const deps = _requireDeps();
|
|
966
1021
|
_retentionTimers.delete(ctmSessionId);
|
|
@@ -1074,11 +1129,13 @@ function _invalidateWorkerFailureCache(deps, ctmSessionId) {
|
|
|
1074
1129
|
session._workerFailure = null;
|
|
1075
1130
|
}
|
|
1076
1131
|
|
|
1077
|
-
function markWorkerTerminal(ctmSessionId, { terminalStatus, failure = null, runtime = null, now = Date.now, retry = 0 } = {}) {
|
|
1132
|
+
function markWorkerTerminal(ctmSessionId, { terminalStatus, failure = null, runtime = null, now = Date.now, retry = 0, firstAttemptMs = null } = {}) {
|
|
1078
1133
|
const deps = _requireDeps();
|
|
1079
1134
|
let row;
|
|
1080
1135
|
try { row = deps.dbModule.getWorkerSession(ctmSessionId); } catch { row = null; /* expected: an unreadable row is treated as no worker row, so nothing is stamped or scheduled -- a later exit-path call or the boot reload retries */ }
|
|
1081
|
-
|
|
1136
|
+
// WP-1: a row that no longer exists, or that another path already hid (hidden_at stamped =
|
|
1137
|
+
// the worker is retired and its row is out of every listing), ends the retry chain here.
|
|
1138
|
+
if (!row || row.hidden_at) return;
|
|
1082
1139
|
const nowMs = typeof now === 'function' ? now() : Number(now);
|
|
1083
1140
|
const nowIso = new Date(nowMs).toISOString();
|
|
1084
1141
|
const patch = {};
|
|
@@ -1113,12 +1170,23 @@ function markWorkerTerminal(ctmSessionId, { terminalStatus, failure = null, runt
|
|
|
1113
1170
|
try {
|
|
1114
1171
|
deps.dbModule.updateWorkerSession(ctmSessionId, patch);
|
|
1115
1172
|
} catch (err) {
|
|
1116
|
-
// WP-7 live finding: a busy write lock fails FAST and the old early return also dropped the wait wake + terminal sentinel -- disposition is now REPORT +
|
|
1173
|
+
// WP-7 live finding: a busy write lock fails FAST and the old early return also dropped the wait wake + terminal sentinel -- disposition is now REPORT + retry of the whole stamp.
|
|
1174
|
+
// WP-1: that retry has NO attempt ceiling -- a transient lock error is not the stamp's
|
|
1175
|
+
// failure, it is contention on the main-thread writer (2026-10-26: 19 s of back-to-back
|
|
1176
|
+
// 259-1210 ms transactions outlived the old 5-attempt / 1.5 s budget and two finished
|
|
1177
|
+
// workers stayed listed as live for 9 h and 6 h). The chain ends at the top of the
|
|
1178
|
+
// re-entered call: row gone / hidden_at set, or the patch finding terminal_at already
|
|
1179
|
+
// written -- and, of course, when this write lands.
|
|
1117
1180
|
// The retry's terminal writes start only when it fires, so the pending retry is tracked by the terminal settle.
|
|
1118
|
-
if (['SQLITE_WRITE_LOCK_BUSY', 'SQLITE_BUSY', 'SQLITE_LOCKED'].includes(err && err.code)
|
|
1181
|
+
if (['SQLITE_WRITE_LOCK_BUSY', 'SQLITE_BUSY', 'SQLITE_LOCKED'].includes(err && err.code)) {
|
|
1182
|
+
const firstAtMs = firstAttemptMs ?? nowMs;
|
|
1183
|
+
if (retry === 5) console.warn(`[delegate-worker] terminal stamp still busy after 6 attempts (${String((err && err.code) || 'write_failed').slice(0, 48)}); retrying every ${TERMINAL_STAMP_RETRY_CAP_MS} ms`);
|
|
1184
|
+
return void workerLedger.trackTerminalWork(new Promise((resolve) => setTimeout(() => { try { markWorkerTerminal(ctmSessionId, { terminalStatus, failure, runtime, now, retry: retry + 1, firstAttemptMs: firstAtMs }); } finally { resolve(); } }, Math.min(100 * (retry + 1), TERMINAL_STAMP_RETRY_CAP_MS)).unref?.()));
|
|
1185
|
+
}
|
|
1119
1186
|
console.warn(`[delegate-worker] terminal stamp failed (${String((err && err.code) || "write_failed").slice(0, 48)})`); return;
|
|
1120
1187
|
}
|
|
1121
1188
|
row = { ...row, ...patch };
|
|
1189
|
+
if (retry > 0) console.log(`[delegate-worker] terminal stamp landed for ${ctmSessionId} after ${retry + 1} attempts in ${nowMs - (firstAttemptMs ?? nowMs)} ms`);
|
|
1122
1190
|
_invalidateWorkerFailureCache(deps, ctmSessionId);
|
|
1123
1191
|
}
|
|
1124
1192
|
const hideAtMs = _computeHideAtMs(row, _retainMsForSession(deps, ctmSessionId));
|
|
@@ -1195,6 +1263,25 @@ function retireNow(session, now) {
|
|
|
1195
1263
|
markWorkerTerminal(session.id, { terminalStatus: result.status, failure: result.failure, runtime, now });
|
|
1196
1264
|
}
|
|
1197
1265
|
|
|
1266
|
+
// WP-2 (worker-terminal-stamp-durable-2026-10-26 D-2/D-3): one idempotent read repair for a
|
|
1267
|
+
// worker that EXITED but whose row never got its exit-time stamp (the write-lock loss WP-1 now
|
|
1268
|
+
// retries). Every lead-facing reader calls this FIRST, so a dead-but-unstamped worker is never
|
|
1269
|
+
// listed live and the stop verb never answers 'stopping' on a process that no longer exists.
|
|
1270
|
+
// Returns 'retired' | 'lost' | null. Idempotent: a stamped or hidden row is a no-op.
|
|
1271
|
+
// retireWorkerOnExit is deliberately NOT awaited -- a proxied worker's stamp is deferred by one
|
|
1272
|
+
// async read and tracked by workerLedger.trackTerminalWork, so callers must not assume the
|
|
1273
|
+
// stamp is synchronous (the next read retries anyway).
|
|
1274
|
+
function repairExitedWorkerRow(ctmSessionId, { now = Date.now } = {}) {
|
|
1275
|
+
const deps = _requireDeps();
|
|
1276
|
+
let row = null;
|
|
1277
|
+
try { row = deps.dbModule.getWorkerSession(ctmSessionId); } catch { row = null; /* expected: an unreadable row cannot be repaired now; the next read retries */ }
|
|
1278
|
+
if (!row || row.terminal_at || row.hidden_at) return null;
|
|
1279
|
+
const session = typeof deps.getSession === 'function' ? deps.getSession(ctmSessionId) : null;
|
|
1280
|
+
if (session && session.agentRole === 'worker' && (session._exitCode != null || session._exited)) { retireWorkerOnExit(session, { now }); return 'retired'; }
|
|
1281
|
+
if (!session) { retireLostWorker(row, { now }); return 'lost'; }
|
|
1282
|
+
return null; // a live process: nothing to repair
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1198
1285
|
// Lifecycle WP-5 (§5 D-10, R-3, §9 S-8, AC-5c): boot re-arm -- every live (hidden_at === '')
|
|
1199
1286
|
// worker_sessions row that already has a persisted hide_at gets its retirement timer
|
|
1200
1287
|
// re-armed at that EXACT value (never recomputed off the current retainMs, so a config
|
|
@@ -1263,7 +1350,7 @@ const retireLostWorker = bootRetire.retireLostWorker, retireSurvivingWorker = bo
|
|
|
1263
1350
|
module.exports = {
|
|
1264
1351
|
configure, _terminalJournalSettled: workerLedger.terminalJournalSettled,
|
|
1265
1352
|
resumeWorkerSession: workerContinuation.resumeWorkerSession,
|
|
1266
|
-
stopWorkerSession: (id, opts) => require('./worker-stop').stopWorkerSession(id, opts, _requireDeps(), { resolveParent: resolveParentCtmSessionId }),
|
|
1353
|
+
stopWorkerSession: (id, opts) => require('./worker-stop').stopWorkerSession(id, opts, _requireDeps(), { resolveParent: resolveParentCtmSessionId, repairExitedWorkerRow }),
|
|
1267
1354
|
probeWorkerRoute: workerContinuation.probeWorkerRoute,
|
|
1268
1355
|
restoreQuotaHoldsAtBoot: workerContinuation.restoreQuotaHoldsAtBoot,
|
|
1269
1356
|
markAgentConsumed: workerContinuation.markAgentConsumed,
|
|
@@ -1290,6 +1377,8 @@ module.exports = {
|
|
|
1290
1377
|
markAcknowledged,
|
|
1291
1378
|
markWorkerTerminal,
|
|
1292
1379
|
retireWorkerOnExit,
|
|
1380
|
+
// WP-2 (D-2): the idempotent read repair both lead-facing readers call before answering.
|
|
1381
|
+
repairExitedWorkerRow,
|
|
1293
1382
|
reloadRetirementsAtBoot,
|
|
1294
1383
|
listPendingRetirements,
|
|
1295
1384
|
_resetRetentionTimersForTest,
|
|
@@ -7,6 +7,25 @@ function configure(getDeps) {
|
|
|
7
7
|
_getDeps = getDeps;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
// WP-4 (docs/remote-hub-followups-credential-projects-rows-idempotency-2026-10-23.html §5 D-11):
|
|
11
|
+
// the create fingerprint an Idempotency-Key retry is compared against -- the JSON of these nine
|
|
12
|
+
// launch-shaping fields, IN THIS ORDER, with a missing field read as ''. Geometry (cols/rows) and
|
|
13
|
+
// label/title are deliberately EXCLUDED: a retry may differ in window size or naming and is still
|
|
14
|
+
// the same create. Pure: reads nothing but `msg`, allocates nothing, touches no socket.
|
|
15
|
+
const CREATE_FINGERPRINT_FIELDS = Object.freeze([
|
|
16
|
+
'cmd', 'args', 'cwd', 'createCwd', 'model_id', 'model_provider', 'mcp_profile', 'agentType', 'chatSessionId',
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
function createFingerprint(msg) {
|
|
20
|
+
const source = msg && typeof msg === 'object' ? msg : {};
|
|
21
|
+
return JSON.stringify(CREATE_FINGERPRINT_FIELDS.map((field) => {
|
|
22
|
+
const value = source[field];
|
|
23
|
+
// `args` is compared AS GIVEN (an argv array serializes element by element); every other
|
|
24
|
+
// field is a scalar. Absent and null both read as ''.
|
|
25
|
+
return value === undefined || value === null ? '' : value;
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
|
|
10
29
|
function prepareCreate(ws, msg) {
|
|
11
30
|
const deps = _getDeps();
|
|
12
31
|
const handleCreateStartedAt = deps.now();
|
|
@@ -67,4 +86,4 @@ function prepareCreate(ws, msg) {
|
|
|
67
86
|
};
|
|
68
87
|
}
|
|
69
88
|
|
|
70
|
-
module.exports = { configure, prepareCreate };
|
|
89
|
+
module.exports = { configure, prepareCreate, createFingerprint };
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// WP-4 (docs/remote-hub-followups-credential-projects-rows-idempotency-2026-10-23.html §5 D-11):
|
|
4
|
+
// the ONE fingerprint definition lives in lib/session-create/identity.js; this module stamps it on a
|
|
5
|
+
// session it fully owned and compares it on the next same-id create.
|
|
6
|
+
const { createFingerprint } = require('./identity');
|
|
7
|
+
|
|
3
8
|
// WP-2b (docs/session-model-authority-and-display-2026-10-15.html §9): the COMPLETE prepared-create
|
|
4
9
|
// orchestration, moved verbatim out of server.js's `handleCreatePrepared`. It owns one whole
|
|
5
10
|
// responsibility -- admitted request -> durable generation -> prepared current plan -> trusted
|
|
@@ -26,11 +31,23 @@
|
|
|
26
31
|
// argv, token or raw send-error text is ever echoed into the report. Every other `_wsSend` in
|
|
27
32
|
// the handler (mcp_profile_invalid, launch-generation-refused, cwd-create-failed,
|
|
28
33
|
// launch_transcript_restore_failed, the folder toast) keeps its original throwing behavior.
|
|
34
|
+
// WP-4 (docs/remote-hub-followups-credential-projects-rows-idempotency-2026-10-23.html §5 D-13):
|
|
35
|
+
// the per-id IN-FLIGHT create ledger -- id -> { fingerprint } (D-11). One entry per create that has
|
|
36
|
+
// passed owner/MCP admission and has not yet left handleCreatePrepared; a second create with the
|
|
37
|
+
// SAME id AND the same fingerprint while the first is in flight is refused `create_in_progress`
|
|
38
|
+
// before it reserves a generation, plans, or spawns anything. The entry is deleted in the handler's
|
|
39
|
+
// `finally`, which covers every exit (success, typed refusal, early null return, throw). A DIFFERENT
|
|
40
|
+
// fingerprint never refuses and never overwrites the first entry (D-14: a model-switch relaunch
|
|
41
|
+
// stays the generation runtime's business).
|
|
42
|
+
const _inflightCreates = new Map();
|
|
43
|
+
|
|
29
44
|
function createPreparedSessionCreate(host) {
|
|
30
45
|
const {
|
|
31
46
|
// Admission / identity
|
|
32
47
|
crypto, _sessionMcpProfileSetting, _wsSend, _codingSessionGenerationRuntime,
|
|
33
48
|
recordSessionDiagnostic, isSessionResumeBlocked, clearSessionResumeBlocked, getSession,
|
|
49
|
+
// WP-4 (§5 D-12): the one `created` frame a duplicate create is re-answered with.
|
|
50
|
+
sendNativeSessionCreated,
|
|
34
51
|
// Input / local path
|
|
35
52
|
serverConfig, path, fs, detectAgentType, extractResumeTarget,
|
|
36
53
|
// Metadata / planning
|
|
@@ -61,6 +78,9 @@ function createPreparedSessionCreate(host) {
|
|
|
61
78
|
}
|
|
62
79
|
}
|
|
63
80
|
|
|
81
|
+
// WP-4 (§5 D-13): admission, then the in-flight claim, then the unchanged orchestration. The
|
|
82
|
+
// claim's `finally` wraps the WHOLE body, so every exit of the orchestration releases it -- and no
|
|
83
|
+
// line of the orchestration itself had to move to make that true.
|
|
64
84
|
async function handleCreatePrepared(ws, msg) {
|
|
65
85
|
if (host._ownerRecovery.rejectRequest(ws, msg)) return null; const id = msg?.id || crypto.randomUUID();
|
|
66
86
|
const preparedMsg = { ...(msg || {}), id };
|
|
@@ -70,6 +90,44 @@ function createPreparedSessionCreate(host) {
|
|
|
70
90
|
if (ws?.readyState === 1) _wsSend(ws, JSON.stringify({ type: 'error', code: 'mcp_profile_invalid', message: `Unknown mcp_profile: ${JSON.stringify(_rawMcpProfile)}` }));
|
|
71
91
|
return null;
|
|
72
92
|
}
|
|
93
|
+
// WP-4 (§5 D-12/D-13): a client-supplied id is the Idempotency-Key, and both duplicate answers
|
|
94
|
+
// are decided HERE, before any generation reservation, plan, markSpawning or markLive:
|
|
95
|
+
// - the id names a LIVE session spawned by an identical create => re-answer ONE `created`
|
|
96
|
+
// frame (same builder a fresh create uses) and return it; nothing is attached or rolled back;
|
|
97
|
+
// - an identical create is still in flight => one typed `create_in_progress` refusal.
|
|
98
|
+
// A DIFFERENT fingerprint is NOT a duplicate (D-14): it walks today's path (attach, or the
|
|
99
|
+
// generation runtime's model-switch relaunch) and leaves the other fingerprint's entry alone.
|
|
100
|
+
let _claimedInflight = false;
|
|
101
|
+
const fingerprint = msg?.id ? createFingerprint(preparedMsg) : '';
|
|
102
|
+
if (fingerprint) {
|
|
103
|
+
const live = getSession(id);
|
|
104
|
+
if (ws && live?.ptyProcess && live._createFingerprint === fingerprint) {
|
|
105
|
+
sendNativeSessionCreated(ws, live);
|
|
106
|
+
return live;
|
|
107
|
+
}
|
|
108
|
+
const inFlight = _inflightCreates.get(id);
|
|
109
|
+
if (inFlight && inFlight.fingerprint === fingerprint) {
|
|
110
|
+
sendPreparedError(ws, {
|
|
111
|
+
type: 'error',
|
|
112
|
+
code: 'create_in_progress',
|
|
113
|
+
id,
|
|
114
|
+
message: 'A create for this session is already in progress',
|
|
115
|
+
});
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
if (!inFlight) { _inflightCreates.set(id, { fingerprint }); _claimedInflight = true; }
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
return await _prepareCreateAdmitted(ws, msg, preparedMsg, id, fingerprint);
|
|
122
|
+
} finally {
|
|
123
|
+
if (_claimedInflight) _inflightCreates.delete(id);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// The orchestration itself: byte-for-byte the body that used to be handleCreatePrepared's, from
|
|
128
|
+
// the generation reservation on, plus the one D-11 fingerprint stamp on the owned success path.
|
|
129
|
+
// Split out ONLY so the D-13 claim above can wrap every exit.
|
|
130
|
+
async function _prepareCreateAdmitted(ws, msg, preparedMsg, id, fingerprint) {
|
|
73
131
|
const reserved = _codingSessionGenerationRuntime.revalidateReservation(await _codingSessionGenerationRuntime.reserveSessionGeneration(id));
|
|
74
132
|
if (!reserved.ok) { recordSessionDiagnostic(id, 'launch-generation-refused', { code: reserved.code, message: reserved.message }); if (ws?.readyState === 1) _wsSend(ws, JSON.stringify({ type: 'error', code: reserved.code, message: `Launch refused: ${reserved.message}` })); return null; }
|
|
75
133
|
const generation = reserved.generation;
|
|
@@ -274,6 +332,10 @@ function createPreparedSessionCreate(host) {
|
|
|
274
332
|
});
|
|
275
333
|
return null;
|
|
276
334
|
}
|
|
335
|
+
// WP-4 (§5 D-11): only a create this handler fully owned, keyed by a client id, can be re-answered
|
|
336
|
+
// later. A retry landing between the `created` frame and this line still finds the in-flight
|
|
337
|
+
// entry above and gets `create_in_progress`.
|
|
338
|
+
if (fingerprint && created && created.type !== 'walle') created._createFingerprint = fingerprint;
|
|
277
339
|
_launchPlanCoordinator.markLive(id, generation, result.plan);
|
|
278
340
|
return created || null;
|
|
279
341
|
}
|
|
@@ -281,4 +343,10 @@ function createPreparedSessionCreate(host) {
|
|
|
281
343
|
return handleCreatePrepared;
|
|
282
344
|
}
|
|
283
345
|
|
|
284
|
-
module.exports = {
|
|
346
|
+
module.exports = {
|
|
347
|
+
createPreparedSessionCreate,
|
|
348
|
+
// WP-4 test seam (AC-4a/§10 abuse case): the in-flight ledger is process-local, so a test that
|
|
349
|
+
// drives the real factory in-process asserts it drains through this accessor rather than reaching
|
|
350
|
+
// into the module's internals.
|
|
351
|
+
inflightCreateCount: () => _inflightCreates.size,
|
|
352
|
+
};
|