create-walle 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (381) hide show
  1. package/README.md +88 -41
  2. package/lib/owner-write-safety.js +135 -0
  3. package/lib/public-runtime.js +13 -5
  4. package/lib/release-schema.json +3 -2
  5. package/lib/runtime-closure-policy.json +7 -5
  6. package/package.json +10 -4
  7. package/template/bin/deps-doctor.js +1 -1
  8. package/template/bin/node-bin.sh +11 -0
  9. package/template/coding-task-manager/api-prompts.js +10 -22
  10. package/template/coding-task-manager/bin/capture-codex-rollout-records.js +417 -0
  11. package/template/coding-task-manager/bin/census-codex-lead-history.js +325 -0
  12. package/template/coding-task-manager/bin/claude-hook.js +7 -3
  13. package/template/coding-task-manager/bin/claude-table-parity.js +307 -0
  14. package/template/coding-task-manager/bin/conversation-storage-auto-migration-lib.js +16 -1
  15. package/template/coding-task-manager/bin/conversation-storage-generation-evidence.js +9 -2
  16. package/template/coding-task-manager/bin/conversation-storage-service-entry-lib.js +16 -1
  17. package/template/coding-task-manager/bin/conversation-storage-walle-owner.js +247 -0
  18. package/template/coding-task-manager/bin/ctm-machine-pair.js +160 -0
  19. package/template/coding-task-manager/bin/lead-wait-census.js +1348 -0
  20. package/template/coding-task-manager/bin/repair-foreign-clone.js +564 -0
  21. package/template/coding-task-manager/bin/repair-resume-echo-titles.js +248 -0
  22. package/template/coding-task-manager/bin/seam-replay-codex.js +301 -0
  23. package/template/coding-task-manager/bin/write-lock-bench.js +111 -0
  24. package/template/coding-task-manager/db.js +705 -286
  25. package/template/coding-task-manager/git-utils.js +44 -0
  26. package/template/coding-task-manager/lib/adopted-host-redial.js +4 -0
  27. package/template/coding-task-manager/lib/agent-presets.js +15 -0
  28. package/template/coding-task-manager/lib/agent-title-apply.js +88 -0
  29. package/template/coding-task-manager/lib/agent-title-backfill-read.js +92 -0
  30. package/template/coding-task-manager/lib/api/host-stats.js +98 -0
  31. package/template/coding-task-manager/lib/api/ingress.js +35 -12
  32. package/template/coding-task-manager/lib/api/machine-pairing.js +285 -0
  33. package/template/coding-task-manager/lib/api/machine-pool.js +307 -0
  34. package/template/coding-task-manager/lib/api/machines.js +461 -0
  35. package/template/coding-task-manager/lib/api/misc-ops.js +20 -5
  36. package/template/coding-task-manager/lib/api/providers.js +53 -6
  37. package/template/coding-task-manager/lib/api/resume-from-store.js +25 -0
  38. package/template/coding-task-manager/lib/api/sessions.js +160 -8
  39. package/template/coding-task-manager/lib/api/transcript-history.js +17 -4
  40. package/template/coding-task-manager/lib/api/worktrees.js +38 -37
  41. package/template/coding-task-manager/lib/approval-drift.js +16 -1
  42. package/template/coding-task-manager/lib/approval-logical-frame.js +5 -1
  43. package/template/coding-task-manager/lib/approval-scope.js +68 -4
  44. package/template/coding-task-manager/lib/auth-context.js +32 -2
  45. package/template/coding-task-manager/lib/auth-rate-limit.js +41 -1
  46. package/template/coding-task-manager/lib/auth-rules.js +186 -45
  47. package/template/coding-task-manager/lib/broadcast-serialize.js +2 -1
  48. package/template/coding-task-manager/lib/browser-session-broker.js +9 -1
  49. package/template/coding-task-manager/lib/browser-use-adapter.js +33 -2
  50. package/template/coding-task-manager/lib/claude-markdown-ansi.js +27 -85
  51. package/template/coding-task-manager/lib/claude-session-title.js +6 -30
  52. package/template/coding-task-manager/lib/claude-table-layout.js +322 -0
  53. package/template/coding-task-manager/lib/client-diag-handler.js +25 -0
  54. package/template/coding-task-manager/lib/codex-config-guard.js +33 -3
  55. package/template/coding-task-manager/lib/codex-harness-qualification.js +12 -0
  56. package/template/coding-task-manager/lib/codex-history-terminal-renderer.js +33 -33
  57. package/template/coding-task-manager/lib/codex-launch-health.js +29 -2
  58. package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
  59. package/template/coding-task-manager/lib/codex-local-links.js +267 -0
  60. package/template/coding-task-manager/lib/codex-markdown-lists.js +160 -0
  61. package/template/coding-task-manager/lib/codex-native-history-prefix.js +4 -0
  62. package/template/coding-task-manager/lib/codex-paths.js +2 -1
  63. package/template/coding-task-manager/lib/codex-source-handoff.js +186 -0
  64. package/template/coding-task-manager/lib/coding-agent-models.js +21 -30
  65. package/template/coding-task-manager/lib/coding-session-model-adapters.js +27 -2
  66. package/template/coding-task-manager/lib/coding-session-model-projection.js +5 -0
  67. package/template/coding-task-manager/lib/coding-session-model-runtime.js +1 -2
  68. package/template/coding-task-manager/lib/coding-session-model-state.js +10 -0
  69. package/template/coding-task-manager/lib/coding-session-model-sync-state.js +0 -1
  70. package/template/coding-task-manager/lib/conversation-import/cursor-guard-owner.js +10 -0
  71. package/template/coding-task-manager/lib/create/resolve-launch-params.js +8 -1
  72. package/template/coding-task-manager/lib/ctm-boot-lifecycle.js +17 -1
  73. package/template/coding-task-manager/lib/db-domain-tables.js +7 -0
  74. package/template/coding-task-manager/lib/db-domains.js +21 -2
  75. package/template/coding-task-manager/lib/db-owner-op-registry.js +7 -0
  76. package/template/coding-task-manager/lib/db-owner-worker-client.js +22 -0
  77. package/template/coding-task-manager/lib/db-schema-source-files.js +1 -0
  78. package/template/coding-task-manager/lib/feature-manifest.js +1 -3
  79. package/template/coding-task-manager/lib/fs-watcher.js +3 -2
  80. package/template/coding-task-manager/lib/harness-history-transition.js +41 -14
  81. package/template/coding-task-manager/lib/headless-inflight-ops.js +203 -0
  82. package/template/coding-task-manager/lib/headless-missing-reply.js +42 -0
  83. package/template/coding-task-manager/lib/headless-term-service.js +60 -23
  84. package/template/coding-task-manager/lib/history-transition-read-op.js +179 -23
  85. package/template/coding-task-manager/lib/host-control-dial.js +167 -0
  86. package/template/coding-task-manager/lib/host-handoff-orchestrator.js +16 -2
  87. package/template/coding-task-manager/lib/host-handoff.js +51 -13
  88. package/template/coding-task-manager/lib/host-health-ledger.js +164 -12
  89. package/template/coding-task-manager/lib/host-self-truth-watchdog.js +4 -0
  90. package/template/coding-task-manager/lib/host-stats.js +328 -0
  91. package/template/coding-task-manager/lib/host-threadpool-evacuation.js +160 -0
  92. package/template/coding-task-manager/lib/host-threadpool-monitor.js +14 -1
  93. package/template/coding-task-manager/lib/hosted-input-dispatch.js +203 -0
  94. package/template/coding-task-manager/lib/hosted-sync-worker.js +113 -5
  95. package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +4 -0
  96. package/template/coding-task-manager/lib/instance-manifest-runtime.js +3 -0
  97. package/template/coding-task-manager/lib/instance-manifest.js +13 -1
  98. package/template/coding-task-manager/lib/instance-sync-status.js +15 -0
  99. package/template/coding-task-manager/lib/kernel/diag-log.js +12 -0
  100. package/template/coding-task-manager/lib/launch-model-override.js +160 -1
  101. package/template/coding-task-manager/lib/launch-plan-worker.js +59 -2
  102. package/template/coding-task-manager/lib/lead-model-observer.js +17 -2
  103. package/template/coding-task-manager/lib/lead-model-switch-runtime.js +57 -4
  104. package/template/coding-task-manager/lib/lead-model-switch.js +11 -2
  105. package/template/coding-task-manager/lib/lead-model-transition-runtime.js +42 -4
  106. package/template/coding-task-manager/lib/lead-model-transition-store.js +61 -3
  107. package/template/coding-task-manager/lib/legacy-draft-cancellation-recovery.js +111 -0
  108. package/template/coding-task-manager/lib/login-tripwire.js +18 -0
  109. package/template/coding-task-manager/lib/machine-pairing.js +238 -0
  110. package/template/coding-task-manager/lib/machine-pool.js +409 -0
  111. package/template/coding-task-manager/lib/machine-registry.js +749 -0
  112. package/template/coding-task-manager/lib/machine-self.js +66 -0
  113. package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +56 -4
  114. package/template/coding-task-manager/lib/media-journal-control-reader.js +473 -0
  115. package/template/coding-task-manager/lib/media-journal.js +84 -27
  116. package/template/coding-task-manager/lib/media-store-options.js +43 -15
  117. package/template/coding-task-manager/lib/migrations/v56-host-samples-and-machine-pool.js +76 -0
  118. package/template/coding-task-manager/lib/mirror-feed-sanitize.js +12 -3
  119. package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +16 -3
  120. package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
  121. package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
  122. package/template/coding-task-manager/lib/model-proxy/collaboration-codec.js +94 -0
  123. package/template/coding-task-manager/lib/model-proxy/handoff.js +494 -0
  124. package/template/coding-task-manager/lib/model-proxy/lead-worker-picks.js +25 -0
  125. package/template/coding-task-manager/lib/model-proxy/native-lead-listener.js +61 -7
  126. package/template/coding-task-manager/lib/model-proxy/proxy-cli.js +200 -0
  127. package/template/coding-task-manager/lib/model-proxy/proxy-instance-record.js +257 -0
  128. package/template/coding-task-manager/lib/model-proxy/responses-face.js +24 -2
  129. package/template/coding-task-manager/lib/model-proxy/stream-accounting.js +205 -0
  130. package/template/coding-task-manager/lib/model-proxy/tokens.js +78 -0
  131. package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
  132. package/template/coding-task-manager/lib/native-history-control.js +62 -14
  133. package/template/coding-task-manager/lib/native-lead-settings-client.js +2 -1
  134. package/template/coding-task-manager/lib/native-lead-tls.js +18 -4
  135. package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +628 -64
  136. package/template/coding-task-manager/lib/owner-action-manifest-data.json +14 -0
  137. package/template/coding-task-manager/lib/painted-row-recorder.js +9 -1
  138. package/template/coding-task-manager/lib/prompt-admission-trace.js +69 -0
  139. package/template/coding-task-manager/lib/prompt-submission-contract.js +21 -1
  140. package/template/coding-task-manager/lib/provider-login-broker.js +192 -9
  141. package/template/coding-task-manager/lib/provider-login-capture.js +153 -0
  142. package/template/coding-task-manager/lib/queue-draft-status-overlay.js +36 -0
  143. package/template/coding-task-manager/lib/raw-codex-history-window.js +5 -3
  144. package/template/coding-task-manager/lib/raw-history-block.js +3 -1
  145. package/template/coding-task-manager/lib/read-pool-client.js +36 -24
  146. package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
  147. package/template/coding-task-manager/lib/read-pool-telemetry.js +14 -5
  148. package/template/coding-task-manager/lib/recent-catalog-query.js +26 -0
  149. package/template/coding-task-manager/lib/recent-session-catalog-response.js +9 -0
  150. package/template/coding-task-manager/lib/remote-command-context.js +12 -0
  151. package/template/coding-task-manager/lib/remote-command-runtime.js +18 -5
  152. package/template/coding-task-manager/lib/remote-machine-catalog.js +1062 -0
  153. package/template/coding-task-manager/lib/remote-machine-create.js +693 -0
  154. package/template/coding-task-manager/lib/remote-machine-link.js +939 -0
  155. package/template/coding-task-manager/lib/remote-machine-proxy.js +520 -0
  156. package/template/coding-task-manager/lib/remote-session-forward.js +306 -0
  157. package/template/coding-task-manager/lib/remote-submission-status.js +6 -0
  158. package/template/coding-task-manager/lib/restart-outcome-truth.js +20 -3
  159. package/template/coding-task-manager/lib/restart-preflight.js +9 -2
  160. package/template/coding-task-manager/lib/restore-backoff-gate.js +10 -2
  161. package/template/coding-task-manager/lib/restore-prepare-retry.js +12 -5
  162. package/template/coding-task-manager/lib/restore-resume-batch.js +22 -1
  163. package/template/coding-task-manager/lib/resume-blocked-wire.js +62 -1
  164. package/template/coding-task-manager/lib/resume-cwd.js +13 -0
  165. package/template/coding-task-manager/lib/resume-from-store.js +12 -1
  166. package/template/coding-task-manager/lib/resume-origin-facts.js +38 -1
  167. package/template/coding-task-manager/lib/resume-preflight.js +21 -16
  168. package/template/coding-task-manager/lib/review-repo-admission.js +39 -6
  169. package/template/coding-task-manager/lib/review-repo-registry.js +17 -0
  170. package/template/coding-task-manager/lib/root-delivery-runtime.js +72 -15
  171. package/template/coding-task-manager/lib/root-delivery-selection.js +37 -1
  172. package/template/coding-task-manager/lib/root-prompt-admission.js +43 -11
  173. package/template/coding-task-manager/lib/root-prompt-flow.js +175 -38
  174. package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
  175. package/template/coding-task-manager/lib/server-memory-sampler-runtime.js +10 -0
  176. package/template/coding-task-manager/lib/server-memory-sampler.js +41 -2
  177. package/template/coding-task-manager/lib/session-analysis-memory.js +2 -0
  178. package/template/coding-task-manager/lib/session-create/codex-native-provider-gate.js +218 -0
  179. package/template/coding-task-manager/lib/session-create/codex-resume-exit-classify.js +52 -0
  180. package/template/coding-task-manager/lib/session-create/delegate-worker.js +95 -6
  181. package/template/coding-task-manager/lib/session-create/identity.js +20 -1
  182. package/template/coding-task-manager/lib/session-create/prepared.js +69 -1
  183. package/template/coding-task-manager/lib/session-create/provider-launch-context.js +77 -0
  184. package/template/coding-task-manager/lib/session-create/provider-process.js +38 -39
  185. package/template/coding-task-manager/lib/session-create/response.js +30 -12
  186. package/template/coding-task-manager/lib/session-create/transcript.js +13 -4
  187. package/template/coding-task-manager/lib/session-create/worker-continuation.js +43 -1
  188. package/template/coding-task-manager/lib/session-create/worker-notices.js +94 -5
  189. package/template/coding-task-manager/lib/session-create/worker-observability.js +99 -7
  190. package/template/coding-task-manager/lib/session-create/worker-stop.js +11 -3
  191. package/template/coding-task-manager/lib/session-create/worker-wait-watcher.js +315 -0
  192. package/template/coding-task-manager/lib/session-geometry-authority-handler.js +10 -1
  193. package/template/coding-task-manager/lib/session-host-manager.js +152 -206
  194. package/template/coding-task-manager/lib/session-jobs.js +17 -9
  195. package/template/coding-task-manager/lib/session-lifecycle.js +20 -50
  196. package/template/coding-task-manager/lib/session-model-action-preconditions.js +68 -3
  197. package/template/coding-task-manager/lib/session-resource-sampler.js +67 -9
  198. package/template/coding-task-manager/lib/session-standup.js +67 -4
  199. package/template/coding-task-manager/lib/session-status-ledger.js +42 -3
  200. package/template/coding-task-manager/lib/session-status-runtime.js +45 -1
  201. package/template/coding-task-manager/lib/session-status.js +2 -2
  202. package/template/coding-task-manager/lib/session-title-mirror.js +25 -57
  203. package/template/coding-task-manager/lib/session-transcript-trash.js +8 -1
  204. package/template/coding-task-manager/lib/startup-pointer-write.js +57 -0
  205. package/template/coding-task-manager/lib/state-sync/painted-frame-field.js +11 -0
  206. package/template/coding-task-manager/lib/storage/coding-session-model-store.js +21 -22
  207. package/template/coding-task-manager/lib/surviving-owner-bootstrap.js +7 -2
  208. package/template/coding-task-manager/lib/surviving-owner-recovery.js +2 -1
  209. package/template/coding-task-manager/lib/terminal-draft-http-status.js +25 -0
  210. package/template/coding-task-manager/lib/transcript-archiver.js +229 -229
  211. package/template/coding-task-manager/lib/transcript-frame-cache.js +12 -2
  212. package/template/coding-task-manager/lib/transcript-guard-job.js +55 -4
  213. package/template/coding-task-manager/lib/transcript-link.js +77 -0
  214. package/template/coding-task-manager/lib/transcript-manifest-store.js +8 -4
  215. package/template/coding-task-manager/lib/transcript-source.js +8 -3
  216. package/template/coding-task-manager/lib/transcript-store.js +40 -0
  217. package/template/coding-task-manager/lib/tunnel-lane-governor.js +6 -3
  218. package/template/coding-task-manager/lib/typing-interaction-trace.js +136 -2
  219. package/template/coding-task-manager/lib/typing-worker-stages.js +208 -1
  220. package/template/coding-task-manager/lib/usage-ledger/claude-parse.js +53 -2
  221. package/template/coding-task-manager/lib/usage-ledger/ingest.js +13 -4
  222. package/template/coding-task-manager/lib/usage-ledger/read.js +14 -0
  223. package/template/coding-task-manager/lib/usage-ledger/schema.js +49 -1
  224. package/template/coding-task-manager/lib/usage-ledger/store.js +337 -6
  225. package/template/coding-task-manager/lib/usage-ledger/task-read.js +123 -5
  226. package/template/coding-task-manager/lib/visible-revision-watchdog.js +8 -12
  227. package/template/coding-task-manager/lib/wake-skip-reasons.js +46 -0
  228. package/template/coding-task-manager/lib/walle-supervisor.js +98 -14
  229. package/template/coding-task-manager/lib/worker-failure-classify.js +13 -1
  230. package/template/coding-task-manager/lib/ws/input.js +1 -0
  231. package/template/coding-task-manager/lib/ws/state-sync.js +19 -2
  232. package/template/coding-task-manager/lib/ws/terminal-attach.js +32 -1
  233. package/template/coding-task-manager/lib/ws-router.js +68 -1
  234. package/template/coding-task-manager/lib/ws-send-admission.js +22 -4
  235. package/template/coding-task-manager/oauth-proxy.js +99 -55
  236. package/template/coding-task-manager/package-lock.json +24 -0
  237. package/template/coding-task-manager/package.json +3 -0
  238. package/template/coding-task-manager/protocol/message-table.json +0 -10
  239. package/template/coding-task-manager/protocol/messages.js +0 -10
  240. package/template/coding-task-manager/protocol/ws-schema.json +0 -20
  241. package/template/coding-task-manager/protocol/ws-schema.md +0 -2
  242. package/template/coding-task-manager/providers/codex-mcp-extract.js +121 -19
  243. package/template/coding-task-manager/providers/codex-mcp.js +14 -5
  244. package/template/coding-task-manager/public/css/app.css +279 -2
  245. package/template/coding-task-manager/public/css/reviews.css +4 -0
  246. package/template/coding-task-manager/public/index.html +297 -240
  247. package/template/coding-task-manager/public/js/app/machines-page.js +1252 -0
  248. package/template/coding-task-manager/public/js/app/models-tab.js +546 -36
  249. package/template/coding-task-manager/public/js/app/worktrees-panel.js +7 -5
  250. package/template/coding-task-manager/public/js/background-work.js +13 -7
  251. package/template/coding-task-manager/public/js/client-health-core.js +81 -0
  252. package/template/coding-task-manager/public/js/client-health-main.js +61 -0
  253. package/template/coding-task-manager/public/js/coding-session-model-presentation.js +44 -4
  254. package/template/coding-task-manager/public/js/core/session-commands.js +676 -0
  255. package/template/coding-task-manager/public/js/core/session-selectors.js +5 -1
  256. package/template/coding-task-manager/public/js/history-app-seam.js +26 -2
  257. package/template/coding-task-manager/public/js/history-painted-rows.js +17 -2
  258. package/template/coding-task-manager/public/js/history-reader-park.js +13 -1
  259. package/template/coding-task-manager/public/js/history-scroller.js +187 -9
  260. package/template/coding-task-manager/public/js/history-transcript-pages.js +5 -5
  261. package/template/coding-task-manager/public/js/machine-pool-shared.js +436 -0
  262. package/template/coding-task-manager/public/js/private-state-scope.js +31 -3
  263. package/template/coding-task-manager/public/js/prompt-submission-client.js +173 -4
  264. package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
  265. package/template/coding-task-manager/public/js/protocol.js +0 -10
  266. package/template/coding-task-manager/public/js/queue-panel-submission-reconcile.js +174 -1
  267. package/template/coding-task-manager/public/js/reviews.js +7 -3
  268. package/template/coding-task-manager/public/js/root-draft-lifetime.js +139 -2
  269. package/template/coding-task-manager/public/js/root-queue-builder-lifetime.js +16 -0
  270. package/template/coding-task-manager/public/js/row-join.js +18 -3
  271. package/template/coding-task-manager/public/js/session-model-picker.js +1 -1
  272. package/template/coding-task-manager/public/js/session-search-utils.js +46 -19
  273. package/template/coding-task-manager/public/js/session-usage-view.js +1 -0
  274. package/template/coding-task-manager/public/js/state-sync-client.js +4 -0
  275. package/template/coding-task-manager/public/js/terminal/runtime-02.js +16 -7
  276. package/template/coding-task-manager/public/js/terminal/runtime-03.js +41 -0
  277. package/template/coding-task-manager/public/js/terminal/runtime-04.js +41 -4
  278. package/template/coding-task-manager/public/js/terminal/runtime-12.js +52 -2
  279. package/template/coding-task-manager/public/js/terminal/runtime-16.js +12 -12
  280. package/template/coding-task-manager/public/js/terminal/runtime-18.js +14 -0
  281. package/template/coding-task-manager/public/js/terminal/runtime-19.js +8 -4
  282. package/template/coding-task-manager/public/js/terminal/runtime-20.js +210 -73
  283. package/template/coding-task-manager/public/js/terminal/runtime-21.js +19 -6
  284. package/template/coding-task-manager/public/js/terminal/runtime-22.js +86 -0
  285. package/template/coding-task-manager/public/js/terminal-history-viewport.js +1 -3
  286. package/template/coding-task-manager/public/js/terminal-wheel.js +39 -2
  287. package/template/coding-task-manager/public/js/walle/chat-conversation.js +11 -3
  288. package/template/coding-task-manager/public/js/walle-session.js +30 -7
  289. package/template/coding-task-manager/public/m/app.css +226 -8
  290. package/template/coding-task-manager/public/m/app.js +502 -9
  291. package/template/coding-task-manager/public/m/index.html +38 -1
  292. package/template/coding-task-manager/public/m/machines.js +1313 -0
  293. package/template/coding-task-manager/public/m/pool-failover.js +391 -0
  294. package/template/coding-task-manager/public/m/private-queue-actions.js +26 -7
  295. package/template/coding-task-manager/public/m/sw.js +5 -0
  296. package/template/coding-task-manager/queue-engine.js +13 -0
  297. package/template/coding-task-manager/server/approvals.js +5 -6
  298. package/template/coding-task-manager/server/boot.js +136 -17
  299. package/template/coding-task-manager/server.js +964 -425
  300. package/template/coding-task-manager/session-integrity.js +7 -9
  301. package/template/coding-task-manager/workers/db-owner-worker.js +53 -7
  302. package/template/coding-task-manager/workers/read-pool-worker.js +17 -6
  303. package/template/coding-task-manager/workers/session-host-pool-process.js +18 -4
  304. package/template/coding-task-manager/workers/session-host-process.js +14 -3
  305. package/template/coding-task-manager/workers/transcript-archive-worker.js +1 -1
  306. package/template/coding-task-manager/workers/worktree-worker.js +34 -0
  307. package/template/package-lock.json +2 -2
  308. package/template/package.json +1 -1
  309. package/template/scripts/conversation-storage-v2-migration.js +78 -11
  310. package/template/shared/conversation-storage-v2-contract.js +42 -6
  311. package/template/shared/fixtures/worker-status-payloads.json +392 -0
  312. package/template/shared/git-remote-key.js +83 -0
  313. package/template/shared/host-identity.js +98 -2
  314. package/template/shared/owner-write-safety.js +135 -0
  315. package/template/shared/public-runtime.js +13 -5
  316. package/template/shared/release-schema.json +3 -2
  317. package/template/shared/report-citations.js +14 -0
  318. package/template/shared/sqlite-write-lock.js +361 -223
  319. package/template/shared/storage/contract.js +35 -3
  320. package/template/wall-e/agent.js +21 -0
  321. package/template/wall-e/api-walle.js +5 -0
  322. package/template/wall-e/bin/brain-purge-legacy-ctm.js +3 -2
  323. package/template/wall-e/bin/walle-mcp-stdio.js +12 -6
  324. package/template/wall-e/brain/helper-rules-schema.js +129 -0
  325. package/template/wall-e/brain/migrations.js +51 -22
  326. package/template/wall-e/brain/model-deployment-facts.js +31 -1
  327. package/template/wall-e/brain/owner-call-whitelist.js +16 -0
  328. package/template/wall-e/brain/schema-ensures.js +22 -0
  329. package/template/wall-e/brain.js +1 -1
  330. package/template/wall-e/coding/host-capabilities.js +2 -2
  331. package/template/wall-e/coding/journal-dispatcher.js +51 -0
  332. package/template/wall-e/coding/session-journal.js +28 -40
  333. package/template/wall-e/coding-orchestrator.js +23 -26
  334. package/template/wall-e/fly.toml +1 -1
  335. package/template/wall-e/http/auth.js +18 -1
  336. package/template/wall-e/http/blog-views-api.js +318 -0
  337. package/template/wall-e/http/helper-rules-api.js +321 -0
  338. package/template/wall-e/http/model-proxy-providers.js +3 -1
  339. package/template/wall-e/http/telemetry-api.js +12 -0
  340. package/template/wall-e/lib/blog-view-policy.js +88 -0
  341. package/template/wall-e/lib/blog-view-store.js +169 -0
  342. package/template/wall-e/lib/client-config-lock.js +2 -1
  343. package/template/wall-e/lib/coding-journal-writer-child-client.js +67 -18
  344. package/template/wall-e/lib/error-kinds.js +10 -1
  345. package/template/wall-e/lib/feature-manifest.js +1 -1
  346. package/template/wall-e/lib/helper-rules/cell-state.js +123 -0
  347. package/template/wall-e/lib/helper-rules/evaluate.js +357 -0
  348. package/template/wall-e/lib/helper-rules/explore-rubric.md +12 -0
  349. package/template/wall-e/lib/helper-rules/grade.js +277 -0
  350. package/template/wall-e/lib/helper-rules/label.js +247 -0
  351. package/template/wall-e/lib/helper-rules/lead-call-hook.js +522 -0
  352. package/template/wall-e/lib/helper-rules/store.js +667 -0
  353. package/template/wall-e/lib/mcp-integration.js +5 -6
  354. package/template/wall-e/lib/runtime-worker-pool.js +1 -0
  355. package/template/wall-e/lib/scheduler-worker-jobs.js +6 -0
  356. package/template/wall-e/lib/telemetry-db-schema.js +37 -0
  357. package/template/wall-e/lib/telemetry-process-client.js +76 -1
  358. package/template/wall-e/llm/default-fallback.js +4 -0
  359. package/template/wall-e/llm/model-catalog.js +13 -4
  360. package/template/wall-e/llm/model-roles.js +6 -0
  361. package/template/wall-e/llm/portkey-http.js +150 -0
  362. package/template/wall-e/llm/portkey-sync.js +15 -145
  363. package/template/wall-e/llm/worker-mcp-routing.js +61 -0
  364. package/template/wall-e/llm/worker-route.js +43 -1
  365. package/template/wall-e/loops/brain-custom-writes.js +45 -0
  366. package/template/wall-e/mcp-protocol-contract.js +41 -1
  367. package/template/wall-e/mcp-server.js +13 -2
  368. package/template/wall-e/mcp-worker-schemas.js +20 -2
  369. package/template/wall-e/memory/ctm-context-client.js +25 -5
  370. package/template/wall-e/shared/release-schema.json +3 -2
  371. package/template/wall-e/shared/sqlite-write-lock.js +361 -223
  372. package/template/wall-e/shared/storage/contract.js +35 -3
  373. package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
  374. package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +6 -0
  375. package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +7 -2
  376. package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +7 -2
  377. package/template/wall-e/tools/local-tools.js +12 -5
  378. package/template/wall-e/workers/brain-owner-worker.js +9 -0
  379. package/template/wall-e/workers/coding-journal-writer-child.js +8 -1
  380. package/template/wall-e/workers/telemetry-worker.js +8 -0
  381. package/template/coding-task-manager/lib/giant-freeze-park.js +0 -31
