create-walle 0.10.0 → 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 (381) hide show
  1. package/README.md +88 -41
  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 +7 -5
  6. package/package.json +10 -4
  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/capture-codex-rollout-records.js +417 -0
  11. package/template/coding-task-manager/bin/census-codex-lead-history.js +325 -0
  12. package/template/coding-task-manager/bin/claude-hook.js +7 -3
  13. package/template/coding-task-manager/bin/claude-table-parity.js +307 -0
  14. package/template/coding-task-manager/bin/conversation-storage-auto-migration-lib.js +16 -1
  15. package/template/coding-task-manager/bin/conversation-storage-generation-evidence.js +9 -2
  16. package/template/coding-task-manager/bin/conversation-storage-service-entry-lib.js +16 -1
  17. package/template/coding-task-manager/bin/conversation-storage-walle-owner.js +247 -0
  18. package/template/coding-task-manager/bin/ctm-machine-pair.js +160 -0
  19. package/template/coding-task-manager/bin/lead-wait-census.js +1348 -0
  20. package/template/coding-task-manager/bin/repair-foreign-clone.js +564 -0
  21. package/template/coding-task-manager/bin/repair-resume-echo-titles.js +248 -0
  22. package/template/coding-task-manager/bin/seam-replay-codex.js +301 -0
  23. package/template/coding-task-manager/bin/write-lock-bench.js +111 -0
  24. package/template/coding-task-manager/db.js +705 -286
  25. package/template/coding-task-manager/git-utils.js +44 -0
  26. package/template/coding-task-manager/lib/adopted-host-redial.js +4 -0
  27. package/template/coding-task-manager/lib/agent-presets.js +15 -0
  28. package/template/coding-task-manager/lib/agent-title-apply.js +88 -0
  29. package/template/coding-task-manager/lib/agent-title-backfill-read.js +92 -0
  30. package/template/coding-task-manager/lib/api/host-stats.js +98 -0
  31. package/template/coding-task-manager/lib/api/ingress.js +35 -12
  32. package/template/coding-task-manager/lib/api/machine-pairing.js +285 -0
  33. package/template/coding-task-manager/lib/api/machine-pool.js +307 -0
  34. package/template/coding-task-manager/lib/api/machines.js +461 -0
  35. package/template/coding-task-manager/lib/api/misc-ops.js +20 -5
  36. package/template/coding-task-manager/lib/api/providers.js +53 -6
  37. package/template/coding-task-manager/lib/api/resume-from-store.js +25 -0
  38. package/template/coding-task-manager/lib/api/sessions.js +160 -8
  39. package/template/coding-task-manager/lib/api/transcript-history.js +17 -4
  40. package/template/coding-task-manager/lib/api/worktrees.js +38 -37
  41. package/template/coding-task-manager/lib/approval-drift.js +16 -1
  42. package/template/coding-task-manager/lib/approval-logical-frame.js +5 -1
  43. package/template/coding-task-manager/lib/approval-scope.js +68 -4
  44. package/template/coding-task-manager/lib/auth-context.js +32 -2
  45. package/template/coding-task-manager/lib/auth-rate-limit.js +41 -1
  46. package/template/coding-task-manager/lib/auth-rules.js +186 -45
  47. package/template/coding-task-manager/lib/broadcast-serialize.js +2 -1
  48. package/template/coding-task-manager/lib/browser-session-broker.js +9 -1
  49. package/template/coding-task-manager/lib/browser-use-adapter.js +33 -2
  50. package/template/coding-task-manager/lib/claude-markdown-ansi.js +27 -85
  51. package/template/coding-task-manager/lib/claude-session-title.js +6 -30
  52. package/template/coding-task-manager/lib/claude-table-layout.js +322 -0
  53. package/template/coding-task-manager/lib/client-diag-handler.js +25 -0
  54. package/template/coding-task-manager/lib/codex-config-guard.js +33 -3
  55. package/template/coding-task-manager/lib/codex-harness-qualification.js +12 -0
  56. package/template/coding-task-manager/lib/codex-history-terminal-renderer.js +33 -33
  57. package/template/coding-task-manager/lib/codex-launch-health.js +29 -2
  58. package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
  59. package/template/coding-task-manager/lib/codex-local-links.js +267 -0
  60. package/template/coding-task-manager/lib/codex-markdown-lists.js +160 -0
  61. package/template/coding-task-manager/lib/codex-native-history-prefix.js +4 -0
  62. package/template/coding-task-manager/lib/codex-paths.js +2 -1
  63. package/template/coding-task-manager/lib/codex-source-handoff.js +186 -0
  64. package/template/coding-task-manager/lib/coding-agent-models.js +21 -30
  65. package/template/coding-task-manager/lib/coding-session-model-adapters.js +27 -2
  66. package/template/coding-task-manager/lib/coding-session-model-projection.js +5 -0
  67. package/template/coding-task-manager/lib/coding-session-model-runtime.js +1 -2
  68. package/template/coding-task-manager/lib/coding-session-model-state.js +10 -0
  69. package/template/coding-task-manager/lib/coding-session-model-sync-state.js +0 -1
  70. package/template/coding-task-manager/lib/conversation-import/cursor-guard-owner.js +10 -0
  71. package/template/coding-task-manager/lib/create/resolve-launch-params.js +8 -1
  72. package/template/coding-task-manager/lib/ctm-boot-lifecycle.js +17 -1
  73. package/template/coding-task-manager/lib/db-domain-tables.js +7 -0
  74. package/template/coding-task-manager/lib/db-domains.js +21 -2
  75. package/template/coding-task-manager/lib/db-owner-op-registry.js +7 -0
  76. package/template/coding-task-manager/lib/db-owner-worker-client.js +22 -0
  77. package/template/coding-task-manager/lib/db-schema-source-files.js +1 -0
  78. package/template/coding-task-manager/lib/feature-manifest.js +1 -3
  79. package/template/coding-task-manager/lib/fs-watcher.js +3 -2
  80. package/template/coding-task-manager/lib/harness-history-transition.js +41 -14
  81. package/template/coding-task-manager/lib/headless-inflight-ops.js +203 -0
  82. package/template/coding-task-manager/lib/headless-missing-reply.js +42 -0
  83. package/template/coding-task-manager/lib/headless-term-service.js +60 -23
  84. package/template/coding-task-manager/lib/history-transition-read-op.js +179 -23
  85. package/template/coding-task-manager/lib/host-control-dial.js +167 -0
  86. package/template/coding-task-manager/lib/host-handoff-orchestrator.js +16 -2
  87. package/template/coding-task-manager/lib/host-handoff.js +51 -13
  88. package/template/coding-task-manager/lib/host-health-ledger.js +164 -12
  89. package/template/coding-task-manager/lib/host-self-truth-watchdog.js +4 -0
  90. package/template/coding-task-manager/lib/host-stats.js +328 -0
  91. package/template/coding-task-manager/lib/host-threadpool-evacuation.js +160 -0
  92. package/template/coding-task-manager/lib/host-threadpool-monitor.js +14 -1
  93. package/template/coding-task-manager/lib/hosted-input-dispatch.js +203 -0
  94. package/template/coding-task-manager/lib/hosted-sync-worker.js +113 -5
  95. package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +4 -0
  96. package/template/coding-task-manager/lib/instance-manifest-runtime.js +3 -0
  97. package/template/coding-task-manager/lib/instance-manifest.js +13 -1
  98. package/template/coding-task-manager/lib/instance-sync-status.js +15 -0
  99. package/template/coding-task-manager/lib/kernel/diag-log.js +12 -0
  100. package/template/coding-task-manager/lib/launch-model-override.js +160 -1
  101. package/template/coding-task-manager/lib/launch-plan-worker.js +59 -2
  102. package/template/coding-task-manager/lib/lead-model-observer.js +17 -2
  103. package/template/coding-task-manager/lib/lead-model-switch-runtime.js +57 -4
  104. package/template/coding-task-manager/lib/lead-model-switch.js +11 -2
  105. package/template/coding-task-manager/lib/lead-model-transition-runtime.js +42 -4
  106. package/template/coding-task-manager/lib/lead-model-transition-store.js +61 -3
  107. package/template/coding-task-manager/lib/legacy-draft-cancellation-recovery.js +111 -0
  108. package/template/coding-task-manager/lib/login-tripwire.js +18 -0
  109. package/template/coding-task-manager/lib/machine-pairing.js +238 -0
  110. package/template/coding-task-manager/lib/machine-pool.js +409 -0
  111. package/template/coding-task-manager/lib/machine-registry.js +749 -0
  112. package/template/coding-task-manager/lib/machine-self.js +66 -0
  113. package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +56 -4
  114. package/template/coding-task-manager/lib/media-journal-control-reader.js +473 -0
  115. package/template/coding-task-manager/lib/media-journal.js +84 -27
  116. package/template/coding-task-manager/lib/media-store-options.js +43 -15
  117. package/template/coding-task-manager/lib/migrations/v56-host-samples-and-machine-pool.js +76 -0
  118. package/template/coding-task-manager/lib/mirror-feed-sanitize.js +12 -3
  119. package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +16 -3
  120. package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
  121. package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
  122. package/template/coding-task-manager/lib/model-proxy/collaboration-codec.js +94 -0
  123. package/template/coding-task-manager/lib/model-proxy/handoff.js +494 -0
  124. package/template/coding-task-manager/lib/model-proxy/lead-worker-picks.js +25 -0
  125. package/template/coding-task-manager/lib/model-proxy/native-lead-listener.js +61 -7
  126. package/template/coding-task-manager/lib/model-proxy/proxy-cli.js +200 -0
  127. package/template/coding-task-manager/lib/model-proxy/proxy-instance-record.js +257 -0
  128. package/template/coding-task-manager/lib/model-proxy/responses-face.js +24 -2
  129. package/template/coding-task-manager/lib/model-proxy/stream-accounting.js +205 -0
  130. package/template/coding-task-manager/lib/model-proxy/tokens.js +78 -0
  131. package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
  132. package/template/coding-task-manager/lib/native-history-control.js +62 -14
  133. package/template/coding-task-manager/lib/native-lead-settings-client.js +2 -1
  134. package/template/coding-task-manager/lib/native-lead-tls.js +18 -4
  135. package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +628 -64
  136. package/template/coding-task-manager/lib/owner-action-manifest-data.json +14 -0
  137. package/template/coding-task-manager/lib/painted-row-recorder.js +9 -1
  138. package/template/coding-task-manager/lib/prompt-admission-trace.js +69 -0
  139. package/template/coding-task-manager/lib/prompt-submission-contract.js +21 -1
  140. package/template/coding-task-manager/lib/provider-login-broker.js +192 -9
  141. package/template/coding-task-manager/lib/provider-login-capture.js +153 -0
  142. package/template/coding-task-manager/lib/queue-draft-status-overlay.js +36 -0
  143. package/template/coding-task-manager/lib/raw-codex-history-window.js +5 -3
  144. package/template/coding-task-manager/lib/raw-history-block.js +3 -1
  145. package/template/coding-task-manager/lib/read-pool-client.js +36 -24
  146. package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
  147. package/template/coding-task-manager/lib/read-pool-telemetry.js +14 -5
  148. package/template/coding-task-manager/lib/recent-catalog-query.js +26 -0
  149. package/template/coding-task-manager/lib/recent-session-catalog-response.js +9 -0
  150. package/template/coding-task-manager/lib/remote-command-context.js +12 -0
  151. package/template/coding-task-manager/lib/remote-command-runtime.js +18 -5
  152. package/template/coding-task-manager/lib/remote-machine-catalog.js +1062 -0
  153. package/template/coding-task-manager/lib/remote-machine-create.js +693 -0
  154. package/template/coding-task-manager/lib/remote-machine-link.js +939 -0
  155. package/template/coding-task-manager/lib/remote-machine-proxy.js +520 -0
  156. package/template/coding-task-manager/lib/remote-session-forward.js +306 -0
  157. package/template/coding-task-manager/lib/remote-submission-status.js +6 -0
  158. package/template/coding-task-manager/lib/restart-outcome-truth.js +20 -3
  159. package/template/coding-task-manager/lib/restart-preflight.js +9 -2
  160. package/template/coding-task-manager/lib/restore-backoff-gate.js +10 -2
  161. package/template/coding-task-manager/lib/restore-prepare-retry.js +12 -5
  162. package/template/coding-task-manager/lib/restore-resume-batch.js +22 -1
  163. package/template/coding-task-manager/lib/resume-blocked-wire.js +62 -1
  164. package/template/coding-task-manager/lib/resume-cwd.js +13 -0
  165. package/template/coding-task-manager/lib/resume-from-store.js +12 -1
  166. package/template/coding-task-manager/lib/resume-origin-facts.js +38 -1
  167. package/template/coding-task-manager/lib/resume-preflight.js +21 -16
  168. package/template/coding-task-manager/lib/review-repo-admission.js +39 -6
  169. package/template/coding-task-manager/lib/review-repo-registry.js +17 -0
  170. package/template/coding-task-manager/lib/root-delivery-runtime.js +72 -15
  171. package/template/coding-task-manager/lib/root-delivery-selection.js +37 -1
  172. package/template/coding-task-manager/lib/root-prompt-admission.js +43 -11
  173. package/template/coding-task-manager/lib/root-prompt-flow.js +175 -38
  174. package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
  175. package/template/coding-task-manager/lib/server-memory-sampler-runtime.js +10 -0
  176. package/template/coding-task-manager/lib/server-memory-sampler.js +41 -2
  177. package/template/coding-task-manager/lib/session-analysis-memory.js +2 -0
  178. package/template/coding-task-manager/lib/session-create/codex-native-provider-gate.js +218 -0
  179. package/template/coding-task-manager/lib/session-create/codex-resume-exit-classify.js +52 -0
  180. package/template/coding-task-manager/lib/session-create/delegate-worker.js +95 -6
  181. package/template/coding-task-manager/lib/session-create/identity.js +20 -1
  182. package/template/coding-task-manager/lib/session-create/prepared.js +69 -1
  183. package/template/coding-task-manager/lib/session-create/provider-launch-context.js +77 -0
  184. package/template/coding-task-manager/lib/session-create/provider-process.js +38 -39
  185. package/template/coding-task-manager/lib/session-create/response.js +30 -12
  186. package/template/coding-task-manager/lib/session-create/transcript.js +13 -4
  187. package/template/coding-task-manager/lib/session-create/worker-continuation.js +43 -1
  188. package/template/coding-task-manager/lib/session-create/worker-notices.js +94 -5
  189. package/template/coding-task-manager/lib/session-create/worker-observability.js +99 -7
  190. package/template/coding-task-manager/lib/session-create/worker-stop.js +11 -3
  191. package/template/coding-task-manager/lib/session-create/worker-wait-watcher.js +315 -0
  192. package/template/coding-task-manager/lib/session-geometry-authority-handler.js +10 -1
  193. package/template/coding-task-manager/lib/session-host-manager.js +152 -206
  194. package/template/coding-task-manager/lib/session-jobs.js +17 -9
  195. package/template/coding-task-manager/lib/session-lifecycle.js +20 -50
  196. package/template/coding-task-manager/lib/session-model-action-preconditions.js +68 -3
  197. package/template/coding-task-manager/lib/session-resource-sampler.js +67 -9
  198. package/template/coding-task-manager/lib/session-standup.js +67 -4
  199. package/template/coding-task-manager/lib/session-status-ledger.js +42 -3
  200. package/template/coding-task-manager/lib/session-status-runtime.js +45 -1
  201. package/template/coding-task-manager/lib/session-status.js +2 -2
  202. package/template/coding-task-manager/lib/session-title-mirror.js +25 -57
  203. package/template/coding-task-manager/lib/session-transcript-trash.js +8 -1
  204. package/template/coding-task-manager/lib/startup-pointer-write.js +57 -0
  205. package/template/coding-task-manager/lib/state-sync/painted-frame-field.js +11 -0
  206. package/template/coding-task-manager/lib/storage/coding-session-model-store.js +21 -22
  207. package/template/coding-task-manager/lib/surviving-owner-bootstrap.js +7 -2
  208. package/template/coding-task-manager/lib/surviving-owner-recovery.js +2 -1
  209. package/template/coding-task-manager/lib/terminal-draft-http-status.js +25 -0
  210. package/template/coding-task-manager/lib/transcript-archiver.js +229 -229
  211. package/template/coding-task-manager/lib/transcript-frame-cache.js +12 -2
  212. package/template/coding-task-manager/lib/transcript-guard-job.js +55 -4
  213. package/template/coding-task-manager/lib/transcript-link.js +77 -0
  214. package/template/coding-task-manager/lib/transcript-manifest-store.js +8 -4
  215. package/template/coding-task-manager/lib/transcript-source.js +8 -3
  216. package/template/coding-task-manager/lib/transcript-store.js +40 -0
  217. package/template/coding-task-manager/lib/tunnel-lane-governor.js +6 -3
  218. package/template/coding-task-manager/lib/typing-interaction-trace.js +136 -2
  219. package/template/coding-task-manager/lib/typing-worker-stages.js +208 -1
  220. package/template/coding-task-manager/lib/usage-ledger/claude-parse.js +53 -2
  221. package/template/coding-task-manager/lib/usage-ledger/ingest.js +13 -4
  222. package/template/coding-task-manager/lib/usage-ledger/read.js +14 -0
  223. package/template/coding-task-manager/lib/usage-ledger/schema.js +49 -1
  224. package/template/coding-task-manager/lib/usage-ledger/store.js +337 -6
  225. package/template/coding-task-manager/lib/usage-ledger/task-read.js +123 -5
  226. package/template/coding-task-manager/lib/visible-revision-watchdog.js +8 -12
  227. package/template/coding-task-manager/lib/wake-skip-reasons.js +46 -0
  228. package/template/coding-task-manager/lib/walle-supervisor.js +98 -14
  229. package/template/coding-task-manager/lib/worker-failure-classify.js +13 -1
  230. package/template/coding-task-manager/lib/ws/input.js +1 -0
  231. package/template/coding-task-manager/lib/ws/state-sync.js +19 -2
  232. package/template/coding-task-manager/lib/ws/terminal-attach.js +32 -1
  233. package/template/coding-task-manager/lib/ws-router.js +68 -1
  234. package/template/coding-task-manager/lib/ws-send-admission.js +22 -4
  235. package/template/coding-task-manager/oauth-proxy.js +99 -55
  236. package/template/coding-task-manager/package-lock.json +24 -0
  237. package/template/coding-task-manager/package.json +3 -0
  238. package/template/coding-task-manager/protocol/message-table.json +0 -10
  239. package/template/coding-task-manager/protocol/messages.js +0 -10
  240. package/template/coding-task-manager/protocol/ws-schema.json +0 -20
  241. package/template/coding-task-manager/protocol/ws-schema.md +0 -2
  242. package/template/coding-task-manager/providers/codex-mcp-extract.js +121 -19
  243. package/template/coding-task-manager/providers/codex-mcp.js +14 -5
  244. package/template/coding-task-manager/public/css/app.css +279 -2
  245. package/template/coding-task-manager/public/css/reviews.css +4 -0
  246. package/template/coding-task-manager/public/index.html +297 -240
  247. package/template/coding-task-manager/public/js/app/machines-page.js +1252 -0
  248. package/template/coding-task-manager/public/js/app/models-tab.js +546 -36
  249. package/template/coding-task-manager/public/js/app/worktrees-panel.js +7 -5
  250. package/template/coding-task-manager/public/js/background-work.js +13 -7
  251. package/template/coding-task-manager/public/js/client-health-core.js +81 -0
  252. package/template/coding-task-manager/public/js/client-health-main.js +61 -0
  253. package/template/coding-task-manager/public/js/coding-session-model-presentation.js +44 -4
  254. package/template/coding-task-manager/public/js/core/session-commands.js +676 -0
  255. package/template/coding-task-manager/public/js/core/session-selectors.js +5 -1
  256. package/template/coding-task-manager/public/js/history-app-seam.js +26 -2
  257. package/template/coding-task-manager/public/js/history-painted-rows.js +17 -2
  258. package/template/coding-task-manager/public/js/history-reader-park.js +13 -1
  259. package/template/coding-task-manager/public/js/history-scroller.js +187 -9
  260. package/template/coding-task-manager/public/js/history-transcript-pages.js +5 -5
  261. package/template/coding-task-manager/public/js/machine-pool-shared.js +436 -0
  262. package/template/coding-task-manager/public/js/private-state-scope.js +31 -3
  263. package/template/coding-task-manager/public/js/prompt-submission-client.js +173 -4
  264. package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
  265. package/template/coding-task-manager/public/js/protocol.js +0 -10
  266. package/template/coding-task-manager/public/js/queue-panel-submission-reconcile.js +174 -1
  267. package/template/coding-task-manager/public/js/reviews.js +7 -3
  268. package/template/coding-task-manager/public/js/root-draft-lifetime.js +139 -2
  269. package/template/coding-task-manager/public/js/root-queue-builder-lifetime.js +16 -0
  270. package/template/coding-task-manager/public/js/row-join.js +18 -3
  271. package/template/coding-task-manager/public/js/session-model-picker.js +1 -1
  272. package/template/coding-task-manager/public/js/session-search-utils.js +46 -19
  273. package/template/coding-task-manager/public/js/session-usage-view.js +1 -0
  274. package/template/coding-task-manager/public/js/state-sync-client.js +4 -0
  275. package/template/coding-task-manager/public/js/terminal/runtime-02.js +16 -7
  276. package/template/coding-task-manager/public/js/terminal/runtime-03.js +41 -0
  277. package/template/coding-task-manager/public/js/terminal/runtime-04.js +41 -4
  278. package/template/coding-task-manager/public/js/terminal/runtime-12.js +52 -2
  279. package/template/coding-task-manager/public/js/terminal/runtime-16.js +12 -12
  280. package/template/coding-task-manager/public/js/terminal/runtime-18.js +14 -0
  281. package/template/coding-task-manager/public/js/terminal/runtime-19.js +8 -4
  282. package/template/coding-task-manager/public/js/terminal/runtime-20.js +210 -73
  283. package/template/coding-task-manager/public/js/terminal/runtime-21.js +19 -6
  284. package/template/coding-task-manager/public/js/terminal/runtime-22.js +86 -0
  285. package/template/coding-task-manager/public/js/terminal-history-viewport.js +1 -3
  286. package/template/coding-task-manager/public/js/terminal-wheel.js +39 -2
  287. package/template/coding-task-manager/public/js/walle/chat-conversation.js +11 -3
  288. package/template/coding-task-manager/public/js/walle-session.js +30 -7
  289. package/template/coding-task-manager/public/m/app.css +226 -8
  290. package/template/coding-task-manager/public/m/app.js +502 -9
  291. package/template/coding-task-manager/public/m/index.html +38 -1
  292. package/template/coding-task-manager/public/m/machines.js +1313 -0
  293. package/template/coding-task-manager/public/m/pool-failover.js +391 -0
  294. package/template/coding-task-manager/public/m/private-queue-actions.js +26 -7
  295. package/template/coding-task-manager/public/m/sw.js +5 -0
  296. package/template/coding-task-manager/queue-engine.js +13 -0
  297. package/template/coding-task-manager/server/approvals.js +5 -6
  298. package/template/coding-task-manager/server/boot.js +136 -17
  299. package/template/coding-task-manager/server.js +964 -425
  300. package/template/coding-task-manager/session-integrity.js +7 -9
  301. package/template/coding-task-manager/workers/db-owner-worker.js +53 -7
  302. package/template/coding-task-manager/workers/read-pool-worker.js +17 -6
  303. package/template/coding-task-manager/workers/session-host-pool-process.js +18 -4
  304. package/template/coding-task-manager/workers/session-host-process.js +14 -3
  305. package/template/coding-task-manager/workers/transcript-archive-worker.js +1 -1
  306. package/template/coding-task-manager/workers/worktree-worker.js +34 -0
  307. package/template/package-lock.json +2 -2
  308. package/template/package.json +1 -1
  309. package/template/scripts/conversation-storage-v2-migration.js +78 -11
  310. package/template/shared/conversation-storage-v2-contract.js +42 -6
  311. package/template/shared/fixtures/worker-status-payloads.json +392 -0
  312. package/template/shared/git-remote-key.js +83 -0
  313. package/template/shared/host-identity.js +98 -2
  314. package/template/shared/owner-write-safety.js +135 -0
  315. package/template/shared/public-runtime.js +13 -5
  316. package/template/shared/release-schema.json +3 -2
  317. package/template/shared/report-citations.js +14 -0
  318. package/template/shared/sqlite-write-lock.js +361 -223
  319. package/template/shared/storage/contract.js +35 -3
  320. package/template/wall-e/agent.js +21 -0
  321. package/template/wall-e/api-walle.js +5 -0
  322. package/template/wall-e/bin/brain-purge-legacy-ctm.js +3 -2
  323. package/template/wall-e/bin/walle-mcp-stdio.js +12 -6
  324. package/template/wall-e/brain/helper-rules-schema.js +129 -0
  325. package/template/wall-e/brain/migrations.js +51 -22
  326. package/template/wall-e/brain/model-deployment-facts.js +31 -1
  327. package/template/wall-e/brain/owner-call-whitelist.js +16 -0
  328. package/template/wall-e/brain/schema-ensures.js +22 -0
  329. package/template/wall-e/brain.js +1 -1
  330. package/template/wall-e/coding/host-capabilities.js +2 -2
  331. package/template/wall-e/coding/journal-dispatcher.js +51 -0
  332. package/template/wall-e/coding/session-journal.js +28 -40
  333. package/template/wall-e/coding-orchestrator.js +23 -26
  334. package/template/wall-e/fly.toml +1 -1
  335. package/template/wall-e/http/auth.js +18 -1
  336. package/template/wall-e/http/blog-views-api.js +318 -0
  337. package/template/wall-e/http/helper-rules-api.js +321 -0
  338. package/template/wall-e/http/model-proxy-providers.js +3 -1
  339. package/template/wall-e/http/telemetry-api.js +12 -0
  340. package/template/wall-e/lib/blog-view-policy.js +88 -0
  341. package/template/wall-e/lib/blog-view-store.js +169 -0
  342. package/template/wall-e/lib/client-config-lock.js +2 -1
  343. package/template/wall-e/lib/coding-journal-writer-child-client.js +67 -18
  344. package/template/wall-e/lib/error-kinds.js +10 -1
  345. package/template/wall-e/lib/feature-manifest.js +1 -1
  346. package/template/wall-e/lib/helper-rules/cell-state.js +123 -0
  347. package/template/wall-e/lib/helper-rules/evaluate.js +357 -0
  348. package/template/wall-e/lib/helper-rules/explore-rubric.md +12 -0
  349. package/template/wall-e/lib/helper-rules/grade.js +277 -0
  350. package/template/wall-e/lib/helper-rules/label.js +247 -0
  351. package/template/wall-e/lib/helper-rules/lead-call-hook.js +522 -0
  352. package/template/wall-e/lib/helper-rules/store.js +667 -0
  353. package/template/wall-e/lib/mcp-integration.js +5 -6
  354. package/template/wall-e/lib/runtime-worker-pool.js +1 -0
  355. package/template/wall-e/lib/scheduler-worker-jobs.js +6 -0
  356. package/template/wall-e/lib/telemetry-db-schema.js +37 -0
  357. package/template/wall-e/lib/telemetry-process-client.js +76 -1
  358. package/template/wall-e/llm/default-fallback.js +4 -0
  359. package/template/wall-e/llm/model-catalog.js +13 -4
  360. package/template/wall-e/llm/model-roles.js +6 -0
  361. package/template/wall-e/llm/portkey-http.js +150 -0
  362. package/template/wall-e/llm/portkey-sync.js +15 -145
  363. package/template/wall-e/llm/worker-mcp-routing.js +61 -0
  364. package/template/wall-e/llm/worker-route.js +43 -1
  365. package/template/wall-e/loops/brain-custom-writes.js +45 -0
  366. package/template/wall-e/mcp-protocol-contract.js +41 -1
  367. package/template/wall-e/mcp-server.js +13 -2
  368. package/template/wall-e/mcp-worker-schemas.js +20 -2
  369. package/template/wall-e/memory/ctm-context-client.js +25 -5
  370. package/template/wall-e/shared/release-schema.json +3 -2
  371. package/template/wall-e/shared/sqlite-write-lock.js +361 -223
  372. package/template/wall-e/shared/storage/contract.js +35 -3
  373. package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
  374. package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +6 -0
  375. package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +7 -2
  376. package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +7 -2
  377. package/template/wall-e/tools/local-tools.js +12 -5
  378. package/template/wall-e/workers/brain-owner-worker.js +9 -0
  379. package/template/wall-e/workers/coding-journal-writer-child.js +8 -1
  380. package/template/wall-e/workers/telemetry-worker.js +8 -0
  381. package/template/coding-task-manager/lib/giant-freeze-park.js +0 -31
