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
@@ -39,35 +39,83 @@ async function readCursor(journalPath) {
39
39
  }
40
40
  }
41
41
 
42
- async function readRecords(journalPath, startOffset = 0) {
43
- const handle = await fs.promises.open(journalPath, 'r');
44
- let size;
45
- let absoluteStart;
46
- let buffer;
47
- try {
48
- size = (await handle.stat()).size;
49
- absoluteStart = Math.min(size, Math.max(0, Number(startOffset) || 0));
50
- buffer = Buffer.allocUnsafe(size - absoluteStart);
51
- let bytesRead = 0;
52
- while (bytesRead < buffer.length) {
53
- const read = await handle.read(buffer, bytesRead, buffer.length - bytesRead, absoluteStart + bytesRead);
54
- if (read.bytesRead === 0) break;
55
- bytesRead += read.bytesRead;
42
+ // I/O working set only: neither the record size nor the recovered records array is capped.
43
+ const JOURNAL_READ_WINDOW_BYTES = 64 * 1024;
44
+
45
+ async function closeAfterJournalFailure(handle, error) {
46
+ try { await handle.close(); }
47
+ catch (closeError) {
48
+ throw new AggregateError([error, closeError], 'Media journal operation and owned handle cleanup failed', { cause: error });
49
+ }
50
+ }
51
+
52
+ async function readJournalWindow(handle, journalPath, position, length) {
53
+ const buffer = Buffer.allocUnsafe(length);
54
+ let filled = 0;
55
+ // Fill one window even when the filesystem returns tiny short reads. Retaining a new 64KiB
56
+ // buffer for each few bytes of progress would amplify a long line's working set needlessly.
57
+ while (filled < length) {
58
+ const remaining = length - filled;
59
+ const { bytesRead } = await handle.read(buffer, filled, remaining, position + filled);
60
+ if (bytesRead === 0) {
61
+ throw Object.assign(new Error(`Media journal ${path.basename(journalPath)} ended before its captured length; recovery is required`),
62
+ { code: 'CTM_MEDIA_JOURNAL_TRUNCATED' });
56
63
  }
57
- if (bytesRead !== buffer.length) buffer = buffer.subarray(0, bytesRead);
58
- } finally {
59
- await handle.close();
64
+ if (!Number.isSafeInteger(bytesRead) || bytesRead < 0 || bytesRead > remaining) {
65
+ throw Object.assign(new Error('Media journal read returned invalid byte progress'),
66
+ { code: 'CTM_MEDIA_JOURNAL_READ_INVALID' });
67
+ }
68
+ filled += bytesRead;
60
69
  }
70
+ return buffer;
71
+ }
72
+
73
+ async function frameJournalRecords(handle, journalPath, absoluteStart, size) {
61
74
  const records = [];
62
- let lineStart = 0;
63
- for (let index = 0; index < buffer.length; index += 1) {
64
- if (buffer[index] !== 0x0a) continue;
65
- const endOffset = absoluteStart + index + 1;
66
- const raw = buffer.subarray(lineStart, index).toString('utf8').trim();
67
- if (raw) records.push({ entry: JSON.parse(raw), startOffset: absoluteStart + lineStart, endOffset });
68
- lineStart = index + 1;
75
+ let pieces = [];
76
+ let lineBytes = 0;
77
+ let lineStart = absoluteStart;
78
+ let position = absoluteStart;
79
+ while (position < size) {
80
+ const buffer = await readJournalWindow(handle, journalPath, position,
81
+ Math.min(JOURNAL_READ_WINDOW_BYTES, size - position));
82
+ let pieceStart = 0;
83
+ for (let index = 0; index < buffer.length; index += 1) {
84
+ if (buffer[index] !== 0x0a) continue;
85
+ pieces.push(buffer.subarray(pieceStart, index));
86
+ lineBytes += index - pieceStart;
87
+ const endOffset = position + index + 1;
88
+ const line = pieces.length === 1 ? pieces[0] : Buffer.concat(pieces, lineBytes);
89
+ // Decode only after newline framing: split UTF-8 sequences survive read/window boundaries.
90
+ const raw = line.toString('utf8').trim();
91
+ if (raw) records.push({ entry: JSON.parse(raw), startOffset: lineStart, endOffset });
92
+ pieces = [];
93
+ lineBytes = 0;
94
+ lineStart = endOffset;
95
+ pieceStart = index + 1;
96
+ }
97
+ if (pieceStart < buffer.length) {
98
+ pieces.push(buffer.subarray(pieceStart));
99
+ lineBytes += buffer.length - pieceStart;
100
+ }
101
+ position += buffer.length;
69
102
  }
70
- return { records, size, partialOffset: absoluteStart + lineStart };
103
+ return { records, size, partialOffset: lineStart };
104
+ }
105
+
106
+ async function readRecords(journalPath, startOffset = 0) {
107
+ const handle = await fs.promises.open(journalPath, 'r');
108
+ let parsed;
109
+ try {
110
+ const size = (await handle.stat()).size;
111
+ const absoluteStart = Math.min(size, Math.max(0, Number(startOffset) || 0));
112
+ parsed = await frameJournalRecords(handle, journalPath, absoluteStart, size);
113
+ } catch (error) {
114
+ await closeAfterJournalFailure(handle, error);
115
+ throw error;
116
+ }
117
+ await handle.close();
118
+ return parsed;
71
119
  }
72
120
 
73
121
  async function createMediaJournal(options = {}) {
@@ -80,7 +128,6 @@ async function createMediaJournal(options = {}) {
80
128
  const journalPath = path.join(root, `journal-${bootId}.jsonl`);
81
129
  const handle = await fs.promises.open(journalPath, 'a', 0o600);
82
130
  await fs.promises.chmod(journalPath, 0o600).catch(() => {});
83
- await syncDirectory(root);
84
131
 
85
132
  const files = new Map();
86
133
  let op = Promise.resolve();
@@ -124,7 +171,14 @@ async function createMediaJournal(options = {}) {
124
171
  files.set(filePath, { cursor: offset, records: parsed.records, size: parsed.size });
125
172
  }
126
173
  }
127
- await refresh();
174
+ try {
175
+ await syncDirectory(root);
176
+ await refresh();
177
+ } catch (error) {
178
+ // A failed constructor returns no journal whose close() the caller could invoke.
179
+ await closeAfterJournalFailure(handle, error);
180
+ throw error;
181
+ }
128
182
 
129
183
  function serialize(fn) {
130
184
  const next = op.then(fn, fn);
@@ -335,6 +389,9 @@ const { DRAFT_CANONICAL_FIELDS, DRAFT_STATE_VERSION_MAX, canonicalDraftProjectio
335
389
 
336
390
  module.exports = {
337
391
  createMediaJournal,
392
+ JOURNAL_READ_WINDOW_BYTES,
393
+ readCursor,
394
+ readJournalWindow,
338
395
  DRAFT_CANONICAL_FIELDS,
339
396
  DRAFT_STATE_VERSION_MAX,
340
397
  canonicalDraftProjection,
@@ -1,5 +1,22 @@
1
1
  'use strict';
2
2
 
3
+ function createMediaJournalProjector(runDbWorkerOp) {
4
+ return (entry, projection = {}) => {
5
+ const urgent = entry.projectionPriority === 'urgent' || projection.urgent;
6
+ const effectiveEntry = urgent && entry.projectionPriority !== 'urgent'
7
+ ? { ...entry, projectionPriority: 'urgent' }
8
+ : entry;
9
+ return runDbWorkerOp('runDbMutation', {
10
+ action: 'applyMediaJournalEntry',
11
+ args: [effectiveEntry],
12
+ }, {
13
+ priority: urgent ? 'urgent' : 'low',
14
+ requestClass: urgent ? 'user-blocking' : 'background',
15
+ timeoutMs: urgent ? 10_000 : 30_000,
16
+ });
17
+ };
18
+ }
19
+
3
20
  function buildMediaStoreOptions({
4
21
  dataDir, bootId, registry, assertDraftOwner, runDbWorkerOp, onDrainProgress,
5
22
  currentOwnerId, isSameOwnerLineage,
@@ -25,21 +42,32 @@ function buildMediaStoreOptions({
25
42
  // drain progress with the load-pressure controller so the media journal participates
26
43
  // in the same producer-debt/freshness framework as read-pool-batch/provider-index above.
27
44
  onDrainProgress,
28
- projectEntry: (entry, projection = {}) => {
29
- const urgent = entry.projectionPriority === 'urgent' || projection.urgent;
30
- const effectiveEntry = urgent && entry.projectionPriority !== 'urgent'
31
- ? { ...entry, projectionPriority: 'urgent' }
32
- : entry;
33
- return runDbWorkerOp('runDbMutation', {
34
- action: 'applyMediaJournalEntry',
35
- args: [effectiveEntry],
36
- }, {
37
- priority: urgent ? 'urgent' : 'low',
38
- requestClass: urgent ? 'user-blocking' : 'background',
39
- timeoutMs: urgent ? 10_000 : 30_000,
40
- });
41
- },
45
+ projectEntry: createMediaJournalProjector(runDbWorkerOp),
42
46
  };
43
47
  }
44
48
 
45
- module.exports = { buildMediaStoreOptions };
49
+ async function prepareMainMediaStore({
50
+ createStore, getDataDir, bootId, runDbWorkerOp, assertDraftOwner,
51
+ onDrainProgress, getCurrentOwnerId, isSameOwnerLineage,
52
+ }) {
53
+ const registry = await runDbWorkerOp('loadMediaStoreRegistry', {}, {
54
+ // Listener activation depends on this registry. It is boot-critical
55
+ // maintenance, while the journal projections below remain background work.
56
+ priority: 'high',
57
+ requestClass: 'maintenance',
58
+ timeoutMs: 30_000,
59
+ });
60
+ return createStore(buildMediaStoreOptions({
61
+ dataDir: getDataDir(), bootId, registry, assertDraftOwner, runDbWorkerOp, onDrainProgress,
62
+ // WP-1 (docs/image-draft-event-ordering-and-verified-adoption-2026-10-14.html §6.4): the store
63
+ // is the single draft version allocator. It recovers the highest pending journal version and
64
+ // then applies verified owner-lineage normalization as one journaled mutation — the draft
65
+ // restamp was removed from db.js restampOwnerLineage so the two can never allocate concurrently.
66
+ // Review finding 9: getCurrentOwnerId delegates to the existing LAZY, MEMOIZED identity
67
+ // accessor only after the DB is open. It returns no invented hostname/port-derived identity.
68
+ // isSameOwnerLineage is the existing dbModule-free lineage snapshot, not a new DB boundary.
69
+ currentOwnerId: getCurrentOwnerId(), isSameOwnerLineage,
70
+ }));
71
+ }
72
+
73
+ module.exports = { buildMediaStoreOptions, createMediaJournalProjector, prepareMainMediaStore };
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ // WP-1 (docs/machine-pool-picker-pairing-dashboard-2026-10-24.html §5 D-4/D-7/D-11, §6.1, §8 WP-1,
4
+ // §9 S-3): the machine-pool schema, in one migration because every piece of it lands with the same
5
+ // reader (lib/host-stats.js today; WP-2/WP-3 for the pairing/self tables).
6
+ //
7
+ // host_samples D-11 — the box owns its own 24 h time series (one row per 60 s tick;
8
+ // ≤1440 rows/machine). No hub-side samples table exists.
9
+ // machine_self D-4 rev 2 — this machine's OWN user settings (display name, favorite,
10
+ // hidden). Exactly one row, id = 1.
11
+ // machine_pairing_offers D-6 — the RFC 8628-shaped code a joining member redeems. The plaintext
12
+ // code is never stored, only its hash.
13
+ // machines.urls_json P-1/P-4 rev 2 — the member's dialable URLs in dial order.
14
+ //
15
+ // Rev 2 deliberately has NO peer_credentials table: D-7 is one shared pool secret in
16
+ // machine-pool.json, never in the DB.
17
+ //
18
+ // The shape follows v55-agent-origin-backfill.js: a narrow dependency object, idempotent SQL, and
19
+ // the version stamp LAST so a failure leaves the ladder where it was. ALTER TABLE has no
20
+ // `IF NOT EXISTS` in SQLite, so the column add is guarded by its own PRAGMA read — which also
21
+ // makes a re-run (a restored snapshot, a partially upgraded file) a no-op rather than an error.
22
+
23
+ const TABLES_DDL = `
24
+ CREATE TABLE IF NOT EXISTS host_samples (
25
+ at INTEGER PRIMARY KEY,
26
+ sample_json TEXT NOT NULL
27
+ );
28
+ CREATE TABLE IF NOT EXISTS machine_self (
29
+ id INTEGER PRIMARY KEY CHECK (id = 1),
30
+ display_name TEXT NOT NULL DEFAULT '',
31
+ favorite INTEGER NOT NULL DEFAULT 0,
32
+ hidden INTEGER NOT NULL DEFAULT 0,
33
+ updated_at INTEGER NOT NULL DEFAULT 0
34
+ );
35
+ CREATE TABLE IF NOT EXISTS machine_pairing_offers (
36
+ id TEXT PRIMARY KEY,
37
+ code_hash TEXT NOT NULL UNIQUE,
38
+ created_at INTEGER NOT NULL,
39
+ expires_at INTEGER NOT NULL,
40
+ redeemed_at INTEGER,
41
+ redeemed_by TEXT
42
+ );
43
+ `;
44
+
45
+ function _tableExists(db, table) {
46
+ try {
47
+ return !!db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(table);
48
+ } catch {
49
+ return false; // expected: a handle that cannot read sqlite_master (pre-boot) has nothing to alter
50
+ }
51
+ }
52
+
53
+ function _tableHasColumn(db, table, column) {
54
+ try {
55
+ return db.prepare(`PRAGMA table_info(${table})`).all().some((c) => c.name === column);
56
+ } catch {
57
+ return false; // expected: no such table on an old/partial database -> nothing to alter
58
+ }
59
+ }
60
+
61
+ function migrateToV56({ getDb, setSchemaVersion, log = console } = {}) {
62
+ if (typeof getDb !== 'function' || typeof setSchemaVersion !== 'function') throw new TypeError('migration dependencies are required');
63
+ const db = getDb();
64
+ db.exec(TABLES_DDL);
65
+ // S-3: existing databases get the column by ALTER; db.js's machines CREATE literal carries the
66
+ // same column for a fresh file, and the two must stay in the same ordinal position.
67
+ let altered = 0;
68
+ if (_tableExists(db, 'machines') && !_tableHasColumn(db, 'machines', 'urls_json')) {
69
+ db.exec("ALTER TABLE machines ADD COLUMN urls_json TEXT NOT NULL DEFAULT '[]'");
70
+ altered = 1;
71
+ }
72
+ setSchemaVersion(56);
73
+ log.log(`[db] Schema migrated to v56 (host samples + machine pool): ${altered} column(s) added`);
74
+ }
75
+
76
+ module.exports = { migrateToV56 };
@@ -151,9 +151,14 @@ function createHostedMirrorFeedCoalescer(options = {}) {
151
151
  clear();
152
152
  if (!buffer) return false;
153
153
  const batch = buffer;
154
+ // WP-4 (clock-free-tests-followups-r4-2026-10-24.html §5 R-9, §6.5): the batch's FIRST-byte
155
+ // time, not the drain time -- a held/buffered batch was received when its first byte arrived,
156
+ // and the hold is exactly the delay this stamp exists to expose. Captured before `startedAt`
157
+ // is cleared below.
158
+ const receivedAt = startedAt || now();
154
159
  buffer = '';
155
160
  startedAt = 0;
156
- deliver(sanitizeAndCoalesceHostedMirrorFeed(batch, command()));
161
+ deliver(sanitizeAndCoalesceHostedMirrorFeed(batch, command()), { receivedAt });
157
162
  return true;
158
163
  };
159
164
  const arm = (ms, opts = {}) => {
@@ -174,7 +179,8 @@ function createHostedMirrorFeedCoalescer(options = {}) {
174
179
  const hasSyncControl = _hasSyncFrameControl(text);
175
180
  const hasRedrawControl = _hasCursorRedrawControl(text);
176
181
  if (!buffer && !hasSyncControl && !hasRedrawControl) {
177
- deliver(sanitizeHostedMirrorFeed(text, command()));
182
+ // WP-4 (R-9, §6.5): the immediate path was never held, so its receipt time IS now.
183
+ deliver(sanitizeHostedMirrorFeed(text, command()), { receivedAt: now() });
178
184
  return true;
179
185
  }
180
186
  if (!startedAt) startedAt = now();
@@ -204,7 +210,10 @@ function createHostedMirrorFeedCoalescer(options = {}) {
204
210
  const prefix = buffer.slice(0, lastOff + SYNC_OFF.length);
205
211
  const trailing = buffer.slice(lastOff + SYNC_OFF.length);
206
212
  buffer = trailing;
207
- deliver(sanitizeAndCoalesceHostedMirrorFeed(prefix, command()));
213
+ // WP-4 (R-9, §6.5): this prefix came out of the same buffer as flush()'s batch, so it carries
214
+ // the same first-byte time. `startedAt` is still live here when `trailing` is non-empty; the
215
+ // `|| now()` fallback only covers a zeroed clock.
216
+ deliver(sanitizeAndCoalesceHostedMirrorFeed(prefix, command()), { receivedAt: startedAt || now() });
208
217
  if (trailing) {
209
218
  // Keep startedAt running for the trailing incomplete fragment and re-arm the safety timer.
210
219
  arm(2000, { reset: true });
@@ -6,13 +6,24 @@ const RECIPE_KEYS = Object.freeze([
6
6
  'version', 'ctmSessionId', 'processGeneration', 'harness', 'providerThreadId',
7
7
  'routeId', 'wireModelId', 'profileFingerprint', 'factsRevision', 'transitionId',
8
8
  ]);
9
- const OPTIONAL_RECIPE_KEYS = Object.freeze(['sourceEnvironmentReference']);
9
+ // `nativeConfigReceipt` is the Codex config.toml receipt read at launch (state + sha256). It rides
10
+ // the recipe so a session ADOPTED from a surviving host after a CTM restart still proves which
11
+ // config its process started under; without it a lead switch refuses source_config_policy_unqualified.
12
+ const OPTIONAL_RECIPE_KEYS = Object.freeze(['sourceEnvironmentReference', 'nativeConfigReceipt']);
10
13
  const SOURCE_ENVIRONMENT_REFERENCES = new Set(['captured-empty', 'private-owner-required']);
11
14
  const ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
12
15
  const CANONICAL_MODEL_ID_PATTERN = /^[A-Za-z0-9@][A-Za-z0-9@._:/+-]{0,255}$/;
13
16
  const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
14
17
  const SHA256_PATTERN = /^[0-9a-f]{64}$/;
15
18
 
19
+ function validConfigReceipt(value) {
20
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
21
+ const keys = Object.keys(value);
22
+ if (keys.length !== 2 || !keys.includes('state') || !keys.includes('sha256')) return false;
23
+ return (value.state === 'present' && typeof value.sha256 === 'string' && SHA256_PATTERN.test(value.sha256))
24
+ || (value.state === 'missing' && value.sha256 === null);
25
+ }
26
+
16
27
  function boundedId(value) {
17
28
  return typeof value === 'string' && ID_PATTERN.test(value);
18
29
  }
@@ -37,6 +48,7 @@ function sanitizedRecipe(value, minimumGeneration = 1) {
37
48
  || RECIPE_KEYS.some((key) => !Object.hasOwn(value, key))) return undefined;
38
49
  if (Object.hasOwn(value, 'sourceEnvironmentReference')
39
50
  && !SOURCE_ENVIRONMENT_REFERENCES.has(value.sourceEnvironmentReference)) return undefined;
51
+ if (Object.hasOwn(value, 'nativeConfigReceipt') && !validConfigReceipt(value.nativeConfigReceipt)) return undefined;
40
52
  if (value.version !== 1 || !boundedId(value.ctmSessionId)
41
53
  || !Number.isSafeInteger(value.processGeneration) || value.processGeneration < minimumGeneration
42
54
  || !['codex-cli', 'claude-code'].includes(value.harness)
@@ -50,8 +62,9 @@ function sanitizedRecipe(value, minimumGeneration = 1) {
50
62
  || typeof value.factsRevision !== 'string' || !SHA256_PATTERN.test(value.factsRevision)) return undefined;
51
63
 
52
64
  if (value.transitionId !== null && !boundedId(value.transitionId)) return undefined;
53
- return Object.fromEntries([...RECIPE_KEYS, ...(Object.hasOwn(value, 'sourceEnvironmentReference')
54
- ? OPTIONAL_RECIPE_KEYS : [])].map((key) => [key, value[key]]));
65
+ return Object.fromEntries([...RECIPE_KEYS, ...OPTIONAL_RECIPE_KEYS.filter((key) => Object.hasOwn(value, key))]
66
+ .map((key) => [key, key === 'nativeConfigReceipt'
67
+ ? { state: value[key].state, sha256: value[key].sha256 } : value[key]]));
55
68
  }
56
69
 
57
70
  function modelExecutionRecipeFromEnv(env) {
@@ -92,6 +92,10 @@ function anthropicErrorType(kind) {
92
92
  if (kind === 'rate_limit_exceeded') return 'rate_limit_error';
93
93
  if (kind === 'insufficient_quota') return 'billing_error';
94
94
  if (kind === 'server_is_overloaded') return 'overloaded_error';
95
+ // WP-2 D-5: the two truthful refusal classes carry their own HTTP status (400 / 403), so the type
96
+ // must not collapse into api_error -- a permission denial is not an upstream fault.
97
+ if (kind === 'model_not_invocable') return 'invalid_request_error';
98
+ if (kind === 'model_access_denied') return 'permission_error';
95
99
  return 'api_error';
96
100
  }
97
101
 
@@ -20,6 +20,9 @@ function createChatResponseTranslators({ normalizeUpstreamOutcome, firstString,
20
20
  if (kind === 'rate_limit_exceeded') return 'rate_limit_error';
21
21
  if (kind === 'insufficient_quota') return 'billing_error';
22
22
  if (kind === 'server_is_overloaded') return 'overloaded_error';
23
+ // WP-2 D-5: same two mappings as anthropic-face.js (HTTP 400 / 403 respectively).
24
+ if (kind === 'model_not_invocable') return 'invalid_request_error';
25
+ if (kind === 'model_access_denied') return 'permission_error';
23
26
  return 'api_error';
24
27
  }
25
28
 
@@ -27,6 +27,12 @@
27
27
  // Nothing here reads or writes the upstream-native Responses body: a
28
28
  // `passthrough` route never calls this module at all.
29
29
 
30
+ // node:crypto is used for sha256 ONLY (WP-3's handoff marker): a digest is a pure,
31
+ // deterministic function of its argument, so this module stays free of randomness
32
+ // -- no randomUUID/no randomBytes, and every call still returns the same value for
33
+ // the same arguments.
34
+ const crypto = require('node:crypto');
35
+
30
36
  // The reserved V2 collaboration namespace (Codex multi_agents_v2.rs) and the
31
37
  // older V1 one (multi_agent_v1). A call is only "readable plaintext" in the V2
32
38
  // namespace -- V1's `spawn_agent` is a different wire contract entirely.
@@ -449,6 +455,93 @@ function validateCollaborationInput(body) {
449
455
  });
450
456
  }
451
457
 
458
+ // ── handed-off compaction drop (WP-3) ────────────────────────────────────
459
+ //
460
+ // A Codex lead whose history holds an OpenAI-encrypted `compaction` item cannot
461
+ // replay that item to a routed (chat-translate) upstream: the ciphertext is bound
462
+ // to its issuer and reads as garbage there. The handoff path therefore injects,
463
+ // into the target thread, ONE developer message carrying a plaintext summary and
464
+ // naming -- by SHA-256 of the ciphertext it replaces -- the exact compaction item
465
+ // it stands for:
466
+ //
467
+ // <ctm_handoff compaction_sha256="<64 lowercase hex>" source_model="<model>">
468
+ // …summary…
469
+ // </ctm_handoff>
470
+ //
471
+ // Codex then sends every subsequent request with BOTH the `compaction` item
472
+ // (verbatim) and, later in `input`, that developer message. The pairing is the
473
+ // whole contract: a compaction item is dropped IFF its own replacement is present
474
+ // in the SAME request, at a LATER index. An unmatched compaction item is history
475
+ // this route cannot read and stays exactly where it was, so `validateCollaborationInput`
476
+ // keeps refusing it (fail-closed) rather than silently losing a turn.
477
+ //
478
+ // The drop is a COPY: the caller's body object and its `input` array are never
479
+ // mutated, and the caller's own body reference is returned untouched when nothing
480
+ // was dropped.
481
+
482
+ const HANDOFF_MARKER_PREFIX = '<ctm_handoff compaction_sha256="';
483
+
484
+ /** sha256 (utf8) of a ciphertext string, lowercase hex. */
485
+ function ciphertextSha256(encryptedContent) {
486
+ return crypto.createHash('sha256').update(encryptedContent, 'utf8').digest('hex');
487
+ }
488
+
489
+ /** True for a `compaction` item whose own ciphertext is present and non-empty. */
490
+ function isCiphertextCompaction(item) {
491
+ return isObject(item)
492
+ && item.type === 'compaction'
493
+ && typeof item.encrypted_content === 'string'
494
+ && item.encrypted_content.length > 0;
495
+ }
496
+
497
+ /**
498
+ * True for the handoff developer message that REPLACES the compaction item whose
499
+ * ciphertext hashes to `sha`.
500
+ *
501
+ * The shape is exact and deliberately narrow: a `message` item whose role is
502
+ * `developer`, whose `content` is an array, and whose FIRST part starts with the
503
+ * literal marker prefix. A bare string `content`, a second-part marker, a
504
+ * `role:'user'` message, a leading space before the marker or a marker for another
505
+ * ciphertext all fail here -- the marker is the pairing proof, so a near-miss must
506
+ * never be treated as one.
507
+ */
508
+ function isHandoffMarkerFor(item, sha) {
509
+ if (!isObject(item) || item.type !== 'message' || item.role !== 'developer') return false;
510
+ if (!Array.isArray(item.content) || item.content.length === 0) return false;
511
+ const first = item.content[0];
512
+ if (!isObject(first) || first.type !== 'input_text' || typeof first.text !== 'string') return false;
513
+ return first.text.startsWith(`${HANDOFF_MARKER_PREFIX}${sha}"`);
514
+ }
515
+
516
+ /**
517
+ * Omit every `compaction` item whose handoff replacement is present LATER in the
518
+ * same `input`.
519
+ *
520
+ * @param {unknown} body a Responses request body
521
+ * @returns {{ok:true, body:object, droppedHandedOffCompactions:number}} `body` is
522
+ * the caller's own object (same reference) whenever nothing was dropped.
523
+ */
524
+ function dropHandedOffCompactions(body) {
525
+ const source = isObject(body) ? body : {};
526
+ const items = normalizeItems(source.input);
527
+ let droppedHandedOffCompactions = 0;
528
+ const kept = [];
529
+ for (let index = 0; index < items.length; index += 1) {
530
+ const item = items[index];
531
+ if (isCiphertextCompaction(item)) {
532
+ const sha = ciphertextSha256(item.encrypted_content);
533
+ let covered = false;
534
+ for (let later = index + 1; later < items.length; later += 1) {
535
+ if (isHandoffMarkerFor(items[later], sha)) { covered = true; break; }
536
+ }
537
+ if (covered) { droppedHandedOffCompactions += 1; continue; }
538
+ }
539
+ kept.push(item);
540
+ }
541
+ if (droppedHandedOffCompactions === 0) return { ok: true, body, droppedHandedOffCompactions };
542
+ return { ok: true, body: { ...source, input: kept }, droppedHandedOffCompactions };
543
+ }
544
+
452
545
  // ── translated function-call constructor ─────────────────────────────────
453
546
 
454
547
  /** True only for a translated call Codex must read as readable plaintext args. */
@@ -488,6 +581,7 @@ function makeTranslatedFunctionCall(fields = {}) {
488
581
 
489
582
  module.exports = {
490
583
  validateCollaborationInput,
584
+ dropHandedOffCompactions,
491
585
  agentMessageToChat,
492
586
  makeTranslatedFunctionCall,
493
587
  isReadableCollaborationCall,