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
@@ -1,5 +1,10 @@
1
1
  'use strict';
2
2
 
3
+ // The one capability that selects the wide `peer` bucket below (WP-3). Spelled as a literal on
4
+ // purpose: lib/auth-rules.js owns the constant, and requiring that module here would put this
5
+ // low-level limiter behind the rule tables (and their consumers) for a one-string comparison.
6
+ const PEER_CONTROL_SCOPE = 'peer-control';
7
+
3
8
  const DEFAULT_LIMITS = Object.freeze({
4
9
  // Remote/mobile page bootstrap fans out across settings, sessions, standup,
5
10
  // auth state, WebSocket setup, and cached refresh probes. Keep read traffic
@@ -8,6 +13,22 @@ const DEFAULT_LIMITS = Object.freeze({
8
13
  mutation: { refillPerMinute: 30, burst: 5 },
9
14
  admin: { refillPerMinute: 10, burst: 2 },
10
15
  step_up: { refillPerMinute: 10, burst: 3 },
16
+ // peer (WP-3, docs/remote-machine-hub-sessions-2026-10-22.html §6.4/§10): a remote machine HUB
17
+ // relays the keystrokes, prompts, approvals and lifecycle frames of every session it views on
18
+ // this box through ONE peer-control bearer — so the whole hub is one rate-limit key here and the
19
+ // `admin`/`mutation` buckets (10/min burst 2, 30/min burst 5) would throttle a hub that is simply
20
+ // relaying several sessions. Sized like `default` (600/min, burst 120) because a relay is
21
+ // read-shaped traffic that happens to be carried by mutating message types. Selected ONLY for an
22
+ // authenticated `peer-control` principal (see rateKindForRule below); every other caller keeps
23
+ // the exact bucket it had before.
24
+ peer: { refillPerMinute: 600, burst: 120 },
25
+ // machine_pair (WP-3, docs/machine-pool-picker-pairing-dashboard-2026-10-24.html §6.3/D-13): the
26
+ // PRE-AUTH POST /api/machine-pairing/join. `sync`'s own bucket. It is the ONLY bucket selected by
27
+ // route rather than by principal: the join has no authenticated caller yet (the request carries the
28
+ // pool secret it is trying to prove), so it is keyed by REMOTE ADDRESS and sized for a human typing
29
+ // a code plus a retry or two — 5/min, burst 5 — never for a script. A code is 20^8; this bucket
30
+ // plus lib/machine-pairing.js's 5-wrong-redeem burn is what keeps the search space out of reach.
31
+ machine_pair: { refillPerMinute: 5, burst: 5 },
11
32
  });
12
33
 
13
34
  const DEFAULT_FAILED_AUTH = Object.freeze({
@@ -131,9 +152,28 @@ class AuthRateLimiter {
131
152
  }
132
153
  }
133
154
 
134
- function rateKindForRule(rule, pathname = '') {
155
+ /**
156
+ * Which bucket a rule is charged to.
157
+ *
158
+ * The optional third argument is the AUTH CONTEXT the decision is being made for. It exists for
159
+ * exactly one case (WP-3 §6.4): a `peer-control` principal is a machine hub relaying many sessions
160
+ * through one bearer, so its mutations are charged to the wide `peer` bucket instead of
161
+ * `admin`/`mutation`. Callers that pass no auth (or an auth with no `peer-control` scope) get
162
+ * byte-identical behaviour to before this argument existed — it can only ever WIDEN a bucket for a
163
+ * peer-control principal, never narrow one.
164
+ *
165
+ * @param {{mutation?: boolean, scope?: string}} rule
166
+ * @param {string} [pathname]
167
+ * @param {{scopes?: Set<string>|string[]}} [auth]
168
+ */
169
+ function rateKindForRule(rule, pathname = '', auth = null) {
135
170
  if (pathname === '/api/auth/begin-step-up') return 'step_up';
136
171
  if (!rule?.mutation) return 'default';
172
+ const scopes = auth && auth.scopes;
173
+ const isPeerControl = scopes instanceof Set
174
+ ? scopes.has(PEER_CONTROL_SCOPE)
175
+ : Array.isArray(scopes) && scopes.includes(PEER_CONTROL_SCOPE);
176
+ if (isPeerControl) return 'peer';
137
177
  if (rule.scope === 'admin' || rule.scope === 'create') return 'admin';
138
178
  return 'mutation';
139
179
  }
@@ -2,30 +2,54 @@
2
2
 
3
3
  const ALL_SCOPES = Object.freeze(['read', 'respond', 'create', 'admin', 'sync']);
4
4
 
5
+ // WP-1 (docs/remote-machine-hub-sessions-2026-10-22.html §5 D-7, §6.2, §9 S-17): the
6
+ // `peer-control` capability. It is deliberately NOT a member of ALL_SCOPES: nothing that
7
+ // parses scopes (env-scoped test tokens, device tokens, the admin expansion in parseScopes)
8
+ // can ever mint it, so the only credential that carries it is the sync peer bearer
9
+ // (CTM_SYNC_PEER_TOKEN / CTM_SYNC_PEER_TOKENS — same secret, no new env var), resolved in
10
+ // lib/auth-context.js's resolveSyncPeerToken. `admin` never implies it and it implies nothing:
11
+ // hasScope()/authorize() grant it ONLY the rule rows that declare `peerControl: true`.
12
+ const PEER_CONTROL_SCOPE = 'peer-control';
13
+
14
+ // §6.2's allowlist of WS message types a peer-control identity may send:
15
+ // attach, keyframe-req, ack, nack, frame-receipt, wake-resync, submit-prompt,
16
+ // approval-resolve, model-change, park, wake, kill, list, create.
17
+ //
18
+ // WP-3b (docs/remote-machine-hub-sessions-2026-10-22.html §6.2, AC-3a) adds the nine types the
19
+ // relay forwards VERBATIM from the hub browser to the box PTY and whose reply the hub fans back
20
+ // out — without them the box answers `auth-error{code:'insufficient_scope'}` and the relayed
21
+ // terminal cannot be typed into: input, interrupt-session, resize, reflow, snapshot, reconcile,
22
+ // flow, terminal-bootstrap, terminal-bootstrap-ack. Their scope/mutation/step-up are UNCHANGED
23
+ // (only the `peerControl` flag is added, exactly like the §6.2 rows above). Every other WS type
24
+ // stays out of reach for the peer bearer (it keeps only the `sync` scope it already had) — in
25
+ // particular `view`, `detach`, `ping` and `client-info` are never forwarded upstream by the
26
+ // relay, so they remain unflagged.
5
27
  const WS_AUTH_RULES = Object.freeze({
6
- list: rule('read', false, 'never'),
7
- attach: rule('read', false, 'never'),
28
+ list: peerControl('read', false, 'never'),
29
+ attach: peerControl('read', false, 'never'),
8
30
  detach: rule('read', false, 'never'),
9
31
  // D-1 (terminal-delivery-contract-simplification-2026-09-21.html): replaces
10
32
  // subscribe/unsubscribe/set-visible/client-visibility with the one full-set
11
33
  // declaration. Same viewer-local, read-only class as its predecessors.
12
34
  view: rule('read', false, 'never'),
13
35
  ping: rule('read', false, 'never'),
14
- 'wake-resync': rule('read', false, 'never'),
36
+ 'wake-resync': peerControl('read', false, 'never'),
15
37
  'subscribe-stream': rule('read', false, 'never'),
16
38
  'unsubscribe-stream': rule('read', false, 'never'),
17
39
  'subscribe-terminal-tail': rule('read', false, 'never'),
18
40
  'unsubscribe-terminal-tail': rule('read', false, 'never'),
19
- snapshot: rule('read', false, 'never'),
20
- reconcile: rule('read', false, 'never'),
21
- resize: rule('read', false, 'never'),
22
- reflow: rule('read', false, 'never'),
41
+ // WP-3b: the relay forwards these viewer-render frames verbatim (S-22 resync/repaint), so the
42
+ // peer bearer must reach them; scope/mutation/step-up are unchanged.
43
+ snapshot: peerControl('read', false, 'never'),
44
+ reconcile: peerControl('read', false, 'never'),
45
+ resize: peerControl('read', false, 'never'),
46
+ reflow: peerControl('read', false, 'never'),
23
47
  // State-sync render protocol: clients acknowledge frames, request bounded
24
48
  // keyframes, and report visible sessions. These only affect that viewer's
25
49
  // stream/backpressure state, not agent execution or persisted session data.
26
- ack: rule('read', false, 'never'),
27
- 'frame-receipt': rule('read', false, 'never'),
28
- 'keyframe-req': rule('read', false, 'never'),
50
+ ack: peerControl('read', false, 'never'),
51
+ 'frame-receipt': peerControl('read', false, 'never'),
52
+ 'keyframe-req': peerControl('read', false, 'never'),
29
53
  'restore-applied': rule('read', false, 'never'),
30
54
  // D-2/WP-2 verdict/adopt round-trip ack — same viewer-local render-protocol
31
55
  // class as frame-receipt/restore-applied: acknowledges a server-shipped
@@ -35,8 +59,8 @@ const WS_AUTH_RULES = Object.freeze({
35
59
  // this session's live output, read-only, no PTY input or persisted-state mutation.
36
60
  // Cold bootstrap is the viewer-local bounded terminal baseline and its
37
61
  // receipt. Neither message writes PTY input or persisted session state.
38
- 'terminal-bootstrap': rule('read', false, 'never'),
39
- 'terminal-bootstrap-ack': rule('read', false, 'never'),
62
+ 'terminal-bootstrap': peerControl('read', false, 'never'),
63
+ 'terminal-bootstrap-ack': peerControl('read', false, 'never'),
40
64
  'session-inventory': rule('read', false, 'never'),
41
65
  'terminal-geometry-claim-v2': rule('read', false, 'never'),
42
66
  'secondary-snapshot-retry': rule('read', false, 'never'),
@@ -46,7 +70,7 @@ const WS_AUTH_RULES = Object.freeze({
46
70
  // Backpressure flow control: a viewer asks to pause/resume its own output
47
71
  // stream (the server further gates an actual PTY pause to raw-eligible
48
72
  // primaries). Same render-control class as reconcile/reflow — read scope.
49
- flow: rule('read', false, 'never'),
73
+ flow: peerControl('read', false, 'never'),
50
74
  // Client-reported render diagnostics (rows-flap attribution probe) — telemetry
51
75
  // into the per-session diagnostics ring; rate-limited + scalar-whitelisted in
52
76
  // handleClientDiag, so viewer (read) scope is sufficient.
@@ -54,7 +78,7 @@ const WS_AUTH_RULES = Object.freeze({
54
78
  'client-theme-report': rule('read', false, 'never'),
55
79
  'client-diag': rule('read', false, 'never'),
56
80
  'client-error-rollup': rule('read', false, 'never'),
57
- nack: rule('read', false, 'never'),
81
+ nack: peerControl('read', false, 'never'),
58
82
  'walle-history-request': rule('read', false, 'never'),
59
83
  'walle-history-reproject': rule('read', false, 'never'),
60
84
  'walle-history-repair-metrics': rule('read', false, 'never'),
@@ -62,37 +86,49 @@ const WS_AUTH_RULES = Object.freeze({
62
86
  // metadata. It cannot write PTY input or persisted session state.
63
87
  'lifecycle-draft': rule('read', false, 'never'),
64
88
 
65
- input: rule('respond', true, 'remote'),
66
- 'interrupt-session': rule('respond', true, 'remote'),
67
- 'submit-prompt': rule('respond', true, 'remote'),
68
- 'resume-prompt-select': rule('respond', true, 'remote'),
89
+ // WP-3b AC-3a: typing in a hub browser reaches the box PTY through these two; the relay
90
+ // forwards them verbatim and the box echoes the result back. respond + stepUp:'remote' for
91
+ // every other caller is unchanged — only the peerControl flag is added.
92
+ input: peerControl('respond', true, 'remote'),
93
+ 'interrupt-session': peerControl('respond', true, 'remote'),
94
+ 'submit-prompt': peerControl('respond', true, 'remote'),
95
+ // WP-1 (docs/remote-resume-choice-click-2026-10-25.html §5 D-2, §9 S-4): the hub forwards the
96
+ // resume strip's choice chip for a MIRROR session, so the peer bearer must reach this row or the
97
+ // box answers with `auth-error insufficient_scope` and the click is lost. FLAG ONLY — the
98
+ // (`respond`, mutation true, step-up 'remote') tuple every other caller sees is unchanged, and the
99
+ // box still runs `_selectResumePromptChoice`'s generation/fingerprint/live-screen validation.
100
+ 'resume-prompt-select': peerControl('respond', true, 'remote'),
69
101
  'revalidate-recovered-prompt': rule('respond', true, 'remote'),
70
102
  'attach-image': rule('respond', true, 'remote'),
71
103
  'attach-image-reference': rule('respond', true, 'remote'),
72
104
  // WP-3 "Deliver now" override on a stalled draft chip -- same class as attach-image (it
73
105
  // triggers the same PTY paste path, just bypassing the reconciler's readiness gate).
74
- 'image-deliver-now': rule('respond', true, 'remote'),
106
+ // WP-1 (docs/remote-session-image-upload-forwarding-2026-10-24.html §6/§7): the hub forwards this
107
+ // verbatim for a mirror session, so the peer bearer must reach it (flag only; scope/mutation/stepUp
108
+ // unchanged). attach-image/attach-image-reference above stay rule(...) — server-side delivery is
109
+ // default ON, so the client never sends them for a box-owned draft.
110
+ 'image-deliver-now': peerControl('respond', true, 'remote'),
75
111
  'walle-message': rule('respond', true, 'remote'),
76
112
  'walle-cancel': rule('respond', true, 'remote'),
77
113
  'walle-permission-response': rule('respond', true, 'remote'),
78
114
  // Resolve a parked protocol-hook approval (allow/deny) — same class as
79
115
  // answering any other approval prompt.
80
- 'approval-resolve': rule('respond', true, 'remote'),
116
+ 'approval-resolve': peerControl('respond', true, 'remote'),
81
117
  // Session lifecycle controls share the same admin boundary as kill/model
82
118
  // changes.
83
119
  'resume-retry': rule('admin', true, 'remote'),
84
- park: rule('admin', true, 'remote'),
120
+ park: peerControl('admin', true, 'remote'),
85
121
  pin: rule('admin', true, 'remote'),
86
- wake: rule('admin', true, 'remote'),
122
+ wake: peerControl('admin', true, 'remote'),
87
123
  prewarm: rule('admin', true, 'remote'),
88
124
 
89
- create: rule('create', true, 'remote'),
125
+ create: peerControl('create', true, 'remote'),
90
126
 
91
- kill: rule('admin', true, 'remote'),
127
+ kill: peerControl('admin', true, 'remote'),
92
128
  rename: rule('admin', true, 'remote'),
93
129
  'update-session-cwd': rule('admin', true, 'remote'),
94
130
  'session-surface-preference': rule('admin', true, 'remote'),
95
- 'model-change': rule('admin', true, 'remote'),
131
+ 'model-change': peerControl('admin', true, 'remote'),
96
132
  });
97
133
 
98
134
  const HTTP_AUTH_RULES = Object.freeze([
@@ -134,21 +170,25 @@ const HTTP_AUTH_RULES = Object.freeze([
134
170
  exact('POST', '/api/remote/messages', 'respond', true, 'never'),
135
171
  regex('POST', /^\/api\/remote\/devices\/[^/]+\/revoke$/, 'admin', true, 'remote', { loopbackOnly: true }),
136
172
  exact('POST', '/api/mobile/attachments/upload', 'respond', true, 'never'),
137
- regex('GET', /^\/api\/sessions\/[^/]+\/image-drafts(?:\/[^/]+\/content)?$/, 'read', false, 'never'),
173
+ // WP-1 (docs/remote-session-image-upload-forwarding-2026-10-24.html §6, D-3): the box-side
174
+ // image-draft family a hub forwards for a MIRROR session. Only the `peerControl` flag is added —
175
+ // scope, mutation and step-up are unchanged for every other caller — exactly like the hub §6.2 rows.
176
+ regex('GET', /^\/api\/sessions\/[^/]+\/image-drafts(?:\/[^/]+\/content)?$/, 'read', false, 'never', { peerControl: true }),
138
177
  regex('GET', /^\/api\/sessions\/[^/]+\/native-subagents\/[^/]+\/messages$/, 'read', false, 'never'),
139
178
  // WP-0 (docs/exited-worker-transcript-view-2026-10-17.html §6c): a routed worker's own read-only
140
179
  // transcript page. Same posture as its native sibling -- GET, read scope, non-mutation, never
141
180
  // step-up. Worker lifecycle (resume/kill/retire) stays on its own stronger rules.
142
181
  regex('GET', /^\/api\/sessions\/[^/]+\/worker-transcript\/messages$/, 'read', false, 'never'),
143
- regex('POST', /^\/api\/sessions\/[^/]+\/image-drafts$/, 'respond', true, 'remote'),
144
- regex('PUT', /^\/api\/sessions\/[^/]+\/image-drafts\/[^/]+$/, 'respond', true, 'remote'),
145
- regex('DELETE', /^\/api\/sessions\/[^/]+\/image-drafts\/[^/]+$/, 'respond', true, 'remote'),
146
- regex('POST', /^\/api\/sessions\/[^/]+\/image-drafts\/[^/]+\/delivery$/, 'respond', true, 'remote'),
147
- regex('POST', /^\/api\/sessions\/[^/]+\/image-drafts\/[^/]+\/freeze$/, 'respond', true, 'remote'),
148
- regex('POST', /^\/api\/sessions\/[^/]+\/image-drafts\/[^/]+\/submit$/, 'respond', true, 'remote'),
182
+ regex('POST', /^\/api\/sessions\/[^/]+\/image-drafts$/, 'respond', true, 'remote', { peerControl: true }),
183
+ regex('PUT', /^\/api\/sessions\/[^/]+\/image-drafts\/[^/]+$/, 'respond', true, 'remote', { peerControl: true }),
184
+ regex('DELETE', /^\/api\/sessions\/[^/]+\/image-drafts$/, 'respond', true, 'remote', { peerControl: true }),
185
+ regex('DELETE', /^\/api\/sessions\/[^/]+\/image-drafts\/[^/]+$/, 'respond', true, 'remote', { peerControl: true }),
186
+ regex('POST', /^\/api\/sessions\/[^/]+\/image-drafts\/[^/]+\/delivery$/, 'respond', true, 'remote', { peerControl: true }),
187
+ regex('POST', /^\/api\/sessions\/[^/]+\/image-drafts\/[^/]+\/freeze$/, 'respond', true, 'remote', { peerControl: true }),
188
+ regex('POST', /^\/api\/sessions\/[^/]+\/image-drafts\/[^/]+\/submit$/, 'respond', true, 'remote', { peerControl: true }),
149
189
  // F2 (image-attach-latency-redesign fix round): retry only flips failed->ready so the
150
190
  // reconciler re-delivers; same mutation class as the other draft-lifecycle routes above.
151
- regex('POST', /^\/api\/sessions\/[^/]+\/image-drafts\/[^/]+\/retry$/, 'respond', true, 'remote'),
191
+ regex('POST', /^\/api\/sessions\/[^/]+\/image-drafts\/[^/]+\/retry$/, 'respond', true, 'remote', { peerControl: true }),
152
192
 
153
193
  exact('POST', '/api/permissions/walle-check', 'admin', true, 'remote', { loopbackOnly: true }),
154
194
  exact('POST', '/api/permissions/check', 'admin', true, 'remote', { loopbackOnly: true }),
@@ -160,6 +200,55 @@ const HTTP_AUTH_RULES = Object.freeze([
160
200
  exact('GET', '/api/permissions/doctor', 'admin', false, 'remote', { loopbackOnly: true }),
161
201
 
162
202
  exact('GET', '/api/setup/network', 'admin', false, 'never'),
203
+ // WP-0 (remote-machine-hub-sessions-2026-10-22.html §6.1): the hub machine registry. Same
204
+ // posture as the setup routes above — the registry decides which boxes this CTM will dial and
205
+ // trusts them with peer-control later (WP-1), so it is an admin act and it is LOCAL-ONLY:
206
+ // loopbackOnly, which (shared/request-provenance.js) already refuses any forwarded/relayed
207
+ // request. Registration is a user action in Settings → Machines (D-11, no flag).
208
+ // WP-2a (machine-pool-picker-pairing-dashboard-2026-10-24.html §5 D-13 rev 2): loopbackOnly is
209
+ // RELAXED. The phone reads GET /api/machines off-LAN (public/m/app.js:13348) and the phone parity
210
+ // contract (no phone-only machine feature) makes the management rows reachable too. The rows carry
211
+ // URLs/hostnames and a credential CLASS WORD, never a token (peerCredential: 'pool'|'env'|'missing').
212
+ // GET /api/machines read, no step-up, NOT loopbackOnly
213
+ // POST /api/machines admin + step-up remote, NOT loopbackOnly
214
+ // POST /api/machines/:id/remove admin + step-up remote, NOT loopbackOnly
215
+ exact('GET', '/api/machines', 'read', false, 'never'),
216
+ exact('POST', '/api/machines', 'admin', true, 'remote'),
217
+ regex('POST', /^\/api\/machines\/[^/]+\/remove$/, 'admin', true, 'remote'),
218
+ // WP-3 (machine-pool-picker-pairing-dashboard-2026-10-24.html §6.3, D-13, §9 S-16): the MEMBER
219
+ // side of pairing — this machine joins a pool by redeeming the box's code. It mints nothing and
220
+ // reads nothing back, but it adopts a shared secret and rewrites this member's roster, so it is
221
+ // the same act as POST /api/machines: admin + step-up remote, NOT loopbackOnly, and NOT
222
+ // peerControl (a peer holds the pool bearer already and never needs to pair over it — pinning it
223
+ // false is what keeps tests/peer-control-auth.test.js's exact set honest).
224
+ exact('POST', '/api/machines/pair', 'admin', true, 'remote'),
225
+ // WP-3 §6.3/D-13: the BOX side's offer mint. Admin + step-up remote for the same reason: the
226
+ // response carries a live bearer code for one redemption, so it is a credential-minting act.
227
+ // POST /api/machine-pairing/join is deliberately ABSENT here — server.js dispatches phase
228
+ // 'api-cloudflare' before the auth wall (lib/api/machine-pairing.js states why), so a row would be
229
+ // a rule the dispatcher never consults.
230
+ exact('POST', '/api/machine-pairing/offers', 'admin', true, 'remote'),
231
+ // WP-2a D-4/D-13: this machine's OWN settings (name override, favorite, hidden). A user on any
232
+ // primary edits a REMOTE member through the ?machine= proxy, so the row must also admit the peer
233
+ // bearer — peerControl: true. Guest/user scope is `read`/never on GET (nothing here is a secret)
234
+ // and admin + step-up remote on POST. lib/api/machine-pool.js refuses a non-peer on the pool rows.
235
+ exact('GET', '/api/machine-self', 'read', false, 'never', { peerControl: true }),
236
+ exact('POST', '/api/machine-self', 'admin', true, 'remote', { peerControl: true }),
237
+ // WP-2a D-13/S-27: the pool routes. There is no dedicated "peer-only" rule mechanism in this
238
+ // codebase (peerControl ADDS the peer bearer to the row's normal reach), so the roster/rotate rows
239
+ // are declared `admin` + peerControl and the HANDLER refuses a non-peer principal with 403
240
+ // `peer_only` (lib/api/machine-pool.js) — the same reviewer-visible pattern as the machine-self
241
+ // rows. POST /api/pool/leave is an ordinary admin + step-up remote act.
242
+ exact('GET', '/api/pool/roster', 'admin', false, 'never', { peerControl: true }),
243
+ exact('POST', '/api/pool/roster', 'admin', true, 'remote', { peerControl: true }),
244
+ exact('POST', '/api/pool/rotate', 'admin', true, 'remote', { peerControl: true }),
245
+ exact('POST', '/api/pool/leave', 'admin', true, 'remote'),
246
+ // WP-1 (machine-pool-picker-pairing-dashboard-2026-10-24.html §6.1/D-13): this machine's own host
247
+ // stats. Read scope, and deliberately NOT loopbackOnly — the pool picker on a phone reaches a
248
+ // member through the hub's ?machine= proxy, which arrives as a non-loopback peer-control request,
249
+ // and the payload is hardware counters, not secrets. peerControl: true is the one reach delta this
250
+ // WP adds (tests/peer-control-auth.test.js pins it).
251
+ exact('GET', '/api/host/stats', 'read', false, 'never', { peerControl: true }),
163
252
  exact('GET', '/api/setup/network/microsoft-dev-tunnel/probe', 'admin', false, 'never'),
164
253
  // Go-Live wizard read view (design doc §Bootstrap & Auth): same posture as
165
254
  // GET /api/setup/network — admin scope, no step-up, remote-readable. The
@@ -201,16 +290,23 @@ const HTTP_AUTH_RULES = Object.freeze([
201
290
  regex('POST', /^\/api\/models\/workflows\/[^/]+\/runs$/, 'admin', true, 'remote'),
202
291
  regex('POST', /^\/api\/models\/workflow-runs\/[^/]+\/(?:abort|skip|retry|decide|rate)$/, 'admin', true, 'remote'),
203
292
  regex('POST', /^\/api\/models\/workflow-suggestions\/[^/]+$/, 'admin', true, 'remote'),
204
- exact('GET', '/api/providers/login-state', 'read', false, 'never'),
205
-
206
- exact('GET', '/api/projects', 'read', false, 'never'),
207
- exact('GET', '/api/path-suggest', 'read', false, 'never'),
208
- exact('POST', '/api/path-status', 'read', false, 'never'),
293
+ // WP-3 (DGX Spark coding readiness §2): the hub reads a BOX's sign-in truth with the peer bearer
294
+ // before it forwards a create — a box whose Claude/Codex CLI is signed out must be refused at the
295
+ // hub, not answered with a login prompt. `peerControl: true` is the only change: plain `read`/never.
296
+ exact('GET', '/api/providers/login-state', 'read', false, 'never', { peerControl: true }),
297
+
298
+ // WP-1 (§6.2/§6.5): the hub's remote picker calls these three on the BOX, over the relay
299
+ // leg, with the peer bearer — hence `peerControl: true`. Scope/step-up are unchanged: they
300
+ // were already plain `read`/never for every other caller.
301
+ exact('GET', '/api/projects', 'read', false, 'never', { peerControl: true }),
302
+ exact('GET', '/api/path-suggest', 'read', false, 'never', { peerControl: true }),
303
+ exact('POST', '/api/path-status', 'read', false, 'never', { peerControl: true }),
209
304
  exact('GET', '/api/rules', 'read', false, 'never'),
210
305
  exact('GET', '/api/rules/list', 'read', false, 'never'),
211
306
  exact('PUT', '/api/rules', 'admin', true, 'remote'),
212
307
 
213
- exact('GET', '/api/recent-sessions', 'read', false, 'never'),
308
+ // WP-1 (§6.2): the hub's catalog read for a mirrored box session.
309
+ exact('GET', '/api/recent-sessions', 'read', false, 'never', { peerControl: true }),
214
310
  exact('GET', '/api/sessions/standup', 'read', false, 'never'),
215
311
  exact('GET', '/api/sessions/git-status', 'read', false, 'never'),
216
312
  exact('GET', '/api/session/prompts', 'read', false, 'never'),
@@ -243,6 +339,16 @@ const HTTP_AUTH_RULES = Object.freeze([
243
339
  regex('POST', /^\/api\/sessions\/[^/]+\/attention-dismiss$/, 'respond', true, 'never'),
244
340
  regex('POST', /^\/api\/sessions\/[^/]+\/submit-prompt$/, 'respond', true, 'never'),
245
341
  regex('POST', /^\/api\/sessions\/[^/]+\/interrupt$/, 'respond', true, 'remote'),
342
+ // WP-1 (docs/remote-machine-hub-sessions-2026-10-22.html §6.2): the box-side lifecycle verbs the
343
+ // hub forwards verbatim for a session the BOX owns (WP-6 added pin: the hub's HTTP lifecycle route
344
+ // forwards all four, and a pin the box refused would surface as a bare 403). Declared explicitly (rather than flagging the
345
+ // /^\/api\/sessions\// catch-all below, which would hand the peer bearer every unmatched session
346
+ // POST) with the catch-all's own admin/step-up posture, plus peerControl.
347
+ regex('POST', /^\/api\/sessions\/[^/]+\/(?:park|wake|revive|pin)$/, 'admin', true, 'remote', { peerControl: true }),
348
+ // WP-1 (§6.2, hub WP-2/§E): where this session's provider transcripts live, read by the hub for a
349
+ // mirrored box session. Previously had NO rule (loopback-only in practice) — the explicit read row
350
+ // is what makes it reachable for the peer bearer; scope read / no step-up / no mutation.
351
+ regex('GET', /^\/api\/sessions\/[^/]+\/transcripts$/, 'read', false, 'never', { peerControl: true }),
246
352
  // Lead-model switch (picker design WP-2): same class as the WS 'model-change' it replaces for
247
353
  // Claude/Codex tabs (admin + step-up); reading the request state is a plain read.
248
354
  regex('POST', /^\/api\/sessions\/[^/]+\/lead-model$/, 'admin', true, 'remote'),
@@ -272,7 +378,15 @@ const HTTP_AUTH_RULES = Object.freeze([
272
378
  exact('POST', '/api/instance/update/apply', 'admin', true, 'remote'),
273
379
  exact('GET', '/api/instance/migrate/export', 'admin', false, 'never'),
274
380
  exact('POST', '/api/instance/migrate/import', 'admin', true, 'remote'),
275
- regex('POST', /^\/api\/providers\/[^/]+\/login$/, 'admin', true, 'remote'),
381
+ // WP-3 (remote-agent-sign-in-from-hub-browser-2026-10-25.html §5 D-7/D-9, §6): the hub forwards
382
+ // a sign-in to the BOX that must run it, over the peer bearer — so both POSTs also declare
383
+ // `peerControl: true`. Scope/mutation/step-up are unchanged: a peer is still evaluated as
384
+ // `admin` + remote step-up, and every non-peer caller sees exactly what it saw before.
385
+ regex('POST', /^\/api\/providers\/[^/]+\/login$/, 'admin', true, 'remote', { peerControl: true }),
386
+ // WP-1 (design §6, S-8): the code-return route is the /login twin — a write on the box
387
+ // (one PTY frame into the flow's own tab), so the same admin scope and the same remote
388
+ // step-up ceremony as starting a sign-in.
389
+ regex('POST', /^\/api\/providers\/[^/]+\/login\/code$/, 'admin', true, 'remote', { peerControl: true }),
276
390
  exact('GET', '/api/sync/outbox', 'sync', false, 'never'),
277
391
  exact('GET', '/api/sync/manifest', 'sync', false, 'never'),
278
392
  exact('POST', '/api/sync/outbox/ack', 'sync', true, 'never'),
@@ -331,10 +445,14 @@ const HTTP_AUTH_RULES = Object.freeze([
331
445
  regex('PUT', /^\/api\/workspaces\//, 'admin', true, 'remote'),
332
446
  regex('DELETE', /^\/api\/workspaces\//, 'admin', true, 'remote'),
333
447
 
334
- exact('GET', '/api/worktrees', 'read', false, 'never'),
335
- exact('POST', '/api/worktrees/create', 'create', true, 'remote'),
336
- regex('GET', /^\/api\/worktrees\/create-jobs\/[a-zA-Z0-9_-]+(?:-[a-zA-Z0-9_-]+)*$/, 'read', false, 'never'),
337
- regex('POST', /^\/api\/worktrees\/create-jobs\/[a-zA-Z0-9_-]+(?:-[a-zA-Z0-9_-]+)*\/cancel$/, 'admin', true, 'remote'),
448
+ // WP-1 (§6.2/§6.5): worktree LIST + CREATE are called on the box by the hub with the peer
449
+ // bearer, so both rows declare peerControl. Their scope/step-up are untouched.
450
+ exact('GET', '/api/worktrees', 'read', false, 'never', { peerControl: true }),
451
+ exact('POST', '/api/worktrees/create', 'create', true, 'remote', { peerControl: true }),
452
+ // Hub WP-5 amendment: an async create on the box returns a BOX job id, so the hub's progress poll
453
+ // and its cancel are proxied there too (lib/remote-machine-proxy.js) — same posture, plus the flag.
454
+ regex('GET', /^\/api\/worktrees\/create-jobs\/[a-zA-Z0-9_-]+(?:-[a-zA-Z0-9_-]+)*$/, 'read', false, 'never', { peerControl: true }),
455
+ regex('POST', /^\/api\/worktrees\/create-jobs\/[a-zA-Z0-9_-]+(?:-[a-zA-Z0-9_-]+)*\/cancel$/, 'admin', true, 'remote', { peerControl: true }),
338
456
  regex('POST', /^\/api\/worktrees\//, 'admin', true, 'remote'),
339
457
  regex('DELETE', /^\/api\/worktrees\//, 'admin', true, 'remote'),
340
458
 
@@ -412,6 +530,14 @@ function rule(scope, mutation, stepUp, extra = {}) {
412
530
  return Object.freeze({ scope, mutation: !!mutation, stepUp, ...extra });
413
531
  }
414
532
 
533
+ // WP-1 (§6.2): the one way a rule row declares that the peer bearer may reach it. Kept as a
534
+ // distinct flag rather than a scope union so `scope` (and everything that reads it — the
535
+ // step-up matrix, the owner manifest, /api/auth/rules consumers) keeps its existing meaning;
536
+ // authorize() consults the flag directly.
537
+ function peerControl(scope, mutation, stepUp) {
538
+ return rule(scope, mutation, stepUp, { peerControl: true });
539
+ }
540
+
415
541
  function exact(method, path, scope, mutation, stepUp, extra = {}) {
416
542
  return Object.freeze({ kind: 'http', method, path, exact: true, ...rule(scope, mutation, stepUp, extra) });
417
543
  }
@@ -437,10 +563,18 @@ function getWsAuthRule(messageType) {
437
563
  function hasScope(auth, requiredScope) {
438
564
  if (!requiredScope) return true;
439
565
  const scopes = auth?.scopes instanceof Set ? auth.scopes : new Set(auth?.scopes || []);
440
- if (requiredScope === 'sync') return scopes.has('sync');
566
+ // Machine-to-machine capabilities are never implied by `admin`: sync keeps its existing
567
+ // special case, and peer-control (WP-1 §6.2) joins it — it is minted only on the peer
568
+ // bearer, so an admin/device token can never acquire it through the admin expansion.
569
+ if (requiredScope === 'sync' || requiredScope === PEER_CONTROL_SCOPE) return scopes.has(requiredScope);
441
570
  return scopes.has('admin') || scopes.has(requiredScope);
442
571
  }
443
572
 
573
+ function hasPeerControlScope(auth) {
574
+ const scopes = auth?.scopes instanceof Set ? auth.scopes : new Set(auth?.scopes || []);
575
+ return scopes.has(PEER_CONTROL_SCOPE);
576
+ }
577
+
444
578
  function authorize(auth, rule, options = {}) {
445
579
  if (!rule) return { ok: false, status: 403, code: 'auth_rule_missing' };
446
580
  if (!auth?.authenticated) return { ok: false, status: 401, code: 'unauthorized' };
@@ -453,6 +587,10 @@ function authorize(auth, rule, options = {}) {
453
587
  if (rule.bootstrapLocal && options.zeroWebauthnCredentials && !auth.isLoopback && !options.isLocalOrTailnet) {
454
588
  return { ok: false, status: 403, code: 'bootstrap_local_required' };
455
589
  }
590
+ // WP-1 (§6.2): a row that declares peerControl is satisfied by the peer-control scope in
591
+ // full — the peer is a machine credential that cannot perform a passkey step-up ceremony,
592
+ // exactly like the sync scope on /api/sync/*. No other rule grants it anything.
593
+ if (rule.peerControl && hasPeerControlScope(auth)) return { ok: true };
456
594
  if (!hasScope(auth, rule.scope)) return { ok: false, status: 403, code: 'insufficient_scope' };
457
595
  if (options.enforceStepUp && !auth.isLoopback && rule.stepUp === 'remote' && !auth.stepUpFresh) {
458
596
  return { ok: false, status: 403, code: 'step_up_required' };
@@ -471,6 +609,7 @@ function publicRule(rule) {
471
609
  stepUp: rule.stepUp,
472
610
  loopbackOnly: !!rule.loopbackOnly,
473
611
  bootstrapLocal: !!rule.bootstrapLocal,
612
+ peerControl: !!rule.peerControl,
474
613
  };
475
614
  }
476
615
 
@@ -484,11 +623,13 @@ function listWsAuthRules() {
484
623
 
485
624
  module.exports = {
486
625
  ALL_SCOPES,
626
+ PEER_CONTROL_SCOPE,
487
627
  HTTP_AUTH_RULES,
488
628
  WS_AUTH_RULES,
489
629
  authorize,
490
630
  getHttpAuthRule,
491
631
  getWsAuthRule,
632
+ hasPeerControlScope,
492
633
  hasScope,
493
634
  listHttpAuthRules,
494
635
  listWsAuthRules,
@@ -145,7 +145,8 @@ function createBroadcastPayloadSerializer({
145
145
  if (options.isMemoBypass?.(session) || !memo) return null;
146
146
  signature += memo.key + '\x1e';
147
147
  }
148
- return signature + '#' + options.sourceSessions.length;
148
+ // extraSignature: rows appended AFTER the local snapshot (the machine hub's remote rows).
149
+ return signature + '#' + options.sourceSessions.length + (options.extraSignature ? '#' + options.extraSignature : '');
149
150
  }
150
151
 
151
152
  function makeRecord(envelope, clients, signature) {
@@ -1189,6 +1189,7 @@ class BrowserSessionBroker {
1189
1189
  if (this.profileMigration?.ensureProfile) record = await this.profileMigration.ensureProfile(record);
1190
1190
  const existing = await this._boundHandle(record, { required: false });
1191
1191
  if (existing && (typeof this.adapter.isLive !== 'function' || this.adapter.isLive(existing))) return existing;
1192
+ if (existing) await this._dropDeadHandle(record, existing);
1192
1193
  let handle;
1193
1194
  try {
1194
1195
  handle = await this.adapter.open({ contextId: record.contextId, profileDir: record.profileDir,
@@ -1210,7 +1211,7 @@ class BrowserSessionBroker {
1210
1211
  record = await this._ensureProcessOwnership(record, { allowTakeover: true });
1211
1212
  const existing = await this._boundHandle(record, { required: false });
1212
1213
  if (existing && (typeof this.adapter.isLive !== 'function' || this.adapter.isLive(existing))) return record;
1213
- if (existing) this.handles.delete(record.contextId);
1214
+ if (existing) await this._dropDeadHandle(record, existing);
1214
1215
  let recovered = record;
1215
1216
  if (record.processGeneration !== this.processGeneration || existing || record.status === 'dormant') {
1216
1217
  recovered = {
@@ -1292,6 +1293,13 @@ class BrowserSessionBroker {
1292
1293
  return handle;
1293
1294
  }
1294
1295
 
1296
+ // A handle that is no longer live (lost page, disconnected browser) still owns whatever of its
1297
+ // browser survives; close it rather than just forgetting it.
1298
+ async _dropDeadHandle(record, handle) {
1299
+ if (this.handles.get(record.contextId) === handle) this.handles.delete(record.contextId);
1300
+ await this.adapter.close(handle).catch((error) => this._reportFailure('dead-handle-close', error));
1301
+ }
1302
+
1295
1303
  _rebindLocalHandle(record) {
1296
1304
  const handle = this.handles.get(record.contextId);
1297
1305
  if (!handle) return;
@@ -31,6 +31,9 @@ function chromiumChildEnvironment(parentEnvironment = process.env) {
31
31
  return environment;
32
32
  }
33
33
 
34
+ // Chromium ChildProcesses whose group has been reaped, so the exit and close paths reap it once.
35
+ const REAPED = new WeakSet();
36
+
34
37
  class BrowserUseAdapter {
35
38
  constructor({
36
39
  puppeteer = null,
@@ -84,6 +87,11 @@ class BrowserUseAdapter {
84
87
  allowedOrigins: new Set([allowedOrigin, ...allowedOrigins].filter(Boolean)),
85
88
  blockedOrigin: '', disconnected: false, lastMainResponseStatus: 0 };
86
89
  browser.once('disconnected', () => { handle.disconnected = true; });
90
+ // A browser lost without close() (crash, external kill) leaves the same helpers behind. Reap on
91
+ // the main process's exit, never on 'disconnected': the CDP socket drops while Chromium is still
92
+ // shutting down, and killing its group then loses storage it has not flushed yet.
93
+ const child = typeof browser.process === 'function' ? browser.process() : null;
94
+ child?.once?.('exit', () => this._reapBrowserProcess(child, 'browser-exit'));
87
95
  page.on('response', (response) => {
88
96
  try {
89
97
  const request = response.request();
@@ -99,7 +107,7 @@ class BrowserUseAdapter {
99
107
  handle.documentGeneration = await this._documentGeneration(handle);
100
108
  return handle;
101
109
  } catch (error) {
102
- await browser.close().catch((closeError) => this._reportFailure('open-cleanup', closeError));
110
+ await this._closeBrowser(browser, 'open-cleanup');
103
111
  throw error;
104
112
  }
105
113
  }
@@ -628,10 +636,33 @@ class BrowserUseAdapter {
628
636
 
629
637
  async close(handle) {
630
638
  if (!handle?.browser) return;
631
- await handle.browser.close().catch((error) => this._reportFailure('browser-close', error));
639
+ await this._closeBrowser(handle.browser, 'browser-close');
632
640
  handle.disconnected = true;
633
641
  }
634
642
 
643
+ // browser.close() returns once Chromium's browser process exits, but its renderer/GPU helpers
644
+ // can outlive it (puppeteer launches Chromium detached, so they share its process group), and
645
+ // its crashpad handler daemonizes into a group of its own still holding the stdio Chromium
646
+ // inherited. Either one keeps this process's end of that stdio open: orphaned helpers pile up
647
+ // and a Node process that should exit never does. Reap the group, then drop our end.
648
+ async _closeBrowser(browser, operation) {
649
+ const child = typeof browser.process === 'function' ? browser.process() : null;
650
+ await browser.close().catch((error) => this._reportFailure(operation, error));
651
+ this._reapBrowserProcess(child, operation);
652
+ }
653
+
654
+ _reapBrowserProcess(child, operation) {
655
+ if (!child || REAPED.has(child)) return;
656
+ REAPED.add(child);
657
+ if (Number.isSafeInteger(child.pid) && child.pid > 0) {
658
+ // ESRCH: the group is already empty. EPERM: macOS refuses a group whose members are all zombies
659
+ // (every member here is this user's Chromium, so it is never a foreign process).
660
+ try { process.kill(-child.pid, 'SIGKILL'); }
661
+ catch (error) { if (error?.code !== 'ESRCH' && error?.code !== 'EPERM') this._reportFailure(`${operation}-group`, error); }
662
+ }
663
+ for (const stream of child?.stdio || []) stream?.destroy?.();
664
+ }
665
+
635
666
  _reportFailure(operation, error) {
636
667
  const kind = String(error?.code || error?.name || 'unknown').slice(0, 128);
637
668
  this.logger?.warn?.(`[browser-use-adapter] ${operation} failed (${kind})`);