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
@@ -8,16 +8,13 @@ const DEFAULT_DEADLINES_MS = Object.freeze({
8
8
  applied: 100,
9
9
  painted: 50,
10
10
  });
11
- const RECOVERY_ACTION = Object.freeze({
12
- 'frame-sent': 'targeted-keyframe',
13
- // Browser main-thread receipt can legitimately exceed this interaction latency
14
- // deadline. It is latency evidence, not proof of baseline corruption, so it
15
- // must neither fence the diff stream nor inject a replacement keyframe.
16
- 'socket-received': 'report-only',
17
- 'apply-started': 'supersede-obsolete',
18
- applied: 'supersede-obsolete',
19
- painted: 'report-only',
20
- });
11
+ // Every incident is report-only: a missed boundary deadline is latency evidence, never proof
12
+ // that a frame was lost. Under host load keydown->frame-sent runs 400-800 ms on a healthy
13
+ // session, and the replacement keyframe the 'frame-sent' miss used to force re-delivered a
14
+ // screen the client had already applied (B3: a duplicate full-grid write per late frame). Lost
15
+ // frames are repaired by the delivery path's own seq/baseline checks, not by a clock.
16
+ // (codex-running-composer-latency-round5-2026-08-09 §11 WP-6 rollback: "Return watchdog to
17
+ // report-only; preserve diagnostics".)
21
18
 
22
19
  function createVisibleRevisionWatchdog(options = {}) {
23
20
  const now = options.now || Date.now;
@@ -88,12 +85,11 @@ function createVisibleRevisionWatchdog(options = {}) {
88
85
  deadlineMs,
89
86
  previousBoundary,
90
87
  missingBoundary,
91
- recoveryAction: RECOVERY_ACTION[missingBoundary] || 'report-only',
92
88
  };
93
89
  row.reported = true;
94
90
  incidents.push(incident); report(incident);
95
91
  recover({ ...incident, scope: row.clientId ? 'client' : 'session' });
96
- if (incident.recoveryAction === 'report-only') pending.delete(id);
92
+ pending.delete(id);
97
93
  }
98
94
  return incidents;
99
95
  }
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+ // WP-3 (§5 D-8): the closed skip-reason vocabulary for the deferred-restore wake pipeline --
3
+ // CamelCase machine tokens (P-3, the K8s-condition convention) that wakeSkips aggregates on; the
4
+ // human sentence lives beside them in a session diagnostic. Closed per release: additive only.
5
+ //
6
+ // Extracted from lib/session-lifecycle.js (which re-exports it) so server.js can name these tokens
7
+ // without growing past its file-size ratchet and so the vocabulary closure test can read the list
8
+ // without pulling in the lifecycle module.
9
+ const WAKE_SKIP_REASONS = Object.freeze([
10
+ 'OwnerRecoveryFence',
11
+ 'BackoffTaskMissingSessionId',
12
+ 'BackoffSessionReplaced',
13
+ 'CapacityRejected',
14
+ 'WakeBlocked',
15
+ 'GateBusy',
16
+ 'ResumeBlocked',
17
+ 'CodexLinkStale',
18
+ 'CodexLinkUnavailable',
19
+ 'CodexLinkBlocked',
20
+ 'LaunchPlanStale',
21
+ 'SessionReplaced',
22
+ 'McpWaitStale',
23
+ 'ResumeStormStale',
24
+ 'LaunchPlanNotSpawning',
25
+ 'RetryNotDeferred',
26
+ 'RetryAlreadyStarting',
27
+ 'RetryNoClients',
28
+ 'DrainPaused',
29
+ 'DrainPressureSkipped',
30
+ 'DrainIneligible',
31
+ 'SourceRecoveryEnvironmentInvalid',
32
+ 'LaunchGenerationUnavailable',
33
+ 'PrepareFailed',
34
+ 'LaunchBlocked',
35
+ 'LeadTransitionTargetOwnerMismatch',
36
+ 'LeadTransitionSourceOwnerMismatch',
37
+ 'LeadTransitionReconciliationFailed',
38
+ 'LeadTransitionReconciliationUnavailable',
39
+ 'LeadSourceRecoveryResumeMismatch',
40
+ 'TranscriptRestoreFailed',
41
+ 'LeadTransitionAttemptIssueRefused',
42
+ 'CreateRollback',
43
+ 'AutoStartNotAttach',
44
+ ]);
45
+
46
+ module.exports = { WAKE_SKIP_REASONS };
@@ -196,6 +196,21 @@ async function listPidsWithPpid() {
196
196
  return rows;
197
197
  }