@@ -0,0 +1,205 @@
1
+ 'use strict';
2
+
3
+ // §5 D-9 / §9 S-5, S-8: how many requests this proxy is still carrying, and what it was carrying when
4
+ // it last stopped. Both truths live here on purpose -- the exit record is nothing but the counter read
5
+ // at exit, and a second copy of that number is exactly how the two would drift apart.
6
+
7
+ const fsp = require('node:fs/promises');
8
+ const path = require('node:path');
9
+
10
+ // The health probe is itself a request, so counting it would make the counter unobservable from the one
11
+ // surface that reports it (the probe would always see its own 1). Only these two status paths are
12
+ // exempt; every other path is counted, keep-alive or not.
13
+ const UNCOUNTED_PATHS = new Set(['/_health', '/_status']);
14
+
15
+ const EXIT_RECORD_VERSION = 1;
16
+
17
+ function _pathOf(url) {
18
+ const raw = String(url || '');
19
+ const cut = raw.indexOf('?');
20
+ return cut === -1 ? raw : raw.slice(0, cut);
21
+ }
22
+
23
+ /**
24
+ * The process's own in-flight stream counter.
25
+ *
26
+ * @param {{startedAt?: string}} [options] `startedAt` is overridable only so a test can pin it.
27
+ */
28
+ function createStreamAccounting({ startedAt = new Date().toISOString() } = {}) {
29
+ let openStreams = 0;
30
+ // §5 D-3: the loaded-code hash this process published, set ONCE after listen by
31
+ // proxy-instance-record.publishInstanceRecord(). Read here so the /_health answer and the on-disk
32
+ // record can never describe different code; null until then (a start that never binds publishes none).
33
+ // §5 D-7 (WP-2): `generation` and `draining` travel the same way -- the successor of a handoff is
34
+ // generation N+1 and says so on /_health while its predecessor is still draining.
35
+ let identity = null;
36
+ // §5 D-7 step 7: the drain ends when this counter reaches zero, so the DECREMENT is what notifies --
37
+ // there is no polling loop and no second copy of the count.
38
+ const idleWaiters = new Set();
39
+
40
+ function _notifyIdle() {
41
+ const waiters = [...idleWaiters];
42
+ idleWaiters.clear();
43
+ for (const waiter of waiters) {
44
+ try {
45
+ waiter();
46
+ } catch (err) {
47
+ // REPORT: a drain waiter that throws is not the counter's problem; the drain deadline owns the
48
+ // fallback, so the failure is worth one line and nothing else.
49
+ process.stderr.write(`[model-proxy] drain waiter failed: ${(err && err.message) || 'unknown'}\n`);
50
+ }
51
+ }
52
+ }
53
+
54
+ return {
55
+ /**
56
+ * Count every request this server is still answering. An idle keep-alive SOCKET carries no request
57
+ * and is never counted; a request is in flight until its response emits 'close' (whether it
58
+ * completed, errored or was cut by the client).
59
+ */
60
+ track(server) {
61
+ server.on('request', (req, res) => {
62
+ if (UNCOUNTED_PATHS.has(_pathOf(req.url))) return;
63
+ openStreams += 1;
64
+ res.once('close', () => {
65
+ openStreams -= 1;
66
+ if (openStreams === 0) _notifyIdle();
67
+ });
68
+ });
69
+ },
70
+ count() {
71
+ return openStreams;
72
+ },
73
+ snapshot() {
74
+ return { openStreams, startedAt, identity };
75
+ },
76
+ /** §5 D-3: the code this process loaded, published once; what /_health reports from. */
77
+ setIdentity(next) {
78
+ identity = next
79
+ ? {
80
+ closureHash: next.closureHash || null,
81
+ closureFiles: Array.isArray(next.closureFiles) ? next.closureFiles : [],
82
+ generation: Number.isSafeInteger(next.generation) && next.generation > 0 ? next.generation : 1,
83
+ draining: next.draining === true,
84
+ }
85
+ : null;
86
+ },
87
+ /** §5 D-7 step 8: the predecessor answers /_health with draining:true from here until it exits. */
88
+ setDraining(value) {
89
+ const current = identity || { closureHash: null, closureFiles: [], generation: 1, draining: false };
90
+ identity = { ...current, draining: value === true };
91
+ },
92
+ /**
93
+ * §5 D-7 step 7: called ONCE by the handoff drain, on the decrement that brings the count to zero.
94
+ * @returns {Function} unsubscribe, so a drain that was already forced does not hold a callback
95
+ */
96
+ onIdle(callback) {
97
+ idleWaiters.add(callback);
98
+ return () => idleWaiters.delete(callback);
99
+ },
100
+ };
101
+ }
102
+
103
+ /**
104
+ * §9 S-8: the process-identity fields the `/_health` answer reports alongside its existing ones. The
105
+ * count is read live, never cached, so the answer can never describe an earlier moment. `generation`
106
+ * and `draining` come from the published identity (a handoff successor is generation N+1 and reports
107
+ * `draining:true` while its predecessor drains); a process that published none reports today's
108
+ * 1/false.
109
+ */
110
+ function healthFields(accounting) {
111
+ const snapshot = accounting.snapshot();
112
+ const identity = snapshot.identity || {};
113
+ return {
114
+ pid: process.pid,
115
+ generation: identity.generation || 1,
116
+ protocol: 'proxy-v1',
117
+ floor_version: 0,
118
+ open_streams: snapshot.openStreams,
119
+ draining: identity.draining === true,
120
+ started_at: snapshot.startedAt,
121
+ // §5 D-3: what CTM compares against its own disk hash to decide adopt vs hand off.
122
+ closure_hash: identity.closureHash || null,
123
+ closure_files: identity.closureFiles || [],
124
+ };
125
+ }
126
+
127
+ /**
128
+ * §9 S-8: the health fields merged into whatever JSON body the responder writes. The relay keeps
129
+ * ownership of its own five fields (unchanged, names and types); this only adds what D-9/S-8 own, and a
130
+ * non-JSON end (there is none today) is passed through untouched rather than guessed at.
131
+ */
132
+ function withHealthFields(res, accounting) {
133
+ const end = res.end.bind(res);
134
+ res.end = (chunk, ...rest) => {
135
+ let body;
136
+ try {
137
+ body = JSON.parse(Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk));
138
+ } catch {
139
+ return end(chunk, ...rest); // expected: only the health JSON body is augmented
140
+ }
141
+ return end(JSON.stringify({ ...body, ...healthFields(accounting) }), ...rest);
142
+ };
143
+ return res;
144
+ }
145
+
146
+ function exitRecordPath(dataDir) {
147
+ return path.join(dataDir, 'oauth-proxy', 'last-exit.json');
148
+ }
149
+
150
+ /**
151
+ * §9 S-5: the previous exit, written once. Atomic (tmp + rename) and owner-only, so a concurrent reader
152
+ * sees either the whole record or no record — never a half-written one. Async like the budget journals
153
+ * beside it (request-budget-store.js): the proxy awaits the write before it exits, and the supervisor
154
+ * reads it off the main thread.
155
+ * @returns {Promise<object>} the record written
156
+ */
157
+ async function writeExitRecord({ dataDir, reason, openStreams }) {
158
+ const record = { v: EXIT_RECORD_VERSION, at: new Date().toISOString(), pid: process.pid, reason, openStreams };
159
+ const target = exitRecordPath(dataDir);
160
+ const tmp = `${target}.${process.pid}.tmp`;
161
+ await fsp.mkdir(path.dirname(target), { recursive: true, mode: 0o700 });
162
+ await fsp.writeFile(tmp, `${JSON.stringify(record)}\n`, { mode: 0o600 });
163
+ await fsp.rename(tmp, target);
164
+ return record;
165
+ }
166
+
167
+ /**
168
+ * @returns {Promise<object|null>} the recorded previous exit, or null when there is none to report. A
169
+ * truncated or hand-edited record is reported as absent rather than thrown: it is evidence of nothing.
170
+ */
171
+ async function readExitRecord({ dataDir }) {
172
+ let raw;
173
+ try {
174
+ raw = await fsp.readFile(exitRecordPath(dataDir), 'utf8');
175
+ } catch (err) {
176
+ if (err && err.code === 'ENOENT') return null; // expected: no previous exit was recorded
177
+ throw err;
178
+ }
179
+ try {
180
+ const record = JSON.parse(raw);
181
+ return record && typeof record === 'object' ? record : null;
182
+ } catch {
183
+ return null; // expected: an unparseable record is not evidence; report it as absent
184
+ }
185
+ }
186
+
187
+ /** One exit is reported once: the supervisor reads the record, then clears it. */
188
+ async function clearExitRecord({ dataDir }) {
189
+ try {
190
+ await fsp.rm(exitRecordPath(dataDir), { force: true });
191
+ } catch (err) {
192
+ // REPORT: a record that cannot be removed is stale truth on disk, which is worth a line.
193
+ console.error(`[model-proxy] exit record not removed: ${(err && err.code) || 'unknown'}`);
194
+ }
195
+ }
196
+
197
+ module.exports = {
198
+ createStreamAccounting,
199
+ healthFields,
200
+ withHealthFields,
201
+ writeExitRecord,
202
+ readExitRecord,
203
+ clearExitRecord,
204
+ exitRecordPath,
205
+ };
@@ -781,6 +781,81 @@ function tokenCount() {
781
781
  return _tokens.size;
782
782
  }