@@ -15,6 +15,9 @@ const {
15
15
  bindObservedDatabaseGenerations,
16
16
  } = require('./conversation-storage-generation-evidence');
17
17
  const { inspectCompletedMarker } = require('./conversation-storage-completed-marker');
18
+ // WP-3 (clock-free r5 R-4..R-8): walle liveness from the write-lock identity record, the
19
+ // proven-owned daemon stop before the first walle write, and a fail-closed foreign holder.
20
+ const { ensureWalleOwnerStopped } = require('./conversation-storage-walle-owner');
18
21
  // No-op when options.profile is absent so behaviour is byte-identical to today: this
19
22
  // module only ever calls profile.measure/measureAsync around existing calls, never adds
20
23
  // new work, so an absent profile just means the timing calls disappear.
@@ -475,7 +478,13 @@ function runAutomaticConversationMigration(options = {}) {
475
478
  }
476
479
  }
477
480
  if (!state.irreversibleAt) {
478
- profile.measure('assert_databases_offline', () => _assertDatabasesOffline(specs));
481
+ // R-5/R-8: stop this instance's PROVEN-OWNED Wall-E before the first walle write, and
482
+ // probe only the databases this run will mutate -- `specs` included a live walle in a
483
+ // CTM-only rerun, which blocked the boot on a database it was never going to touch.
484
+ const walleSpecForPreflight = specs.find((spec) => spec.id === 'walle');
485
+ const walleOwner = walleSpecForPreflight && plannedDatabaseIds.has('walle')
486
+ ? ensureWalleOwnerStopped(walleSpecForPreflight.path, options.walleOwner || {}) : null;
487
+ profile.measure('assert_databases_offline', () => _assertDatabasesOffline(plannedSpecs()));
479
488
  const headroomDatabaseIds = new Set(plannedDatabaseIds);
480
489
  if (headroomDatabaseIds.has('ctm') && specs.some((spec) => spec.id === 'walle')) {
481
490
  headroomDatabaseIds.add('walle');
@@ -486,6 +495,7 @@ function runAutomaticConversationMigration(options = {}) {
486
495
  options,
487
496
  )),
488
497
  databaseOwnership: 'offline',
498
+ ...(walleOwner ? { walleOwner } : {}),
489
499
  };
