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
@@ -34,6 +34,7 @@ const UNKNOWN_IDLE_BACKOFF_MS = 60000;
34
34
  const OPEN_TURN_IDLE_PROBE_AFTER_MS = 15000;
35
35
  // D-6: a session tree's CPU-time delta is process evidence at or above this floor AND at or above 5 % of one core
36
36
  // over the sample interval (the park predicate's cpuPct >= 5); the rate keeps a 60 s sample from firing on 0.8 %.
37
+ const BG_OUTPUT_UNREADABLE_DIAG_MS = 10 * 60 * 1000;
37
38
  const CPU_ACTIVE_MIN_DELTA_MS = 500;
38
39
  const CPU_ACTIVE_MIN_CORE_FRACTION = 0.05;
39
40
  // The fast tick fires on a 10 s timer whose jitter must not skip a due sample.
@@ -886,6 +887,17 @@ function createBindings(d) {
886
887
  shape.zeros = 0; // a newer snapshot (or no shell to check) restarts the count
887
888
  shape.observedAt = ledger.backgroundObservedAt;
888
889
  }
890
+ // D-2: a descendant CPU-time delta is silent-activity evidence; the baseline is independent of snapshots.
891
+ const cpuNow = Number(sample.descendantCpuTimeMs);
892
+ if (Number.isFinite(cpuNow)) {
893
+ if (Number.isFinite(shape.cpuTimeMs)) {
894
+ const delta = cpuNow - shape.cpuTimeMs;
895
+ const intervalMs = Number(sample.intervalMs) > 0 ? Number(sample.intervalMs) : (sampledAt - (Number(shape.cpuSampledAt) || 0));
896
+ if (delta >= Math.max(CPU_ACTIVE_MIN_DELTA_MS, CPU_ACTIVE_MIN_CORE_FRACTION * intervalMs)) ledger.markBackgroundActive(isHookShell, sampledAt);
897
+ }
898
+ shape.cpuTimeMs = cpuNow;
899
+ shape.cpuSampledAt = sampledAt;
900
+ }
889
901
  const leaders = Number(sample.descendantGroupLeaderCount);
890
902
  if (leaders > 0 && sampledAt > ledger.backgroundObservedAt && [...ledger.background.values()].some(isHookShell)) {
891
903
  const renewed = ledger.renewBackground(isHookShell, sampledAt); // D-1
@@ -915,6 +927,29 @@ function createBindings(d) {
915
927
  d.markHeartbeatDirty(session);
916
928
  }
917
929
 
930
+ // D-3: the output file of each live hook shell is a second silent-activity signal. Never throws.
931
+ async function observeTaskOutputs(session, sampledAt) {
932
+ try {
933
+ const ledger = session && session._statusLedger;
934
+ if (!ledger || !d.fsp) return;
935
+ const entries = [...ledger.background.values()].filter((e) => e.type === 'shell' && e.source === 'hook');
936
+ for (const e of entries) {
937
+ const p = typeof d.taskOutputPathFor === 'function' ? d.taskOutputPathFor(session, e.id) : '';
938
+ if (!p) continue;
939
+ try {
940
+ const st = await d.fsp.stat(p);
941
+ ledger.observeBackgroundOutput(e.id, { size: st.size, mtimeMs: st.mtimeMs }, sampledAt);
942
+ } catch (err) {
943
+ const at = Number(sampledAt) || Date.now();
944
+ if (!(at - (Number(ledger._bgOutputUnreadableAt) || 0) < BG_OUTPUT_UNREADABLE_DIAG_MS)) {
945
+ ledger._bgOutputUnreadableAt = at;
946
+ d.recordDiagnostic(session.id, 'status-background-output-unreadable', { id: e.id, code: (err && err.code) || '' });
947
+ }
948
+ }
949
+ }
950
+ } catch { /* expected: evidence-only observer must never throw into the sampler */ }
951
+ }
952
+
918
953
  // D-6 cadence: 60 s baseline is the lifecycle sweep's own ps; while any ledger is probable/unknown/near-stall
919
954
  // (or a session was just CPU-active) this tick takes ONE cheap ps at STATUS_FAST_SAMPLE_MS. At baseline it is a no-op.
920
955
  let cpuTickRunning = false;
@@ -979,7 +1014,7 @@ function createBindings(d) {
979
1014
  }
980
1015
  }
981
1016
 