198
198
 
199
+ // D-8 (WP-5, docs/clock-free-tests-followups-2026-10-23.html §6): proving that ONE pid is ours
200
+ // needs only that pid's row. The whole `ps -axo` table is 82-474 ms and ~1 MB on a busy host; a
201
+ // `ps -p <pid>` read is a single process. Rows are parsed by the SAME parser the whole-table read
202
+ // uses, so ownership (isOwnedRow) sees an identical row shape.
203
+ // rows -> ps ran; [] means the pid has no row (not ours, or already gone)
204
+ // null -> the probe itself failed; callers must treat that as "not proven owned", never as [].
205
+ async function listPidRow(pid) {
206
+ const { err, stdout } = await execFileCapture('ps', ['-p', String(pid), '-o', 'pid=,ppid=,command='], {
207
+ timeout: PS_SNAPSHOT_TIMEOUT_MS,
208
+ maxBuffer: 1024 * 1024,
209
+ });
210
+ if (err) return null;
211
+ return parsePsWithPpid(stdout);
212
+ }
213
+
199
214
  // WP-P2 orphan reap path signature — a small, pure, directly-testable
200
215
  // predicate deliberately kept separate from the exact-title match. A bare
201
216
  // `command.includes(rootMarker)` has two failure modes: (1) no path-boundary
@@ -223,6 +238,7 @@ const defaultProcessOps = {
223
238
  findListeningPidsByExactCommand,
224
239
  getProcessUptime,
225
240
  listPidsWithPpid,
241
+ listPidRow,
226
242
  };
227
243
 