490
500
  checkpoint('preflight');
491
501
  checkpoint('inventory');
@@ -654,6 +664,11 @@ function runAutomaticConversationMigration(options = {}) {
654
664
  expandWalleDependency = true;
655
665
  } finally { ctmDb.close(); walleDb.close(); }
656
666
  if (expandWalleDependency) {
667
+ // R-5/R-8 (still inside the !state.irreversibleAt guard above): the expansion adds
668
+ // walle AFTER the preflight probe, so the owner must be proven stopped here too,
669
+ // before rebuild_walle -- and walle alone is re-probed.
670
+ state.preflight.walleOwner = ensureWalleOwnerStopped(walleSpec.path, options.walleOwner || {});
671
+ _assertDatabasesOffline([walleSpec]);
657
672
  plannedDatabaseIds.add('walle');
658
673
  delete state.cleanBackups.walle;
659
674
  state.restartPreflight.plannedDatabaseIds = specs
@@ -5,6 +5,7 @@ const Database = require('better-sqlite3');
5
5
  const migration = require('../../scripts/conversation-storage-v2-migration');
6
6
  const {
7
7
  sanitizeInspectionViolations,
8
+ inspectionErrorClass,
8
9
  } = require('../../shared/conversation-storage-v2-contract');
9
10
 
10
11
  // Moved from conversation-storage-auto-migration-lib.js (WP-5 un-golf/split: keeping the
@@ -170,8 +171,14 @@ function inspectCompletedMarkerSpec(spec, expectedGeneration, generationEvidence
170
171
  violations,
171
172
  ...(generationChanged ? { generationChanged: true } : {}),
172
173
  };
173
- } catch {
174
- return { clean: false, reasonCodes: ['inspection_failed'], violations: [] };
174
+ } catch (error) {
175
+ // REPORT: an inspection that throws still plans a rerun (RERUN_CLASS, unchanged), but the
176
+ // boot log now names the error class instead of an empty violation list (clock-free r6 OQ-5).
177
+ return {
178
+ clean: false,
179
+ reasonCodes: ['inspection_failed'],
180
+ violations: [{ code: 'inspection_failed', objectId: inspectionErrorClass(error), count: 1 }],
181
+ };
175
182
  }
