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
@@ -23,6 +23,18 @@ function textOrEmpty(value) {
23
23
  return value == null ? '' : value;
24
24
  }
25
25
 
26
+ // A `usage_reread_prior.fact_key`: the JSON array of a fact's primary-key values, in primary-key
27
+ // column order (WP-3). Every key is written by this module, so a value that cannot be read is a
28
+ // corrupted or future-format row and names no fact.
29
+ function parseFactKey(raw) {
30
+ try {
31
+ const parsed = JSON.parse(String(raw));
32
+ return Array.isArray(parsed) ? parsed : null;
33
+ } catch (err) {
34
+ return null; // expected: an unreadable snapshot key names no fact, so the row is ignored
35
+ }
36
+ }
37
+
26
38
  function callParams(call, fileId) {
27
39
  // A row that already declares its own identity ambiguous carries no cost, whatever the caller
28
40
  // supplied: the INSERT path has no stored row to compare against, so the rule has to hold here
@@ -657,6 +669,11 @@ function openUsageLedger(dbPath, { Database = null } = {}) {
657
669
  // wrote under an older reading, not upsert beside them. These run inside
658
670
  // upsertBatchTxn, before the new facts, so the swap is atomic.
659
671
  //
672
+ // CODEX ONLY since WP-3. A Claude file's re-read is a SNAPSHOT plus an EOF sweep
673
+ // (see "non-destructive Claude re-read" below): the vendor rewrites a compacted
674
+ // transcript to FEWER rows, so "the new reading did not emit it" is not evidence that
675
+ // the fact is gone. The statements here are still exactly the Codex path (D-10).
676
+ //
660
677
  // Known limitation: a Codex call key shared by several fork files (a copied parent
661
678
  // history) is stored under the FIRST file that wrote it. Resetting that one file
662
679
  // deletes the shared row until the file re-reads it -- which happens in the same
@@ -699,6 +716,168 @@ function openUsageLedger(dbPath, { Database = null } = {}) {
699
716
  SELECT session_id, prompt_id, 0 FROM usage_prompts
700
717
  WHERE source_file_id = @source_file_id AND preview IS NULL`);
701
718
 
719
+ // --- non-destructive Claude re-read (WP-3 D-7/D-8) ---------------------------
720
+ // Claude Code REWRITES a compacted transcript: it drops every row before the last
721
+ // compact boundary and renames a new file over the path. A re-read therefore sees
722
+ // FEWER rows than the reading that wrote the stored facts, and the WP-8b delete above
723
+ // would destroy usage nothing can re-create (the incident: 4.5 days of a lead
724
+ // session's calls and every launch receipt). The rule for a Claude file is instead:
725
+ //
726
+ // 1. RESET (a re-read from 0): SNAPSHOT the fact keys this file had written into
727
+ // `usage_reread_prior`. Nothing is deleted.
728
+ // 2. EVERY SLICE, after the fact upserts: a fact the slice RE-EMITTED leaves the
729
+ // snapshot (it is not missing), and every vendor id the slice OBSERVED is
730
+ // recorded in `usage_reread_seen`.
731
+ // 3. EOF (`final`): a fact still in the snapshot is deleted IFF its vendor id was
732
+ // OBSERVED (the vendor still has the row and this reading no longer produces the
733
+ // fact) OR its vendor id is not derivable at all (an older identity form, where
734
+ // nothing can prove the vendor dropped it). A fact whose source row the vendor
735
+ // DELETED has no observed id and is KEPT -- that is D-7.
736
+ //
737
+ // Both scratch tables are cleared for the file in the same transaction that writes the
738
+ // checkpoint, so no re-read state outlives the pass. Codex files never touch them.
739
+ const clearRereadSeenStmt = db.prepare('DELETE FROM usage_reread_seen WHERE file_id = ?');
740
+ const clearRereadPriorStmt = db.prepare('DELETE FROM usage_reread_prior WHERE file_id = ?');
741
+ const insertRereadPriorStmt = db.prepare(`
742
+ INSERT OR IGNORE INTO usage_reread_prior (file_id, kind, fact_key, vendor_id)
743
+ VALUES (@file_id, @kind, @fact_key, @vendor_id)`);
744
+ const deleteRereadPriorKeysStmt = db.prepare(`
745
+ DELETE FROM usage_reread_prior
746
+ WHERE file_id = @file_id AND kind = @kind
747
+ AND fact_key IN (SELECT value FROM json_each(@keys))`);
748
+ const rereadPriorCountStmt = db.prepare(
749
+ 'SELECT count(*) AS n FROM usage_reread_prior WHERE file_id = ?'
750
+ );
751
+ const selectRereadPriorStmt = db.prepare(
752
+ 'SELECT kind, fact_key, vendor_id FROM usage_reread_prior WHERE file_id = ?'
753
+ );
754
+ const selectRereadSeenStmt = db.prepare(
755
+ 'SELECT kind, vendor_id FROM usage_reread_seen WHERE file_id = ?'
756
+ );
757
+ const insertRereadSeenStmt = db.prepare(`
758
+ INSERT OR IGNORE INTO usage_reread_seen (file_id, kind, vendor_id)
759
+ VALUES (@file_id, @kind, @vendor_id)`);
760
+ // The sessions a doomed CALL attributed: the rollup joins THROUGH the call row, so they
761
+ // must be re-derived after it goes. (A spawn's session rides its own fact key.) Scoped to
762
+ // THIS file, exactly like the delete it feeds: another source file may own the row now.
763
+ const callSessionsByKeysStmt = db.prepare(`
764
+ SELECT DISTINCT session_id FROM usage_calls
765
+ WHERE call_key IN (SELECT value FROM json_each(@keys))
766
+ AND source_file_id = @source_file_id`);
767
+ // The evidence a doomed PROMPT still holds (an alias's digest is part of its own key).
768
+ // Scoped to this file: the evidence belongs to the row the sweep is about to delete, so a
769
+ // prompt another source now owns is neither read for evidence nor tombstoned here.
770
+ const promptEvidenceByKeyStmt = db.prepare(`
771
+ SELECT provider_message_digest, source_event_digest, preview FROM usage_prompts
772
+ WHERE session_id = ? AND prompt_id = ? AND source_file_id = ?`);
773
+ // The whole-file form of preserveLegacyRedactionsStmt, for ONE doomed prompt (D-8), scoped
774
+ // to the file that still owns the row for the same reason.
775
+ const preserveLegacyRedactionStmt = db.prepare(`
776
+ INSERT OR IGNORE INTO usage_prompt_purges (session_id, prompt_id, purged_at)
777
+ SELECT session_id, prompt_id, 0 FROM usage_prompts
778
+ WHERE session_id = @session_id AND prompt_id = @prompt_id AND preview IS NULL
779
+ AND source_file_id = @source_file_id`);
780
+
781
+ // The six fact kinds a Claude file writes, and the snapshot/delete statements per kind.
782
+ // * `columns` is the fact's PRIMARY KEY in column order: `fact_key` is the JSON array of
783
+ // those values, built identically here and in SQL (`json_array(...)` in the snapshot
784
+ // assignments below), so a key written by one side is always found by the other.
785
+ // * `vendor` is the id the VENDOR gave the record the fact came from -- the thing D-8
786
+ // decides on -- and `seen` is the `usage_reread_seen.kind` that id is recorded under.
787
+ // `null` means "not derivable" and is swept, as WP-8b did.
788
+ // * `sourceScoped` adds `AND source_file_id = @source_file_id` to the DELETE. The primary
789
+ // key alone is not enough: a fact this file snapshotted may have been REWRITTEN by
790
+ // another source file before this file's EOF sweep -- the `.jsonl.bak` sibling writes
791
+ // the same `call_key` and moves its `source_file_id` -- and deleting by key alone would
792
+ // destroy the OTHER source's fact. `alias` is not source-scoped because its own key
793
+ // already ends in `source_file_id`, so a key of another source's alias can never match.
794
+ const CLAUDE_CALL_KEY_RE = /^claude:[^:]*:[^:]*:(.+)$/;
795
+ const REREAD_KINDS = [
796
+ {
797
+ kind: 'call',
798
+ table: 'usage_calls',
799
+ columns: ['call_key'],
800
+ seen: 'response',
801
+ sourceScoped: true,
802
+ key: (row) => JSON.stringify([row.call_key]),
803
+ // Identity is `claude:<session>:<agent>:<message.id>`, so the vendor id is everything after
804
+ // the third colon. A key of any other shape carries no derivable id, so it is swept.
805
+ vendor: (row) => {
806
+ const match = CLAUDE_CALL_KEY_RE.exec(textOrEmpty(row.call_key));
807
+ return match ? match[1] : null;
808
+ },
809
+ },
810
+ {
811
+ kind: 'prompt',
812
+ table: 'usage_prompts',
813
+ columns: ['session_id', 'prompt_id'],
814
+ seen: 'prompt',
815
+ sourceScoped: true,
816
+ key: (row) => JSON.stringify([row.session_id, row.prompt_id]),
817
+ vendor: (row) => textOrNull(row.prompt_id),
818
+ },
819
+ {
820
+ kind: 'alias',
821
+ table: 'usage_prompt_aliases',
822
+ columns: ['session_id', 'prompt_id', 'provider_message_digest', 'source_file_id'],
823
+ seen: 'prompt',
824
+ // NOT source-scoped: `source_file_id` is part of this fact's own key.
825
+ key: (row, fileId) => JSON.stringify([
826
+ row.session_id,
827
+ row.prompt_id,
828
+ textOrEmpty(row.provider_message_digest),
829
+ row.source_file_id == null ? fileId : row.source_file_id,
830
+ ]),
831
+ vendor: (row) => textOrNull(row.prompt_id),
832
+ },
833
+ {
834
+ kind: 'spawn',
835
+ table: 'usage_spawns',
836
+ columns: ['session_id', 'tool_use_id'],
837
+ seen: 'tool_use',
838
+ sourceScoped: true,
839
+ key: (row) => JSON.stringify([row.session_id, row.tool_use_id]),
840
+ vendor: (row) => textOrNull(row.tool_use_id),
841
+ },
842
+ {
843
+ kind: 'delegation',
844
+ table: 'usage_delegations',
845
+ columns: ['session_id', 'call_id'],
846
+ seen: 'tool_use',
847
+ sourceScoped: true,
848
+ key: (row) => JSON.stringify([row.session_id, row.call_id]),
849
+ vendor: (row) => textOrNull(row.call_id),
850
+ },
851
+ {
852
+ kind: 'attempt',
853
+ table: 'usage_delegation_attempts',
854
+ columns: ['session_id', 'call_id'],
855
+ seen: 'tool_use',
856
+ sourceScoped: true,
857
+ key: (row) => JSON.stringify([row.session_id, row.call_id]),
858
+ vendor: (row) => textOrNull(row.call_id),
859
+ },
860
+ ];
861
+ const WHERE_BY_COLUMNS = (columns) => columns.map((column) => `${column} = @${column}`).join(' AND ');
862
+ for (const spec of REREAD_KINDS) {
863
+ spec.selectFileStmt = db.prepare(
864
+ `SELECT ${spec.columns.join(', ')} FROM ${spec.table} WHERE source_file_id = ?`
865
+ );
866
+ // The scope clause is the anti-clobber guard: this file may only delete a fact it still
867
+ // OWNS. Another source may have re-written the row since the snapshot was taken (WP-3).
868
+ const scope = spec.sourceScoped ? ' AND source_file_id = @source_file_id' : '';
869
+ spec.deleteByKeyStmt = db.prepare(
870
+ `DELETE FROM ${spec.table} WHERE ${WHERE_BY_COLUMNS(spec.columns)}${scope}`
871
+ );
872
+ }
873
+ const REREAD_KIND_BY_NAME = new Map(REREAD_KINDS.map((spec) => [spec.kind, spec]));
874
+ // The bind object for one snapshot key plus the source scope: the primary-key values, back
875
+ // under their column names, and `source_file_id` for a source-scoped kind.
876
+ const rereadKeyParams = (spec, values, fileId) => ({
877
+ ...Object.fromEntries(spec.columns.map((column, index) => [column, values[index]])),
878
+ ...(spec.sourceScoped ? { source_file_id: fileId } : {}),
879
+ });
880
+
702
881
  // --- reprice pass (WP-5) -----------------------------------------------------
703
882
  // Keyset, not OFFSET: every row is read EXACTLY once per pass, so a pass is one scan
704
883
  // however many rounds it takes (the caller re-enters with the previous lastKey).
@@ -829,6 +1008,10 @@ function openUsageLedger(dbPath, { Database = null } = {}) {
829
1008
  recompute = true,
830
1009
  reset = false,
831
1010
  price = null,
1011
+ // WP-3: the vendor ids this slice OBSERVED and whether this slice reached EOF. Both are
1012
+ // used only for a Claude file (D-10: Codex ignores them).
1013
+ observed = null,
1014
+ final = false,
832
1015
  } = payload;
833
1016
 
834
1017
  const now = Date.now();
@@ -877,11 +1060,16 @@ function openUsageLedger(dbPath, { Database = null } = {}) {
877
1060
  }).changes;
878
1061
  return { changes, session_id: stored.session_id };
879
1062
  };
880
- // `reset` (WP-8b): the caller is re-reading this file from byte 0 (a parser_version
881
- // or head-fingerprint change discarded its checkpoint), so the facts the file wrote
882
- // under the OLD reading must be removed before the new ones land. Read the PREVIOUS
883
- // identity before the fill-forward upsert mutates the row.
1063
+ // `reset` (WP-8b, Claude semantics replaced in WP-3): the caller is re-reading this file
1064
+ // from byte 0 (a parser_version or head-fingerprint change discarded its checkpoint), so the
1065
+ // facts the file wrote under the OLD reading must be reconciled before the new ones land.
1066
+ // For a CODEX file that is the eager source-scoped delete; for a CLAUDE file it is a
1067
+ // SNAPSHOT plus an EOF sweep, because the vendor may have removed rows the facts came from.
1068
+ // Read the PREVIOUS identity before the fill-forward upsert mutates the row.
884
1069
  const prevFileRow = reset ? (selectFileStmt.get(file.source_path) || null) : null;
1070
+ // The provider decides which reconciliation runs, so it is read from the BATCH (never from
1071
+ // the row the upsert is about to write, which the fill-forward keeps either way).
1072
+ const isClaude = textOrEmpty(file.provider) === 'claude';
885
1073
 
886
1074
  const fileRow = upsertFileStmt.get({
887
1075
  source_path: file.source_path,
@@ -895,7 +1083,23 @@ function openUsageLedger(dbPath, { Database = null } = {}) {
895
1083
 
896
1084
  const sessionSet = new Set();
897
1085
  let changedFacts = 0;
898
- if (prevFileRow) {
1086
+ if (prevFileRow && isClaude) {
1087
+ // WP-3 D-7: a Claude re-read deletes NOTHING up front. Every fact the file had written is
1088
+ // recorded as a key in `usage_reread_prior`, and the EOF sweep decides which of them the
1089
+ // new reading is allowed to remove. The observations start over with the re-read: ids seen
1090
+ // by the attempt this one replaced must not license a delete.
1091
+ clearRereadSeenStmt.run(prevFileRow.file_id);
1092
+ for (const spec of REREAD_KINDS) {
1093
+ for (const row of spec.selectFileStmt.all(prevFileRow.file_id)) {
1094
+ insertRereadPriorStmt.run({
1095
+ file_id: prevFileRow.file_id,
1096
+ kind: spec.kind,
1097
+ fact_key: spec.key(row, prevFileRow.file_id),
1098
+ vendor_id: spec.vendor(row),
1099
+ });
1100
+ }
1101
+ }
1102
+ } else if (prevFileRow) {
899
1103
  // Every call the file previously wrote is stale: collect the sessions it touched
900
1104
  // (their rollups must be re-derived) and delete the rows.
901
1105
  for (const r of selectFileCallSessionsStmt.all(prevFileRow.file_id)) {
@@ -918,11 +1122,16 @@ function openUsageLedger(dbPath, { Database = null } = {}) {
918
1122
  changedFacts += deleteFileSpawnsStmt.run(prevFileRow.file_id).changes;
919
1123
  changedFacts += deleteFileDelegationsStmt.run(prevFileRow.file_id).changes;
920
1124
  changedFacts += deleteFileAttemptsStmt.run(prevFileRow.file_id).changes;
1125
+ changedFacts += deleteFileAliasesStmt.run(prevFileRow.file_id).changes;
1126
+ }
1127
+ if (prevFileRow) {
1128
+ // The reported row and a stale agent row are replaced for BOTH providers: neither is a
1129
+ // fact the vendor's rows are the only evidence of (a report is a re-derivable summary of
1130
+ // calls the ledger keeps, and an agent row is derived metadata).
921
1131
  for (const r of selectFileCallSessionsForReportedStmt.all(prevFileRow.file_id)) {
922
1132
  if (r.session_id != null) sessionSet.add(r.session_id);
923
1133
  }
924
1134
  changedFacts += deleteFileReportedStmt.run(prevFileRow.file_id).changes;
925
- changedFacts += deleteFileAliasesStmt.run(prevFileRow.file_id).changes;
926
1135
  // A stale agent row (e.g. a v1 Codex thread id that is no longer the main agent)
927
1136
  // would show as a phantom subagent; drop it (and re-derive that session's rollup, which
928
1137
  // joined through the row). `session_id = NULL` matches nothing.
@@ -1006,6 +1215,128 @@ function openUsageLedger(dbPath, { Database = null } = {}) {
1006
1215
  }
1007
1216
  if (reported) changedFacts += upsertReportedStmt.run(reportedParams(reported, fileId)).changes;
1008
1217
 
1218
+ // --- WP-3 D-8: reconcile the snapshot with what this slice saw ------------------
1219
+ // EVERY SLICE, after the facts are written. A fact this slice RE-EMITTED is not missing, so
1220
+ // it leaves the snapshot; the ids this slice OBSERVED are recorded, and the EOF sweep below
1221
+ // deletes a prior fact only when its id is among them. Both halves are gated on a caller that
1222
+ // brought `observed` (the ingestor, for a Claude file), so a plain append -- and a Codex file
1223
+ // -- compiles no statement at all here.
1224
+ if (isClaude && observed) {
1225
+ // With no snapshot there is nothing to reconcile against, and nothing to record: a file
1226
+ // that never reset is simply being appended to.
1227
+ if (rereadPriorCountStmt.get(fileId).n > 0) {
1228
+ const emitted = new Map();
1229
+ const addKeys = (kind, keys) => {
1230
+ if (keys.length === 0) return;
1231
+ emitted.set(kind, [...(emitted.get(kind) || []), ...keys]);
1232
+ };
1233
+ addKeys('call', calls.map((call) => REREAD_KIND_BY_NAME.get('call').key(call)));
1234
+ addKeys('prompt', prompts.map((prompt) => REREAD_KIND_BY_NAME.get('prompt').key(prompt)));
1235
+ addKeys('alias', aliases
1236
+ .filter((alias) => !!textOrEmpty(alias.provider_message_digest))
1237
+ .map((alias) => REREAD_KIND_BY_NAME.get('alias').key(alias, fileId)));
1238
+ addKeys('spawn', spawns.map((spawn) => REREAD_KIND_BY_NAME.get('spawn').key(spawn)));
1239
+ addKeys('delegation', delegations.map((edge) => REREAD_KIND_BY_NAME.get('delegation').key(edge)));
1240
+ addKeys('attempt', attempts.map((attempt) => REREAD_KIND_BY_NAME.get('attempt').key(attempt)));
1241
+ for (const [kind, keys] of emitted) {
1242
+ deleteRereadPriorKeysStmt.run({ file_id: fileId, kind, keys: JSON.stringify(keys) });
1243
+ }
1244
+ const seen = [
1245
+ ['response', observed.responses],
1246
+ ['prompt', observed.prompts],
1247
+ ['tool_use', observed.toolUses],
1248
+ ];
1249
+ for (const [kind, ids] of seen) {
1250
+ for (const id of ids || []) {
1251
+ if (id == null) continue;
1252
+ insertRereadSeenStmt.run({ file_id: fileId, kind, vendor_id: id });
1253
+ }
1254
+ }
1255
+ }
1256
+ }
1257
+
1258
+ // --- WP-3 D-8: the EOF sweep ---------------------------------------------------
1259
+ // A prior fact that this pass never re-emitted is deleted IFF its vendor id was observed
1260
+ // (the vendor still has the row and this reading no longer produces the fact) OR the fact
1261
+ // carries no derivable vendor id (an older identity form, exactly the WP-8b behaviour). A
1262
+ // fact whose source row the vendor DELETED was never observed and is KEPT -- D-7.
1263
+ if (isClaude && final) {
1264
+ const priorRows = selectRereadPriorStmt.all(fileId);
1265
+ if (priorRows.length > 0) {
1266
+ const seen = new Set(selectRereadSeenStmt.all(fileId).map((row) => `${row.kind}\u0000${row.vendor_id}`));
1267
+ const doomed = new Map();
1268
+ for (const row of priorRows) {
1269
+ const spec = REREAD_KIND_BY_NAME.get(row.kind);
1270
+ if (!spec) continue;
1271
+ if (row.vendor_id != null && !seen.has(`${spec.seen}\u0000${row.vendor_id}`)) continue;
1272
+ const values = parseFactKey(row.fact_key);
1273
+ if (!values) continue;
1274
+ if (!doomed.has(spec.kind)) doomed.set(spec.kind, []);
1275
+ doomed.get(spec.kind).push(values);
1276
+ }
1277
+
1278
+ // Calls first, and their sessions collected BEFORE the rows go: the rollup joins through
1279
+ // the call row, so deleting one changes the rollup of the session it attributed. Both
1280
+ // statements are scoped to THIS file, so a call another source now owns is neither
1281
+ // collected nor deleted.
1282
+ const callValues = doomed.get('call') || [];
1283
+ if (callValues.length > 0) {
1284
+ const keys = JSON.stringify(callValues.map((values) => values[0]));
1285
+ for (const r of callSessionsByKeysStmt.all({ keys, source_file_id: fileId })) {
1286
+ if (r.session_id != null) sessionSet.add(r.session_id);
1287
+ }
1288
+ for (const values of callValues) {
1289
+ changedFacts += REREAD_KIND_BY_NAME.get('call')
1290
+ .deleteByKeyStmt.run({ call_key: values[0], source_file_id: fileId }).changes;
1291
+ }
1292
+ }
1293
+
1294
+ // A prompt that goes takes its redaction evidence with it, exactly as the whole-file
1295
+ // reset did: a legacy bare-NULL preview becomes a durable tombstone first (nothing can
1296
+ // prove it was not an older writer's redaction), and any existing tombstone keeps the
1297
+ // digest that identifies the same event under a new identity. The evidence read is
1298
+ // source-scoped, so it runs only for a prompt row this file still owns -- i.e. one the
1299
+ // DELETE below will actually remove.
1300
+ for (const values of doomed.get('prompt') || []) {
1301
+ const [sessionId, promptId] = values;
1302
+ const stored = promptEvidenceByKeyStmt.get(sessionId, promptId, fileId);
1303
+ if (stored) {
1304
+ if (stored.preview == null) {
1305
+ changedFacts += preserveLegacyRedactionStmt.run({
1306
+ session_id: sessionId, prompt_id: promptId, source_file_id: fileId,
1307
+ }).changes;
1308
+ }
1309
+ changedFacts += capturePurgeEvidence(sessionId, promptId, stored);
1310
+ }
1311
+ changedFacts += REREAD_KIND_BY_NAME.get('prompt')
1312
+ .deleteByKeyStmt.run({ session_id: sessionId, prompt_id: promptId, source_file_id: fileId }).changes;
1313
+ }
1314
+ for (const values of doomed.get('alias') || []) {
1315
+ const [sessionId, promptId, digest] = values;
1316
+ changedFacts += capturePurgeEvidence(sessionId, promptId, { provider_message_digest: digest });
1317
+ changedFacts += REREAD_KIND_BY_NAME.get('alias')
1318
+ .deleteByKeyStmt.run(rereadKeyParams(REREAD_KIND_BY_NAME.get('alias'), values, fileId)).changes;
1319
+ }
1320
+
1321
+ // A spawn's session rides its own fact key, so the rollup re-derivation needs no lookup
1322
+ // (re-deriving a session that did not move is harmless, unlike skipping one that did).
1323
+ for (const values of doomed.get('spawn') || []) {
1324
+ if (values[0] != null) sessionSet.add(values[0]);
1325
+ changedFacts += REREAD_KIND_BY_NAME.get('spawn')
1326
+ .deleteByKeyStmt.run(rereadKeyParams(REREAD_KIND_BY_NAME.get('spawn'), values, fileId)).changes;
1327
+ }
1328
+ for (const kind of ['delegation', 'attempt']) {
1329
+ const spec = REREAD_KIND_BY_NAME.get(kind);
1330
+ for (const values of doomed.get(kind) || []) {
1331
+ changedFacts += spec.deleteByKeyStmt.run(rereadKeyParams(spec, values, fileId)).changes;
1332
+ }
1333
+ }
1334
+ }
1335
+ // The pass is over: no re-read state outlives the transaction that writes its checkpoint.
1336
+ clearRereadPriorStmt.run(fileId);
1337
+ clearRereadSeenStmt.run(fileId);
1338
+ }
1339
+
1009
1340
  if (checkpoint) updateCheckpointStmt.run(checkpointParams(file.source_path, checkpoint));
1010
1341
 
1011
1342
  const sessions = [...sessionSet];
@@ -101,6 +101,17 @@ function buildTaskSnapshot(coreDb, ctmSessionId, scope, ledger, { legacyDb = nul
101
101
  // member that was never read at all.)
102
102
  const nativePoisoned = new Set();
103
103
  let bytes = turns.reduce((n, row) => n + Buffer.byteLength(row.text || ''), 0);
104
+ // D-3 (WP-1): a prompt written before `usage_prompts.source_file_id` existed carries NO digest and
105
+ // NO alias -- there is nothing to join on. Its `ts` is the transcript row's own `timestamp`, the
106
+ // SAME value the canonical turn recorded as `started_at_ms`, so an EXACT-equality index is a proof
107
+ // (never a window): the legacy prompt and the canonical human turn are the same transcript row.
108
+ const canonicalHumanByTs = new Map();
109
+ for (const turn of turns) {
110
+ if (turn.position_key == null || turn.kind !== 'human') continue;
111
+ const at = Number(turn.ts);
112
+ if (!canonicalHumanByTs.has(at)) canonicalHumanByTs.set(at, []);
113
+ canonicalHumanByTs.get(at).push(turn);
114
+ }
104
115
  for (const prompt of ledger.prompts || []) {
105
116
  if (!self.has(prompt.session_id) || !['human', 'user_prompt', 'local_command', 'compact_boundary', 'compaction', 'command', null, undefined].includes(prompt.kind)) continue;
106
117
  if (prompt.kind === 'command' && fallbackKind(prompt) === 'human') continue;
@@ -119,6 +130,16 @@ function buildTaskSnapshot(coreDb, ctmSessionId, scope, ledger, { legacyDb = nul
119
130
  if (incomplete || targets.size === 0) nativePoisoned.add(nativeKey);
120
131
  }
121
132
  const ref = { session_id: prompt.session_id, prompt_id: prompt.prompt_id };
133
+ // D-3 (WP-1): a SOURCE-LESS prompt (no `source_file_id`) with no digest, no event digest and no
134
+ // aliases has no identity evidence at all, so an EXACT-equal timestamp against a UNIQUE canonical
135
+ // human turn is the only proof available: same transcript row, therefore same turn. Every other
136
+ // shape is left untouched -- a prompt WITH a source file is never matched by time.
137
+ if (targets.size === 0 && !incomplete && prompt.source_less === true
138
+ && !prompt.provider_message_digest && !prompt.source_event_digest
139
+ && (!prompt.aliases || prompt.aliases.length === 0)) {
140
+ const sameStart = canonicalHumanByTs.get(Number(prompt.ts));
141
+ if (sameStart && sameStart.length === 1) targets.add(sameStart[0]);
142
+ }
122
143
  if (targets.size === 1 && !incomplete) {
123
144
  const turn = [...targets][0];
124
145
  if (!turn.source_refs.some((r) => qualified(r.session_id, r.prompt_id) === qualified(ref.session_id, ref.prompt_id))) turn.source_refs.push(ref);
@@ -424,9 +445,22 @@ function readAgentGraph(db, ids) {
424
445
  const params = { ids: JSON.stringify(ids) };
425
446
  if (!columns(db, 'usage_agents').size || !columns(db, 'usage_spawns').has('source_file_id')) return { agents: [], spawns: [], truncated: false };
426
447
  const agents = db.prepare(`SELECT session_id,agent_id,parent_tool_use_id FROM usage_agents WHERE session_id IN (SELECT value FROM json_each(@ids)) ORDER BY session_id,agent_id LIMIT ${GRAPH_LIMIT + 1}`).all(params);
427
- const spawns = db.prepare(`SELECT s.session_id,s.tool_use_id,s.prompt_id,f.agent_id AS parent_agent_id
428
- FROM usage_spawns s JOIN usage_files f ON f.file_id=s.source_file_id AND f.session_id=s.session_id
429
- WHERE s.session_id IN (SELECT value FROM json_each(@ids)) ORDER BY s.session_id,s.tool_use_id,s.source_file_id LIMIT ${GRAPH_LIMIT + 1}`).all(params);
448
+ // D-1 (WP-1): a spawn row written BEFORE the `source_file_id` column existed still names the
449
+ // prompt it ran under. The join is therefore a LEFT JOIN and the row survives on its prompt_id
450
+ // alone: the parser sets `prompt_id` only when the spawning file IS the main thread
451
+ // (claude-parse.js), so a sourceless row with a prompt_id is provably a MAIN-THREAD spawn, whose
452
+ // parent agent is the empty (main) agent id. A sourceless row with NO prompt_id proves nothing and
453
+ // is excluded here rather than resolved to an invented owner.
454
+ const spawns = db.prepare(`SELECT s.session_id, s.tool_use_id, s.prompt_id,
455
+ CASE WHEN f.file_id IS NOT NULL THEN f.agent_id
456
+ WHEN s.source_file_id IS NULL AND s.prompt_id IS NOT NULL THEN ''
457
+ ELSE NULL END AS parent_agent_id
458
+ FROM usage_spawns s
459
+ LEFT JOIN usage_files f ON f.file_id = s.source_file_id AND f.session_id = s.session_id
460
+ WHERE s.session_id IN (SELECT value FROM json_each(@ids))
461
+ AND (f.file_id IS NOT NULL OR (s.source_file_id IS NULL AND s.prompt_id IS NOT NULL))
462
+ ORDER BY s.session_id, s.tool_use_id, s.source_file_id
463
+ LIMIT ${GRAPH_LIMIT + 1}`).all(params);
430
464
  return { agents: agents.slice(0, GRAPH_LIMIT), spawns: spawns.slice(0, GRAPH_LIMIT), truncated: agents.length > GRAPH_LIMIT || spawns.length > GRAPH_LIMIT };
431
465
  }
432
466
 
@@ -443,6 +477,9 @@ const DELEGATION_EDGE_LIMIT = 20000;
443
477
  // the worker record it created. The legacy recovery window (R-6b) is exactly this, so a launch can
444
478
  // never be matched to a call older than the deadline that could have produced it.
445
479
  const MAX_PROXY_TIMEOUT_MS = 900000;
480
+ // The worker-launch fallback window: the same physical bound as R-6 -- a worker record can never
481
+ // be newer than the launch call that created it by more than the proxy deadline.
482
+ const WORKER_LAUNCH_WINDOW_MS = MAX_PROXY_TIMEOUT_MS;
446
483
  function readDelegationEdges(db, workerIds, sessionIds) {
447
484
  if (!columns(db, 'usage_delegations').size) return { edges: [], truncated: false };
448
485
  // The edge's agent is the agent its file's CALLS are stored under. A Codex root rollout's file
@@ -652,6 +689,74 @@ function projectTasks(ledgerDb, snapshot, scope, ledger, { grouping = 'heuristic
652
689
  if (ts !== null && ts >= from && ts <= launch.at_ms) addEdgeTarget(key, row);
653
690
  }
654
691
  }
692
+ // WP-2: the LAST resort for a worker generation the lead's transcript can no longer prove. When the
693
+ // vendor REWROTE the lead file, neither the accepted edge (WP-0) nor the launch attempt (R-6)
694
+ // survives, and the calls fell into `delegation_edge_missing` even though CTM's OWN records say
695
+ // when the worker was launched (its `created_at` / continuation receipts) and which lead turns
696
+ // exist (the canonical turn index). The fallback charges the generation to the canonical turn that
697
+ // was ACTIVE at the launch instant -- the greatest turn start not after it -- and only when no
698
+ // OTHER canonical turn could have held the launch call: a turn starting inside the proxy deadline
699
+ // window, or two launches of the same generation naming different turns, is AMBIGUOUS, not a
700
+ // guess. It never touches a key the edges or R-6 already filled, and it never runs for a NESTED
701
+ // worker (whose launch was made by another worker, not by a lead turn) or on a canonical window
702
+ // that is unavailable or capped -- a partial turn index cannot prove which turn was active.
703
+ const fallbackUsable = snapshot.canonical_available === true && snapshot.canonical_window_truncated !== true;
704
+ // (worker, generation) keys whose launch-time evidence named more than one possible turn. Kept
705
+ // beside `edgeTargets` rather than inside it: no target was proven, so no bucket may exist.
706
+ const launchAmbiguous = new Set();
707
+ if (fallbackUsable) {
708
+ const canonicalStarts = resolved
709
+ .filter((turn) => turn.position_key != null && Number.isFinite(Number(turn.ts)))
710
+ .map((turn) => ({ turn_key: turn.turn_key, ts: Number(turn.ts) }))
711
+ .sort((a, b) => a.ts - b.ts);
712
+ const scopedWorkers = canonicalStarts.length > 0 ? scope.workers || [] : [];
713
+ for (const worker of scopedWorkers) {
714
+ if (String(worker.parent_ctm_session_id || '') !== String(snapshot.ctm_session_id)) continue;
715
+ const byGeneration = new Map();
716
+ for (const launch of worker.launches || []) {
717
+ const generation = Number(launch.generation);
718
+ if (!Number.isInteger(generation) || generation < 1) continue;
719
+ if (!byGeneration.has(generation)) byGeneration.set(generation, []);
720
+ byGeneration.get(generation).push(launch);
721
+ }
722
+ for (const [generation, launches] of byGeneration) {
723
+ const key = `${worker.ctm_session_id}\u0000${generation}`;
724
+ const existing = edgeTargets.get(key);
725
+ if (existing && existing.size > 0) continue;
726
+ const ends = worker.generation_ends_ms;
727
+ let pick = null;
728
+ let ambiguous = false;
729
+ let unresolved = false;
730
+ for (const launch of launches) {
731
+ // No instant on the launch record: nothing to compare against the turn index.
732
+ const at = finiteOrNull(launch.at_ms);
733
+ if (at === null) { unresolved = true; break; }
734
+ // The turn active at the launch: the greatest canonical start not after it. A launch
735
+ // before EVERY canonical turn has no active turn, so it stays a named gap.
736
+ let start = null;
737
+ for (const candidate of canonicalStarts) {
738
+ if (candidate.ts > at) break;
739
+ start = candidate;
740
+ }
741
+ if (start === null) { unresolved = true; break; }
742
+ // A RESUME is only possible after the previous generation ended: the window opens at the
743
+ // later of the proxy deadline and that boundary.
744
+ let lo = at - WORKER_LAUNCH_WINDOW_MS;
745
+ if (generation >= 2 && Array.isArray(ends) && Number.isFinite(ends[generation - 2])) {
746
+ lo = Math.max(lo, ends[generation - 2]);
747
+ }
748
+ // A turn STARTED inside the window, so the launch call could equally have run under it.
749
+ if (start.ts > lo) { ambiguous = true; continue; }
750
+ if (pick && pick.turn_key !== start.turn_key) { ambiguous = true; continue; }
751
+ pick = start;
752
+ }
753
+ if (unresolved) continue;
754
+ if (ambiguous) { launchAmbiguous.add(key); continue; }
755
+ if (!pick) continue;
756
+ edgeTargets.set(key, new Map([['fallback', { session_id: null, agent_id: '', prompt_id: null, turn_key: pick.turn_key, ts: pick.ts }]]));
757
+ }
758
+ }
759
+ }
655
760
  // D-3: the generation a worker row falls in, from the worker's OWN recorded boundaries. A row
656
761
  // past every recorded end belongs to the RUNNING generation (`worker.generation`); a row that
657
762
  // starts in one generation and ends in another is split, never silently charged to one of them.
@@ -683,10 +788,19 @@ function projectTasks(ledgerDb, snapshot, scope, ledger, { grouping = 'heuristic
683
788
  if (ctm == null || String(ctm) === '') return { reason: 'delegation_edge_missing' };
684
789
  const resolvedGeneration = generationOf(workerById.get(String(ctm)), row.first_ts, row.last_ts);
685
790
  if (resolvedGeneration.reason) return { reason: resolvedGeneration.reason };
686
- const bucket = edgeTargets.get(`${ctm}\u0000${resolvedGeneration.generation}`);
687
- if (!bucket || bucket.size === 0) return { reason: 'delegation_edge_missing' };
791
+ const edgeKey = `${ctm}\u0000${resolvedGeneration.generation}`;
792
+ const bucket = edgeTargets.get(edgeKey);
793
+ if (!bucket || bucket.size === 0) {
794
+ // WP-2: the launch-time evidence named MORE than one possible turn, so the gap is
795
+ // ambiguity -- a different fact from "no launch record at all".
796
+ if (launchAmbiguous.has(edgeKey)) return { reason: 'delegation_edge_ambiguous' };
797
+ return { reason: 'delegation_edge_missing' };
798
+ }
688
799
  if (bucket.size > 1) return { reason: 'delegation_edge_ambiguous' };
689
800
  const target = [...bucket.values()][0];
801
+ // A WP-2 fallback target names a canonical turn directly (it never came from a transcript, so it
802
+ // has no session/agent/prompt); the caller labels a non-prompt turn `non_prompt_turn`.
803
+ if (target.turn_key) return { turn: target.turn_key, prompt: null };
690
804
  const role = roots.get(target.session_id);
691
805
  if (role === 'delegated') {
692
806
  // A nested launch carries no timestamp of its own; the launching edge's own time is the
@@ -744,6 +858,10 @@ function projectTasks(ledgerDb, snapshot, scope, ledger, { grouping = 'heuristic
744
858
  }
745
859
  const edges = spawns.get(qualified(row.session_id, matches[0].parent_tool_use_id)) || [];
746
860
  const unique = new Map(edges.map((e) => [qualified(e.parent_agent_id, e.prompt_id), e]));
861
+ // D-2: NO spawn row and SEVERAL spawn rows are different facts. `size === 0` proves the
862
+ // parent link was never recorded; only `size > 1` is a genuine ambiguity. Folding them
863
+ // together misnamed every missing row as a conflict.
864
+ if (unique.size === 0) return { reason: 'native_spawn_missing' };
747
865
  if (unique.size !== 1) return { reason: 'native_spawn_ambiguous' };
748
866
  const edge = [...unique.values()][0];
749
867
  prompt = edge.prompt_id;
@@ -8,16 +8,13 @@ const DEFAULT_DEADLINES_MS = Object.freeze({
8
8
  applied: 100,
9
9
  painted: 50,
10
10
  });
11
- const RECOVERY_ACTION = Object.freeze({
12
- 'frame-sent': 'targeted-keyframe',
13
- // Browser main-thread receipt can legitimately exceed this interaction latency
14
- // deadline. It is latency evidence, not proof of baseline corruption, so it
15
- // must neither fence the diff stream nor inject a replacement keyframe.
16
- 'socket-received': 'report-only',
17
- 'apply-started': 'supersede-obsolete',
18
- applied: 'supersede-obsolete',
19
- painted: 'report-only',
20
- });
11
+ // Every incident is report-only: a missed boundary deadline is latency evidence, never proof
12
+ // that a frame was lost. Under host load keydown->frame-sent runs 400-800 ms on a healthy
13
+ // session, and the replacement keyframe the 'frame-sent' miss used to force re-delivered a
14
+ // screen the client had already applied (B3: a duplicate full-grid write per late frame). Lost
15
+ // frames are repaired by the delivery path's own seq/baseline checks, not by a clock.
16
+ // (codex-running-composer-latency-round5-2026-08-09 §11 WP-6 rollback: "Return watchdog to
17
+ // report-only; preserve diagnostics".)
21
18
 
22
19
  function createVisibleRevisionWatchdog(options = {}) {
23
20
  const now = options.now || Date.now;
@@ -88,12 +85,11 @@ function createVisibleRevisionWatchdog(options = {}) {
88
85
  deadlineMs,
89
86
  previousBoundary,
90
87
  missingBoundary,
91
- recoveryAction: RECOVERY_ACTION[missingBoundary] || 'report-only',
92
88
  };
93
89
  row.reported = true;
94
90
  incidents.push(incident); report(incident);
95
91
  recover({ ...incident, scope: row.clientId ? 'client' : 'session' });
96
- if (incident.recoveryAction === 'report-only') pending.delete(id);
92
+ pending.delete(id);
97
93
  }
98
94
  return incidents;
99
95
  }