create-walle 0.10.2 → 0.10.3

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.
Files changed (488) hide show
  1. package/README.md +3 -1
  2. package/lib/release-schema.json +2 -2
  3. package/lib/runtime-closure-policy.json +8 -7
  4. package/package.json +1 -1
  5. package/template/coding-task-manager/api-prompts.js +45 -7
  6. package/template/coding-task-manager/bin/claude-hook.js +55 -1
  7. package/template/coding-task-manager/bin/ctm-relay-client.js +35 -12
  8. package/template/coding-task-manager/bin/land.js +2069 -0
  9. package/template/coding-task-manager/bin/memory-census.js +4 -2
  10. package/template/coding-task-manager/bin/memory-sync-verify.js +151 -0
  11. package/template/coding-task-manager/bin/probe-refusal-scope.js +530 -0
  12. package/template/coding-task-manager/db.js +156 -32
  13. package/template/coding-task-manager/lib/acp/acp-client.js +383 -0
  14. package/template/coding-task-manager/lib/acp/dsh-context-truth.js +176 -0
  15. package/template/coding-task-manager/lib/acp/dsh-launch-model.js +302 -0
  16. package/template/coding-task-manager/lib/acp/dsh-mcp-servers.js +194 -0
  17. package/template/coding-task-manager/lib/acp/dsh-prompt-lane.js +616 -0
  18. package/template/coding-task-manager/lib/acp/dsh-route-patch.js +308 -0
  19. package/template/coding-task-manager/lib/acp/dsh-session-runtime.js +522 -0
  20. package/template/coding-task-manager/lib/acp/dsh-transcript.js +395 -0
  21. package/template/coding-task-manager/lib/acp/dsh-usage-log.js +162 -0
  22. package/template/coding-task-manager/lib/acp/ndjson-stdio-transport.js +225 -0
  23. package/template/coding-task-manager/lib/adopted-host-redial.js +47 -11
  24. package/template/coding-task-manager/lib/adopted-launch-environment.js +123 -0
  25. package/template/coding-task-manager/lib/agent-auto-update-runtime.js +0 -3
  26. package/template/coding-task-manager/lib/agent-auto-update.js +26 -10
  27. package/template/coding-task-manager/lib/agent-bundle/build.js +657 -0
  28. package/template/coding-task-manager/lib/agent-bundle/codex-install.js +558 -0
  29. package/template/coding-task-manager/lib/agent-bundle/drift.js +120 -0
  30. package/template/coding-task-manager/lib/agent-bundle/gc.js +522 -0
  31. package/template/coding-task-manager/lib/agent-bundle/inventory.js +346 -0
  32. package/template/coding-task-manager/lib/agent-bundle/push.js +717 -0
  33. package/template/coding-task-manager/lib/agent-bundle/skill-transfer.js +724 -0
  34. package/template/coding-task-manager/lib/agent-bundle/spawn.js +119 -0
  35. package/template/coding-task-manager/lib/agent-bundle/store.js +568 -0
  36. package/template/coding-task-manager/lib/agent-capabilities.js +142 -12
  37. package/template/coding-task-manager/lib/agent-cli-cache.js +30 -5
  38. package/template/coding-task-manager/lib/agent-definitions.js +19 -0
  39. package/template/coding-task-manager/lib/agent-hooks-codex-toml.js +28 -16
  40. package/template/coding-task-manager/lib/agent-hooks-codex-workspace.js +9 -1
  41. package/template/coding-task-manager/lib/agent-hooks-installer.js +23 -8
  42. package/template/coding-task-manager/lib/agent-hooks-prune.js +37 -11
  43. package/template/coding-task-manager/lib/agent-hooks-shim.js +105 -0
  44. package/template/coding-task-manager/lib/agent-presets.js +84 -1
  45. package/template/coding-task-manager/lib/agent-upgrade-jobs.js +12 -1
  46. package/template/coding-task-manager/lib/agent-version-service.js +74 -11
  47. package/template/coding-task-manager/lib/all-sessions-query.js +6 -2
  48. package/template/coding-task-manager/lib/api/agent-bundles.js +436 -0
  49. package/template/coding-task-manager/lib/api/backup-retention.js +83 -0
  50. package/template/coding-task-manager/lib/api/conversation-sync-routes.js +124 -0
  51. package/template/coding-task-manager/lib/api/diagnostics.js +549 -69
  52. package/template/coding-task-manager/lib/api/fleet.js +118 -0
  53. package/template/coding-task-manager/lib/api/hub-bridge.js +243 -0
  54. package/template/coding-task-manager/lib/api/ingress.js +9 -1
  55. package/template/coding-task-manager/lib/api/landing.js +403 -0
  56. package/template/coding-task-manager/lib/api/machines.js +691 -2
  57. package/template/coding-task-manager/lib/api/memory-sync-routes.js +335 -0
  58. package/template/coding-task-manager/lib/api/misc-ops.js +8 -3
  59. package/template/coding-task-manager/lib/api/remote-sessions.js +1326 -0
  60. package/template/coding-task-manager/lib/api/session-attach.js +623 -0
  61. package/template/coding-task-manager/lib/api/session-messages.js +1384 -0
  62. package/template/coding-task-manager/lib/api/sessions.js +77 -51
  63. package/template/coding-task-manager/lib/api/setup-network.js +544 -16
  64. package/template/coding-task-manager/lib/api/setup-wizard-handlers.js +884 -0
  65. package/template/coding-task-manager/lib/api/setup.js +829 -10
  66. package/template/coding-task-manager/lib/api/walle-bridge.js +9 -1
  67. package/template/coding-task-manager/lib/attachment-reconciler-wiring.js +5 -2
  68. package/template/coding-task-manager/lib/auth-rate-limit.js +23 -0
  69. package/template/coding-task-manager/lib/auth-rules.js +292 -21
  70. package/template/coding-task-manager/lib/background-stop.js +369 -0
  71. package/template/coding-task-manager/lib/best-of-attempts.js +4 -1
  72. package/template/coding-task-manager/lib/best-of-launch-plan.js +52 -0
  73. package/template/coding-task-manager/lib/box-keep-awake.js +89 -0
  74. package/template/coding-task-manager/lib/broadcast-reason-stats.js +152 -0
  75. package/template/coding-task-manager/lib/broadcast-serialize.js +5 -0
  76. package/template/coding-task-manager/lib/build-info.js +4 -2
  77. package/template/coding-task-manager/lib/claude-model-truth.js +13 -2
  78. package/template/coding-task-manager/lib/claude-transcript-terminal-renderer.js +17 -13
  79. package/template/coding-task-manager/lib/client-diag-handler.js +64 -2
  80. package/template/coding-task-manager/lib/codex-app-server-client.js +14 -16
  81. package/template/coding-task-manager/lib/codex-configuration-history.js +55 -0
  82. package/template/coding-task-manager/lib/codex-harness-qualification.js +43 -20
  83. package/template/coding-task-manager/lib/codex-rollout-history.js +743 -0
  84. package/template/coding-task-manager/lib/coding-agent-models.js +27 -12
  85. package/template/coding-task-manager/lib/coding-cli-skill-installer.js +44 -14
  86. package/template/coding-task-manager/lib/coding-session-model-adapters.js +43 -2
  87. package/template/coding-task-manager/lib/coding-session-model-owner.js +1 -1
  88. package/template/coding-task-manager/lib/coding-session-model-public.js +7 -1
  89. package/template/coding-task-manager/lib/coding-session-model-runtime.js +23 -4
  90. package/template/coding-task-manager/lib/coding-session-model-scan.js +31 -0
  91. package/template/coding-task-manager/lib/coding-session-model-state.js +7 -0
  92. package/template/coding-task-manager/lib/conversation-freshness-diagnostics.js +280 -0
  93. package/template/coding-task-manager/lib/conversation-item-clock.js +261 -0
  94. package/template/coding-task-manager/lib/conversation-source-registry.js +5 -0
  95. package/template/coding-task-manager/lib/conversation-store-v2.js +44 -0
  96. package/template/coding-task-manager/lib/conversation-sync.js +355 -0
  97. package/template/coding-task-manager/lib/create/resolve-launch-params.js +96 -12
  98. package/template/coding-task-manager/lib/create/worker-role-profile.js +4 -4
  99. package/template/coding-task-manager/lib/create-intent-lease.js +51 -0
  100. package/template/coding-task-manager/lib/ctm-session-context-api.js +97 -6
  101. package/template/coding-task-manager/lib/db-domain-tables.js +1 -0
  102. package/template/coding-task-manager/lib/db-domains.js +1 -0
  103. package/template/coding-task-manager/lib/db-owner-dispatch-probe.js +32 -0
  104. package/template/coding-task-manager/lib/db-owner-request-counts.js +178 -0
  105. package/template/coding-task-manager/lib/db-owner-worker-client.js +68 -14
  106. package/template/coding-task-manager/lib/db-schema-source-files.js +5 -0
  107. package/template/coding-task-manager/lib/deferred-restore-attempt.js +6 -2
  108. package/template/coding-task-manager/lib/feature-manifest.js +4 -0
  109. package/template/coding-task-manager/lib/fleet-conversation-route.js +97 -0
  110. package/template/coding-task-manager/lib/fleet-fanout.js +78 -0
  111. package/template/coding-task-manager/lib/fleet-hub.js +762 -0
  112. package/template/coding-task-manager/lib/fleet-origin-follower.js +252 -0
  113. package/template/coding-task-manager/lib/fleet-resolver-effects.js +147 -0
  114. package/template/coding-task-manager/lib/fleet-resolver-tick.js +148 -0
  115. package/template/coding-task-manager/lib/fleet-resolver.js +454 -0
  116. package/template/coding-task-manager/lib/fleet-session-list-routes.js +114 -0
  117. package/template/coding-task-manager/lib/fleet-session-lists.js +672 -0
  118. package/template/coding-task-manager/lib/fleet-update.js +798 -0
  119. package/template/coding-task-manager/lib/fs-watcher-root-watch.js +141 -0
  120. package/template/coding-task-manager/lib/fs-watcher.js +58 -42
  121. package/template/coding-task-manager/lib/harness-history-transition.js +50 -9
  122. package/template/coding-task-manager/lib/headless-term-service.js +84 -8
  123. package/template/coding-task-manager/lib/hermetic-agent-launch-guard.js +10 -0
  124. package/template/coding-task-manager/lib/history-transition-read-op.js +175 -8
  125. package/template/coding-task-manager/lib/host-cpu-categories.js +310 -0
  126. package/template/coding-task-manager/lib/host-fseventsd-sampler.js +399 -0
  127. package/template/coding-task-manager/lib/host-handoff.js +16 -19
  128. package/template/coding-task-manager/lib/host-stats.js +150 -3
  129. package/template/coding-task-manager/lib/hosted-sync-worker.js +43 -320
  130. package/template/coding-task-manager/lib/hub-bridge-access.js +798 -0
  131. package/template/coding-task-manager/lib/hub-bridge-box.js +409 -0
  132. package/template/coding-task-manager/lib/hub-bridge-mcp.js +380 -0
  133. package/template/coding-task-manager/lib/hub-bridge-mesh.js +313 -0
  134. package/template/coding-task-manager/lib/hub-bridge-ops.js +721 -0
  135. package/template/coding-task-manager/lib/hub-bridge.js +398 -0
  136. package/template/coding-task-manager/lib/idle-notify.js +491 -0
  137. package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +8 -0
  138. package/template/coding-task-manager/lib/instance-manifest.js +5 -1
  139. package/template/coding-task-manager/lib/instance-sync-status.js +18 -8
  140. package/template/coding-task-manager/lib/landing/agent-rule.txt +1 -0
  141. package/template/coding-task-manager/lib/landing/batch-runner.js +366 -0
  142. package/template/coding-task-manager/lib/landing/config.js +536 -0
  143. package/template/coding-task-manager/lib/landing/ctm-view.js +256 -0
  144. package/template/coding-task-manager/lib/landing/gate.js +360 -0
  145. package/template/coding-task-manager/lib/landing/git.js +344 -0
  146. package/template/coding-task-manager/lib/landing/lease.js +247 -0
  147. package/template/coding-task-manager/lib/landing/merge-delta.js +380 -0
  148. package/template/coding-task-manager/lib/landing/proof.js +223 -0
  149. package/template/coding-task-manager/lib/landing/queue.js +998 -0
  150. package/template/coding-task-manager/lib/landing/radar.js +489 -0
  151. package/template/coding-task-manager/lib/landing/resolve-cli.js +424 -0
  152. package/template/coding-task-manager/lib/landing/state.js +263 -0
  153. package/template/coding-task-manager/lib/landing/stop.js +49 -0
  154. package/template/coding-task-manager/lib/landing/sync-cli.js +475 -0
  155. package/template/coding-task-manager/lib/landing/sync.js +456 -0
  156. package/template/coding-task-manager/lib/landing/train.js +885 -0
  157. package/template/coding-task-manager/lib/landing/window.js +57 -0
  158. package/template/coding-task-manager/lib/launch-environment.js +121 -0
  159. package/template/coding-task-manager/lib/launch-model-override.js +36 -10
  160. package/template/coding-task-manager/lib/launch-plan-pool.js +2 -1
  161. package/template/coding-task-manager/lib/launch-plan-warm-cache.js +6 -0
  162. package/template/coding-task-manager/lib/lead-model-observer.js +57 -1
  163. package/template/coding-task-manager/lib/lead-model-options.js +160 -3
  164. package/template/coding-task-manager/lib/lead-model-restore-reconciliation.js +106 -24
  165. package/template/coding-task-manager/lib/lead-model-switch-runtime.js +352 -68
  166. package/template/coding-task-manager/lib/lead-model-switch.js +114 -10
  167. package/template/coding-task-manager/lib/lead-model-transition-runtime.js +14 -2
  168. package/template/coding-task-manager/lib/lead-model-transition-store.js +24 -6
  169. package/template/coding-task-manager/lib/lead-route-preflight.js +179 -0
  170. package/template/coding-task-manager/lib/lead-switch-history-mode.js +110 -0
  171. package/template/coding-task-manager/lib/lead-wire-agent.js +7 -0
  172. package/template/coding-task-manager/lib/lifecycle-wake-experiment.js +9 -2
  173. package/template/coding-task-manager/lib/live-defer-announce.js +45 -0
  174. package/template/coding-task-manager/lib/machine-registry.js +750 -5
  175. package/template/coding-task-manager/lib/macos-capabilities.js +32 -15
  176. package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +433 -53
  177. package/template/coding-task-manager/lib/mainloop-observability.js +14 -9
  178. package/template/coding-task-manager/lib/media-draft-owner.js +16 -3
  179. package/template/coding-task-manager/lib/memory-join.js +337 -0
  180. package/template/coding-task-manager/lib/memory-sync-verify.js +393 -0
  181. package/template/coding-task-manager/lib/memory-sync.js +565 -0
  182. package/template/coding-task-manager/lib/migrations/v57-delegate-pref-mirror-opencode.js +68 -0
  183. package/template/coding-task-manager/lib/migrations/v58-subagent-launch-native-subagents.js +42 -0
  184. package/template/coding-task-manager/lib/migrations/v59-coding-session-model-state-dsh.js +73 -0
  185. package/template/coding-task-manager/lib/migrations/v60-delegate-pref-mirror-dsh.js +78 -0
  186. package/template/coding-task-manager/lib/migrations/v61-conversation-item-clock.js +71 -0
  187. package/template/coding-task-manager/lib/model-availability.js +727 -0
  188. package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +4 -6
  189. package/template/coding-task-manager/lib/model-host-receipt.js +6 -3
  190. package/template/coding-task-manager/lib/model-id-rule.js +19 -0
  191. package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
  192. package/template/coding-task-manager/lib/model-proxy/auto-mode-review.js +110 -23
  193. package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
  194. package/template/coding-task-manager/lib/model-proxy/generation-probe.js +60 -17
  195. package/template/coding-task-manager/lib/model-proxy/historical-tool-declarations.js +37 -0
  196. package/template/coding-task-manager/lib/model-proxy/lead-request-validation.js +1 -0
  197. package/template/coding-task-manager/lib/model-proxy/lead-token-admin.js +130 -0
  198. package/template/coding-task-manager/lib/model-proxy/refusal-scope.js +151 -0
  199. package/template/coding-task-manager/lib/model-proxy/request-budget-store.js +11 -2
  200. package/template/coding-task-manager/lib/model-proxy/tokens.js +69 -5
  201. package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
  202. package/template/coding-task-manager/lib/native-agent-model-args.js +25 -22
  203. package/template/coding-task-manager/lib/native-history-control.js +6 -4
  204. package/template/coding-task-manager/lib/native-lead-settings-client.js +44 -4
  205. package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +173 -36
  206. package/template/coding-task-manager/lib/online-backup-worker-client.js +2 -0
  207. package/template/coding-task-manager/lib/opencode-cli.js +480 -0
  208. package/template/coding-task-manager/lib/opencode-launch-model.js +471 -0
  209. package/template/coding-task-manager/lib/opencode-v1-plugin/model-carrier.mjs +35 -0
  210. package/template/coding-task-manager/lib/opencode-v2-plugin/server.js +118 -0
  211. package/template/coding-task-manager/lib/owner-action-manifest-data.json +1 -1
  212. package/template/coding-task-manager/lib/park-composer-draft.js +54 -0
  213. package/template/coding-task-manager/lib/park-fresh-relaunch.js +41 -10
  214. package/template/coding-task-manager/lib/perf-phase-trace.js +72 -0
  215. package/template/coding-task-manager/lib/perf-summary.js +7 -0
  216. package/template/coding-task-manager/lib/perf-tracker.js +63 -2
  217. package/template/coding-task-manager/lib/project-config.js +7 -0
  218. package/template/coding-task-manager/lib/prompt-purge-v2.js +52 -1
  219. package/template/coding-task-manager/lib/provider-login-broker.js +72 -15
  220. package/template/coding-task-manager/lib/provider-login-tab.js +10 -2
  221. package/template/coding-task-manager/lib/read-pool-client.js +89 -2
  222. package/template/coding-task-manager/lib/read-pool-worker-handle.js +29 -4
  223. package/template/coding-task-manager/lib/recent-catalog-query.js +15 -0
  224. package/template/coding-task-manager/lib/recent-session-catalog-response.js +67 -10
  225. package/template/coding-task-manager/lib/recovery-evidence.js +26 -11
  226. package/template/coding-task-manager/lib/relay-client.js +57 -3
  227. package/template/coding-task-manager/lib/relink-audit.js +34 -5
  228. package/template/coding-task-manager/lib/remote-machine-catalog.js +321 -26
  229. package/template/coding-task-manager/lib/remote-machine-create.js +576 -36
  230. package/template/coding-task-manager/lib/remote-machine-link.js +546 -59
  231. package/template/coding-task-manager/lib/remote-machine-proxy.js +143 -9
  232. package/template/coding-task-manager/lib/remote-session-forward.js +65 -7
  233. package/template/coding-task-manager/lib/remote-terminal-target.js +9 -2
  234. package/template/coding-task-manager/lib/restart-guard.js +18 -6
  235. package/template/coding-task-manager/lib/restore-seed-modes.js +87 -0
  236. package/template/coding-task-manager/lib/restored-scrollback-hydration-runtime.js +22 -2
  237. package/template/coding-task-manager/lib/resume-admission.js +17 -3
  238. package/template/coding-task-manager/lib/review-repo-registry.js +58 -19
  239. package/template/coding-task-manager/lib/root-delivery-runtime.js +2 -2
  240. package/template/coding-task-manager/lib/root-prompt-flow.js +1 -1
  241. package/template/coding-task-manager/lib/runtime-contract.js +3 -1
  242. package/template/coding-task-manager/lib/schema-indexes.js +1 -0
  243. package/template/coding-task-manager/lib/session-context-locator.js +742 -0
  244. package/template/coding-task-manager/lib/session-create/attachment.js +2 -2
  245. package/template/coding-task-manager/lib/session-create/create-phases.js +118 -0
  246. package/template/coding-task-manager/lib/session-create/delegate-worker.js +86 -16
  247. package/template/coding-task-manager/lib/session-create/dsh-session.js +930 -0
  248. package/template/coding-task-manager/lib/session-create/lead-route-launch.js +186 -13
  249. package/template/coding-task-manager/lib/session-create/native-lead-lifetime.js +55 -3
  250. package/template/coding-task-manager/lib/session-create/opencode-lead-provider.js +50 -0
  251. package/template/coding-task-manager/lib/session-create/prepared.js +162 -38
  252. package/template/coding-task-manager/lib/session-create/provider-process.js +160 -28
  253. package/template/coding-task-manager/lib/session-create/response.js +7 -7
  254. package/template/coding-task-manager/lib/session-create/structured-session.js +27 -0
  255. package/template/coding-task-manager/lib/session-create/subagent-pick-table.js +3 -1
  256. package/template/coding-task-manager/lib/session-create/subagent-pick.js +458 -9
  257. package/template/coding-task-manager/lib/session-create/transcript.js +1 -1
  258. package/template/coding-task-manager/lib/session-create/worker-budget-setup.js +21 -2
  259. package/template/coding-task-manager/lib/session-create/worker-continuation.js +7 -1
  260. package/template/coding-task-manager/lib/session-create/worker-launch-sandbox.js +39 -10
  261. package/template/coding-task-manager/lib/session-create/worker-observability.js +2 -2
  262. package/template/coding-task-manager/lib/session-empty.js +13 -10
  263. package/template/coding-task-manager/lib/session-host-manager.js +60 -0
  264. package/template/coding-task-manager/lib/session-jobs.js +3 -1
  265. package/template/coding-task-manager/lib/session-lifecycle.js +10 -4
  266. package/template/coding-task-manager/lib/session-model-action-preconditions.js +6 -2
  267. package/template/coding-task-manager/lib/session-model-intent-contract.js +11 -8
  268. package/template/coding-task-manager/lib/session-park.js +738 -0
  269. package/template/coding-task-manager/lib/session-path-grant.js +13 -4
  270. package/template/coding-task-manager/lib/session-status-ledger.js +106 -4
  271. package/template/coding-task-manager/lib/session-transport.js +17 -0
  272. package/template/coding-task-manager/lib/session-workspace-projection.js +54 -5
  273. package/template/coding-task-manager/lib/session-workspace-tracking-runtime.js +213 -1
  274. package/template/coding-task-manager/lib/skill-autocomplete-query.js +120 -3
  275. package/template/coding-task-manager/lib/skill-autocomplete.js +15 -1
  276. package/template/coding-task-manager/lib/sqlite-file-set.js +39 -0
  277. package/template/coding-task-manager/lib/standup-search-adapter.js +16 -2
  278. package/template/coding-task-manager/lib/state-sync/client-ledger.js +12 -0
  279. package/template/coding-task-manager/lib/state-sync/frame-emitter.js +319 -100
  280. package/template/coding-task-manager/lib/storage/coding-session-model-store.js +4 -1
  281. package/template/coding-task-manager/lib/storage/delegate-pref-mirror-store.js +32 -9
  282. package/template/coding-task-manager/lib/structured-session-boot-sweep.js +235 -0
  283. package/template/coding-task-manager/lib/submission-ack.js +90 -2
  284. package/template/coding-task-manager/lib/systemd-scope.js +115 -0
  285. package/template/coding-task-manager/lib/telemetry-receiver.js +39 -0
  286. package/template/coding-task-manager/lib/terminal-attach-geometry.js +985 -0
  287. package/template/coding-task-manager/lib/terminal-bootstrap.js +30 -0
  288. package/template/coding-task-manager/lib/terminal-draft-operation.js +357 -0
  289. package/template/coding-task-manager/lib/terminal-input-admission.js +17 -1
  290. package/template/coding-task-manager/lib/terminal-input-prepared.js +563 -0
  291. package/template/coding-task-manager/lib/transcript-cache-temps.js +171 -0
  292. package/template/coding-task-manager/lib/transcript-frame-cache.js +8 -3
  293. package/template/coding-task-manager/lib/transcript-source.js +36 -4
  294. package/template/coding-task-manager/lib/transcript-store.js +9 -0
  295. package/template/coding-task-manager/lib/typing-interaction-trace.js +106 -3
  296. package/template/coding-task-manager/lib/usage-ledger/backfill.js +40 -5
  297. package/template/coding-task-manager/lib/usage-ledger/client.js +4 -1
  298. package/template/coding-task-manager/lib/usage-ledger/dsh-parse.js +152 -0
  299. package/template/coding-task-manager/lib/usage-ledger/dsh-read.js +73 -0
  300. package/template/coding-task-manager/lib/usage-ledger/ingest.js +34 -6
  301. package/template/coding-task-manager/lib/usage-ledger/live.js +35 -8
  302. package/template/coding-task-manager/lib/usage-ledger/read.js +29 -20
  303. package/template/coding-task-manager/lib/usage-ledger/schema.js +61 -1
  304. package/template/coding-task-manager/lib/usage-ledger/task-coordinator.js +4 -2
  305. package/template/coding-task-manager/lib/wake-skip-reasons.js +1 -0
  306. package/template/coding-task-manager/lib/walle/turn.js +1 -1
  307. package/template/coding-task-manager/lib/walle-history-serve.js +838 -0
  308. package/template/coding-task-manager/lib/walle-kill-authority.js +2 -2
  309. package/template/coding-task-manager/lib/walle-log-rotation.js +164 -1
  310. package/template/coding-task-manager/lib/walle-mcp-auto-config.js +56 -2
  311. package/template/coding-task-manager/lib/walle-row-history.js +495 -0
  312. package/template/coding-task-manager/lib/walle-supervisor.js +33 -14
  313. package/template/coding-task-manager/lib/watch-worker-client.js +203 -0
  314. package/template/coding-task-manager/lib/worker-terminal-output.js +5 -2
  315. package/template/coding-task-manager/lib/ws/hub-bridge.js +35 -0
  316. package/template/coding-task-manager/lib/ws/lifecycle.js +6 -11
  317. package/template/coding-task-manager/lib/ws/misc.js +1 -1
  318. package/template/coding-task-manager/lib/ws/state-sync.js +106 -4
  319. package/template/coding-task-manager/lib/ws/terminal-attach.js +91 -0
  320. package/template/coding-task-manager/oauth-proxy.js +112 -115
  321. package/template/coding-task-manager/package-lock.json +10 -94
  322. package/template/coding-task-manager/package.json +4 -3
  323. package/template/coding-task-manager/protocol/message-table.json +10 -0
  324. package/template/coding-task-manager/protocol/messages.js +10 -0
  325. package/template/coding-task-manager/protocol/ws-schema.json +20 -0
  326. package/template/coding-task-manager/protocol/ws-schema.md +2 -0
  327. package/template/coding-task-manager/public/css/app.css +170 -0
  328. package/template/coding-task-manager/public/css/walle-session.css +4 -0
  329. package/template/coding-task-manager/public/index.html +473 -243
  330. package/template/coding-task-manager/public/js/agent-command.js +86 -0
  331. package/template/coding-task-manager/public/js/app/backups-panel.js +111 -5
  332. package/template/coding-task-manager/public/js/app/coding-agent-upgrade.js +103 -4
  333. package/template/coding-task-manager/public/js/app/machines-page.js +1328 -8
  334. package/template/coding-task-manager/public/js/app/models-tab.js +113 -38
  335. package/template/coding-task-manager/public/js/app/restart-banner.js +53 -0
  336. package/template/coding-task-manager/public/js/app/worktrees-panel.js +184 -2
  337. package/template/coding-task-manager/public/js/background-work.js +196 -27
  338. package/template/coding-task-manager/public/js/coding-session-model-presentation.js +138 -10
  339. package/template/coding-task-manager/public/js/core/active-session-groups.js +179 -0
  340. package/template/coding-task-manager/public/js/core/pending-create.js +238 -0
  341. package/template/coding-task-manager/public/js/core/recent-machine-source.js +228 -0
  342. package/template/coding-task-manager/public/js/core/session-commands.js +180 -14
  343. package/template/coding-task-manager/public/js/dashboard-client-function-order.json +8 -0
  344. package/template/coding-task-manager/public/js/document-review-links.js +9 -1
  345. package/template/coding-task-manager/public/js/file-resolution-client.js +52 -1
  346. package/template/coding-task-manager/public/js/history-app-seam.js +22 -2
  347. package/template/coding-task-manager/public/js/machine-pool-shared.js +558 -3
  348. package/template/coding-task-manager/public/js/message-renderer.js +4 -3
  349. package/template/coding-task-manager/public/js/model-picker.js +3 -1
  350. package/template/coding-task-manager/public/js/native-subagent-review.js +15 -2
  351. package/template/coding-task-manager/public/js/new-session-agent-grid.js +346 -0
  352. package/template/coding-task-manager/public/js/pasted-content.js +236 -0
  353. package/template/coding-task-manager/public/js/pe-private-content-lifetime.js +39 -7
  354. package/template/coding-task-manager/public/js/private-model-lifetime.js +6 -2
  355. package/template/coding-task-manager/public/js/prompts.js +11 -10
  356. package/template/coding-task-manager/public/js/protocol.js +10 -0
  357. package/template/coding-task-manager/public/js/restart-banner-policy.js +29 -1
  358. package/template/coding-task-manager/public/js/session-model-picker.js +447 -57
  359. package/template/coding-task-manager/public/js/session-search-utils.js +18 -2
  360. package/template/coding-task-manager/public/js/session-usage-view.js +2 -1
  361. package/template/coding-task-manager/public/js/session-workspace-tracking.js +51 -1
  362. package/template/coding-task-manager/public/js/state-sync-client.js +10 -1
  363. package/template/coding-task-manager/public/js/stream-view.js +6 -5
  364. package/template/coding-task-manager/public/js/terminal/conv-only-session.js +287 -0
  365. package/template/coding-task-manager/public/js/terminal/runtime-09.js +2 -0
  366. package/template/coding-task-manager/public/js/terminal/runtime-10.js +5 -5
  367. package/template/coding-task-manager/public/js/terminal/runtime-12.js +8 -1
  368. package/template/coding-task-manager/public/js/terminal/runtime-16.js +4 -0
  369. package/template/coding-task-manager/public/js/terminal/runtime-20.js +1 -1
  370. package/template/coding-task-manager/public/js/terminal-attachment-draft.js +67 -13
  371. package/template/coding-task-manager/public/js/terminal-image-orchestration.js +52 -6
  372. package/template/coding-task-manager/public/js/terminal-wheel.js +23 -3
  373. package/template/coding-task-manager/public/js/walle/chat-conversation.js +4 -4
  374. package/template/coding-task-manager/public/js/walle.js +6 -0
  375. package/template/coding-task-manager/public/js/ws/live-dispatch.js +10 -0
  376. package/template/coding-task-manager/public/m/app.css +140 -0
  377. package/template/coding-task-manager/public/m/app.js +507 -68
  378. package/template/coding-task-manager/public/m/index.html +17 -0
  379. package/template/coding-task-manager/public/m/machines.js +838 -4
  380. package/template/coding-task-manager/public/m/private-api.js +31 -1
  381. package/template/coding-task-manager/public/usage.html +1 -0
  382. package/template/coding-task-manager/scripts/dev-route-fixture/synthetic-route-upstream.js +104 -0
  383. package/template/coding-task-manager/scripts/dev-route-fixture.js +339 -0
  384. package/template/coding-task-manager/scripts/opencode-lead-route-probe.js +195 -0
  385. package/template/coding-task-manager/scripts/opencode-v2-contract-probe/fake-anthropic.js +168 -0
  386. package/template/coding-task-manager/scripts/opencode-v2-contract-probe/probe-plugin/server.js +95 -0
  387. package/template/coding-task-manager/scripts/opencode-v2-contract-probe.js +673 -0
  388. package/template/coding-task-manager/server.js +1950 -11679
  389. package/template/coding-task-manager/session-integrity.js +64 -4
  390. package/template/coding-task-manager/workers/db-owner-worker.js +19 -2
  391. package/template/coding-task-manager/workers/online-backup-worker.js +1 -0
  392. package/template/coding-task-manager/workers/read-pool-model-collector.js +51 -3
  393. package/template/coding-task-manager/workers/read-pool-worker.js +76 -6
  394. package/template/coding-task-manager/workers/session-host-pool-process.js +6 -6
  395. package/template/coding-task-manager/workers/session-host-process.js +5 -5
  396. package/template/coding-task-manager/workers/watch-worker.js +105 -0
  397. package/template/coding-task-manager/workers/worktree-worker.js +168 -33
  398. package/template/ctm-relay/lib/protocol.js +14 -1
  399. package/template/package-lock.json +2 -2
  400. package/template/package.json +1 -1
  401. package/template/shared/backup-retention.js +82 -0
  402. package/template/shared/conversation-admission.js +2 -0
  403. package/template/shared/conversation-provenance.js +3 -7
  404. package/template/shared/conversation-storage-v2-contract.js +5 -0
  405. package/template/shared/linked-worktree.js +51 -0
  406. package/template/shared/macos-fda-target.js +57 -0
  407. package/template/shared/orphan-parents.js +55 -0
  408. package/template/shared/release-schema.json +2 -2
  409. package/template/shared/storage/contract.js +3 -0
  410. package/template/wall-e/adapters/imessage.js +12 -0
  411. package/template/wall-e/agent.js +71 -4
  412. package/template/wall-e/api-walle.js +21 -1
  413. package/template/wall-e/bin/train-gemma-e4b-tooluse.js +2 -2
  414. package/template/wall-e/bin/walle-mcp-stdio.js +5 -5
  415. package/template/wall-e/brain/brain-join.js +859 -0
  416. package/template/wall-e/brain/brain-sync-membership.js +20 -0
  417. package/template/wall-e/brain/brain-sync-store.js +131 -77
  418. package/template/wall-e/brain/brain-sync-v2-schema.js +203 -0
  419. package/template/wall-e/brain/brain-sync-v2.js +578 -0
  420. package/template/wall-e/brain/delegate-model-prefs-store.js +13 -4
  421. package/template/wall-e/brain/deterministic-id.js +51 -0
  422. package/template/wall-e/brain/memories.js +2 -2
  423. package/template/wall-e/brain/migrations.js +40 -6
  424. package/template/wall-e/brain/model-identity-migration.js +1 -1
  425. package/template/wall-e/brain/owner-call-whitelist.js +23 -7
  426. package/template/wall-e/brain/schema.js +45 -85
  427. package/template/wall-e/brain.js +171 -35
  428. package/template/wall-e/chat.js +3 -3
  429. package/template/wall-e/coding/agent-shell-env.js +7 -2
  430. package/template/wall-e/coding/prompt-bundle.js +66 -21
  431. package/template/wall-e/config/main-loop-exemptions.json +2 -2
  432. package/template/wall-e/http/admin-api.js +37 -0
  433. package/template/wall-e/http/gws-api.js +4 -0
  434. package/template/wall-e/http/helper-rules-api.js +7 -1
  435. package/template/wall-e/http/model-admin.js +8 -1
  436. package/template/wall-e/http/model-delegate-pref-admin.js +3 -3
  437. package/template/wall-e/http/model-proxy-providers.js +174 -1
  438. package/template/wall-e/http/model-route-health.js +157 -0
  439. package/template/wall-e/http/sync-api.js +278 -464
  440. package/template/wall-e/jobs/job-runner.js +28 -1
  441. package/template/wall-e/lib/brain-owner-op-priority.js +9 -1
  442. package/template/wall-e/lib/ctm-data-dir.js +26 -0
  443. package/template/wall-e/lib/derivation-home.js +147 -0
  444. package/template/wall-e/lib/helper-rules/evaluate.js +50 -9
  445. package/template/wall-e/lib/helper-rules/grade.js +51 -8
  446. package/template/wall-e/lib/helper-rules/lead-call-hook.js +38 -5
  447. package/template/wall-e/lib/helper-rules/store.js +4 -2
  448. package/template/wall-e/lib/landing-mcp-tools.js +170 -0
  449. package/template/wall-e/lib/mcp-client-config.js +133 -14
  450. package/template/wall-e/lib/mcp-gateway-migrate.js +28 -64
  451. package/template/wall-e/lib/mcp-integration.js +58 -13
  452. package/template/wall-e/lib/parent-death.js +7 -1
  453. package/template/wall-e/lib/scheduler-worker-jobs.js +6 -4
  454. package/template/wall-e/llm/delegate-lead-prefs.js +16 -1
  455. package/template/wall-e/llm/delegate-vendor-default.js +12 -5
  456. package/template/wall-e/llm/model-roles.js +1 -1
  457. package/template/wall-e/llm/native-delegate-selection.js +56 -8
  458. package/template/wall-e/llm/worker-delegate-args.js +72 -7
  459. package/template/wall-e/llm/worker-mcp-routing.js +66 -17
  460. package/template/wall-e/loops/brain-custom-writes.js +19 -0
  461. package/template/wall-e/loops/ingest.js +17 -1
  462. package/template/wall-e/loops/think.js +19 -4
  463. package/template/wall-e/mcp-contract.js +222 -6
  464. package/template/wall-e/mcp-protocol-contract.js +40 -5
  465. package/template/wall-e/mcp-server.js +128 -29
  466. package/template/wall-e/mcp-worker-schemas.js +5 -5
  467. package/template/wall-e/memory/ctm-context-client.js +85 -0
  468. package/template/wall-e/memory/ctm-session-context.js +9 -3
  469. package/template/wall-e/memory/hub-bridge-client.js +388 -0
  470. package/template/wall-e/memory/replica-origin.js +56 -0
  471. package/template/wall-e/memory/session-pool.js +135 -0
  472. package/template/wall-e/runtime/live-capabilities.js +8 -3
  473. package/template/wall-e/server.js +4 -2
  474. package/template/wall-e/shared/release-schema.json +2 -2
  475. package/template/wall-e/shared/sqlite-file-set.js +39 -0
  476. package/template/wall-e/shared/storage/contract.js +3 -0
  477. package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
  478. package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +7 -1
  479. package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +4 -4
  480. package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +6 -6
  481. package/template/wall-e/skills/_templates/fleet-sync/VERSION +1 -0
  482. package/template/wall-e/skills/_templates/fleet-sync/released-sha256.json +16 -0
  483. package/template/wall-e/skills/_templates/fleet-sync/templates/claude/SKILL.md +64 -0
  484. package/template/wall-e/skills/_templates/fleet-sync/templates/codex/SKILL.md +64 -0
  485. package/template/wall-e/tools/chrome-automation.js +6 -0
  486. package/template/wall-e/training/setup-cuda-train-env.sh +2 -2
  487. package/template/wall-e/workers/brain-owner-worker.js +18 -4
  488. package/template/wall-e/workers/mcp-gateway-process.js +5 -5