176
183
  }
177
184
 
@@ -12,6 +12,7 @@ const {
12
12
  buildConversationMigrationStatus,
13
13
  } = require('../lib/conversation-storage-migration-status');
14
14
  const { rebindCompletedGenerations } = require('./conversation-storage-generation-evidence');
15
+ const { resolveInstanceTag } = require('../lib/instance-tag');
15
16
  const { createPhaseProfile } = require('../lib/phase-profile');
16
17
  const {
17
18
  sanitizeInspectionViolations,
@@ -102,7 +103,13 @@ function buildConversationStorageServiceLog(result = {}) {
102
103
  recoveryArtifactCount: status.recoveryArtifactCount,
103
104
  cleanupPendingCount: status.cleanupPendingCount,
104
105
  };
105
- const restartPreflight = _restartPreflightForServiceLog(result.restartPreflight);
106
+ // R-6 (clock-free r4): restartPreflight describes a migration that was PLANNED by the
107
+ // boot that ran it. An already_complete / generation_rebound boot (result.skipped set)
108
+ // only re-reads the journal, so re-emitting the recorded object would print a stale
109
+ // re-run plan for a boot that ran no migration -- exactly what misled r3's census.
110
+ const restartPreflight = result.skipped == null
111
+ ? _restartPreflightForServiceLog(result.restartPreflight)
112
+ : null;
106
113
  if (restartPreflight) summary.restartPreflight = restartPreflight;
107
114
  return summary;
108
115
  }
