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
@@ -0,0 +1,328 @@
1
+ 'use strict';
2
+
3
+ // WP-1 (docs/machine-pool-picker-pairing-dashboard-2026-10-24.html §5 D-10/D-11/D-15/D-16, §6.1,
4
+ // §8 WP-1, §9 S-1/S-2): the host-stats primitive. One instance per CTM process owns "what is this
5
+ // host doing right now", fed by the EXISTING 60 s resource tick (no new timer) and retained 24 h in
6
+ // this machine's own DB (D-11: the box is the truth; there is no hub-side time-series).
7
+ //
8
+ // Every number here is the machine describing itself, so the same sample serves the local picker
9
+ // ("This Mac") and a peer that dialed this box through the existing ?machine= proxy (D-12).
10
+ //
11
+ // Two rules shape the implementation:
12
+ // * `record()` NEVER throws. It runs from a lifecycle sweep, so a failing sub-read (a missing
13
+ // /proc, an absent nvidia-smi, a closed DB handle during teardown) must degrade to a `null`
14
+ // FIELD, never to an aborted sweep. Each read is individually guarded.
15
+ // * All I/O is ASYNC (main-thread lint): statfs/readFile are the promise forms and the only
16
+ // process launch is one async, 3 s-bounded nvidia-smi probe on linux.
17
+ //
18
+ // The injected `execFile` is called in METHOD position on the `io` bag below rather than as a bare
19
+ // `execFile(...)`. That is the same convention lib/session-resource-sampler.js uses for its own
20
+ // injected runner (`this.exec(...)`): bin/main-thread-lint.js's bare-launch rule reads a bare
21
+ // `execFile(` as a new, unledgered main-thread spawn site, and this WP does not own the exemption
22
+ // ledger. Behavior is identical — one async execFile with a fixed argv and a 3 s timeout.
23
+
24
+ const os = require('node:os');
25
+ const fs = require('node:fs');
26
+ const childProcess = require('node:child_process');
27
+ const { promisify } = require('node:util');
28
+
29
+ const DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1000;
30
+ const DEFAULT_HISTORY_LIMIT = 1440;
31
+
32
+ // §6.1 (exact argv), observed on the DGX Spark's GB10: memory.used/memory.total are `[N/A]` on
33
+ // unified memory, so `unifiedMemory: true` is derived from exactly that.
34
+ const NVIDIA_SMI_ARGS = Object.freeze([
35
+ '--query-gpu=name,utilization.gpu,memory.used,memory.total,temperature.gpu,power.draw',
36
+ '--format=csv,noheader,nounits',
37
+ ]);
38
+ const NVIDIA_SMI_TIMEOUT_MS = 3000;
39
+
40
+ const BYTES_PER_MIB = 1024 * 1024;
41
+ const BYTES_PER_KIB = 1024;
42
+
43
+ /** `null` for anything that is not a finite number — the one way a missing reading enters a field. */
44
+ function _numOrNull(value) {
45
+ // An absent/blank value must be NULL, never 0: `Number('')` is 0, and a fabricated 0 on a missing
46
+ // reading is exactly the "no total, no swap" lie this module refuses to tell about os.freemem().
47
+ const text = String(value == null ? '' : value).trim();
48
+ if (!text) return null;
49
+ const n = Number(text);
50
+ return Number.isFinite(n) ? n : null;
51
+ }
52
+
53
+ /** nvidia-smi prints `[N/A]` (or `[Not Supported]`) for a metric the driver cannot read. */
54
+ function _smiOrNull(raw) {
55
+ const text = String(raw == null ? '' : raw).trim();
56
+ if (!text || /^\[.*\]$/.test(text) || /^N\/A$/i.test(text)) return null;
57
+ return _numOrNull(text);
58
+ }
59
+
60
+ /** §6.1: `MemTotal − MemAvailable`, plus swap, from /proc/meminfo (linux only). */
61
+ function parseMeminfo(text) {
62
+ const fields = {};
63
+ for (const line of String(text || '').split('\n')) {
64
+ const match = /^([A-Za-z_()]+):\s*(\d+)\s*kB\s*$/.exec(line.trim());
65
+ if (match) fields[match[1]] = Number(match[2]);
66
+ }
67
+ const total = Number.isFinite(fields.MemTotal) ? fields.MemTotal * BYTES_PER_KIB : null;
68
+ const available = Number.isFinite(fields.MemAvailable) ? fields.MemAvailable * BYTES_PER_KIB : null;
69
+ const swapTotal = Number.isFinite(fields.SwapTotal) ? fields.SwapTotal * BYTES_PER_KIB : null;
70
+ const swapFree = Number.isFinite(fields.SwapFree) ? fields.SwapFree * BYTES_PER_KIB : null;
71
+ return {
72
+ memTotalBytes: total,
73
+ memUsedBytes: total != null && available != null ? Math.max(0, total - available) : null,
74
+ swapUsedBytes: swapTotal != null && swapFree != null ? Math.max(0, swapTotal - swapFree) : null,
75
+ };
76
+ }
77
+
78
+ /** §6.1: first line only, comma separated, MiB for the two memory columns → bytes. */
79
+ function parseNvidiaSmi(stdout) {
80
+ const line = String(stdout || '').split('\n').map(part => part.trim()).find(part => part.length > 0);
81
+ if (!line) return null;
82
+ const parts = line.split(',').map(part => part.trim());
83
+ const memUsedMiB = _smiOrNull(parts[2]);
84
+ const memTotalMiB = _smiOrNull(parts[3]);
85
+ return {
86
+ name: String(parts[0] || '').slice(0, 120),
87
+ utilPct: _smiOrNull(parts[1]),
88
+ tempC: _smiOrNull(parts[4]),
89
+ powerW: _smiOrNull(parts[5]),
90
+ memUsedBytes: memUsedMiB == null ? null : Math.round(memUsedMiB * BYTES_PER_MIB),
91
+ memTotalBytes: memTotalMiB == null ? null : Math.round(memTotalMiB * BYTES_PER_MIB),
92
+ // P (unified memory): no readable memory split ⇒ the host memory above IS the GPU's.
93
+ unifiedMemory: memUsedMiB == null || memTotalMiB == null,
94
+ };
95
+ }
96
+
97
+ /** Cumulative cpu times, summed over cores — the delta between two calls is the util numerator. */
98
+ function cpuTimes(cpuList) {
99
+ let idle = 0;
100
+ let total = 0;
101
+ for (const cpu of Array.isArray(cpuList) ? cpuList : []) {
102
+ const times = cpu && cpu.times;
103
+ if (!times) continue;
104
+ const values = ['user', 'nice', 'sys', 'idle', 'irq'].map(key => Number(times[key]) || 0);
105
+ idle += values[3];
106
+ total += values.reduce((sum, value) => sum + value, 0);
107
+ }
108
+ return { idle, total };
109
+ }
110
+
111
+ function createHostStats({
112
+ db,
113
+ platform = process.platform,
114
+ now = () => Date.now(),
115
+ cpus = () => os.cpus(),
116
+ loadavg = () => os.loadavg(),
117
+ statfs = (p) => fs.promises.statfs(p),
118
+ readFile = (p) => fs.promises.readFile(p, 'utf8'),
119
+ execFile = promisify(childProcess.execFile),
120
+ diskPath = os.homedir(),
121
+ nvidiaSmiPath = '/usr/bin/nvidia-smi',
122
+ countTasks = () => ({ running: 0, needsYou: 0, idle: 0, workers: 0 }),
123
+ lastCreateAt = () => 0,
124
+ retentionMs = DEFAULT_RETENTION_MS,
125
+ } = {}) {
126
+ // Method-position calls only (see the header): the lint's bare-launch rule scans for a bare
127
+ // `execFile(`, not an injected runner reached through its owning bag.
128
+ const io = { statfs, readFile, execFile };
129
+
130
+ let lastCpu = null;
131
+ let latestSample = null;
132
+ // nvidia-smi presence is probed ONCE per process: the boot image of a box does not change under it.
133
+ let nvidiaSmiAvailability = null;
134
+
135
+ // D-10: a delta of two cumulative samples; the first tick has no previous, so it reports null
136
+ // rather than a fabricated 0.
137
+ function _cpuPct() {
138
+ let current = null;
139
+ try {
140
+ current = cpuTimes(cpus());
141
+ } catch {
142
+ return null; // expected: os.cpus() unavailable in a restricted sandbox
143
+ }
144
+ if (!current || current.total <= 0) return null;
145
+ const previous = lastCpu;
146
+ lastCpu = current;
147
+ if (!previous) return null;
148
+ const totalDelta = current.total - previous.total;
149
+ const idleDelta = current.idle - previous.idle;
150
+ if (!(totalDelta > 0)) return null;
151
+ return Math.max(0, Math.min(100, Math.round((1 - idleDelta / totalDelta) * 10000) / 100));
152
+ }
153
+
154
+ function _load() {
155
+ try {
156
+ const values = loadavg() || [];
157
+ return [Number(values[0]) || 0, Number(values[1]) || 0, Number(values[2]) || 0];
158
+ } catch {
159
+ return [0, 0, 0]; // expected: a platform without load-average support
160
+ }
161
+ }
162
+
163
+ function _cores() {
164
+ try {
165
+ return Math.max(1, (cpus() || []).length);
166
+ } catch {
167
+ return 1; // expected: os.cpus() unavailable
168
+ }
169
+ }
170
+
171
+ // D-10: darwin reads the sampler's vm_stat host fields; linux reads /proc/meminfo; anything else
172
+ // is null. `os.freemem()` is never used — it reports one number with no total and no swap.
173
+ async function _memory(snapshot) {
174
+ if (platform === 'darwin') {
175
+ const host = (snapshot && snapshot.host) || {};
176
+ return {
177
+ memUsedBytes: _numOrNull(host.memoryUsedBytes),
178
+ memTotalBytes: _numOrNull(host.memoryTotalBytes),
179
+ swapUsedBytes: _numOrNull(host.swapUsedBytes),
180
+ };
181
+ }
182
+ if (platform === 'linux') {
183
+ try {
184
+ return parseMeminfo(await io.readFile('/proc/meminfo'));
185
+ } catch {
186
+ return { memUsedBytes: null, memTotalBytes: null, swapUsedBytes: null }; // expected: no /proc in a container
187
+ }
188
+ }
189
+ return { memUsedBytes: null, memTotalBytes: null, swapUsedBytes: null }; // expected: platform without a memory contract
190
+ }
191
+
192
+ async function _disk() {
193
+ try {
194
+ const stats = await io.statfs(diskPath);
195
+ const blockSize = Number(stats && stats.bsize) || 0;
196
+ if (!blockSize) return { diskFreeBytes: null, diskTotalBytes: null };
197
+ return {
198
+ // bavail (free to an unprivileged writer), not bfree — the number a user can act on.
199
+ diskFreeBytes: Math.max(0, (Number(stats.bavail) || 0) * blockSize),
200
+ diskTotalBytes: Math.max(0, (Number(stats.blocks) || 0) * blockSize),
201
+ };
202
+ } catch {
203
+ return { diskFreeBytes: null, diskTotalBytes: null }; // expected: home dir unreadable/statfs unsupported
204
+ }
205
+ }
206
+
207
+ // D-10: linux only, one async execFile with a 3 s timeout. Absence of nvidia-smi is a normal
208
+ // answer (`gpu: null`), not an error, and the probe itself is cached per process.
209
+ async function _gpu() {
210
+ if (platform !== 'linux') return null; // expected: GPU probe is linux-only by design
211
+ if (nvidiaSmiAvailability === null) {
212
+ try {
213
+ await fs.promises.access(nvidiaSmiPath, fs.constants.X_OK);
214
+ nvidiaSmiAvailability = true;
215
+ } catch {
216
+ nvidiaSmiAvailability = false; // expected: no nvidia-smi on this box
217
+ }
218
+ }
219
+ if (!nvidiaSmiAvailability) return null;
220
+ try {
221
+ const { stdout } = await io.execFile(nvidiaSmiPath, [...NVIDIA_SMI_ARGS], { timeout: NVIDIA_SMI_TIMEOUT_MS });
222
+ return parseNvidiaSmi(stdout);
223
+ } catch {
224
+ return null; // expected: driver absent, or the query timed out on a wedged GPU
225
+ }
226
+ }
227
+
228
+ // D-10: the sampler's own process tree rss, in bytes.
229
+ function _ctmRssBytes(snapshot) {
230
+ const rssMB = snapshot && snapshot.processTree ? Number(snapshot.processTree.rssMB) : NaN;
231
+ return Number.isFinite(rssMB) ? Math.max(0, Math.round(rssMB * BYTES_PER_MIB)) : null;
232
+ }
233
+
234
+ // D-15: injected by server.js, computed from the session-status ledger. A throwing counter must
235
+ // not lose the rest of the sample.
236
+ function _tasks() {
237
+ try {
238
+ const counts = countTasks() || {};
239
+ return {
240
+ running: Number(counts.running) || 0,
241
+ needsYou: Number(counts.needsYou) || 0,
242
+ idle: Number(counts.idle) || 0,
243
+ workers: Number(counts.workers) || 0,
244
+ };
245
+ } catch {
246
+ return { running: 0, needsYou: 0, idle: 0, workers: 0 }; // expected: ledger unavailable during teardown
247
+ }
248
+ }
249
+
250
+ // D-16: the newest session create on THIS machine, so Recent agrees on every primary.
251
+ function _lastUsedAt() {
252
+ try {
253
+ return Number(lastCreateAt()) || 0;
254
+ } catch {
255
+ return 0; // expected: session map unavailable during teardown
256
+ }
257
+ }
258
+
259
+ /**
260
+ * One sample per resource tick. Never throws and never rejects: every sub-read is guarded and a
261
+ * failed one contributes `null` (or the all-zero task bucket) to the sample.
262
+ */
263
+ async function record(resourceSnapshot) {
264
+ const at = now();
265
+ const [memory, disk, gpu] = await Promise.all([
266
+ _memory(resourceSnapshot),
267
+ _disk(),
268
+ _gpu(),
269
+ ]);
270
+ const load = _load();
271
+ const sample = {
272
+ at,
273
+ cpuPct: _cpuPct(),
274
+ load1: load[0],
275
+ load5: load[1],
276
+ load15: load[2],
277
+ cores: _cores(),
278
+ memUsedBytes: memory.memUsedBytes,
279
+ memTotalBytes: memory.memTotalBytes,
280
+ swapUsedBytes: memory.swapUsedBytes,
281
+ diskFreeBytes: disk.diskFreeBytes,
282
+ diskTotalBytes: disk.diskTotalBytes,
283
+ gpu,
284
+ ctmRssBytes: _ctmRssBytes(resourceSnapshot),
285
+ tasks: _tasks(),
286
+ lastUsedAt: _lastUsedAt(),
287
+ };
288
+ // Memory FIRST, then the write: a reader that pairs latest() with history() (lib/api/host-stats.js
289
+ // does, and asserts latest.at >= samples[last].at) must never see a stored row newer than the
290
+ // in-memory sample beside it. record()'s row is committed before this await resolves, so setting
291
+ // latestSample afterwards leaves exactly that window open.
292
+ latestSample = sample;
293
+ try {
294
+ const sampleJson = JSON.stringify(sample);
295
+ if (db && typeof db.recordHostSample === 'function') {
296
+ await db.recordHostSample({ at, sampleJson, purgeBeforeMs: at - retentionMs });
297
+ }
298
+ } catch {
299
+ // HANDLE: history is best-effort; the sample is still returned and held in memory, so a
300
+ // full/closed DB costs this box its time series, never the live read a picker makes.
301
+ // expected: the DB handle is closed or full (teardown, disk exhaustion)
302
+ }
303
+ return sample;
304
+ }
305
+
306
+ function latest() {
307
+ return latestSample;
308
+ }
309
+
310
+ /** Oldest first, the same order the wire shape documents (§6.1). */
311
+ async function history({ sinceMs = now() - retentionMs, limit = DEFAULT_HISTORY_LIMIT } = {}) {
312
+ if (!db || typeof db.listHostSamples !== 'function') return [];
313
+ const rows = await db.listHostSamples({ sinceMs, limit });
314
+ const samples = [];
315
+ for (const row of Array.isArray(rows) ? rows : []) {
316
+ try {
317
+ samples.push(JSON.parse(row.sample_json));
318
+ } catch {
319
+ // expected: a row written by an older/newer shape is skipped, never fatal
320
+ }
321
+ }
322
+ return samples;
323
+ }
324
+
325
+ return { record, latest, history };
326
+ }
327
+
328
+ module.exports = { createHostStats, parseMeminfo, parseNvidiaSmi, cpuTimes, NVIDIA_SMI_ARGS, DEFAULT_RETENTION_MS };
@@ -0,0 +1,160 @@
1
+ 'use strict';
2
+
3
+ // WP-3 (stale-pty-host-evacuation design doc, §5 D-3, §6): the supervisor action
4
+ // for a host whose libuv thread pool is dead. The monitor report is a fact, the
5
+ // readiness ledger's recordThreadpool turns that fact into ONE verdict per
6
+ // stall episode (D-2), and this module is what acts on it:
7
+ //
8
+ // evacuate first, revive second, never keep.
9
+ //
10
+ // Evacuation is a single handoffHost(..., { reason: 'evacuate-threadpool' })
11
+ // attempt -- NEVER handoffHostWithRetry: the retry exists for transient spawn
12
+ // races on healthy hosts, and on a stalled host the second attempt just repeats
13
+ // the same wait (I-1: 14:18:17 -> 14:18:27 in the design doc's evidence). A
14
+ // successful handoff keeps the agents (their PTYs are inherited by the
15
+ // successor, which has a fresh pool); anything else falls through to the
16
+ // existing revive path, which kills the wedged host and lets its sessions cold
17
+ // resume.
18
+ //
19
+ // A stalled host is NEVER marked staleHost and NEVER left on the 10-minute retry
20
+ // (D-3): this module writes no stale flag and schedules nothing. It also never
21
+ // declares a host dead itself -- that is the orchestrator's `successor-lost-
22
+ // after-release` path, whose outcome is reported here as 'lost'.
23
+ //
24
+ // Dependency-injected exactly like lib/host-health-revive.js: no process globals,
25
+ // no server.js require, every collaborator passed in. Single-flight per hostId
26
+ // (a Map<hostId, Promise> that exists only for the duration of one active call),
27
+ // so two stall frames for the same host cannot start two handoffs.
28
+
29
+ function createEvacuateStalledHost({
30
+ manager,
31
+ handoffHost,
32
+ reviveUnresponsiveHost,
33
+ recordSessionDiagnostic,
34
+ hostHealth = null,
35
+ log = console.warn,
36
+ handoffOpts = {},
37
+ } = {}) {
38
+ const _inFlight = new Map(); // hostId -> Promise<outcome>
39
+
40
+ // The sessions this host is routing RIGHT NOW -- captured before the handoff,
41
+ // which is what re-points (or tears down) their adapters.
42
+ function routedSessionIds(hostId) {
43
+ const ids = [];
44
+ for (const [sessionId, adapter] of manager.hosts) {
45
+ if (adapter && adapter._hostId === hostId && !adapter.closed) ids.push(sessionId);
46
+ }
47
+ return ids;
48
+ }
49
+
50
+ async function evacuateOnce(id, verdict) {
51
+ const sessionIds = routedSessionIds(id);
52
+ const oldestPendingMs = Number(verdict && verdict.oldestPendingMs) || 0;
53
+ let result = null;
54
+ let handoffReason = null;
55
+ try {
56
+ result = await handoffHost(manager, id, {
57
+ ...handoffOpts, reason: 'evacuate-threadpool', hostHealth, recordSessionDiagnostic,
58
+ });
59
+ handoffReason = result && typeof result.reason === 'string' ? result.reason : null;
60
+ } catch (err) {
61
+ // handoffHost is documented never to reject (it returns { ok:false, attempted, reason });
62
+ // defend anyway and let the catch-all branch below decide (revive).
63
+ log(`[host-evacuate-error] host=${id} handoffHost threw: ${err?.message || err}`); // REPORT: treated as an unattempted failure below
64
+ result = null;
65
+ }
66
+
67
+ let outcome;
68
+ if (result && result.ok === true) {
69
+ outcome = 'evacuated';
70
+ } else if (result && result.attempted === false && handoffReason === 'owner-recovery-pending') {
71
+ // A live owner is still recovering; nothing to do yet. Re-arm the ledger's
72
+ // episode evaluation (episode start is kept) so the next stalled frame
73
+ // decides again the moment the owner is done.
74
+ outcome = 'deferred';
75
+ hostHealth?.resetThreadpoolEpisode?.(id);
76
+ } else if (result && result.ok === false && handoffReason === 'successor-lost-after-release') {
77
+ // The orchestrator already declared this host dead and tore its adapters
78
+ // down; its sessions cold resume. Reviving would be a second kill.
79
+ outcome = 'lost';
80
+ } else {
81
+ // Any other ok:false (attempted or not) -- the old host cannot be replaced,
82
+ // so fall back to the terminal path: kill it and let the sessions resume.
83
+ let revived = null;
84
+ try {
85
+ revived = await reviveUnresponsiveHost(id, {
86
+ misses: 0,
87
+ stall: { op: 'threadpool', sessionId8: '', durationMs: oldestPendingMs },
88
+ });
89
+ } catch (err) {
90
+ log(`[host-evacuate-error] host=${id} reviveUnresponsiveHost threw: ${err?.message || err}`); // REPORT: outcome stays unknown below
91
+ }
92
+ if (revived === 'killed') outcome = 'revived';
93
+ else if (revived === 'capped') outcome = 'capped';
94
+ else outcome = 'unknown-host'; // 'unknown-host', a throw, or any unrecognized result
95
+ }
96
+
97
+ log(`[host-evacuate] host=${id} outcome=${outcome} reason=${handoffReason || '-'} sessions=${sessionIds.length} oldestPendingMs=${oldestPendingMs}`);
98
+ for (const sessionId of sessionIds) {
99
+ recordSessionDiagnostic?.(sessionId, 'host-evacuate', { hostId: id, outcome, reason: handoffReason, oldestPendingMs });
100
+ }
101
+ return outcome;
102
+ }
103
+
104
+ return async function evacuateStalledHost(hostId, verdict) {
105
+ const id = String(hostId || '');
106
+ if (!id) return 'unknown-host';
107
+ const existing = _inFlight.get(id);
108
+ if (existing) return existing;
109
+ const p = evacuateOnce(id, verdict).finally(() => { _inFlight.delete(id); });
110
+ _inFlight.set(id, p);
111
+ return p;
112
+ };
113
+ }
114
+
115
+ // WP-3 (design doc §5 D-4, §8 WP-3, §9 S-8): the BOOT half of the escalation.
116
+ // Reconcile has just adopted every surviving host, so each adopted host's own
117
+ // state-reply threadpool snapshot is fed to the same readiness ledger the live
118
+ // onThreadpoolReport path feeds, and the ids the ledger verdicts 'evacuate'
119
+ // right now are returned -- the caller passes that Set as
120
+ // runUpgradePendingHandoffs({ skipHostIds }) so a host already on the
121
+ // evacuation path is never ALSO handed off by the upgrade pass.
122
+ //
123
+ // A missing/shapeless snapshot is a pre-WP-3 host (its state reply carried no
124
+ // threadpool field): today's behaviour, untouched. A host the manager no longer
125
+ // routes is skipped too -- the ledger is keyed by hostId, so acting on a stale
126
+ // id could only arm a timer for a host nobody serves.
127
+ //
128
+ // A stalled snapshot that is not due YET (oldestPendingMs under the deadline)
129
+ // returns null and arms the ledger's own timer, whose onEvacuate fires later:
130
+ // that host is deliberately NOT in the skip set -- its upgrade handoff may
131
+ // still run, and the orchestrator's single-flight channel is per-host while a
132
+ // failed upgrade handoff already ends in stale/retry (D-4).
133
+ function evacuateStalledAdoptedHosts({
134
+ hostHealth,
135
+ evacuateStalledHost,
136
+ threadpoolByHostId,
137
+ ownsHostId,
138
+ log = console.warn,
139
+ } = {}) {
140
+ const skipHostIds = new Set();
141
+ if (!hostHealth || typeof hostHealth.recordThreadpool !== 'function') return skipHostIds;
142
+ if (!threadpoolByHostId || typeof threadpoolByHostId.entries !== 'function') return skipHostIds;
143
+ // REPORT disposition for both failure shapes (a rejection and a synchronous
144
+ // throw): boot must not block on -- or be aborted by -- one evacuation.
145
+ const report = (id, err) => log(`[host-evacuate-boot] host=${id} evacuation failed: ${err?.message || err}`);
146
+ for (const [hostId, snapshot] of threadpoolByHostId) {
147
+ const id = String(hostId || '');
148
+ const snap = snapshot && typeof snapshot === 'object' ? snapshot : null;
149
+ if (!id || !snap || (snap.state !== 'ok' && snap.state !== 'stalled')) continue; // pre-WP-3 host: no state change
150
+ if (typeof ownsHostId === 'function' && !ownsHostId(id)) continue; // no longer routed: nothing to evacuate
151
+ const verdict = hostHealth.recordThreadpool(id, snap);
152
+ if (!verdict) continue; // not due yet (the ledger's own timer may fire later) -- never skipped
153
+ skipHostIds.add(id);
154
+ try { Promise.resolve(evacuateStalledHost(id, verdict)).catch((err) => report(id, err)); }
155
+ catch (err) { report(id, err); } // REPORT: evacuateStalledHost never rejects, but a synchronous throw must not abort boot
156
+ }
157
+ return skipHostIds;
158
+ }
159
+
160
+ module.exports = { createEvacuateStalledHost, evacuateStalledAdoptedHosts };
@@ -69,6 +69,11 @@ function createHostThreadpoolMonitor({
69
69
  setTimeoutFn = setTimeout,
70
70
  clearTimeoutFn = clearTimeout,
71
71
  onFrame = () => {},
72
+ // WP-0 (D-6/S-14): the frame's per-session view. A caller-provided provider
73
+ // (the pool wires the headless service's oldest in-flight ops here) is merged
74
+ // into EVERY emitted frame. Report-only: a throwing provider is swallowed --
75
+ // it can never change `state`, fail a probe, or stop a frame going out.
76
+ extraSnapshot = () => ({}),
72
77
  // Injectable seam for unit tests only (defaults to the real module): lets a
73
78
  // test simulate a request the real threadpool would never finish (init
74
79
  // fires, destroy never does) without needing an actual stuck libuv worker.
@@ -134,7 +139,15 @@ function createHostThreadpoolMonitor({
134
139
  }
135
140
 
136
141
  function emitFrame(extra = {}) {
137
- onFrame({ ...snapshot(), ...extra });
142
+ // WP-0: a report-only extraSnapshot provider is merged shallowly into every
143
+ // frame, but `state` is re-asserted from the monitor's own snapshot last, so
144
+ // a provider can never change what the detector decided.
145
+ let extraFields = {};
146
+ try {
147
+ extraFields = extraSnapshot() || {};
148
+ } catch { /* expected: a report-only provider must not break the frame */ }
149
+ const base = snapshot();
150
+ onFrame({ ...base, ...extraFields, ...extra, state: base.state });
138
151
  }
139
152
 
140
153
  function resetWindow() {