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
@@ -456,7 +456,14 @@ function resolveCreateLaunchParams(ctx) {
456
456
  // alias because startup_tasks still stores that legacy column.
457
457
  const requestedTitle = String(msg.title || msg.label || '').replace(/^Resume:\s*/i, '').trim().slice(0, 120);
458
458
  const walleResumeRequest = msg.resumeWalle === true && !msg._isRestore;
459
- const hasExplicitClientTitle = !msg._isRestore && !walleResumeRequest && !!requestedTitle;
459
+ // D-1: every resume sender echoes the row's own DISPLAYED title back (`label`/`title`), and
460
+ // an echoed label is not user intent — so a resume never yields a tier-1 (user) name.
461
+ // D-13/WP-5: a CTM-composed label (`Shell:`, `Folder:`, `Code Review:`, a review session, a
462
+ // best-of attempt, the Claude Desktop fork) declares itself with `titleIntent:'default'`, so it is
463
+ // written at tier 3 like any other echo. An absent titleIntent keeps today's semantics (an old
464
+ // client causes no regression), and the New Session modal's typed label still lands tier 1.
465
+ const hasExplicitClientTitle = !msg._isRestore && !walleResumeRequest && !isResumeLaunch
466
+ && msg.titleIntent !== 'default' && !!requestedTitle;
460
467
  let title = requestedTitle;
461
468
  if (!title) {
462
469
  const agentName = cmd.includes('claude') ? 'Claude Code'
@@ -91,8 +91,9 @@ function ctmLifecycleJournalPath({ env = process.env, configDir = null, instance
91
91
  return path.join(resolveEffectiveDataDir({ env, configDir }), 'logs', fileName);
92
92
  }
93
93
 
94
- function appendCtmBootLine({ pid = process.pid, env = process.env, configDir = null, instanceTag = null, port = null } = {}) {
94
+ function appendCtmBootLine({ pid = process.pid, env = process.env, configDir = null, instanceTag = null, port = null, enteredAtServer = false } = {}) {
95
95
  _reportLaunchdProcessTypeOnBoot(env);
96
+ _reportBootEntryOnBoot(env, { enteredAtServer });
96
97
  return appendLifecycleLine(ctmLifecycleJournalPath({ env, configDir, instanceTag, port }), {
97
98
  ts: new Date().toISOString(),
98
99
  event: 'ctm-boot',
@@ -127,10 +128,25 @@ function _reportLaunchdProcessTypeOnBoot(env = process.env) {
127
128
  } catch { /* expected: a boot-time advisory must never block boot; the plist may be unreadable on a hardened install */ }
128
129
  }
129
130
 
131
+ // WP-2 (clock-free r6 OQ-6): report-only boot check for the OTHER install drift — a launchd-managed
132
+ // CTM whose plist still points at server.js directly, so bin/ctm-service-entry.js's conversation-storage
133
+ // boot gate never ran. Rather than diff the plist (a second source of truth, and one that can't tell a
134
+ // wrapper that execs the entry from a real bypass — D-2), the caller reports the RUNTIME fact
135
+ // `require.main === module`. Warns once per boot so the drift can't stay silent for a month; never blocks
136
+ // boot, never invokes launchctl, never reads or writes the plist. Only the real launchd-managed primary
137
+ // is checked — a /ctm-dev instance (CTM_MANAGED_BY_LAUNCHD unset/'0') or non-darwin host has no plist.
138
+ function _reportBootEntryOnBoot(env = process.env, { enteredAtServer = false, platform = process.platform, warn = console.warn } = {}) {
139
+ if (!enteredAtServer || env.CTM_MANAGED_BY_LAUNCHD !== '1' || platform !== 'darwin') return;
140
+ try {
141
+ warn('[launchd] CTM was started at server.js, so the conversation-storage boot gate did not run; re-run bin/install-service.sh or npx create-walle to enter through bin/ctm-service-entry.js');
142
+ } catch { /* expected: a boot-time advisory must never block boot */ }
143
+ }
144
+
130
145
  module.exports = {
131
146
  resolvePrimaryDataDir,
132
147
  resolveEffectiveDataDir,
133
148
  isPrimaryInstance,
134
149
  ctmLifecycleJournalPath,
135
150
  appendCtmBootLine,
151
+ _reportBootEntryOnBoot,
136
152
  };
@@ -93,6 +93,13 @@ const TABLE_DOMAINS = Object.freeze({
93
93
  // WP-3 (§6.4 S-12): where a shipped transcript landed on THIS instance. Same domain as
94
94
  // agent_sessions (the identity row it is written alongside) so one handle serves the consumer.
95
95
  sync_transcript_replicas: 'core',
96
+ // WP-0 (remote-machine-hub-sessions-2026-10-22.html §6.1): the hub machine registry.
97
+ machines: 'core',
98
+ // WP-1 (machine-pool-picker-pairing-dashboard-2026-10-24.html §6.1, D-4/D-6/D-11): the machine
99
+ // pool's own tables, same domain as `machines` — one 'core' handle serves the whole feature.
100
+ host_samples: 'core',
101
+ machine_self: 'core',
102
+ machine_pairing_offers: 'core',
96
103
  // Legacy pre-v1 predecessors of ctm_sessions/agent_sessions/session_index.
97
104
  // Superseded but their CREATE TABLE IF NOT EXISTS guards remain for
98
105
  // fresh-v0 / mid-migration databases. Same domain as their successors
@@ -193,6 +193,26 @@ const CTM_WRITE_OPS = Object.freeze([
193
193
  'deleteChain', 'deleteCtmSession', 'deleteFolder', 'deleteHook', 'deleteImage',
194
194
  'deletePermissionRule', 'deletePrompt', 'deleteReview', 'deleteReviewComment',
195
195
  'deleteSessionAnalysis', 'deleteStaleAnalyses', 'deleteTemplate',
196
+ // WP-2 (docs/remote-machine-hub-sessions-2026-10-22.html §6.1/§8, deferred from WP-0): the hub
197
+ // machine-registry writes. `machines` is domain 'core' (lib/db-domain-tables.js), and each op's
198
+ // OP_META row in shared/storage/contract.js carries the same tag — Gate 2c (CTM_WRITE_OPS →
199
+ // OP_META kind:'write' + domain) and Gate 2c-inventory (a domain-tagged write op outside this
200
+ // list) are exactly the two assertions that require the pair to land together.
201
+ 'upsertMachineRow', 'recordMachineProbe', 'deleteMachineRow',
202
+ // WP-1 (machine-pool-picker-pairing-dashboard-2026-10-24.html §6.1, D-11): the 60 s host-sample
203
+ // write, on the same `core` handle as the machines rows above; its OP_META row in
204
+ // shared/storage/contract.js carries the matching kind:'write' + domain + tables:['host_samples'].
205
+ 'recordHostSample',
206
+ // WP-2a (machine-pool-picker-pairing-dashboard-2026-10-24.html §6.1, D-4/P-5/P-6): the
207
+ // machine-owned settings write, the P-5 roster replacement (one transaction: upsert listed,
208
+ // delete unlisted, never self) and the leave() sweep. Each OP_META row in shared/storage/contract.js
209
+ // carries the matching kind:'write' + domain:'core'.
210
+ 'setMachineSelf', 'replaceRosterMachines', 'deleteAllMachineRows',
211
+ // WP-3 (machine-pool-picker-pairing-dashboard-2026-10-24.html §6.3, D-6/D-9): the pairing-offer
212
+ // writes — mint, single-use redeem, and the 5-wrong-redeem burn. `machine_pairing_offers` is
213
+ // classified 'core' in lib/db-domain-tables.js and each op's OP_META row in
214
+ // shared/storage/contract.js carries the matching kind:'write' + domain + tables.
215
+ 'insertPairingOffer', 'markPairingOfferRedeemed', 'burnLivePairingOffers',
196
216
  'deleteWorkspace', 'detachPromotedProviderChildSessions', 'duplicatePrompt',
197
217
  'enqueueSyncOutbox', 'flushSessionDiagnostics', 'flushSessionMetadataUpdates', 'heartbeatStartupTasks', 'incrementApprovalRuleMatch',
198
218
  'markStartupTaskExited', 'markStartupTaskUserPromptInput', 'markSyncOutboxDeliveredThrough', 'recordSyncOutboxServed',
@@ -205,7 +225,7 @@ const CTM_WRITE_OPS = Object.freeze([
205
225
  'supersedeHeldApprovalDecision', 'expireStaleHeldApprovalDecision', 'resolveApprovalDecision', 'claimWallePermissionResolution', 'commitWallePermissionResolution', 'expireOpenWallePermissions', 'restorePromptVersion', 'reviveStartupTask',
206
226
  'saveApprovalAiRefinementRule', 'saveApprovalRescuePattern', 'saveImage',
207
227
  'setAlwaysAsk', 'setPermRuleExceptions', 'setPromptParent',
208
- 'setScrollbackIdentity', 'setSessionStar', 'setSessionTitleMirror', 'setSessionTitleStatusNew',
228
+ 'setScrollbackIdentity', 'setSessionStar', 'setSessionTitleMirror', 'setSessionTitleStatusNew', 'applyAgentTitleBackfillRows',
209
229
  'setSetting', 'startAnalysisRun', 'toggleApprovalRule', 'toggleAutoApproval',
210
230
  'toggleHook', 'trackPromptUsage', 'transferSessionOwnership', 'updateChain',
211
231
  'updateCodingSessionModel', 'updateFolder', 'updateHarvestState',
@@ -256,7 +276,6 @@ const CTM_WRITE_OPS = Object.freeze([
256
276
  'recordSyncPeerConflict', 'updateStartupTaskTitle', 'updateStartupTaskClaudeSession',
257
277
  'updateStartupTaskAttentionDismissal', 'updateStartupTaskLifecycle',
258
278
  'markStartupTaskParked', 'prepareScrollbackForSession', 'upsertAgentSessionIdentity',
259
- 'promoteStartupTaskTitleIfUserNamed', 'repairSessionTitlesFromStartupTasks',
260
279
  'repairCodexRolloutAgentIdentities', 'cleanupOrphanScrollbackIdentities',
261
280
  ].concat(require('../../shared/storage/ctm-domain-operation-contract').WORKSPACE_ACTIVITY_OWNER_WRITE_OPS));
262
281
 
@@ -124,6 +124,13 @@ const RAW_DB_OWNER_OPS = {
124
124
  updatePromptEffectiveness: { expectedMs: 5000, resumable: false },
125
125
  applySafeRelinkRepairs: { expectedMs: 1000, resumable: false },
126
126
  recoverSessionIntegrity: { expectedMs: 1000, resumable: false },
127
+ // WP-3 (worker-transcript-link-derived-not-discovered-2026-10-25.html §5 D-5): one bounded,
128
+ // idempotent boot pass that fills agent_sessions.jsonl_path for Claude rows whose transcript is
129
+ // already at the derived path. `expectedMs` is a budget HINT, not a deadline: the dispatch
130
+ // deadline is max(interactive floor 60s, expectedMs*8) (lib/db-owner-op-deadline.js), and the op
131
+ // is additionally bounded by payload.limit (<=500 rows). Not resumable: the caller is a
132
+ // fire-and-forget boot hook with no redrive, and a redrive could only re-run a no-op.
133
+ linkDerivedClaudeTranscripts: { expectedMs: 2000, resumable: false },
127
134
  backfillSessionMessagesAsync: { expectedMs: 500, resumable: false },
128
135
  runContentRowsBackfillSweep: { expectedMs: 500, resumable: false },
129
136
  deleteStaleSessionAnalysesFromArtifact: {
@@ -937,12 +937,34 @@ class CtmDbOwnerWorkerClient {
937
937
  }).catch(() => {});
938
938
  }
939
939
 
940
+ // WP-A P-2 (freeze-probes): the up-to-`limit` OLDEST client requests still outstanding on this
941
+ // worker, each { op, ageMs, started } -- what a memory step should be read against. One pass over
942
+ // the Map with a bounded insert (never a sort of the whole pending set: this runs on the sampler path).
943
+ _oldestPendingOps(limit = 5) {
944
+ const now = Date.now();
945
+ const oldest = [];
946
+ for (const pending of this._pending.values()) {
947
+ const entry = {
948
+ op: String(pending?.op || 'unknown'),
949
+ ageMs: Math.max(0, now - (Number(pending?.enqueuedAt) || now)),
950
+ started: Number(pending?.startedAt) > 0,
951
+ };
952
+ let at = oldest.length;
953
+ while (at > 0 && oldest[at - 1].ageMs < entry.ageMs) at -= 1;
954
+ if (at >= limit) continue;
955
+ oldest.splice(at, 0, entry);
956
+ if (oldest.length > limit) oldest.pop();
957
+ }
958
+ return oldest;
959
+ }
960
+
940
961
  getStatus() {
941
962
  const s = {
942
963
  ...this._status,
943
964
  ready: this._ready,
944
965
  closed: this._closed,
945
966
  pendingClientRequests: this._pending.size,
967
+ pendingOps: this._oldestPendingOps(),
946
968
  degraded: this.isDegraded(),
947
969
  degradedUntil: this._degradedUntil,
948
970
  consecutiveTimeouts: this._consecutiveTimeouts,
@@ -39,6 +39,7 @@ const SCHEMA_SOURCE_FILES = Object.freeze([
39
39
  'lib/migrations/v52-coding-session-launch-generations.js',
40
40
  'lib/migrations/v53-session-model-intent.js',
41
41
  'lib/migrations/v54-session-lead-model.js',
42
+ 'lib/migrations/v56-host-samples-and-machine-pool.js',
42
43
  'lib/migrations/v29-usage-accounting.js', 'lib/migrations/v30-usage-reporting.js', 'lib/migrations/v36-conversation-projection-deletes.js',
43
44
  '../shared/usage-store.js', '../shared/usage-reporting-schema.js',
44
45
  'lib/delete-ctm-session-saga.js',
@@ -612,8 +612,6 @@ const FEATURES = [
612
612
  { id: "ctm_ctm_fp_memo", env: "CTM_FP_MEMO", owner: "ctm-core-team", stage: "stable", defaultOn: true, since: "2026-08-24", expires: null },
613
613
  // !== '0' comparison found; 1 site(s); first: lib/ws/state-sync.js:470
614
614
  { id: "ctm_ctm_frame_nack", env: "CTM_FRAME_NACK", owner: "ctm-core-team", stage: "stable", defaultOn: true, since: "2026-08-24", expires: null },
615
- // !== '0' comparison found; 1 site(s); first: server.js:36110
616
- { id: "ctm_ctm_freeze_park_giant", env: "CTM_FREEZE_PARK_GIANT", owner: "ctm-core-team", stage: "stable", defaultOn: true, since: "2026-08-24", expires: null },
617
615
  // numeric-tunable suffix/read shape; 1 site(s); first: lib/perf-tracker.js:829
618
616
  { id: "ctm_ctm_freeze_probe_ms", env: "CTM_FREEZE_PROBE_MS", owner: "ctm-core-team", stage: "ops", tunable: true, default: 0, since: "2026-08-24", expires: null, note: "numeric tunable" },
619
617
  // !== '0' comparison found; 1 site(s); first: server.js:445
@@ -1559,7 +1557,7 @@ const FEATURES = [
1559
1557
  // no polarity/tunable/config pattern matched; 1 site(s); first: db.js:4416
1560
1558
  { id: "ctm_ctm_sqlite_synchronous", env: "CTM_SQLITE_SYNCHRONOUS", owner: "ctm-core-team", stage: "ops", defaultOn: true, since: "2026-08-24", expires: null, note: "unclassified raw read; needs manual triage" },
1561
1559
  // numeric-tunable suffix/read shape; 1 site(s); first: db.js:4373
1562
- { id: "ctm_ctm_sqlite_write_lock_stale_ms", env: "CTM_SQLITE_WRITE_LOCK_STALE_MS", owner: "ctm-core-team", stage: "ops", tunable: true, default: 0, since: "2026-08-24", expires: null, note: "numeric tunable" },
1560
+ { id: "ctm_ctm_sqlite_write_lock_stale_ms", env: "CTM_SQLITE_WRITE_LOCK_STALE_MS", owner: "ctm-core-team", stage: "removed", defaultOn: false, since: "2026-08-24", expires: null, note: "retired 2026-10-23 (fsevents-churn WP-1, docs/fsevents-churn-kernel-write-lock-2026-10-23.html D-4): the SQLite write lock is a kernel lock on a .mutex lock DB released by the OS when its holder dies, so stale-claim recovery and its age knob were deleted; no read of this name survives." },
1563
1561
  // !== '0' comparison found; 1 site(s); first: server.js:448
1564
1562
  { id: "ctm_ctm_stale_grid_embargo", env: "CTM_STALE_GRID_EMBARGO", owner: "ctm-core-team", stage: "stable", defaultOn: true, since: "2026-08-24", expires: null },
1565
1563
  // !== '0' comparison found; 1 site(s); first: server.js:19764
@@ -4,6 +4,7 @@ const path = require('path');
4
4
  const EventEmitter = require('events');
5
5
  const { namedInterval, runSync, wrap } = require('./perf-tracker');
6
6
  const envFlags = require('./env-flags');
7
+ const { isWritableOnlyByOwner } = require('../../shared/owner-write-safety');
7
8
 
8
9
  // Design doc mainloop-composer-freeze-2026-08-20.html §5 D-1, §6, §9 S-1/S-4. The 60s
9
10
  // scan tick and the attach/reattach directory walk ran completely unwrapped, so the
@@ -103,7 +104,7 @@ function resolveRoot(root) {
103
104
  result.refusalCode = 'wrong-owner';
104
105
  return result;
105
106
  }
106
- if ((Number(targetStat.mode) & 0o022) !== 0) {
107
+ if (!isWritableOnlyByOwner(targetStat)) {
107
108
  result.validationStatus = 'refused';
108
109
  result.refusalCode = 'unsafe-permissions';
109
110
  return result;
@@ -159,7 +160,7 @@ async function resolveRootAsync(root) {
159
160
  result.refusalCode = 'wrong-owner';
160
161
  return result;
161
162
  }
162
- if ((Number(targetStat.mode) & 0o022) !== 0) {
163
+ if (!isWritableOnlyByOwner(targetStat)) {
163
164
  result.validationStatus = 'refused';
164
165
  result.refusalCode = 'unsafe-permissions';
165
166
  return result;
@@ -4,8 +4,14 @@ const NATIVE_ORIGINS = new Set(['native-codex', 'native-claude', 'native-opencod
4
4
  const KNOWN_ORIGINS = new Set([...NATIVE_ORIGINS, 'routed-proxy', 'portable-transcript']);
5
5
  // S-3: the adapter/schema ids and the version membership test come from the one qualified table.
6
6
  const { CODEX_CONTROL_ADAPTER, CODEX_COMPACTION_SCHEMA_RECEIPT, CODEX_PRE_TURN_COMPACTION_SCHEMA,
7
- isQualifiedCodexVersion } = require('./codex-harness-qualification');
8
- const PORTABLE_HISTORY_KINDS = new Set(['text', 'tool-pair', 'native-world-state', 'native-lifecycle']);
7
+ isQualifiedCodexVersion, isNonElevatedCyberAccessProgram } = require('./codex-harness-qualification');
8
+ // S-4: the ONE portable-kind set. `native-media` is a message whose only non-text content parts are
9
+ // input_image parts (D-2); it is portable only to a Codex target, which is the planner's job below.
10
+ const PORTABLE_HISTORY_KINDS = new Set(['text', 'tool-pair', 'native-world-state', 'native-lifecycle', 'native-media']);
11
+ // D-5/D-6 (docs/codex-lead-routed-switch-history-handoff-2026-10-21.html §6.2): the plaintext handoff
12
+ // the source model writes is injected into the forked target, so it consumes target context that the
13
+ // source estimate does not count. 32 KiB of summary text is 8_192 tokens at four characters each.
14
+ const HANDOFF_TOKEN_RESERVE = 8_192;
9
15
 
10
16
  function inspectTransition({ sourceRecipe, targetRecipe, snapshot, signal } = {}) {
11
17
  if (signal?.aborted) return refuse('transition_cancelled');
@@ -17,9 +23,11 @@ function inspectTransition({ sourceRecipe, targetRecipe, snapshot, signal } = {}
17
23
  const targetProviderId = nonempty(targetRecipe.providerId);
18
24
  const origin = nonempty(sourceRecipe.origin);
19
25
  const codexHistorySource = origin === 'native-codex' || sourceRecipe.harness === 'codex-cli';
26
+ // D-3: qualification is per record (D-1) plus the installed binary that performs the fork. The
27
+ // rollout's CREATOR version (session_meta.cli_version, written once at creation) is not the writer
28
+ // of its later records, so it is not a gate here.
20
29
  const qualifiedCodexHistorySource = codexHistorySource
21
- && sourceRecipe.controlAdapterVersion === CODEX_CONTROL_ADAPTER
22
- && isQualifiedCodexVersion(sourceRecipe.programVersion);
30
+ && sourceRecipe.controlAdapterVersion === CODEX_CONTROL_ADAPTER;
23
31
  if (!origin) return refuse('source_origin_unknown');
24
32
  if (!KNOWN_ORIGINS.has(origin)) return refuse('source_origin_unsupported');
25
33
  if (!sourceProviderId || !targetProviderId) return refuse('provider_identity_unknown');
@@ -31,7 +39,7 @@ function inspectTransition({ sourceRecipe, targetRecipe, snapshot, signal } = {}
31
39
  if (nativeSource && sourceProviderId === targetProviderId) {
32
40
  return {
33
41
  action: 'reuse', reason: 'same_provider_native_history', sourceCheckpoint,
34
- estimateTokens, targetBudgetTokens: null, evidenceKind,
42
+ estimateTokens, targetBudgetTokens: null, evidenceKind, handoff: null,
35
43
  };
36
44
  }
37
45
 
@@ -40,12 +48,8 @@ function inspectTransition({ sourceRecipe, targetRecipe, snapshot, signal } = {}
40
48
  if (!sourceCheckpoint) return refuse('source_checkpoint_unknown');
41
49
  if (codexHistorySource && sourceProviderId !== targetProviderId
42
50
  && (!qualifiedCodexHistorySource
43
- || !isQualifiedCodexVersion(sourceRecipe.programVersion)
44
- || sourceRecipe.cyberAccessProgram !== null
45
- || snapshot.previousSettings?.cyberAccessProgram !== null
46
- || (snapshot.previousSettings?.programVersion !== undefined
47
- && snapshot.previousSettings?.programVersion !== null
48
- && !isQualifiedCodexVersion(snapshot.previousSettings.programVersion))
51
+ || !isNonElevatedCyberAccessProgram(sourceRecipe.cyberAccessProgram)
52
+ || !isNonElevatedCyberAccessProgram(snapshot.previousSettings?.cyberAccessProgram)
49
53
  || targetRecipe.harness !== 'codex-cli' || !isQualifiedCodexVersion(targetRecipe.harnessVersion))) {
50
54
  return refuse('native_fork_adapter_unqualified', { sourceCheckpoint });
51
55
  }
@@ -71,7 +75,27 @@ function inspectTransition({ sourceRecipe, targetRecipe, snapshot, signal } = {}
71
75
  }
72
76
  }
73
77
  if (snapshot.opaque === true || has(snapshot.historyKinds, 'opaque')) return refuse('opaque_history_not_portable', { sourceCheckpoint });
74
- if (snapshot.compacted === true) return refuse('compacted_source_fork_unqualified', { sourceCheckpoint });
78
+ // D-2: native media is portable exactly when the target is Codex, which strips images the target
79
+ // cannot accept (core/src/context_manager/normalize.rs:346 @ rust-v0.163.0). A non-Codex target
80
+ // gets a plain refusal instead of a silent rewrite of the source history.
81
+ if (has(snapshot.historyKinds, 'native-media') && targetRecipe.harness !== 'codex-cli') {
82
+ return refuse('history_media_not_portable', { sourceCheckpoint });
83
+ }
84
+ // D-5/D-6: a sealed `compaction` item is carried by a plaintext handoff the SOURCE model writes
85
+ // before the switch. Exactly one sealed digest can be covered, and only the native openai source
86
+ // (whose own model can be asked to write it) can produce one; everything else keeps a named refusal.
87
+ const sealed = (Array.isArray(snapshot.encryptedCompactions) ? snapshot.encryptedCompactions : [])
88
+ .filter((sha) => typeof sha === 'string' && /^[a-f0-9]{64}$/.test(sha));
89
+ if (sealed.length > 1) return refuse('encrypted_compaction_multiple', { sourceCheckpoint });
90
+ const handoff = sealed.length === 1 && snapshot.handoffCovers !== true
91
+ ? (qualifiedCodexHistorySource && origin === 'native-codex' && sourceProviderId === 'openai'
92
+ ? { required: true, compactionSha256: sealed[0] } : null)
93
+ : null;
94
+ if (sealed.length === 1 && snapshot.handoffCovers !== true && !handoff) {
95
+ return refuse('encrypted_compaction_handoff_unavailable', { sourceCheckpoint });
96
+ }
97
+ // D-4: a plaintext compaction is portable. Codex fork rebuilds the window from
98
+ // replacement_history, and no test, doc or incident ever justified the blanket refusal.
75
99
  if (snapshot.encrypted === true || has(snapshot.historyKinds, 'encrypted')) return refuse('encrypted_history_not_portable', { sourceCheckpoint });
76
100
  if (!Array.isArray(snapshot.historyKinds) || snapshot.historyKinds.length === 0
77
101
  || snapshot.historyKinds.some((kind) => !PORTABLE_HISTORY_KINDS.has(kind))) {
@@ -126,7 +150,10 @@ function inspectTransition({ sourceRecipe, targetRecipe, snapshot, signal } = {}
126
150
  : null;
127
151
  const evidence = { sourceCheckpoint, estimateTokens, targetBudgetTokens, evidenceKind,
128
152
  ...(preTurnCompaction ? { preTurnCompaction } : {}) };
129
- if (estimateTokens <= targetBudgetTokens) return { action: 'reuse', reason: 'history_fits_target', ...evidence };
153
+ // The injected handoff is target context the source estimate cannot see, so the comparison
154
+ // reserves it while every reported evidence field keeps the measured source value.
155
+ const budgetTokens = handoff?.required ? estimateTokens + HANDOFF_TOKEN_RESERVE : estimateTokens;
156
+ if (budgetTokens <= targetBudgetTokens) return { action: 'reuse', reason: 'history_fits_target', ...evidence, handoff };
130
157
 
131
158
  // Phase A has no qualified source-owned compact adapter. Native API acknowledgements alone
132
159
  // are not durable checkpoint receipts, so a recipe flag cannot authorize mutation here.
@@ -191,4 +218,4 @@ function refuse(reason, details = {}) {
191
218
  return { action: 'unsupported', reason, ...details };
192
219
  }
193
220
 
194
- module.exports = { inspectTransition, computeTargetBudget };
221
+ module.exports = { inspectTransition, computeTargetBudget, PORTABLE_HISTORY_KINDS, HANDOFF_TOKEN_RESERVE };
@@ -0,0 +1,203 @@
1
+ 'use strict';
2
+
3
+ // WP-0 (stale-pty-host-evacuation-2026-10-24.html, D-6 §5/§6, S-12): the ONE
4
+ // shared op-stamp slot pair in headless-term-service.js could only ever name
5
+ // whichever op had stamped last, and any op finishing zeroed it unconditionally.
6
+ // This module keeps a PER-SESSION in-flight op table instead, so a wedged op
7
+ // names its own session (and the depth of work queued behind it) even while
8
+ // another session is happily churning.
9
+ //
10
+ // Report-only by construction: nothing here can reject, time out, retry or
11
+ // reorder an op (AC-0e). Every entry is a plain value; `now` is injected so
12
+ // tests can grow an age without a wall clock.
13
+
14
+ // An op that has been in flight this long is worth a one-shot
15
+ // `headless-op-overdue` diagnostic (D-6 §6): long enough that a healthy host
16
+ // never trips it, short enough to name the wedge inside the watchdog's own
17
+ // minute-scale reporting.
18
+ const HEADLESS_OP_OVERDUE_MS = 30_000;
19
+ // How many entries a threadpool frame may carry (D-6 §6: the five oldest).
20
+ const HEADLESS_OP_FRAME_MAX = 5;
21
+
22
+ function _sidOf(value) {
23
+ return value == null ? '' : String(value);
24
+ }
25
+
26
+ // createInFlightOps({ now }) -> { begin, end, noteQueued, list, oldest }.
27
+ //
28
+ // `queued` is the number of ops enqueued for that session and not yet started,
29
+ // i.e. the depth waiting behind the running one. It is tracked in `waiting` (a
30
+ // session -> count map) rather than on the entry, so two ops enqueued
31
+ // synchronously -- before the chain's first microtask stamps either of them --
32
+ // still count correctly: enqueue bumps `waiting`, a start decrements it, and
33
+ // list()/begin() report the remainder.
34
+ function createInFlightOps({ now = Date.now } = {}) {
35
+ const entries = new Map(); // sessionId -> { sessionId, opType, startedAt, seq, queued }
36
+ const waiting = new Map(); // sessionId -> ops enqueued but not yet started
37
+ let seqCounter = 0;
38
+
39
+ const waitingFor = (sid) => waiting.get(sid) || 0;
40
+
41
+ function _setWaiting(sid, count) {
42
+ if (count > 0) waiting.set(sid, count);
43
+ else waiting.delete(sid);
44
+ }
45
+
46
+ // Called when an op actually starts (the stamp site of the per-session chain,
47
+ // and ONLY that chain -- an op dispatched outside it, e.g. a direct
48
+ // getViewportText, must not register here: `entries` holds one row per session,
49
+ // so it would overwrite a wedged chain op and its end() would then delete the
50
+ // wedge). Returns its seq, which the caller passes back to end() so a stale
51
+ // finisher cannot delete a newer op.
52
+ function begin(sessionId, opType) {
53
+ const sid = _sidOf(sessionId);
54
+ const behind = Math.max(0, waitingFor(sid) - 1);
55
+ _setWaiting(sid, behind);
56
+ const seq = (seqCounter += 1);
57
+ entries.set(sid, {
58
+ sessionId: sid,
59
+ opType: opType == null || opType === '' ? 'none' : String(opType),
60
+ startedAt: now(),
61
+ seq,
62
+ queued: behind,
63
+ });
64
+ return seq;
65
+ }
66
+
67
+ // Compare-and-clear: deletes the entry only if this seq is still the current
68
+ // one for the session. A late finisher from an earlier op is a no-op.
69
+ function end(sessionId, seq) {
70
+ const sid = _sidOf(sessionId);
71
+ const current = entries.get(sid);
72
+ if (!current || current.seq !== seq) return false;
73
+ entries.delete(sid);
74
+ return true;
75
+ }
76
+
77
+ // Called at enqueue time, before the op starts.
78
+ function noteQueued(sessionId, delta = 1) {
79
+ const sid = _sidOf(sessionId);
80
+ const next = Math.max(0, waitingFor(sid) + (Number(delta) || 0));
81
+ _setWaiting(sid, next);
82
+ const current = entries.get(sid);
83
+ if (current) current.queued = next;
84
+ return next;
85
+ }
86
+
87
+ function list() {
88
+ const out = [];
89
+ for (const entry of entries.values()) {
90
+ out.push({ ...entry, queued: waitingFor(entry.sessionId) });
91
+ }
92
+ return out;
93
+ }
94
+
95
+ // The n oldest in-flight ops, oldest first: { sid8, op, ageMs, queued, seq }.
96
+ // `at` defaults to now() so a test can assert a growing age on a frozen clock.
97
+ function oldest(n, at = now()) {
98
+ const limit = Number.isFinite(Number(n)) ? Math.max(0, Math.trunc(Number(n))) : 0;
99
+ const rows = [];
100
+ for (const entry of list()) {
101
+ rows.push({
102
+ sid8: entry.sessionId.slice(0, 8),
103
+ op: entry.opType,
104
+ startedAt: entry.startedAt,
105
+ seq: entry.seq,
106
+ ageMs: Math.max(0, at - entry.startedAt),
107
+ queued: entry.queued,
108
+ });
109
+ }
110
+ rows.sort((a, b) => (a.startedAt - b.startedAt) || (a.seq - b.seq));
111
+ return rows.slice(0, limit);
112
+ }
113
+
114
+ return { begin, end, noteQueued, list, oldest };
115
+ }
116
+
117
+ // Frame-side sanitizer: the manager keeps only strings/numbers it can trust in
118
+ // the stored snapshot, capped at `max` entries, oldest first. Sorting by age
119
+ // here (not just at the producer) is what makes "≤5, oldest first" a property
120
+ // of the stored snapshot rather than of the transport. Anything unparseable is
121
+ // normalised rather than thrown away.
122
+ function sanitizeSessionOps(entries, max = HEADLESS_OP_FRAME_MAX) {
123
+ const limit = Number.isFinite(Number(max)) ? Math.max(0, Math.trunc(Number(max))) : HEADLESS_OP_FRAME_MAX;
124
+ const rows = [];
125
+ for (const raw of Array.isArray(entries) ? entries : []) {
126
+ const sid8 = _sidOf(raw?.sid8).slice(0, 8);
127
+ if (!sid8) continue;
128
+ const ageMs = Number(raw?.ageMs);
129
+ const queued = Number(raw?.queued);
130
+ const seq = Number(raw?.seq);
131
+ rows.push({
132
+ sid8,
133
+ op: raw?.op == null || raw.op === '' ? 'none' : String(raw.op),
134
+ ageMs: Number.isFinite(ageMs) ? ageMs : 0,
135
+ queued: Number.isFinite(queued) ? queued : 0,
136
+ seq: Number.isFinite(seq) ? seq : 0,
137
+ });
138
+ }
139
+ rows.sort((a, b) => b.ageMs - a.ageMs); // stable: equal ages keep producer order
140
+ return rows.slice(0, limit);
141
+ }
142
+
143
+ // Edge trigger for the report-only `headless-op-overdue` row (D-6 §6): at most
144
+ // one hit per (hostId, sid8, seq) at or above thresholdMs. Each host's dedupe
145
+ // set is re-derived from ITS OWN latest frame, so it can never grow past the ops
146
+ // actually present in that frame -- and a seq that leaves the frame and later
147
+ // returns is a NEW seq, so it reports again.
148
+ function createOverdueEdgeTrigger({ thresholdMs = HEADLESS_OP_OVERDUE_MS } = {}) {
149
+ // hostId -> Set<'sid8|seq'>. The set is PER HOST: one shared set pruned against
150
+ // whichever host framed last would drop another host's keys, and that host's
151
+ // next frame would then re-report an op it had already reported.
152
+ const reportedByHost = new Map();
153
+ const keyOf = (entry) => `${entry.sid8}|${entry.seq}`;
154
+
155
+ function next(frameEntries, hostId, resolveSessionId) {
156
+ const entries = Array.isArray(frameEntries) ? frameEntries : [];
157
+ const host = String(hostId);
158
+ const present = new Set();
159
+ for (const entry of entries) present.add(keyOf(entry));
160
+ if (!reportedByHost.has(host)) reportedByHost.set(host, new Set());
161
+ const reported = reportedByHost.get(host);
162
+ for (const key of reported) {
163
+ if (!present.has(key)) reported.delete(key);
164
+ }
165
+ if (reported.size === 0) reportedByHost.delete(host);
166
+ const hits = [];
167
+ for (const entry of entries) {
168
+ if (!(Number(entry.ageMs) >= thresholdMs)) continue;
169
+ const key = keyOf(entry);
170
+ let seen = reportedByHost.get(host);
171
+ if (!seen) { seen = new Set(); reportedByHost.set(host, seen); }
172
+ if (seen.has(key)) continue;
173
+ seen.add(key);
174
+ hits.push({
175
+ sessionId: typeof resolveSessionId === 'function' ? resolveSessionId(entry.sid8) : entry.sid8,
176
+ details: {
177
+ hostId: host,
178
+ op: String(entry.op),
179
+ ageMs: Number(entry.ageMs),
180
+ queued: Number(entry.queued) || 0,
181
+ seq: Number(entry.seq) || 0,
182
+ },
183
+ });
184
+ }
185
+ return hits;
186
+ }
187
+
188
+ function size() {
189
+ let total = 0;
190
+ for (const set of reportedByHost.values()) total += set.size;
191
+ return total;
192
+ }
193
+
194
+ return { next, size };
195
+ }
196
+
197
+ module.exports = {
198
+ createInFlightOps,
199
+ createOverdueEdgeTrigger,
200
+ sanitizeSessionOps,
201
+ HEADLESS_OP_OVERDUE_MS,
202
+ HEADLESS_OP_FRAME_MAX,
203
+ };
@@ -29,6 +29,48 @@ function headlessMissingReplies(message) {
29
29
  switch (msg.type) {
30
30
  case 'serialize':
31
31
  return [{ type: 'serialized', ...base, data: '', cols: 0, rows: 0, fp: '', missing: true }];
32
+ // WP-1 (stale-pty-host-evacuation-2026-10-24.html §5 D-5, §6 missing-reply case, §9 S-11):
33
+ // 'serialize-bootstrap' shares its body with 'serialize' in handleMessage
34
+ // (lib/headless-term-service.js:1796-1804), so it must share the REPLY TYPE too. The bootstrap
35
+ // gate listens only for `type === 'serialized'` with its own requestId (server.js:40730) and
36
+ // clears the moment `bootstrap` is absent (server.js:40746-40749, reason 'serialize-invalid').
37
+ // The generic default answer below produced 'serialize-bootstrap-result', which the gate ignored
38
+ // -- an honest "missing" was indistinguishable from a hung host, and the cold client waited out
39
+ // the 3 s deadline (reason 'deadline', phase 'serializing').
40
+ case 'serialize-bootstrap':
41
+ return [{ type: 'serialized', ...base, data: '', cols: 0, rows: 0, fp: '', bootstrap: null, missing: true }];
42
+ // 'recheck' is answered with 'feed-result' on BOTH normal branches (lib/headless-term-service.js:1506-1510
43
+ // for a session with no mirror, :1532-1544 for a live one) -- server.js keys the pending approval
44
+ // entry on that type, so the generic 'recheck-result' left the caller waiting for a reply it never got.
45
+ case 'recheck':
46
+ return [{
47
+ type: 'feed-result', ...base, hasApproval: false, providerId: null, text: null,
48
+ rawDetected: false, rawText: null, debugLines: [], missing: true,
49
+ }];
50
+ // The bootstrap handshake's two post-serialize ops answer 'terminal-bootstrap-activated' /
51
+ // 'terminal-bootstrap-flushed', keyed by clientId + socketGeneration + requestId
52
+ // (lib/headless-term-service.js:1956-1960, :1969-1973). server.js clears its gate only when it can
53
+ // find the client by those three fields, and an `accepted: false` is what turns "there is no host
54
+ // state to activate/flush" into an immediate failure instead of the gate's phase deadline
55
+ // (server.js:31247-31260 activated, :31300-31346 flushed).
56
+ case 'terminal-bootstrap-ack':
57
+ return [{
58
+ type: 'terminal-bootstrap-activated', ...base,
59
+ clientId: String(msg.clientId || ''), socketGeneration: Number(msg.socketGeneration || 0),
60
+ accepted: false, missing: true,
61
+ }];
62
+ case 'terminal-bootstrap-flush':
63
+ return [{
64
+ type: 'terminal-bootstrap-flushed', ...base,
65
+ clientId: String(msg.clientId || ''), socketGeneration: Number(msg.socketGeneration || 0),
66
+ accepted: false, throughSeq: 0, missing: true,
67
+ }];
68
+ // The archive-quiesce barrier and the painted-row drop both always post their own reply type
69
+ // (lib/headless-term-service.js:2173, :2180) -- the caller matches on (type, sessionId, requestId).
70
+ case 'flush-archive':
71
+ return [{ type: 'archive-flushed', ...base, missing: true }];
72
+ case 'drop-painted':
73
+ return [{ type: 'painted-dropped', ...base, missing: true }];
32
74
  case 'getViewportText':
33
75
  return [{ type: 'viewport-text', ...base, text: '', missing: true, cols: 0, rows: 0 }];
34
76
  case 'getText':