@@ -129,6 +136,14 @@ function resolveAutomaticConversationMigrationOptions(env = process.env, homedir
129
136
  },
130
137
  batchLimit: Number(env.CTM_CONVERSATION_MIGRATION_BATCH_ROWS) || undefined,
131
138
  batchByteLimit: Number(env.CTM_CONVERSATION_MIGRATION_BATCH_BYTES) || undefined,
139
+ // WP-3 (clock-free r5 R-5/R-6): the kill authority the boot migration uses to prove and
140
+ // stop THIS instance's Wall-E before it touches wall-e-brain.db. Same tag derivation the
141
+ // supervisor uses (server.js:5897), so a dev/test instance can never signal the primary's
142
+ // daemon. Kept out of buildConversationStorageServiceLog, which never serializes options.
143
+ walleOwner: {
144
+ configDir: ctmDataDir,
145
+ instanceTag: resolveInstanceTag({ env, configDir: ctmDataDir, port: parseInt(env.CTM_PORT || '3456', 10) }),
146
+ },
132
147
  };
133
148
  }
134
149
 
@@ -0,0 +1,247 @@
1
+ 'use strict';
2
+
3
+ // WP-3 / R-4..R-8 of docs/clock-free-tests-followups-r5-2026-10-25.html §5.
4
+ //
5
+ // Liveness for the boot migration's destructive walle re-run comes from Wall-E's own
6
+ // write-lock identity record — `<db>.write-lock`, a 1,024-byte space-padded JSON
7
+ // `{pid,...}` that PERSISTS after release (shared/sqlite-write-lock.js:1-40,250, D-3) —
8
+ // not from a `BEGIN IMMEDIATE` probe. Probe P1 showed that an owner which is alive with an
9
+ // idle connection (or no connection at all) passes the transaction probe, and the purge
10
+ // then rewrites the database under it (the 2026-07-20 column-loss incident, r4 §1.1).
11
+ //
12
+ // The module is synchronous on purpose: bin/ctm-service-entry.js runs this migration
13
+ // BEFORE it requires ../server, and the stop has to finish before the first walle write.
14
+ //
15
+ // R-6: nothing here is ever signalled on a name match. The one pid that may receive a
16
+ // signal is the daemon pid read from `<configDir>/walle.pid`, and only after a single
17
+ // `ps -p <pid>` row proves it is this instance's own Wall-E (lib/walle-kill-authority.js).
18
+ // R-7: any other live write-lock holder stays fail-closed (`conversation_migration_database_busy`),
19
+ // which the caller maps to exit 78 exactly as before.
20
+
21
+ const fs = require('node:fs');
22
+ const path = require('node:path');
23
+ const { execFileSync } = require('node:child_process');
24
+ const { processTitleCandidates } = require('../lib/process-title');
25
+ const { isOwnedRow } = require('../lib/walle-kill-authority');
26
+ const { intFlag } = require('../lib/env-flags');
27
+
28
+ const WALLE_PID_FILE = 'walle.pid';
29
+ const WRITE_LOCK_SUFFIX = '.write-lock';
30
+ const PS_TIMEOUT_MS = 5000;
31
+ const PS_MAX_BUFFER = 1024 * 1024;
32
+ // One SIGKILL grace window after the graceful budget is spent; a killed-but-unreaped
33
+ // process is still a live pid until the kernel reaps it.
34
+ const SIGKILL_GRACE_MS = 1000;
35
+ // Same budget, default and clamp as lib/walle-supervisor.js:333,389 — a Wall-E that is
36
+ // legitimately draining inside its own WALLE_SHUTDOWN_DEADLINE_MS gets to finish.
37
+ const CHILD_EXIT_WAIT_ENV = 'WALLE_CHILD_EXIT_WAIT_MS';
38
+ const DEFAULT_CHILD_EXIT_WAIT_MS = 16000;
39
+ const MIN_CHILD_EXIT_WAIT_MS = 5000;
40
+ const MAX_CHILD_EXIT_WAIT_MS = 120000;
41
+ const DEFAULT_LOCK_RELEASE_WAIT_MS = 2000;
42
+ const DEFAULT_POLL_MS = 100;
43
+
44
+ function _childExitWaitMs() {
45
+ return Math.max(MIN_CHILD_EXIT_WAIT_MS, Math.min(MAX_CHILD_EXIT_WAIT_MS,
46
+ intFlag(CHILD_EXIT_WAIT_ENV, DEFAULT_CHILD_EXIT_WAIT_MS)));
47
+ }
48
+
49
+ // R-4: the owner identity, read off disk. null covers ENOENT (nothing ever wrote here),
50
+ // EISDIR and a torn read of a v2 in-place write — every one of those means "no usable
51
+ // identity", never "throw". Shape copied from shared/sqlite-write-lock.js:250 (_readIdentity)
52
+ // and wall-e/bin/brain-purge-legacy-ctm.js:236-245 (liveReason's first check).
53
+ function readWriteLockOwnerPid(dbPath) {
54
+ let record;
55
+ try {
56
+ record = JSON.parse(fs.readFileSync(`${dbPath}${WRITE_LOCK_SUFFIX}`, 'utf8'));
57
+ } catch {
58
+ /* expected: ENOENT (no writer yet), EISDIR (a directory sits at the path) and a torn
59
+ read of an in-place v2 write all mean "no identity readable right now". */
60
+ return null;
61
+ }
62
+ const pid = Number(record?.pid);
63
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
64
+ }
65
+
66
+ // R-4: only ESRCH proves the pid is gone. EPERM means the pid EXISTS but is owned by
67
+ // another user — kill(pid,0) probes existence, not killability — so every non-ESRCH
68
+ // error fails toward "alive" (shared/sqlite-write-lock.js:231-245).
69
+ function pidAlive(pid, kill = process.kill) {
70
+ const value = Number(pid);
71
+ if (!Number.isFinite(value) || value <= 0) return false;
72
+ try {
73
+ kill(value, 0);
74
+ return true;
75
+ } catch (error) {
76
+ return error?.code !== 'ESRCH';
77
+ }
78
+ }
79
+
80
+ // A pid that has exited but has not been REAPED is still a live pid for kill(pid,0): the
81
+ // kernel keeps a zombie's pid until its parent waits. libuv only waits when the event loop
82
+ // runs, and this migration blocks the loop for its whole duration (see the module comment:
83
+ // it runs before ../server is required), so a daemon this process spawned -- ppid === myPid,
84
+ // which lib/walle-kill-authority.js:17 explicitly accepts as owned -- stays kill(pid,0)-alive
85
+ // forever after it has exited. Measured on this host: `kill(pid,0)` says alive while
86
+ // `ps -o stat=` says "Z" for the whole blocked window, then the pid disappears once the loop
87
+ // turns. A zombie has exited, so it must read as gone -- otherwise the SIGTERM we just sent
88
+ // would be reported back to the caller as "a live holder that is not this instance's Wall-E".
89
+ // Only consulted when kill(pid,0) already says alive, so the common reparented-to-launchd
90
+ // daemon (ppid 1, reaped by init) never pays for it.
91
+ function _isZombiePid(pid) {
92
+ try {
93
+ const stdout = execFileSync('ps', ['-p', String(pid), '-o', 'stat='], {
94
+ encoding: 'utf8',
95
+ timeout: PS_TIMEOUT_MS,
96
+ maxBuffer: 4096,
97
+ });
98
+ return String(stdout).trim().startsWith('Z');
99
+ } catch {
100
+ /* expected: ps exits non-zero when the pid has no row -- it is gone, or it was reaped
101
+ between the two probes. Either way there is no zombie to report. */
102
+ return false;
103
+ }
104
+ }
105
+
106
+ function _readWalleDaemonPid(configDir) {
107
+ if (!configDir) return null;
108
+ try {
109
+ const pid = Number.parseInt(fs.readFileSync(path.join(String(configDir), WALLE_PID_FILE), 'utf8').trim(), 10);
110
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
111
+ } catch {
112
+ /* expected: no pid file (Wall-E never started for this config dir) or an unreadable
113
+ one both mean "no daemon to stop" — R-7 still fails closed on the write-lock owner. */
114
+ return null;
115
+ }
116
+ }
117
+
118
+ // The same synchronous sleep idiom the write-lock module uses (shared/sqlite-write-lock.js:226-229).
119
+ function sleepSync(ms) {
120
+ const buffer = new SharedArrayBuffer(4);
121
+ Atomics.wait(new Int32Array(buffer), 0, 0, ms);
122
+ }
123
+
124
+ let _parsePsWithPpid = null;
125
+
126
+ // Rows for ONE pid, parsed by the supervisor's own parser so ownership sees an identical
127
+ // row shape (lib/walle-supervisor.js:166-184,205-212). Loaded lazily: the migration runs
128
+ // before ../server boots, and the ps path is only reached when a live owner record exists.
129
+ function _psRow(pid) {
130
+ if (!_parsePsWithPpid) ({ parsePsWithPpid: _parsePsWithPpid } = require('../lib/walle-supervisor'));
131
+ try {
132
+ const stdout = execFileSync('ps', ['-p', String(pid), '-o', 'pid=,ppid=,command='], {
133
+ encoding: 'utf8',
134
+ timeout: PS_TIMEOUT_MS,
135
+ maxBuffer: PS_MAX_BUFFER,
136
+ });
137
+ return _parsePsWithPpid(stdout);
138
+ } catch (error) {
139
+ // A non-zero exit is ps answering "no such process" -> an empty row list.
140
+ // Anything else (ps un-runnable, timed out) is the probe itself failing -> null, which
141
+ // ownership must read as "not proven owned", never as "there is no process".
142
+ return Number.isInteger(error?.status) ? [] : null;
143
+ }
144
+ }
145
+
146
+ // R-4/R-5/R-7 liveness, one predicate: gone (ESRCH) or exited-and-not-yet-reaped (zombie).
147
+ function _hasExited(pid, ops) {
148
+ return !pidAlive(pid, ops.kill) || ops.zombie(pid);
149
+ }
150
+
151
+ // Poll until the pid is gone, bounded by BOTH the deadline and an iteration cap: a caller
152
+ // that injects a `now` which does not advance must not spin this loop forever.
153
+ function _waitForExit(pid, timeoutMs, pollMs, ops) {
154
+ if (_hasExited(pid, ops)) return true;
155
+ const budget = Math.max(0, Number(timeoutMs) || 0);
156
+ const poll = Math.max(1, Number(pollMs) || DEFAULT_POLL_MS);
157
+ const deadline = ops.now() + budget;
158
+ const maxIterations = Math.ceil(budget / poll) + 1;
159
+ for (let index = 0; index < maxIterations; index += 1) {
160
+ if (ops.now() >= deadline) break;
161
+ ops.sleep(Math.max(1, Math.min(poll, deadline - ops.now())));
162
+ if (_hasExited(pid, ops)) return true;
163
+ }
164
+ return _hasExited(pid, ops);
165
+ }
166
+
167
+ // R-5/R-6/R-7. Returns { stopped, pid, waitedMs, signal }; throws
168
+ // `conversation_migration_database_busy` when a live holder survives (R-7).
169
+ function ensureWalleOwnerStopped(walleDbPath, options = {}) {
170
+ const injected = options.ops || {};
171
+ const ops = {
172
+ kill: typeof injected.kill === 'function' ? injected.kill : process.kill,
173
+ psRow: typeof injected.psRow === 'function' ? injected.psRow : _psRow,
174
+ sleep: typeof injected.sleep === 'function' ? injected.sleep : sleepSync,
175
+ now: typeof injected.now === 'function' ? injected.now : Date.now,
176
+ myPid: Number.isInteger(injected.myPid) ? injected.myPid : process.pid,
177
+ // A caller that injects its own process table owns liveness entirely -- probing the
178
+ // real ps for a fake pid would be meaningless (and would fork a process per poll). The
179
+ // real zombie probe is only armed on the real process.kill path.
180
+ zombie: typeof injected.zombie === 'function' ? injected.zombie
181
+ : (typeof injected.kill === 'function' ? () => false : _isZombiePid),
182
+ };
183
+ const instanceTag = options.instanceTag ? String(options.instanceTag) : null;
184
+ const childExitWaitMs = Number.isFinite(options.childExitWaitMs)
185
+ ? Math.max(0, Number(options.childExitWaitMs)) : _childExitWaitMs();
186
+ const lockReleaseWaitMs = Number.isFinite(options.lockReleaseWaitMs)
187
+ ? Math.max(0, Number(options.lockReleaseWaitMs)) : DEFAULT_LOCK_RELEASE_WAIT_MS;
188
+ const pollMs = Number.isFinite(options.pollMs) ? Math.max(1, Number(options.pollMs)) : DEFAULT_POLL_MS;
189
+ const startedAt = ops.now();
190
+
191
+ const ownerPid = readWriteLockOwnerPid(walleDbPath);
192
+ if (ownerPid === null || ownerPid === ops.myPid || _hasExited(ownerPid, ops)) {
193
+ // No usable record, our own pid, or a provably dead writer: nothing to stop, and no
194
+ // ownership probe is even attempted.
195
+ return { stopped: false, pid: null, waitedMs: 0, signal: null };
196
+ }
197
+
198
+ let daemonPid = null;
199
+ let signal = null;
200
+ if (options.configDir && instanceTag) {
201
+ const candidate = _readWalleDaemonPid(options.configDir);
202
+ if (candidate !== null && !_hasExited(candidate, ops)) daemonPid = candidate;
203
+ }
204
+ if (daemonPid !== null) {
205
+ const rows = ops.psRow(daemonPid);
206
+ const row = (Array.isArray(rows) ? rows : []).find((entry) => Number(entry?.pid) === daemonPid) || null;
207
+ const owned = isOwnedRow(row, {
208
+ titles: new Set(processTitleCandidates('walle', instanceTag)),
209
+ myPid: ops.myPid,
210
+ });
211
+ if (owned) {
212
+ ops.kill(daemonPid, 'SIGTERM');
213
+ signal = 'SIGTERM';
214
+ if (!_waitForExit(daemonPid, childExitWaitMs, pollMs, ops)) {
215
+ ops.kill(daemonPid, 'SIGKILL');
216
+ signal = 'SIGKILL';
217
+ _waitForExit(daemonPid, SIGKILL_GRACE_MS, pollMs, ops);
218
+ }
219
+ }
220
+ }
221
+ // The write-lock owner may be a different pid than the daemon (a worker, or a foreign
222
+ // process). It gets the same short grace — the lock is released by process exit — and
223
+ // then fails closed below. It is never signalled unless it IS the proven daemon pid.
224
+ if (!_hasExited(ownerPid, ops)) _waitForExit(ownerPid, lockReleaseWaitMs, pollMs, ops);
225
+
226
+ if (!_hasExited(ownerPid, ops)) {
227
+ const error = new Error(
228
+ `database is owned by a live process that is not this instance's Wall-E: walle pid=${ownerPid}`,
229
+ );
230
+ error.code = 'conversation_migration_database_busy';
231
+ error.database = 'walle';
232
+ error.ownerPid = ownerPid;
233
+ throw error;
234
+ }
235
+ return {
236
+ stopped: signal !== null,
237
+ pid: daemonPid,
238
+ waitedMs: Math.max(0, ops.now() - startedAt),
239
+ signal,
240
+ };
241
+ }
242
+
243
+ module.exports = {
244
+ ensureWalleOwnerStopped,
245
+ pidAlive,
246
+ readWriteLockOwnerPid,
247
+ };
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // WP-3 (docs/machine-pool-picker-pairing-dashboard-2026-10-24.html §6.3, §9 S-23): the pairing CLI.
5
+ //
6
+ // ctm-machine-pair offer [--port <n>] [--json]
7
+ //
8
+ // Mints a pairing offer on the LOCAL CTM (`POST /api/machine-pairing/offers`) and prints the code,
9
+ // the expiry and the whole `ctmpair1:` pairing string, so a terminal user can pair a machine without
10
+ // opening the Machines page.
11
+ //
12
+ // IT NEVER PRINTS A TOKEN. The offer response carries a CODE (a single-use, TTL-bounded, 20^8-space
13
+ // secret) and nothing else secret; the pool token only ever travels member -> box. The response body
14
+ // is re-projected field by field rather than echoed, so a future response field can never leak
15
+ // through this CLI by accident.
16
+ //
17
+ // AUTH. The route is admin + step-up remote (lib/auth-rules.js), and a LOOPBACK request is already
18
+ // admin-scoped with no step-up (`hasScope`/`authorize`: loopback satisfies every scope and
19
+ // `stepUp === 'remote'` is skipped for loopback), so no cookie, bearer or device token is needed —
20
+ // this is the same posture every other loopback drivetool has. `--port` (or `CTM_PORT`, default
21
+ // 3456) is the only knob; the request always goes to 127.0.0.1 so it can never mint an offer on a
22
+ // remote machine.
23
+ //
24
+ // argv parsing only, no prompts: this is meant to be scriptable.
25
+
26
+ const http = require('node:http');
27
+ // CTM_PORT is read through lib/env-flags.js (the one truthiness/number parser) rather than
28
+ // `process.env` directly: bin/flag-lint.js fails a new raw read of a CTM_* flag outside that module,
29
+ // and CTM_PORT is a genuinely shared knob (bin/dev.sh, lib/hosted-primary-runtime.js).
30
+ const { intFlag } = require('../lib/env-flags');
31
+
32
+ const DEFAULT_PORT = 3456;
33
+
34
+ function parseArgs(argv) {
35
+ const envPort = intFlag('CTM_PORT', DEFAULT_PORT);
36
+ const out = { command: '', port: envPort > 0 ? envPort : DEFAULT_PORT, json: false, help: false };
37
+ for (let i = 0; i < argv.length; i += 1) {
38
+ const arg = argv[i];
39
+ if (arg === '--port' || arg === '-p') {
40
+ const value = Number(argv[++i]);
41
+ if (Number.isInteger(value) && value > 0) out.port = value;
42
+ } else if (arg === '--json') {
43
+ out.json = true;
44
+ } else if (arg === '--help' || arg === '-h') {
45
+ out.help = true;
46
+ } else if (!out.command) {
47
+ out.command = arg;
48
+ }
49
+ }
50
+ return out;
51
+ }
52
+
53
+ function usage(out) {
54
+ out.write('usage: ctm-machine-pair offer [--port <n>] [--json]\n');
55
+ out.write('\n');
56
+ out.write(' offer mint a pairing code on the local CTM and print it\n');
57
+ out.write('\n');
58
+ out.write(' --port <n> CTM port (default: CTM_PORT, else 3456)\n');
59
+ out.write(' --json print the offer as JSON instead of a human report\n');
60
+ }
61
+
62
+ /**
63
+ * One loopback POST. `127.0.0.1` is hard-coded on purpose: a pairing offer must never be mintable on
64
+ * a machine other than the one this process is on.
65
+ */
66
+ function postLoopback(port, pathname, body) {
67
+ return new Promise((resolve, reject) => {
68
+ const payload = Buffer.from(JSON.stringify(body || {}));
69
+ const req = http.request({
70
+ host: '127.0.0.1',
71
+ port,
72
+ path: pathname,
73
+ method: 'POST',
74
+ headers: {
75
+ 'Content-Type': 'application/json',
76
+ 'Content-Length': payload.length,
77
+ Accept: 'application/json',
78
+ },
79
+ }, (res) => {
80
+ const chunks = [];
81
+ res.on('data', (chunk) => chunks.push(chunk));
82
+ res.on('end', () => {
83
+ const raw = Buffer.concat(chunks).toString('utf8');
84
+ let parsed = null;
85
+ try {
86
+ parsed = raw ? JSON.parse(raw) : null;
87
+ } catch {
88
+ parsed = null; // expected: a non-JSON error page is reported by status alone
89
+ }
90
+ resolve({ status: Number(res.statusCode) || 0, body: parsed, raw });
91
+ });
92
+ });
93
+ req.on('error', reject);
94
+ req.end(payload);
95
+ });
96
+ }
97
+
98
+ function formatExpiry(expiresAt) {
99
+ const at = Number(expiresAt);
100
+ if (!Number.isFinite(at) || at <= 0) return 'unknown';
101
+ const seconds = Math.max(0, Math.round((at - Date.now()) / 1000));
102
+ return `${new Date(at).toISOString()} (in ${seconds}s)`;
103
+ }
104
+
105
+ async function main(argv, io) {
106
+ const out = io.out;
107
+ const err = io.err;
108
+ const args = parseArgs(argv);
109
+ if (args.help || !args.command) {
110
+ usage(args.command ? out : err);
111
+ return args.command ? 0 : 2;
112
+ }
113
+ if (args.command !== 'offer') {
114
+ err.write(`ctm-machine-pair: unknown command '${args.command}'\n`);
115
+ usage(err);
116
+ return 2;
117
+ }
118
+ let res = null;
119
+ try {
120
+ res = await postLoopback(args.port, '/api/machine-pairing/offers', {});
121
+ } catch (error) {
122
+ err.write(`ctm-machine-pair: cannot reach CTM on 127.0.0.1:${args.port} — ${error && error.message ? error.message : error}\n`);
123
+ return 1;
124
+ }
125
+ if (res.status !== 201 || !res.body || res.body.ok !== true) {
126
+ const code = (res.body && (res.body.error || res.body.code)) || `http_${res.status}`;
127
+ err.write(`ctm-machine-pair: offer refused — ${code}\n`);
128
+ if (code === 'pool_member_exists') err.write(' this machine is already in a pool; leave it first\n');
129
+ if (code === 'pairing_offer_limit') err.write(' three offers are already live; wait for one to expire\n');
130
+ if (code === 'auth_rule_missing' || code === 'unauthorized') err.write(` is CTM running on port ${args.port}?\n`);
131
+ return 1;
132
+ }
133
+ // Field-by-field projection: the response is NOT echoed, so a token added to it later cannot leak.
134
+ const offer = {
135
+ code: String(res.body.code || ''),
136
+ expiresAt: Number(res.body.expiresAt) || 0,
137
+ pairingString: String(res.body.pairingString || ''),
138
+ candidates: Array.isArray(res.body.candidates) ? res.body.candidates.map((url) => String(url || '')) : [],
139
+ };
140
+ if (args.json) {
141
+ out.write(`${JSON.stringify(offer, null, 2)}\n`);
142
+ return 0;
143
+ }
144
+ out.write(`Pairing code: ${offer.code}\n`);
145
+ out.write(`Expires: ${formatExpiry(offer.expiresAt)}\n`);
146
+ if (offer.candidates.length) out.write(`Reachable at: ${offer.candidates.join(', ')}\n`);
147
+ out.write(`Pairing string: ${offer.pairingString}\n`);
148
+ return 0;
149
+ }
150
+
151
+ if (require.main === module) {
152
+ main(process.argv.slice(2), { out: process.stdout, err: process.stderr })
153
+ .then((code) => { process.exitCode = code; })
154
+ .catch((error) => {
155
+ process.stderr.write(`ctm-machine-pair: ${error && error.stack ? error.stack : error}\n`);
156
+ process.exitCode = 1;
157
+ });
158
+ }
159
+
160
+ module.exports = { parseArgs, postLoopback, main };