982
- return { timeDrivenTick, inputWaitIsIdle, markHookObserved, ledgerFor, verdictFor, recordEvidence, recordWaitCleared, syncWaitFields, composerMayClose, composerRetryMs, keepBlockingWait, cardGoneTwice, cardSeenTwice, cardKeyFor, scheduleWaitEvaluation, cancelWaitEvaluations, recordPtyChunk, fireStallBackstop, observeTranscriptBoundary, observeTranscriptGrowth, observeCodexRollout, liveStatus, tick, applyCpuSample, applyProcessShape, processSampleTick };
1017
+ return { timeDrivenTick, inputWaitIsIdle, markHookObserved, ledgerFor, verdictFor, recordEvidence, recordWaitCleared, syncWaitFields, composerMayClose, composerRetryMs, keepBlockingWait, cardGoneTwice, cardSeenTwice, cardKeyFor, scheduleWaitEvaluation, cancelWaitEvaluations, recordPtyChunk, fireStallBackstop, observeTranscriptBoundary, observeTranscriptGrowth, observeCodexRollout, liveStatus, tick, applyCpuSample, applyProcessShape, observeTaskOutputs, processSampleTick };
983
1018
  }
984
1019
 
985
1020
  // W-1: under host load shedding the wait-state sweep still owes the sessions whose status truth is at stake: wait fields set,
@@ -998,6 +1033,14 @@ function shedSweepIds(sessionMap, approverBuffers, cap = 4, now = Date.now()) {
998
1033
  return due.slice(0, cap).map(({ id, s }) => { s._reconcileVisitedAt = now; return id; });
999
1034
  }
1000
1035
 
1036
+ // D-3: Claude Code's background-shell output file. Pure; the server passes uid and its canonical project-entry encoder.
1037
+ function claudeTaskOutputPathFor(session, taskId, { uid, encodeClaudeProjectEntry } = {}) {
1038
+ const sid = String((session && session.agentSessionId) || '').trim();
1039
+ const cwd = String((session && session.cwd) || '');
1040
+ if (!sid || !cwd || !/^[\w.-]{1,80}$/.test(String(taskId)) || String(taskId) === '..' || String(taskId) === '.') return '';
1041
+ return require('path').join('/tmp', 'claude-' + uid, encodeClaudeProjectEntry(cwd), sid, 'tasks', taskId + '.output');
1042
+ }
1043
+
1001
1044
  // R-10: the constant-size empty keepalive frame, built on the main thread (never behind a read-pool op that can time out).
