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,218 @@
1
+ 'use strict';
2
+
3
+ // WP-1 — docs/codex-routed-fork-cold-restore-guard-2026-10-22.html §5 D-1..D-4, §6.
4
+ //
5
+ // The incident this closes: a Codex LEAD is launched natively with startup args `["resume", T]` and
6
+ // NO `session_lead_route` row. Codex 0.163's `thread/resume` injects the thread's own stored
7
+ // provider (`$CODEX_HOME/state_5.sqlite` -> threads.model_provider, e.g. `ctm-lead`) into its config
8
+ // load, which then fails "Model provider `ctm-lead` not found" -> exit 1 -> the 3-restart budget
9
+ // burns 50 s and the session ends blocked with a message that says nothing about the cause.
10
+ //
11
+ // D-1: the provider a spawned Codex process loads is CTM's decision (the route row), never the
12
+ // string in the thread. D-2: that string is read ONLY to decide whether a NATIVE launch can
13
+ // succeed. So before a native spawn this gate asks: is the thread's stored provider one a native
14
+ // `codex` config load resolves (its built-ins ∪ the user `[model_providers]` table)? If not, the
15
+ // launch is either repaired from CTM's own transition journal (D-3, the same row the commit would
16
+ // have written) or refused deterministically before any PTY exists (D-4) — never spawned to die.
17
+ //
18
+ // Nothing here is flag-gated, and nothing here spawns a process: reading the state row is a single
19
+ // readonly SQLite lookup and the rollout fallback only reads the file's first line.
20
+
21
+ const path = require('node:path');
22
+ const { getCodexStateThreadById, readCodexRolloutMetadata } = require('../session-history');
23
+ const codexConfigGuard = require('../codex-config-guard');
24
+
25
+ // codex-rs/core/src/config/mod.rs built_in_model_providers(); `openai` is the default when unset.
26
+ const NATIVE_BUILTIN_PROVIDERS = Object.freeze(['openai', 'ollama', 'lmstudio', 'amazon-bedrock']);
27
+
28
+ // The route/model columns `session_lead_route` accepts (lib/storage/session-lead-route-store.js).
29
+ const MAX_ROUTE_FIELD_LENGTH = 200;
30
+
31
+ /**
32
+ * Read the provider a native `codex resume <threadId>` would load for this thread.
33
+ *
34
+ * Order is the authority order Codex itself uses (D-2 decision table): the state DB row wins — it
35
+ * is what `thread/resume` reads — then the rollout's own `session_meta.model_provider`, then the
36
+ * built-in default. `codexHome` is the CODEX_HOME directory the launch uses; the state DB lookup
37
+ * takes the HOME it derives `<HOME>/.codex` from, so `homeDir` may be passed explicitly and is
38
+ * otherwise taken to be `codexHome`'s parent.
39
+ *
40
+ * @returns {{ provider: string, source: 'state_db'|'rollout'|'default', rolloutPath: string|null }}
41
+ */
42
+ function readCodexThreadProvider({ threadId, codexHome = null, homeDir, rolloutPath = null,
43
+ getStateThread = getCodexStateThreadById, readRolloutMeta = readCodexRolloutMetadata } = {}) {
44
+ const id = String(threadId || '').trim();
45
+ const fallbackPath = rolloutPath ? String(rolloutPath) : null;
46
+ if (!id) return { provider: 'openai', source: 'default', rolloutPath: fallbackPath };
47
+ const resolvedHome = homeDir !== undefined && homeDir !== null
48
+ ? homeDir
49
+ : (codexHome ? path.dirname(String(codexHome)) : undefined);
50
+ let row = null;
51
+ try {
52
+ row = getStateThread(id, resolvedHome) || null;
53
+ } catch {
54
+ row = null; /* expected: a corrupt/unreadable state DB degrades to the rollout fallback (D-2) */
55
+ }
56
+ const stateProvider = String(row?.model_provider || '').trim();
57
+ const stateRolloutPath = String(row?.rollout_path || '').trim();
58
+ if (stateProvider) {
59
+ return { provider: stateProvider, source: 'state_db', rolloutPath: stateRolloutPath || fallbackPath };
60
+ }
61
+ const file = stateRolloutPath || fallbackPath;
62
+ if (file) {
63
+ let meta = null;
64
+ try {
65
+ meta = readRolloutMeta(file) || null;
66
+ } catch {
67
+ meta = null; /* expected: a missing/malformed rollout is not a provider source (D-2) */
68
+ }
69
+ const rolloutProvider = String(meta?.model_provider || '').trim();
70
+ if (rolloutProvider) return { provider: rolloutProvider, source: 'rollout', rolloutPath: file };
71
+ }
72
+ return { provider: 'openai', source: 'default', rolloutPath: file || null };
73
+ }
74
+
75
+ /**
76
+ * Decide whether a NATIVE spawn can serve this thread (D-2).
77
+ * native === true iff provider ∈ NATIVE_BUILTIN_PROVIDERS ∪ keys(config.toml[model_providers]).
78
+ *
79
+ * @returns {{ native: boolean, provider: string, source: string, configProviders: string[] }}
80
+ */
81
+ function classifyCodexThreadProvider({ provider, source = '', codexHome = null,
82
+ readConfigToml = codexConfigGuard.readModelProviderKeys } = {}) {
83
+ const id = String(provider || '').trim();
84
+ let configProviders = [];
85
+ try {
86
+ const listed = readConfigToml(codexHome);
87
+ if (Array.isArray(listed)) configProviders = listed.map((key) => String(key));
88
+ } catch {
89
+ configProviders = []; /* expected: an unreadable config.toml declares no user providers (S-5) */
90
+ }
91
+ const native = !!id && (NATIVE_BUILTIN_PROVIDERS.includes(id) || configProviders.includes(id));
92
+ return { native, provider: id, source, configProviders };
93
+ }
94
+
95
+ /**
96
+ * D-3. Find the newest `coding_model_transitions` row for `ctmSessionId` whose parsed target recipe
97
+ * names this thread on the Codex harness with a route, and write `session_lead_route` from it.
98
+ *
99
+ * The journal is CTM's own record of the fork it launched, so this never guesses: no matching row
100
+ * means no repair (`{ ok: false, reason: 'no_journal_route' }`) and D-4 refuses the launch. An
101
+ * EXISTING route row wins byte-for-byte: the read below short-circuits, and the store's write is
102
+ * INSERT … ON CONFLICT DO NOTHING through the DB-owner mutation lane (awaited), so two concurrent
103
+ * cold restores converge on one row (meta-test M-1).
104
+ *
105
+ * @returns {{ ok: true, route: { route_id: string, model: string, context_window: null, transition_id: string }, written: boolean }
106
+ * | { ok: false, reason: 'no_journal_route' }}
107
+ */
108
+ async function reconcileLeadRouteFromJournal({ ctmSessionId, threadId, dbModule, recordSessionDiagnostic } = {}) {
109
+ const id = String(ctmSessionId || '').trim();
110
+ const thread = String(threadId || '').trim();
111
+ if (!id || !thread) return { ok: false, reason: 'no_journal_route' };
112
+ let rows = [];
113
+ try {
114
+ rows = typeof dbModule?.getCodingModelTransitionsForThread === 'function'
115
+ ? (dbModule.getCodingModelTransitionsForThread(id, thread) || [])
116
+ : [];
117
+ } catch {
118
+ rows = []; /* expected: an unreadable journal is not a route source — refuse, never guess (D-3) */
119
+ }
120
+ const row = rows.find((candidate) => {
121
+ const recipe = candidate?.target_recipe || {};
122
+ return String(recipe.providerThreadId || '') === thread
123
+ && String(recipe.harness || '') === 'codex-cli'
124
+ && String(recipe.routeId || '').trim().length > 0;
125
+ }) || null;
126
+ if (!row) return { ok: false, reason: 'no_journal_route' };
127
+ const routeId = String(row.target_recipe.routeId || '').trim();
128
+ const model = String(row.target_recipe.wireModelId || '').trim();
129
+ const transitionId = String(row.transition_id || '').trim();
130
+ if (!routeId || !model || !transitionId
131
+ || routeId.length > MAX_ROUTE_FIELD_LENGTH || model.length > MAX_ROUTE_FIELD_LENGTH) {
132
+ return { ok: false, reason: 'no_journal_route' };
133
+ }
134
+ let existing = null;
135
+ try {
136
+ existing = typeof dbModule?.getSessionLeadRoute === 'function' ? dbModule.getSessionLeadRoute(id) : null;
137
+ } catch {
138
+ existing = null; /* expected: an unreadable route row is treated as absent — the store's write is idempotent */
139
+ }
140
+ let written = false;
141
+ let route;
142
+ if (existing) {
143
+ route = {
144
+ route_id: existing.route_id, model: existing.model,
145
+ context_window: existing.context_window == null ? null : existing.context_window,
146
+ transition_id: existing.transition_id || null,
147
+ };
148
+ } else {
149
+ let stored = null;
150
+ if (typeof dbModule?.setSessionLeadRoute === 'function') {
151
+ stored = await dbModule.setSessionLeadRoute({
152
+ ctmSessionId: id, agent: 'codex', routeId, model, display: model,
153
+ contextWindow: null, transitionId,
154
+ });
155
+ }
156
+ written = !!stored;
157
+ route = { route_id: routeId, model, context_window: null, transition_id: transitionId };
158
+ }
159
+ if (typeof recordSessionDiagnostic === 'function') {
160
+ recordSessionDiagnostic(id, 'lead-route-reconciled-from-journal', {
161
+ transition_id: route.transition_id, route_id: route.route_id, model: route.model,
162
+ provider_thread_id: thread, written,
163
+ });
164
+ }
165
+ return { ok: true, route, written };
166
+ }
167
+
168
+ /**
169
+ * The D-4 refusal message. ONE sentence, and the picker is the way out (server.js's
170
+ * RESUME_BLOCK_MESSAGES and the client's error map both render this same provider id).
171
+ */
172
+ function codexProviderMissingMessage(provider) {
173
+ return `This Codex thread was created on gateway provider \`${String(provider || '')}\`; pick a model for this session to relaunch it.`;
174
+ }
175
+
176
+ /**
177
+ * The launch-time hook (§9 S-3), called by provider-process.js when a Codex lead is about to be
178
+ * spawned natively on a resume thread (no `_launchLeadRoute` object and no route row). Returns
179
+ * `{ native: true }` when the thread's stored provider is servable natively, `{ target }` when
180
+ * the journal repaired a route (the caller runs its routed branch unchanged), or `{ refused: true }`
181
+ * after the deterministic resume block was recorded and the client told (D-4).
182
+ */
183
+ async function gateNativeCodexLaunch({ sessionId, threadId, env = {}, preparedLaunchPlan = null, dbModule,
184
+ recordSessionDiagnostic, markSessionResumeBlocked, ws = null, wsSend = null } = {}) {
185
+ const codexHome = env.CODEX_HOME || (env.HOME ? path.join(env.HOME, '.codex') : null);
186
+ const threadRead = readCodexThreadProvider({
187
+ threadId, codexHome, homeDir: env.HOME || null,
188
+ rolloutPath: preparedLaunchPlan?.resume?.rolloutInfo?.rolloutPath
189
+ || preparedLaunchPlan?.resume?.preflight?.transcriptPath || null,
190
+ });
191
+ const threadClass = classifyCodexThreadProvider({ ...threadRead, codexHome });
192
+ if (threadClass.native) return { native: true };
193
+ const reconciled = await reconcileLeadRouteFromJournal({ ctmSessionId: sessionId, threadId, dbModule, recordSessionDiagnostic });
194
+ if (reconciled.ok) {
195
+ return { target: { routeId: reconciled.route.route_id, model: reconciled.route.model, contextWindow: null } };
196
+ }
197
+ if (typeof recordSessionDiagnostic === 'function') {
198
+ recordSessionDiagnostic(sessionId, 'codex-thread-provider-unservable', {
199
+ provider_thread_id: threadId, provider: threadClass.provider, source: threadRead.source,
200
+ config_providers: threadClass.configProviders.length,
201
+ });
202
+ }
203
+ const message = codexProviderMissingMessage(threadClass.provider);
204
+ if (typeof markSessionResumeBlocked === 'function') {
205
+ markSessionResumeBlocked(sessionId, { code: 'codex_provider_missing', message, agentType: 'codex', deterministic: true }, 'codex-provider-gate');
206
+ }
207
+ if (ws && typeof wsSend === 'function') wsSend(ws, JSON.stringify({ type: 'error', code: 'codex_provider_missing', message }));
208
+ return { refused: true };
209
+ }
210
+
211
+ module.exports = {
212
+ NATIVE_BUILTIN_PROVIDERS,
213
+ readCodexThreadProvider,
214
+ classifyCodexThreadProvider,
215
+ reconcileLeadRouteFromJournal,
216
+ codexProviderMissingMessage,
217
+ gateNativeCodexLaunch,
218
+ };
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ // WP-2 — docs/codex-routed-fork-cold-restore-guard-2026-10-22.html §5 D-5, §9 S-9/S-10, §8 WP-2.
4
+ //
5
+ // The belt to WP-1's braces. WP-1 refuses a native Codex launch *before* it spawns when the
6
+ // thread's stored provider is not one a native config load resolves. This module covers the exits
7
+ // that get past any such gate: a thread whose provider changes between the decision and the spawn,
8
+ // and the D-8 paths that never consult the gate at all.
9
+ //
10
+ // Codex reports "Model provider `x` not found" as a plain anyhow error with exit 1 — the SAME exit
11
+ // code as a transient startup fault — so the exit code alone cannot discriminate: only the captured
12
+ // output text can. CTM already buffers that text per attempt (`session._resumeAttemptOutput`, 8 KiB,
13
+ // reset at the start of every attempt), which is what this classifier reads.
14
+ //
15
+ // A config-load failure is a precondition failure: no amount of retrying resolves it, so the
16
+ // caller blocks deterministically instead of spending the 3-restart budget (50 s) to reach the same
17
+ // state with a message that names nothing. Nothing here is flag-gated and nothing here spawns a
18
+ // process: the whole decision is one regex over the caller's string.
19
+
20
+ // The one Codex phrase this classifies, pinned deliberately narrow (§11: "do not widen the exit
21
+ // classifier beyond the one Codex phrase"; every other startup fault keeps the restart budget).
22
+ // The provider id is the backtick-quoted run Codex echoes back from the thread's stored
23
+ // `model_provider`.
24
+ const CODEX_PROVIDER_MISSING_RE = /Model provider `([^`]+)` not found/;
25
+
26
+ /**
27
+ * Classify one attempt's captured Codex output (the caller passes `session._resumeAttemptOutput`).
28
+ *
29
+ * @param {string} output the attempt buffer — never scrollback; a non-string degrades to ''
30
+ * @returns {{ code: 'codex_provider_missing', provider: string, line: string } | null}
31
+ * `null` for any output that does not carry the phrase, and for a phrase with no usable provider
32
+ * id (e.g. "Model provider `` not found"), so a malformed line can only ever fail OPEN into
33
+ * today's restart budget rather than blocking a thread it cannot name.
34
+ */
35
+ function classifyCodexResumeExit(output) {
36
+ const text = typeof output === 'string' ? output : '';
37
+ if (!text) return null;
38
+ const match = CODEX_PROVIDER_MISSING_RE.exec(text);
39
+ if (!match) return null;
40
+ const provider = String(match[1] || '').trim();
41
+ if (!provider) return null;
42
+ // `detail` is the whole line the phrase landed on (Codex wraps the anyhow error across the
43
+ // "thread/resume failed: ..." prefix and the "(code -32600)" suffix); the line is the unit a
44
+ // human reads, and the provider id alone would drop the surrounding failure context.
45
+ const lineStart = text.lastIndexOf('\n', Math.max(0, match.index - 1)) + 1;
46
+ const lineEndRaw = text.indexOf('\n', match.index);
47
+ const lineEnd = lineEndRaw === -1 ? text.length : lineEndRaw;
48
+ const line = text.slice(lineStart, lineEnd).replace(/\r$/, '').trim();
49
+ return { code: 'codex_provider_missing', provider, line: line || match[0] };
50
+ }
51
+
52
+ module.exports = { CODEX_PROVIDER_MISSING_RE, classifyCodexResumeExit };
@@ -23,7 +23,7 @@ const { armAt } = require('./clamped-timer');
23
23
  const admission = require('../worker-admission');
24
24
  const { createWorkerAdmissionExit } = require('./worker-admission-exit');
25
25
  const { createWorkerContinuation, validCallerToolUseId } = require('./worker-continuation');
26
- const { createWorkerObservability, safeToolCounts, progressForGeneration, baselineChanged, statusProgress, currentGenerationReceipts, currentReceiptState, computeExitFailure, boundedWorkerText, isColdDeferredSession, workerHideLogLine, probeCachedFileState, THROTTLED_WINDOW_MS, EVENT_RECEIPT_LIMIT } = require('./worker-observability');
26
+ const { createWorkerObservability, safeToolCounts, progressForGeneration, baselineChanged, statusProgress, currentGenerationReceipts, currentReceiptState, computeExitFailure, boundedWorkerText, isColdDeferredSession, workerHideLogLine, probeCachedFileState, THROTTLED_WINDOW_MS, EVENT_RECEIPT_LIMIT, WORKER_REPORT_TEXT_CAP_BYTES } = require('./worker-observability');
27
27
  const { killSessionProcess } = require('../session-class'); // worker-session-tree WP-2: the one kill authority
28
28
  const { loadWorkerBrief, validateWorkerCwd, validateBriefPath, validateBriefRealpathInsideCwd, renderedBriefPathForSession, renderedBriefWithBudget, ownedPaths: parseOwnedPaths, findScopeOverlap } = require('./worker-brief');
29
29
  const { checkCallerAuthority } = require('./worker-launch-sandbox');
@@ -49,10 +49,27 @@ const EFFORT_VALUES = new Set(['low', 'medium', 'high', 'xhigh']);
49
49
  const { createProxyRouteReadiness, DelegateError, setLeadRouteReadiness, DEFAULT_ROUTE_ALIAS, DEFAULT_MODEL, READINESS_BUDGET_MS } = require('./proxy-route-readiness');
50
50
  const DEFAULT_TOKEN_TTL_MS = 24 * 60 * 60 * 1000 + 10 * 60 * 1000; // session lifetime + 10 min (D-4)
51
51
 
52
+ // D-3 (lead-cost-routing-round2-2026-10-24.html §5 D-3, §6.1/§6.2): the ONE citation counter,
53
+ // shared with wall-e/lib/helper-rules/label.js so the counter and the labeller cannot drift.
54
+ const { countReportCitations } = require('../../../shared/report-citations');
55
+
52
56
  // Only generated UUIDs are accepted before report paths are constructed, preventing encoded
53
57
  // path traversal through GET /api/sessions/:id/worker-status?include_report=1.
54
58
  const SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
55
59
 
60
+ // The statuses whose payload carries a citation count; every other status reports null/null
61
+ // because no text has been selected yet (D-3's §6.2 table).
62
+ const WORKER_TERMINAL_STATUSES = new Set(['report_ready', 'exited', 'failed']);
63
+
64
+ // D-3: read at most WORKER_REPORT_TEXT_CAP_BYTES of a report/last-message file, decode as UTF-8
65
+ // and drop a trailing U+FFFD left by a split multi-byte sequence -- byte-for-byte the semantics
66
+ // lib/api/sessions.js:931 applies to an ?include_report=1 read. A throw (ENOENT/EACCES/EISDIR)
67
+ // is the caller's to classify: getWorkerStatus turns it into report_read_error, never a failure.
68
+ function _readBoundedReportText(filePath) {
69
+ const bytes = fs.readFileSync(filePath);
70
+ return bytes.subarray(0, WORKER_REPORT_TEXT_CAP_BYTES).toString('utf8').replace(/\uFFFD$/, '');
71
+ }
72
+
56
73
  function _assertValidSessionId(id) {
57
74
  if (!SESSION_ID_RE.test(String(id || ''))) {
58
75
  throw new DelegateError('session_invalid', 400, 'ctm_session_id is not a valid session id');
@@ -791,6 +808,31 @@ function getWorkerStatus(ctmSessionId, { now = Date.now } = {}) {
791
808
  status = 'starting';
792
809
  }
793
810
 
811
+ // D-3 (lead-cost-routing-round2-2026-10-24.html §5 D-3, §6.2): CTM is the ONLY counter of a
812
+ // report's distinct `file:line` citations. Count whichever text the row's OWN report_source
813
+ // names -- the report file, the last-message fallback, or the final provider result -- on
814
+ // every read, independent of ?include_report=1, so the whole-set wait's nested `worker`
815
+ // payload (which never carries report_text) still answers with a row count. Non-terminal
816
+ // statuses have selected no text yet and answer null/null; a read failure degrades to 0/0
817
+ // plus report_read_error and never fails the status read (same posture as report_present).
818
+ const terminalStatus = WORKER_TERMINAL_STATUSES.has(status);
819
+ let citationText = '';
820
+ let reportReadError = null;
821
+ if (terminalStatus) {
822
+ if (reportSource === 'report' || reportSource === 'last_message') {
823
+ try {
824
+ citationText = _readBoundedReportText(reportSource === 'report' ? reportPath : lastMessagePath);
825
+ } catch (error) {
826
+ // expected: report/last-message file missing, unreadable or a directory -- the count
827
+ // degrades to 0 with the error code exposed, exactly like the include_report=1 reader.
828
+ reportReadError = (error && error.code) || 'read_failed';
829
+ citationText = '';
830
+ }
831
+ } else if (reportSource === 'final_result') {
832
+ citationText = typeof successfulFinalText === 'string' ? successfulFinalText : '';
833
+ }
834
+ }
835
+
794
836
  const identity = workerRow ? 'marked' : 'unmarked';
795
837
 
796
838
  return {
@@ -799,6 +841,12 @@ function getWorkerStatus(ctmSessionId, { now = Date.now } = {}) {
799
841
  report_present: reportPresent,
800
842
  report_source: reportSource,
801
843
  report_text: reportText,
844
+ // D-3 (§6.2): the citation count and the character count of the text report_source names,
845
+ // never gated by include_report. report_read_error rides along ONLY when the file read
846
+ // threw (its error code, or 'read_failed'), in which case both counts are 0.
847
+ report_citation_rows: terminalStatus ? countReportCitations(citationText) : null,
848
+ report_chars: terminalStatus ? citationText.length : null,
849
+ ...(reportReadError ? { report_read_error: reportReadError } : {}),
802
850
  report_provisional: !!reportPresent && alive,
803
851
  last_message_path: lastMessagePath,
804
852
  turns: observer ? Number(observer.count || 0) : (Number.isInteger(runtime?.turns) ? runtime.turns : (Number.isInteger(session?._workerTurnCount) ? session._workerTurnCount : null)),
@@ -961,6 +1009,13 @@ function cancelRetirement(ctmSessionId) {
961
1009
  // that never exits doesn't spam the log every 60s forever.
962
1010
  const HIDE_LIVE_PROCESS_RETRY_MS = 60_000;
963
1011
 
1012
+ // WP-1 (durable terminal stamp, worker-terminal-stamp-durable-2026-10-26): the ceiling for
1013
+ // markWorkerTerminal's transient-lock retry backoff. The write lock is held by the db-owner core
1014
+ // writer's transactions (observed 259-1210 ms back-to-back for 19 s on 2026-10-26), so
1015
+ // the retry has NO attempt ceiling -- it re-arms until the stamp lands or the row is already
1016
+ // terminal / hidden / gone -- and the geometric backoff stops growing here.
1017
+ const TERMINAL_STAMP_RETRY_CAP_MS = 5000;
1018
+
964
1019
  async function _hideWorker(ctmSessionId, { now = Date.now } = {}) {
965
1020
  const deps = _requireDeps();
966
1021
  _retentionTimers.delete(ctmSessionId);
@@ -1074,11 +1129,13 @@ function _invalidateWorkerFailureCache(deps, ctmSessionId) {
1074
1129
  session._workerFailure = null;
1075
1130
  }
1076
1131
 
1077
- function markWorkerTerminal(ctmSessionId, { terminalStatus, failure = null, runtime = null, now = Date.now, retry = 0 } = {}) {
1132
+ function markWorkerTerminal(ctmSessionId, { terminalStatus, failure = null, runtime = null, now = Date.now, retry = 0, firstAttemptMs = null } = {}) {
1078
1133
  const deps = _requireDeps();
1079
1134
  let row;
1080
1135
  try { row = deps.dbModule.getWorkerSession(ctmSessionId); } catch { row = null; /* expected: an unreadable row is treated as no worker row, so nothing is stamped or scheduled -- a later exit-path call or the boot reload retries */ }
1081
- if (!row) return;
1136
+ // WP-1: a row that no longer exists, or that another path already hid (hidden_at stamped =
1137
+ // the worker is retired and its row is out of every listing), ends the retry chain here.
1138
+ if (!row || row.hidden_at) return;
1082
1139
  const nowMs = typeof now === 'function' ? now() : Number(now);
1083
1140
  const nowIso = new Date(nowMs).toISOString();
1084
1141
  const patch = {};
@@ -1113,12 +1170,23 @@ function markWorkerTerminal(ctmSessionId, { terminalStatus, failure = null, runt
1113
1170
  try {
1114
1171
  deps.dbModule.updateWorkerSession(ctmSessionId, patch);
1115
1172
  } catch (err) {
1116
- // WP-7 live finding: a busy write lock fails FAST and the old early return also dropped the wait wake + terminal sentinel -- disposition is now REPORT + bounded retry of the whole stamp.
1173
+ // WP-7 live finding: a busy write lock fails FAST and the old early return also dropped the wait wake + terminal sentinel -- disposition is now REPORT + retry of the whole stamp.
1174
+ // WP-1: that retry has NO attempt ceiling -- a transient lock error is not the stamp's
1175
+ // failure, it is contention on the main-thread writer (2026-10-26: 19 s of back-to-back
1176
+ // 259-1210 ms transactions outlived the old 5-attempt / 1.5 s budget and two finished
1177
+ // workers stayed listed as live for 9 h and 6 h). The chain ends at the top of the
1178
+ // re-entered call: row gone / hidden_at set, or the patch finding terminal_at already
1179
+ // written -- and, of course, when this write lands.
1117
1180
  // The retry's terminal writes start only when it fires, so the pending retry is tracked by the terminal settle.
1118
- if (['SQLITE_WRITE_LOCK_BUSY', 'SQLITE_BUSY', 'SQLITE_LOCKED'].includes(err && err.code) && retry < 5) return void workerLedger.trackTerminalWork(new Promise((resolve) => setTimeout(() => { try { markWorkerTerminal(ctmSessionId, { terminalStatus, failure, runtime, now, retry: retry + 1 }); } finally { resolve(); } }, 100 * (retry + 1)).unref?.()));
1181
+ if (['SQLITE_WRITE_LOCK_BUSY', 'SQLITE_BUSY', 'SQLITE_LOCKED'].includes(err && err.code)) {
1182
+ const firstAtMs = firstAttemptMs ?? nowMs;
1183
+ if (retry === 5) console.warn(`[delegate-worker] terminal stamp still busy after 6 attempts (${String((err && err.code) || 'write_failed').slice(0, 48)}); retrying every ${TERMINAL_STAMP_RETRY_CAP_MS} ms`);
1184
+ return void workerLedger.trackTerminalWork(new Promise((resolve) => setTimeout(() => { try { markWorkerTerminal(ctmSessionId, { terminalStatus, failure, runtime, now, retry: retry + 1, firstAttemptMs: firstAtMs }); } finally { resolve(); } }, Math.min(100 * (retry + 1), TERMINAL_STAMP_RETRY_CAP_MS)).unref?.()));
1185
+ }
1119
1186
  console.warn(`[delegate-worker] terminal stamp failed (${String((err && err.code) || "write_failed").slice(0, 48)})`); return;
1120
1187
  }
1121
1188
  row = { ...row, ...patch };
1189
+ if (retry > 0) console.log(`[delegate-worker] terminal stamp landed for ${ctmSessionId} after ${retry + 1} attempts in ${nowMs - (firstAttemptMs ?? nowMs)} ms`);
1122
1190
  _invalidateWorkerFailureCache(deps, ctmSessionId);
1123
1191
  }
1124
1192
  const hideAtMs = _computeHideAtMs(row, _retainMsForSession(deps, ctmSessionId));
@@ -1195,6 +1263,25 @@ function retireNow(session, now) {
1195
1263
  markWorkerTerminal(session.id, { terminalStatus: result.status, failure: result.failure, runtime, now });
1196
1264
  }
1197
1265
 
1266
+ // WP-2 (worker-terminal-stamp-durable-2026-10-26 D-2/D-3): one idempotent read repair for a
1267
+ // worker that EXITED but whose row never got its exit-time stamp (the write-lock loss WP-1 now
1268
+ // retries). Every lead-facing reader calls this FIRST, so a dead-but-unstamped worker is never
1269
+ // listed live and the stop verb never answers 'stopping' on a process that no longer exists.
1270
+ // Returns 'retired' | 'lost' | null. Idempotent: a stamped or hidden row is a no-op.
1271
+ // retireWorkerOnExit is deliberately NOT awaited -- a proxied worker's stamp is deferred by one
1272
+ // async read and tracked by workerLedger.trackTerminalWork, so callers must not assume the
1273
+ // stamp is synchronous (the next read retries anyway).
1274
+ function repairExitedWorkerRow(ctmSessionId, { now = Date.now } = {}) {
1275
+ const deps = _requireDeps();
1276
+ let row = null;
1277
+ try { row = deps.dbModule.getWorkerSession(ctmSessionId); } catch { row = null; /* expected: an unreadable row cannot be repaired now; the next read retries */ }
1278
+ if (!row || row.terminal_at || row.hidden_at) return null;
1279
+ const session = typeof deps.getSession === 'function' ? deps.getSession(ctmSessionId) : null;
1280
+ if (session && session.agentRole === 'worker' && (session._exitCode != null || session._exited)) { retireWorkerOnExit(session, { now }); return 'retired'; }
1281
+ if (!session) { retireLostWorker(row, { now }); return 'lost'; }
1282
+ return null; // a live process: nothing to repair
1283
+ }
1284
+
1198
1285
  // Lifecycle WP-5 (§5 D-10, R-3, §9 S-8, AC-5c): boot re-arm -- every live (hidden_at === '')
1199
1286
  // worker_sessions row that already has a persisted hide_at gets its retirement timer
1200
1287
  // re-armed at that EXACT value (never recomputed off the current retainMs, so a config
@@ -1263,7 +1350,7 @@ const retireLostWorker = bootRetire.retireLostWorker, retireSurvivingWorker = bo
1263
1350
  module.exports = {
1264
1351
  configure, _terminalJournalSettled: workerLedger.terminalJournalSettled,
1265
1352
  resumeWorkerSession: workerContinuation.resumeWorkerSession,
1266
- stopWorkerSession: (id, opts) => require('./worker-stop').stopWorkerSession(id, opts, _requireDeps(), { resolveParent: resolveParentCtmSessionId }),
1353
+ stopWorkerSession: (id, opts) => require('./worker-stop').stopWorkerSession(id, opts, _requireDeps(), { resolveParent: resolveParentCtmSessionId, repairExitedWorkerRow }),
1267
1354
  probeWorkerRoute: workerContinuation.probeWorkerRoute,
1268
1355
  restoreQuotaHoldsAtBoot: workerContinuation.restoreQuotaHoldsAtBoot,
1269
1356
  markAgentConsumed: workerContinuation.markAgentConsumed,
@@ -1290,6 +1377,8 @@ module.exports = {
1290
1377
  markAcknowledged,
1291
1378
  markWorkerTerminal,
1292
1379
  retireWorkerOnExit,
1380
+ // WP-2 (D-2): the idempotent read repair both lead-facing readers call before answering.
1381
+ repairExitedWorkerRow,
1293
1382
  reloadRetirementsAtBoot,
1294
1383
  listPendingRetirements,
1295
1384
  _resetRetentionTimersForTest,
@@ -7,6 +7,25 @@ function configure(getDeps) {
7
7
  _getDeps = getDeps;
8
8
  }
9
9
 
10
+ // WP-4 (docs/remote-hub-followups-credential-projects-rows-idempotency-2026-10-23.html §5 D-11):
11
+ // the create fingerprint an Idempotency-Key retry is compared against -- the JSON of these nine
12
+ // launch-shaping fields, IN THIS ORDER, with a missing field read as ''. Geometry (cols/rows) and
13
+ // label/title are deliberately EXCLUDED: a retry may differ in window size or naming and is still
14
+ // the same create. Pure: reads nothing but `msg`, allocates nothing, touches no socket.
15
+ const CREATE_FINGERPRINT_FIELDS = Object.freeze([
16
+ 'cmd', 'args', 'cwd', 'createCwd', 'model_id', 'model_provider', 'mcp_profile', 'agentType', 'chatSessionId',
17
+ ]);
18
+
19
+ function createFingerprint(msg) {
20
+ const source = msg && typeof msg === 'object' ? msg : {};
21
+ return JSON.stringify(CREATE_FINGERPRINT_FIELDS.map((field) => {
22
+ const value = source[field];
23
+ // `args` is compared AS GIVEN (an argv array serializes element by element); every other
24
+ // field is a scalar. Absent and null both read as ''.
25
+ return value === undefined || value === null ? '' : value;
26
+ }));
27
+ }
28
+
10
29
  function prepareCreate(ws, msg) {
11
30
  const deps = _getDeps();
12
31
  const handleCreateStartedAt = deps.now();
@@ -67,4 +86,4 @@ function prepareCreate(ws, msg) {
67
86
  };
68
87
  }
69
88
 
70
- module.exports = { configure, prepareCreate };
89
+ module.exports = { configure, prepareCreate, createFingerprint };
@@ -1,5 +1,10 @@
1
1
  'use strict';
2
2
 
3
+ // WP-4 (docs/remote-hub-followups-credential-projects-rows-idempotency-2026-10-23.html §5 D-11):
4
+ // the ONE fingerprint definition lives in lib/session-create/identity.js; this module stamps it on a
5
+ // session it fully owned and compares it on the next same-id create.
6
+ const { createFingerprint } = require('./identity');
7
+
3
8
  // WP-2b (docs/session-model-authority-and-display-2026-10-15.html §9): the COMPLETE prepared-create
4
9
  // orchestration, moved verbatim out of server.js's `handleCreatePrepared`. It owns one whole
5
10
  // responsibility -- admitted request -> durable generation -> prepared current plan -> trusted
@@ -26,11 +31,23 @@
26
31
  // argv, token or raw send-error text is ever echoed into the report. Every other `_wsSend` in
27
32
  // the handler (mcp_profile_invalid, launch-generation-refused, cwd-create-failed,
28
33
  // launch_transcript_restore_failed, the folder toast) keeps its original throwing behavior.
34
+ // WP-4 (docs/remote-hub-followups-credential-projects-rows-idempotency-2026-10-23.html §5 D-13):
35
+ // the per-id IN-FLIGHT create ledger -- id -> { fingerprint } (D-11). One entry per create that has
36
+ // passed owner/MCP admission and has not yet left handleCreatePrepared; a second create with the
37
+ // SAME id AND the same fingerprint while the first is in flight is refused `create_in_progress`
38
+ // before it reserves a generation, plans, or spawns anything. The entry is deleted in the handler's
39
+ // `finally`, which covers every exit (success, typed refusal, early null return, throw). A DIFFERENT
40
+ // fingerprint never refuses and never overwrites the first entry (D-14: a model-switch relaunch
41
+ // stays the generation runtime's business).
42
+ const _inflightCreates = new Map();
43
+
29
44
  function createPreparedSessionCreate(host) {
30
45
  const {
31
46
  // Admission / identity
32
47
  crypto, _sessionMcpProfileSetting, _wsSend, _codingSessionGenerationRuntime,
33
48
  recordSessionDiagnostic, isSessionResumeBlocked, clearSessionResumeBlocked, getSession,
49
+ // WP-4 (§5 D-12): the one `created` frame a duplicate create is re-answered with.
50
+ sendNativeSessionCreated,
34
51
  // Input / local path
35
52
  serverConfig, path, fs, detectAgentType, extractResumeTarget,
36
53
  // Metadata / planning
@@ -61,6 +78,9 @@ function createPreparedSessionCreate(host) {
61
78
  }
62
79
  }
63
80
 
81
+ // WP-4 (§5 D-13): admission, then the in-flight claim, then the unchanged orchestration. The
82
+ // claim's `finally` wraps the WHOLE body, so every exit of the orchestration releases it -- and no
83
+ // line of the orchestration itself had to move to make that true.
64
84
  async function handleCreatePrepared(ws, msg) {
65
85
  if (host._ownerRecovery.rejectRequest(ws, msg)) return null; const id = msg?.id || crypto.randomUUID();
66
86
  const preparedMsg = { ...(msg || {}), id };
@@ -70,6 +90,44 @@ function createPreparedSessionCreate(host) {
70
90
  if (ws?.readyState === 1) _wsSend(ws, JSON.stringify({ type: 'error', code: 'mcp_profile_invalid', message: `Unknown mcp_profile: ${JSON.stringify(_rawMcpProfile)}` }));
71
91
  return null;
72
92
  }
93
+ // WP-4 (§5 D-12/D-13): a client-supplied id is the Idempotency-Key, and both duplicate answers
94
+ // are decided HERE, before any generation reservation, plan, markSpawning or markLive:
95
+ // - the id names a LIVE session spawned by an identical create => re-answer ONE `created`
96
+ // frame (same builder a fresh create uses) and return it; nothing is attached or rolled back;
97
+ // - an identical create is still in flight => one typed `create_in_progress` refusal.
98
+ // A DIFFERENT fingerprint is NOT a duplicate (D-14): it walks today's path (attach, or the
99
+ // generation runtime's model-switch relaunch) and leaves the other fingerprint's entry alone.
100
+ let _claimedInflight = false;
101
+ const fingerprint = msg?.id ? createFingerprint(preparedMsg) : '';
102
+ if (fingerprint) {
103
+ const live = getSession(id);
104
+ if (ws && live?.ptyProcess && live._createFingerprint === fingerprint) {
105
+ sendNativeSessionCreated(ws, live);
106
+ return live;
107
+ }
108
+ const inFlight = _inflightCreates.get(id);
109
+ if (inFlight && inFlight.fingerprint === fingerprint) {
110
+ sendPreparedError(ws, {
111
+ type: 'error',
112
+ code: 'create_in_progress',
113
+ id,
114
+ message: 'A create for this session is already in progress',
115
+ });
116
+ return null;
117
+ }
118
+ if (!inFlight) { _inflightCreates.set(id, { fingerprint }); _claimedInflight = true; }
119
+ }
120
+ try {
121
+ return await _prepareCreateAdmitted(ws, msg, preparedMsg, id, fingerprint);
122
+ } finally {
123
+ if (_claimedInflight) _inflightCreates.delete(id);
124
+ }
125
+ }
126
+
127
+ // The orchestration itself: byte-for-byte the body that used to be handleCreatePrepared's, from
128
+ // the generation reservation on, plus the one D-11 fingerprint stamp on the owned success path.
129
+ // Split out ONLY so the D-13 claim above can wrap every exit.
130
+ async function _prepareCreateAdmitted(ws, msg, preparedMsg, id, fingerprint) {
73
131
  const reserved = _codingSessionGenerationRuntime.revalidateReservation(await _codingSessionGenerationRuntime.reserveSessionGeneration(id));
74
132
  if (!reserved.ok) { recordSessionDiagnostic(id, 'launch-generation-refused', { code: reserved.code, message: reserved.message }); if (ws?.readyState === 1) _wsSend(ws, JSON.stringify({ type: 'error', code: reserved.code, message: `Launch refused: ${reserved.message}` })); return null; }
75
133
  const generation = reserved.generation;
@@ -274,6 +332,10 @@ function createPreparedSessionCreate(host) {
274
332
  });
275
333
  return null;
276
334
  }
335
+ // WP-4 (§5 D-11): only a create this handler fully owned, keyed by a client id, can be re-answered
336
+ // later. A retry landing between the `created` frame and this line still finds the in-flight
337
+ // entry above and gets `create_in_progress`.
338
+ if (fingerprint && created && created.type !== 'walle') created._createFingerprint = fingerprint;
277
339
  _launchPlanCoordinator.markLive(id, generation, result.plan);
278
340
  return created || null;
279
341
  }
@@ -281,4 +343,10 @@ function createPreparedSessionCreate(host) {
281
343
  return handleCreatePrepared;
282
344
  }
283
345
 
284
- module.exports = { createPreparedSessionCreate };
346
+ module.exports = {
347
+ createPreparedSessionCreate,
348
+ // WP-4 test seam (AC-4a/§10 abuse case): the in-flight ledger is process-local, so a test that
349
+ // drives the real factory in-process asserts it drains through this accessor rather than reaching
350
+ // into the module's internals.
351
+ inflightCreateCount: () => _inflightCreates.size,
352
+ };