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,160 @@
1
+ 'use strict';
2
+
3
+ // Codex list markers, at parity with what the TUI paints, ported from
4
+ // codex-rs/tui/src/markdown_render.rs start_item (:1014-1090), prefix_spans (:2535-2565)
5
+ // codex-rs/tui/src/markdown_render/task_lists.rs task_list_marker
6
+ // codex-rs/tui/src/markdown_render_tests.rs list_unordered_single / list_nested / nested_five_levels_mixed_lists / ...
7
+ //
8
+ // Why this module exists: the reader joins CTM's rendered rows to the rows the live Codex screen
9
+ // actually shows, by identity of the row text. Codex draws unordered items as `• ` (PR #47322, on
10
+ // by default) and nests by a FIXED 4-column step, while the first CTM renderer drew `- ` indented by
11
+ // floor(spaces/2)*2 — so every list row missed the join and the reader drew it twice.
12
+ //
13
+ // The two facts that make the port non-obvious:
14
+ // * depth is the list nesting level; `width = depth*4 - 3`. The marker of an item at depth d is
15
+ // ONLY its own marker: prefix_spans skips every list context except the innermost, so an outer
16
+ // level contributes nothing to a nested row (no cumulative indent).
17
+ // * depth is NOT floor(spaces/2). It comes from a CommonMark container stack over the source
18
+ // lines; each open level remembers its item's CONTENT column, and a list line either opens a
19
+ // deeper level, lands as a sibling, or closes levels.
20
+ //
21
+ // Pure: no DOM, no clock, no I/O. Same input => same output. Tabs are already expanded to 4 spaces
22
+ // by the caller (renderMarkdownTextBlock does this before the line loop).
23
+
24
+ const BULLET = '•';
25
+ const UNCHECKED = '☐';
26
+ const CHECKED = '☑';
27
+
28
+ // Codex nests by a fixed 4-column step (markdown_render.rs:1042: `width = depth * 4 - 3`).
29
+ const HANG_STEP = 4;
30
+
31
+ // `- x` / `* x` / `+ x` and `1. x` / `1) x`. The content must be non-blank, so a bare rule or a
32
+ // lone marker keeps falling through to the renderer's paragraph path exactly as before.
33
+ const BULLET_LINE = /^( *)([-*+])( +)(\S.*)$/;
34
+ const ORDERED_LINE = /^( *)(\d{1,4})([.)])( +)(\S.*)$/;
35
+ // A GFM task checkbox, right after the marker (task_lists.rs:8-24). `[X]` counts as checked.
36
+ const TASK = /^\[([ xX])\](?: |$)/;
37
+
38
+ // `width` for a depth (markdown_render.rs:1042): depth 1 => 1, depth 2 => 5, depth 3 => 9 ...
39
+ function markerWidthFor(depth) {
40
+ return depth * HANG_STEP - 3;
41
+ }
42
+
43
+ // The column a wrapped (continuation) row of this item hangs at (markdown_render.rs:1057-1061):
44
+ // unordered `width + 1`, ordered `width + 2` whatever the digit count. A task item hangs at its whole
45
+ // marker's display width instead (itemOf).
46
+ function hangFor(entry) {
47
+ return entry.width + (entry.ordered ? 2 : 1);
48
+ }
49
+
50
+ // markdown_render.rs:1043-1056 + task_lists.rs:15-19: the task checkbox REPLACES a trailing `• `
51
+ // and keeps an ordered `N. ` prefix.
52
+ function markerFor(entry, index) {
53
+ const glyph = entry.task ? (entry.checked ? CHECKED : UNCHECKED) : BULLET;
54
+ if (!entry.ordered) return ' '.repeat(Math.max(0, entry.width - 1)) + glyph + ' ';
55
+ return String(index).padStart(entry.width) + '. ' + (entry.task ? glyph + ' ' : '');
56
+ }
57
+
58
+ // Builds the row a list item paints: the marker (which already carries its own leading columns)
59
+ // plus the source text. `indent` is 0 for an item row because the marker string holds its column,
60
+ // and `hang` is the absolute continuation column. The caller adds the message prefix in front
61
+ // (rule R-1.6) and strips inline markdown from `text` alone, so the marker never joins a match.
62
+ function itemOf(entry, index, text) {
63
+ const marker = markerFor(entry, index);
64
+ // A task item hangs at its WHOLE marker's display width (task_lists.rs:22), so `10. ☐ ` hangs 6;
65
+ // every char of a marker is one column wide.
66
+ return { marker, text, indent: 0, hang: entry.task ? marker.length : hangFor(entry) };
67
+ }
68
+
69
+ // A tracker owns the CommonMark container stack for one text block. `consume(line)` must be called
70
+ // with EVERY source line, in order, including blank ones: a blank line does not close the stack,
71
+ // and a non-list line may be a continuation paragraph of the innermost item.
72
+ function createListTracker() {
73
+ const stack = []; // innermost last; each entry = { ordered, task, checked, width, contentCol, next }
74
+
75
+ const push = (fields) => {
76
+ const entry = {
77
+ ordered: fields.ordered,
78
+ task: fields.task,
79
+ checked: fields.checked,
80
+ width: markerWidthFor(stack.length + 1),
81
+ contentCol: fields.contentCol,
82
+ next: fields.next,
83
+ };
84
+ stack.push(entry);
85
+ return entry;
86
+ };
87
+
88
+ return {
89
+ // -> null when the line is not a list item (or a continuation of one); otherwise the row
90
+ // { marker, text, indent, hang } the renderer draws. `marker` is '' for a continuation row.
91
+ consume(rawLine) {
92
+ const line = String(rawLine == null ? '' : rawLine);
93
+ if (!line.trim()) return null; // a blank line does not close (R-1.5)
94
+
95
+ const bullet = BULLET_LINE.exec(line);
96
+ const ordered = bullet ? null : ORDERED_LINE.exec(line);
97
+
98
+ if (!bullet && !ordered) {
99
+ const top = stack[stack.length - 1];
100
+ const indent = line.length - line.replace(/^ */, '').length;
101
+ if (top && indent >= top.contentCol) {
102
+ // A non-blank, non-list line indented to the item's content column continues that item,
103
+ // rendered at the level's hang (R-1.5).
104
+ const hang = hangFor(top);
105
+ return { marker: '', text: line.trim(), indent: hang, hang };
106
+ }
107
+ stack.length = 0; // any other non-blank line closes every level
108
+ return null;
109
+ }
110
+
111
+ const markerIndent = (bullet ? bullet[1] : ordered[1]).length;
112
+ // The marker LENGTH is `-` (1) or `N.` (digits + the bracket) — the checkbox is not part of
113
+ // it, so `- [ ] a` still has content column 2.
114
+ const markerLen = bullet ? 1 : ordered[2].length + 1;
115
+ const gap = (bullet ? bullet[3] : ordered[4]).length;
116
+ const contentCol = markerIndent + markerLen + gap;
117
+ const content = bullet ? bullet[4] : ordered[5];
118
+ const task = TASK.exec(content);
119
+ const isOrdered = !bullet;
120
+ const text = task ? content.slice(task[0].length) : content;
121
+ const fields = {
122
+ ordered: isOrdered,
123
+ task: !!task,
124
+ checked: !!task && task[1] !== ' ',
125
+ contentCol,
126
+ next: isOrdered ? Number(ordered[2]) : 0,
127
+ };
128
+
129
+ const top = stack[stack.length - 1];
130
+ let entry;
131
+ if (top && markerIndent >= top.contentCol) {
132
+ entry = push(fields); // deeper than the innermost item's content -> a nested level
133
+ } else {
134
+ // Pop while the marker column sits left of the content column of the level BELOW the top.
135
+ while (stack.length >= 2 && markerIndent < stack[stack.length - 2].contentCol) stack.pop();
136
+ const sibling = stack[stack.length - 1];
137
+ if (!sibling || sibling.ordered !== isOrdered) {
138
+ // A sibling of the other type starts a NEW list at this depth; its numbering restarts
139
+ // from its own first source number.
140
+ if (sibling) stack.pop();
141
+ entry = push(fields);
142
+ } else {
143
+ entry = sibling; // same list: keep the level, refresh the per-item task state
144
+ entry.task = fields.task;
145
+ entry.checked = fields.checked;
146
+ }
147
+ }
148
+
149
+ let index = 0;
150
+ if (entry.ordered) {
151
+ index = entry.next; // counts from the list's first source number, per sibling
152
+ entry.next += 1;
153
+ }
154
+
155
+ return itemOf(entry, index, text);
156
+ },
157
+ };
158
+ }
159
+
160
+ module.exports = { createListTracker };
@@ -191,6 +191,10 @@ function findCanonicalRolloutsByFilenameId(threadId, storageRoots) {
191
191
  const candidate = path.join(dir, entry.name);
192
192
  if (entry.isDirectory()) { stack.push({ dir: candidate, depth: depth + 1 }); continue; }
193
193
  if (!entry.isFile() || sessionHistory.codexRolloutIdFromPath(candidate).toLowerCase() !== wanted) continue;
194
+ // Codex rust-v0.163.0 rollout/src/compression.rs should_skip_compressed_sibling: one rollout is
195
+ // one physical file, and a `.jsonl.zst` is hidden while its plain `.jsonl` exists (Codex
196
+ // compresses an idle thread, then appends to a re-materialized plain file beside the stale copy).
197
+ if (candidate.endsWith('.jsonl.zst') && transcriptSource.statTranscriptSync(candidate.slice(0, -'.zst'.length)).exists) continue;
194
198
  const info = transcriptSource.statTranscriptSync(candidate);
195
199
  if (!info.exists) throw Object.assign(new Error('native history candidate changed during lookup'), { code: 'native_history_lineage_changed' });
196
200
  let canonical;
@@ -26,6 +26,7 @@ const fs = require('fs');
26
26
  const path = require('path');
27
27
  const os = require('os');
28
28
  const crypto = require('crypto');
29
+ const { isWritableOnlyByOwner } = require('../../shared/owner-write-safety');
29
30
 
30
31
  function homeBase(homeDir) {
31
32
  return homeDir || process.env.HOME || process.env.USERPROFILE || os.homedir() || '';
@@ -77,7 +78,7 @@ function resolveCodexSessionsRoot(options = {}) {
77
78
  out.refusalCode = 'wrong-owner';
78
79
  return out;
79
80
  }
80
- if ((Number(stat.mode) & 0o022) !== 0) {
81
+ if (!isWritableOnlyByOwner(stat)) {
81
82
  out.validationStatus = 'refused';
82
83
  out.refusalCode = 'unsafe-permissions';
83
84
  return out;
@@ -0,0 +1,186 @@
1
+ 'use strict';
2
+
3
+ /*
4
+ * WP-2 D-5/D-6 (docs/codex-lead-routed-switch-history-handoff-2026-10-21.html §6.3, §5 D-5/D-6).
5
+ *
6
+ * A GPT Codex lead whose last compaction holds an OpenAI-encrypted `compaction` item cannot carry
7
+ * that ciphertext to a routed target model. The user's decision (2026-10-21) is that the SOURCE GPT
8
+ * model writes a plaintext handoff summary before the switch.
9
+ *
10
+ * The summary is produced by an EPHEMERAL fork of the source thread, so:
11
+ * - the user's live source PTY and source thread are never touched (anti-goal: never submit into
12
+ * the live source session);
13
+ * - no rollout file is persisted for the fork (measured on Codex 0.163.0: the rollout-file set is
14
+ * byte-identical before and after);
15
+ * - the turn is a real model turn on the user's own ChatGPT account (risk R-1: it spends quota and
16
+ * can take up to HANDOFF_DEADLINE_MS, which is why the caller shows a "compacting" phase).
17
+ *
18
+ * This module owns the child process, the app-server JSON-RPC sequence, and the text validation. It
19
+ * writes nothing anywhere: the returned text travels in memory to lib/native-history-control.js,
20
+ * which injects it into the forked target as one developer message.
21
+ */
22
+
23
+ const { spawn } = require('node:child_process');
24
+ const crypto = require('node:crypto');
25
+ const path = require('node:path');
26
+ // The ONE process-launch/RPC implementation. `allowTurn: true` is what makes the handoff turn legal:
27
+ // every fork and readback path keeps the fail-closed `native_fork_emitted_model_turn` guard.
28
+ const { createBoundedRpc, terminateChild } = require('./native-history-control');
29
+
30
+ // Codex rust-v0.163.0 codex-rs/prompts/templates/compact/prompt.md, verbatim (Apache-2.0; the file
31
+ // carries the upstream copyright header). Kept byte-identical on purpose: the source model must be
32
+ // asked the same question Codex asks its own compactor, or the summary is a different artifact.
33
+ const CODEX_COMPACT_PROMPT = 'You are performing a CONTEXT CHECKPOINT COMPACTION. Create a handoff summary for another LLM that will resume the task.\n'
34
+ + '\n'
35
+ + 'Include:\n'
36
+ + '- Current progress and key decisions made\n'
37
+ + '- Important context, constraints, or user preferences\n'
38
+ + '- What remains to be done (clear next steps)\n'
39
+ + '- Any critical data, examples, or references needed to continue\n'
40
+ + '\n'
41
+ + 'Be concise, structured, and focused on helping the next LLM seamlessly continue the work.\n';
42
+ // The CTM addition (§6.3): what a provider switch additionally needs that a same-provider compaction
43
+ // does not. It is not a tool-using turn -- the fork is read-only and every tool is refused.
44
+ const CTM_HANDOFF_ADDITION = '\n\nAlso include, verbatim and complete: every file path you changed or must change, exact error strings and test names still failing, commands that must run next, and the single next action. Do not call tools. Reply with the summary only.';
45
+ const HANDOFF_PROMPT = `${CODEX_COMPACT_PROMPT}${CTM_HANDOFF_ADDITION}`;
46
+
47
+ const HANDOFF_MIN_CHARS = 200;
48
+ const HANDOFF_MAX_CHARS = 32_768;
49
+ const HANDOFF_DEADLINE_MS = 240_000;
50
+ const HANDOFF_APP_SERVER_ARGS = Object.freeze(['app-server', '-c', 'approval_policy="never"',
51
+ '-c', 'sandbox_mode="read-only"']);
52
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
53
+ const MARKER_PREFIX = '<ctm_handoff';
54
+
55
+ /**
56
+ * Ask the SOURCE model for the plaintext handoff that will cover its sealed compaction.
57
+ *
58
+ * @param {{executablePath:string, homeDir:string, codexHome:string, workingDirectory:string, tmpDir?:string|null}} source
59
+ * @param {{sourceThreadId:string, lastTurnId:string}} checkpoint
60
+ * @param {{signal?:AbortSignal, timeoutMs?:number}} [opts]
61
+ * @returns {Promise<{ok:true, text:string, textSha256:string, turnId:string, model:string|null}
62
+ * | {ok:false, reason:'handoff_fork_failed'|'handoff_turn_failed'|'handoff_empty'|'handoff_too_long'
63
+ * |'handoff_marker_in_text'|'handoff_timeout'|'transition_cancelled'}>}
64
+ */
65
+ async function writeSourceHandoff(source, checkpoint, { signal, timeoutMs } = {}) {
66
+ if (signal?.aborted) return failure('transition_cancelled');
67
+ const sourceThreadId = nonempty(checkpoint?.sourceThreadId);
68
+ const lastTurnId = nonempty(checkpoint?.lastTurnId);
69
+ const executablePath = nonempty(source?.executablePath);
70
+ if (![executablePath, source?.homeDir, source?.codexHome, source?.workingDirectory]
71
+ .every((value) => typeof value === 'string' && path.isAbsolute(value))
72
+ || (source?.tmpDir != null && !path.isAbsolute(source.tmpDir))
73
+ || !UUID_RE.test(sourceThreadId) || !lastTurnId) return failure('handoff_fork_failed');
74
+ const deadlineMs = Number.isSafeInteger(timeoutMs) && timeoutMs > 0
75
+ ? Math.min(timeoutMs, HANDOFF_DEADLINE_MS) : HANDOFF_DEADLINE_MS;
76
+ const state = { turnId: '', completed: null, agentText: '', onComplete: null };
77
+ let stage = 'fork';
78
+ let child = null;
79
+ let rpc = null;
80
+ try {
81
+ child = spawn(executablePath, [...HANDOFF_APP_SERVER_ARGS], { cwd: source.workingDirectory,
82
+ env: handoffEnv(source), stdio: ['pipe', 'pipe', 'pipe'] });
83
+ child.stderr.resume();
84
+ rpc = createBoundedRpc(child, { signal, timeoutMs: deadlineMs, allowTurn: true,
85
+ onNotification: (method, params) => observe(state, method, params) });
86
+ const initialized = await rpc.request('initialize', {
87
+ clientInfo: { name: 'ctm-source-handoff', version: '1' }, capabilities: {},
88
+ });
89
+ if (!initialized || rpc.closed) return failure('handoff_fork_failed');
90
+ await rpc.notify('initialized', {});
91
+ const forked = await rpc.request('thread/fork', { threadId: sourceThreadId, lastTurnId,
92
+ ephemeral: true, excludeTurns: true });
93
+ const ephemeralId = nonempty(forked?.thread?.id || forked?.threadId || forked?.thread_id);
94
+ if (!UUID_RE.test(ephemeralId) || ephemeralId === sourceThreadId) return failure('handoff_fork_failed');
95
+ stage = 'turn';
96
+ const started = await rpc.request('turn/start', { threadId: ephemeralId,
97
+ input: [{ type: 'text', text: HANDOFF_PROMPT }] });
98
+ state.turnId = nonempty(started?.turn?.id || started?.turnId || started?.id);
99
+ if (!state.turnId) return failure('handoff_turn_failed');
100
+ const turn = await waitForTurn(state, signal, deadlineMs);
101
+ if (signal?.aborted) return failure('transition_cancelled');
102
+ if (!turn || turn.timedOut) return failure(turn ? 'handoff_timeout' : 'handoff_turn_failed');
103
+ if (nonempty(turn.status) !== 'completed') return failure('handoff_turn_failed');
104
+ const text = (agentMessageText(turn) || state.agentText).trim();
105
+ if (text.length < HANDOFF_MIN_CHARS) return failure('handoff_empty');
106
+ if (text.length > HANDOFF_MAX_CHARS) return failure('handoff_too_long');
107
+ // The target's own readback looks for this prefix; a summary that already contains one could
108
+ // forge coverage for an unrelated sealed item.
109
+ if (text.includes(MARKER_PREFIX)) return failure('handoff_marker_in_text');
110
+ return { ok: true, text, textSha256: sha256(text), turnId: state.turnId,
111
+ model: nonempty(turn.model || turn.modelId) || null };
112
+ } catch (error) {
113
+ if (signal?.aborted || error?.code === 'transition_cancelled') return failure('transition_cancelled');
114
+ if (error?.code === 'native_rpc_deadline') return failure('handoff_timeout');
115
+ return failure(stage === 'turn' ? 'handoff_turn_failed' : 'handoff_fork_failed');
116
+ } finally {
117
+ // The ephemeral fork must never outlive this call, on any path -- including a refusal or an abort.
118
+ if (rpc) await rpc.close();
119
+ else await terminateChild(child);
120
+ }
121
+ }
122
+
123
+ function handoffEnv(source) {
124
+ const env = { PATH: process.env.PATH || '/usr/bin:/bin', HOME: source.homeDir,
125
+ CODEX_HOME: source.codexHome, LANG: process.env.LANG || 'C',
126
+ CTM_PROCESS_ROLE: 'ctm-source-handoff' };
127
+ if (source.tmpDir) env.TMPDIR = source.tmpDir;
128
+ return env;
129
+ }
130
+
131
+ // The single notification sink: turn/completed carries the finished turn (with its items), and
132
+ // item/completed is the fallback if a Codex version reports the agent message only there.
133
+ function observe(state, method, params) {
134
+ if (method === 'item/completed') {
135
+ const item = params?.item;
136
+ if (nonempty(item?.type) === 'agentMessage' && typeof item.text === 'string'
137
+ && (!state.turnId || nonempty(params?.turnId || item.turn_id) === state.turnId)) {
138
+ state.agentText = item.text;
139
+ }
140
+ return;
141
+ }
142
+ if (method !== 'turn/completed') return;
143
+ const turn = params?.turn;
144
+ if (state.turnId && nonempty(turn?.id) !== state.turnId) return;
145
+ state.completed = isRecord(turn) ? turn : null;
146
+ const settle = state.onComplete;
147
+ state.onComplete = null;
148
+ if (settle) settle(state.completed);
149
+ }
150
+
151
+ function waitForTurn(state, signal, deadlineMs) {
152
+ if (state.completed) return Promise.resolve(state.completed);
153
+ return new Promise((resolve) => {
154
+ let settled = false;
155
+ const timer = setTimeout(() => finish({ timedOut: true }), deadlineMs);
156
+ const onAbort = () => finish(null);
157
+ function finish(value) {
158
+ if (settled) return;
159
+ settled = true;
160
+ clearTimeout(timer);
161
+ state.onComplete = null;
162
+ signal?.removeEventListener('abort', onAbort);
163
+ resolve(value);
164
+ }
165
+ state.onComplete = finish;
166
+ signal?.addEventListener('abort', onAbort, { once: true });
167
+ if (state.completed) finish(state.completed);
168
+ });
169
+ }
170
+
171
+ function agentMessageText(turn) {
172
+ if (!Array.isArray(turn?.items)) return '';
173
+ for (let index = turn.items.length - 1; index >= 0; index -= 1) {
174
+ const item = turn.items[index];
175
+ if (nonempty(item?.type) === 'agentMessage' && typeof item.text === 'string') return item.text;
176
+ }
177
+ return '';
178
+ }
179
+
180
+ function failure(reason) { return { ok: false, reason }; }
181
+ function sha256(value) { return crypto.createHash('sha256').update(value).digest('hex'); }
182
+ function nonempty(value) { return typeof value === 'string' && value.trim() ? value.trim() : ''; }
183
+ function isRecord(value) { return !!value && typeof value === 'object' && !Array.isArray(value); }
184
+
185
+ module.exports = { writeSourceHandoff, HANDOFF_PROMPT, HANDOFF_MIN_CHARS, HANDOFF_MAX_CHARS,
186
+ HANDOFF_DEADLINE_MS };
@@ -288,18 +288,6 @@ function isGenericCodingSessionLabel(label, agentType = 'codex') {
288
288
  || lower === 'session';
289
289
  }
290
290
 
291
- function shouldApplyCodexAutoTitle({ session, existingTitle } = {}) {
292
- if (existingTitle && existingTitle.userRenamed) return false;
293
- for (const candidate of [session?.label, existingTitle?.title]) {
294
- const text = normalizeTitleText(candidate);
295
- if (text && !/^Codex:\s*/i.test(text) && !isGenericCodingSessionLabel(text, 'codex')) return false;
296
- }
297
- const title = normalizeTitleText(existingTitle?.title || session?.label || '');
298
- if (!title) return true;
299
- if (/^Codex:\s*/i.test(title) && !isGenericCodingSessionLabel(title, 'codex')) return true;
300
- return isGenericCodingSessionLabel(title, 'codex');
301
- }
302
-
303
291
  function resolveModelToRegistry(modelName) {
304
292
  if (!modelName) return null;
305
293
  let provider = null;
@@ -561,11 +549,23 @@ function createCodingAgentModelSync({
561
549
  if (!modelStateRuntime) _persistSessionModel(session, model);
562
550
  changed = true;
563
551
  }
564
- if (agentType === 'codex' && !session._codexTitleSynced) {
552
+ if (agentType === 'codex') {
565
553
  const existingTitle = titlesById.get(session.id) || null;
566
- if (existingTitle && existingTitle.userRenamed) {
567
- session._codexTitleSynced = true;
568
- } else if (!shouldApplyCodexAutoTitle({ session, existingTitle })) {
554
+ // WP-4/D-10: the thread's OWN name (threads.name) is read on EVERY tick — there is no
555
+ // one-shot `_codexTitleSynced` latch any more, and no title-vs-label precedence test that
556
+ // could let a CTM title block the name (S-22). applyProviderTitle owns the tier rules: it
557
+ // re-asserts a drifted tier-1 (user-renamed) row outward, skips a row already carrying this
558
+ // exact name, and writes the agent name over an older tier-2/tier-3 title.
559
+ const agentName = session._claudeSessionId ? getCodexThreadAgentName(session._claudeSessionId) : '';
560
+ if (agentName) {
561
+ const outcome = applyProviderTitle({ db: dbModule, sessionId: session.id, provider: 'codex', agentName }); // not a dbModule method — no dispatchOwnerMutation
562
+ if (outcome.applied === 'agent') {
563
+ session.label = `Codex: ${agentName}`;
564
+ changed = true;
565
+ }
566
+ } else {
567
+ // No thread name at all: the CTM live label is the only signal left, so it repairs a
568
+ // stale auto-title. Never a tier-2 (`provider_title`) row, never the user's own name.
569
569
  const liveLabel = normalizeTitleText(session.label);
570
570
  if (liveLabel
571
571
  && !isGenericCodingSessionLabel(liveLabel, 'codex')
@@ -573,16 +573,6 @@ function createCodingAgentModelSync({
573
573
  dispatchOwnerMutation(dbModule, 'setSessionTitleNew', [session.id, liveLabel, false], 'coding-agent title');
574
574
  changed = true;
575
575
  }
576
- session._codexTitleSynced = true;
577
- } else {
578
- // Requires the exact Codex thread id — cwd/spawn-time matching is unsafe for periodic refresh.
579
- const agentName = session._claudeSessionId ? getCodexThreadAgentName(session._claudeSessionId) : '';
580
- if (agentName) {
581
- session.label = `Codex: ${agentName}`;
582
- session._codexTitleSynced = true;
583
- applyProviderTitle({ db: dbModule, sessionId: session.id, provider: 'codex', agentName }); // not a dbModule method — no dispatchOwnerMutation
584
- changed = true;
585
- }
586
576
  }
587
577
  }
588
578
  }
@@ -688,7 +678,7 @@ function createCodingAgentModelSync({
688
678
  if (agentType === 'gemini' && !modelStateRuntime) {
689
679
  context.result = { id: session.id, modelName: _modelArgValue(session.args) };
690
680
  } else {
691
- const item = { id: session.id, agentType, directory: session.cwd || (session.meta && session.meta.cwd) || '', knownSourceRevision: agentType === 'opencode' ? modelSyncState.knownSourceRevision(session.id) : '', claudeJsonlPath, codexThreadId: agentType === 'codex' ? (session._providerResumeId || session._claudeSessionId || '') : (session._claudeSessionId || session._providerResumeId || null), wantCodexTitle: agentType === 'codex' && session._codexTitleSynced !== true && !!(session._providerResumeId || session._claudeSessionId) };
681
+ const item = { id: session.id, agentType, directory: session.cwd || (session.meta && session.meta.cwd) || '', knownSourceRevision: agentType === 'opencode' ? modelSyncState.knownSourceRevision(session.id) : '', claudeJsonlPath, codexThreadId: agentType === 'codex' ? (session._providerResumeId || session._claudeSessionId || '') : (session._claudeSessionId || session._providerResumeId || null), wantCodexTitle: agentType === 'codex' && !!(session._providerResumeId || session._claudeSessionId) }; // WP-4/D-10: no one-shot latch gates the name read
692
682
  if (modelStateRuntime) {
693
683
  Object.assign(item, { owner: context.ownerSnapshot, providerSessionId: context.ownerSnapshot.providerSessionId, processGeneration: context.ownerSnapshot.processGeneration, providerEnv: modelProviderEnv(session), geminiTranscriptPath: session._geminiTranscriptPath || '' });
694
684
  // The pre-await half of the source binding, and the prior checkpoint this request may carry.
@@ -758,7 +748,7 @@ function createCodingAgentModelSync({
758
748
  && (session.model_id !== model.model_id || session.model_provider !== model.model_provider);
759
749
  if (agentType === 'claude' && model) claudeResolved += 1; if (modelChanged && session.model_id && String(session.model_source || '') === 'db:restore') repairedStamps += 1; // I-6: ONLY a stamp still carrying the pre-apply db:restore provenance is a repair; a mid-session /model switch on a live-sourced row is a normal change
760
750
  const truthChanged = _claudeTruthNeedsApply(session, truth);
761
- const needsTitleDecision = agentType === 'codex' && session._codexTitleSynced !== true;
751
+ const needsTitleDecision = agentType === 'codex'; // WP-4/D-10: a title decision is taken on every probe, never once
762
752
  // Codex: the effort of the rollout's latest turn_context (observed, never requested).
763
753
  const codexEffortChanged = agentType === 'codex' && result.modelEffort !== undefined
764
754
  && String(session.model_effort || '') !== String(result.modelEffort || '');
@@ -853,7 +843,9 @@ function createCodingAgentModelSync({
853
843
  changed = true;
854
844
  }
855
845
  }
856
- if (entry.titleSynced) session._codexTitleSynced = true;
846
+ // entry.titleSynced is the batch's "the title decision ran" receipt (the completion cache
847
+ // below and the durable applier's latch both consume it); the session no longer carries a
848
+ // one-shot `_codexTitleSynced` gate of its own.
857
849
  const complete = (!context.intent.modelId || entry.modelPersisted)
858
850
  && (agentType !== 'codex' || entry.titleSynced);
859
851
  if (complete) modelSyncState.cacheCompletedContext(context);
@@ -942,5 +934,4 @@ module.exports = {
942
934
  opencodeDataDbPath,
943
935
  isGenericCodingSessionLabel,
944
936
  resolveModelToRegistry,
945
- shouldApplyCodexAutoTitle,
946
937
  };
@@ -3,9 +3,28 @@
3
3
  // Pure decoders for native provider evidence. These functions deliberately do no discovery or I/O.
4
4
  const SYNTHETIC = '<synthetic>';
5
5
  const { CLAUDE_ALIASES } = require('./claude-model-selection-vocabulary');
6
+ const { WORKER_MODEL_ALIAS } = require('./model-proxy/lead-worker-picks');
6
7
  const ALIASES = new Set([...CLAUDE_ALIASES, 'default']);
7
8
  const GEMINI_ALIASES = new Set(['auto', 'default', 'pro', 'flash', 'flash-lite']);
8
9
 
10
+ /*
11
+ * The ONE reserved delegate alias (WORKER_MODEL_ALIAS in lib/model-proxy/lead-worker-picks.js). It names
12
+ * what a lead's SUBAGENTS ask the proxy for -- never a model a session's own CLI runs -- so it is
13
+ * refused by VALUE (trimmed, case-insensitive) before any producer can hand it to the ledger as a
14
+ * lead fact. Normal CLI aliases and policies keep their existing behavior.
15
+ *
16
+ * The value is normalised through the SAME one marker rule `modelClass` applies below:
17
+ * The reserved alias with `[1m]` is wearing the session's context marker, not a different
18
+ * model, so it can never be a lead fact either. Exactly one marker, and no blacklist of lookalikes
19
+ * (lookalikes, `claude-opus-5-5[1m]` and every real id keep their existing behavior).
20
+ */
21
+ function isReservedWorkerModel(value) {
22
+ if (typeof value !== 'string') return false;
23
+ const folded = value.trim().toLowerCase();
24
+ const base = folded.endsWith('[1m]') ? folded.slice(0, -4) : folded;
25
+ return base === WORKER_MODEL_ALIAS;
26
+ }
27
+
9
28
  function bounded(value, required = false) {
10
29
  if (typeof value !== 'string') return null;
11
30
  const valueTrimmed = value.trim();
@@ -46,6 +65,7 @@ function emit(out, args) { const item = observation(...args); if (item) out.push
46
65
  function modelClass(raw) {
47
66
  const name = bounded(raw, true);
48
67
  if (!name) return null;
68
+ if (isReservedWorkerModel(name)) return null;
49
69
  const suffix = name.toLowerCase().endsWith('[1m]') ? '[1m]' : '';
50
70
  const base = suffix ? name.slice(0, -4) : name;
51
71
  const folded = base.toLowerCase();
@@ -165,7 +185,9 @@ function codexSettingsCandidate(owner, rec, index, resumeId) {
165
185
  if (rec.type !== 'event_msg' || p.type !== 'thread_settings_applied' || !p.thread_id ||
166
186
  (p.thread_id !== owner.providerSessionId && p.thread_id !== resumeId) || !p.thread_settings) return null;
167
187
  const model = bounded(p.thread_settings.model, true);
168
- if (!model) return null;
188
+ // Codex carries the same reserved alias in its thread settings and turn contexts when a lead's
189
+ // subagents are routed through the proxy; neither can be the lead's own model (AC-0d).
190
+ if (!model || isReservedWorkerModel(model)) return null;
169
191
  const ordinal = Number.isSafeInteger(rec.ordinal) && rec.ordinal >= 0 ? String(rec.ordinal) : null;
170
192
  return { at: time(rec.timestamp), index, value: model, effort: p.thread_settings.reasoning_effort ?? null,
171
193
  sourceRevision: ordinal, source: 'codex:thread-settings' };
@@ -186,6 +208,7 @@ function codexTurnContextCandidate(owner, rec, index, resumeId) {
186
208
  if (!(rec.type === 'turn_context' || (rec.type === 'event_msg' && p.type === 'turn_context'))) return null;
187
209
  const model = bounded(p.model ?? p.model_id, true);
188
210
  const turnId = p.turn_id == null ? null : String(p.turn_id);
211
+ if (isReservedWorkerModel(model)) return null;
189
212
  return { at: time(rec.timestamp), index, payload: p, model, turnId };
190
213
  }
191
214
 
@@ -297,4 +320,6 @@ module.exports = { claudeModelObservations, claudeHookModelObservations, codexMo
297
320
  laterRank: later, isCodexRelatedRecord, codexThreadMatches,
298
321
  claudeIdentityCandidate, claudeTurnCandidate, claudeIdentityObservations, claudeTurnObservations,
299
322
  codexSettingsCandidate, codexSettingsObservations, codexTurnContextCandidate, codexTerminalCandidate,
300
- codexTurnObservations };
323
+ codexTurnObservations,
324
+ // WP-0: the one shared "is this the reserved delegate alias?" predicate (see the doc comment above).
325
+ isReservedWorkerModel };
@@ -1,11 +1,16 @@
1
1
  'use strict';
2
2
 
3
+ const { isReservedWorkerModel } = require('./coding-session-model-adapters');
4
+
3
5
  const PRIORITY = Object.freeze({ resolved: 3, lastCompletedTurn: 2, selection: 1 });
4
6
  const MODEL_RESULT_PAGE_SIZE = 8;
5
7
  const MAX_MODEL_RESULT_BYTES = 1024 * 1024;
6
8
 
7
9
  function concrete(fact, allowNullKind) {
8
10
  return !!fact && typeof fact.value === 'string' && fact.value.trim() !== '' && Buffer.byteLength(fact.value.trim(), 'utf8') <= 256 && !fact.value.includes('<synthetic>') && Number.isFinite(fact.observedAtMs) && fact.observedAtMs >= 0 &&
11
+ // WP-0 (AC-0c): a historical row may still HOLD the reserved alias (it is kept for audit), but it
12
+ // is never a compatibility model -- the real selection/turn wins by the existing precedence below.
13
+ !isReservedWorkerModel(fact.value) &&
9
14
  (fact.selectionKind === 'concrete' || (allowNullKind && fact.selectionKind === null));
10
15
  }
11
16
 
@@ -1470,7 +1470,7 @@ function createDurableModelResultApplier({ modelStateRuntime, isCurrent, isOwner
1470
1470
  context.capturedSignature = refreshSignature(context.session, context.agentType);
1471
1471
  if (context.agentType === 'opencode' && context.result.sourceRevision) context.nextSourceRevision = String(context.result.sourceRevision);
1472
1472
  cacheCompleted(context);
1473
- if (context.agentType === 'codex' && context.session._codexTitleSynced !== true) {
1473
+ if (context.agentType === 'codex') { // WP-4/D-10: the thread name is decided on every completed probe, never once
1474
1474
  context.needsTitleDecision = true;
1475
1475
  intents.push({ id: context.session.id, agentType: 'codex', generation, capturedSignature: context.capturedSignature,
1476
1476
  agentSessionId: context.ownerSnapshot.providerSessionId, liveLabel: normalizeTitle(context.session.label),
@@ -1488,7 +1488,6 @@ function createDurableModelResultApplier({ modelStateRuntime, isCurrent, isOwner
1488
1488
  const entry = byId.get(String(context.session.id));
1489
1489
  if (!entry || Number(entry.generation) !== generation || entry.capturedSignature !== context.capturedSignature) continue;
1490
1490
  if (entry.titleApplied && entry.title && entry.titleKind !== 'live') { const label = `Codex: ${entry.title}`; if (context.session.label !== label) { context.session.label = label; titleChanged = true; } }
1491
- if (entry.titleSynced) context.session._codexTitleSynced = true;
1492
1491
  if (entry.titleSynced) cacheCompleted(context);
1493
1492
  }
1494
1493
  if (titleChanged && typeof broadcastSessionList === 'function') broadcastSessionList();
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { isReservedWorkerModel } = require('./coding-session-model-adapters');
4
+
3
5
  const SOURCE_GROUPS = {
4
6
  claude: {
5
7
  selection: ['hook:PostModelSwitch', 'hook:SessionStart', 'transcript:identity', 'db:restore'],
@@ -54,6 +56,14 @@ function validateObservation(input) {
54
56
  if (fields.outcome !== 'completed' || fields.value === null || fields.selectionKind !== 'concrete') return null;
55
57
  }
56
58
  if (fields.kind === 'selection' && ['alias', 'policy'].includes(fields.selectionKind) && fields.value === null) return null;
59
+ /*
60
+ * WP-0 (AC-0b): the reserved delegate alias is refused by VALUE for every kind, whatever
61
+ * selectionKind a caller claims for it -- a lead session's own model can never be what its
62
+ * subagents ask the proxy for. `validFact`/`validPrior` deliberately stay permissive, so a row
63
+ * already persisted with the alias still loads (the projection skips the fact; history is never
64
+ * rewritten or dropped).
65
+ */
66
+ if (isReservedWorkerModel(fields.value)) return null;
57
67
  return { ...fields, processGeneration: input.processGeneration, observedAtMs: input.observedAtMs };
58
68
  }
59
69
 
@@ -127,7 +127,6 @@ function createCodingSessionModelSyncState({
127
127
  String(session?.model_source || ''),
128
128
  Number(session?.model_observed_at || 0),
129
129
  String(session?.label || ''),
130
- session?._codexTitleSynced === true,
131
130
  String(session?._claudeSessionId || session?.agentSessionId || ''),
132
131
  /*
133
132
  * Corrective finding 10: the PURE provider-source inputs a checkpoint's offsets are relative to.
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const path = require('path');
3
4
  const { ANCHOR_PREFIX, cursorAnchorGuard } = require('./source-checkpoint');
4
5
 
5
6
  // WP-1 (design D-3/D-4): the conversation cursor is an anchor guard and nothing else.
@@ -77,13 +78,22 @@ function commitConversationBatchAndTranscriptCursor(db, options) {
77
78
  OR transcript_files.status IS NOT 'ok'
78
79
  OR transcript_files.last_error IS NOT ''
79
80
  `);
81
+ // A row's `ctm_session_id` changing under this upsert used to be silent -- one committed import
82
+ // re-keys the file to a different owner (a re-import of the same rollout under another session)
83
+ // with nothing in the log to say so. Read the prior owner inside the transaction, before the write.
84
+ const readPriorOwner = db.prepare('SELECT ctm_session_id FROM transcript_files WHERE jsonl_path = ?');
80
85
  return db.transaction(() => {
86
+ const priorOwner = readPriorOwner.get(options.filePath);
81
87
  const committed = options.commitBatch(db, options.batch);
82
88
  // WP-1 (S-3b): the guard is stored as-is; the manifest receipt binding is deleted with
83
89
  // the whole-prefix proof it fenced.
84
90
  upsert.run(options.filePath, options.agentSessionId, options.ctmSessionId, options.provider,
85
91
  String(options.stat.dev ?? ''), String(options.stat.ino ?? ''), Number(options.stat.size || 0),
86
92
  Number(options.stat.mtimeMs || 0), options.offset, options.parserVersion, options.guard);
93
+ // One line, ids only (truncated). The basename, never the full path, and never any content.
94
+ const prior = priorOwner ? String(priorOwner.ctm_session_id || '') : '';
95
+ const next = String(options.ctmSessionId || '');
96
+ if (priorOwner && prior !== next) console.log(`[transcript-owner] cursor commit changed owner ${path.basename(String(options.filePath || ''))}: ${prior.slice(0, 8)} -> ${next.slice(0, 8)}`);
87
97
  return committed;
88
98
  })();
89
99
  }