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.
Files changed (341) hide show
  1. package/README.md +4 -0
  2. package/lib/owner-write-safety.js +135 -0
  3. package/lib/public-runtime.js +13 -5
  4. package/lib/release-schema.json +3 -2
  5. package/lib/runtime-closure-policy.json +6 -4
  6. package/package.json +1 -1
  7. package/template/bin/deps-doctor.js +1 -1
  8. package/template/bin/node-bin.sh +11 -0
  9. package/template/coding-task-manager/api-prompts.js +10 -22
  10. package/template/coding-task-manager/bin/claude-hook.js +7 -3
  11. package/template/coding-task-manager/bin/claude-table-parity.js +307 -0
  12. package/template/coding-task-manager/bin/conversation-storage-auto-migration-lib.js +16 -1
  13. package/template/coding-task-manager/bin/conversation-storage-generation-evidence.js +9 -2
  14. package/template/coding-task-manager/bin/conversation-storage-service-entry-lib.js +16 -1
  15. package/template/coding-task-manager/bin/conversation-storage-walle-owner.js +247 -0
  16. package/template/coding-task-manager/bin/ctm-machine-pair.js +160 -0
  17. package/template/coding-task-manager/bin/lead-wait-census.js +1348 -0
  18. package/template/coding-task-manager/bin/repair-foreign-clone.js +564 -0
  19. package/template/coding-task-manager/bin/repair-resume-echo-titles.js +248 -0
  20. package/template/coding-task-manager/bin/write-lock-bench.js +111 -0
  21. package/template/coding-task-manager/db.js +705 -286
  22. package/template/coding-task-manager/git-utils.js +44 -0
  23. package/template/coding-task-manager/lib/adopted-host-redial.js +4 -0
  24. package/template/coding-task-manager/lib/agent-presets.js +15 -0
  25. package/template/coding-task-manager/lib/agent-title-apply.js +88 -0
  26. package/template/coding-task-manager/lib/agent-title-backfill-read.js +92 -0
  27. package/template/coding-task-manager/lib/api/host-stats.js +98 -0
  28. package/template/coding-task-manager/lib/api/ingress.js +35 -12
  29. package/template/coding-task-manager/lib/api/machine-pairing.js +285 -0
  30. package/template/coding-task-manager/lib/api/machine-pool.js +307 -0
  31. package/template/coding-task-manager/lib/api/machines.js +461 -0
  32. package/template/coding-task-manager/lib/api/misc-ops.js +20 -5
  33. package/template/coding-task-manager/lib/api/providers.js +53 -6
  34. package/template/coding-task-manager/lib/api/resume-from-store.js +25 -0
  35. package/template/coding-task-manager/lib/api/sessions.js +160 -8
  36. package/template/coding-task-manager/lib/api/transcript-history.js +17 -4
  37. package/template/coding-task-manager/lib/api/worktrees.js +38 -37
  38. package/template/coding-task-manager/lib/approval-drift.js +16 -1
  39. package/template/coding-task-manager/lib/approval-logical-frame.js +5 -1
  40. package/template/coding-task-manager/lib/approval-scope.js +68 -4
  41. package/template/coding-task-manager/lib/auth-context.js +32 -2
  42. package/template/coding-task-manager/lib/auth-rate-limit.js +41 -1
  43. package/template/coding-task-manager/lib/auth-rules.js +186 -45
  44. package/template/coding-task-manager/lib/broadcast-serialize.js +2 -1
  45. package/template/coding-task-manager/lib/browser-session-broker.js +9 -1
  46. package/template/coding-task-manager/lib/browser-use-adapter.js +33 -2
  47. package/template/coding-task-manager/lib/claude-markdown-ansi.js +27 -85
  48. package/template/coding-task-manager/lib/claude-session-title.js +6 -30
  49. package/template/coding-task-manager/lib/claude-table-layout.js +322 -0
  50. package/template/coding-task-manager/lib/client-diag-handler.js +5 -0
  51. package/template/coding-task-manager/lib/codex-config-guard.js +33 -3
  52. package/template/coding-task-manager/lib/codex-harness-qualification.js +12 -0
  53. package/template/coding-task-manager/lib/codex-launch-health.js +29 -2
  54. package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
  55. package/template/coding-task-manager/lib/codex-paths.js +2 -1
  56. package/template/coding-task-manager/lib/coding-agent-models.js +21 -30
  57. package/template/coding-task-manager/lib/coding-session-model-adapters.js +27 -2
  58. package/template/coding-task-manager/lib/coding-session-model-projection.js +5 -0
  59. package/template/coding-task-manager/lib/coding-session-model-runtime.js +1 -2
  60. package/template/coding-task-manager/lib/coding-session-model-state.js +10 -0
  61. package/template/coding-task-manager/lib/coding-session-model-sync-state.js +0 -1
  62. package/template/coding-task-manager/lib/conversation-import/cursor-guard-owner.js +10 -0
  63. package/template/coding-task-manager/lib/create/resolve-launch-params.js +8 -1
  64. package/template/coding-task-manager/lib/ctm-boot-lifecycle.js +17 -1
  65. package/template/coding-task-manager/lib/db-domain-tables.js +7 -0
  66. package/template/coding-task-manager/lib/db-domains.js +21 -2
  67. package/template/coding-task-manager/lib/db-owner-op-registry.js +7 -0
  68. package/template/coding-task-manager/lib/db-schema-source-files.js +1 -0
  69. package/template/coding-task-manager/lib/feature-manifest.js +1 -1
  70. package/template/coding-task-manager/lib/fs-watcher.js +3 -2
  71. package/template/coding-task-manager/lib/harness-history-transition.js +3 -3
  72. package/template/coding-task-manager/lib/headless-inflight-ops.js +203 -0
  73. package/template/coding-task-manager/lib/headless-missing-reply.js +42 -0
  74. package/template/coding-task-manager/lib/headless-term-service.js +55 -17
  75. package/template/coding-task-manager/lib/host-control-dial.js +167 -0
  76. package/template/coding-task-manager/lib/host-handoff-orchestrator.js +16 -2
  77. package/template/coding-task-manager/lib/host-handoff.js +51 -13
  78. package/template/coding-task-manager/lib/host-health-ledger.js +164 -12
  79. package/template/coding-task-manager/lib/host-self-truth-watchdog.js +4 -0
  80. package/template/coding-task-manager/lib/host-stats.js +328 -0
  81. package/template/coding-task-manager/lib/host-threadpool-evacuation.js +160 -0
  82. package/template/coding-task-manager/lib/host-threadpool-monitor.js +14 -1
  83. package/template/coding-task-manager/lib/hosted-input-dispatch.js +203 -0
  84. package/template/coding-task-manager/lib/hosted-sync-worker.js +113 -5
  85. package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +4 -0
  86. package/template/coding-task-manager/lib/instance-manifest-runtime.js +3 -0
  87. package/template/coding-task-manager/lib/instance-manifest.js +13 -1
  88. package/template/coding-task-manager/lib/instance-sync-status.js +15 -0
  89. package/template/coding-task-manager/lib/launch-model-override.js +160 -1
  90. package/template/coding-task-manager/lib/launch-plan-worker.js +59 -2
  91. package/template/coding-task-manager/lib/lead-model-switch-runtime.js +57 -4
  92. package/template/coding-task-manager/lib/lead-model-switch.js +3 -2
  93. package/template/coding-task-manager/lib/lead-model-transition-store.js +61 -3
  94. package/template/coding-task-manager/lib/legacy-draft-cancellation-recovery.js +111 -0
  95. package/template/coding-task-manager/lib/login-tripwire.js +1 -0
  96. package/template/coding-task-manager/lib/machine-pairing.js +238 -0
  97. package/template/coding-task-manager/lib/machine-pool.js +409 -0
  98. package/template/coding-task-manager/lib/machine-registry.js +749 -0
  99. package/template/coding-task-manager/lib/machine-self.js +66 -0
  100. package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +52 -4
  101. package/template/coding-task-manager/lib/media-journal-control-reader.js +473 -0
  102. package/template/coding-task-manager/lib/media-journal.js +84 -27
  103. package/template/coding-task-manager/lib/media-store-options.js +43 -15
  104. package/template/coding-task-manager/lib/migrations/v56-host-samples-and-machine-pool.js +76 -0
  105. package/template/coding-task-manager/lib/mirror-feed-sanitize.js +12 -3
  106. package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
  107. package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
  108. package/template/coding-task-manager/lib/model-proxy/handoff.js +494 -0
  109. package/template/coding-task-manager/lib/model-proxy/lead-worker-picks.js +25 -0
  110. package/template/coding-task-manager/lib/model-proxy/native-lead-listener.js +61 -7
  111. package/template/coding-task-manager/lib/model-proxy/proxy-cli.js +200 -0
  112. package/template/coding-task-manager/lib/model-proxy/proxy-instance-record.js +257 -0
  113. package/template/coding-task-manager/lib/model-proxy/stream-accounting.js +205 -0
  114. package/template/coding-task-manager/lib/model-proxy/tokens.js +78 -0
  115. package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
  116. package/template/coding-task-manager/lib/native-history-control.js +2 -2
  117. package/template/coding-task-manager/lib/native-lead-settings-client.js +2 -1
  118. package/template/coding-task-manager/lib/native-lead-tls.js +18 -4
  119. package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +628 -64
  120. package/template/coding-task-manager/lib/owner-action-manifest-data.json +14 -0
  121. package/template/coding-task-manager/lib/painted-row-recorder.js +9 -1
  122. package/template/coding-task-manager/lib/prompt-submission-contract.js +21 -1
  123. package/template/coding-task-manager/lib/provider-login-broker.js +192 -9
  124. package/template/coding-task-manager/lib/provider-login-capture.js +153 -0
  125. package/template/coding-task-manager/lib/queue-draft-status-overlay.js +36 -0
  126. package/template/coding-task-manager/lib/raw-codex-history-window.js +5 -3
  127. package/template/coding-task-manager/lib/read-pool-client.js +5 -2
  128. package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
  129. package/template/coding-task-manager/lib/recent-catalog-query.js +26 -0
  130. package/template/coding-task-manager/lib/recent-session-catalog-response.js +9 -0
  131. package/template/coding-task-manager/lib/remote-command-context.js +12 -0
  132. package/template/coding-task-manager/lib/remote-command-runtime.js +13 -1
  133. package/template/coding-task-manager/lib/remote-machine-catalog.js +1062 -0
  134. package/template/coding-task-manager/lib/remote-machine-create.js +693 -0
  135. package/template/coding-task-manager/lib/remote-machine-link.js +939 -0
  136. package/template/coding-task-manager/lib/remote-machine-proxy.js +520 -0
  137. package/template/coding-task-manager/lib/remote-session-forward.js +306 -0
  138. package/template/coding-task-manager/lib/remote-submission-status.js +6 -0
  139. package/template/coding-task-manager/lib/restart-outcome-truth.js +20 -3
  140. package/template/coding-task-manager/lib/restart-preflight.js +9 -2
  141. package/template/coding-task-manager/lib/restore-backoff-gate.js +10 -2
  142. package/template/coding-task-manager/lib/restore-prepare-retry.js +12 -5
  143. package/template/coding-task-manager/lib/restore-resume-batch.js +22 -1
  144. package/template/coding-task-manager/lib/resume-blocked-wire.js +62 -1
  145. package/template/coding-task-manager/lib/resume-cwd.js +13 -0
  146. package/template/coding-task-manager/lib/resume-from-store.js +12 -1
  147. package/template/coding-task-manager/lib/resume-origin-facts.js +38 -1
  148. package/template/coding-task-manager/lib/resume-preflight.js +21 -16
  149. package/template/coding-task-manager/lib/review-repo-admission.js +39 -6
  150. package/template/coding-task-manager/lib/review-repo-registry.js +17 -0
  151. package/template/coding-task-manager/lib/root-delivery-runtime.js +72 -15
  152. package/template/coding-task-manager/lib/root-delivery-selection.js +37 -1
  153. package/template/coding-task-manager/lib/root-prompt-admission.js +33 -2
  154. package/template/coding-task-manager/lib/root-prompt-flow.js +167 -33
  155. package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
  156. package/template/coding-task-manager/lib/session-analysis-memory.js +2 -0
  157. package/template/coding-task-manager/lib/session-create/codex-native-provider-gate.js +218 -0
  158. package/template/coding-task-manager/lib/session-create/codex-resume-exit-classify.js +52 -0
  159. package/template/coding-task-manager/lib/session-create/delegate-worker.js +95 -6
  160. package/template/coding-task-manager/lib/session-create/identity.js +20 -1
  161. package/template/coding-task-manager/lib/session-create/prepared.js +69 -1
  162. package/template/coding-task-manager/lib/session-create/provider-launch-context.js +77 -0
  163. package/template/coding-task-manager/lib/session-create/provider-process.js +34 -37
  164. package/template/coding-task-manager/lib/session-create/response.js +30 -12
  165. package/template/coding-task-manager/lib/session-create/transcript.js +13 -4
  166. package/template/coding-task-manager/lib/session-create/worker-continuation.js +43 -1
  167. package/template/coding-task-manager/lib/session-create/worker-notices.js +94 -5
  168. package/template/coding-task-manager/lib/session-create/worker-observability.js +99 -7
  169. package/template/coding-task-manager/lib/session-create/worker-stop.js +11 -3
  170. package/template/coding-task-manager/lib/session-create/worker-wait-watcher.js +315 -0
  171. package/template/coding-task-manager/lib/session-geometry-authority-handler.js +10 -1
  172. package/template/coding-task-manager/lib/session-host-manager.js +152 -206
  173. package/template/coding-task-manager/lib/session-jobs.js +17 -9
  174. package/template/coding-task-manager/lib/session-lifecycle.js +18 -0
  175. package/template/coding-task-manager/lib/session-model-action-preconditions.js +9 -0
  176. package/template/coding-task-manager/lib/session-resource-sampler.js +19 -9
  177. package/template/coding-task-manager/lib/session-standup.js +67 -4
  178. package/template/coding-task-manager/lib/session-status-ledger.js +42 -3
  179. package/template/coding-task-manager/lib/session-status-runtime.js +45 -1
  180. package/template/coding-task-manager/lib/session-title-mirror.js +25 -57
  181. package/template/coding-task-manager/lib/session-transcript-trash.js +8 -1
  182. package/template/coding-task-manager/lib/startup-pointer-write.js +57 -0
  183. package/template/coding-task-manager/lib/state-sync/painted-frame-field.js +11 -0
  184. package/template/coding-task-manager/lib/storage/coding-session-model-store.js +21 -22
  185. package/template/coding-task-manager/lib/surviving-owner-bootstrap.js +7 -2
  186. package/template/coding-task-manager/lib/surviving-owner-recovery.js +2 -1
  187. package/template/coding-task-manager/lib/terminal-draft-http-status.js +25 -0
  188. package/template/coding-task-manager/lib/transcript-archiver.js +229 -229
  189. package/template/coding-task-manager/lib/transcript-frame-cache.js +12 -2
  190. package/template/coding-task-manager/lib/transcript-guard-job.js +55 -4
  191. package/template/coding-task-manager/lib/transcript-link.js +77 -0
  192. package/template/coding-task-manager/lib/transcript-manifest-store.js +8 -4
  193. package/template/coding-task-manager/lib/transcript-source.js +8 -3
  194. package/template/coding-task-manager/lib/transcript-store.js +40 -0
  195. package/template/coding-task-manager/lib/tunnel-lane-governor.js +6 -3
  196. package/template/coding-task-manager/lib/typing-interaction-trace.js +92 -0
  197. package/template/coding-task-manager/lib/typing-worker-stages.js +60 -0
  198. package/template/coding-task-manager/lib/visible-revision-watchdog.js +8 -12
  199. package/template/coding-task-manager/lib/wake-skip-reasons.js +46 -0
  200. package/template/coding-task-manager/lib/walle-supervisor.js +98 -14
  201. package/template/coding-task-manager/lib/worker-failure-classify.js +13 -1
  202. package/template/coding-task-manager/lib/ws/state-sync.js +19 -2
  203. package/template/coding-task-manager/lib/ws/terminal-attach.js +32 -1
  204. package/template/coding-task-manager/lib/ws-router.js +68 -1
  205. package/template/coding-task-manager/lib/ws-send-admission.js +22 -4
  206. package/template/coding-task-manager/oauth-proxy.js +99 -55
  207. package/template/coding-task-manager/package-lock.json +24 -0
  208. package/template/coding-task-manager/package.json +3 -0
  209. package/template/coding-task-manager/providers/codex-mcp-extract.js +121 -19
  210. package/template/coding-task-manager/providers/codex-mcp.js +14 -5
  211. package/template/coding-task-manager/public/css/app.css +279 -1
  212. package/template/coding-task-manager/public/css/reviews.css +4 -0
  213. package/template/coding-task-manager/public/index.html +294 -223
  214. package/template/coding-task-manager/public/js/app/machines-page.js +1252 -0
  215. package/template/coding-task-manager/public/js/app/models-tab.js +546 -36
  216. package/template/coding-task-manager/public/js/app/worktrees-panel.js +7 -5
  217. package/template/coding-task-manager/public/js/background-work.js +13 -7
  218. package/template/coding-task-manager/public/js/coding-session-model-presentation.js +44 -4
  219. package/template/coding-task-manager/public/js/core/session-commands.js +676 -0
  220. package/template/coding-task-manager/public/js/core/session-selectors.js +5 -1
  221. package/template/coding-task-manager/public/js/history-app-seam.js +26 -2
  222. package/template/coding-task-manager/public/js/history-painted-rows.js +17 -2
  223. package/template/coding-task-manager/public/js/history-reader-park.js +13 -1
  224. package/template/coding-task-manager/public/js/history-scroller.js +187 -9
  225. package/template/coding-task-manager/public/js/history-transcript-pages.js +5 -5
  226. package/template/coding-task-manager/public/js/machine-pool-shared.js +436 -0
  227. package/template/coding-task-manager/public/js/private-state-scope.js +31 -3
  228. package/template/coding-task-manager/public/js/prompt-submission-client.js +173 -8
  229. package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
  230. package/template/coding-task-manager/public/js/queue-panel-submission-reconcile.js +174 -1
  231. package/template/coding-task-manager/public/js/reviews.js +7 -3
  232. package/template/coding-task-manager/public/js/root-draft-lifetime.js +139 -2
  233. package/template/coding-task-manager/public/js/root-queue-builder-lifetime.js +16 -0
  234. package/template/coding-task-manager/public/js/row-join.js +4 -2
  235. package/template/coding-task-manager/public/js/session-model-picker.js +1 -1
  236. package/template/coding-task-manager/public/js/session-search-utils.js +46 -19
  237. package/template/coding-task-manager/public/js/state-sync-client.js +4 -0
  238. package/template/coding-task-manager/public/js/terminal/runtime-02.js +16 -7
  239. package/template/coding-task-manager/public/js/terminal/runtime-03.js +41 -0
  240. package/template/coding-task-manager/public/js/terminal/runtime-04.js +41 -4
  241. package/template/coding-task-manager/public/js/terminal/runtime-12.js +52 -2
  242. package/template/coding-task-manager/public/js/terminal/runtime-16.js +12 -12
  243. package/template/coding-task-manager/public/js/terminal/runtime-18.js +14 -0
  244. package/template/coding-task-manager/public/js/terminal/runtime-19.js +8 -4
  245. package/template/coding-task-manager/public/js/terminal/runtime-20.js +210 -73
  246. package/template/coding-task-manager/public/js/terminal/runtime-21.js +19 -6
  247. package/template/coding-task-manager/public/js/terminal/runtime-22.js +86 -0
  248. package/template/coding-task-manager/public/js/terminal-history-viewport.js +1 -1
  249. package/template/coding-task-manager/public/js/terminal-wheel.js +39 -2
  250. package/template/coding-task-manager/public/js/walle/chat-conversation.js +11 -3
  251. package/template/coding-task-manager/public/js/walle-session.js +30 -7
  252. package/template/coding-task-manager/public/m/app.css +226 -8
  253. package/template/coding-task-manager/public/m/app.js +502 -9
  254. package/template/coding-task-manager/public/m/index.html +38 -1
  255. package/template/coding-task-manager/public/m/machines.js +1313 -0
  256. package/template/coding-task-manager/public/m/pool-failover.js +391 -0
  257. package/template/coding-task-manager/public/m/private-queue-actions.js +26 -7
  258. package/template/coding-task-manager/public/m/sw.js +5 -0
  259. package/template/coding-task-manager/queue-engine.js +13 -0
  260. package/template/coding-task-manager/server/approvals.js +5 -6
  261. package/template/coding-task-manager/server/boot.js +136 -17
  262. package/template/coding-task-manager/server.js +902 -239
  263. package/template/coding-task-manager/session-integrity.js +7 -9
  264. package/template/coding-task-manager/workers/db-owner-worker.js +53 -7
  265. package/template/coding-task-manager/workers/read-pool-worker.js +17 -6
  266. package/template/coding-task-manager/workers/session-host-pool-process.js +18 -4
  267. package/template/coding-task-manager/workers/session-host-process.js +14 -3
  268. package/template/coding-task-manager/workers/transcript-archive-worker.js +1 -1
  269. package/template/coding-task-manager/workers/worktree-worker.js +34 -0
  270. package/template/package-lock.json +2 -2
  271. package/template/package.json +1 -1
  272. package/template/scripts/conversation-storage-v2-migration.js +78 -11
  273. package/template/shared/conversation-storage-v2-contract.js +42 -6
  274. package/template/shared/fixtures/worker-status-payloads.json +392 -0
  275. package/template/shared/git-remote-key.js +83 -0
  276. package/template/shared/host-identity.js +98 -2
  277. package/template/shared/owner-write-safety.js +135 -0
  278. package/template/shared/public-runtime.js +13 -5
  279. package/template/shared/release-schema.json +3 -2
  280. package/template/shared/report-citations.js +14 -0
  281. package/template/shared/sqlite-write-lock.js +361 -223
  282. package/template/shared/storage/contract.js +35 -3
  283. package/template/wall-e/agent.js +21 -0
  284. package/template/wall-e/api-walle.js +5 -0
  285. package/template/wall-e/bin/brain-purge-legacy-ctm.js +3 -2
  286. package/template/wall-e/bin/walle-mcp-stdio.js +12 -6
  287. package/template/wall-e/brain/helper-rules-schema.js +129 -0
  288. package/template/wall-e/brain/migrations.js +51 -22
  289. package/template/wall-e/brain/model-deployment-facts.js +31 -1
  290. package/template/wall-e/brain/owner-call-whitelist.js +16 -0
  291. package/template/wall-e/brain/schema-ensures.js +22 -0
  292. package/template/wall-e/brain.js +1 -1
  293. package/template/wall-e/coding/journal-dispatcher.js +51 -0
  294. package/template/wall-e/coding/session-journal.js +28 -40
  295. package/template/wall-e/coding-orchestrator.js +23 -26
  296. package/template/wall-e/http/auth.js +18 -1
  297. package/template/wall-e/http/blog-views-api.js +318 -0
  298. package/template/wall-e/http/helper-rules-api.js +321 -0
  299. package/template/wall-e/http/model-proxy-providers.js +3 -1
  300. package/template/wall-e/http/telemetry-api.js +12 -0
  301. package/template/wall-e/lib/blog-view-policy.js +88 -0
  302. package/template/wall-e/lib/blog-view-store.js +169 -0
  303. package/template/wall-e/lib/client-config-lock.js +2 -1
  304. package/template/wall-e/lib/coding-journal-writer-child-client.js +67 -18
  305. package/template/wall-e/lib/error-kinds.js +10 -1
  306. package/template/wall-e/lib/feature-manifest.js +1 -1
  307. package/template/wall-e/lib/helper-rules/cell-state.js +123 -0
  308. package/template/wall-e/lib/helper-rules/evaluate.js +357 -0
  309. package/template/wall-e/lib/helper-rules/explore-rubric.md +12 -0
  310. package/template/wall-e/lib/helper-rules/grade.js +277 -0
  311. package/template/wall-e/lib/helper-rules/label.js +247 -0
  312. package/template/wall-e/lib/helper-rules/lead-call-hook.js +522 -0
  313. package/template/wall-e/lib/helper-rules/store.js +667 -0
  314. package/template/wall-e/lib/mcp-integration.js +5 -6
  315. package/template/wall-e/lib/runtime-worker-pool.js +1 -0
  316. package/template/wall-e/lib/scheduler-worker-jobs.js +6 -0
  317. package/template/wall-e/lib/telemetry-db-schema.js +37 -0
  318. package/template/wall-e/lib/telemetry-process-client.js +76 -1
  319. package/template/wall-e/llm/default-fallback.js +4 -0
  320. package/template/wall-e/llm/model-catalog.js +13 -4
  321. package/template/wall-e/llm/model-roles.js +6 -0
  322. package/template/wall-e/llm/portkey-http.js +150 -0
  323. package/template/wall-e/llm/portkey-sync.js +15 -145
  324. package/template/wall-e/llm/worker-mcp-routing.js +61 -0
  325. package/template/wall-e/llm/worker-route.js +43 -1
  326. package/template/wall-e/loops/brain-custom-writes.js +45 -0
  327. package/template/wall-e/mcp-protocol-contract.js +41 -1
  328. package/template/wall-e/mcp-server.js +13 -2
  329. package/template/wall-e/mcp-worker-schemas.js +20 -2
  330. package/template/wall-e/memory/ctm-context-client.js +25 -5
  331. package/template/wall-e/shared/release-schema.json +3 -2
  332. package/template/wall-e/shared/sqlite-write-lock.js +361 -223
  333. package/template/wall-e/shared/storage/contract.js +35 -3
  334. package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
  335. package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +6 -0
  336. package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +7 -2
  337. package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +7 -2
  338. package/template/wall-e/tools/local-tools.js +6 -4
  339. package/template/wall-e/workers/brain-owner-worker.js +9 -0
  340. package/template/wall-e/workers/coding-journal-writer-child.js +8 -1
  341. package/template/wall-e/workers/telemetry-worker.js +8 -0
