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
@@ -22,6 +22,11 @@ function closedReceipt(stream) {
22
22
  return stream.closed ? Promise.resolve() : new Promise((resolve) => stream.once('close', resolve));
23
23
  }
24
24
 
25
+ // A hit refreshes the entry's mtime (LRU order), but a shared 4 GiB cache does not need 5 s LRU
26
+ // granularity: touching at most once per minute per file drops mtime-only writes by ~12x while keeping
27
+ // the eviction order of multi-GB entries unchanged (fsevents-churn D-10b).
28
+ const TOUCH_MIN_INTERVAL_MS = 60_000;
29
+
25
30
  function createDecodedFrameCache({ withDecoder, cacheRoot, cacheMax, now, frameFile, pruneCache, codeError }) {
26
31
  const entries = new Map();
27
32
 
@@ -34,7 +39,12 @@ function createDecodedFrameCache({ withDecoder, cacheRoot, cacheMax, now, frameF
34
39
  }
35
40
 
36
41
  async function touch(handle, opts) {
37
- const when = new Date(now(opts));
42
+ const st = await handle.stat();
43
+ const at = now(opts);
44
+ // The inode's mtime IS the last-touch record: entries are dropped from the map when their last
45
+ // lease closes, so anything in memory would reset on every acquisition cycle (model-sync, 5 s).
46
+ if (at - st.mtimeMs < TOUCH_MIN_INTERVAL_MS) return; // fresh enough: a touch this minute already happened
47
+ const when = new Date(at);
38
48
  await handle.utimes(when, when);
39
49
  }
40
50
 
@@ -241,4 +251,4 @@ function createDecodedFrameCache({ withDecoder, cacheRoot, cacheMax, now, frameF
241
251
  return { acquireDecodedFrame };
242
252
  }
243
253
 
244
- module.exports = { createDecodedFrameCache };
254
+ module.exports = { createDecodedFrameCache, TOUCH_MIN_INTERVAL_MS };
@@ -24,12 +24,35 @@ const ARCHIVE_INTERVAL_MS = 60 * 60 * 1000;
24
24
  const SCRUB_INTERVAL_MS = 24 * 60 * 60 * 1000;
25
25
  const ARCHIVE_TIMEOUT_MS = 60 * 60 * 1000; // a multi-GB compress + verify outlasts the 10 minute default
26
26
 
27
+ // D-24/WP-6: the counters the `restoredOriginals` recovery result carries, flattened for the job result and
28
+ // its log line. A cycle that skipped the phase (or a guard that never reported one) counts as all zeros.
29
+ function _restoredCounts(restoredOriginals) {
30
+ const r = restoredOriginals || {};
31
+ const count = (v) => (Array.isArray(v) ? v.length : 0);
32
+ return {
33
+ units: Number(r.restoredUnits) || 0,
34
+ files: Number(r.restoredFiles) || 0,
35
+ linked: Number(r.linkedFromTrash) || 0,
36
+ decodedBytes: Number(r.decodedBytes) || 0,
37
+ deferredLowDisk: Number(r.deferredLowDisk) || 0,
38
+ targetExists: count(r.targetExists),
39
+ failed: count(r.failed),
40
+ };
41
+ }
42
+
27
43
  // Scheduler job `transcript-archive` (D-16..D-19, D-23): archive idle transcripts, purge the trash after the
28
44
  // grace period, scrub archives once a day. `env` supplies what only the host knows:
29
45
  // paths() -> { projectsRoot, codexSessionsDir, walleSessionsDir, trashRoot, dataDir? }
30
46
  // liveCtmSessionIds() -> ids of CTM sessions with a live PTY (a live session owns its transcript)
31
47
  // hostShedding() -> true while the load-pressure controller is shedding work
32
- function registerArchiveJob(scheduler, guard, env, shouldRun) {
48
+ // now() -> (optional, test seam) ms clock; defaults to Date.now
49
+ // log(line) -> (optional, test seam) one-line sink; defaults to console.log
50
+ // WP-6 observability: a skipped cycle and an empty cycle used to look identical in the logs. Every primary run
51
+ // now logs exactly one `ran` line, and a run that the `shouldRun` guard held back logs a `deferred` line at most
52
+ // once per ARCHIVE_INTERVAL_MS -- with how long it has been waiting and why. A successful run resets the
53
+ // deferral state. `deferReason` (5th argument, optional) is a function returning a short reason string ('' when
54
+ // none); without it the reason is 'guard'.
55
+ function registerArchiveJob(scheduler, guard, env, shouldRun, deferReason) {
33
56
  let first = true;
34
57
  let claudeAfter = '';
35
58
  let codexAfter = '';
@@ -37,6 +60,26 @@ function registerArchiveJob(scheduler, guard, env, shouldRun) {
37
60
  let claudeDigestAfter = '';
38
61
  let digestAfter = '';
39
62
  let lastScrubAt = 0;
63
+ const nowMs = () => (typeof env.now === 'function' ? Number(env.now()) : Date.now());
64
+ const log = typeof env.log === 'function' ? env.log : console.log;
65
+ // Deferral state (WP-6): startedAt = when the guard first held a cycle back, checks/reasons = what it saw,
66
+ // loggedAt = the last `deferred` line, so a long deferral is one line per interval, not one per tick.
67
+ const defer = { startedAt: 0, checks: 0, reasons: new Map(), loggedAt: 0 };
68
+ const resetDefer = () => { defer.startedAt = 0; defer.checks = 0; defer.reasons.clear(); defer.loggedAt = 0; };
69
+ const guardedShouldRun = typeof shouldRun === 'function' ? (...args) => {
70
+ if (shouldRun(...args)) return true;
71
+ const now = nowMs();
72
+ if (!defer.startedAt) defer.startedAt = now;
73
+ defer.checks += 1;
74
+ const reason = String((typeof deferReason === 'function' ? deferReason() : '') || 'guard');
75
+ defer.reasons.set(reason, (defer.reasons.get(reason) || 0) + 1);
76
+ if (!defer.loggedAt || now - defer.loggedAt >= ARCHIVE_INTERVAL_MS) {
77
+ defer.loggedAt = now;
78
+ const reasons = Array.from(defer.reasons, ([name, count]) => `${name}:${count}`).join(',');
79
+ log(`[transcript-archive] deferred ${Math.floor((now - defer.startedAt) / 60000)}m checks=${defer.checks} reasons=${reasons}`);
80
+ }
81
+ return false;
82
+ } : shouldRun;
40
83
  scheduler.register({
41
84
  name: 'transcript-archive',
42
85
  heavy: true,
@@ -44,10 +87,10 @@ function registerArchiveJob(scheduler, guard, env, shouldRun) {
44
87
  startDelayMs: 10 * 60 * 1000,
45
88
  priority: 9,
46
89
  pool: 'io',
47
- shouldRun,
90
+ shouldRun: guardedShouldRun,
48
91
  run: async () => {
49
92
  if (typeof guard.isPrimary !== 'function' || !guard.isPrimary()) return { skipped: { not_primary: 1 } };
50
- const now = Date.now();
93
+ const now = nowMs();
51
94
  const scrub = now - lastScrubAt >= SCRUB_INTERVAL_MS;
52
95
  const result = await guard.run('archive', {
53
96
  isPrimary: true,
@@ -77,9 +120,17 @@ function registerArchiveJob(scheduler, guard, env, shouldRun) {
77
120
  digestAfter = digest.nextCodexDigestAfter || '';
78
121
  }
79
122
  const { archived, bytesIn, bytesOut, trashed, purged, verified, corrupt, skipped } = result;
123
+ const restored = _restoredCounts(result.restoredOriginals);
124
+ const errors = (result.errors || []).length + (digest ? digest.errors.length : 0);
125
+ const waitedMs = defer.startedAt ? Math.max(0, nowMs() - defer.startedAt) : 0;
126
+ log(`[transcript-archive] ran archived=${Number(archived) || 0} restoredUnits=${restored.units} `
127
+ + `restoredFiles=${restored.files} linked=${restored.linked} decodedBytes=${restored.decodedBytes} `
128
+ + `deferredLowDisk=${restored.deferredLowDisk} targetExists=${restored.targetExists} `
129
+ + `restoreFailed=${restored.failed} errors=${errors} waitedMs=${waitedMs}`);
130
+ resetDefer();
80
131
  return {
81
132
  archived, bytesIn, bytesOut, trashed, purged, verified, corrupt, skipped, digested: digest ? digest.digested : 0,
82
- errors: (result.errors || []).length + (digest ? digest.errors.length : 0),
133
+ errors, restored,
83
134
  };
84
135
  },
85
136
  });
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+
3
+ // WP-2 (worker-transcript-link-derived-not-discovered-2026-10-25.html §5 D-4/D-7, §6, §8):
4
+ // the resume preflight predicts a Claude worker's transcript path from (cwd, agent_session_id)
5
+ // when agent_sessions.jsonl_path is empty, and must VERIFY the prediction before it links it --
6
+ // a predicted path is not evidence. This module is the one place that verification happens.
7
+ //
8
+ // It is the only module besides lib/transcript-source.js that names a transcript path, and it
9
+ // touches that path ONLY through transcript-source functions: `require('fs')` here would be a
10
+ // new site for bin/transcript-open-lint.js (the read has to stay on the one sanctioned reader,
11
+ // so archive/trash states, sibling `.zst` archives and the manifest all resolve identically).
12
+
13
+ const transcriptSource = require('./transcript-source');
14
+
15
+ // 64 KiB: enough to reach the session id in Claude Code's first record while never pulling a
16
+ // multi-megabyte transcript into memory. It is NOT a bound on the first LINE: a measured 34 of
17
+ // the 300 most recent real transcripts start with a single `queue-operation` record of 66 KB to
18
+ // 387 KB, so the head routinely ends mid-record. Only the `sessionId` key is read out of it
19
+ // (a bounded scan, never a JSON.parse of the whole record, which such a line would fail).
20
+ const HEAD_BYTES = 65536;
21
+
22
+ // Claude Code writes `"sessionId"` before any user content in the first record. Measured offset in
23
+ // real transcripts: byte 87. The value is a session uuid, so no escapes are possible.
24
+ const SESSION_ID_RE = /"sessionId"\s*:\s*"([^"]*)"/;
25
+
26
+ /**
27
+ * Verify that `derivedPath` really is the transcript of `agentSessionId`.
28
+ *
29
+ * States (the caller reports them verbatim in a refusal, so the lead can tell "the file is not
30
+ * where Claude writes it" from "the file is there but belongs to someone else"):
31
+ * plain -- a local file whose first JSON line names this session (ok: true)
32
+ * missing -- nothing at the path (or the resolver itself threw)
33
+ * archived -- resolved to an archived/trashed source; resume restores those by path, so a
34
+ * predicted path that is not plain is never linked
35
+ * unreadable -- the head could not be read, or its first line names no sessionId at all
36
+ * (empty file, non-JSON bytes, a record without the key)
37
+ * id-mismatch -- readable, but its sessionId is a different session
38
+ *
39
+ * @returns {Promise<{ok: boolean, state: string, headSessionId: string|null}>}
40
+ */
41
+ async function verifyClaudeTranscriptAt(derivedPath, agentSessionId, {
42
+ resolveTranscriptSource = transcriptSource.resolveTranscriptSource,
43
+ readTranscriptHead = transcriptSource.readTranscriptHead,
44
+ } = {}) {
45
+ let source = null;
46
+ try {
47
+ source = await resolveTranscriptSource(derivedPath);
48
+ } catch {
49
+ // expected: an unreadable/absent path is exactly the "no transcript there" answer
50
+ return { ok: false, state: 'missing', headSessionId: null };
51
+ }
52
+ if (!source || source.state === 'missing') return { ok: false, state: 'missing', headSessionId: null };
53
+ // Anything not plain (archived, trashed, ...) is reported as archived: the resolve path below
54
+ // restores an archived transcript at the LINKED path, and a derived path has no link yet.
55
+ if (source.state !== 'plain') return { ok: false, state: 'archived', headSessionId: null };
56
+
57
+ let head = '';
58
+ try {
59
+ head = String(await readTranscriptHead(derivedPath, HEAD_BYTES));
60
+ } catch {
61
+ // expected: an unreadable/absent file -- unverifiable, never a link
62
+ return { ok: false, state: 'unreadable', headSessionId: null };
63
+ }
64
+
65
+ // Scan the first line only (or the whole head when it is one unterminated line, i.e. the record
66
+ // is longer than HEAD_BYTES). Never parse the record: a first line over 64 KiB is normal and
67
+ // would throw. The key precedes user content, so the first match is the record's own session id.
68
+ const firstLine = head.split('\n')[0];
69
+ const match = SESSION_ID_RE.exec(firstLine);
70
+ if (!match) return { ok: false, state: 'unreadable', headSessionId: null };
71
+
72
+ const headSessionId = match[1];
73
+ if (headSessionId !== String(agentSessionId || '')) return { ok: false, state: 'id-mismatch', headSessionId };
74
+ return { ok: true, state: 'plain', headSessionId };
75
+ }
76
+
77
+ module.exports = { verifyClaudeTranscriptAt };
@@ -42,14 +42,18 @@ function _row(input) {
42
42
  const ALL_STATES = Object.freeze([...ARCHIVE_STATES, ...LEGACY_STATES]);
43
43
 
44
44
  // WP-9. Rows in any of `states`, optionally changed before `olderThanMs` (state_changed_at_ms) or with a source
45
- // mtime before `mtimeBeforeMs`. orderBy: 'mtime' (oldest source first) | 'verified' (least recently verified
46
- // first) | 'changed' (oldest state change first, the default). Bounded to 2000 rows.
45
+ // mtime before `mtimeBeforeMs`. orderBy: 'mtime' (oldest source first) | 'mtime_desc' (newest source first, the
46
+ // WP-2 recovery order) | 'verified' (least recently verified first) | 'changed' (oldest state change first, the
47
+ // default). Bounded to 2000 rows.
47
48
  function listByState(db, { states = [], olderThanMs = 0, mtimeBeforeMs = 0, orderBy = 'changed', limit = 500 } = {}) {
48
49
  const wanted = (states || []).map(String).filter((s) => ALL_STATES.includes(s));
49
50
  if (!wanted.length) return [];
50
51
  const bounded = Math.max(1, Math.min(2000, Number(limit) || 500));
51
- const order = { mtime: 'source_mtime_ms, source_path', verified: 'last_verified_at_ms, source_path' }[orderBy]
52
- || 'state_changed_at_ms, source_path';
52
+ const order = {
53
+ mtime: 'source_mtime_ms, source_path',
54
+ mtime_desc: 'source_mtime_ms DESC, source_path',
55
+ verified: 'last_verified_at_ms, source_path',
56
+ }[orderBy] || 'state_changed_at_ms, source_path';
53
57
  const marks = wanted.map(() => '?').join(',');
54
58
  const where = [`state IN (${marks})`];
55
59
  const params = [...wanted];
@@ -22,7 +22,7 @@ const { finished } = require('node:stream/promises');
22
22
  const zlib = require('node:zlib');
23
23
  const { decodeFrame, waitForDecodedFrameClose, decompressParams } = require('./transcript-archive-codec');
24
24
  const { getDataDir } = require('./data-dir');
25
- const { createDecodedFrameCache } = require('./transcript-frame-cache');
25
+ const { createDecodedFrameCache, TOUCH_MIN_INTERVAL_MS } = require('./transcript-frame-cache');
26
26
 
27
27
  const CACHE_DIR_NAME = 'transcript-archive-cache';
28
28
  const DEFAULT_CACHE_MAX_BYTES = 4 * 1024 * 1024 * 1024;
@@ -1109,8 +1109,13 @@ function _decodeFrameSync(archive, index, opts, learnLength) {
1109
1109
  // The cached bytes are this request's decoded bytes: charge them before returning, so a warm
1110
1110
  // cache hit is accounted for exactly like a cold decode would be.
1111
1111
  if (ledger && learnLength) _chargeLearnedDecoded(ledger, `${archive.cacheKey}:${index}`, hit.size);
1112
- const when = new Date(_now(opts));
1113
- fs.utimesSync(file, when, when);
1112
+ // Same rule as the async cache's touch (D-10b): the hit's own mtime is the last-touch record, so a
1113
+ // file touched in the last minute is left alone instead of paying a utimes write per read.
1114
+ const at = _now(opts);
1115
+ if (at - hit.mtimeMs >= TOUCH_MIN_INTERVAL_MS) {
1116
+ const when = new Date(at);
1117
+ fs.utimesSync(file, when, when);
1118
+ }
1114
1119
  return file;
1115
1120
  }
1116
1121
  const max = _cacheMax(opts);
@@ -59,6 +59,11 @@ function setErrorSink(sink) {
59
59
  }
60
60
  const { shouldYieldAfterChunk } = require('./conversation-import/chunk-runner');
61
61
  const { createImportMetrics } = require('./conversation-import/import-metrics');
62
+ // WP-2 (D-4): the ingest lane already parses every record, so the agent's own session name
63
+ // (`ai-title` / `custom-title`) is captured here and applied at the ingestJsonlFile choke point.
64
+ const { extractTitleSignalFromRecord } = require('./session-title-signals');
65
+ const { getAgentCapabilities } = require('./agent-capabilities');
66
+ const { applyAgentTitleSignals } = require('./agent-title-apply');
62
67
 
63
68
  const PARSER_VERSION = 3;
64
69
  const DEFAULT_INITIAL_TAIL_BYTES = 2 * 1024 * 1024;
@@ -1008,6 +1013,16 @@ function claudeMessagesFromEntry(entry, state = {}) {
1008
1013
  if (queued) entry = queued;
1009
1014
  if (shouldDropInheritedClaudeEntry(entry, state)) return [];
1010
1015
 
1016
+ // WP-2 (D-4): a `custom-title`/`ai-title` record produces no rows, but CTM is already reading
1017
+ // every record here — keep the last non-inherited one of each kind so ingestJsonlFile can apply
1018
+ // it (lib/agent-title-apply.js). Last wins; an ai-title never clears a custom-title.
1019
+ const titleSignal = extractTitleSignalFromRecord(entry);
1020
+ if (titleSignal.userTitle || titleSignal.providerTitle) {
1021
+ if (!state.agentTitleSignals) state.agentTitleSignals = {};
1022
+ if (titleSignal.userTitle) state.agentTitleSignals.customTitle = titleSignal.userTitle;
1023
+ else state.agentTitleSignals.aiTitle = titleSignal.providerTitle;
1024
+ }
1025
+
1011
1026
  if (entry.type === 'system' && entry.subtype === 'compact_boundary'
1012
1027
  && structuredCapture.structuredCaptureEnabled()) {
1013
1028
  const compactMeta = entry.compactMetadata || {};
@@ -1458,6 +1473,8 @@ function healSelfLinkedTranscriptRows(db, agentSessionId, ownerCtmSessionId) {
1458
1473
  try {
1459
1474
  const f = db.prepare('UPDATE transcript_files SET ctm_session_id=? WHERE agent_session_id=? AND ctm_session_id=?').run(owner, agentId, agentId);
1460
1475
  const e = db.prepare('UPDATE transcript_events SET ctm_session_id=? WHERE agent_session_id=? AND ctm_session_id=?').run(owner, agentId, agentId);
1476
+ // Rows whose owner changed used to change silently. Ids only, truncated; never a path, never content.
1477
+ if (f.changes || e.changes) console.log(`[transcript-owner] re-keyed self-link agent=${agentId.slice(0, 8)} -> owner=${owner.slice(0, 8)} files=${f.changes || 0} events=${e.changes || 0}`);
1461
1478
  return { files: f.changes || 0, events: e.changes || 0 };
1462
1479
  } catch { return { files: 0, events: 0 }; }
1463
1480
  }
@@ -1688,6 +1705,10 @@ function parseTranscriptTail(options = {}) {
1688
1705
  return {
1689
1706
  ...base,
1690
1707
  messages: projected.messages,
1708
+ // WP-2 (D-4): the agent-name signal rides with the parsed window so both the inline ingest
1709
+ // and the read-pool → preParsed apply path (workers/read-pool-worker.js parseTranscriptDelta)
1710
+ // carry it without a second read.
1711
+ agentTitleSignals: state.agentTitleSignals || null,
1691
1712
  metrics,
1692
1713
  read: { nextOffset: read.nextOffset, partialLine: read.partialLine || '', bytesRead: read.bytesRead },
1693
1714
  };
@@ -2034,6 +2055,8 @@ function _ingestJsonlFileV2(db, options = {}) {
2034
2055
  agentSessionId,
2035
2056
  generation: 1,
2036
2057
  fileSize: stat.size,
2058
+ // WP-2 (D-4): the parsed window's agent-name signal (title records produce no rows).
2059
+ agentTitleSignals: (preParsed || parsed).agentTitleSignals || null,
2037
2060
  oversizedNonRetainedRecordsSkipped,
2038
2061
  metrics: {
2039
2062
  ...(parsed.metrics || {}),
@@ -2466,6 +2489,8 @@ function _ingestJsonlFile(db, options = {}) {
2466
2489
 
2467
2490
  let read;
2468
2491
  let messages;
2492
+ // WP-2 (D-4): the agent-name signal (title records produce no rows) from whichever parse ran.
2493
+ let agentTitleSignals = null;
2469
2494
  if (preParsed) {
2470
2495
  // Off-thread parse already produced the delta (parseTranscriptTail) — apply is writes only.
2471
2496
  read = {
@@ -2474,6 +2499,7 @@ function _ingestJsonlFile(db, options = {}) {
2474
2499
  bytesRead: Number(preParsed.read?.bytesRead || 0),
2475
2500
  };
2476
2501
  messages = Array.isArray(preParsed.messages) ? preParsed.messages : [];
2502
+ agentTitleSignals = preParsed.agentTitleSignals || null;
2477
2503
  } else {
2478
2504
  const maxBytes = Math.min(
2479
2505
  Math.max(0, readCeiling - cursor.offset),
@@ -2501,6 +2527,7 @@ function _ingestJsonlFile(db, options = {}) {
2501
2527
  });
2502
2528
  metrics.parseMs += Math.max(0, Date.now() - parseStartedAt);
2503
2529
  messages = projected.messages;
2530
+ agentTitleSignals = state.agentTitleSignals || null;
2504
2531
  metrics.recordsParsed += projected.recordsParsed;
2505
2532
  }
2506
2533
  metrics.sourceBytesRead += Number(read.bytesRead || 0);
@@ -3266,6 +3293,7 @@ function _ingestJsonlFile(db, options = {}) {
3266
3293
  values: insertChunkStatementCounts,
3267
3294
  },
3268
3295
  overBudget: elapsedMs > IMPORT_SLICE_HARD_MAX_MS,
3296
+ agentTitleSignals,
3269
3297
  messages: partial ? messages.slice(0, processedCount) : messages,
3270
3298
  rowCursorDelta: {
3271
3299
  ctmSessionId,
@@ -3367,6 +3395,18 @@ function ingestJsonlFile(db, options = {}) {
3367
3395
  result,
3368
3396
  coreDb: options.coreDb,
3369
3397
  });
3398
+ // WP-2 (D-4/D-5/D-9, S-17): the single choke point for every ingest (event delta, targeted
3399
+ // import, v13 migration) — apply the agent's own name to its CTM row. Gated on the provider
3400
+ // declaring it reads ai-titles; a refused delta applied nothing, so it has no signal to apply.
3401
+ // D-11: the caller publishes the outcome (titleApply) on the main thread.
3402
+ if (result && !result.refused && result.agentTitleSignals && ownerCtmSessionId
3403
+ && getAgentCapabilities(normalizeProvider(options.provider, filePath)).titleSpec?.readsAiTitle) {
3404
+ result.titleApply = applyAgentTitleSignals(options.coreDb || db, {
3405
+ ctmSessionId: ownerCtmSessionId,
3406
+ provider: normalizeProvider(options.provider, filePath),
3407
+ ...result.agentTitleSignals,
3408
+ });
3409
+ }
3370
3410
  return result;
3371
3411
  }
3372
3412
  function dryRunTranscriptByteBackfill(db, options = {}) {
@@ -65,10 +65,13 @@ function _socketBytesWritten(socket) {
65
65
  // The traffic class of a message, from the channel the caller already resolved (`opts.channel`
66
66
  // || the decoded `type`) plus the class `_wsSend` may have resolved explicitly (`opts.class` —
67
67
  // the live-status heartbeat sends `session-activity` as `{ class: 'stream', channel: 'heartbeat' }`).
68
- // `classifyWsPayload` stays the one authority for the class split.
68
+ // T2 (docs/walle-turn-lifecycle-delivery-2026-10-26.html §3): the classification is delegated with
69
+ // the ACTUAL payload plus the caller's class and channel — a synthetic `{ type: channel }` object
70
+ // threw away the lifecycle fields (recordType/partType) that make a walle-transcript-append opener
71
+ // a control notification rather than a replaceable sample, which in economy let progress replace a
72
+ // queued opener. `classifyWsPayload` stays the one authority for the class split.
69
73
  function _channelClass(channel, payload, cls) {
70
- const ch = String(channel || '');
71
- return classifyWsPayload(ch ? { type: ch } : payload, cls);
74
+ return classifyWsPayload(payload, cls, channel);
72
75
  }
73
76
 
74
77
  function createTunnelLaneGovernor({ now = Date.now, setIntervalFn = setInterval, clearIntervalFn = clearInterval, send } = {}) {
@@ -1,5 +1,12 @@
1
1
  'use strict';
2
2
 
3
+ // WP-3 (composer-freeze-followups-load-sigstop-attribution-2026-10-20.html §5 D-C1/D-C2):
4
+ // READ THE STAGE NAMES HONESTLY. Every stage after `pty-written` EXCEPT `echo-bytes`/`echo-cell`
5
+ // is stamped on the session's whole pending id set by the FIRST output/feed/frame after the key
6
+ // -- "first X after the key", which is approximate, not causal (D-C1; the pty-output leg lands
7
+ // 0-3 ms after pty-written in 14 of 18 retained slow rows, which no agent CLI can do).
8
+ // `echo-bytes`/`echo-cell` are the only per-key matches today, and they are shadow-only
9
+ // instrumentation for WP-4/WP-5's decision (D-C2) -- nothing reads them as truth yet.
3
10
  const STAGES = Object.freeze([
4
11
  'browser-keydown',
5
12
  'server-received',
@@ -10,7 +17,18 @@ const STAGES = Object.freeze([
10
17
  // echoed). Required (not in OPTIONAL_STAGES below), same as 'pty-written'.
11
18
  'host-posted',
12
19
  'pty-written',
20
+ // WP-4 (clock-free-tests-followups-r4-2026-10-24.html §5 R-9, §6.5): the host's OWN receipt
21
+ // of the PTY batch -- the instant its first byte entered the mirror-feed coalescer, carried on
22
+ // the 'feed' op as `hostReceivedAt`. `pty-output` below is the MAIN-thread arrival, which is a
23
+ // different clock hop; neither is causal for the other (every post-`pty-written` stage is
24
+ // "first X after the key"). Optional, like its neighbours.
25
+ 'host-pty-data',
13
26
  'pty-output',
27
+ // WP-3 D-C2: the two shadow, per-key echo matchers -- VS Code typeahead's byte rule and
28
+ // mosh's cell rule (see lib/typing-worker-stages.js, which runs them in the mirror). Both are
29
+ // OPTIONAL (OPTIONAL_STAGES below), so neither can move lastCompletedBoundary.
30
+ 'echo-bytes',
31
+ 'echo-cell',
14
32
  // WP-0 (composer-freeze-stop-and-wait-emitter-2026-09-16.html §5 D-1, §6 API specs, §8 WP-0):
15
33
  // four stages stamped INSIDE the worker/pool-host, between `pty-output` and `mirror-revision`
16
34
  // -- the 16.1 s gap the census found with zero instrumentation coverage (§1.5). All four are
@@ -22,6 +40,19 @@ const STAGES = Object.freeze([
22
40
  'mirror-written',
23
41
  'emit-offered',
24
42
  'emit-refused',
43
+ // WP-4 (clock-free-tests-followups-r3-2026-10-24.html §5 R-7, §6 WP-4): stamped by the worker
44
+ // (lib/typing-worker-stages.js's noteFramePostedForEchoed, called from emitSettled) for exactly
45
+ // the pending ids the frame that just SHIPPED already covers (their own inputSeq <= that
46
+ // frame's echoedThroughSeq, carried as `offeredEchoSeq`). Optional, like its neighbours above:
47
+ // a key whose frame is refused, or that is delivered without ever being pending, never gets it.
48
+ 'frame-posted',
49
+ // WP-4 (clock-free-tests-followups-r4-2026-10-24.html §5 R-7/R-8, §6.5): the FIRST main-thread
50
+ // touch of the frame that carries this key's echo (`session-host-manager.js`'s
51
+ // `_stampMainArrival`), split out from `mirror-revision` (where main stamped the interaction)
52
+ // to price the receiver-loop leg alone. Carries `mainSpans` (R-8) -- the top recent main-thread
53
+ // spans overlapping [frame-posted.at, frame-arrived.at]. Optional, same reason as
54
+ // `frame-posted`: a key whose frame was refused, or that never had a pending row, never gets it.
55
+ 'frame-arrived',
25
56
  'mirror-revision',
26
57
  'frame-sent',
27
58
  'socket-received',
@@ -52,7 +83,19 @@ const STAGE_INDEX = new Map(STAGES.map((stage, index) => [stage, index]));
52
83
  // D-1: stages that may be absent from an otherwise-complete row without that absence meaning
53
84
  // "the interaction stalled here" -- the sequential boundary walk in note() skips these rather
54
85
  // than treating a miss as the end of the row's progress (see the walk below).
55
- const OPTIONAL_STAGES = new Set(['pty-output', 'worker-received', 'mirror-written', 'emit-offered', 'emit-refused']);
86
+ // WP-3 D-C2: 'echo-bytes'/'echo-cell' join them -- they are shadow stamps most keys never get.
87
+ const OPTIONAL_STAGES = new Set([
88
+ 'pty-output', 'worker-received', 'mirror-written', 'emit-offered', 'emit-refused',
89
+ // WP-4 (R-7): 'frame-posted' joins them -- its absence must never move
90
+ // lastCompletedBoundary/missingBoundary for a row that shipped its frame before anything was
91
+ // pending (the common case).
92
+ 'frame-posted',
93
+ // WP-4 r4 (R-7/R-8/R-9): 'frame-arrived' and 'host-pty-data' join for the same reason -- both
94
+ // are first-touch stamps on paths most interactions never take (a refused frame; a host whose
95
+ // coalescer delivered before any keystroke was pending), so neither may end the boundary walk.
96
+ 'frame-arrived', 'host-pty-data',
97
+ 'echo-bytes', 'echo-cell',
98
+ ]);
56
99
  const WORKLOAD_FIELDS = Object.freeze([
57
100
  'rawBatchClass', 'synchronizedFrameCount', 'mirrorChangedRows', 'frameKind',
58
101
  'frameBytes', 'queuedBytes', 'queueDepth', 'codexRepairActions',
@@ -69,6 +112,27 @@ function boundedText(value, max = 80) {
69
112
  return String(value || '').replace(/[^A-Za-z0-9_.:-]/g, '').slice(0, max);
70
113
  }
71
114
 
115
+ // WP-4 r4 (R-8, §6.5): R-8's floor -- mainSpans are computed only for a frame-posted ->
116
+ // frame-arrived window at or above this, so the fast path allocates nothing.
117
+ const MAIN_SPANS_MIN_WINDOW_MS = 15;
118
+
119
+ // The `frame-arrived` record's mainSpans shape: a malformed entry is DROPPED rather than repaired
120
+ // into a plausible-looking 0 -- a reader may only ever see entries the sender actually produced.
121
+ // Name is bounded to 120 chars and overlapMs floored to a non-negative integer, and the record is
122
+ // the TOP 3 by overlapMs whatever order the sender used: the ranking is part of this record's
123
+ // contract (§6.5), not of the sender's good manners.
124
+ function sanitizeMainSpans(rawSpans) {
125
+ const mainSpans = [];
126
+ for (const entry of Array.isArray(rawSpans) ? rawSpans : []) {
127
+ if (!entry || typeof entry !== 'object') continue;
128
+ const name = String(entry.name == null ? '' : entry.name).slice(0, 120);
129
+ if (!name) continue;
130
+ mainSpans.push({ name, overlapMs: Math.max(0, Math.floor(finite(entry.overlapMs))) });
131
+ }
132
+ mainSpans.sort((a, b) => b.overlapMs - a.overlapMs);
133
+ return mainSpans.slice(0, 3);
134
+ }
135
+
72
136
  function percentile(values, pct) {
73
137
  if (!values.length) return 0;
74
138
  const sorted = values.slice().sort((a, b) => a - b);
@@ -99,6 +163,23 @@ function stageMsFromKeydown(row, stage) {
99
163
  return stageDelta(row, stage, 'browser-keydown');
100
164
  }
101
165
 
166
+ // WP-3 D-C3: an expired row must not report an OPTIONAL stage as "where it got to" -- the
167
+ // reverse scan it used to do picked `pty-output` over `pty-written` for a key that stalled
168
+ // after a stray output chunk (I-6 / R9). `lastObservedStage` is therefore the last observed
169
+ // REQUIRED stage (fallback `browser-keydown`), and the last observed OPTIONAL stage is reported
170
+ // separately as `lastOptionalStage` ('' when there is none) so the shadow echo stamps stay
171
+ // readable without ever being mistaken for progress.
172
+ function observedStageSplit(row) {
173
+ let lastObservedStage = 'browser-keydown';
174
+ let lastOptionalStage = '';
175
+ for (const stage of STAGES) {
176
+ if (!row.stages[stage]) continue;
177
+ if (OPTIONAL_STAGES.has(stage)) lastOptionalStage = stage;
178
+ else lastObservedStage = stage;
179
+ }
180
+ return { lastObservedStage, lastOptionalStage };
181
+ }
182
+
102
183
  // D-7/S-14: builds the flat, ≤24-key `typing-interaction-slow` payload from the SAME stage
103
184
  // vector `note()`/`begin()` already maintain -- no second clock, no parallel bookkeeping.
104
185
  // `lastStage` is passed explicitly by the two callers below rather than read from
@@ -118,6 +199,10 @@ function buildSlowInteractionRow(row, ktpMs, lastStage) {
118
199
  printable: row.printable === true,
119
200
  ktpMs: Math.max(0, finite(ktpMs, 0)),
120
201
  lastStage: lastStage || row.lastCompletedBoundary || 'browser-keydown',
202
+ // WP-3 D-C3: the last OPTIONAL stage observed for this row ('' when none) -- carried on
203
+ // every slow/worst vector so `lastStage` can stay required-only without losing the shadow
204
+ // echo stamps.
205
+ lastOptionalStage: observedStageSplit(row).lastOptionalStage,
121
206
  dropReason: dropped ? boundedText(dropped.reason, 64) : '',
122
207
  gate: refused ? refused.gate : '',
123
208
  gateTop: refused ? refused.gate : '',
@@ -131,7 +216,12 @@ function buildSlowInteractionRow(row, ktpMs, lastStage) {
131
216
  serverReceivedMs: stageMsFromKeydown(row, 'server-received'),
132
217
  ptyWrittenMs: stageMsFromKeydown(row, 'pty-written'),
133
218
  ptyOutputMs: stageMsFromKeydown(row, 'pty-output'),
219
+ // WP-3 D-C2: the shadow per-key matchers' legs, same keydown rule as ptyOutputMs above.
220
+ echoBytesMs: stageMsFromKeydown(row, 'echo-bytes'),
221
+ echoCellMs: stageMsFromKeydown(row, 'echo-cell'),
134
222
  mirrorRevisionMs: stageMsFromKeydown(row, 'mirror-revision'),
223
+ // WP-4 (R-7): the shipped-frame leg, same keydown rule as mirrorRevisionMs above.
224
+ framePostedMs: stageMsFromKeydown(row, 'frame-posted'),
135
225
  frameSentMs: stageMsFromKeydown(row, 'frame-sent'),
136
226
  socketReceivedMs: stageMsFromKeydown(row, 'socket-received'),
137
227
  applyStartedMs: stageMsFromKeydown(row, 'apply-started'),
@@ -181,6 +271,9 @@ function createTypingInteractionTraceStore(options = {}) {
181
271
  // emitting workerLoopMaxMs: 0 / host: {0,0,0} exactly as before this WP.
182
272
  const getRollupExtras = typeof options.getRollupExtras === 'function' ? options.getRollupExtras : null;
183
273
  const rollupIntervalMs = Math.max(1000, finite(options.rollupIntervalMs, 60_000));
274
+ // WP-4 r4 (R-8): `(startMs, endMs) => [{ name, overlapMs }]`, a pure read of the caller's
275
+ // main-thread span ring (server.js `_mainSpansForWindow`). Absent -> no spans are computed.
276
+ const mainSpansForWindow = typeof options.mainSpansForWindow === 'function' ? options.mainSpansForWindow : null;
184
277
  const rows = new Map();
185
278
  const publicRows = new Map();
186
279
  const pendingClosed = new Map();
@@ -280,6 +373,8 @@ function createTypingInteractionTraceStore(options = {}) {
280
373
  workerReceivedMs: stageDelta(row, 'worker-received', 'browser-keydown'),
281
374
  mirrorWrittenMs: stageDelta(row, 'mirror-written', 'browser-keydown'),
282
375
  emitOfferedMs: stageDelta(row, 'emit-offered', 'browser-keydown'),
376
+ // WP-4 (R-7): the shipped-frame leg for the closed interaction, read the same way.
377
+ framePostedMs: stageMsFromKeydown(row, 'frame-posted'),
283
378
  gate,
284
379
  gateHoldMs,
285
380
  clientId: row.clientId,
@@ -302,12 +397,14 @@ function createTypingInteractionTraceStore(options = {}) {
302
397
 
303
398
  function captureExpiredInteraction(row, at) {
304
399
  if (row.stages.painted) return;
305
- const lastObservedStage = STAGES.slice().reverse().find(stage => row.stages[stage]) || 'browser-keydown';
400
+ // WP-3 D-C3: required-only `lastObservedStage` + a separate `lastOptionalStage` (I-6/R9).
401
+ const { lastObservedStage, lastOptionalStage } = observedStageSplit(row);
306
402
  const ageMs = Math.max(0, at - row.startedAt);
307
403
  pushBounded(pendingExpired, rollupGroupKey(row.sessionId, row.agentType), {
308
404
  ageMs,
309
405
  clientId: row.clientId,
310
406
  lastObservedStage,
407
+ lastOptionalStage,
311
408
  });
312
409
  windowExpiredHistory.push({ at });
313
410
  if (windowExpiredHistory.length > WINDOW_HISTORY_CAP) windowExpiredHistory.shift();
@@ -411,10 +508,43 @@ function createTypingInteractionTraceStore(options = {}) {
411
508
  rejectedSurfaceEpoch: Math.max(0, finite(event.rejectedSurfaceEpoch)),
412
509
  sweepSkipReason: boundedText(event.sweepSkipReason, 24),
413
510
  };
511
+ } else if (stage === 'emit-offered' || stage === 'frame-posted') {
512
+ // WP-4 (R-7): these two stages carry the OFFERED/SHIPPED frame's echoedThroughSeq, so a
513
+ // reader can compare it against this row's own `inputSeq` (set once by begin(), above) --
514
+ // that comparison is what separates "the offered frame did not yet contain this key's
515
+ // echo" from "worker->main delivery / main-thread queueing". A sender that carries no seq
516
+ // records `{ at }` alone: an absent seq must never read as a real 0.
517
+ // First-wins like every other stage: the `!previous || at < previous.at` guard above owns
518
+ // idempotency, so a later note never rewrites this record.
519
+ const offeredEchoSeq = event.offeredEchoSeq;
520
+ row.stages[stage] = offeredEchoSeq == null
521
+ ? { at }
522
+ : { at, offeredEchoSeq: Math.max(0, Math.floor(finite(offeredEchoSeq))) };
523
+ } else if (stage === 'frame-arrived') {
524
+ // WP-4 r4 (R-8, §6.5): the arrival stamp may carry `mainSpans` -- the top ≤3 main-thread
525
+ // spans overlapping [frame-posted.at, frame-arrived.at], each `{ name, overlapMs }`.
526
+ // Same shape rule as `offeredEchoSeq` above: no array offered means `{ at }` alone (the
527
+ // store may still attach computed spans below, once both stamps are present).
528
+ row.stages[stage] = Array.isArray(event.mainSpans)
529
+ ? { at, mainSpans: sanitizeMainSpans(event.mainSpans) }
530
+ : { at };
414
531
  } else {
415
532
  row.stages[stage] = { at };
416
533
  }
417
534
  }
535
+ // R-8, order-independent: the worker posts `frame-posted` as its own 'typing-stage' message
536
+ // AFTER the frame shipped (headless-term-service.js emitSettled -> noteFramePostedForEchoed),
537
+ // so main usually stamps `frame-arrived` BEFORE it knows `frame-posted`. Compute the spans when
538
+ // the SECOND of the two lands, once per row; the window is in the past either way, and the
539
+ // span ring (lib/perf-tracker.js, 3 s+) still holds it.
540
+ if (mainSpansForWindow && (stage === 'frame-posted' || stage === 'frame-arrived')) {
541
+ const arrival = row.stages['frame-arrived'];
542
+ const postedAt = row.stages['frame-posted']?.at;
543
+ if (arrival && !arrival.mainSpans && Number.isFinite(postedAt)
544
+ && arrival.at - postedAt >= MAIN_SPANS_MIN_WINDOW_MS) {
545
+ arrival.mainSpans = sanitizeMainSpans(mainSpansForWindow(postedAt, arrival.at));
546
+ }
547
+ }
418
548
  if (stage === 'painted' && !previous) captureClosedInteraction(row);
419
549
  row.updatedAt = Math.max(row.updatedAt, at);
420
550
  row.lastCompletedBoundary = '';
@@ -740,6 +870,9 @@ function noteWorkerStageMessage(store, msg = {}) {
740
870
  sessionId: msg.sessionId,
741
871
  stage: msg.stage,
742
872
  at: msg.at,
873
+ // WP-4 (R-7): the offered/shipped frame's echoedThroughSeq rides the same worker message as
874
+ // the stage stamp, so emit-offered/frame-posted rows can be read against their inputSeq.
875
+ offeredEchoSeq: msg.offeredEchoSeq,
743
876
  gate: msg.gate,
744
877
  inFlightSeq: msg.inFlightSeq,
745
878
  inFlightKind: msg.inFlightKind,
@@ -769,4 +902,5 @@ function pendingInteractionIdsFor(byInputSeqMap, limit = 24) {
769
902
 
770
903
  module.exports = {
771
904
  createTypingInteractionTraceStore, STAGES, GATES, noteWorkerStageMessage, pendingInteractionIdsFor,
905
+ MAIN_SPANS_MIN_WINDOW_MS,
772
906
  };