783
783
 
784
+ // ── §9 S-10 / §5 D-7 state export (WP-2 handoff) ────────────────────────────────────────────────
785
+ //
786
+ // The token registry is memory-only, and the handoff successor is a DIFFERENT process, so the rows
787
+ // have to travel over IPC. Two shapes live in `_tokens` and they are not equally serializable:
788
+ // * a lead entry is plain data (its `aliases` is the only non-JSON member, a Set);
789
+ // * a worker entry additionally owns a live `budget` handle (lib/model-proxy/request-budget.js: a
790
+ // journal, a clock, in-process callbacks). That handle is PROCESS state, not a row -- it is
791
+ // deliberately NOT serialized, and the imported row carries `budget: null`, exactly the shape
792
+ // `mintWorkerToken` already produces for a token minted without a policy. Authorization
793
+ // (`authorize()`, `tokenEntry()`) is therefore preserved byte-for-byte, while admission accounting
794
+ // stays with the owner that holds the journal: CTM re-mints/replays worker tokens itself, the same
795
+ // way a fresh spawn is served today.
796
+
797
+ /** @returns {Array<{token:string, entry:object}>} every NON-REVOKED row, in IPC-safe (plain JSON) form. */
798
+ function exportAll() {
799
+ const rows = [];
800
+ for (const [token, entry] of _tokens) {
801
+ if (!entry || entry.revoked) continue;
802
+ if (entry.kind === 'lead') {
803
+ rows.push({ token, entry: { ...entry, aliases: [...entry.aliases] } });
804
+ continue;
805
+ }
806
+ rows.push({
807
+ token,
808
+ entry: {
809
+ kind: 'worker',
810
+ ctmSessionId: entry.ctmSessionId,
811
+ aliases: [...entry.aliases],
812
+ binding: entry.binding || null,
813
+ expiresAt: entry.expiresAt,
814
+ revoked: false,
815
+ },
816
+ });
817
+ }
818
+ return rows;
819
+ }
820
+
821
+ /**
822
+ * Register each exported row verbatim (§9 S-10). A row that is not shaped like an export is SKIPPED
823
+ * rather than throwing: a successor that cannot read one row must still come up serving the rest, and
824
+ * the row it skipped simply authorizes nothing. Re-registering an identical lead row is a no-op, which
825
+ * is what makes an overlap between this and a later CTM replay harmless.
826
+ * @returns {number} how many rows were registered
827
+ */
828
+ function importAll(rows) {
829
+ if (!Array.isArray(rows)) return 0;
830
+ let imported = 0;
831
+ for (const row of rows) {
832
+ if (!row || typeof row !== 'object' || Array.isArray(row)) continue;
833
+ const { token, entry } = row;
834
+ if (typeof token !== 'string' || !entry || typeof entry !== 'object' || Array.isArray(entry)) continue;
835
+ if (!Array.isArray(entry.aliases) || entry.aliases.some((alias) => typeof alias !== 'string')) continue;
836
+ const next = entry.kind === 'lead'
837
+ ? { ...entry, aliases: new Set(entry.aliases), expiresAt: Infinity, revoked: entry.revoked === true }
838
+ : {
839
+ kind: 'worker',
840
+ ctmSessionId: entry.ctmSessionId,
841
+ aliases: new Set(entry.aliases),
842
+ binding: entry.binding || null,
843
+ expiresAt: typeof entry.expiresAt === 'number' ? entry.expiresAt : Infinity,
844
+ revoked: entry.revoked === true,
845
+ // The budget handle is process state and is re-derived by its owner, never carried across
846
+ // processes (see the export note above).
847
+ budget: null,
848
+ };
849
+ _tokens.set(token, next);
850
+ // The certificate -> token index is part of the registry, so a rehydrated mTLS owner keeps
851
+ // resolving by fingerprint without a replay from CTM.
852
+ const fingerprint = _resolvedNativeCertFingerprint(next);
853
+ if (fingerprint) _nativeCertOwners.set(fingerprint, token);
854
+ imported += 1;
855
+ }
856
+ return imported;
857
+ }
858
+
784
859
  module.exports = {
785
860
  TOKEN_PREFIX,
786
861
  TOKEN_RE,
@@ -819,4 +894,7 @@ module.exports = {
819
894
  stopWorkerBudget,
820
895
  resetTokens,
821
896
  tokenCount,
897
+ // §9 S-10 (WP-2 handoff): the memory-only rows, in IPC-safe form, and their verbatim re-registration.
898
+ exportAll,
899
+ importAll,
822
900
  };
@@ -9,6 +9,8 @@ const SAFE_MESSAGES = Object.freeze({
9
9
  rate_limit_exceeded: 'The provider is rate limiting requests.',
10
10
  server_is_overloaded: 'The provider is temporarily overloaded.',
11
11
  server_error: 'The provider could not complete the request.',
12
+ model_not_invocable: 'This route cannot invoke this model id; it needs the model’s inference-profile id.',
13
+ model_access_denied: 'The route’s provider account denies access to this model.',
12
14
  });
13
15
 
14
16
  const SAFE_CODE = /^[A-Za-z0-9_.-]{1,64}$/;
@@ -16,6 +18,7 @@ const SAFE_PROVIDER_CODES = new Set([
16
18
  'invalid_prompt', 'invalid_request', 'invalid_request_error', 'model_not_found',
17
19
  'context_length_exceeded', 'insufficient_quota', 'rate_limit_exceeded',
18
20
  'server_is_overloaded', 'overloaded_error', 'server_error', 'internal_error',
21
+ 'model_not_invocable', 'model_access_denied',
19
22
  ]);
20
23
 
21
24
  function _stringCode(value) {
@@ -36,6 +39,12 @@ function _classify(code, status, message) {
36
39
  if (/insufficient[_ -]?quota|quota[_ -]?exceeded|billing/.test(folded)) return 'insufficient_quota';
37
40
  if (/rate[_ -]?limit|too many requests/.test(folded) || status === 429) return 'rate_limit_exceeded';
38
41
  if (/overloaded|capacity[_ -]?exceeded/.test(folded)) return 'server_is_overloaded';
42
+ // D-5 (docs/routed-anthropic-lead-effort-levels-2026-10-25.html): a permission denial does not clear
43
+ // on retry, and a 400/422 naming on-demand/inference-profile is a route/model-id mismatch, not a bad
44
+ // prompt. Both run after the four message-driven classes above (a 429 naming "inference profile" stays
45
+ // rate_limit_exceeded) and before the generic invalid/4xx line. The provider's own prose is never copied.
46
+ if (status === 401 || status === 403 || code === '401' || code === '403') return 'model_access_denied';
47
+ if (/on-demand throughput|inference profile/.test(folded) && (status === 400 || status === 422 || code === '400' || code === '422')) return 'model_not_invocable';
39
48
  if (/invalid|model[_ -]?not[_ -]?found|history/.test(folded) || ['400', '404', '422'].includes(code) || [400, 404, 422].includes(status)) return 'invalid_prompt';
40
49
  return 'server_error';
41
50
  }
@@ -5,16 +5,36 @@ const { StringDecoder } = require('node:string_decoder');
5
5
  const path = require('node:path');
6
6
  const { codexProxyProviderArgs } = require('./codex-launch-health');
7
7
  // S-5: the adapter id and the qualified-version membership test come from the one table.
8
- const { CODEX_CONTROL_ADAPTER, isQualifiedCodexVersion } = require('./codex-harness-qualification');
8
+ const { CODEX_CONTROL_ADAPTER, isQualifiedCodexVersion, isNonElevatedCyberAccessProgram } = require('./codex-harness-qualification');
9
9
 
10
10
  const QUALIFIED_ADAPTER = CODEX_CONTROL_ADAPTER;
11
11
  const MAX_FRAME_BYTES = 1024 * 1024;
12
+ const TURNS_PAGE_LIMIT = 8;
12
13
  const DEFAULT_TIMEOUT_MS = 20_000;
13
14
  const MAX_TIMEOUT_MS = 30_000;
14
15
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
15
16
  const POLICY_CONFIG_KEYS = new Set(['approval_policy', 'sandbox_mode', 'sandbox_workspace_write.network_access',
16
17
  'sandbox_workspace_write.writable_roots', 'sandbox_workspace_write.allowed_domains',
17
18
  'sandbox_workspace_write.exclude_tmpdir_env_var', 'sandbox_workspace_write.exclude_slash_tmp']);
19
+ const HANDOFF_SHA_RE = /^[a-f0-9]{64}$/;
20
+ // D-6/D-7 (docs/codex-lead-routed-switch-history-handoff-2026-10-21.html §6.4): the plaintext
21
+ // handoff the source model wrote, verbatim from Codex rust-v0.163.0
22
+ // codex-rs/prompts/templates/compact/summary_prefix.md (Apache-2.0, attributed there). It is the
23
+ // same preamble Codex puts in front of a native summary, so the target reads it as one.
24
+ const SUMMARY_PREFIX = 'Another language model started to solve this problem and produced a summary of its thinking process. You also have access to the state of the tools that were used by that language model. Use this to build on the work that has already been done and avoid duplicating work. Here is the summary produced by the other language model, use the information in this summary to assist with your own analysis:';
25
+ // The ONE marker grammar the scanner (lib/history-transition-read-op.js HANDOFF_MARKER) and the
26
+ // proxy (lib/model-proxy/collaboration-codec.js) both read. The sha binds the summary to exactly
27
+ // one sealed compaction item; the model name is what the source model was when it wrote it.
28
+ const HANDOFF_MARKER_PREFIX = '<ctm_handoff compaction_sha256="';
29
+
30
+ function handoffMessage(handoff) {
31
+ if (!isRecord(handoff) || !HANDOFF_SHA_RE.test(String(handoff.compactionSha256 || ''))
32
+ || typeof handoff.text !== 'string' || !handoff.text.trim()
33
+ || !nonempty(handoff.sourceModel)) throw adapterError('native_handoff_inject_failed');
34
+ const body = `${HANDOFF_MARKER_PREFIX}${handoff.compactionSha256}" source_model="${handoff.sourceModel}">\n`
35
+ + SUMMARY_PREFIX + '\n\n' + handoff.text + '\n</ctm_handoff>';
36
+ return { type: 'message', role: 'developer', content: [{ type: 'input_text', text: body }] };
37
+ }
18
38
 
19
39
  function createCodexNativeHistoryControl({ sourceRecipe, targetRecipe } = {}) {
20
40
  const source = validateSource(sourceRecipe);
@@ -88,11 +108,14 @@ function validatePolicyOverrides(value) {
88
108
  return Object.freeze(output);
89
109
  }
90
110
 
91
- async function forkCheckpoint(source, target, checkpoint, { signal, timeoutMs = DEFAULT_TIMEOUT_MS, targetAuthToken } = {}) {
111
+ async function forkCheckpoint(source, target, checkpoint, { signal, timeoutMs = DEFAULT_TIMEOUT_MS, targetAuthToken, handoff } = {}) {
92
112
  const sourceThreadId = nonempty(checkpoint?.sourceThreadId);
93
113
  const lastTurnId = nonempty(checkpoint?.lastTurnId);
94
114
  const sourceGeneration = nonempty(checkpoint?.sourceGeneration);
95
115
  if (!UUID_RE.test(sourceThreadId) || !lastTurnId || !sourceGeneration) throw adapterError('source_checkpoint_invalid');
116
+ // D-7: the handoff message is validated BEFORE the fork starts, so an unusable summary can never
117
+ // leave a half-created target thread behind.
118
+ const handoffItem = handoff === undefined || handoff === null ? null : handoffMessage(handoff);
96
119
  if (signal?.aborted) throw adapterError('transition_cancelled');
97
120
  const deadlineMs = Number.isSafeInteger(timeoutMs) && timeoutMs > 0 ? Math.min(timeoutMs, MAX_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS;
98
121
  const deadlineAt = Date.now() + deadlineMs;
@@ -106,13 +129,22 @@ async function forkCheckpoint(source, target, checkpoint, { signal, timeoutMs =
106
129
  });
107
130
  if (!initialized || rpc.closed) throw adapterError('native_initialize_failed');
108
131
  await rpc.notify('initialized', {});
132
+ // excludeTurns: a full-history fork response is the whole thread in one frame (a live 23 MB
133
+ // rollout hydrates to 5.5 MB); only the new thread id is read from it.
109
134
  const result = await rpc.request('thread/fork', {
110
- threadId: sourceThreadId, lastTurnId, model: target.modelId, ephemeral: false,
135
+ threadId: sourceThreadId, lastTurnId, model: target.modelId, ephemeral: false, excludeTurns: true,
111
136
  });
112
137
  if (signal?.aborted) throw adapterError('transition_cancelled');
113
138
  const targetThreadId = nonempty(result?.thread?.id || result?.threadId || result?.thread_id);
114
139
  createdTargetId = targetThreadId;
115
140
  if (!UUID_RE.test(targetThreadId) || targetThreadId === sourceThreadId) throw adapterError('native_fork_checkpoint_invalid');
141
+ // D-7: the covered compaction's plaintext handoff is persisted INTO the forked thread as one
142
+ // developer message. Codex sends the sealed `compaction` item on verbatim; the proxy drops it
143
+ // only when this same request carries the marker, so a failed inject must fail the fork closed.
144
+ if (handoffItem) {
145
+ try { await rpc.request('thread/inject_items', { threadId: targetThreadId, items: [handoffItem] }); }
146
+ catch { throw adapterError('native_handoff_inject_failed'); }
147
+ }
116
148
  // Close the creating process before the readback. A new app-server must recover the
117
149
  // fork from disk; resuming in the process that created it could pass from cache alone.
118
150
  await rpc.close();
@@ -171,21 +203,33 @@ async function coldResume(source, args, target, threadId, expectedLastTurnId, si
171
203
  });
172
204
  if (!initialized || rpc.closed) throw adapterError('native_readback_initialize_failed');
173
205
  await rpc.notify('initialized', {});
174
- const resumed = await rpc.request('thread/resume', { threadId });
206
+ const resumed = await rpc.request('thread/resume', { threadId, excludeTurns: true });
175
207
  const resumedId = nonempty(resumed?.thread?.id || resumed?.threadId || resumed?.thread_id);
176
208
  if (resumedId !== threadId) throw adapterError('native_fork_cold_resume_mismatch');
177
- const readback = await rpc.request('thread/read', { threadId, includeTurns: true });
178
- const readThread = readback?.thread;
179
- const turns = readThread?.turns;
180
- if (readThread?.id !== threadId || !Array.isArray(turns)) throw adapterError('native_fork_cold_resume_checkpoint_mismatch');
181
- const restored = turns.find((turn) => turn?.id === expectedLastTurnId && turn?.status === 'completed');
182
- if (!restored) throw adapterError('native_fork_cold_resume_checkpoint_mismatch');
209
+ const restored = await findTurn(rpc, threadId, expectedLastTurnId);
210
+ if (restored?.status !== 'completed') throw adapterError('native_fork_cold_resume_checkpoint_mismatch');
183
211
  return { ...resumed, restoredCheckpoint: { turnId: restored.id, status: restored.status } };
184
212
  } finally {
185
213
  await rpc.close();
186
214
  }
187
215
  }
188
216
 
217
+ // Codex rust-v0.163.0 thread/turns/list, newest first and without items: the fork ends at the
218
+ // checkpoint, so it is on the first page, and a page stays ~2 KB however long the thread is
219
+ // (measured on a 24-turn thread: 3 pages of 8, largest frame 2,021 B).
220
+ async function findTurn(rpc, threadId, turnId) {
221
+ let cursor = null;
222
+ do {
223
+ const page = await rpc.request('thread/turns/list', { threadId, limit: TURNS_PAGE_LIMIT,
224
+ sortDirection: 'desc', itemsView: 'notLoaded', ...(cursor ? { cursor } : {}) });
225
+ if (!Array.isArray(page?.data)) throw adapterError('native_fork_cold_resume_checkpoint_mismatch');
226
+ const turn = page.data.find((item) => item?.id === turnId);
227
+ if (turn) return turn;
228
+ cursor = nonempty(page.nextCursor);
229
+ } while (cursor);
230
+ return null;
231
+ }
232
+
189
233
  function buildAppServerArgs(target) {
190
234
  const args = codexProxyProviderArgs({
191
235
  providerId: target.providerId, name: target.providerName, proxyPort: target.proxyPort,
@@ -200,7 +244,7 @@ function buildAppServerArgs(target) {
200
244
  return args;
201
245
  }
202
246
 
203
- function createBoundedRpc(child, { signal, timeoutMs }) {
247
+ function createBoundedRpc(child, { signal, timeoutMs, allowTurn = false, onNotification = null }) {
204
248
  let buffer = '';
205
249
  let closed = false;
206
250
  let nextId = 1;
@@ -240,11 +284,14 @@ function createBoundedRpc(child, { signal, timeoutMs }) {
240
284
  || (message.params !== undefined && !isRecord(message.params))) {
241
285
  fail(adapterError('native_rpc_frame_invalid')); void terminateOnce(); return;
242
286
  }
243
- if (/^(?:turn|item)\//.test(message.method)) {
287
+ // D-6: the ephemeral handoff fork is the ONE caller that runs a model turn on purpose, and it
288
+ // asks for that explicitly. Every fork/readback path keeps the fail-closed guard.
289
+ if (!allowTurn && /^(?:turn|item)\//.test(message.method)) {
244
290
  fail(adapterError('native_fork_emitted_model_turn'));
245
291
  void terminateOnce();
246
292
  return;
247
293
  }
294
+ if (onNotification) onNotification(message.method, message.params);
248
295
  continue;
249
296
  }
250
297
  if (!Number.isSafeInteger(message.id) || !waiters.has(message.id)) {
@@ -369,7 +416,7 @@ function absolute(value) { return typeof value === 'string' && path.isAbsolute(v
369
416
  function nonempty(value) { return typeof value === 'string' && value.trim() ? value.trim() : ''; }
370
417
  function validAlias(value) { return typeof value === 'string' && /^[a-z0-9][a-z0-9._-]{0,63}$/i.test(value); }
371
418
  function validEnvKey(value) { return typeof value === 'string' && /^[A-Z_][A-Z0-9_]{0,63}$/.test(value); }
372
- function validCyberAccessProgram(value) { return value === null; }
419
+ function validCyberAccessProgram(value) { return isNonElevatedCyberAccessProgram(value); }
373
420
  function validApprovalPolicy(value) { return ['never', 'on-request', 'on-failure', 'untrusted'].includes(value); }
374
421
  function validSandboxPolicy(value) {
375
422
  return isRecord(value) && ['read-only', 'workspace-write', 'danger-full-access'].includes(value.type)
@@ -384,4 +431,5 @@ function sameSandboxPolicy(a, b) {
384
431
  }
385
432
  function isRecord(value) { return !!value && typeof value === 'object' && !Array.isArray(value); }
386
433
 
387
- module.exports = { createCodexNativeHistoryControl, buildAppServerArgs, buildAppServerEnv, QUALIFIED_ADAPTER };
434
+ module.exports = { createCodexNativeHistoryControl, buildAppServerArgs, buildAppServerEnv, QUALIFIED_ADAPTER,
435
+ createBoundedRpc, terminateChild, handoffMessage, SUMMARY_PREFIX, HANDOFF_MARKER_PREFIX };
@@ -53,6 +53,7 @@ const { spawn } = require('node:child_process');
53
53
 
54
54
  const { LAUNCH_PLAN_DEFAULT_REQUEST_TIMEOUT_MS } = require('./launch-plan-pool');
55
55
  const { buildNativeLeadBaseUrl } = require('./native-lead-endpoint');
56
+ const { isWritableOnlyByOwner } = require('../../shared/owner-write-safety');
56
57
  const {
57
58
  NATIVE_LEAD_TLS_CODES,
58
59
  LOOPBACK_ADDRESS,
@@ -190,7 +191,7 @@ async function _ensureGeneratedDirAsync(dirPath, dataDir) {
190
191
  || base.stat.isSymbolicLink()
191
192
  || !base.stat.isDirectory()
192
193
  || (typeof process.getuid === 'function' && Number(base.stat.uid) !== process.getuid())
193
- || (Number(base.stat.mode) & 0o022) !== 0) {
194
+ || !isWritableOnlyByOwner(base.stat)) {
194
195
  _fail(NATIVE_LEAD_SETTINGS_CLIENT_CODES.RESERVE_FAILED);
195
196
  }
196
197
  let current = dataDir;
@@ -38,6 +38,7 @@
38
38
  const fs = require('node:fs');
39
39
  const path = require('node:path');
40
40
  const crypto = require('node:crypto');
41
+ const { isWritableOnlyByOwner } = require('../../shared/owner-write-safety');
41
42
 
42
43
  // --- fixed, public vocabulary ---------------------------------------------------------------
43
44
 
@@ -283,7 +284,7 @@ function _assertPrivateRoot(dirPath) {
283
284
  }
284
285
  if (stat.isSymbolicLink() || !stat.isDirectory()) _fail(NATIVE_LEAD_TLS_CODES.PRIVATE_PATH_INVALID);
285
286
  if (typeof process.getuid === 'function' && stat.uid !== process.getuid()) _fail(NATIVE_LEAD_TLS_CODES.PRIVATE_PATH_INVALID);
286
- if ((stat.mode & 0o022) !== 0) _fail(NATIVE_LEAD_TLS_CODES.PRIVATE_PATH_INVALID);
287
+ if (!isWritableOnlyByOwner(stat)) _fail(NATIVE_LEAD_TLS_CODES.PRIVATE_PATH_INVALID);
287
288
  return stat;
288
289
  }
289
290
 
@@ -380,18 +381,31 @@ function syncWritePrivateFileExclusive(filePath, contents, { code = NATIVE_LEAD_
380
381
  /**
381
382
  * The deterministic snapshot seam shared by both private readers: after a bounded read, the descriptor
382
383
  * must still describe EXACTLY the file that was opened — same device/inode (a replacement), same size
383
- * and same mtime/ctime (a same-size mutation), same uid/mode (a permissions change). Pure, so the
384
- * check is unit-testable without a filesystem race or any real-time threshold.
384
+ * and same mtime (a same-size mutation), same uid/mode (a permissions change). Pure, so the check is
385
+ * unit-testable without a filesystem race or any real-time threshold.
386
+ *
387
+ * ctime is compared only while the link count is UNCHANGED (D-8,
388
+ * docs/nightly-followups-r3-pty-selector-flakes-2026-10-22.html). A hard-link count change moves ctime
389
+ * by itself — unlinking a sibling link (macOS, probe I-4: nlink 2 -> 1, mtime equal, ctime different)
390
+ * or creating one (Linux) rewrites the inode's ctime without touching the bytes or their mtime. That is
391
+ * exactly the window the authority publisher occupies: it links the candidate to the authority
392
+ * (`fs.linkSync(candidateFile, authorityPath)`) and then unlinks its own candidate link, so a reader
393
+ * sitting between its two fstats sees only the link-count swap and would otherwise fail closed with
394
+ * `native_tls_authority_invalid` on a file nobody modified. The nlink delta fully explains the ctime
395
+ * delta, so the ctime comparison is skipped when nlink moved; dev, ino, size, mtimeMs, uid and mode are
396
+ * still compared unconditionally, which keeps the same-size-write detection for the case that matters
397
+ * (a rewrite that preserves mtime while nlink is unchanged).
385
398
  *
386
399
  * @returns {boolean} true when `after` is still the same, unmodified, correctly-permissioned file.
387
400
  */
388
401
  function isSamePrivateFileSnapshot(opened, after) {
389
402
  if (!opened || !after) return false;
403
+ const linkCountChanged = Number(after.nlink) !== Number(opened.nlink);
390
404
  return Number(after.dev) === Number(opened.dev)
391
405
  && Number(after.ino) === Number(opened.ino)
392
406
  && Number(after.size) === Number(opened.size)
393
407
  && Number(after.mtimeMs) === Number(opened.mtimeMs)
394
- && Number(after.ctimeMs) === Number(opened.ctimeMs)
408
+ && (linkCountChanged || Number(after.ctimeMs) === Number(opened.ctimeMs))
395
409
  && Number(after.uid) === Number(opened.uid)
396
410
  && Number(after.mode) === Number(opened.mode);
397
411
  }