@@ -67,6 +67,10 @@ const NOT_RESUMABLE_PARK_CODES = new Set(['session_not_resumable']);
67
67
  // this module re-exports the history message projector so no import path changes.
68
68
  const DEFAULT_RELAUNCH_DEADLINE_MS = 180_000;
69
69
  const ACTIVE_JOURNAL_STATES = new Set(['queued', 'preparing', 'compacting', 'verified', 'handing_off', 'awaiting_turn']);
70
+ // WP-1/D-2: provider statuses that mean "this target can never serve a turn as configured" (model
71
+ // access, bad request, auth). Everything else -- 408/409/429/5xx/529, or no status at all -- is
72
+ // transient, so the switch keeps waiting for a later successful turn instead of undoing the pick.
73
+ const PERMANENT_TURN_FAILURE_STATUSES = new Set([400, 401, 403, 404, 422]);
70
74
 
71
75
  // historyPreparationMessage / parkRefusalDetail / PARK_REFUSAL_SENTENCES: see the approved helper.
72
76
 
@@ -141,6 +145,7 @@ function createLeadModelSwitch(deps) {
141
145
  revalidateHistoryBeforePark = async () => ({ ok: false, reason: 'history_revalidation_unavailable' }),
142
146
  cleanupStagedTarget = async () => true,
143
147
  prepareTargetPlan = async () => null,
148
+ resolveSourceHistoryMode = null, // WP-0 D-1; absent -> 'present' (pre-change behaviour)
144
149
  stageTarget = async () => null,
145
150
  getLeadRoute = () => null,
146
151
  persistLeadRoute = () => {},
@@ -386,12 +391,25 @@ function createLeadModelSwitch(deps) {
386
391
  entry.req.cancellable = false;
387
392
  entry.req.detail = 'Restoring the captured source provider lifetime.';
388
393
  emit(entry, 'recovering');
394
+ entry.recoveryRunning = true;
389
395
  entry.recoveryPromise = Promise.resolve().then(() => cleanupStaged(entry)).then(() => recoverSource({
390
396
  sessionId: entry.req.ctm_session_id, source: entry.sourceInfo, transition: entry.transition,
391
397
  targetStarted: !!entry.targetStarted, targetOwner: entry.targetOwner || null,
392
398
  })).then((result) => {
393
399
  entry.recovered = !!result?.ok;
394
- if (!entry.recovered) {
400
+ if (!entry.recovered && recoveryRefusalIsTransient(result)) {
401
+ // Live 10-28 (textbook 3ae5a121, transition 79c30333): the target's first turn was refused
402
+ // and this recovery ran while the server still called the session `running` (it went idle
403
+ // 15 s later); the park refused it and recovery ended for good, leaving the session on a
404
+ // target that cannot serve a turn. A busy refusal waits for the next idle edge instead,
405
+ // exactly like the forward park (REQUEUE_PARK_CODES).
406
+ entry.recoveryPromise = null;
407
+ entry.recoveryAwaitingIdle = true;
408
+ const cause = entry.failureOutcome?.detail || 'The requested target was not applied.';
409
+ entry.req.detail = `${cause} The captured source provider is restored once the session is idle.`;
410
+ diagnostic(entry.req.ctm_session_id, 'lead-transition-source-recovery-deferred', { protections: result.protections || [] });
411
+ armRecoveryRetry(entry, Number(result.retryAt));
412
+ } else if (!entry.recovered) {
395
413
  entry.req.recovery_reason = 'source_recovery_failed';
396
414
  diagnostic(entry.req.ctm_session_id, 'lead-transition-source-recovery-failed', { reason: String(result?.reason || 'unknown').slice(0, 120) });
397
415
  const cause = entry.failureOutcome?.detail || 'The requested target was not applied.';
@@ -412,8 +430,47 @@ function createLeadModelSwitch(deps) {
412
430
  diagnostic(entry.req.ctm_session_id, 'lead-source-recovery-failed', { message: recoveryDetail });
413
431
  emit(entry, entry.req.state);
414
432
  return { ok: false, reason: 'source_recovery_failed' };
415
- });
416
- return entry.recoveryPromise;
433
+ }).finally(() => { entry.recoveryRunning = false; });
434
+ return entry.recoveryPromise || Promise.resolve(null);
435
+ }
436
+
437
+ function recoveryRefusalIsTransient(result) {
438
+ const protections = Array.isArray(result?.protections) ? result.protections : [];
439
+ return REQUEUE_PARK_CODES.has(String(result?.reason || '')) && !protections.some((p) => NON_TRANSIENT_PROTECTIONS.has(p));
440
+ }
441
+
442
+ function armRecoveryRetry(entry, at) {
443
+ if (!Number.isFinite(at) || at <= now()) return;
444
+ if (entry.recoveryRetryTimer) clearTimeoutFn(entry.recoveryRetryTimer);
445
+ entry.recoveryRetryTimer = setTimeoutFn(() => {
446
+ entry.recoveryRetryTimer = null;
447
+ void notifyStatus(entry.req.ctm_session_id);
448
+ }, at - now());
449
+ if (entry.recoveryRetryTimer && typeof entry.recoveryRetryTimer.unref === 'function') entry.recoveryRetryTimer.unref();
450
+ }
451
+
452
+ // A recovery waiting for idle re-runs on the next idle edge, only while its request is still the
453
+ // session's latest one: a new pick replaces the entry, and that pick's own park moves the target.
454
+ function retryRecoveryWhenIdle(entry) {
455
+ const id = entry.req.ctm_session_id;
456
+ if (entry.recoveryCheck) { entry.recoveryEdgePending = true; return entry.recoveryCheck; }
457
+ entry.recoveryCheck = Promise.resolve().then(async () => {
458
+ for (let checkCount = 0; checkCount < 2; checkCount += 1) {
459
+ entry.recoveryEdgePending = false;
460
+ let idle = false;
461
+ try { idle = (await isIdle(id)) === true; }
462
+ catch (error) { diagnostic(id, 'lead-model-idle-check-failed', { message: String(error && error.message || error).slice(0, 200) }); }
463
+ if (entries.get(id) !== entry || !entry.recoveryAwaitingIdle) return;
464
+ if (idle) {
465
+ entry.recoveryAwaitingIdle = false;
466
+ if (entry.recoveryRetryTimer) { clearTimeoutFn(entry.recoveryRetryTimer); entry.recoveryRetryTimer = null; }
467
+ await recoverAbandonedSource(entry);
468
+ return;
469
+ }
470
+ if (!entry.recoveryEdgePending) return;
471
+ }
472
+ }).finally(() => { entry.recoveryCheck = null; });
473
+ return entry.recoveryCheck;
417
474
  }
418
475
 
419
476
  function isPending(entry) {
@@ -468,8 +525,8 @@ function createLeadModelSwitch(deps) {
468
525
  if (!routeId && !effortOnly && !isSwitchableModel(info.agent, modelId)) return refuse(400, 'lead_model_not_switchable', { model_id: modelId });
469
526
  if (scope === 'session+default' && info.agent !== 'codex') return refuse(400, 'lead_default_unsupported', { agent: info.agent });
470
527
  if (info.launchEnvironment?.state !== 'captured') {
471
- return refuse(409, 'lead_switch_unsupported', { reason: 'source_launch_environment_unavailable',
472
- message: 'The source launch environment cannot be recovered safely, so its process was left running.' });
528
+ try { diagnostic(id, 'lead-model-refused', { reason: 'source_launch_environment_unavailable', cause: info.launchEnvironment?.reason ?? null }); } catch (error) { process.stderr.write(`[lead-transition] diagnostic sink failed code=${String(error?.code || 'sink_failed').slice(0, 64)}\n`); }
529
+ return refuse(409, 'lead_switch_unsupported', { reason: 'source_launch_environment_unavailable', cause: info.launchEnvironment?.reason ?? null, message: 'The source launch environment cannot be recovered safely, so its process was left running.' });
473
530
  }
474
531
 
475
532
  const existing = entries.get(id);
@@ -481,7 +538,10 @@ function createLeadModelSwitch(deps) {
481
538
  return refuse(409, 'client_request_conflict');
482
539
  }
483
540
  if (existing?.confirmationPromise) return refuse(409, 'lead_transition_committing', { request: publicRequest(existing.req) });
484
- if (existing?.handoffStarted && (existing.sourceParked || existing.targetStarted) && !existing.recovered && ['cancelled', 'failed'].includes(existing.req.state)) {
541
+ // Only a recovery that is RUNNING blocks a pick. One waiting for idle is superseded by the pick
542
+ // (whose own park waits for idle), and one that ended without restoring the source must not
543
+ // refuse every later pick until a CTM restart (live 10-28, textbook 3ae5a121).
544
+ if (existing?.recoveryRunning && ['cancelled', 'failed'].includes(existing.req.state)) {
485
545
  return refuse(409, 'lead_switch_recovery_in_flight', { request: publicRequest(existing.req) });
486
546
  }
487
547
  if (existing?.req.state === 'awaiting_turn' && existing.handoffStarted && !existing.recovered) {
@@ -611,10 +671,22 @@ function createLeadModelSwitch(deps) {
611
671
  };
612
672
  entries.set(id, entry);
613
673
  emit(entry, 'preparing');
674
+ // WP-0 D-1: resolve the source history mode ONCE, before the plan, the claim and any park; both
675
+ // recipes carry it ('none' = never-prompted source, so a resume-less target is legitimate).
676
+ let historyMode = 'present';
677
+ if (typeof resolveSourceHistoryMode === 'function') {
678
+ try { historyMode = (await resolveSourceHistoryMode(id)) === 'none' ? 'none' : 'present'; }
679
+ catch (error) { // HANDLE: D-2 -- a broken resolver is never evidence the conversation is gone.
680
+ diagnostic(id, 'lead-history-mode-failed', { code: String(error?.code || 'history_mode_failed').slice(0, 64) });
681
+ }
682
+ if (!ownsRequest()) return superseded();
683
+ if (!sourceStillMatches()) return refuse(409, 'model_owner_changed');
684
+ }
685
+ entry.sourceInfo.historyMode = historyMode;
614
686
  try {
615
687
  targetPlan = await prepareTargetPlan({
616
688
  sessionId: id, transitionId: requestId, revision: requestRevision,
617
- source: { ...info, processGeneration: sourceGeneration, providerThreadId: sourceThreadId },
689
+ source: { ...info, processGeneration: sourceGeneration, providerThreadId: sourceThreadId, historyMode },
618
690
  target: { agent: info.agent, routeId: routeId || null, modelId }, signal: controller.signal,
619
691
  });
620
692
  } catch (error) {
@@ -702,7 +774,7 @@ function createLeadModelSwitch(deps) {
702
774
  const pendingClaim = Promise.resolve(beginTransition({
703
775
  transitionId: requestId, revision: requestRevision, sessionId: id, agent: info.agent,
704
776
  source: { ...info, processGeneration: sourceGeneration, providerThreadId: sourceThreadId,
705
- leadRoute: sourceLeadRoute },
777
+ leadRoute: sourceLeadRoute, historyMode },
706
778
  target: { agent: info.agent, routeId: routeId || null, modelId, deployment: routed?.deployment || null,
707
779
  profileFingerprint: routed?.profileFingerprint || null, factsRevision: routed?.deployment?.factsRevision || null,
708
780
  preparedProfile: routed?.preparedProfile || null, resolvedRoute: routed?.resolvedRoute || null },
@@ -768,7 +840,7 @@ function createLeadModelSwitch(deps) {
768
840
  return refuse(500, 'lead_transition_persist_failed', { message: String(error?.message || error).slice(0, 200) });
769
841
  }
770
842
  }
771
- if (providerTransition && info.agent !== 'claude') {
843
+ if (providerTransition && info.agent !== 'claude' && entry.sourceInfo.historyMode !== 'none') {
772
844
  if (!entry.transition) { requestControllers.delete(id); fail(entry, 'lead_transition_owner_unavailable', 'The transition owner is not available.'); return refuse(409, 'lead_transition_owner_unavailable'); }
773
845
  try {
774
846
  historyPreparation = await prepareHistory({
@@ -1012,6 +1084,7 @@ function createLeadModelSwitch(deps) {
1012
1084
  function notifyStatus(sessionId) {
1013
1085
  const id = String(sessionId || '');
1014
1086
  const entry = entries.get(id);
1087
+ if (entry?.recoveryAwaitingIdle) return retryRecoveryWhenIdle(entry);
1015
1088
  if (!entry || !['queued', 'relaunching'].includes(entry.req.state)) return Promise.resolve();
1016
1089
  if (entry.check || entry.req.state === 'relaunching') { entry.edgePending = true; return entry.check || entry.tail; }
1017
1090
  return tryFire(id);
@@ -1338,6 +1411,21 @@ function createLeadModelSwitch(deps) {
1338
1411
  }
1339
1412
  const at = Number(obs.observedAtMs);
1340
1413
  if (!Number.isFinite(at) || at <= Number(req.relaunched_at)) return { settled: false, ignored: 'not_after_relaunch' };
1414
+ /*
1415
+ * WP-1/D-2. A Claude first turn the API REFUSED is a positive failure the transcript already
1416
+ * records (an `isApiErrorMessage` assistant row), so it is decided HERE -- after the owner checks
1417
+ * and the post-relaunch check, and BEFORE `no_model`, because a refusal carries no model name by
1418
+ * construction and used to be swallowed as "no observation yet". A permanent status ends the
1419
+ * switch and the existing failure path restores the captured source; a transient one leaves the
1420
+ * request in `awaiting_turn` for the next successful turn to commit.
1421
+ */
1422
+ if (req.agent === 'claude' && obs.outcome === 'failed') {
1423
+ if (PERMANENT_TURN_FAILURE_STATUSES.has(Number(obs.apiErrorStatus))) {
1424
+ fail(entry, 'turn_failed', `Claude rejected the first turn after the restart: API Error ${obs.apiErrorStatus}. Pick another model.`, { observed_source: obs.source });
1425
+ return { settled: true, state: 'failed' };
1426
+ }
1427
+ return { settled: false, ignored: 'turn_failed_transient' };
1428
+ }
1341
1429
  const model = String(obs.model || '').trim();
1342
1430
  if (!model) return { settled: false, ignored: 'no_model' };
1343
1431
  const observedEffort = typeof obs.effort === 'string' && obs.effort.trim() ? obs.effort.trim().toLowerCase() : null;
@@ -1466,6 +1554,21 @@ function createLeadModelSwitch(deps) {
1466
1554
  return !!entry && entry.req.state === 'awaiting_turn';
1467
1555
  }
1468
1556
 
1557
+ /**
1558
+ * WP-1/D-4 (presumed abort). A restore found a transition this controller still holds in memory in
1559
+ * `awaiting_turn`: the target was never confirmed by a turn, so the switch is abandoned and the
1560
+ * ordinary failure path restores the captured source. Only an in-memory `awaiting_turn` entry is a
1561
+ * side effect; anything else is a typed no-op so the caller can fall back to the journal.
1562
+ * @param {string} sessionId
1563
+ * @param {string} reason
1564
+ */
1565
+ function abandonUnconfirmedTarget(sessionId, reason = 'target_unconfirmed_presumed_abort') {
1566
+ const entry = entries.get(String(sessionId || ''));
1567
+ if (!entry || entry.req.state !== 'awaiting_turn') return { ok: false, reason: 'not_awaiting' };
1568
+ fail(entry, reason, 'The switch was never confirmed by a turn before the session stopped; the original model is restored. Pick the model again to retry.');
1569
+ return { ok: true, state: 'failed' };
1570
+ }
1571
+
1469
1572
  // Resolves when the in-flight relaunch (if any) has finished. For tests and shutdown.
1470
1573
  async function settled(sessionId) {
1471
1574
  const entry = entries.get(String(sessionId || ''));
@@ -1495,7 +1598,8 @@ function createLeadModelSwitch(deps) {
1495
1598
  }
1496
1599
 
1497
1600
  return { request, get, cancel, notifyStatus, observe, observeSelection, wantsObservation, settled,
1498
- adoptAwaitingTransition, recoverUnownedTransition, reconcileUnownedTransition, _effortPlanCount: () => effortPlans.size };
1601
+ abandonUnconfirmedTarget, adoptAwaitingTransition, recoverUnownedTransition, reconcileUnownedTransition,
1602
+ _effortPlanCount: () => effortPlans.size };
1499
1603
  }
1500
1604
 
1501
1605
  module.exports = {
@@ -12,8 +12,10 @@ const { rebindCodexForkLaunchPlan } = require('./launch-model-override');
12
12
  const codexPaths = require('./codex-paths');
13
13
  const { _probeVersion } = require('./harness-model-profile');
14
14
  // S-6: the receipt id and the default control-adapter id are qualified-table constants.
15
- const { CODEX_COMPACTION_SCHEMA_RECEIPT, CODEX_CONTROL_ADAPTER, isQualifiedCodexVersion } =
15
+ const { CODEX_COMPACTION_SCHEMA_RECEIPT, CODEX_CONTROL_ADAPTER } =
16
16
  require('./codex-harness-qualification');
17
+ // WP-2 S-9: the ONE versioned proxy-filter receipt the planner gates the tool-declaration kind on.
18
+ const { CODEX_HISTORICAL_TOOLS_FILTER_RECEIPT } = require('./model-proxy/historical-tool-declarations');
17
19
  const MAX_PROFILE_CATALOG_BYTES = 1024 * 1024;
18
20
 
19
21
  // WP-2A (generation-zero review): the canonical SOURCE generation is the SAME injected canonical
@@ -167,6 +169,16 @@ function createLeadModelTransitionRuntime(host = {}) {
167
169
  factsRevision: target.factsRevision || target.deployment.factsRevision,
168
170
  origin: 'routed-proxy',
169
171
  controlAdapterVersion: transitionControlAdapterVersion,
172
+ // WP-2 D-4: the planner is handed the receipt the DURABLE target recipe carried -- read back
173
+ // from the store on a restart exactly as on the first pass -- and nothing else: any value that
174
+ // is not the one exact constant is normalized to null before the portability gate sees it, so a
175
+ // forged/foreign spelling can never qualify the historical tool-declaration kind. A recipe that
176
+ // never carried the key (a row written before this receipt existed) keeps its exact shape and is
177
+ // therefore treated as having no receipt proof at all, never as an implicit one.
178
+ ...(targetRecipe && Object.hasOwn(targetRecipe, 'historyToolFilterReceipt')
179
+ ? { historyToolFilterReceipt: targetRecipe.historyToolFilterReceipt === CODEX_HISTORICAL_TOOLS_FILTER_RECEIPT
180
+ ? CODEX_HISTORICAL_TOOLS_FILTER_RECEIPT : null }
181
+ : {}),
170
182
  // S-7: the measured binary's version, never a constant. A null profile already refuses in
171
183
  // validateTargetCatalogCompactionProfile before this recipe is built.
172
184
  harnessVersion: target.preparedProfile?.cliVersion,
@@ -535,7 +547,7 @@ async function validateTargetCatalogCompactionProfile({ profile, modelId, profil
535
547
  && profile.compactionHash === null;
536
548
  const present = profile?.compactionHashStatus === 'known-present-with-native-schema-receipt'
537
549
  && typeof profile.compactionHash === 'string' && profile.compactionHash.length > 0;
538
- if (!profile || profile.harness !== 'codex-cli' || !isQualifiedCodexVersion(profile.cliVersion)
550
+ if (!profile || profile.harness !== 'codex-cli' || !nonempty(profile.cliVersion)
539
551
  || profileFingerprint !== profile.fingerprint || factsRevision !== profile.factsRevision
540
552
  || (!absent && !present)
541
553
  || profile.compactionHashSchemaReceipt !== CODEX_COMPACTION_SCHEMA_RECEIPT
@@ -15,8 +15,13 @@ const REVISION_CHANGED_CODE = 'model_revision_changed';
15
15
  const OWNER_CHANGED_CODE = 'model_owner_changed';
16
16
  const EFFORT_CHANGED_CODE = 'model_effort_changed';
17
17
  // S-9: the receipt id is one qualified-table constant, never a local literal.
18
- const { CODEX_COMPACTION_SCHEMA_RECEIPT, isQualifiedCodexVersion } = require('./codex-harness-qualification');
19
- const RECIPE_KEYS = new Set(['id', 'sessionId', 'providerThreadId', 'harness', 'providerId', 'routeId', 'wireModelId', 'sourceModelId', 'processGeneration', 'profileFingerprint', 'authReference', 'approvalPolicyReference', 'compactHash', 'compactHashStatus', 'compactHashSchemaReceipt', 'catalogSha256', 'factsRevision', 'metadataRevision', 'origin', 'controlAdapterVersion', 'programVersion', 'cyberAccessProgram', 'contextTokens', 'usableContextTokens', 'nativeCompactThresholdTokens', 'inputTokens', 'plannedRequestOutputTokens', 'outputCapSource', 'approvalPolicy', 'sandboxPolicy', 'harnessVersion', 'routeTransitionId', 'sourceEffort', 'sourceEnvironmentReference']);
18
+ const { CODEX_COMPACTION_SCHEMA_RECEIPT } = require('./codex-harness-qualification');
19
+ // S-9/WP-2: the SAME one versioned proxy-filter receipt the proxy filter and the planner use.
20
+ const { CODEX_HISTORICAL_TOOLS_FILTER_RECEIPT } = require('./model-proxy/historical-tool-declarations');
21
+ // WP-0 D-1: the journaled history mode's two spellings come from the ONE history-mode module, so the
22
+ // store can never accept a third one the gates would read as 'present'.
23
+ const { HISTORY_MODES, MODEL_ORIGINS } = require('./lead-switch-history-mode');
24
+ const RECIPE_KEYS = new Set(['id', 'sessionId', 'providerThreadId', 'harness', 'providerId', 'routeId', 'wireModelId', 'sourceModelId', 'processGeneration', 'profileFingerprint', 'authReference', 'approvalPolicyReference', 'compactHash', 'compactHashStatus', 'compactHashSchemaReceipt', 'catalogSha256', 'factsRevision', 'metadataRevision', 'origin', 'controlAdapterVersion', 'programVersion', 'cyberAccessProgram', 'contextTokens', 'usableContextTokens', 'nativeCompactThresholdTokens', 'inputTokens', 'plannedRequestOutputTokens', 'outputCapSource', 'approvalPolicy', 'sandboxPolicy', 'harnessVersion', 'routeTransitionId', 'sourceEffort', 'sourceEnvironmentReference', 'historyToolFilterReceipt', 'historyMode', 'modelOrigin']);
20
25
  const PHASE_EDGES = new Map([
21
26
  ['queued', new Set(['preparing', 'failed', 'cancelled'])],
22
27
  ['preparing', new Set(['compacting', 'verified', 'failed', 'cancelled'])],
@@ -55,13 +60,27 @@ function validateRecipe(value, sessionId, generation, label) {
55
60
  if (!copy.id || !copy.sessionId || copy.sessionId !== sessionId || !copy.providerThreadId) throw new TypeError(`${label} is missing its session/thread reference`);
56
61
  if (!Number.isSafeInteger(copy.processGeneration) || copy.processGeneration !== generation) throw new TypeError(`${label} generation mismatch`);
57
62
  if (copy.profileFingerprint != null && !/^[a-f0-9]{64}$/.test(copy.profileFingerprint)) throw new TypeError(`${label} profile fingerprint is invalid`);
58
- if (copy.sourceModelId != null && !/^[A-Za-z0-9@][A-Za-z0-9@._:/+-]{0,255}$/.test(copy.sourceModelId)) throw new TypeError(`${label} source model id is invalid`);
63
+ if (copy.sourceModelId != null && !isValidOverrideModel(copy.sourceModelId)) throw new TypeError(`${label} source model id is invalid`);
59
64
  if (copy.sourceEffort != null && (typeof copy.sourceEffort !== 'string' || !/^[a-z]{2,12}$/.test(copy.sourceEffort))) throw new TypeError(`${label} source effort is invalid`);
60
65
  if (copy.sourceEnvironmentReference != null && !['captured-empty', 'private-owner-required'].includes(copy.sourceEnvironmentReference)) {
61
66
  throw new TypeError(`${label} source environment reference is invalid`);
62
67
  }
68
+ // WP-2: the filter receipt is a fixed, versioned capability claim, never free text. It is either
69
+ // absent/null (no such proof) or EXACTLY the one constant the local proxy filter issues; any other
70
+ // value or shape is refused here so a persisted row can never invent one.
71
+ if (copy.historyToolFilterReceipt != null && copy.historyToolFilterReceipt !== CODEX_HISTORICAL_TOOLS_FILTER_RECEIPT) {
72
+ throw new TypeError(`${label}.historyToolFilterReceipt is not a known receipt`);
73
+ }
63
74
  if (copy.approvalPolicy != null && !['never', 'on-request', 'on-failure', 'untrusted'].includes(copy.approvalPolicy)) throw new TypeError(`${label}.approvalPolicy is invalid`);
64
75
  if (copy.origin != null && !['native-codex', 'native-claude', 'routed-proxy', 'portable-transcript', 'unknown'].includes(copy.origin)) throw new TypeError(`${label}.origin is invalid`);
76
+ // WP-0 D-1: the journaled history mode is one of exactly two spellings. ABSENT is valid and reads
77
+ // as 'present' (openHistoryMode), so every recipe written before this change stays loadable; an
78
+ // unknown spelling is refused here rather than being silently read back as 'present'.
79
+ if (copy.historyMode != null && !HISTORY_MODES.includes(copy.historyMode)) throw new TypeError(`${label}.historyMode is invalid`);
80
+ // WP-2 D-6 (§5/§6): the source recipe records WHERE the source model came from, in exactly two
81
+ // spellings. ABSENT stays valid (a recipe written before this change reads as 'observed'); a third
82
+ // spelling is refused here rather than being read back as 'observed'.
83
+ if (copy.modelOrigin != null && !MODEL_ORIGINS.includes(copy.modelOrigin)) throw new TypeError(`${label}.modelOrigin is invalid`);
65
84
  if (copy.routeTransitionId != null && !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(copy.routeTransitionId)) throw new TypeError(`${label}.routeTransitionId is invalid`);
66
85
  if (copy.cyberAccessProgram != null && (typeof copy.cyberAccessProgram !== 'string' || copy.cyberAccessProgram.length > 128)) throw new TypeError(`${label}.cyberAccessProgram is invalid`);
67
86
  for (const key of ['factsRevision', 'metadataRevision']) {
@@ -79,7 +98,7 @@ function validateRecipe(value, sessionId, generation, label) {
79
98
  if (compactionMetadataPresent && (!qualifiedCompactionValue
80
99
  || copy.compactHashSchemaReceipt !== CODEX_COMPACTION_SCHEMA_RECEIPT
81
100
  || !/^[a-f0-9]{64}$/.test(String(copy.catalogSha256 || ''))
82
- || copy.harness !== 'codex-cli' || !isQualifiedCodexVersion(copy.harnessVersion)
101
+ || copy.harness !== 'codex-cli' || !(typeof copy.harnessVersion === 'string' && copy.harnessVersion.trim())
83
102
  || !/^[a-f0-9]{64}$/.test(String(copy.profileFingerprint || '')))) {
84
103
  throw new TypeError(`${label} compaction absence lacks a qualified generated-catalog receipt`);
85
104
  }
@@ -116,8 +135,7 @@ function validateTransitionRequest(value) {
116
135
  }
117
136
  const modelId = value.modelId;
118
137
  const effort = value.effort;
119
- if (typeof modelId !== 'string' || modelId.length < 1 || modelId.length > 200
120
- || !/^[A-Za-z0-9][A-Za-z0-9._:/@+\-\[\]]*$/.test(modelId)) throw new TypeError('transition request modelId is invalid');
138
+ if (!isValidOverrideModel(modelId)) throw new TypeError('transition request modelId is invalid');
121
139
  if (effort !== null && effort !== undefined && effort !== ''
122
140
  && (typeof effort !== 'string' || !/^[a-z]{2,12}$/.test(effort))) throw new TypeError('transition request effort is invalid');
123
141
  if (!['session', 'session+default'].includes(value.scope) || typeof value.effortOnly !== 'boolean') {
@@ -0,0 +1,179 @@
1
+ 'use strict';
2
+
3
+ // WP-1 (docs/lead-route-model-invocability-preflight-2026-10-28.html §5 D-1/D-2, §6.2).
4
+ //
5
+ // A routed Lead switch used to validate only that the route resolves and the model is in the
6
+ // catalog, then parked the running session and relaunched it — learning on the FIRST turn that the
7
+ // route cannot run the model (live 10-28: proxy log
8
+ // `upstream_outcome=model_not_invocable upstream_status=400 retryable=0`). This module runs the
9
+ // proxy's EXISTING one-message generation probe against (route, model) while the switch is still
10
+ // being validated, before any park, and refuses the switch on a non-retryable verdict.
11
+ //
12
+ // The readiness steps mirror lib/session-create/worker-continuation.js's probeWorkerRoute (health
13
+ // wait, route push, then the probe) because that is the one proven order: a freshly-(re)started
14
+ // proxy has an empty route table, so the route push has to precede the probe.
15
+ //
16
+ // Fail-open by construction (D-2): a transport failure, a timeout, a retryable verdict, an
17
+ // admin-channel error, a thrown probe or an unready proxy all return `{ refusal: null }` and the
18
+ // switch proceeds exactly as today — the existing first-turn path still guards it.
19
+ //
20
+ // Dependency-injected, like the other *-runtime factories: nothing is looked up by name at module
21
+ // load, so requiring this module stays inert and a test can supply its own stubs.
22
+
23
+ const { normalizeUpstreamOutcome, SAFE_MESSAGES } = require('./model-proxy/upstream-outcome');
24
+ const { READINESS_BUDGET_MS } = require('./session-create/proxy-route-readiness');
25
+
26
+ const PROBE_TIMEOUT_MS = 30_000;
27
+ // One bounded diagnostic vocabulary: every string a diagnostic carries is capped here, so a
28
+ // provider-supplied value can never grow the diagnostic sink.
29
+ const DIAGNOSTIC_TEXT_MAX = 120;
30
+ const REPORT_ERROR_MAX = 200;
31
+ const PICK_ANOTHER = ' Pick another route or model.';
32
+
33
+ // The switch's reason vocabulary (§6.2). Every non-retryable kind maps to a reason; only an access
34
+ // denial gets its own (the user's fix differs: ask the account owner, not "try a different id").
35
+ const REFUSAL_REASONS = Object.freeze({
36
+ model_access_denied: 'target_model_access_denied',
37
+ model_not_invocable: 'target_model_not_invocable',
38
+ model_not_found: 'target_model_not_invocable',
39
+ });
40
+
41
+ // §12a rule 3: refuse ONLY on a kind that proves the pair cannot run the model. Every other kind --
42
+ // `invalid_prompt` above all -- is refused by nothing, because the probe's own fixed 16-token
43
+ // request can be what the upstream disliked; the live first turn is the authority for those.
44
+ const REFUSING_KINDS = new Set(Object.keys(REFUSAL_REASONS));
45
+
46
+ function _bound(value) {
47
+ return String(value == null ? '' : value).slice(0, DIAGNOSTIC_TEXT_MAX);
48
+ }
49
+
50
+ function createLeadRoutePreflight({ getSupervisor, getReadiness, report, diagnostic, now = Date.now } = {}) {
51
+ // The D-3 report is FIRE-AND-FORGET by contract: it is a health hint for the picker, never an
52
+ // input to the switch. It is never awaited, a synchronous throw is swallowed the same way as a
53
+ // rejection, and a failure is DIAGNOSED (REPORT disposition) rather than raised.
54
+ function reportHealth(sessionId, body) {
55
+ if (typeof report !== 'function') return;
56
+ const failed = (error) => {
57
+ try {
58
+ if (typeof diagnostic !== 'function') return;
59
+ diagnostic(sessionId, 'lead-route-health-report-failed', {
60
+ message: String((error && error.message) || error).slice(0, REPORT_ERROR_MAX),
61
+ });
62
+ } catch { /* expected: a diagnostic sink failure must never change the switch outcome */ }
63
+ };
64
+ let pending;
65
+ try {
66
+ pending = report(body);
67
+ } catch (error) {
68
+ failed(error);
69
+ return;
70
+ }
71
+ if (pending && typeof pending.then === 'function') Promise.resolve(pending).catch(failed);
72
+ }
73
+
74
+ // Exactly ONE diagnostic per invocation, with the bounded vocabulary §6.2 names.
75
+ function recordDiagnostic(sessionId, detail) {
76
+ try {
77
+ if (typeof diagnostic !== 'function') return;
78
+ diagnostic(sessionId, 'lead-route-preflight', {
79
+ route_id: _bound(detail.routeId),
80
+ model: _bound(detail.modelId),
81
+ ok: detail.ok === true,
82
+ kind: detail.kind == null ? null : _bound(detail.kind),
83
+ // §12a rule 1: which wire the verdict is about -- a verdict on the wrong wire is not a
84
+ // verdict, and the one line has to say which one it was taken on.
85
+ wire: detail.wire == null ? null : _bound(detail.wire),
86
+ http_status: Number.isInteger(detail.httpStatus) ? detail.httpStatus : null,
87
+ probe_ms: Number.isFinite(detail.probeMs) ? Math.max(0, Math.round(detail.probeMs)) : null,
88
+ ...(detail.skipped ? { skipped: _bound(detail.skipped) } : {}),
89
+ });
90
+ } catch { /* expected: a diagnostic sink failure must never change the switch outcome */ }
91
+ }
92
+
93
+ // A skipped preflight is not a verdict: the switch proceeds and the skip is diagnosed so the
94
+ // missing evidence is visible (the same disposition the delegate path gives an unready proxy).
95
+ function skip(sessionId, routeId, modelId, skipped) {
96
+ recordDiagnostic(sessionId, { routeId, modelId, ok: false, skipped, probeMs: null });
97
+ return { refusal: null };
98
+ }
99
+
100
+ return async function preflight({ routeId, modelId, sessionId, signal, face } = {}) {
101
+ // 1. The proxy supervisor is the only process that can run the probe (it holds the route's
102
+ // credentials and CA). A host without one has no probe to run and no verdict to give.
103
+ let supervisor = null;
104
+ try {
105
+ supervisor = typeof getSupervisor === 'function' ? getSupervisor() : null;
106
+ } catch { return { refusal: null }; /* expected: an unreadable supervisor is no probe */ }
107
+ if (!supervisor || typeof supervisor.probeRouteGeneration !== 'function') return { refusal: null };
108
+
109
+ // 2/3. The same readiness order the delegate path uses: healthy proxy, then route push.
110
+ try {
111
+ if (typeof supervisor.isRunning === 'function' && !supervisor.isRunning()) {
112
+ const started = supervisor.start();
113
+ if (!started || started.ok === false) return skip(sessionId, routeId, modelId, 'proxy_unready');
114
+ }
115
+ const readiness = typeof getReadiness === 'function' ? getReadiness() : null;
116
+ await readiness.waitForProxyHealth(READINESS_BUDGET_MS, signal);
117
+ await readiness.pushRoutesIfNeeded(routeId, signal);
118
+ } catch { return skip(sessionId, routeId, modelId, 'proxy_unready'); }
119
+
120
+ // 4. The probe itself, honouring the request's own cancellation.
121
+ const startedAt = now();
122
+ let result;
123
+ try {
124
+ result = await supervisor.probeRouteGeneration(routeId, { model: modelId, timeoutMs: PROBE_TIMEOUT_MS, face });
125
+ } catch { return skip(sessionId, routeId, modelId, 'probe_threw'); }
126
+ const probeMs = now() - startedAt;
127
+ const httpStatus = Number.isInteger(result && result.http_status) ? result.http_status : null;
128
+ const succeeded = !!(result && result.generation_succeeded === true);
129
+ const outcome = (result && result.failure && result.failure.upstream_outcome) || null;
130
+ const kind = outcome ? outcome.kind : null;
131
+ const wire = (result && typeof result.probe_wire === 'string') ? result.probe_wire : null;
132
+ const diagnostic = { routeId, modelId, ok: succeeded, kind, wire, httpStatus, probeMs };
133
+ // §12a rule 4: a verdict is only wire-independent when the route offers exactly one wire -- on a
134
+ // multi-wire route the same pair can be invocable on the wire the probe dialled and dead on the
135
+ // one another harness uses, so this switch keeps the refusal local and reports nothing.
136
+ const reportable = !!result && result.route_wire_count === 1;
137
+
138
+ // A superseded request is the caller's to refuse: this path never reports and never refuses on its
139
+ // own. The probe DID run and DID produce a verdict, so the one diagnostic is still recorded (D-2
140
+ // "always record ONE diagnostic") — the verdict is evidence even when nothing acts on it.
141
+ if (signal && signal.aborted) {
142
+ recordDiagnostic(sessionId, diagnostic);
143
+ return { refusal: null };
144
+ }
145
+
146
+ if (succeeded) {
147
+ if (reportable) reportHealth(sessionId, { route_id: routeId, model: modelId, ok: true, outcome: null, http_status: httpStatus });
148
+ recordDiagnostic(sessionId, diagnostic);
149
+ return { refusal: null };
150
+ }
151
+
152
+ if (kind && REFUSING_KINDS.has(kind)) {
153
+ // §12a rule 3: only a kind that proves the pair cannot run the model refuses. A non-retryable
154
+ // `invalid_prompt` (the probe's own 16-token shape, an upstream that dislikes it) does NOT:
155
+ // it falls through to the proceed path below.
156
+ if (reportable) {
157
+ // §12a rule 4: the pair-class verdict is recorded with the status and the kind, exactly as
158
+ // before -- but only when it holds for every harness the route serves.
159
+ reportHealth(sessionId, { route_id: routeId, model: modelId, ok: false, outcome: _bound(kind), http_status: httpStatus });
160
+ }
161
+ recordDiagnostic(sessionId, diagnostic);
162
+ return {
163
+ refusal: {
164
+ ok: false,
165
+ reason: REFUSAL_REASONS[kind],
166
+ message: (SAFE_MESSAGES[kind] || SAFE_MESSAGES.invalid_prompt) + PICK_ANOTHER,
167
+ },
168
+ };
169
+ }
170
+
171
+ // Retryable, no upstream outcome, `http_status: null`, an HTTP-level admin error
172
+ // ({error:'route_unknown'}), or a non-retryable kind that only indicts the probe's own request:
173
+ // no report (a probe is not a load signal) and no refusal.
174
+ recordDiagnostic(sessionId, diagnostic);
175
+ return { refusal: null };
176
+ };
177
+ }
178
+
179
+ module.exports = { createLeadRoutePreflight };
@@ -0,0 +1,110 @@
1
+ 'use strict';
2
+
3
+ // WP-0/WP-1 (docs/lead-switch-never-prompted-session-2026-10-30.html §5 D-1..D-4, §6): ONE history
4
+ // mode per transition — 'none' (this source lifetime has nothing to carry) or 'present' (today's
5
+ // behaviour) — decided ONCE before the switch claims its transition, carried on both recipes, and
6
+ // read back by every gate that used to re-derive (or silently assume) the answer.
7
+ //
8
+ // D-2, the rule that keeps this safe: 'none' is returned ONLY for a DEFINITE absence. The oracle is
9
+ // the same off-thread resume ladder park already consults (lib/park-fresh-relaunch.js
10
+ // `parkFindsNothingToResume`); a false answer, a thrown error, a timeout or an agent with no ladder
11
+ // all mean 'present'. "An unreadable store is not evidence that the conversation is gone"
12
+ // (superset-sh/superset#7144, adopted in §3 P-3), so a flaky ladder can only cost a refusal, never a
13
+ // lost conversation. A recipe written before this change carries no `historyMode` key and reads as
14
+ // 'present' — byte-for-byte today's path.
15
+
16
+ const { extractResumeTarget } = require('./session-history');
17
+ const { parkFindsNothingToResume } = require('./park-fresh-relaunch');
18
+
19
+ const HISTORY_MODES = Object.freeze(['none', 'present']);
20
+ // WP-2 D-6 (§5/§6): the only two spellings a source recipe's `modelOrigin` may carry.
21
+ const MODEL_ORIGINS = Object.freeze(['observed', 'cli-default']);
22
+
23
+ /**
24
+ * Resolve the SOURCE lifetime's history mode. NEVER throws (D-2): any error, rejection or timeout is
25
+ * 'present'.
26
+ *
27
+ * @param {object} input
28
+ * @param {'claude'|'codex'|string} input.agentType
29
+ * @param {string} input.ctmSessionId
30
+ * @param {string} input.jsonlPath claude: the transcript path park would stat; codex: the rollout path or ''
31
+ * @param {string} [input.resumeId] the source provider thread id
32
+ * @param {boolean} [input.resumeEvidence] true when park's own inputs already found a transcript/rollout ON DISK
33
+ * @param {(query: object) => Promise<{ rung?: string }>} input.resolveRung resolveResumeRungOffThread bound to dbPath
34
+ * @returns {Promise<'none'|'present'>}
35
+ */
36
+ async function resolveSourceHistoryMode({ agentType, ctmSessionId, jsonlPath, resumeId, resumeEvidence = false, resolveRung } = {}) {
37
+ try {
38
+ // A transcript/rollout file already on disk is proof there IS something to resume, so the ladder
39
+ // is never consulted for it (park short-circuits the same way, lib/park-fresh-relaunch.js).
40
+ if (resumeEvidence === true) return 'present';
41
+ const nothingToResume = await parkFindsNothingToResume({ agentType, ctmSessionId, jsonlPath, resumeId, resolveRung });
42
+ return nothingToResume === true ? 'none' : 'present';
43
+ } catch {
44
+ // HANDLE: D-2 — an unreadable store, a rejected ladder or a timeout is never evidence of absence,
45
+ // so the refusal side of every gate keeps today's behaviour.
46
+ return 'present';
47
+ }
48
+ }
49
+
50
+ /** Recipe read with the D-2 default: a missing or unknown key is 'present'. */
51
+ function recipeHistoryMode(recipe) {
52
+ return recipe?.historyMode === 'none' ? 'none' : 'present';
53
+ }
54
+
55
+ /**
56
+ * WP-2 D-6 (§5/§6): WHERE the source lifetime's model came from — recorded, never guessed.
57
+ *
58
+ * 'cli-default' means this source never named a model of its own: the mode is 'none' (nothing was
59
+ * ever prompted) AND `cliDefaultModel` (captured by the runtime's `getSessionInfo`) proved an empty
60
+ * `session.model_id`, no `--model`/`-m`/`--model=` in the captured launch argv and no
61
+ * `session_lead_model` pin row. Rolling such a source back must relaunch the ORIGINAL argv and pass
62
+ * NO one-shot model override at all, so the CLI's own default applies by construction (D-6 rejected
63
+ * both substituting `cli_default` as `--model` and re-reading the CLI's config file).
64
+ *
65
+ * Everything else is 'observed' — today's meaning: the model was read from the session/argv, and a
66
+ * rollback passes it as the one-shot override exactly as before. A recipe written before this change
67
+ * carries no `cliDefaultModel`, so it reads 'observed'.
68
+ */
69
+ function sourceModelOrigin(source) {
70
+ return recipeHistoryMode(source) === 'none' && source?.cliDefaultModel === true ? 'cli-default' : 'observed';
71
+ }
72
+
73
+ /** The value of a Claude argv's `--session-id <id>` / `--session-id=<id>`, or '' when absent. */
74
+ function claudeSessionIdArg(args) {
75
+ const list = Array.isArray(args) ? args.map(String) : [];
76
+ for (let i = 0; i < list.length; i += 1) {
77
+ if (list[i] === '--session-id') return String(list[i + 1] || '');
78
+ if (list[i].startsWith('--session-id=')) return list[i].slice('--session-id='.length);
79
+ }
80
+ return '';
81
+ }
82
+
83
+ /**
84
+ * The single thread clause shared by the pre-park planner check (D-4) and authorizeLiveTargetRestore
85
+ * (D-3) — one definition, so the two gates can never drift apart (§7).
86
+ *
87
+ * For 'present' it is EXACTLY the historical conjunct: the plan must resume the target recipe's own
88
+ * provider thread. For 'none' the plan must carry no resume selector at all, and a Claude plan's own
89
+ * `--session-id` must name the target recipe's thread: that argv is what the fresh process is given,
90
+ * so it is the thread its canonical owner will report.
91
+ *
92
+ * @returns {boolean}
93
+ */
94
+ function targetThreadClause({ source, target, launchPlan } = {}) {
95
+ const planThread = String(launchPlan?.resume?.sessionId || '');
96
+ const targetThread = String(target?.providerThreadId || '');
97
+ if (recipeHistoryMode(target) === 'present') return targetThread.length > 0 && planThread === targetThread;
98
+ // A 'none' target recipe is only meaningful with a 'none' SOURCE: the mode is one fact per
99
+ // transition, recorded twice.
100
+ if (recipeHistoryMode(source) !== 'none') return false;
101
+ if (planThread !== '') return false;
102
+ if (extractResumeTarget(launchPlan?.agentType, launchPlan?.args || [])) return false;
103
+ if (launchPlan?.agentType === 'claude') return targetThread.length > 0 && claudeSessionIdArg(launchPlan.args) === targetThread;
104
+ // codex: the new thread id is unknown until the thread links, so the owner check after launch
105
+ // binds it instead.
106
+ return true;
107
+ }
108
+
109
+ module.exports = { HISTORY_MODES, MODEL_ORIGINS, resolveSourceHistoryMode, recipeHistoryMode,
110
+ sourceModelOrigin, claudeSessionIdArg, targetThreadClause };
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+ // The CLI wire a lead agent speaks to CTM's model proxy. OpenCode leads and OpenCode's routed
3
+ // workers both use the Anthropic Messages face, so route servability is asked of 'claude'.
4
+ // This is the ONE rule: public/js/session-model-picker.js mirrors it as `leadWireAgent` for the
5
+ // Lead AND Delegate tabs, and tests pin the two bodies equal (docs/opencode-model-selection-round-2-shared-route-logic-2026-10-30.html §5 R-1, §6.1, §7).
6
+ function leadWireAgent(agent) { return agent === 'opencode' ? 'claude' : agent; }
7
+ module.exports = { leadWireAgent };