1002
1045
  function keepaliveFrame(now) {
1003
1046
  return JSON.stringify({ type: 'session-activity', sessions: [], keepalive: true, statusTimestamp: Number(now) || 0, serverNow: Number(now) || 0 });
@@ -1015,6 +1058,7 @@ function rowFields(v, reason) {
1015
1058
 
1016
1059
  module.exports = {
1017
1060
  createBindings,
1061
+ claudeTaskOutputPathFor,
1018
1062
  movingCardKey,
1019
1063
  shedSweepIds,
1020
1064
  keepaliveFrame,
@@ -11,7 +11,7 @@ const RESUMING_RESUME_PHASES = new Set(['preparing', 'spawning', 'loading', 'str
11
11
  // projectSessionStatus(session) is a pure reducer over the fields server.js already writes
12
12
  // today (`_lifecycleState`, `_resumeAttemptState.phase`, `_resumeBlocked`, `_pendingApproval`,
13
13
  // `_pressureShed`) — see server.js's RESUME_PHASE enum (lib/resume-attempt-state.js) and the
14
- // `_lifecycleState` values ('frozen'/'live'/'parking'/'parked'/'deferred'/'waking'/'degraded'/
14
+ // `_lifecycleState` values ('live'/'parking'/'parked'/'deferred'/'waking'/'degraded'/
15
15
  // 'exited') for the source vocabulary this projects from.
16
16
  //
17
17
  // requestTransition(sessionId, to, cause) is the ONE gate for changing the FSM's *phase*.
@@ -48,7 +48,7 @@ function projectSessionStatus(session) {
48
48
  else if (lifecycleState === 'parked' || lifecycleState === 'parking') phase = 'parked';
49
49
  else if (lifecycleState === 'degraded' || resumePhase === 'blocked') phase = 'degraded';
50
50
  else if (resumeBlocked || RESUMING_RESUME_PHASES.has(resumePhase)) phase = 'resuming';
51
- else if (lifecycleState === 'waking' || lifecycleState === 'deferred' || lifecycleState === 'frozen') phase = 'attaching';
51
+ else if (lifecycleState === 'waking' || lifecycleState === 'deferred') phase = 'attaching';
52
52
  else if (lifecycleState === 'live' || (lifecycleState === '' && (resumePhase === 'ready' || resumePhase === ''))) phase = 'live';
53
53
  else phase = 'detached';
54
54
 
@@ -76,14 +76,14 @@ async function _mirrorSessionTitleAttempt(o) {
76
76
  const message = (error && error.message) || String(error);
77
77
  try {
78
78
  if (db && typeof db.setSessionTitleMirror === 'function') {
79
- db.setSessionTitleMirror(sessionId, { state: 'failed', error: message, value: '' });
79
+ db.setSessionTitleMirror(sessionId, { state: 'failed', reason: 'exception', error: message, value: '' });
80
80
  }
81
81
  } catch (persistError) {
82
82
  // expected: a persist failure on this catch-all path must not turn a resolved
83
83
  // outcome into a throw either.
84
84
  void persistError;
85
85
  }
86
- return { state: 'failed', reason: '', error: message };
86
+ return { state: 'failed', reason: 'exception', error: message };
87
87
  }
88
88
  }
89
89
 
@@ -91,7 +91,10 @@ async function _mirrorSessionTitleInner({ db, sessionId, adapters, getTitleSpec
91
91
  const logger = typeof log === 'function' ? log : () => {};
92
92
  const persist = (state, reason, error, value) => {
93
93
  try {
94
- db.setSessionTitleMirror(sessionId, { state, error: error || '', value: value || '' });
94
+ // D-12: the REASON travels with the outcome — db.setSessionTitleMirror composes
95
+ // title_mirror_error = reason + (error ? ': ' + error : ''), so an `unsupported` row can no
96
+ // longer sit there with an empty error and no recorded cause (S-24).
97
+ db.setSessionTitleMirror(sessionId, { state, reason: reason || '', error: error || '', value: value || '' });
95
98
  } catch (persistError) {
96
99
  // expected: a persist failure must not turn a resolved outcome into a throw —
97
100
  // the caller still gets the real state/reason/error, just unrecorded in the DB.
@@ -147,10 +150,11 @@ async function _mirrorSessionTitleInner({ db, sessionId, adapters, getTitleSpec
147
150
  outcome = { state: 'unsupported', reason: 'unknown_title_spec_kind', error: '' };
148
151
  }
149
152
 
150
- // Step 7: persist + return.
153
+ // Step 7: persist + return. D-12: a failure that reports no reason of its own still gets one, so
154
+ // `failed`/`unsupported` never lands with an empty reason to compose from.
151
155
  return persist(
152
156
  outcome.state,
153
- outcome.reason || '',
157
+ outcome.reason || (outcome.state === 'failed' ? 'adapter_failed' : ''),
154
158
  outcome.error || '',
155
159
  outcome.state === 'mirrored' ? name : row.value
156
160
  );
@@ -166,6 +170,14 @@ async function _mirrorSessionTitleInner({ db, sessionId, adapters, getTitleSpec
166
170
  // Synchronous by design (setSessionTitleNew is a plain DB write, not provider I/O) — callers on
167
171
  // a live-session sync tick (lib/coding-agent-models.js, workers/db-owner-worker.js) never await
168
172
  // this; a rejection would have nothing to propagate to, so it's log-and-drop and retried next tick.
173
+ // D-9/AC-4c: the LAST agent name a re-assert was fired for, per CTM session. The observed name is
174
+ // re-read on every sync tick (D-10) while the agent's own name only changes when the AGENT renames
175
+ // itself, so a plain per-session memo is what turns "observed on every tick" into "mirrored once per
176
+ // observed name". Convergence clears the entry (the agent's name equals the CTM title again), so a
177
+ // later drift fires again. In-memory by design: it is a coalescing memo for a live process, never a
178
+ // durable state machine — a restart simply re-observes.
179
+ const _lastReassertedName = new Map();
180
+
169
181
  function applyProviderTitle(o = {}) {
170
182
  const { db, sessionId, provider, mirror = mirrorSessionTitle } = o;
171
183
  const agentName = normalizeSessionTitle(o.agentName);
@@ -175,13 +187,20 @@ function applyProviderTitle(o = {}) {
175
187
  if (agentName && agentName !== normalizeSessionTitle(row.title)) {
176
188
  // Fire-and-forget re-assert: the agent has drifted from the user's CTM name: push the
177
189
  // CTM name back out. NEVER write the agent's name over a user rename.
190
+ const key = String(sessionId || '');
191
+ if (_lastReassertedName.get(key) === agentName) return { applied: 'none', reason: 'already_reasserted' };
192
+ _lastReassertedName.set(key, agentName);
178
193
  void mirror({ db, sessionId });
179
194
  return { applied: 'reasserted' };
180
195
  }
196
+ if (agentName) _lastReassertedName.delete(String(sessionId || ''));
181
197
  return { applied: 'none' };
182
198
  }
183
199
 
184
200
  if (agentName) {
201
+ // No write churn: a row already carrying this exact name (tier 2 or a tier-3 title that
202
+ // happens to match) is left alone — no setSessionTitleNew, no updated_at bump.
203
+ if (row && normalizeSessionTitle(row.title) === agentName) return { applied: 'none', reason: 'unchanged' };
185
204
  try {
186
205
  // Explicitly detached: the write is best-effort off a live-session sync tick and this
187
206
  // function is synchronous, so a rejection has no caller to propagate to — the next tick
@@ -202,57 +221,6 @@ function applyProviderTitle(o = {}) {
202
221
  return { applied: 'none' };
203
222
  }
204
223
 
205
- // WP-3 (session-rename-mirror §5 D-1, D-4; AC-3d/AC-3e): resolves the Claude agent's OWN
206
- // session name for feeding applyProviderTitle, on demand for ONE session — never call this from
207
- // a full-catalog scan loop (see AC-3f's "no process spawn" sibling concern for Codex; this one's
208
- // hazard is the SDK round-trip, not a spawn). Guarded by getTitleSpec('claude')?.readsUserName:
209
- // an absent/false spec (the kill switch is `titleSpec: null`) falls straight back to the
210
- // caller-supplied parser result (parsedTitle, e.g. parseSessionFile's tail/head-scan title)
211
- // WITHOUT ever touching the SDK adapter (AC-3e). `customTitle` (info.userName) outranks the
212
- // derived `aiTitle` (info.summary when it differs from the first prompt) — same precedence
213
- // lib/claude-session-title.js's readClaudeSessionTitle already encodes into its own return shape.
214
- async function resolveClaudeAgentName(o = {}) {
215
- const { sessionId, dir, sdk, parsedTitle = '', getTitleSpec = defaultGetTitleSpec, adapter } = o;
216
- const spec = getTitleSpec('claude');
217
- if (!spec || !spec.readsUserName) return normalizeSessionTitle(parsedTitle);
218
- const claude = adapter || _defaultClaudeAdapter();
219
- const info = await claude.readClaudeSessionTitle({ sessionId, dir, sdk });
220
- if (!info) return normalizeSessionTitle(parsedTitle);
221
- const name = info.userName || info.aiTitle || '';
222
- return normalizeSessionTitle(name || parsedTitle);
223
- }
224
-
225
- // Fix round 1 (Finding 1): production wiring for resolveClaudeAgentName — one call per Claude
226
- // session becoming known/linked to CTM (never a scan/batch loop; the SDK round-trip is the
227
- // per-session hazard, matching AC-3f's spawn concern for Codex's sibling). Callers use
228
- // `void fillClaudeAgentTitle(...)` — async, fire-and-forget, never awaited on a hot sync path.
229
- // Cheap guard BEFORE the SDK read: a tier-1 (user_renamed) row never triggers the round-trip.
230
- // Never throws into the caller — any error (a throwing db read, a rejecting resolve/apply) is
231
- // reported via recordSessionDiagnostic (HANDLE) and swallowed; `unsupported`/`sdk_absent` from
232
- // resolveClaudeAgentName is a normal, silent `{applied:'none'}`, not an error.
233
- async function fillClaudeAgentTitle(o = {}) {
234
- const { db, sessionId, provider = 'claude', claudeSessionId, dir, sdk, getTitleSpec, adapter, recordSessionDiagnostic, log } = o;
235
- try {
236
- const row = db.getSessionTitleMirror(sessionId);
237
- if (row && row.userRenamed && row.title) return { applied: 'none' };
238
- const agentName = await resolveClaudeAgentName({ sessionId: claudeSessionId, dir, sdk, getTitleSpec, adapter });
239
- if (!agentName) return { applied: 'none' };
240
- return applyProviderTitle({ db, sessionId, provider, agentName });
241
- } catch (error) {
242
- const message = (error && error.message) || String(error);
243
- try {
244
- if (typeof recordSessionDiagnostic === 'function') {
245
- recordSessionDiagnostic(sessionId, 'claude-agent-title-fill-error', { error: message });
246
- }
247
- } catch (diagError) {
248
- // expected: diagnostics must never turn a swallowed error into a throw.
249
- void diagError;
250
- }
251
- if (typeof log === 'function') log('fillClaudeAgentTitle failed', message);
252
- return { applied: 'none' };
253
- }
254
- }
255
-
256
224
  // F1 (final review, AC-3g): shared guard for the tier-3 "live label" writer — the ONLY
257
225
  // question this answers is "does `existing` already carry a tier that a tier-3 write must
258
226
  // never clobber", NEVER a db access itself (pure, so both an on-thread caller with a
@@ -269,4 +237,4 @@ function canWriteTier3Title(existing) {
269
237
  return true;
270
238
  }
271
239
 
272
- module.exports = { mirrorSessionTitle, isUuidLike, applyProviderTitle, resolveClaudeAgentName, fillClaudeAgentTitle, canWriteTier3Title };
240
+ module.exports = { mirrorSessionTitle, isUuidLike, applyProviderTitle, canWriteTier3Title };
@@ -196,7 +196,14 @@ async function findTrashedTranscript({ trashRoot, fileName, fs: fsImpl = fsPromi
196
196
  } catch { continue; /* expected: missing/corrupt manifest in this trash dir */ }
197
197
  for (const entry of manifest.entries || []) {
198
198
  if (path.basename(entry.originalPath) === fileName) {
199
- return { trashPath: entry.trashPath, originalPath: entry.originalPath, trashedAt: manifest.trashedAt };
199
+ // WP-3 (D-13): the manifest already carries WHY it was trashed and when; the card's "why"
200
+ // sentence is built from these two facts alone, so they travel with the lookup result.
201
+ return {
202
+ trashPath: entry.trashPath,
203
+ originalPath: entry.originalPath,
204
+ trashedAt: manifest.trashedAt,
205
+ reason: String(manifest.reason || ''),
206
+ };
200
207
  }
201
208
  }
202
209
  }
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+ // WP-3 (docs/codex-routed-fork-cold-restore-guard-2026-10-22.html §9 S-11, §10 AC-3a/3b): the
3
+ // startup pointer is written ONLY through db.updateStartupTaskAgentSession, whose
4
+ // `resumesCandidateFork` guard (lib/lead-model-transition-store.js, commit 77b9b3c0e3) keeps the
5
+ // source pointer while a lead-model transition is still active. session-integrity.js used to
6
+ // re-point it with a raw `UPDATE startup_tasks SET args …` that bypassed the guard: the 30-min pass
7
+ // picks the newest transcript row — exactly the uncommitted fork of a half-switched Codex session.
8
+ //
9
+ // Callers hand this the raw better-sqlite3 core handle (workers/db-owner-worker.js
10
+ // 'recoverSessionIntegrity' → db.getDomainDb('core')) or the db module; either way the write goes
11
+ // through the module's guarded writer against the same core table. The writer returns nothing, so
12
+ // `args` is compared before/after to keep the fixed/skipped accounting a raw UPDATE's `.changes` gave.
13
+
14
+ function readStartupPointerArgs(handle, sessionId) {
15
+ try {
16
+ return handle.prepare('SELECT args FROM startup_tasks WHERE ctm_session_id = ?').get(sessionId)?.args;
17
+ } catch {
18
+ /* expected: no startup_tasks/args column in a partial fixture schema — the write below reports the real failure */
19
+ return undefined;
20
+ }
21
+ }
22
+
23
+ // Lazy require: db.js is loaded by the owner process already; nothing in its graph requires this file.
24
+ function startupPointerWriter(handle) {
25
+ if (handle && typeof handle.updateStartupTaskAgentSession === 'function') {
26
+ return handle.updateStartupTaskAgentSession.bind(handle);
27
+ }
28
+ const dbModule = require('../db.js');
29
+ if (typeof dbModule.updateStartupTaskAgentSession !== 'function') {
30
+ throw new Error('db.updateStartupTaskAgentSession is unavailable; refusing a raw startup_tasks pointer write');
31
+ }
32
+ return dbModule.updateStartupTaskAgentSession;
33
+ }
34
+
35
+ // The `agent_project_dir`/`claude_project_dir` value currently on the row — the guarded writer
36
+ // rewrites BOTH columns from one argument, so a caller that only means to move `args` hands back
37
+ // what is already stored instead of clearing the dirs.
38
+ function readStartupTaskProjectDir(handle, sessionId) {
39
+ for (const column of ['agent_project_dir', 'claude_project_dir']) {
40
+ try {
41
+ return handle.prepare(`SELECT ${column} AS dir FROM startup_tasks WHERE ctm_session_id = ?`).get(sessionId)?.dir || '';
42
+ } catch {
43
+ /* expected: column absent on this schema — try the sibling column */
44
+ }
45
+ }
46
+ return '';
47
+ }
48
+
49
+ // Move the startup pointer through the guarded writer; returns 1 when the row changed, else 0.
50
+ function writeStartupPointerGuarded(handle, sessionId, resumeArgs, projectDir) {
51
+ const write = startupPointerWriter(handle);
52
+ const before = readStartupPointerArgs(handle, sessionId);
53
+ write(sessionId, Array.isArray(resumeArgs) ? resumeArgs[1] : '', projectDir, resumeArgs);
54
+ return readStartupPointerArgs(handle, sessionId) === before ? 0 : 1;
55
+ }
56
+
57
+ module.exports = { writeStartupPointerGuarded, readStartupTaskProjectDir };
@@ -27,12 +27,23 @@ function nearestBreaks(breaks, top) {
27
27
  return list.slice().sort((a, b) => Math.abs(top - a.at) - Math.abs(top - b.at)).slice(0, BREAKS_MAX).sort((a, b) => a.at - b.at);
28
28
  }
29
29
 
30
+ function holdOf(h) {
31
+ if (!h || typeof h !== 'object') return null;
32
+ return { kind: typeof h.kind === 'string' ? h.kind : null, rows: isInt(h.rows) && h.rows >= 0 ? h.rows : 0, top: isInt(h.top) ? h.top : null };
33
+ }
34
+
30
35
  // `deps` = { readPainted(id, epoch, from, count), paintedRevisedSince(id, seq) }.
31
36
  // -> an object to spread into the frame: {} or { painted: { epoch, top, pinned, floor, revision, breaks, below[, revisedFrom][, from, rows] } }
32
37
  function paintedFrameField(deps, id, keyframe, ackedGrid, grid) {
33
38
  const now = grid && grid.painted;
34
39
  if (!now || typeof now !== 'object') return {};
35
40
  const state = { epoch: now.epoch, top: now.top, pinned: now.pinned, floor: now.floor, revision: isInt(now.revision) ? now.revision : 0, breaks: nearestBreaks(now.breaks, now.top), below: isInt(now.below) && now.below > 0 ? now.below : 0 }; // R-F2 (reader-seam-proof-followups): rows the app holds below its screen; a host that never measured it (or an at-rest app) sends 0
41
+ // Clock-free tests (WP-6): `samples` (the recorder's monotone observe count) and `hold` (the rejected, uncommitted samples) ride
42
+ // the field so a reader waits on "this write was sampled" instead of a window; neither one decides whether a frame is sent.
43
+ // Each is present only when the recorder published it, so an older recorder's field is unchanged.
44
+ if (isInt(now.samples) && now.samples >= 0) state.samples = now.samples;
45
+ const hold = holdOf(now.hold);
46
+ if (hold) state.hold = hold;
36
47
  const before = ackedGrid && ackedGrid.painted;
37
48
  if (keyframe || !before || before.epoch !== now.epoch) return { painted: state };
38
49
  const beforeRevision = isInt(before.revision) ? before.revision : 0;
@@ -282,10 +282,10 @@ function persistModelSelectionBatch(db, payload = {}, deps = {}) {
282
282
  if (!intents.length) return { generation, entries: [] };
283
283
  // Fail fast on a wiring mistake instead of a half-working batch (the model half would
284
284
  // persist while the codex title half silently threw).
285
- for (const name of ['isGenericCodingSessionLabel', 'shouldApplyCodexAutoTitle', 'applyProviderTitle', 'canWriteTier3Title']) {
285
+ for (const name of ['isGenericCodingSessionLabel', 'applyProviderTitle', 'canWriteTier3Title']) {
286
286
  if (typeof deps[name] !== 'function') throw new TypeError(`persistModelSelectionBatch requires deps.${name}`);
287
287
  }
288
- const { isGenericCodingSessionLabel, shouldApplyCodexAutoTitle } = deps;
288
+ const { isGenericCodingSessionLabel } = deps;
289
289
  const { applyProviderTitle, canWriteTier3Title } = deps;
290
290
  const entries = [];
291
291
  for (const intent of intents) {
@@ -326,27 +326,26 @@ function persistModelSelectionBatch(db, payload = {}, deps = {}) {
326
326
  if (intent.agentType === 'codex') {
327
327
  const existingTitle = db.getSessionTitleNew(id) || null;
328
328
  const liveLabel = String(intent.liveLabel || '').trim();
329
- const session = { label: liveLabel };
330
- if (existingTitle?.userRenamed) {
331
- entry.titleSynced = true;
332
- } else if (!shouldApplyCodexAutoTitle({ session, existingTitle }) && canWriteTier3Title({ userRenamed: existingTitle?.userRenamed, title_source: db.getDomainDb('core').prepare('SELECT title_source FROM ctm_sessions WHERE id=?').get(id)?.title_source })) {
333
- if (liveLabel
334
- && !isGenericCodingSessionLabel(liveLabel, 'codex')
335
- && String(existingTitle?.title || '').trim() !== liveLabel) {
336
- db.setSessionTitleNew(id, liveLabel, false);
337
- entry.titleApplied = true;
338
- entry.title = liveLabel;
339
- entry.titleKind = 'live';
340
- }
341
- entry.titleSynced = true;
342
- } else {
343
- const agentName = String(intent.title || '').trim();
344
- if (agentName) {
345
- const outcome = applyProviderTitle({ db, sessionId: id, provider: 'codex', agentName });
346
- if (outcome.applied === 'agent') { entry.titleApplied = true; entry.title = agentName; entry.titleKind = 'provider'; }
347
- entry.titleSynced = true;
348
- }
329
+ // WP-4/D-10: the thread's own name wins on EVERY observation — no one-shot `titleSynced`
330
+ // latch, and no `shouldApplyCodexAutoTitle` precedence test that could let a CTM title block
331
+ // the name (S-22). applyProviderTitle owns the tier rules (tier-1 re-assert, equal-name
332
+ // no-op, tier-2 write); the label-only fallback runs only when the thread has no name.
333
+ const agentName = String(intent.title || '').trim();
334
+ if (agentName) {
335
+ const outcome = applyProviderTitle({ db, sessionId: id, provider: 'codex', agentName });
336
+ if (outcome.applied === 'agent') { entry.titleApplied = true; entry.title = agentName; entry.titleKind = 'provider'; }
337
+ } else if (liveLabel
338
+ && !isGenericCodingSessionLabel(liveLabel, 'codex')
339
+ && String(existingTitle?.title || '').trim() !== liveLabel
340
+ && canWriteTier3Title({ userRenamed: existingTitle?.userRenamed, title_source: db.getDomainDb('core').prepare('SELECT title_source FROM ctm_sessions WHERE id=?').get(id)?.title_source })) {
341
+ db.setSessionTitleNew(id, liveLabel, false);
342
+ entry.titleApplied = true;
343
+ entry.title = liveLabel;
344
+ entry.titleKind = 'live';
349
345
  }
346
+ // The decision RAN (whatever it decided) — this receipt is the batch's completion signal
347
+ // (cacheCompleted) and the durable applier's latch; it is no longer a one-shot name gate.
348
+ entry.titleSynced = true;
350
349
  }
351
350
  entries.push(entry);
352
351
  }
@@ -6,7 +6,7 @@ const { createRecoveryProductionAdapters } = require('./recovery-production-adap
6
6
  const { waitFor } = require('../../bin/wait-for');
7
7
 
8
8
  function createSurvivingOwnerBootstrap(options) {
9
- const { dataDir, log = console.warn } = options;
9
+ const { dataDir, log = console.warn, onSkip = () => {} } = options;
10
10
  const adapters = createRecoveryProductionAdapters(options);
11
11
  const recovery = createSurvivingOwnerRecovery({ dataDir, ...adapters });
12
12
  let loaded = false, unresolved = false, failed = false, lastReason = '';
@@ -28,10 +28,15 @@ function createSurvivingOwnerBootstrap(options) {
28
28
  function assertAdmission() {
29
29
  if (protectsSession()) throw Object.assign(new Error('Session is preserved by pending owner recovery'), { code: 'OWNER_RECOVERY_PENDING', status: 409 });
30
30
  }
31
- function admissionResult() {
31
+ // WP-3 (§9 S-20 / AC-3e): `(id, task)` are accepted for the skip DIAGNOSTIC only -- callers
32
+ // (server.js _startDeferredRestoreSession/_Attempt/Now) have always passed them and were silently
33
+ // ignored. The fence VERDICT is unchanged and never inspects them: it is still exactly
34
+ // "is any owner-recovery operation still pending?".
35
+ function admissionResult(id, task) {
32
36
  try { assertAdmission(); return null; }
33
37
  catch (error) {
34
38
  if (error.code !== 'OWNER_RECOVERY_PENDING') throw error;
39
+ onSkip('OwnerRecoveryFence', id || task?.session_id || '', null);
35
40
  return { ok: false, status: 'blocked', code: 'owner_recovery_pending', reason: 'owner_recovery_pending', retryable: true, message: error.message };
36
41
  }
37
42
  }
@@ -10,6 +10,7 @@ const path = require('path');
10
10
  const crypto = require('crypto');
11
11
  const { readPrivateJson } = require('./recovery-private-file');
12
12
  const { assertRecoveryInput, verifyConfirmationReceipt } = require('./recovery-evidence');
13
+ const { isWritableOnlyByOwner } = require('../../shared/owner-write-safety');
13
14
 
14
15
  const DIRNAME = 'surviving-owner-recovery';
15
16
  const STAGES = new Set(['staged', 'awaiting-preservation', 'retiring', 'ready-to-adopt', 'completed']);
@@ -27,7 +28,7 @@ function operationPath(dataDir, operationId) {
27
28
  async function ensureOperationDir(dataDir) {
28
29
  if (!path.isAbsolute(dataDir)) throw new Error('recovery data directory must be absolute');
29
30
  const root = await fs.promises.lstat(dataDir);
30
- if (!root.isDirectory() || root.isSymbolicLink() || root.uid !== process.getuid?.() || (root.mode & 0o022)) throw new Error('unsafe recovery data directory');
31
+ if (!root.isDirectory() || root.isSymbolicLink() || root.uid !== process.getuid?.() || !isWritableOnlyByOwner(root)) throw new Error('unsafe recovery data directory');
31
32
  const dir = operationDir(dataDir);
32
33
  try { await fs.promises.mkdir(dir, { mode: 0o700 }); }
33
34
  catch (error) { if (error.code !== 'EEXIST') throw error; /* expected: existing private operation directory */ }
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ function terminalDraftHttpStatus(error) {
4
+ if (!error) return 500;
5
+ if (error.code === 'CTM_MAIN_DB_REQUEST_FORBIDDEN') return 501;
6
+ if (error.code === 'CTM_OWNERSHIP_UNAVAILABLE' || error.code === 'CTM_DB_OWNER_UNAVAILABLE' ||
7
+ error.code === 'CTM_MEDIA_JOURNAL_UNAVAILABLE') return 503;
8
+ if (error.code === 'CTM_DRAFT_NOT_FOUND' || error.code === 'CTM_DRAFT_SESSION_NOT_FOUND') return 404;
9
+ if (error.code === 'CTM_DRAFT_PURGED') return 410;
10
+ if (error.code === 'CTM_DRAFT_SESSION_RETIRING') return 409;
11
+ if (error.code === 'CTM_SESSION_OWNER_MISMATCH') return 409;
12
+ if (error.code === 'CTM_DRAFT_REVISION_CONFLICT') return 412;
13
+ // CTM_IMAGE_IDEMPOTENCY_CONFLICT is the legacy/mobile lanes' equivalent of the draft
14
+ // lane's idempotency conflict (media-store requestId dedupe) — same 409 semantic
15
+ // (WP-9 lane-parity: distinguishable from a malformed 400 without inspecting `code`).
16
+ if (error.code === 'CTM_DRAFT_IDEMPOTENCY_CONFLICT' || error.code === 'CTM_IMAGE_IDEMPOTENCY_CONFLICT' ||
17
+ error.code === 'CTM_DRAFT_DELIVERY_SUPERSEDED' ||
18
+ error.code === 'CTM_DRAFT_DELIVERY_STATE') return 409;
19
+ if (error.code === 'CTM_DRAFT_FROZEN' || error.code === 'CTM_DRAFT_REQUEST_CANCELLED') return 409;
20
+ if (error.code === 'CTM_DRAFT_IMAGE_INVALID' || error.code === 'CTM_DRAFT_SESSION_REQUIRED' ||
21
+ error.code === 'CTM_DRAFT_IDENTITY_INVALID' || error.code === 'CTM_DRAFT_DELIVERY_OUTCOME_INVALID') return 400;
22
+ return 500;
23
+ }
24
+
25
+ module.exports = { terminalDraftHttpStatus };