@@ -10,7 +10,7 @@ const {
10
10
  createRootDeliveryRuntime, getRootDeliveryJobLifetime, isRootDeliveryJobLifetimeCurrent,
11
11
  } = require('./root-delivery-runtime');
12
12
  const { ROOT_PROMPT_ACTION } = require('./remote-command-purpose');
13
- const { selectRootDeliveryAttempt } = require('./root-delivery-selection');
13
+ const { selectRootDeliveryAttempt, selectRootDeliveryRecovery } = require('./root-delivery-selection');
14
14
  const promptAdmissionTrace = require('./prompt-admission-trace');
15
15
 
16
16
  const TERMINAL_STATUSES = new Set([
@@ -19,6 +19,49 @@ const TERMINAL_STATUSES = new Set([
19
19
  const PREFLIGHT_RETRYABLE = new Set([
20
20
  'session_input_in_flight', 'session_resume_not_ready', 'command_deferred_until_idle',
21
21
  ]);
22
+ const PREFLIGHT_USER_ACTION = new Set(['session_not_live', 'session_waiting_for_approval']);
23
+ // WP-0 (queue-delivery-recovery): one wording per recovery action, so the persisted
24
+ // row, the broadcast frame and the direct reply cannot describe the same verdict
25
+ // differently. Both describe the preserved prompt and the action the user owns;
26
+ // neither claims the prompt was not sent while its effect is unknown.
27
+ const RESEND_MESSAGE = 'Your prompt is saved and was not sent. Choose Resend to try again.';
28
+ const REVIEW_MESSAGE = 'CTM could not safely confirm this desktop delivery. Review the submission before resending.';
29
+ const SAFE_DIAGNOSTIC_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/u;
30
+
31
+ function safeDiagnosticId(value) {
32
+ return typeof value === 'string' && SAFE_DIAGNOSTIC_ID_RE.test(value) ? value : '';
33
+ }
34
+
35
+ // WP-0: what a cached (restart-surviving) parent row plus its scoped delivery
36
+ // receipts prove about the prompt, and the exact action the user owns. Delegates
37
+ // the proof rules to root-delivery-selection so read evidence and classification
38
+ // cannot drift; anything uncertified stays `unknown`/`review`.
39
+ function classifyReplayRecovery(snapshot) {
40
+ const verdict = selectRootDeliveryRecovery(snapshot);
41
+ if (verdict.deliveryState === 'sent') {
42
+ const recorded = String(snapshot?.parent?.status || '');
43
+ return Object.freeze({
44
+ recoveryAction: verdict.recoveryAction, deliveryState: verdict.deliveryState,
45
+ reason: '', // Canonical Root confirmation stores 'sent' for provider_transcript_match.
46
+ terminalStatus: recorded === 'sent' ? 'durable_user_message_seen' : recorded,
47
+ });
48
+ }
49
+ if (verdict.recoveryAction === 'resend') {
50
+ // The certified no-effect proof names the exact earlier blocker; keep that
51
+ // specific reason instead of flattening it to the generic refusal code.
52
+ const blocker = snapshot?.latest?.result?.result?.delivery_evidence?.blocker;
53
+ const specific = typeof blocker === 'string'
54
+ && (PREFLIGHT_RETRYABLE.has(blocker) || PREFLIGHT_USER_ACTION.has(blocker)) ? blocker : '';
55
+ return Object.freeze({
56
+ recoveryAction: verdict.recoveryAction, deliveryState: verdict.deliveryState,
57
+ reason: specific, terminalStatus: '',
58
+ });
59
+ }
60
+ return Object.freeze({
61
+ recoveryAction: verdict.recoveryAction, deliveryState: verdict.deliveryState,
62
+ reason: '', terminalStatus: '',
63
+ });
64
+ }
22
65
  const SAFE_DIAGNOSTIC_ERROR_KINDS = new Set(['ReferenceError', 'TypeError', 'RangeError', 'SqliteError', 'Error']);
23
66
  const SAFE_DIAGNOSTIC_SOURCE_FILES = new Set([
24
67
  'root-prompt-admission.js', 'remote-relay-store.js', 'server.js', 'main-db-census.js',
@@ -50,6 +93,7 @@ function safeResult(response, publicId, sessionId, fallbackSource = 'unknown') {
50
93
  reason: mapped.reason,
51
94
  source: mapped.source,
52
95
  retryable: mapped.retryable,
96
+ recoveryAction: mapped.recoveryAction,
53
97
  ...(mapped.message ? { message: mapped.message } : {}),
54
98
  ...(detail.duplicate === true || response?.duplicate === true ? { duplicate: true } : {}),
55
99
  ...(detail.retrying === true || response?.retrying === true ? { retrying: true } : {}),
@@ -69,6 +113,7 @@ function safeResult(response, publicId, sessionId, fallbackSource = 'unknown') {
69
113
  reason: mapped.reason,
70
114
  source: mapped.source,
71
115
  retryable: mapped.retryable,
116
+ recoveryAction: mapped.recoveryAction,
72
117
  ...(mapped.message ? { message: mapped.message } : {}),
73
118
  ...(Number.isInteger(httpStatus) && httpStatus >= 400 && httpStatus <= 599 ? { httpStatus } : {}),
74
119
  };
@@ -95,6 +140,11 @@ function createRootPromptFlow(deps = {}) {
95
140
  const observerLifetimes = new WeakMap();
96
141
  const sessionDrains = new WeakMap();
97
142
  const httpRequestScopes = new WeakMap();
143
+ // WP-0: the normalized session/client-message correlation for the operation
144
+ // currently running on a captured scope, so every diagnostic that scope emits
145
+ // (admission, driver, delivery stage, replay recovery) can be joined to the one
146
+ // user-visible submission. Both values are bounded ids, never receipt ids.
147
+ const scopeCorrelation = new WeakMap();
98
148
 
99
149
  function registryFor(db) {
100
150
  let registry = registries.get(db);
@@ -121,6 +171,9 @@ function createRootPromptFlow(deps = {}) {
121
171
  function reportFailure(scope, phase, code, details = {}) {
122
172
  if (!scope?.isCurrent()) return;
123
173
  const event = { category: 'root-prompt-flow', phase, code };
174
+ const correlation = scopeCorrelation.get(scope);
175
+ if (correlation?.sessionId) event.sessionId = correlation.sessionId;
176
+ if (correlation?.clientMessageId) event.clientMessageId = correlation.clientMessageId;
124
177
  for (const key of ['returnedRecordConfirmed', 'canonicalRecordConfirmed', 'recordFieldsMatch']) {
125
178
  if (typeof details[key] === 'boolean') event[key] = details[key];
126
179
  }
@@ -183,6 +236,11 @@ function createRootPromptFlow(deps = {}) {
183
236
  },
184
237
  isDeliveryCurrent: (_handle, metadata) => scope.isCurrent() === true && now() < metadata.expiresAt,
185
238
  now,
239
+ // WP-0 criterion 6: the driver reports the exact rejected handoff stage
240
+ // (delivery_snapshot / delivery_text / delivery_selection / delivery_dispatch /
241
+ // admission_metadata) with a static code. reportFailure bounds the event and
242
+ // swallows a throwing sink, so diagnostics can never change an outcome.
243
+ reportDiagnostic: (event) => reportFailure(scope, event?.phase, event?.code, {}),
186
244
  });
187
245
  }
188
246
 
@@ -231,14 +289,18 @@ function createRootPromptFlow(deps = {}) {
231
289
  {
232
290
  retire(registry, job);
233
291
  if (job.scope.isCurrent()) {
292
+ // WP-0 criterion 7: the certificate is gone without a completion proof,
293
+ // so the effect of this prompt is UNKNOWN. Say so, and keep the
294
+ // conservative review action rather than claiming nothing was sent.
295
+ const expired = code === 'root_delivery_expired';
234
296
  try {
235
297
  await storeStatus(job.sessionId, job.receiptId, 'needs_attention', {
236
- reason: code === 'root_delivery_expired'
237
- ? code : 'root_job_unresolved',
298
+ reason: expired ? code : 'root_job_unresolved',
238
299
  retryable: false,
239
- message: code === 'root_delivery_expired'
240
- ? 'The desktop delivery permission expired. Resend this prompt to try again.'
241
- : 'CTM could not safely confirm this desktop delivery. Review the submission and resend if needed.',
300
+ recoveryAction: 'review',
301
+ message: expired
302
+ ? 'The desktop delivery permission expired before CTM could confirm this delivery. Review the submission before resending.'
303
+ : REVIEW_MESSAGE,
242
304
  source: job.source,
243
305
  });
244
306
  } catch { reportFailure(job.scope, 'publish_unresolved', 'status_write_failed'); }
@@ -322,6 +384,11 @@ function createRootPromptFlow(deps = {}) {
322
384
  const scope = operationScope || commandRuntime.captureRootPromptCoordinator();
323
385
  if (!scope || !requestCurrent(scope, current)) return denial('remote_command_auth_unavailable');
324
386
  const publicId = agentSessionKernel.normalizeClientMessageId(options.clientMessageId);
387
+ // WP-0: correlate every diagnostic this operation emits with its normalized
388
+ // session/client-message identity (bounded ids only -- never a receipt id).
389
+ scopeCorrelation.set(scope, {
390
+ sessionId: safeDiagnosticId(sessionId), clientMessageId: safeDiagnosticId(publicId),
391
+ });
325
392
  const authState = { auth: null, namespace: null };
326
393
  const freshContext = transportKind === 'ws'
327
394
  ? (socket, namespace) => commandRuntime.freshRootPromptContext(socket, namespace)
@@ -384,6 +451,15 @@ function createRootPromptFlow(deps = {}) {
384
451
  driver, writeActive: true, autoPending: true, inFlight: false, lastResult: null,
385
452
  };
386
453
  registry.set(receiptId, job);
454
+ } else {
455
+ // WP-0 criterion 4: a committed admission whose delivery facade cannot be
456
+ // registered is a typed handoff failure, never a silent acceptance. The
457
+ // committed historical record is left exactly as it is.
458
+ reportFailure(scope, 'driver_registration', 'root_driver_registration_unavailable');
459
+ admitted = { ...admitted, response: { ...denial('root_driver_registration_unavailable', 503),
460
+ recoveryAction: 'resend',
461
+ message: 'CTM saved your prompt but could not start delivery. Resend to try again.',
462
+ } };
387
463
  }
388
464
  }
389
465
  if (job) {
@@ -393,37 +469,86 @@ function createRootPromptFlow(deps = {}) {
393
469
  void firstDrive.catch(() => reportFailure(scope, 'drive', 'unexpected_failure'));
394
470
  }
395
471
  } else if (admitted?.response?.ok === true) {
396
- // A cached parent is not a process-local write marker. Never adopt it after restart.
472
+ // A cached parent is not a process-local write marker. Never adopt it after
473
+ // restart, and never answer with its cached acceptance: a reply is truthful
474
+ // only when this process holds the job, or when a scoped read proves what
475
+ // actually happened to the prompt.
397
476
  let context = null;
477
+ let contextFailed = false;
398
478
  try { context = await freshContext(client, null); }
399
- catch { reportFailure(scope, 'drive', 'unexpected_failure'); }
400
- if (context?.ok && requestCurrent(scope, current)) {
401
- const receiptId = remoteCommandStore.remoteCommandReceiptId(context.namespace.namespaceHash, publicId);
402
- const resident = registry.get(receiptId);
403
- let requestedIntentHash = '';
479
+ catch { contextFailed = true; }
480
+ if (!contextFailed && (!context?.ok || !requestCurrent(scope, current))) contextFailed = true;
481
+ if (contextFailed) {
482
+ // WP-0 criterion 4: no current authority and no proof -- refuse, never
483
+ // fall through to the cached `received`/`accepted` response.
484
+ reportFailure(scope, 'replay_recovery', 'root_replay_context_unavailable');
485
+ return denial(context?.code || 'remote_command_auth_unavailable', context?.status || 503);
486
+ }
487
+ const receiptId = remoteCommandStore.remoteCommandReceiptId(context.namespace.namespaceHash, publicId);
488
+ const resident = registry.get(receiptId);
489
+ let requestedIntentHash = '';
490
+ try {
491
+ const intentHandle = rootPromptIntent.captureRootPromptIntent({
492
+ sessionId, text, source: options.source, attachments: options.attachments,
493
+ }, { maxPromptBytes });
494
+ requestedIntentHash = intentHandle.body.intent_hash;
495
+ } catch { requestedIntentHash = ''; reportFailure(scope, 'replay_recovery', 'root_replay_intent_unavailable'); }
496
+ const residentProof = resident && getCommittedRootAdmission(resident.admissionHandle);
497
+ if (resident?.writeActive && now() < resident.expiresAt && resident.scope.isCurrent()
498
+ && residentProof?.selector?.sessionId === sessionId
499
+ && residentProof.selector.namespaceHash === context.namespace.namespaceHash
500
+ && residentProof.selector.intentHash === requestedIntentHash) {
501
+ // A same-id manual retry with a live local job keeps the existing child
502
+ // selection semantics; no read evidence is consulted for it.
503
+ job = resident;
504
+ const retry = driveJob(registry, resident, false);
505
+ void retry.catch(() => reportFailure(scope, 'drive', 'unexpected_failure'));
506
+ } else {
507
+ // WP-0 criterion 3: the delivery permission is gone (restart, lost
508
+ // registry). Ask the scoped store what the prompt's own receipts prove --
509
+ // authenticated namespace + session + command id + a freshly captured
510
+ // intent hash -- and recheck the request scope after every await.
511
+ let snapshot = null;
512
+ let readFailed = false;
404
513
  try {
405
- const intentHandle = rootPromptIntent.captureRootPromptIntent({
406
- sessionId, text, source: options.source, attachments: options.attachments,
407
- }, { maxPromptBytes });
408
- requestedIntentHash = intentHandle.body.intent_hash;
409
- } catch { requestedIntentHash = ''; reportFailure(scope, 'drive', 'unexpected_failure'); }
410
- const residentProof = resident && getCommittedRootAdmission(resident.admissionHandle);
411
- if (resident?.writeActive && now() < resident.expiresAt && resident.scope.isCurrent()
412
- && residentProof?.selector?.sessionId === sessionId
413
- && residentProof.selector.namespaceHash === context.namespace.namespaceHash
414
- && residentProof.selector.intentHash === requestedIntentHash) {
415
- job = resident;
416
- const retry = driveJob(registry, resident, false);
417
- void retry.catch(() => reportFailure(scope, 'drive', 'unexpected_failure'));
514
+ snapshot = await runReadPoolOp('getLatestRootJobDeliveryReceipt', {
515
+ dbPath: scope.dbPath,
516
+ selector: {
517
+ namespaceHash: context.namespace.namespaceHash, commandId: publicId,
518
+ instanceId: scope.instanceId, sessionId, intentHash: requestedIntentHash,
519
+ },
520
+ }, { telemetryReason: 'root-prompt-replay-recovery' });
521
+ } catch { readFailed = true; }
522
+ if (!requestCurrent(scope, current)) return denial('remote_command_auth_unavailable');
523
+ if (readFailed) reportFailure(scope, 'replay_recovery', 'root_delivery_replay_unavailable');
524
+ const proof = classifyReplayRecovery(readFailed ? null : snapshot);
525
+ if (proof.deliveryState === 'sent') {
526
+ // Read evidence proves the prompt was already delivered: report the
527
+ // recorded sent signal as a duplicate. No new delivery, no new
528
+ // authority, and the preserved row is not downgraded.
529
+ admitted = { ...admitted, response: {
530
+ ok: true, status: proof.terminalStatus, duplicate: true,
531
+ source: options.source || 'web',
532
+ } };
418
533
  } else {
534
+ const action = proof.recoveryAction === 'resend' ? 'resend' : 'review';
535
+ const status = action === 'resend' ? 'needs_attention' : 'blocked';
536
+ const details = {
537
+ reason: proof.reason || 'root_job_unresolved', retryable: false,
538
+ recoveryAction: action,
539
+ message: action === 'resend' ? RESEND_MESSAGE : REVIEW_MESSAGE,
540
+ source: options.source || 'web',
541
+ };
542
+ reportFailure(scope, 'replay_recovery', 'root_job_unresolved');
419
543
  try {
420
- await storeStatus(sessionId, receiptId, 'needs_attention', {
421
- reason: 'root_job_unresolved', retryable: false,
422
- message: 'The original desktop delivery permission is unavailable. Resend this prompt to try again.',
423
- source: options.source || 'web',
424
- });
544
+ await storeStatus(sessionId, receiptId, status, details);
425
545
  } catch { reportFailure(scope, 'proof_loss_status', 'status_write_failed'); }
426
- admitted = { ...admitted, response: denial('root_job_unresolved', 409) };
546
+ if (!requestCurrent(scope, current)) return denial('remote_command_auth_unavailable');
547
+ admitted = { ...admitted, response: {
548
+ ok: false, status, code: 'root_job_unresolved', httpStatus: 409,
549
+ reason: details.reason, message: details.message, retryable: false,
550
+ recoveryAction: action, source: details.source,
551
+ } };
427
552
  }
428
553
  }
429
554
  }
@@ -446,6 +571,7 @@ function createRootPromptFlow(deps = {}) {
446
571
  type: 'submit-prompt-status', id: sessionId, clientMessageId: publicId,
447
572
  status: safe.status, signal: safe.signal, reason: safe.reason, source: safe.source,
448
573
  code: safe.code || '', retryable: safe.retryable === true,
574
+ recoveryAction: safe.recoveryAction || 'none',
449
575
  ...(safe.message ? { message: safe.message } : {}),
450
576
  };
451
577
  try { sendWsJson(client, frame); } catch { reportFailure(scope, 'status_reply', 'socket_write_failed'); }
@@ -506,6 +632,7 @@ function createRootPromptFlow(deps = {}) {
506
632
  const scope = capturedScope || commandRuntime.captureRootPromptCoordinator();
507
633
  if (!scope || (databaseHandle && scope.databaseHandle !== databaseHandle)
508
634
  || (instanceId && scope.instanceId !== instanceId) || !scope.isCurrent()) return 0;
635
+ scopeCorrelation.set(scope, { sessionId: safeDiagnosticId(sessionId) });
509
636
  let rows;
510
637
  try { rows = remoteRelayStore.listRemoteSubmissions(scope.databaseHandle, { sessionId, limit: 200 }); }
511
638
  catch { return 0; }
@@ -527,7 +654,12 @@ function createRootPromptFlow(deps = {}) {
527
654
  if (!scope.isCurrent()) break;
528
655
  const details = {
529
656
  reason: 'root_job_unresolved', retryable: false,
530
- message: 'The original desktop delivery permission is unavailable. Resend this prompt to try again.',
657
+ // WP-0 criterion 7: a missing process-local marker is not proof that the
658
+ // prompt was never sent (a restart can lose the marker after delivery).
659
+ // Only a scoped no-effect certificate would justify "resend"; none is
660
+ // reachable from a bare durable row, so the action is review.
661
+ recoveryAction: 'review',
662
+ message: REVIEW_MESSAGE,
531
663
  source: row.result?.source || 'web',
532
664
  };
533
665
  try {
@@ -551,6 +683,7 @@ function createRootPromptFlow(deps = {}) {
551
683
  if (!id) return { driven: 0, escalated: 0 };
552
684
  const scope = commandRuntime.captureRootPromptCoordinator();
553
685
  if (!scope || !scope.isCurrent()) return { driven: 0, escalated: 0 };
686
+ scopeCorrelation.set(scope, { sessionId: safeDiagnosticId(id) });
554
687
  const registry = registryFor(scope.databaseHandle);
555
688
  const inFlight = sessionDrains.get(scope.databaseHandle) || new Set();
556
689
  if (!sessionDrains.has(scope.databaseHandle)) sessionDrains.set(scope.databaseHandle, inFlight);
@@ -569,7 +702,8 @@ function createRootPromptFlow(deps = {}) {
569
702
  try {
570
703
  await storeStatus(job.sessionId, job.receiptId, 'needs_attention', {
571
704
  reason: 'root_delivery_expired', retryable: false,
572
- message: 'The desktop delivery permission expired. Resend this prompt to try again.',
705
+ recoveryAction: 'review',
706
+ message: 'The desktop delivery permission expired before CTM could confirm this delivery. Review the submission before resending.',
573
707
  });
574
708
  } catch { reportFailure(job.scope, 'expiry_status', 'status_write_failed'); }
575
709
  continue;
@@ -103,6 +103,8 @@ function createScrollbackArchive(options = {}) {
103
103
  const flushMs = options.flushMs !== undefined && Number.isFinite(Number(options.flushMs))
104
104
  ? Number(options.flushMs)
105
105
  : ARCHIVE_FLUSH_MS;
106
+ // Test seam, like `now`: a test drives ticks itself instead of waiting on the wall clock.
107
+ const scheduleInterval = typeof options.setInterval === 'function' ? options.setInterval : setInterval;
106
108
  const pendingMaxRows = Math.max(1, Number(options.pendingMaxRows) || ARCHIVE_PENDING_MAX_ROWS);
107
109
  const pendingMaxBytes = Math.max(1, Number(options.pendingMaxBytes) || ARCHIVE_PENDING_MAX_BYTES);
108
110
  const blockBytes = Math.max(1, Number(options.blockBytes) || DEFAULT_BLOCK_BYTES);
@@ -525,12 +527,12 @@ function createScrollbackArchive(options = {}) {
525
527
 
526
528
  function _scheduleTimer() {
527
529
  if (timer || flushMs <= 0) return;
528
- timer = setInterval(() => {
530
+ const tick = () => {
529
531
  // A slow async tick skips re-entry; the next tick resumes round-robin order.
530
- if (tickInFlight) return;
532
+ if (tickInFlight) return null;
531
533
  tickInFlight = true;
532
534
  const ids = Array.from(sessions.keys());
533
- (async () => {
535
+ return (async () => {
534
536
  // 1) Write buffered appends for every session -- cheap, one appendFile
535
537
  // per session's active bucket, no gzip involved.
536
538
  for (const id of ids) {
@@ -561,7 +563,8 @@ function createScrollbackArchive(options = {}) {
561
563
  })()
562
564
  .catch(() => { /* expected: the background timer tick itself must never throw -- every step inside already catches its own failures; this is the final backstop */ })
563
565
  .finally(() => { tickInFlight = false; });
564
- }, flushMs);
566
+ };
567
+ timer = scheduleInterval(tick, flushMs);
565
568
  if (typeof timer.unref === 'function') timer.unref();
566
569
  }
567
570
 
@@ -322,6 +322,8 @@ function loadCodingJournalOutcomes(sessionIds, options = {}) {
322
322
  archived_at, consolidated_at, extraction_status
323
323
  FROM memories INDEXED BY idx_memories_session_summary
324
324
  WHERE archived_at IS NULL
325
+ AND source IN ('walle-coding', 'walle-diary')
326
+ AND memory_type IN ('coding_journal', 'coding_journal_segment', 'agent_diary')
325
327
  AND source = 'walle-coding'
326
328
  AND memory_type = 'coding_journal'
327
329
  AND subject IN (${placeholders})
@@ -0,0 +1,218 @@
1
+ 'use strict';
2
+
3
+ // WP-1 — docs/codex-routed-fork-cold-restore-guard-2026-10-22.html §5 D-1..D-4, §6.
4
+ //
5
+ // The incident this closes: a Codex LEAD is launched natively with startup args `["resume", T]` and
6
+ // NO `session_lead_route` row. Codex 0.163's `thread/resume` injects the thread's own stored
7
+ // provider (`$CODEX_HOME/state_5.sqlite` -> threads.model_provider, e.g. `ctm-lead`) into its config
8
+ // load, which then fails "Model provider `ctm-lead` not found" -> exit 1 -> the 3-restart budget
9
+ // burns 50 s and the session ends blocked with a message that says nothing about the cause.
10
+ //
11
+ // D-1: the provider a spawned Codex process loads is CTM's decision (the route row), never the
12
+ // string in the thread. D-2: that string is read ONLY to decide whether a NATIVE launch can
13
+ // succeed. So before a native spawn this gate asks: is the thread's stored provider one a native
14
+ // `codex` config load resolves (its built-ins ∪ the user `[model_providers]` table)? If not, the
15
+ // launch is either repaired from CTM's own transition journal (D-3, the same row the commit would
16
+ // have written) or refused deterministically before any PTY exists (D-4) — never spawned to die.
17
+ //
18
+ // Nothing here is flag-gated, and nothing here spawns a process: reading the state row is a single
19
+ // readonly SQLite lookup and the rollout fallback only reads the file's first line.
20
+
21
+ const path = require('node:path');
22
+ const { getCodexStateThreadById, readCodexRolloutMetadata } = require('../session-history');
23
+ const codexConfigGuard = require('../codex-config-guard');
24
+
25
+ // codex-rs/core/src/config/mod.rs built_in_model_providers(); `openai` is the default when unset.
26
+ const NATIVE_BUILTIN_PROVIDERS = Object.freeze(['openai', 'ollama', 'lmstudio', 'amazon-bedrock']);
27
+
28
+ // The route/model columns `session_lead_route` accepts (lib/storage/session-lead-route-store.js).
29
+ const MAX_ROUTE_FIELD_LENGTH = 200;
30
+
31
+ /**
32
+ * Read the provider a native `codex resume <threadId>` would load for this thread.
33
+ *
34
+ * Order is the authority order Codex itself uses (D-2 decision table): the state DB row wins — it
35
+ * is what `thread/resume` reads — then the rollout's own `session_meta.model_provider`, then the
36
+ * built-in default. `codexHome` is the CODEX_HOME directory the launch uses; the state DB lookup
37
+ * takes the HOME it derives `<HOME>/.codex` from, so `homeDir` may be passed explicitly and is
38
+ * otherwise taken to be `codexHome`'s parent.
39
+ *
40
+ * @returns {{ provider: string, source: 'state_db'|'rollout'|'default', rolloutPath: string|null }}
41
+ */
42
+ function readCodexThreadProvider({ threadId, codexHome = null, homeDir, rolloutPath = null,
43
+ getStateThread = getCodexStateThreadById, readRolloutMeta = readCodexRolloutMetadata } = {}) {
44
+ const id = String(threadId || '').trim();
45
+ const fallbackPath = rolloutPath ? String(rolloutPath) : null;
46
+ if (!id) return { provider: 'openai', source: 'default', rolloutPath: fallbackPath };
47
+ const resolvedHome = homeDir !== undefined && homeDir !== null
48
+ ? homeDir
49
+ : (codexHome ? path.dirname(String(codexHome)) : undefined);
50
+ let row = null;
51
+ try {
52
+ row = getStateThread(id, resolvedHome) || null;
53
+ } catch {
54
+ row = null; /* expected: a corrupt/unreadable state DB degrades to the rollout fallback (D-2) */
55
+ }
56
+ const stateProvider = String(row?.model_provider || '').trim();
57
+ const stateRolloutPath = String(row?.rollout_path || '').trim();
58
+ if (stateProvider) {
59
+ return { provider: stateProvider, source: 'state_db', rolloutPath: stateRolloutPath || fallbackPath };
60
+ }
61
+ const file = stateRolloutPath || fallbackPath;
62
+ if (file) {
63
+ let meta = null;
64
+ try {
65
+ meta = readRolloutMeta(file) || null;
66
+ } catch {
67
+ meta = null; /* expected: a missing/malformed rollout is not a provider source (D-2) */
68
+ }
69
+ const rolloutProvider = String(meta?.model_provider || '').trim();
70
+ if (rolloutProvider) return { provider: rolloutProvider, source: 'rollout', rolloutPath: file };
71
+ }
72
+ return { provider: 'openai', source: 'default', rolloutPath: file || null };
73
+ }
74
+
75
+ /**
76
+ * Decide whether a NATIVE spawn can serve this thread (D-2).
77
+ * native === true iff provider ∈ NATIVE_BUILTIN_PROVIDERS ∪ keys(config.toml[model_providers]).
78
+ *
79
+ * @returns {{ native: boolean, provider: string, source: string, configProviders: string[] }}
80
+ */
81
+ function classifyCodexThreadProvider({ provider, source = '', codexHome = null,
82
+ readConfigToml = codexConfigGuard.readModelProviderKeys } = {}) {
83
+ const id = String(provider || '').trim();
84
+ let configProviders = [];
85
+ try {
86
+ const listed = readConfigToml(codexHome);
87
+ if (Array.isArray(listed)) configProviders = listed.map((key) => String(key));
88
+ } catch {
89
+ configProviders = []; /* expected: an unreadable config.toml declares no user providers (S-5) */
90
+ }
91
+ const native = !!id && (NATIVE_BUILTIN_PROVIDERS.includes(id) || configProviders.includes(id));
92
+ return { native, provider: id, source, configProviders };
93
+ }
94
+
95
+ /**
96
+ * D-3. Find the newest `coding_model_transitions` row for `ctmSessionId` whose parsed target recipe
97
+ * names this thread on the Codex harness with a route, and write `session_lead_route` from it.
98
+ *
99
+ * The journal is CTM's own record of the fork it launched, so this never guesses: no matching row
100
+ * means no repair (`{ ok: false, reason: 'no_journal_route' }`) and D-4 refuses the launch. An
101
+ * EXISTING route row wins byte-for-byte: the read below short-circuits, and the store's write is
102
+ * INSERT … ON CONFLICT DO NOTHING through the DB-owner mutation lane (awaited), so two concurrent
103
+ * cold restores converge on one row (meta-test M-1).
104
+ *
105
+ * @returns {{ ok: true, route: { route_id: string, model: string, context_window: null, transition_id: string }, written: boolean }
106
+ * | { ok: false, reason: 'no_journal_route' }}
107
+ */
108
+ async function reconcileLeadRouteFromJournal({ ctmSessionId, threadId, dbModule, recordSessionDiagnostic } = {}) {
109
+ const id = String(ctmSessionId || '').trim();
110
+ const thread = String(threadId || '').trim();
111
+ if (!id || !thread) return { ok: false, reason: 'no_journal_route' };
112
+ let rows = [];
113
+ try {
114
+ rows = typeof dbModule?.getCodingModelTransitionsForThread === 'function'
115
+ ? (dbModule.getCodingModelTransitionsForThread(id, thread) || [])
116
+ : [];
117
+ } catch {
118
+ rows = []; /* expected: an unreadable journal is not a route source — refuse, never guess (D-3) */
119
+ }
120
+ const row = rows.find((candidate) => {
121
+ const recipe = candidate?.target_recipe || {};
122
+ return String(recipe.providerThreadId || '') === thread
123
+ && String(recipe.harness || '') === 'codex-cli'
124
+ && String(recipe.routeId || '').trim().length > 0;
125
+ }) || null;
126
+ if (!row) return { ok: false, reason: 'no_journal_route' };
127
+ const routeId = String(row.target_recipe.routeId || '').trim();
128
+ const model = String(row.target_recipe.wireModelId || '').trim();
129
+ const transitionId = String(row.transition_id || '').trim();
130
+ if (!routeId || !model || !transitionId
131
+ || routeId.length > MAX_ROUTE_FIELD_LENGTH || model.length > MAX_ROUTE_FIELD_LENGTH) {
132
+ return { ok: false, reason: 'no_journal_route' };
133
+ }
134
+ let existing = null;
135
+ try {
136
+ existing = typeof dbModule?.getSessionLeadRoute === 'function' ? dbModule.getSessionLeadRoute(id) : null;
137
+ } catch {
138
+ existing = null; /* expected: an unreadable route row is treated as absent — the store's write is idempotent */
139
+ }
140
+ let written = false;
141
+ let route;
142
+ if (existing) {
143
+ route = {
144
+ route_id: existing.route_id, model: existing.model,
145
+ context_window: existing.context_window == null ? null : existing.context_window,
146
+ transition_id: existing.transition_id || null,
147
+ };
148
+ } else {
149
+ let stored = null;
150
+ if (typeof dbModule?.setSessionLeadRoute === 'function') {
151
+ stored = await dbModule.setSessionLeadRoute({
152
+ ctmSessionId: id, agent: 'codex', routeId, model, display: model,
153
+ contextWindow: null, transitionId,
154
+ });
155
+ }
156
+ written = !!stored;
157
+ route = { route_id: routeId, model, context_window: null, transition_id: transitionId };
158
+ }
159
+ if (typeof recordSessionDiagnostic === 'function') {
160
+ recordSessionDiagnostic(id, 'lead-route-reconciled-from-journal', {
161
+ transition_id: route.transition_id, route_id: route.route_id, model: route.model,
162
+ provider_thread_id: thread, written,
163
+ });
164
+ }
165
+ return { ok: true, route, written };
166
+ }
167
+
168
+ /**
169
+ * The D-4 refusal message. ONE sentence, and the picker is the way out (server.js's
170
+ * RESUME_BLOCK_MESSAGES and the client's error map both render this same provider id).
171
+ */
172
+ function codexProviderMissingMessage(provider) {
173
+ return `This Codex thread was created on gateway provider \`${String(provider || '')}\`; pick a model for this session to relaunch it.`;
174
+ }
175
+
176
+ /**
177
+ * The launch-time hook (§9 S-3), called by provider-process.js when a Codex lead is about to be
178
+ * spawned natively on a resume thread (no `_launchLeadRoute` object and no route row). Returns
179
+ * `{ native: true }` when the thread's stored provider is servable natively, `{ target }` when
180
+ * the journal repaired a route (the caller runs its routed branch unchanged), or `{ refused: true }`
181
+ * after the deterministic resume block was recorded and the client told (D-4).
182
+ */
183
+ async function gateNativeCodexLaunch({ sessionId, threadId, env = {}, preparedLaunchPlan = null, dbModule,
184
+ recordSessionDiagnostic, markSessionResumeBlocked, ws = null, wsSend = null } = {}) {
185
+ const codexHome = env.CODEX_HOME || (env.HOME ? path.join(env.HOME, '.codex') : null);
186
+ const threadRead = readCodexThreadProvider({
187
+ threadId, codexHome, homeDir: env.HOME || null,
188
+ rolloutPath: preparedLaunchPlan?.resume?.rolloutInfo?.rolloutPath
189
+ || preparedLaunchPlan?.resume?.preflight?.transcriptPath || null,
190
+ });
191
+ const threadClass = classifyCodexThreadProvider({ ...threadRead, codexHome });
192
+ if (threadClass.native) return { native: true };
193
+ const reconciled = await reconcileLeadRouteFromJournal({ ctmSessionId: sessionId, threadId, dbModule, recordSessionDiagnostic });
194
+ if (reconciled.ok) {
195
+ return { target: { routeId: reconciled.route.route_id, model: reconciled.route.model, contextWindow: null } };
196
+ }
197
+ if (typeof recordSessionDiagnostic === 'function') {
198
+ recordSessionDiagnostic(sessionId, 'codex-thread-provider-unservable', {
199
+ provider_thread_id: threadId, provider: threadClass.provider, source: threadRead.source,
200
+ config_providers: threadClass.configProviders.length,
201
+ });
202
+ }
203
+ const message = codexProviderMissingMessage(threadClass.provider);
204
+ if (typeof markSessionResumeBlocked === 'function') {
205
+ markSessionResumeBlocked(sessionId, { code: 'codex_provider_missing', message, agentType: 'codex', deterministic: true }, 'codex-provider-gate');
206
+ }
207
+ if (ws && typeof wsSend === 'function') wsSend(ws, JSON.stringify({ type: 'error', code: 'codex_provider_missing', message }));
208
+ return { refused: true };
209
+ }
210
+
211
+ module.exports = {
212
+ NATIVE_BUILTIN_PROVIDERS,
213
+ readCodexThreadProvider,
214
+ classifyCodexThreadProvider,
215
+ reconcileLeadRouteFromJournal,
216
+ codexProviderMissingMessage,
217
+ gateNativeCodexLaunch,
218
+ };
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ // WP-2 — docs/codex-routed-fork-cold-restore-guard-2026-10-22.html §5 D-5, §9 S-9/S-10, §8 WP-2.
4
+ //
5
+ // The belt to WP-1's braces. WP-1 refuses a native Codex launch *before* it spawns when the
6
+ // thread's stored provider is not one a native config load resolves. This module covers the exits
7
+ // that get past any such gate: a thread whose provider changes between the decision and the spawn,
8
+ // and the D-8 paths that never consult the gate at all.
9
+ //
10
+ // Codex reports "Model provider `x` not found" as a plain anyhow error with exit 1 — the SAME exit
11
+ // code as a transient startup fault — so the exit code alone cannot discriminate: only the captured
12
+ // output text can. CTM already buffers that text per attempt (`session._resumeAttemptOutput`, 8 KiB,
13
+ // reset at the start of every attempt), which is what this classifier reads.
14
+ //
15
+ // A config-load failure is a precondition failure: no amount of retrying resolves it, so the
16
+ // caller blocks deterministically instead of spending the 3-restart budget (50 s) to reach the same
17
+ // state with a message that names nothing. Nothing here is flag-gated and nothing here spawns a
18
+ // process: the whole decision is one regex over the caller's string.
19
+
20
+ // The one Codex phrase this classifies, pinned deliberately narrow (§11: "do not widen the exit
21
+ // classifier beyond the one Codex phrase"; every other startup fault keeps the restart budget).
22
+ // The provider id is the backtick-quoted run Codex echoes back from the thread's stored
23
+ // `model_provider`.
24
+ const CODEX_PROVIDER_MISSING_RE = /Model provider `([^`]+)` not found/;
25
+
26
+ /**
27
+ * Classify one attempt's captured Codex output (the caller passes `session._resumeAttemptOutput`).
28
+ *
29
+ * @param {string} output the attempt buffer — never scrollback; a non-string degrades to ''
30
+ * @returns {{ code: 'codex_provider_missing', provider: string, line: string } | null}
31
+ * `null` for any output that does not carry the phrase, and for a phrase with no usable provider
32
+ * id (e.g. "Model provider `` not found"), so a malformed line can only ever fail OPEN into
33
+ * today's restart budget rather than blocking a thread it cannot name.
34
+ */
35
+ function classifyCodexResumeExit(output) {
36
+ const text = typeof output === 'string' ? output : '';
37
+ if (!text) return null;
38
+ const match = CODEX_PROVIDER_MISSING_RE.exec(text);
39
+ if (!match) return null;
40
+ const provider = String(match[1] || '').trim();
41
+ if (!provider) return null;
42
+ // `detail` is the whole line the phrase landed on (Codex wraps the anyhow error across the
43
+ // "thread/resume failed: ..." prefix and the "(code -32600)" suffix); the line is the unit a
44
+ // human reads, and the provider id alone would drop the surrounding failure context.
45
+ const lineStart = text.lastIndexOf('\n', Math.max(0, match.index - 1)) + 1;
46
+ const lineEndRaw = text.indexOf('\n', match.index);
47
+ const lineEnd = lineEndRaw === -1 ? text.length : lineEndRaw;
48
+ const line = text.slice(lineStart, lineEnd).replace(/\r$/, '').trim();
49
+ return { code: 'codex_provider_missing', provider, line: line || match[0] };
50
+ }
51
+
52
+ module.exports = { CODEX_PROVIDER_MISSING_RE, classifyCodexResumeExit };