create-walle 0.10.1 → 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 +4 -0
- 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 +6 -4
- package/package.json +1 -1
- 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/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/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 +5 -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-launch-health.js +29 -2
- package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
- package/template/coding-task-manager/lib/codex-paths.js +2 -1
- 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-schema-source-files.js +1 -0
- package/template/coding-task-manager/lib/feature-manifest.js +1 -1
- package/template/coding-task-manager/lib/fs-watcher.js +3 -2
- package/template/coding-task-manager/lib/harness-history-transition.js +3 -3
- 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 +55 -17
- 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/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-switch-runtime.js +57 -4
- package/template/coding-task-manager/lib/lead-model-switch.js +3 -2
- 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 +1 -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 +52 -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-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/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/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 +2 -2
- 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-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/read-pool-client.js +5 -2
- package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
- 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 +13 -1
- 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 +33 -2
- package/template/coding-task-manager/lib/root-prompt-flow.js +167 -33
- package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
- 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 +34 -37
- 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 +18 -0
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +9 -0
- package/template/coding-task-manager/lib/session-resource-sampler.js +19 -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-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 +92 -0
- package/template/coding-task-manager/lib/typing-worker-stages.js +60 -0
- 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/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/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 -1
- package/template/coding-task-manager/public/css/reviews.css +4 -0
- package/template/coding-task-manager/public/index.html +294 -223
- 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/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 -8
- package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
- 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 +4 -2
- 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/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 -1
- 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 +902 -239
- 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/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/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 +6 -4
- 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
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// native-same-thread-model-switch / §6.5: the PRIVATE launch capture a rollback recovery replays.
|
|
4
|
+
// It is recorded for EVERY native harness whose `recoverSource` can resume the same canonical
|
|
5
|
+
// thread -- Codex AND Claude -- because this capture is the only proof of THIS spawn's
|
|
6
|
+
// executable/argv/cwd/home/generation. It is taken from the prepared launch plan at the REAL
|
|
7
|
+
// spawn (never synthesized later from mutable session fields), and every field is a copy: the
|
|
8
|
+
// argv array is cloned, so a later `session.args` rewrite cannot edit the receipt.
|
|
9
|
+
// WP-0b: extracted verbatim from provider-process.js (same spawn point) so that file stays under
|
|
10
|
+
// the file-size ceiling; behavior is unchanged. The existing Codex receipt dependency is called
|
|
11
|
+
// once as before; this adds no reader, state, or credential copy.
|
|
12
|
+
|
|
13
|
+
const path = require('node:path');
|
|
14
|
+
const { strFlag } = require('../env-flags');
|
|
15
|
+
|
|
16
|
+
// Explicit launch inputs (never the whole process env): the prepared plan, the caller's argv/cwd,
|
|
17
|
+
// the ACTUAL spawn env, the recipe/generation the launch was prepared with, the Codex config
|
|
18
|
+
// receipt already read for this spawn, and the existing codex-paths dependency (no extra read).
|
|
19
|
+
// Returns `null` for a harness with no same-canonical-thread rollout to resume.
|
|
20
|
+
function buildModelTransitionLaunchContext({ agentType, preparedLaunchPlan, cmd, args, cwd, spawnEnv,
|
|
21
|
+
serverHome, executionRecipe, processGeneration, nativeConfigReceipt = null, codexPaths = null }) {
|
|
22
|
+
if (agentType !== 'codex' && agentType !== 'claude') return null;
|
|
23
|
+
const capturedHome = spawnEnv.HOME || serverHome || null;
|
|
24
|
+
const capturedTmpDir = typeof spawnEnv.TMPDIR === 'string' && path.isAbsolute(spawnEnv.TMPDIR)
|
|
25
|
+
? spawnEnv.TMPDIR : null;
|
|
26
|
+
if (agentType === 'claude') {
|
|
27
|
+
// A native Claude launch claims NO Codex receipt/config: `codexHome`, `nativeStorageRootReceipt`
|
|
28
|
+
// and `nativeConfigReceipt` (read from the Codex `config.toml`) are explicitly null, so a
|
|
29
|
+
// rollback can never mistake this capture for a Codex one. The configured storage root is the
|
|
30
|
+
// Claude `projects/` dir under the ACTUAL `CLAUDE_CONFIG_DIR` (else `HOME/.claude`) and stays
|
|
31
|
+
// null when neither is known. The canonical thread is this launch's OWN recipe thread --
|
|
32
|
+
// computed by `launchProviderThreadId` from the resume plan, an injected/explicit
|
|
33
|
+
// `--session-id` and the caller's argv -- never inferred from the CTM session id, and left
|
|
34
|
+
// null (a fail-closed refusal in `recoverSource`) when the identity is unknown.
|
|
35
|
+
// A blank child CLAUDE_CONFIG_DIR falls back to that child's HOME/.claude, never the parent's
|
|
36
|
+
// config root. Codex keeps its CTM_CODEX_SESSIONS_DIR fallback below, unchanged.
|
|
37
|
+
const claudeConfigDir = spawnEnv.CLAUDE_CONFIG_DIR
|
|
38
|
+
|| (capturedHome ? path.join(capturedHome, '.claude') : null);
|
|
39
|
+
return {
|
|
40
|
+
executablePath: preparedLaunchPlan?.cmd || cmd,
|
|
41
|
+
args: Array.isArray(preparedLaunchPlan?.args) ? [...preparedLaunchPlan.args] : [...args],
|
|
42
|
+
workingDirectory: preparedLaunchPlan?.cwd || cwd,
|
|
43
|
+
providerThreadId: executionRecipe?.providerThreadId || null,
|
|
44
|
+
processGeneration,
|
|
45
|
+
homeDir: capturedHome,
|
|
46
|
+
codexHome: null,
|
|
47
|
+
nativeStorageRootConfiguredPath: claudeConfigDir ? path.join(claudeConfigDir, 'projects') : null,
|
|
48
|
+
nativeStorageRootReceipt: null,
|
|
49
|
+
tmpDir: capturedTmpDir,
|
|
50
|
+
nativeConfigReceipt: null,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const sessionsDir = spawnEnv.CTM_CODEX_SESSIONS_DIR || strFlag('CTM_CODEX_SESSIONS_DIR', '');
|
|
54
|
+
return {
|
|
55
|
+
executablePath: preparedLaunchPlan?.cmd || cmd,
|
|
56
|
+
args: Array.isArray(preparedLaunchPlan?.args) ? [...preparedLaunchPlan.args] : [...args],
|
|
57
|
+
workingDirectory: preparedLaunchPlan?.cwd || cwd,
|
|
58
|
+
providerThreadId: preparedLaunchPlan?.resume?.providerResumeId || preparedLaunchPlan?.resume?.sessionId
|
|
59
|
+
|| preparedLaunchPlan?.resume?.agentSessionId || null,
|
|
60
|
+
processGeneration,
|
|
61
|
+
homeDir: capturedHome,
|
|
62
|
+
codexHome: spawnEnv.CODEX_HOME || (capturedHome ? path.join(capturedHome, '.codex') : null),
|
|
63
|
+
nativeStorageRootConfiguredPath: spawnEnv.CTM_CODEX_SESSIONS_DIR
|
|
64
|
+
|| sessionsDir
|
|
65
|
+
|| ((spawnEnv.CODEX_HOME || capturedHome)
|
|
66
|
+
? path.join(spawnEnv.CODEX_HOME || path.join(capturedHome, '.codex'), 'sessions') : null),
|
|
67
|
+
nativeStorageRootReceipt: codexPaths?.codexSessionsRootReceipt?.({
|
|
68
|
+
homeDir: capturedHome,
|
|
69
|
+
codexHomePath: spawnEnv.CODEX_HOME || (capturedHome ? path.join(capturedHome, '.codex') : null),
|
|
70
|
+
sessionsDir: sessionsDir || undefined,
|
|
71
|
+
}) || null,
|
|
72
|
+
tmpDir: capturedTmpDir,
|
|
73
|
+
nativeConfigReceipt: nativeConfigReceipt || { state: 'unknown' },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { buildModelTransitionLaunchContext };
|
|
@@ -20,8 +20,11 @@ const { RECIPE_ENV_KEY, MAX_RECIPE_BYTES, modelExecutionRecipeMetadata } = requi
|
|
|
20
20
|
const { validateDeferredRestoreAttempt } = require('../deferred-restore-attempt');
|
|
21
21
|
const { getPrivateSourceRecoveryEnvironment, capturePrivateSourceEnvironment,
|
|
22
22
|
hasCapturedEmptySourceEnvironmentRestore } = require('../lead-model-restore-reconciliation');
|
|
23
|
-
const { modelStorageRootsFromEnv } = require('../host-handoff');
|
|
24
|
-
|
|
23
|
+
const { modelStorageRootsFromEnv, privateOverridesFromEnv } = require('../host-handoff');
|
|
24
|
+
// WP-0b: the rollback launch capture (moved out of this file, same spawn point) -- see
|
|
25
|
+
// lib/session-create/provider-launch-context.js for the field-by-field contract.
|
|
26
|
+
const { buildModelTransitionLaunchContext } = require('./provider-launch-context');
|
|
27
|
+
const codexNativeProviderGate = require('./codex-native-provider-gate');
|
|
25
28
|
const MAX_CODEX_CONFIG_BYTES = 1024 * 1024;
|
|
26
29
|
|
|
27
30
|
async function readCodexConfigReceipt(codexHome, onCleanupError = () => {}) {
|
|
@@ -195,6 +198,7 @@ async function prepareProviderSession(state) {
|
|
|
195
198
|
_wsSend,
|
|
196
199
|
recordSessionDiagnostic,
|
|
197
200
|
harnessModelProfileLoader,
|
|
201
|
+
markSessionResumeBlocked, // WP-1: the deterministic refusal hook (server.js)
|
|
198
202
|
} = _getDeps();
|
|
199
203
|
const { args, cmd, createAgentType, cwd, env, id, msg, preparedLaunchPlan, restoreSeedSnapshot, ws, signal,
|
|
200
204
|
claudeResumeId, _injectedSessionId } = state;
|
|
@@ -324,6 +328,14 @@ async function prepareProviderSession(state) {
|
|
|
324
328
|
if (typeof recordSessionDiagnostic === 'function') recordSessionDiagnostic(id, 'lead-route-read-failed', { message: String(error && error.message || error).slice(0, 200) });
|
|
325
329
|
}
|
|
326
330
|
}
|
|
331
|
+
// WP-1 (codex-routed-fork-cold-restore-guard §5 D-2..D-4): a native `codex resume T` loads the provider
|
|
332
|
+
// STORED IN T; the gate refuses (D-4) or repairs the route from the journal (D-3) before any spawn.
|
|
333
|
+
if (!target && !readFailed && createAgentType === 'codex' && resolvedProviderThreadId) {
|
|
334
|
+
const gated = await codexNativeProviderGate.gateNativeCodexLaunch({ sessionId: id, threadId: resolvedProviderThreadId,
|
|
335
|
+
env, preparedLaunchPlan, dbModule, recordSessionDiagnostic, markSessionResumeBlocked, ws, wsSend: _wsSend });
|
|
336
|
+
if (gated.refused) { if (runtime) await runtime.close(); return { handled: true, providerState: null }; }
|
|
337
|
+
if (gated.target) target = gated.target;
|
|
338
|
+
}
|
|
327
339
|
if (target && !readFailed) {
|
|
328
340
|
let lr;
|
|
329
341
|
if (target.preparedPlan) {
|
|
@@ -863,13 +875,17 @@ function createProviderSession(state) {
|
|
|
863
875
|
const leadApplied = _applyLeadRouteToSpawn({ launchArgs, env, leadRoute });
|
|
864
876
|
launchArgs = leadApplied.launchArgs;
|
|
865
877
|
// The host receipt carries only whether the original per-session overrides were empty or
|
|
866
|
-
// require a surviving private owner. Storage roots
|
|
867
|
-
// launch context
|
|
878
|
+
// require a surviving private owner. Storage roots and TMPDIR projected into the launch context
|
|
879
|
+
// (`launchEnvironmentOverrides`) are launch context, not private overrides: for EVERY spawn, not
|
|
880
|
+
// only a host restore, they are dropped before the receipt is computed, so a projected HOME/TMPDIR
|
|
881
|
+
// never turns an otherwise empty override set into a private environment. The spawned child still
|
|
882
|
+
// receives them (`launchEnvOverrides` below).
|
|
868
883
|
const trustedCapturedEmptySourceEnvironment = msg._isRestore === true
|
|
869
884
|
&& hasCapturedEmptySourceEnvironmentRestore(msg);
|
|
870
885
|
const sourceOverrideCapture = capturePrivateSourceEnvironment(sourceRecoveryEnvironment
|
|
871
|
-
? sourceRecoveryEnvironment.environment
|
|
872
|
-
|
|
886
|
+
? privateOverridesFromEnv(sourceRecoveryEnvironment.environment)
|
|
887
|
+
: trustedCapturedEmptySourceEnvironment ? {}
|
|
888
|
+
: privateOverridesFromEnv(msg.env && typeof msg.env === 'object' ? msg.env : {}));
|
|
873
889
|
const sourceEnvironmentReference = trustedCapturedEmptySourceEnvironment ? 'captured-empty'
|
|
874
890
|
: sourceOverrideCapture.state === 'captured'
|
|
875
891
|
? (Object.keys(sourceOverrideCapture.values || {}).length === 0 ? 'captured-empty' : 'private-owner-required')
|
|
@@ -1065,32 +1081,14 @@ function createProviderSession(state) {
|
|
|
1065
1081
|
_modelProcessGeneration: modelProcessGeneration,
|
|
1066
1082
|
_modelExecutionRecipe: modelExecutionRecipeMetadata(executionRecipe,
|
|
1067
1083
|
{ sessionId: id, processGeneration: modelProcessGeneration }).modelExecutionRecipe || null,
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|| preparedLaunchPlan?.resume?.agentSessionId || null,
|
|
1077
|
-
processGeneration: modelProcessGeneration,
|
|
1078
|
-
homeDir: capturedHome,
|
|
1079
|
-
codexHome: leadApplied.spawnEnv.CODEX_HOME || (capturedHome ? path.join(capturedHome, '.codex') : null),
|
|
1080
|
-
nativeStorageRootConfiguredPath: leadApplied.spawnEnv.CTM_CODEX_SESSIONS_DIR
|
|
1081
|
-
|| sessionsDir
|
|
1082
|
-
|| ((leadApplied.spawnEnv.CODEX_HOME || capturedHome)
|
|
1083
|
-
? path.join(leadApplied.spawnEnv.CODEX_HOME || path.join(capturedHome, '.codex'), 'sessions') : null),
|
|
1084
|
-
nativeStorageRootReceipt: codexPaths?.codexSessionsRootReceipt?.({
|
|
1085
|
-
homeDir: capturedHome,
|
|
1086
|
-
codexHomePath: leadApplied.spawnEnv.CODEX_HOME || (capturedHome ? path.join(capturedHome, '.codex') : null),
|
|
1087
|
-
sessionsDir: sessionsDir || undefined,
|
|
1088
|
-
}) || null,
|
|
1089
|
-
tmpDir: typeof leadApplied.spawnEnv.TMPDIR === 'string' && path.isAbsolute(leadApplied.spawnEnv.TMPDIR)
|
|
1090
|
-
? leadApplied.spawnEnv.TMPDIR : null,
|
|
1091
|
-
nativeConfigReceipt: nativeConfigReceipt || { state: 'unknown' },
|
|
1092
|
-
};
|
|
1093
|
-
})() } : {}),
|
|
1084
|
+
// native-same-thread-model-switch / §6.5, WP-0b: the PRIVATE launch capture a rollback recovery
|
|
1085
|
+
// replays, built at THIS REAL spawn point by the sibling module (see
|
|
1086
|
+
// lib/session-create/provider-launch-context.js for the field-by-field contract).
|
|
1087
|
+
...((createAgentType === 'codex' || createAgentType === 'claude') ? { _modelTransitionLaunchContext: buildModelTransitionLaunchContext({
|
|
1088
|
+
agentType: createAgentType, preparedLaunchPlan, cmd, args, cwd,
|
|
1089
|
+
spawnEnv: leadApplied.spawnEnv, serverHome: serverConfig.HOME,
|
|
1090
|
+
executionRecipe, processGeneration: modelProcessGeneration, nativeConfigReceipt, codexPaths,
|
|
1091
|
+
}) } : {}),
|
|
1094
1092
|
clients: sessionClients,
|
|
1095
1093
|
_primaryWs: null,
|
|
1096
1094
|
// scrollback removed — headless terminal is source of truth (Phase 3D)
|
|
@@ -1455,11 +1453,10 @@ function createProviderSession(state) {
|
|
|
1455
1453
|
provider: sessionAgentType,
|
|
1456
1454
|
providerResumeId: sessionAgentType === 'codex' ? (claudeResumeId || '') : '',
|
|
1457
1455
|
cwd,
|
|
1458
|
-
|
|
1459
|
-
userRenamed: !!session.userRenamed,
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
titleStatusReason: session.userRenamed ? 'new_session_title' : '',
|
|
1456
|
+
// D-2 (session-name design 2026-10-23): a resume of a titled row keeps its title + provenance (upsertSession keeps omitted keys).
|
|
1457
|
+
...(isResumeLaunch && existingTitle?.title ? {} : { title: session.title || session.label, userRenamed: !!session.userRenamed,
|
|
1458
|
+
titleSource: session.userRenamed ? 'user' : '', titleStatus: session.userRenamed ? 'user_named' : '',
|
|
1459
|
+
titleStatusReason: session.userRenamed ? 'new_session_title' : '' }),
|
|
1463
1460
|
projectEntry: sessionAgentType === 'claude' ? encodeClaudeProjectEntry(cwd) : encodeProjectEntry(cwd),
|
|
1464
1461
|
projectPath: cwd,
|
|
1465
1462
|
},
|
|
@@ -9,6 +9,12 @@ const { releaseAdmissionOnExit, retireWorkerOnExit, scheduleWorkerObservation }
|
|
|
9
9
|
// whole frame is built by the SAME bounded pure transport leaf (it copies the fixed typed whitelist out
|
|
10
10
|
// of the payload `_sessionPayload` already produced).
|
|
11
11
|
const { buildCreatedSessionPayload } = require('../coding-session-model-public');
|
|
12
|
+
// WP-2 (docs/codex-routed-fork-cold-restore-guard-2026-10-22.html §5 D-5): the module carries the
|
|
13
|
+
// rationale — a Codex config-load failure is a precondition failure, never a restart-budget consumer.
|
|
14
|
+
const { classifyCodexResumeExit } = require('./codex-resume-exit-classify');
|
|
15
|
+
const { codexProviderMissingMessage } = require('./codex-native-provider-gate'); // WP-1 owns the one block sentence
|
|
16
|
+
// WP-1 (D-1/D-2, S-11): the pure chooser for the block a deferred exit carries (a direct require, like the two leaves above: the injected `runtime` is built in server.js, which this WP does not own).
|
|
17
|
+
const { deferBlockForExit } = require('../resume-blocked-wire');
|
|
12
18
|
|
|
13
19
|
// D-9 (delegated-worker-lifecycle-identity-observability-2026-09-27.html §5 D-9, §9 S-15):
|
|
14
20
|
// best-effort proxy-token revoke for a worker session exiting on the CREATE path (never
|
|
@@ -887,7 +893,7 @@ function installResponseRuntime(runtime) {
|
|
|
887
893
|
// classification BEFORE this handler is installed; the handler then passes its FULL event through it.
|
|
888
894
|
const leadRouteCleanup = runtime.session.agentRole === 'worker' ? null : require('./lead-route-launch').createLeadRouteLifetimeCleanup({ sessionId: runtime.session.id, ownership: runtime.session._leadRouteTokenOwnership, hosted: _useSessionHostProcessForPty() === true });
|
|
889
895
|
const onPtyExit = async (exitEvent) => {
|
|
890
|
-
const { exitCode, signal } = exitEvent || {};
|
|
896
|
+
const { exitCode, signal, exitSource } = exitEvent || {};
|
|
891
897
|
runtime.sessionInputAuthority.observeProvider({ sessionId: runtime.id, status: 'exited' });
|
|
892
898
|
runtime.session._ptyExitLifecycle = {
|
|
893
899
|
stage: 'capturing-viewport',
|
|
@@ -1064,6 +1070,20 @@ function installResponseRuntime(runtime) {
|
|
|
1064
1070
|
// retry path as an exit 1 rather than being mistaken for a completed terminal lifecycle.
|
|
1065
1071
|
const maxAutoRestarts = 3;
|
|
1066
1072
|
const restartCount = runtime.session._autoRestartCount || 0;
|
|
1073
|
+
// WP-2 (§5 D-5, §9 S-9/S-10): classify THIS attempt's buffer — never scrollback — before the
|
|
1074
|
+
// auto-restart decision, and block a Codex config-load failure instead of spending the budget.
|
|
1075
|
+
const codexProviderMissingExit = (agentType === 'codex' && exitedBeforeResumeReady) ? classifyCodexResumeExit(runtime.session._resumeAttemptOutput || '') : null;
|
|
1076
|
+
if (codexProviderMissingExit) {
|
|
1077
|
+
runtime.recordSessionDiagnostic(runtime.id, 'codex-provider-missing-exit', { provider: codexProviderMissingExit.provider, exitCode });
|
|
1078
|
+
// Marked ONCE. This is what makes the auto-restart decision below false
|
|
1079
|
+
// (`resumeBlocked: isSessionResumeBlocked(runtime.id)`), so `handleCreatePrepared` is never
|
|
1080
|
+
// scheduled for this exit; the flow then falls through to the ordinary exit-persistence path,
|
|
1081
|
+
// which sees the session already blocked and keeps the placeholder + startup recipe as is.
|
|
1082
|
+
markSessionResumeBlocked(runtime.id, {
|
|
1083
|
+
code: 'codex_provider_missing', detail: codexProviderMissingExit.line, agentType: 'codex', deterministic: true,
|
|
1084
|
+
message: codexProviderMissingMessage(codexProviderMissingExit.provider),
|
|
1085
|
+
}, 'codex-provider-exit');
|
|
1086
|
+
}
|
|
1067
1087
|
// A worker continuation does launch `-p --resume` / `exec resume`, but its exit is terminal and
|
|
1068
1088
|
// never an interactive auto-restart (that would relaunch it as a TUI under the worker's identity,
|
|
1069
1089
|
// stuck at worker-status 'starting'). lib/session-class.js declares that: relaunchFromRestore=false.
|
|
@@ -1208,25 +1228,22 @@ function installResponseRuntime(runtime) {
|
|
|
1208
1228
|
const exitPersistence = sessionExitPersistenceDecision({
|
|
1209
1229
|
intentionalKill: !!runtime.session._intentionalKill,
|
|
1210
1230
|
exitCode,
|
|
1211
|
-
signal,
|
|
1231
|
+
signal, exitSource,
|
|
1212
1232
|
isResumable,
|
|
1213
1233
|
resumeContext: resumeExitContext(runtime.session), deferOnAbnormalExit: sessionPolicy(runtime.session).deferOnAbnormalExit,
|
|
1214
1234
|
});
|
|
1215
1235
|
runtime.session._ptyExitLifecycle.stage = `persistence-${String(exitPersistence.action || 'unknown')}`;
|
|
1216
1236
|
if (exitPersistence.action === 'defer') {
|
|
1217
1237
|
const latestResumeExit = resumeExitContext(runtime.session);
|
|
1218
|
-
|
|
1219
|
-
|
|
1238
|
+
const label = getAgentCapabilities(agentType).label || 'The agent';
|
|
1239
|
+
// WP-1 (D-1/D-2): the block comes from the exit EVIDENCE -- an unknown exit yields null (defer with no block: exited, and the next input resumes it).
|
|
1240
|
+
const block = deferBlockForExit({ exitPersistence, latestResumeExit, exitCode, signal, autoRestartCount: runtime.session._autoRestartCount || 0, label });
|
|
1241
|
+
if (block && !isSessionResumeBlocked(runtime.id)) {
|
|
1220
1242
|
markSessionResumeBlocked(runtime.id, {
|
|
1221
|
-
|
|
1222
|
-
? (latestResumeExit.code || 'resume_failed')
|
|
1223
|
-
: 'session_crash_loop',
|
|
1224
|
-
message: latestResumeExit.failedBeforeReady
|
|
1225
|
-
? `${label} did not become ready while resuming.`
|
|
1226
|
-
: `${label} exited unexpectedly and automatic recovery was exhausted.`,
|
|
1243
|
+
...block,
|
|
1227
1244
|
detail: resumeFailureDetail(agentType, runtime.session._resumeAttemptOutput || ''),
|
|
1228
1245
|
agentType,
|
|
1229
|
-
});
|
|
1246
|
+
}, 'response-defer');
|
|
1230
1247
|
}
|
|
1231
1248
|
// Keep the CTM identity and startup recipe as a deferred placeholder. Retiring the startup
|
|
1232
1249
|
// task here would make retry impossible. Failed resume mirrors also stay provisional so they
|
|
@@ -1243,7 +1260,8 @@ function installResponseRuntime(runtime) {
|
|
|
1243
1260
|
runtime.session._restoreStarting = false;
|
|
1244
1261
|
runtime.session._lifecycleState = 'deferred';
|
|
1245
1262
|
runtime._markServerSessionTurnClosed(runtime.session, Date.now(), 'session-exit'); // R-15: PROCESS_EXIT, never a dead wait
|
|
1246
|
-
|
|
1263
|
+
// WP-1 (D-1): read the flag, never assert it -- a defer with no block is "exited, resumes on input", not "resuming".
|
|
1264
|
+
runtime.session._resumeBlocked = isSessionResumeBlocked(runtime.id);
|
|
1247
1265
|
runtime.session._exitPersistenceReason = exitPersistence.reason;
|
|
1248
1266
|
runtime.session._restoreTask = {
|
|
1249
1267
|
session_id: runtime.id,
|
|
@@ -120,12 +120,21 @@ async function createWalleSession(state) {
|
|
|
120
120
|
if (createResult) deps.recordWalleTranscriptAppend(session, createResult);
|
|
121
121
|
for (const record of seeded.records) deps.recordWalleTranscriptAppend(session, record);
|
|
122
122
|
try {
|
|
123
|
+
// WP-0 (session-name-agent-title-precedence-2026-10-23.html §5 D-2): reusing an existing
|
|
124
|
+
// Wall-E transcript must not rewrite the row's title or its provenance — the client echoes
|
|
125
|
+
// the displayed name back (D-1), and upsertSession keeps the stored value for any omitted
|
|
126
|
+
// title key (or empty titleSource/titleStatus/titleStatusReason), so omit them on a resume
|
|
127
|
+
// of a row that already has a title.
|
|
128
|
+
const preserveTitleProvenance = !msg._isRestore
|
|
129
|
+
&& (launch.walleResumeRequest || launch.isResumeLaunch) && !!existingTitle?.title;
|
|
123
130
|
void deps.detachOwnerMutation('walle-session-created', () => deps.dbModule.upsertSession(id, {
|
|
124
131
|
agentSessionId: chatSessionId, provider: 'walle', cwd, projectPath: cwd,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
132
|
+
...(preserveTitleProvenance ? {} : {
|
|
133
|
+
title: session.title, userRenamed: !!session.userRenamed,
|
|
134
|
+
titleSource: session.userRenamed ? 'user' : '',
|
|
135
|
+
titleStatus: session.userRenamed ? 'user_named' : '',
|
|
136
|
+
titleStatusReason: session.userRenamed ? 'new_session_title' : '',
|
|
137
|
+
}),
|
|
129
138
|
jsonlPath, fileSize: 0, modifiedAt: '', model: modelId || '', hostname: deps.hostname,
|
|
130
139
|
}));
|
|
131
140
|
} catch (error) {
|