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
@@ -0,0 +1,520 @@
1
+ 'use strict';
2
+
3
+ // WP-4 (docs/remote-machine-hub-sessions-2026-10-22.html §6.5, §7, §8 AC-4a/AC-4c, §9 S-9/S-10):
4
+ // hub-side proxy for the four picker/create routes, plus the same-repo match helper.
5
+ //
6
+ // The picker asks the HUB for projects/suggestions/status, but when a machine is selected the
7
+ // answers must come from the BOX — the box's filesystem is the truth about where a session on the
8
+ // box can start. So each of the four routes gains one optional routing parameter:
9
+ //
10
+ // ?machine=<instance_id> or X-CTM-Machine: <instance_id>
11
+ //
12
+ // Routing never reads the body: a POST that is not machine-routed reaches the local handler with
13
+ // its request stream untouched.
14
+ //
15
+ // The single entry point is `maybeProxyMachineRequest(req, res, url, { route })`. It returns
16
+ // true — it fully answered the request (proxied, or a machine_unknown / machine_unreachable /
17
+ // peer_credential_missing / machine_source_mixed refusal)
18
+ // false — nothing was decided here; the caller MUST fall through to the local handler
19
+ //
20
+ // The rules that drive the shape:
21
+ // AC-4a an unknown machine is a 404 `machine_unknown`; an unreachable one is a 502
22
+ // `machine_unreachable` whose body NAMES the machine (`machineName`), because the copy
23
+ // the picker renders is the only thing the user sees.
24
+ // D-7 the ONLY credential is the peer bearer (`buildPeerControlHeaders`, imported from
25
+ // lib/remote-machine-create.js). No new env var, no new secret, no new token mint.
26
+ // D-1/D-2 the bearer is mandatory on EVERY leg, including a loopback/LAN or SSH-forwarded box:
27
+ // no call is ever sent bare, and a credential the hub does not have is a 503
28
+ // `peer_credential_missing` (never `machine_unreachable`) whose copy names the env var
29
+ // and the machine. Loopback position is not a credential.
30
+ // local `machine` absent, or `machine` == this instance's own minted id, is BIT-IDENTICAL to
31
+ // today: the hook returns false without touching req or res and the local handler
32
+ // answers. No client/public change is involved (WP-5 owns the UI).
33
+ //
34
+ // Dependency note: this module must NOT require lib/hosted-sync-worker.js (dependency-cycle lint —
35
+ // machine-registry's header explains it). The peer credential comes from
36
+ // lib/remote-machine-create.js, which builds it from env itself; the machine list comes from
37
+ // lib/machine-registry.js through the same db-owner Proxy lib/api/machines.js uses.
38
+
39
+ const { normalizeGitRemote } = require('../../shared/git-remote-key');
40
+
41
+ const MACHINE_HEADER = 'x-ctm-machine';
42
+ const MACHINE_QUERY_PARAM = 'machine';
43
+ const DEFAULT_TIMEOUT_MS = 10_000;
44
+ const BODY_LIMIT_BYTES = 1024 * 1024;
45
+
46
+ // The four routes this WP proxies (§9 S-9/S-10). Keyed `METHOD /pathname`, the same spelling the
47
+ // lead passes as `{ route }`.
48
+ const PROXIED_ROUTES = Object.freeze([
49
+ 'GET /api/projects',
50
+ 'GET /api/path-suggest',
51
+ 'POST /api/path-status',
52
+ 'POST /api/worktrees/create',
53
+ // WP-5 amendment: the async create's progress poll + cancel live on the box that owns the job.
54
+ // The wrapper (lib/api/worktrees.js) names these templated keys; the REAL path is forwarded.
55
+ 'GET /api/worktrees/create-jobs/:id',
56
+ 'POST /api/worktrees/create-jobs/:id/cancel',
57
+ // WP-1 (machine-pool-picker-pairing-dashboard-2026-10-24.html §6.1 / S-5): host stats are read
58
+ // per machine by the picker/dashboard, over the same ?machine= + peer-bearer leg as the rows
59
+ // above. The box's response is self-describing (`instanceId`), so nothing hub-specific is added.
60
+ 'GET /api/host/stats',
61
+ // WP-2a (machine-pool-picker-pairing-dashboard-2026-10-24.html §6.2, D-4 rev 2): the machine's
62
+ // OWN settings (display name / favorite / hidden). A user on any primary edits a remote member
63
+ // through the existing ?machine= + peer-bearer leg, exactly like the routes above — the member
64
+ // sees the pool principal. Both verbs are proxied (D-13: GET read + POST admin, peerControl).
65
+ 'GET /api/machine-self',
66
+ 'POST /api/machine-self',
67
+ // WP-3 (remote-agent-sign-in-from-hub-browser-2026-10-25.html §5 D-7/D-9, §6, S-11..S-14): the
68
+ // coding-agent OAuth sign-in trio, so a hub page (desktop or phone) can read a BOX's sign-in
69
+ // truth and start/complete a sign-in ON that box without opening the box's own URL. The two
70
+ // POSTs are admin + remote step-up + peerControl (lib/auth-rules.js); the GET is the plain read
71
+ // it already was. The templated POST keys use the same `:id` spelling as the create-job rows.
72
+ 'GET /api/providers/login-state',
73
+ 'POST /api/providers/:id/login',
74
+ 'POST /api/providers/:id/login/code',
75
+ ]);
76
+
77
+ // S-10 mixed-request refusal: a create that names a REMOTE machine must carry the BOX-owned source
78
+ // repo in `cwd`. These are the hub-local worktree identities (WP-5's picker would have produced
79
+ // them locally) — present alongside a remote machine they disagree about WHERE the worktree lives.
80
+ const LOCAL_SOURCE_FIELDS = Object.freeze(['localWorktreePath', 'localPath', 'hubCwd', 'sourceMachine']);
81
+
82
+ // AC-4c recency: the /api/projects row fields that order two checkouts of the SAME repo. Today's
83
+ // rows carry none of them (server.js apiGetProjects returns {id, path, exists}); the list is here
84
+ // so a row that gains one is preferred without touching this module again, and the array-order
85
+ // fallback (first row wins) is what a keyless row set gets.
86
+ const RECENCY_FIELDS = Object.freeze([
87
+ 'lastUsedAt', 'lastActivityAt', 'updatedAt', 'mtimeMs', 'mtime', 'lastSeenAt', 'createdAt',
88
+ ]);
89
+
90
+ let _injected = null;
91
+
92
+ /** Test/host seam: override any production dependency (registry, db, env, fetch, headers, …). */
93
+ function configure(deps) {
94
+ _injected = deps && typeof deps === 'object' ? deps : null;
95
+ }
96
+
97
+ function _string(value, max = 1000) {
98
+ return String(value == null ? '' : value).trim().slice(0, max);
99
+ }
100
+
101
+ function _trimUrl(url) {
102
+ return _string(url, 1000).replace(/\/+$/, '');
103
+ }
104
+
105
+ /**
106
+ * WP-1 (D-2): the missing-credential verdict for both lanes this module owns. The code and the
107
+ * copy live in lib/remote-machine-create.js — the ONE module that owns the peer credential — and
108
+ * are required lazily for the same reason the header builder is (this module must never pull
109
+ * lib/hosted-sync-worker.js into its graph; see the dependency note at the top).
110
+ *
111
+ * @returns {{code: string, error: string}}
112
+ */
113
+ function _peerCredentialMissing(machineName) {
114
+ const mod = require('./remote-machine-create');
115
+ return { code: mod.PEER_CREDENTIAL_MISSING, error: mod.peerCredentialMissingMessage(machineName) };
116
+ }
117
+
118
+ // Lazy: the pure helpers (matchBoxProject, normalizeGitRemote) and the unit test must never load
119
+ // db.js / the owner client. Mirrors lib/remote-machine-create.js's seam.
120
+ function _productionDeps() {
121
+ return {
122
+ env: process.env,
123
+ // The same handle server.js hands lib/api/machines.js: the DB-owner Proxy, so the machine
124
+ // list read here is the one the routes read.
125
+ db: require('../db-owner-client'),
126
+ registry: require('./machine-registry'),
127
+ };
128
+ }
129
+
130
+ function _deps(overrides) {
131
+ return Object.assign({}, _productionDeps(), _injected || {}, overrides || {});
132
+ }
133
+
134
+ // ---------------------------------------------------------------------------------------------
135
+ // Pure helpers (exported — AC-4c's unit cases live with the normalization table)
136
+ // ---------------------------------------------------------------------------------------------
137
+
138
+ /**
139
+ * AC-4c: the box path whose checkout is the SAME repo as a hub project.
140
+ *
141
+ * Exact key match on `gitRemoteKey`, never a prefix/substring match (two different repos under
142
+ * one owner must not collide). Several checkouts of one repo are legal — the picker then opens
143
+ * the most recently used one, per RECENCY_FIELDS, falling back to array order so the result is
144
+ * deterministic for a row set that carries no timestamps at all.
145
+ *
146
+ * @param {string} localProjectKey normalizeGitRemote(...) of the hub-side project
147
+ * @param {Array<{gitRemoteKey?: string, path?: string}>} boxProjects the BOX's /api/projects rows
148
+ * @returns {{path: string, project: object}|null}
149
+ */
150
+ function matchBoxProject(localProjectKey, boxProjects) {
151
+ const key = _string(localProjectKey, 1000);
152
+ if (!key || !Array.isArray(boxProjects)) return null;
153
+ let best = null;
154
+ let bestScore = null;
155
+ for (let index = 0; index < boxProjects.length; index += 1) {
156
+ const row = boxProjects[index];
157
+ if (!row || typeof row !== 'object') continue;
158
+ if (_string(row.gitRemoteKey, 1000) !== key) continue;
159
+ const score = _recencyScore(row, index);
160
+ if (!best || score > bestScore) {
161
+ best = row;
162
+ bestScore = score;
163
+ }
164
+ }
165
+ if (!best) return null;
166
+ return { path: _string(best.path, 4000), project: best };
167
+ }
168
+
169
+ function _recencyScore(row, index) {
170
+ for (const field of RECENCY_FIELDS) {
171
+ const raw = row[field];
172
+ if (raw == null || raw === '') continue;
173
+ const numeric = typeof raw === 'number' ? raw : Date.parse(String(raw));
174
+ if (Number.isFinite(numeric)) return numeric;
175
+ }
176
+ // No timestamp anywhere: the earliest row wins, so the same input always yields the same path.
177
+ // (`index ? -index : 0` rather than `-index`: -0 is never what a score should compare as.)
178
+ return index ? -index : 0;
179
+ }
180
+
181
+ /**
182
+ * The routing machine id, or '' when this request is not machine-routed at all.
183
+ * Query/header are consulted first so the common GET path never touches the request body.
184
+ */
185
+ function _machineFromRequest(req, url) {
186
+ let fromQuery = '';
187
+ try { fromQuery = _string(url && url.searchParams ? url.searchParams.get(MACHINE_QUERY_PARAM) : '', 160); } catch { /* expected: a url-like without searchParams */ }
188
+ if (fromQuery) return fromQuery;
189
+ const headers = (req && req.headers) || {};
190
+ return _string(headers[MACHINE_HEADER] || headers['X-CTM-Machine'] || '', 160);
191
+ }
192
+
193
+ // The process's own MINTED instance id (D-3: never hostname, env tag or URL) — the same source
194
+ // lib/remote-machine-create.js uses for its local-vs-remote decision.
195
+ /** Synchronous pre-check for route wiring: does this request name a machine at all? */
196
+ function isMachineRouted(req, url) {
197
+ return !!_machineFromRequest(req, _resolveUrl(req, url));
198
+ }
199
+
200
+ function _localInstanceId(deps) {
201
+ if (typeof deps.localInstanceId === 'string') return _string(deps.localInstanceId, 160);
202
+ try {
203
+ const identity = require('./instance-identity-access').getInstanceIdentity({ required: false });
204
+ return _string(identity && identity.instanceId, 160);
205
+ } catch {
206
+ return ''; /* expected: identity not minted yet; an unknown local id never matches a box row */
207
+ }
208
+ }
209
+
210
+ function _resolveUrl(req, url) {
211
+ if (url && typeof url.searchParams !== 'undefined') return url;
212
+ try { return new URL((req && req.url) || '/', 'http://localhost'); } catch { return new URL('/', 'http://localhost'); }
213
+ }
214
+
215
+ function _routeKey(req, url, options) {
216
+ if (options && options.route) return String(options.route);
217
+ const method = String((req && req.method) || 'GET').toUpperCase();
218
+ return `${method} ${url.pathname}`;
219
+ }
220
+
221
+ function _sendJson(res, status, payload) {
222
+ const body = JSON.stringify(payload);
223
+ if (res.headersSent) return true;
224
+ res.writeHead(status, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) });
225
+ res.end(body);
226
+ return true;
227
+ }
228
+
229
+ // ---------------------------------------------------------------------------------------------
230
+ // Request-body handling
231
+ // ---------------------------------------------------------------------------------------------
232
+
233
+ /** Read the whole request body (only ever called once the request is known to be machine-routed). */
234
+ function _readRawBody(req) {
235
+ return new Promise((resolve, reject) => {
236
+ let raw = '';
237
+ let size = 0;
238
+ let done = false;
239
+ req.on('data', (chunk) => {
240
+ if (done) return;
241
+ size += chunk.length;
242
+ if (size > BODY_LIMIT_BYTES) {
243
+ done = true;
244
+ reject(new Error('request_body_too_large'));
245
+ return;
246
+ }
247
+ raw += chunk;
248
+ });
249
+ req.on('end', () => { if (!done) { done = true; resolve(raw); } });
250
+ req.on('error', (error) => { if (!done) { done = true; reject(error); } });
251
+ });
252
+ }
253
+
254
+ // ---------------------------------------------------------------------------------------------
255
+ // The hook
256
+ // ---------------------------------------------------------------------------------------------
257
+
258
+ /**
259
+ * §6.5 entry point. Answers the request itself when it names a machine; otherwise defers.
260
+ *
261
+ * @param {import('http').IncomingMessage} req
262
+ * @param {import('http').ServerResponse} res
263
+ * @param {URL|null} url parsed request url; derived from req.url when null/omitted
264
+ * @param {{route?: string}} [options] `'METHOD /pathname'`; derived from req when omitted
265
+ * @returns {Promise<boolean>} true = answered (do NOT run the local handler)
266
+ */
267
+ async function maybeProxyMachineRequest(req, res, url, options = {}) {
268
+ const deps = _deps(options.deps);
269
+ const parsed = _resolveUrl(req, url);
270
+ const route = _routeKey(req, parsed, options);
271
+ if (!PROXIED_ROUTES.includes(route)) return false;
272
+
273
+ const isPost = route.startsWith('POST ');
274
+ const machine = _machineFromRequest(req, parsed);
275
+ if (!machine) return false;
276
+ let rawBody = null;
277
+ let parsedBody = null;
278
+
279
+ // This instance IS the named machine (§6.5) -> the local handler is the box handler. Checked
280
+ // BEFORE the registry lookup because a hub does not list ITSELF among its boxes: `machine=<my own
281
+ // id>` is the "I am the box" spelling, not an unknown machine.
282
+ const localId = _localInstanceId(deps);
283
+ if (localId && machine === localId) return false;
284
+
285
+ // P-3 (rev 2) one-hop guard. Every member-to-member request carries X-CTM-Forwarded-By. A request
286
+ // that carries it AND names a machine that is NOT this instance is asking us to forward an
287
+ // already-forwarded request to a third machine — the A→B→C chain / A→B→A loop P-3 forbids.
288
+ // Nothing is dialled. A request naming no machine, or naming THIS instance, is unchanged above:
289
+ // the browser's own direct request (no header) and a member's request to run locally both pass.
290
+ const forwardedBy = require('./remote-machine-create').forwardedByOf(req);
291
+ if (forwardedBy) {
292
+ return _sendJson(res, 400, {
293
+ ok: false,
294
+ code: 'machine_forward_loop',
295
+ error: `This request was already forwarded by ${forwardedBy}; a forwarded request is never forwarded again`,
296
+ machine,
297
+ forwardedBy,
298
+ });
299
+ }
300
+
301
+ const registry = deps.registry || require('./machine-registry');
302
+ const { machines } = registry.listMachines({ db: deps.db });
303
+ const target = (machines || []).find((row) => row.instanceId === machine);
304
+ if (!target) {
305
+ return _sendJson(res, 404, { ok: false, code: 'machine_unknown', error: `Unknown machine: ${machine}`, machine });
306
+ }
307
+
308
+ const machineName = _string(target.displayName, 200) || machine;
309
+ const unreachable = (extra = {}) => _sendJson(res, 502, {
310
+ ok: false,
311
+ code: 'machine_unreachable',
312
+ error: `Machine ${machineName} is unreachable`,
313
+ machine,
314
+ machineName,
315
+ ...extra,
316
+ });
317
+
318
+ // S-10: a create that mixes a hub-local worktree source with a remote machine is refused rather
319
+ // than silently creating a worktree on the hub (whose path the caller would then hand to the box).
320
+ if (route === 'POST /api/worktrees/create') {
321
+ if (parsedBody == null) {
322
+ if (rawBody == null) {
323
+ try { rawBody = await _readRawBody(req); } catch { rawBody = ''; }
324
+ }
325
+ try { parsedBody = rawBody.trim() ? JSON.parse(rawBody) : {}; } catch { parsedBody = null; }
326
+ }
327
+ if (!parsedBody || typeof parsedBody !== 'object') {
328
+ return _sendJson(res, 400, { ok: false, code: 'invalid_json', error: 'Invalid JSON body' });
329
+ }
330
+ const mixed = LOCAL_SOURCE_FIELDS.filter((field) => {
331
+ const value = parsedBody[field];
332
+ if (value == null || value === '' || value === false) return false;
333
+ if (field === 'sourceMachine') return _string(value, 160) !== machine;
334
+ return true;
335
+ });
336
+ if (mixed.length) {
337
+ return _sendJson(res, 400, {
338
+ ok: false,
339
+ code: 'machine_source_mixed',
340
+ error: `Worktree create names machine ${machineName} and a local worktree source (${mixed.join(', ')}); a remote worktree must be created on the machine`,
341
+ machine,
342
+ machineName,
343
+ mixedFields: mixed,
344
+ });
345
+ }
346
+ }
347
+
348
+ const baseUrl = _trimUrl(target.url);
349
+ if (!baseUrl) return unreachable({ error: `Machine ${machineName} has no URL` });
350
+
351
+ const buildHeaders = deps.buildPeerControlHeaders
352
+ || require('./remote-machine-create').buildPeerControlHeaders;
353
+ // P-8: the bearer is chosen per TARGET — `machine` is the box this request is being proxied to,
354
+ // so a legacy env-token box that never joined this hub's pool is dialled with the env bearer.
355
+ const peerHeaders = buildHeaders(deps.env || process.env, { instanceId: machine });
356
+ // WP-1 (D-1/D-2): the bearer is mandatory on every leg, so a missing one is answered HERE, with
357
+ // its own code and status, and no request is ever sent bare. `unreachable` keeps meaning exactly
358
+ // what it says: the box was dialled and did not answer.
359
+ if (!peerHeaders) {
360
+ const missing = _peerCredentialMissing(machineName);
361
+ return _sendJson(res, 503, {
362
+ ok: false,
363
+ code: missing.code,
364
+ error: missing.error,
365
+ machine,
366
+ machineName,
367
+ });
368
+ }
369
+
370
+ // Strip the routing field from what the box sees: `machine` is the hub's protocol, not the
371
+ // box's, and the box's own handlers must stay byte-identical to the pre-WP-4 ones.
372
+ const forwardUrl = new URL(parsed.toString());
373
+ forwardUrl.searchParams.delete(MACHINE_QUERY_PARAM);
374
+ const forwardTarget = `${baseUrl}${forwardUrl.pathname}${forwardUrl.search}`;
375
+
376
+ const fetchImpl = deps.fetchImpl || deps.fetch || globalThis.fetch;
377
+ if (typeof fetchImpl !== 'function') return unreachable({ error: 'fetch_unavailable' });
378
+
379
+ const timeoutMs = Number(deps.timeoutMs || DEFAULT_TIMEOUT_MS) || DEFAULT_TIMEOUT_MS;
380
+ const timeoutFn = typeof deps.setTimeoutFn === 'function' ? deps.setTimeoutFn : setTimeout;
381
+ const clearFn = typeof deps.clearTimeoutFn === 'function' ? deps.clearTimeoutFn : clearTimeout;
382
+ const controller = typeof AbortController === 'function' ? new AbortController() : null;
383
+ let timer = null;
384
+ const timeout = new Promise((resolve) => {
385
+ timer = timeoutFn(() => {
386
+ try { if (controller) controller.abort(); } catch { /* expected: abort on a settled fetch */ }
387
+ resolve({ __timeout: true });
388
+ }, timeoutMs);
389
+ if (timer && typeof timer.unref === 'function') timer.unref();
390
+ });
391
+
392
+ let payload;
393
+ if (isPost) {
394
+ if (rawBody == null) {
395
+ try { rawBody = await _readRawBody(req); } catch { rawBody = ''; }
396
+ }
397
+ let outBody = {};
398
+ try { outBody = rawBody.trim() ? JSON.parse(rawBody) : {}; } catch { outBody = {}; }
399
+ if (outBody && typeof outBody === 'object') delete outBody.machine;
400
+ payload = JSON.stringify(outBody);
401
+ }
402
+
403
+ const requestOptions = {
404
+ method: String(req.method || 'GET').toUpperCase(),
405
+ headers: {
406
+ ...peerHeaders,
407
+ ...(isPost ? { 'Content-Type': 'application/json' } : {}),
408
+ },
409
+ ...(isPost ? { body: payload } : {}),
410
+ ...(controller ? { signal: controller.signal } : {}),
411
+ redirect: 'manual',
412
+ };
413
+
414
+ let response;
415
+ try {
416
+ response = await Promise.race([Promise.resolve(fetchImpl(forwardTarget, requestOptions)), timeout]);
417
+ } catch (error) {
418
+ if (timer) clearFn(timer);
419
+ return unreachable({ detail: _string(error && error.message, 300) });
420
+ }
421
+ if (timer) clearFn(timer);
422
+ if (!response || response.__timeout) return unreachable({ detail: 'timeout' });
423
+
424
+ let text = '';
425
+ try {
426
+ text = await response.text();
427
+ } catch (error) {
428
+ return unreachable({ detail: _string(error && error.message, 300) });
429
+ }
430
+ if (res.headersSent) return true;
431
+ // The box's status and body are returned VERBATIM (§6.5): the picker parses the box's own
432
+ // payload shape, and a hub that re-shaped it would drift from the local case.
433
+ res.writeHead(Number(response.status) || 502, {
434
+ 'Content-Type': _string(response.headers && response.headers.get && response.headers.get('content-type'), 200)
435
+ || 'application/json',
436
+ });
437
+ res.end(text);
438
+ return true;
439
+ }
440
+
441
+ /**
442
+ * WP-6 (§7 ownership routing, AC-6b): send ONE request to a box over the SAME credential and with
443
+ * the SAME verbatim-response contract `maybeProxyMachineRequest` uses for the picker routes — the
444
+ * caller (`lib/remote-machine-catalog.js`) is what decides that the id belongs to the box, and what
445
+ * its own refusal looks like when the box cannot be reached.
446
+ *
447
+ * @param {{machine: object, pathname: string, search?: string, method?: string, body?: string}} request
448
+ * @returns {Promise<{ok: true, status: number, contentType: string, text: string}|{ok: false, code: string, error: string}>}
449
+ */
450
+ async function forwardMachineHttp({ machine, pathname, search = '', method = 'POST', body = '', deps = {} } = {}) {
451
+ const d = _deps(deps);
452
+ const baseUrl = _trimUrl(machine && machine.url);
453
+ if (!baseUrl) return { ok: false, code: 'machine_unreachable', error: 'machine_url_missing' };
454
+ const buildHeaders = d.buildPeerControlHeaders || require('./remote-machine-create').buildPeerControlHeaders;
455
+ // P-8: same per-target choice as maybeProxyMachineRequest — the catalog knows which box this is.
456
+ const peerHeaders = buildHeaders(d.env || process.env, { instanceId: _string(machine && machine.instanceId, 160) });
457
+ // WP-1 (D-2): the caller (lib/remote-machine-catalog.js) turns this code into a 503 with the
458
+ // same body; keeping `machine_unreachable` out of it is what makes the config error greppable.
459
+ if (!peerHeaders) {
460
+ const missing = _peerCredentialMissing(_string(machine && machine.displayName, 200));
461
+ return { ok: false, code: missing.code, error: missing.error };
462
+ }
463
+ const fetchImpl = d.fetchImpl || d.fetch || globalThis.fetch;
464
+ if (typeof fetchImpl !== 'function') return { ok: false, code: 'machine_unreachable', error: 'fetch_unavailable' };
465
+ const timeoutMs = Number(d.timeoutMs || DEFAULT_TIMEOUT_MS) || DEFAULT_TIMEOUT_MS;
466
+ const timeoutFn = typeof d.setTimeoutFn === 'function' ? d.setTimeoutFn : setTimeout;
467
+ const clearFn = typeof d.clearTimeoutFn === 'function' ? d.clearTimeoutFn : clearTimeout;
468
+ const controller = typeof AbortController === 'function' ? new AbortController() : null;
469
+ let timer = null;
470
+ const timeout = new Promise((resolve) => {
471
+ timer = timeoutFn(() => {
472
+ try { if (controller) controller.abort(); } catch { /* expected: abort on a settled fetch */ }
473
+ resolve({ __timeout: true });
474
+ }, timeoutMs);
475
+ if (timer && typeof timer.unref === 'function') timer.unref();
476
+ });
477
+ const verb = String(method || 'POST').toUpperCase();
478
+ const hasBody = verb !== 'GET' && verb !== 'HEAD';
479
+ let response;
480
+ try {
481
+ response = await Promise.race([Promise.resolve(fetchImpl(`${baseUrl}${pathname}${search}`, {
482
+ method: verb,
483
+ headers: { ...peerHeaders, ...(hasBody ? { 'Content-Type': 'application/json' } : {}) },
484
+ ...(hasBody ? { body: body || '' } : {}),
485
+ ...(controller ? { signal: controller.signal } : {}),
486
+ redirect: 'manual',
487
+ })), timeout]);
488
+ } catch (error) {
489
+ if (timer) clearFn(timer);
490
+ return { ok: false, code: 'machine_unreachable', error: _string(error && error.message, 300) };
491
+ }
492
+ if (timer) clearFn(timer);
493
+ if (!response || response.__timeout) return { ok: false, code: 'machine_unreachable', error: 'timeout' };
494
+ let text = '';
495
+ try {
496
+ text = await response.text();
497
+ } catch (error) {
498
+ return { ok: false, code: 'machine_unreachable', error: _string(error && error.message, 300) };
499
+ }
500
+ return {
501
+ ok: true,
502
+ status: Number(response.status) || 502,
503
+ contentType: _string(response.headers && response.headers.get && response.headers.get('content-type'), 200) || 'application/json',
504
+ text,
505
+ };
506
+ }
507
+
508
+ module.exports = {
509
+ MACHINE_HEADER,
510
+ MACHINE_QUERY_PARAM,
511
+ DEFAULT_TIMEOUT_MS,
512
+ PROXIED_ROUTES,
513
+ configure,
514
+ isMachineRouted,
515
+ maybeProxyMachineRequest,
516
+ forwardMachineHttp,
517
+ matchBoxProject,
518
+ normalizeGitRemote,
519
+ _recencyScore,
520
+ };