228
244
  function isSourceCheckout(rootDir) {
@@ -391,6 +407,13 @@ function createWalleSupervisor({
391
407
  // spawned child (see spawnWalle()) — childPid always takes precedence.
392
408
  let adoptedPid = null;
393
409
  let lastBootAdoption = null; // WP-7 (S-26)
410
+ // D-7 (WP-5): the status GET's snapshot, declared HERE (not next to getStatusSnapshot())
411
+ // because killAll/apiStop/restartQuiet — any of which may run before that statement — clear it.
412
+ // `statusSnapshot` is the last computed status + its timestamp; `statusSnapshotInFlight` is the
413
+ // single-flight slot concurrent getStatusSnapshot() callers share.
414
+ let statusSnapshot = null; // { status, at }
415
+ let statusSnapshotInFlight = null;
416
+ let statusSnapshotGeneration = 0; // bumped by every invalidation; an older in-flight result is never stored
394
417
  // WP-S2 crash-streak/backoff state. crashStreak counts consecutive
395
418
  // unexpected exits (reset once the child has stayed ready for
396
419
  // restartStreakResetMs — see markChildReady()/childBecameReadyAt).
@@ -1797,6 +1820,7 @@ function createWalleSupervisor({
1797
1820
  // longer childExitWaitMs so a wall-e mid-drain has room to exit cleanly
1798
1821
  // instead of being SIGKILLed out from under itself.
1799
1822
  async function killAll(waitMs = 2000) {
1823
+ invalidateStatusSnapshot();
1800
1824
  artifactAuthorityRegistry.clear('wall_e_stopping');
1801
1825
  const pidSet = await collectManagedPids();
1802
1826
  if (pidSet.size === 0) return waitForPortFree();
@@ -2180,6 +2204,10 @@ function createWalleSupervisor({
2180
2204
  // the failure exactly once — Node can emit `error`, `exit`, or both for a
2181
2205
  // failed spawn depending on platform/failure mode.
2182
2206
  function handleChildFailure({ code = null, signal = null, spawnError = null } = {}) {
2207
+ // D-7 (WP-5): the child is gone — drop any cached status that still says running:true.
2208
+ // Invalidate BEFORE the pid guard below: a duplicate/late exit event for an older pid is
2209
+ // still an exit, and the cached snapshot is stale either way.
2210
+ invalidateStatusSnapshot();
2183
2211
  if (childPid !== myPid) return;
2184
2212
  const failureAuthorityGeneration = childFailureAuthorityGeneration;
2185
2213
  artifactAuthorityRegistry.clear('wall_e_exit');
@@ -2454,9 +2482,33 @@ function createWalleSupervisor({
2454
2482
  // D-3/D-4 (kill authority): adoption (pid-file OR ps-title discovery)
2455
2483
  // requires the SAME ownership check as collectManagedPids() — a
2456
2484
  // same-title pid with a foreign ppid is never adopted and never becomes
2457
- // pidSource:'discovered'. One ps snapshot is shared by both checks below.
2485
+ // pidSource:'discovered'.
2458
2486
  const myPid = processOps.pid || process.pid;
2459
2487
  const titles = new Set(processTitles());
2488
+
2489
+ // D-8 (WP-5): a live pid-file pid is proven ours by ONE `ps -p <pid>` row, never the
2490
+ // whole ~1 MB table (82-474 ms busy); injected processOps without listPidRow answer from
2491
+ // THEIR table, never a real ps. A null result (probe FAILED) is "not proven owned".
2492
+ if (savedPid && isAlive(savedPid)) {
2493
+ let savedRows = null;
2494
+ try {
2495
+ savedRows = processOps.listPidRow ? await processOps.listPidRow(savedPid) : processOps.listPidsWithPpid
2496
+ ? (await processOps.listPidsWithPpid() || []).filter((row) => row.pid === savedPid) : await listPidRow(savedPid);
2497
+ } catch { /* expected: best-effort per-pid ownership probe; a failure leaves savedRows=null, so the saved pid is never (wrongly) adopted */ }
2498
+ const savedRow = Array.isArray(savedRows) ? indexRowsByPid(savedRows).get(savedPid) : undefined;
2499
+ if (isOwnedRow(savedRow, { titles, myPid })) {
2500
+ // WP-S3 (B11): this pid was NOT spawned by us this process lifetime —
2501
+ // adopt it into the same supervision the interval monitor gives a real
2502
+ // child (arm the monitor if it isn't already, and start tracking it so
2503
+ // a later tick's isAlive(adoptedPid) poll can detect it disappearing).
2504
+ adoptedPid = savedPid;
2505
+ if (!intentionallyStopped) ensureMcpReadinessMonitor(); // R-5: a status read must never re-arm after an intentional stop
2506
+ return statusWithDiagnostics({ running: true, pid: savedPid, pidSource: 'pid_file' });
2507
+ }
2508
+ }
2509
+ if (savedPid) stalePid = savedPid;
2510
+
2511
+ // Discovery branch: the ONE consumer of the whole-table snapshot.
2460
2512
  let rows = null;
2461
2513
  try {
2462
2514
  rows = processOps.listPidsWithPpid ? await processOps.listPidsWithPpid() : await listPidsWithPpid();
@@ -2467,17 +2519,6 @@ function createWalleSupervisor({
2467
2519
  const psFailedFlag = Array.isArray(rows) ? {} : { ps_snapshot_failed: true };
2468
2520
  const owned = (pid) => isOwnedRow(byPid.get(pid), { titles, myPid });
2469
2521
 
2470
- if (savedPid && isAlive(savedPid) && owned(savedPid)) {
2471
- // WP-S3 (B11): this pid was NOT spawned by us this process lifetime —
2472
- // adopt it into the same supervision the interval monitor gives a real
2473
- // child (arm the monitor if it isn't already, and start tracking it so
2474
- // a later tick's isAlive(adoptedPid) poll can detect it disappearing).
2475
- adoptedPid = savedPid;
2476
- ensureMcpReadinessMonitor();
2477
- return statusWithDiagnostics({ running: true, pid: savedPid, pidSource: 'pid_file' });
2478
- }
2479
- if (savedPid) stalePid = savedPid;
2480
-
2481
2522
  let discovered = [];
2482
2523
  try {
2483
2524
  const titleRowPids = [...byPid.values()]
@@ -2494,7 +2535,7 @@ function createWalleSupervisor({
2494
2535
  const pid = discovered[0];
2495
2536
  // WP-S3 (B11): same adoption as the pid-file branch above.
2496
2537
  adoptedPid = pid;
2497
- ensureMcpReadinessMonitor();
2538
+ if (!intentionallyStopped) ensureMcpReadinessMonitor(); // R-5: same guard as the pid-file branch above
2498
2539
  return statusWithDiagnostics({
2499
2540
  running: true,
2500
2541
  pid,
@@ -2514,7 +2555,45 @@ function createWalleSupervisor({
2514
2555
  }, { checkNative: true });
2515
2556
  }
2516
2557
 
2558
+ // D-7 (WP-5, docs/clock-free-tests-followups-2026-10-23.html §6): the status GET serves this
2559
+ // snapshot instead of running kill-authority discovery (a whole-table ps) on every request.
2560
+ // A cached status is served only while it is (a) younger than one monitor interval AND
2561
+ // (b) still consistent with the process table — `!running` is trivially consistent, otherwise
2562
+ // one isAlive() syscall (kill(pid, 0), ~5 µs) must pass, so a daemon that died since the
2563
+ // snapshot is never reported as running. Anything else is ONE single-flight getStatus():
2564
+ // concurrent callers share the promise instead of each spawning their own ps.
2565
+ // KILL PATHS MUST NEVER CALL THIS — collectManagedPids/reapOrphans/stop need a fresh, whole
2566
+ // table read taken immediately before they signal (see the anti-goal in the design doc §11).
2567
+ function invalidateStatusSnapshot() { statusSnapshot = null; statusSnapshotInFlight = null; statusSnapshotGeneration += 1; }
2568
+
2569
+ async function getStatusSnapshot() {
2570
+ if (pendingChildFailure) return { running: false, pid: null, pidSource: null, recovering: true, snapshotAgeMs: 0 }; // D-6 (WP-3): never wait on the crash-recovery pipeline (npm install, up to 180 s); this answer is not cached, getStatus() still awaits it.
2571
+ const cached = statusSnapshot;
2572
+ const cacheAgeMs = cached ? Date.now() - cached.at : null;
2573
+ if (cached && cacheAgeMs < mcpMonitorIntervalMs && (!cached.status.running || isAlive(cached.status.pid))) {
2574
+ return { ...cached.status, snapshotAgeMs: cacheAgeMs };
2575
+ }
2576
+ if (!statusSnapshotInFlight) {
2577
+ // A getStatus() that began before an invalidation (stop/start/restart/exit) must not write its
2578
+ // pre-invalidation answer back into the cache: store only if no invalidation happened since.
2579
+ const generation = statusSnapshotGeneration;
2580
+ const inFlight = getStatus().then((status) => {
2581
+ const entry = { status, at: Date.now() };
2582
+ if (generation === statusSnapshotGeneration) statusSnapshot = entry;
2583
+ return entry;
2584
+ });
2585
+ statusSnapshotInFlight = inFlight;
2586
+ // Clear the in-flight slot without turning a rejected getStatus() into an unhandled
2587
+ // rejection: the awaiter below still sees the rejection, and the next call retries.
2588
+ const clear = () => { if (statusSnapshotInFlight === inFlight) statusSnapshotInFlight = null; };
2589
+ void inFlight.then(clear, clear);
2590
+ }
2591
+ const fresh = await statusSnapshotInFlight;
2592
+ return { ...fresh.status, snapshotAgeMs: Date.now() - fresh.at };
2593
+ }
2594
+
2517
2595
  function apiStop(req, res) {
2596
+ invalidateStatusSnapshot();
2518
2597
  intentionallyStopped = true;
2519
2598
  stopGeneration += 1;
2520
2599
  clearRestartTimer();
@@ -2535,6 +2614,7 @@ function createWalleSupervisor({
2535
2614
  }
2536
2615
  starting = true;
2537
2616
  intentionallyStopped = false;
2617
+ invalidateStatusSnapshot();
2538
2618
  appendLifecycleEvent('api_start', { pid: childPid, trigger: 'api_start' });
2539
2619
  // WP-S2 (Req #7): an explicit human-initiated start is a clean slate —
2540
2620
  // same as apiRestart/restartQuiet below.
@@ -2674,6 +2754,7 @@ function createWalleSupervisor({
2674
2754
  }
2675
2755
 
2676
2756
  function restartQuiet() {
2757
+ invalidateStatusSnapshot();
2677
2758
  return ensureNativeDependenciesReady('config_change').then((ready) => {
2678
2759
  if (!ready.ok) {
2679
2760
  console.error(`[setup] Wall-E restart blocked: ${ready.health.message}`);
@@ -2742,6 +2823,7 @@ function createWalleSupervisor({
2742
2823
  }
2743
2824
 
2744
2825
  async function apiRestart(req, res) {
2826
+ invalidateStatusSnapshot();
2745
2827
  restartInFlight = restartInFlight || runRestart().finally(() => { restartInFlight = null; });
2746
2828
  const { status, body } = await restartInFlight;
2747
2829
  return writeJson(res, status, body);
@@ -2846,6 +2928,8 @@ function createWalleSupervisor({
2846
2928
  // last-resort safety-net timer comfortably above it instead of guessing.
2847
2929
  childExitWaitMs,
2848
2930
  getStatus,
2931
+ // D-7 (WP-5): the status-GET snapshot. NEVER call this from a kill path.
2932
+ getStatusSnapshot,
2849
2933
  getNativeDependencyStatus,
2850
2934
  isIntentionallyStopped: () => intentionallyStopped,
2851
2935
  killAll,
@@ -2861,4 +2945,4 @@ function createWalleSupervisor({
2861
2945
  };
2862
2946
  }
2863
2947
 
2864
- module.exports = { createWalleSupervisor: (opts) => require('./walle-restart-settled').withRestartSettled(createWalleSupervisor(opts)), rotateBoundedLog, classifyExit, resolveCrashLogPath, psSnapshotRows, PS_SNAPSHOT_MAX_BUFFER };
2948
+ module.exports = { createWalleSupervisor: (opts) => require('./walle-restart-settled').withRestartSettled(createWalleSupervisor(opts)), rotateBoundedLog, classifyExit, resolveCrashLogPath, parsePsWithPpid, psSnapshotRows, PS_SNAPSHOT_MAX_BUFFER };
@@ -110,6 +110,18 @@ function nextStepFor({ code = '', klass = '', reason = null } = {}) {
110
110
  return CLASS_NEXT_STEPS[klass] || CLASS_NEXT_STEPS.terminal;
111
111
  }
112
112
 
113
+ /** WP-2 (session-stops-mid-job-2026-10-24.html D-9, §6): the one lead_next_step text on a
114
+ * non-terminal wait answer. RC-1's stall was a lead that read a wake with its worker still
115
+ * live ("I'm waiting for its report") and ended its turn with no wait in flight, so the text
116
+ * names the still-live worker and the two verbs that keep the lead attached to it.
117
+ * @param {object} [input] @param {string} [input.workerId] the still-live worker
118
+ * @param {string} [input.status] that worker's status string
119
+ * @returns {string|null} null when no worker is named (no live worker to wait on) */
120
+ function waitNextStep({ workerId, status } = {}) {
121
+ if (!workerId) return null;
122
+ return `Worker ${workerId} is still ${status}. Call walle_worker_wait again to keep waiting -- ending your turn with no wait in flight leaves you idle until your user types -- or walle_stop_worker if you no longer need it.`;
123
+ }
124
+
113
125
  // Worker-session-tree WP-0 ("every worker death gets a name"): an exit-sourced failure used to
114
126
  // carry `code: ''` -- exit 129 (SIGHUP) had no field explaining it. This names the signal
115
127
  // behind an exit: from `exit.signal` (a node-pty string like 'SIGHUP', or a positive numeric OS
@@ -256,4 +268,4 @@ function classifyFailure({ source, http_status = null, code = '', message = '',
256
268
  };
257
269
  }
258
270
 
259
- module.exports = { classifyFailure, signalName, extractHttpStatus, extractCode, extractRetryAfterMs, nextStepFor, NEXT_STEPS, CLASS_NEXT_STEPS };
271
+ module.exports = { classifyFailure, signalName, extractHttpStatus, extractCode, extractRetryAfterMs, nextStepFor, waitNextStep, NEXT_STEPS, CLASS_NEXT_STEPS };
@@ -67,6 +67,9 @@ function _serializeKey(sessionId, cols, rows, scrollbackRows, rolloutHistory) {
67
67
  // softMs, hardMs, makeReqId(), buildPost(reqId), refreshCache(entry, resp),
68
68
  // scrollbackRows, rolloutHistory }. The last two are KEY inputs (I8) -- every
69
69
  // caller must supply them, whether or not it ends up creating the entry.
70
+ // WP-3: the hard timer passes `issuedAt: entry.startedAt` to recordMiss and
71
+ // routes a 'degraded' verdict to a 'host-degraded' diagnostic -- never to
72
+ // _reviveUnresponsiveHost, which only an 'unresponsive' verdict reaches.
70
73
  function _attachOrPostSerialize(sessionId, cols, rows, waiter, entryHooks) {
71
74
  const key = _serializeKey(sessionId, cols, rows, entryHooks.scrollbackRows, entryHooks.rolloutHistory);
72
75
  const existing = _inflightSerialize.get(key);
@@ -98,9 +101,23 @@ function _attachOrPostSerialize(sessionId, cols, rows, waiter, entryHooks) {
98
101
  for (const w of entry.waiters) w.onHardTimeout?.(entry);
99
102
  // WP-1 (D-1/D-2): the readiness ledger's one deadlined-miss input for a
100
103
  // hosted session -- ONE call per coalesced request, never per waiter.
104
+ // WP-3 (session-stops-mid-job-2026-10-24.html, D-11): `issuedAt` is this
105
+ // entry's own creation time, so the ledger can tell "this request family
106
+ // timed out" (readiness) from "the host has said nothing at all" (liveness).
101
107
  if (entryHooks.hostId) {
102
- const verdictResult = entryHooks.hostHealth?.recordMiss?.(entryHooks.hostId, { sessionId, kind: 'hard', deadlineMs: 60000 });
103
- if (verdictResult) {
108
+ const verdictResult = entryHooks.hostHealth?.recordMiss?.(entryHooks.hostId, { sessionId, kind: 'hard', deadlineMs: 60000, issuedAt: entry.startedAt });
109
+ if (verdictResult?.verdict === 'degraded') {
110
+ // D-11: a host that relayed a frame after this request was issued is
111
+ // talking -- its serialize path is slow/wedged, but it and every agent
112
+ // in it stay alive. Report once per session for this host; NEVER revive.
113
+ _deps?.recordSessionDiagnostic?.(sessionId, 'host-degraded', {
114
+ hostId: entryHooks.hostId,
115
+ reason: 'serialize-timeout',
116
+ lastFrameAgoMs: verdictResult.lastFrameAgoMs,
117
+ });
118
+ } else if (verdictResult?.verdict === 'unresponsive') {
119
+ // D-12: only a host that has sent NO frame of any kind since the first
120
+ // missed request was issued reaches here.
104
121
  // Final-review fix C2: this is fire-and-forget from a timer. Without a
105
122
  // `.catch`, ANY rejection inside the revive (registry read, crash-cap
106
123
  // read, diagnostic write, host shutdown) becomes an unhandled rejection
@@ -25,6 +25,19 @@ const {
25
25
  // socket reference rewritten to `ctx.ws` inline.
26
26
 
27
27
  const { registerWsHandlers } = require('../ws-router');
28
+ // WP-2 (docs/remote-machine-hub-sessions-2026-10-22.html §5 D-10, §6.3, §9 S-1): the remote-create
29
+ // seam. Required here (not injected through configure()) so server.js needs no edit for the
30
+ // branch; its production dependencies are resolved LAZILY inside the module, so loading this file
31
+ // still never touches db.js.
32
+ const remoteMachineCreate = require('../remote-machine-create');
33
+ // WP-3 (docs/remote-machine-hub-sessions-2026-10-22.html §6.4, S-22): the live-plane relay seam.
34
+ // Loading this file installs lib/remote-machine-link.js's pre-dispatch hook (lib/ws-router.js),
35
+ // which diverts any message naming a MIRROR session to the box and returns false — leaving the
36
+ // local path byte-identical — for every other session. Installed here, at boot, because this file
37
+ // is where the WP-2 remote-create branch already lives; the hook is inert until a mirror session
38
+ // exists, so no server.js edit is needed for it.
39
+ const remoteMachineLink = require('../remote-machine-link');
40
+ remoteMachineLink.installIntercept();
28
41
 
29
42
  let _deps = null;
30
43
 
@@ -50,6 +63,7 @@ function configure({
50
63
  _subscribeTerminalTail,
51
64
  _unsubscribeTerminalTail,
52
65
  _ensureCodexThreadLinkOffThread,
66
+ remoteCreateDeps,
53
67
  } = {}) {
54
68
  _deps = {
55
69
  handleCreatePrepared,
@@ -73,6 +87,7 @@ function configure({
73
87
  _subscribeTerminalTail,
74
88
  _unsubscribeTerminalTail,
75
89
  _ensureCodexThreadLinkOffThread,
90
+ remoteCreateDeps: remoteCreateDeps && typeof remoteCreateDeps === 'object' ? remoteCreateDeps : null,
76
91
  };
77
92
  }
78
93
 
@@ -225,6 +240,14 @@ async function handleCreate(ctx, msg) {
225
240
  }
226
241
  return;
227
242
  }
243
+ // WP-2 (S-1): a create naming a REGISTERED REMOTE machine is forwarded to that box instead of
244
+ // being prepared here. The check is BEFORE the local plan, so nothing local (generation,
245
+ // prepared plan, PTY) is ever created for it; `machine` absent or naming THIS instance's minted
246
+ // id falls through to the unchanged local path (S-2 — the local deny list above is untouched
247
+ // and is never widened for forwarded frames).
248
+ if (remoteMachineCreate.isRemoteMachineTarget(msg, _deps.remoteCreateDeps || {})) {
249
+ return remoteMachineCreate.forwardRemoteCreate(ctx, msg, _deps.remoteCreateDeps || {});
250
+ }
228
251
  return _deps.handleCreatePrepared(ctx.ws, msg);
229
252
  }
230
253
 
@@ -250,4 +273,12 @@ function register() {
250
273
  });
251
274
  }
252
275
 
253
- module.exports = { configure, register };
276
+ module.exports = {
277
+ configure,
278
+ register,
279
+ // Exported for the WP-2 enforcement test (tests/create-forward-allowlist.test.js): the LIVE
280
+ // deny list and the LIVE §6.3 allowlist, so the test can never drift from either constant.
281
+ PUBLIC_CREATE_INTERNAL_CONTROLS,
282
+ REMOTE_CREATE_FORWARD_ALLOWLIST: remoteMachineCreate.REMOTE_CREATE_FORWARD_ALLOWLIST,
283
+ filterRemoteCreateForward: remoteMachineCreate.filterRemoteCreateForward,
284
+ };
@@ -77,6 +77,64 @@ class WsRouter {
77
77
 
78
78
  const _router = new WsRouter();
79
79
 
80
+ // ---------------------------------------------------------------------------------------------
81
+ // Pre-dispatch hooks (WP-3, docs/remote-machine-hub-sessions-2026-10-22.html §6.4)
82
+ //
83
+ // The ONE seam a relay needs. A machine-hub session lives on a REMOTE box, not in this process's
84
+ // `sessions` Map (OQ-4), so every message that NAMES such a session must be diverted before a
85
+ // local handler dereferences it. Rather than touch each lib/ws/*.js group, a module registers a
86
+ // hook here and `dispatchWs` offers it every inbound frame first (in registration order):
87
+ //
88
+ // hook(ctx, msg) -> true the hook handled it; the local dispatch is SKIPPED
89
+ // hook(ctx, msg) -> false not mine; the next hook, then the normal handler, runs
90
+ //
91
+ // Hooks are consulted OUTSIDE `WsRouter.dispatch` on purpose: `dispatch` is the pure registry
92
+ // primitive (unit-tested directly), and a hook is a deployment-level concern of the process's
93
+ // singleton. A hook that throws is REPORTED and treated as "not handled" — one broken relay must
94
+ // never take the whole WS surface down with it.
95
+ // ---------------------------------------------------------------------------------------------
96
+ const _preDispatchHooks = [];
97
+
98
+ function registerWsPreDispatchHook(name, hook) {
99
+ if (!name || typeof name !== 'string') {
100
+ throw new TypeError('registerWsPreDispatchHook: name must be a non-empty string');
101
+ }
102
+ if (typeof hook !== 'function') {
103
+ throw new TypeError(`registerWsPreDispatchHook(${name}): hook must be a function`);
104
+ }
105
+ if (_preDispatchHooks.some((entry) => entry.name === name)) {
106
+ throw new Error(`registerWsPreDispatchHook: duplicate hook name: ${name}`);
107
+ }
108
+ const entry = { name, hook };
109
+ _preDispatchHooks.push(entry);
110
+ return () => {
111
+ const at = _preDispatchHooks.indexOf(entry);
112
+ if (at >= 0) _preDispatchHooks.splice(at, 1);
113
+ };
114
+ }
115
+
116
+ // SYNCHRONOUS on purpose: hooks return a plain boolean, so a message no hook claims (every local
117
+ // keystroke) reaches its handler in the same tick it always did — no added microtask.
118
+ function runWsPreDispatchHooks(ctx, msg) {
119
+ for (const entry of _preDispatchHooks) {
120
+ let handled = false;
121
+ try {
122
+ handled = entry.hook(ctx, msg);
123
+ } catch (error) {
124
+ // REPORT, then fall through: the relay is a routing optimization, never a gate — a hook
125
+ // fault must degrade to the pre-WP-3 behaviour (the local handler), not drop the frame.
126
+ console.error(`[ws-router] pre-dispatch hook ${entry.name} failed:`, error?.stack || error);
127
+ handled = false;
128
+ }
129
+ if (handled === true) return true;
130
+ }
131
+ return false;
132
+ }
133
+
134
+ function listWsPreDispatchHooks() {
135
+ return _preDispatchHooks.map((entry) => entry.name);
136
+ }
137
+
80
138
  function registerWsHandler(type, handler) {
81
139
  _router.registerOne('_default', type, handler);
82
140
  }
@@ -86,6 +144,7 @@ function registerWsHandlers(groupName, handlersByType) {
86
144
  }
87
145
 
88
146
  async function dispatchWs(ctx, msg) {
147
+ if (runWsPreDispatchHooks(ctx, msg)) return true;
89
148
  return _router.dispatch(ctx, msg);
90
149
  }
91
150
 
@@ -93,4 +152,12 @@ function listRegisteredWsTypes() {
93
152
  return _router.list();
94
153
  }
95
154
 
96
- module.exports = { registerWsHandler, registerWsHandlers, dispatchWs, listRegisteredWsTypes, WsRouter };
155
+ module.exports = {
156
+ registerWsHandler,
157
+ registerWsHandlers,
158
+ registerWsPreDispatchHook,
159
+ listWsPreDispatchHooks,
160
+ dispatchWs,
161
+ listRegisteredWsTypes,
162
+ WsRouter,
163
+ };
@@ -79,15 +79,33 @@ function prepareExactJsonEnvelope(payload, stringify = JSON.stringify) {
79
79
  return { encoded, bytes: measuredBytes };
80
80
  }
81
81
 
82
- function classifyWsPayload(payload, explicitClass = '') {
82
+ // T1 (docs/walle-turn-lifecycle-delivery-2026-10-26.html §3): the `walle-transcript-append`
83
+ // channel carries BOTH replaceable stream samples and turn LIFECYCLE control notifications —
84
+ // server.js's _recordWalleTranscriptAppend emits recordType=user/assistant rows and bare
85
+ // partType=turn_terminal parts, and the client (public/js/walle-session.js
86
+ // handleTranscriptAppend) flips generating state or forces a refetch on exactly those.
87
+ // Shedding them as replaceable stream samples is what left a live turn reading
88
+ // isGenerating=false while its interactive user echo still arrived. Classification therefore
89
+ // reads the already-resolved channel plus the payload's decoded recordType/partType, normalized
90
+ // exactly like the client's String(value || '').toLowerCase(); every other append stays stream.
91
+ // `explicitChannel` is the caller's already-resolved channel (wsSend's `options.channel`).
92
+ function classifyWsPayload(payload, explicitClass = '', explicitChannel = '') {
83
93
  if (['interactive', 'stream', 'bulk'].includes(explicitClass)) return explicitClass;
84
- const channel = payloadChannel(payload);
94
+ const channel = payloadChannel(payload, explicitChannel);
85
95
  if (process.env.CTM_WS_SOLICITED_REPLY_INTERACTIVE !== '0' && SOLICITED_REPLY_CHANNELS.has(channel)) {
86
96
  return 'interactive';
87
97
  }
88
98
  if (process.env.CTM_WALLE_WS_CLASS_SPLIT !== '0') {
89
99
  if (WALLE_TERMINAL_CHANNELS.has(channel) || channel.startsWith('walle-permission')) return 'interactive';
90
- if (WALLE_STREAM_CHANNELS.has(channel)) return 'stream';
100
+ if (WALLE_STREAM_CHANNELS.has(channel)) {
101
+ if (channel === 'walle-transcript-append') {
102
+ const decoded = normalizePayload(payload);
103
+ const recordType = String(decoded?.recordType || '').toLowerCase();
104
+ const partType = String(decoded?.partType || '').toLowerCase();
105
+ if (recordType === 'user' || recordType === 'assistant' || partType === 'turn_terminal') return 'interactive';
106
+ }
107
+ return 'stream';
108
+ }
91
109
  }
92
110
  if (['frame', 'terminal-tail', 'terminal-dirty', 'session-activity', 'fingerprint'].includes(channel)) return 'stream';
93
111
  if (['sessions', 'wake-digest', 'wake-manifest', 'conversation-updated', 'data-changed'].includes(channel)
@@ -133,7 +151,7 @@ function wsSend(ws, payload, options = {}) {
133
151
  const state = ws._ctmAdmission || createAdmissionState(options);
134
152
  ws._ctmAdmission = state;
135
153
  const now = Number(options.now) || Date.now();
136
- const trafficClass = classifyWsPayload(payload, options.class);
154
+ const trafficClass = classifyWsPayload(payload, options.class, options.channel);
137
155
  const channel = payloadChannel(payload, options.channel);
138
156
  const decision = options.admissionEnabled === false
139
157
  ? { result: ws.readyState === 1 ? ADMISSION_RESULTS.SENT : ADMISSION_RESULTS.DROPPED_BACKPRESSURE, reason: 'disabled' }