create-walle 0.10.1 → 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 (341) hide show
  1. package/README.md +4 -0
  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 +6 -4
  6. package/package.json +1 -1
  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/claude-hook.js +7 -3
  11. package/template/coding-task-manager/bin/claude-table-parity.js +307 -0
  12. package/template/coding-task-manager/bin/conversation-storage-auto-migration-lib.js +16 -1
  13. package/template/coding-task-manager/bin/conversation-storage-generation-evidence.js +9 -2
  14. package/template/coding-task-manager/bin/conversation-storage-service-entry-lib.js +16 -1
  15. package/template/coding-task-manager/bin/conversation-storage-walle-owner.js +247 -0
  16. package/template/coding-task-manager/bin/ctm-machine-pair.js +160 -0
  17. package/template/coding-task-manager/bin/lead-wait-census.js +1348 -0
  18. package/template/coding-task-manager/bin/repair-foreign-clone.js +564 -0
  19. package/template/coding-task-manager/bin/repair-resume-echo-titles.js +248 -0
  20. package/template/coding-task-manager/bin/write-lock-bench.js +111 -0
  21. package/template/coding-task-manager/db.js +705 -286
  22. package/template/coding-task-manager/git-utils.js +44 -0
  23. package/template/coding-task-manager/lib/adopted-host-redial.js +4 -0
  24. package/template/coding-task-manager/lib/agent-presets.js +15 -0
  25. package/template/coding-task-manager/lib/agent-title-apply.js +88 -0
  26. package/template/coding-task-manager/lib/agent-title-backfill-read.js +92 -0
  27. package/template/coding-task-manager/lib/api/host-stats.js +98 -0
  28. package/template/coding-task-manager/lib/api/ingress.js +35 -12
  29. package/template/coding-task-manager/lib/api/machine-pairing.js +285 -0
  30. package/template/coding-task-manager/lib/api/machine-pool.js +307 -0
  31. package/template/coding-task-manager/lib/api/machines.js +461 -0
  32. package/template/coding-task-manager/lib/api/misc-ops.js +20 -5
  33. package/template/coding-task-manager/lib/api/providers.js +53 -6
  34. package/template/coding-task-manager/lib/api/resume-from-store.js +25 -0
  35. package/template/coding-task-manager/lib/api/sessions.js +160 -8
  36. package/template/coding-task-manager/lib/api/transcript-history.js +17 -4
  37. package/template/coding-task-manager/lib/api/worktrees.js +38 -37
  38. package/template/coding-task-manager/lib/approval-drift.js +16 -1
  39. package/template/coding-task-manager/lib/approval-logical-frame.js +5 -1
  40. package/template/coding-task-manager/lib/approval-scope.js +68 -4
  41. package/template/coding-task-manager/lib/auth-context.js +32 -2
  42. package/template/coding-task-manager/lib/auth-rate-limit.js +41 -1
  43. package/template/coding-task-manager/lib/auth-rules.js +186 -45
  44. package/template/coding-task-manager/lib/broadcast-serialize.js +2 -1
  45. package/template/coding-task-manager/lib/browser-session-broker.js +9 -1
  46. package/template/coding-task-manager/lib/browser-use-adapter.js +33 -2
  47. package/template/coding-task-manager/lib/claude-markdown-ansi.js +27 -85
  48. package/template/coding-task-manager/lib/claude-session-title.js +6 -30
  49. package/template/coding-task-manager/lib/claude-table-layout.js +322 -0
  50. package/template/coding-task-manager/lib/client-diag-handler.js +5 -0
  51. package/template/coding-task-manager/lib/codex-config-guard.js +33 -3
  52. package/template/coding-task-manager/lib/codex-harness-qualification.js +12 -0
  53. package/template/coding-task-manager/lib/codex-launch-health.js +29 -2
  54. package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
  55. package/template/coding-task-manager/lib/codex-paths.js +2 -1
  56. package/template/coding-task-manager/lib/coding-agent-models.js +21 -30
  57. package/template/coding-task-manager/lib/coding-session-model-adapters.js +27 -2
  58. package/template/coding-task-manager/lib/coding-session-model-projection.js +5 -0
  59. package/template/coding-task-manager/lib/coding-session-model-runtime.js +1 -2
  60. package/template/coding-task-manager/lib/coding-session-model-state.js +10 -0
  61. package/template/coding-task-manager/lib/coding-session-model-sync-state.js +0 -1
  62. package/template/coding-task-manager/lib/conversation-import/cursor-guard-owner.js +10 -0
  63. package/template/coding-task-manager/lib/create/resolve-launch-params.js +8 -1
  64. package/template/coding-task-manager/lib/ctm-boot-lifecycle.js +17 -1
  65. package/template/coding-task-manager/lib/db-domain-tables.js +7 -0
  66. package/template/coding-task-manager/lib/db-domains.js +21 -2
  67. package/template/coding-task-manager/lib/db-owner-op-registry.js +7 -0
  68. package/template/coding-task-manager/lib/db-schema-source-files.js +1 -0
  69. package/template/coding-task-manager/lib/feature-manifest.js +1 -1
  70. package/template/coding-task-manager/lib/fs-watcher.js +3 -2
  71. package/template/coding-task-manager/lib/harness-history-transition.js +3 -3
  72. package/template/coding-task-manager/lib/headless-inflight-ops.js +203 -0
  73. package/template/coding-task-manager/lib/headless-missing-reply.js +42 -0
  74. package/template/coding-task-manager/lib/headless-term-service.js +55 -17
  75. package/template/coding-task-manager/lib/host-control-dial.js +167 -0
  76. package/template/coding-task-manager/lib/host-handoff-orchestrator.js +16 -2
  77. package/template/coding-task-manager/lib/host-handoff.js +51 -13
  78. package/template/coding-task-manager/lib/host-health-ledger.js +164 -12
  79. package/template/coding-task-manager/lib/host-self-truth-watchdog.js +4 -0
  80. package/template/coding-task-manager/lib/host-stats.js +328 -0
  81. package/template/coding-task-manager/lib/host-threadpool-evacuation.js +160 -0
  82. package/template/coding-task-manager/lib/host-threadpool-monitor.js +14 -1
  83. package/template/coding-task-manager/lib/hosted-input-dispatch.js +203 -0
  84. package/template/coding-task-manager/lib/hosted-sync-worker.js +113 -5
  85. package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +4 -0
  86. package/template/coding-task-manager/lib/instance-manifest-runtime.js +3 -0
  87. package/template/coding-task-manager/lib/instance-manifest.js +13 -1
  88. package/template/coding-task-manager/lib/instance-sync-status.js +15 -0
  89. package/template/coding-task-manager/lib/launch-model-override.js +160 -1
  90. package/template/coding-task-manager/lib/launch-plan-worker.js +59 -2
  91. package/template/coding-task-manager/lib/lead-model-switch-runtime.js +57 -4
  92. package/template/coding-task-manager/lib/lead-model-switch.js +3 -2
  93. package/template/coding-task-manager/lib/lead-model-transition-store.js +61 -3
  94. package/template/coding-task-manager/lib/legacy-draft-cancellation-recovery.js +111 -0
  95. package/template/coding-task-manager/lib/login-tripwire.js +1 -0
  96. package/template/coding-task-manager/lib/machine-pairing.js +238 -0
  97. package/template/coding-task-manager/lib/machine-pool.js +409 -0
  98. package/template/coding-task-manager/lib/machine-registry.js +749 -0
  99. package/template/coding-task-manager/lib/machine-self.js +66 -0
  100. package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +52 -4
  101. package/template/coding-task-manager/lib/media-journal-control-reader.js +473 -0
  102. package/template/coding-task-manager/lib/media-journal.js +84 -27
  103. package/template/coding-task-manager/lib/media-store-options.js +43 -15
  104. package/template/coding-task-manager/lib/migrations/v56-host-samples-and-machine-pool.js +76 -0
  105. package/template/coding-task-manager/lib/mirror-feed-sanitize.js +12 -3
  106. package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
  107. package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
  108. package/template/coding-task-manager/lib/model-proxy/handoff.js +494 -0
  109. package/template/coding-task-manager/lib/model-proxy/lead-worker-picks.js +25 -0
  110. package/template/coding-task-manager/lib/model-proxy/native-lead-listener.js +61 -7
  111. package/template/coding-task-manager/lib/model-proxy/proxy-cli.js +200 -0
  112. package/template/coding-task-manager/lib/model-proxy/proxy-instance-record.js +257 -0
  113. package/template/coding-task-manager/lib/model-proxy/stream-accounting.js +205 -0
  114. package/template/coding-task-manager/lib/model-proxy/tokens.js +78 -0
  115. package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
  116. package/template/coding-task-manager/lib/native-history-control.js +2 -2
  117. package/template/coding-task-manager/lib/native-lead-settings-client.js +2 -1
  118. package/template/coding-task-manager/lib/native-lead-tls.js +18 -4
  119. package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +628 -64
  120. package/template/coding-task-manager/lib/owner-action-manifest-data.json +14 -0
  121. package/template/coding-task-manager/lib/painted-row-recorder.js +9 -1
  122. package/template/coding-task-manager/lib/prompt-submission-contract.js +21 -1
  123. package/template/coding-task-manager/lib/provider-login-broker.js +192 -9
  124. package/template/coding-task-manager/lib/provider-login-capture.js +153 -0
  125. package/template/coding-task-manager/lib/queue-draft-status-overlay.js +36 -0
  126. package/template/coding-task-manager/lib/raw-codex-history-window.js +5 -3
  127. package/template/coding-task-manager/lib/read-pool-client.js +5 -2
  128. package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
  129. package/template/coding-task-manager/lib/recent-catalog-query.js +26 -0
  130. package/template/coding-task-manager/lib/recent-session-catalog-response.js +9 -0
  131. package/template/coding-task-manager/lib/remote-command-context.js +12 -0
  132. package/template/coding-task-manager/lib/remote-command-runtime.js +13 -1
  133. package/template/coding-task-manager/lib/remote-machine-catalog.js +1062 -0
  134. package/template/coding-task-manager/lib/remote-machine-create.js +693 -0
  135. package/template/coding-task-manager/lib/remote-machine-link.js +939 -0
  136. package/template/coding-task-manager/lib/remote-machine-proxy.js +520 -0
  137. package/template/coding-task-manager/lib/remote-session-forward.js +306 -0
  138. package/template/coding-task-manager/lib/remote-submission-status.js +6 -0
  139. package/template/coding-task-manager/lib/restart-outcome-truth.js +20 -3
  140. package/template/coding-task-manager/lib/restart-preflight.js +9 -2
  141. package/template/coding-task-manager/lib/restore-backoff-gate.js +10 -2
  142. package/template/coding-task-manager/lib/restore-prepare-retry.js +12 -5
  143. package/template/coding-task-manager/lib/restore-resume-batch.js +22 -1
  144. package/template/coding-task-manager/lib/resume-blocked-wire.js +62 -1
  145. package/template/coding-task-manager/lib/resume-cwd.js +13 -0
  146. package/template/coding-task-manager/lib/resume-from-store.js +12 -1
  147. package/template/coding-task-manager/lib/resume-origin-facts.js +38 -1
  148. package/template/coding-task-manager/lib/resume-preflight.js +21 -16
  149. package/template/coding-task-manager/lib/review-repo-admission.js +39 -6
  150. package/template/coding-task-manager/lib/review-repo-registry.js +17 -0
  151. package/template/coding-task-manager/lib/root-delivery-runtime.js +72 -15
  152. package/template/coding-task-manager/lib/root-delivery-selection.js +37 -1
  153. package/template/coding-task-manager/lib/root-prompt-admission.js +33 -2
  154. package/template/coding-task-manager/lib/root-prompt-flow.js +167 -33
  155. package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
  156. package/template/coding-task-manager/lib/session-analysis-memory.js +2 -0
  157. package/template/coding-task-manager/lib/session-create/codex-native-provider-gate.js +218 -0
  158. package/template/coding-task-manager/lib/session-create/codex-resume-exit-classify.js +52 -0
  159. package/template/coding-task-manager/lib/session-create/delegate-worker.js +95 -6
  160. package/template/coding-task-manager/lib/session-create/identity.js +20 -1
  161. package/template/coding-task-manager/lib/session-create/prepared.js +69 -1
  162. package/template/coding-task-manager/lib/session-create/provider-launch-context.js +77 -0
  163. package/template/coding-task-manager/lib/session-create/provider-process.js +34 -37
  164. package/template/coding-task-manager/lib/session-create/response.js +30 -12
  165. package/template/coding-task-manager/lib/session-create/transcript.js +13 -4
  166. package/template/coding-task-manager/lib/session-create/worker-continuation.js +43 -1
  167. package/template/coding-task-manager/lib/session-create/worker-notices.js +94 -5
  168. package/template/coding-task-manager/lib/session-create/worker-observability.js +99 -7
  169. package/template/coding-task-manager/lib/session-create/worker-stop.js +11 -3
  170. package/template/coding-task-manager/lib/session-create/worker-wait-watcher.js +315 -0
  171. package/template/coding-task-manager/lib/session-geometry-authority-handler.js +10 -1
  172. package/template/coding-task-manager/lib/session-host-manager.js +152 -206
  173. package/template/coding-task-manager/lib/session-jobs.js +17 -9
  174. package/template/coding-task-manager/lib/session-lifecycle.js +18 -0
  175. package/template/coding-task-manager/lib/session-model-action-preconditions.js +9 -0
  176. package/template/coding-task-manager/lib/session-resource-sampler.js +19 -9
  177. package/template/coding-task-manager/lib/session-standup.js +67 -4
  178. package/template/coding-task-manager/lib/session-status-ledger.js +42 -3
  179. package/template/coding-task-manager/lib/session-status-runtime.js +45 -1
  180. package/template/coding-task-manager/lib/session-title-mirror.js +25 -57
  181. package/template/coding-task-manager/lib/session-transcript-trash.js +8 -1
  182. package/template/coding-task-manager/lib/startup-pointer-write.js +57 -0
  183. package/template/coding-task-manager/lib/state-sync/painted-frame-field.js +11 -0
  184. package/template/coding-task-manager/lib/storage/coding-session-model-store.js +21 -22
  185. package/template/coding-task-manager/lib/surviving-owner-bootstrap.js +7 -2
  186. package/template/coding-task-manager/lib/surviving-owner-recovery.js +2 -1
  187. package/template/coding-task-manager/lib/terminal-draft-http-status.js +25 -0
  188. package/template/coding-task-manager/lib/transcript-archiver.js +229 -229
  189. package/template/coding-task-manager/lib/transcript-frame-cache.js +12 -2
  190. package/template/coding-task-manager/lib/transcript-guard-job.js +55 -4
  191. package/template/coding-task-manager/lib/transcript-link.js +77 -0
  192. package/template/coding-task-manager/lib/transcript-manifest-store.js +8 -4
  193. package/template/coding-task-manager/lib/transcript-source.js +8 -3
  194. package/template/coding-task-manager/lib/transcript-store.js +40 -0
  195. package/template/coding-task-manager/lib/tunnel-lane-governor.js +6 -3
  196. package/template/coding-task-manager/lib/typing-interaction-trace.js +92 -0
  197. package/template/coding-task-manager/lib/typing-worker-stages.js +60 -0
  198. package/template/coding-task-manager/lib/visible-revision-watchdog.js +8 -12
  199. package/template/coding-task-manager/lib/wake-skip-reasons.js +46 -0
  200. package/template/coding-task-manager/lib/walle-supervisor.js +98 -14
  201. package/template/coding-task-manager/lib/worker-failure-classify.js +13 -1
  202. package/template/coding-task-manager/lib/ws/state-sync.js +19 -2
  203. package/template/coding-task-manager/lib/ws/terminal-attach.js +32 -1
  204. package/template/coding-task-manager/lib/ws-router.js +68 -1
  205. package/template/coding-task-manager/lib/ws-send-admission.js +22 -4
  206. package/template/coding-task-manager/oauth-proxy.js +99 -55
  207. package/template/coding-task-manager/package-lock.json +24 -0
  208. package/template/coding-task-manager/package.json +3 -0
  209. package/template/coding-task-manager/providers/codex-mcp-extract.js +121 -19
  210. package/template/coding-task-manager/providers/codex-mcp.js +14 -5
  211. package/template/coding-task-manager/public/css/app.css +279 -1
  212. package/template/coding-task-manager/public/css/reviews.css +4 -0
  213. package/template/coding-task-manager/public/index.html +294 -223
  214. package/template/coding-task-manager/public/js/app/machines-page.js +1252 -0
  215. package/template/coding-task-manager/public/js/app/models-tab.js +546 -36
  216. package/template/coding-task-manager/public/js/app/worktrees-panel.js +7 -5
  217. package/template/coding-task-manager/public/js/background-work.js +13 -7
  218. package/template/coding-task-manager/public/js/coding-session-model-presentation.js +44 -4
  219. package/template/coding-task-manager/public/js/core/session-commands.js +676 -0
  220. package/template/coding-task-manager/public/js/core/session-selectors.js +5 -1
  221. package/template/coding-task-manager/public/js/history-app-seam.js +26 -2
  222. package/template/coding-task-manager/public/js/history-painted-rows.js +17 -2
  223. package/template/coding-task-manager/public/js/history-reader-park.js +13 -1
  224. package/template/coding-task-manager/public/js/history-scroller.js +187 -9
  225. package/template/coding-task-manager/public/js/history-transcript-pages.js +5 -5
  226. package/template/coding-task-manager/public/js/machine-pool-shared.js +436 -0
  227. package/template/coding-task-manager/public/js/private-state-scope.js +31 -3
  228. package/template/coding-task-manager/public/js/prompt-submission-client.js +173 -8
  229. package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
  230. package/template/coding-task-manager/public/js/queue-panel-submission-reconcile.js +174 -1
  231. package/template/coding-task-manager/public/js/reviews.js +7 -3
  232. package/template/coding-task-manager/public/js/root-draft-lifetime.js +139 -2
  233. package/template/coding-task-manager/public/js/root-queue-builder-lifetime.js +16 -0
  234. package/template/coding-task-manager/public/js/row-join.js +4 -2
  235. package/template/coding-task-manager/public/js/session-model-picker.js +1 -1
  236. package/template/coding-task-manager/public/js/session-search-utils.js +46 -19
  237. package/template/coding-task-manager/public/js/state-sync-client.js +4 -0
  238. package/template/coding-task-manager/public/js/terminal/runtime-02.js +16 -7
  239. package/template/coding-task-manager/public/js/terminal/runtime-03.js +41 -0
  240. package/template/coding-task-manager/public/js/terminal/runtime-04.js +41 -4
  241. package/template/coding-task-manager/public/js/terminal/runtime-12.js +52 -2
  242. package/template/coding-task-manager/public/js/terminal/runtime-16.js +12 -12
  243. package/template/coding-task-manager/public/js/terminal/runtime-18.js +14 -0
  244. package/template/coding-task-manager/public/js/terminal/runtime-19.js +8 -4
  245. package/template/coding-task-manager/public/js/terminal/runtime-20.js +210 -73
  246. package/template/coding-task-manager/public/js/terminal/runtime-21.js +19 -6
  247. package/template/coding-task-manager/public/js/terminal/runtime-22.js +86 -0
  248. package/template/coding-task-manager/public/js/terminal-history-viewport.js +1 -1
  249. package/template/coding-task-manager/public/js/terminal-wheel.js +39 -2
  250. package/template/coding-task-manager/public/js/walle/chat-conversation.js +11 -3
  251. package/template/coding-task-manager/public/js/walle-session.js +30 -7
  252. package/template/coding-task-manager/public/m/app.css +226 -8
  253. package/template/coding-task-manager/public/m/app.js +502 -9
  254. package/template/coding-task-manager/public/m/index.html +38 -1
  255. package/template/coding-task-manager/public/m/machines.js +1313 -0
  256. package/template/coding-task-manager/public/m/pool-failover.js +391 -0
  257. package/template/coding-task-manager/public/m/private-queue-actions.js +26 -7
  258. package/template/coding-task-manager/public/m/sw.js +5 -0
  259. package/template/coding-task-manager/queue-engine.js +13 -0
  260. package/template/coding-task-manager/server/approvals.js +5 -6
  261. package/template/coding-task-manager/server/boot.js +136 -17
  262. package/template/coding-task-manager/server.js +902 -239
  263. package/template/coding-task-manager/session-integrity.js +7 -9
  264. package/template/coding-task-manager/workers/db-owner-worker.js +53 -7
  265. package/template/coding-task-manager/workers/read-pool-worker.js +17 -6
  266. package/template/coding-task-manager/workers/session-host-pool-process.js +18 -4
  267. package/template/coding-task-manager/workers/session-host-process.js +14 -3
  268. package/template/coding-task-manager/workers/transcript-archive-worker.js +1 -1
  269. package/template/coding-task-manager/workers/worktree-worker.js +34 -0
  270. package/template/package-lock.json +2 -2
  271. package/template/package.json +1 -1
  272. package/template/scripts/conversation-storage-v2-migration.js +78 -11
  273. package/template/shared/conversation-storage-v2-contract.js +42 -6
  274. package/template/shared/fixtures/worker-status-payloads.json +392 -0
  275. package/template/shared/git-remote-key.js +83 -0
  276. package/template/shared/host-identity.js +98 -2
  277. package/template/shared/owner-write-safety.js +135 -0
  278. package/template/shared/public-runtime.js +13 -5
  279. package/template/shared/release-schema.json +3 -2
  280. package/template/shared/report-citations.js +14 -0
  281. package/template/shared/sqlite-write-lock.js +361 -223
  282. package/template/shared/storage/contract.js +35 -3
  283. package/template/wall-e/agent.js +21 -0
  284. package/template/wall-e/api-walle.js +5 -0
  285. package/template/wall-e/bin/brain-purge-legacy-ctm.js +3 -2
  286. package/template/wall-e/bin/walle-mcp-stdio.js +12 -6
  287. package/template/wall-e/brain/helper-rules-schema.js +129 -0
  288. package/template/wall-e/brain/migrations.js +51 -22
  289. package/template/wall-e/brain/model-deployment-facts.js +31 -1
  290. package/template/wall-e/brain/owner-call-whitelist.js +16 -0
  291. package/template/wall-e/brain/schema-ensures.js +22 -0
  292. package/template/wall-e/brain.js +1 -1
  293. package/template/wall-e/coding/journal-dispatcher.js +51 -0
  294. package/template/wall-e/coding/session-journal.js +28 -40
  295. package/template/wall-e/coding-orchestrator.js +23 -26
  296. package/template/wall-e/http/auth.js +18 -1
  297. package/template/wall-e/http/blog-views-api.js +318 -0
  298. package/template/wall-e/http/helper-rules-api.js +321 -0
  299. package/template/wall-e/http/model-proxy-providers.js +3 -1
  300. package/template/wall-e/http/telemetry-api.js +12 -0
  301. package/template/wall-e/lib/blog-view-policy.js +88 -0
  302. package/template/wall-e/lib/blog-view-store.js +169 -0
  303. package/template/wall-e/lib/client-config-lock.js +2 -1
  304. package/template/wall-e/lib/coding-journal-writer-child-client.js +67 -18
  305. package/template/wall-e/lib/error-kinds.js +10 -1
  306. package/template/wall-e/lib/feature-manifest.js +1 -1
  307. package/template/wall-e/lib/helper-rules/cell-state.js +123 -0
  308. package/template/wall-e/lib/helper-rules/evaluate.js +357 -0
  309. package/template/wall-e/lib/helper-rules/explore-rubric.md +12 -0
  310. package/template/wall-e/lib/helper-rules/grade.js +277 -0
  311. package/template/wall-e/lib/helper-rules/label.js +247 -0
  312. package/template/wall-e/lib/helper-rules/lead-call-hook.js +522 -0
  313. package/template/wall-e/lib/helper-rules/store.js +667 -0
  314. package/template/wall-e/lib/mcp-integration.js +5 -6
  315. package/template/wall-e/lib/runtime-worker-pool.js +1 -0
  316. package/template/wall-e/lib/scheduler-worker-jobs.js +6 -0
  317. package/template/wall-e/lib/telemetry-db-schema.js +37 -0
  318. package/template/wall-e/lib/telemetry-process-client.js +76 -1
  319. package/template/wall-e/llm/default-fallback.js +4 -0
  320. package/template/wall-e/llm/model-catalog.js +13 -4
  321. package/template/wall-e/llm/model-roles.js +6 -0
  322. package/template/wall-e/llm/portkey-http.js +150 -0
  323. package/template/wall-e/llm/portkey-sync.js +15 -145
  324. package/template/wall-e/llm/worker-mcp-routing.js +61 -0
  325. package/template/wall-e/llm/worker-route.js +43 -1
  326. package/template/wall-e/loops/brain-custom-writes.js +45 -0
  327. package/template/wall-e/mcp-protocol-contract.js +41 -1
  328. package/template/wall-e/mcp-server.js +13 -2
  329. package/template/wall-e/mcp-worker-schemas.js +20 -2
  330. package/template/wall-e/memory/ctm-context-client.js +25 -5
  331. package/template/wall-e/shared/release-schema.json +3 -2
  332. package/template/wall-e/shared/sqlite-write-lock.js +361 -223
  333. package/template/wall-e/shared/storage/contract.js +35 -3
  334. package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
  335. package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +6 -0
  336. package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +7 -2
  337. package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +7 -2
  338. package/template/wall-e/tools/local-tools.js +6 -4
  339. package/template/wall-e/workers/brain-owner-worker.js +9 -0
  340. package/template/wall-e/workers/coding-journal-writer-child.js +8 -1
  341. package/template/wall-e/workers/telemetry-worker.js +8 -0
@@ -427,6 +427,15 @@ function reconstructSessionModelTransitionSource({ sessionId, source, sourceLaun
427
427
  const rebound = require('./launch-model-override').ensureCodexResumeTarget(args, source.providerThreadId);
428
428
  if (!rebound.ok) return null;
429
429
  args = rebound.args;
430
+ } else if (agent === 'claude') {
431
+ // The SAME canonical normalization for a native Claude source: the recorded capture may name its
432
+ // own thread through an explicit `--session-id` (the identity CTM injected at startup) and may
433
+ // still carry that launch's original positional prompt. Only the canonical owner's own
434
+ // `--resume <source thread>` is the source recipe, so the existing extractor below reads a
435
+ // rebound argv instead of rejecting the capture outright.
436
+ const rebound = require('./launch-model-override').ensureClaudeResumeTarget(args, source.providerThreadId);
437
+ if (!rebound.ok) return null;
438
+ args = rebound.args;
430
439
  }
431
440
  if ((require('./session-history').extractResumeTarget(agent, args)?.sessionId || '') !== String(source.providerThreadId)) return null;
432
441
  return { ...context, args };
@@ -79,14 +79,14 @@ function splitAgentDescendantRows(rows, agentPid) {
79
79
  const agentRssMB = agentRow ? Math.round(((Number(agentRow.rssKB) || 0) / 1024) * 100) / 100 : 0;
80
80
  const pending = [...(byParent.get(agentPid) || [])];
81
81
  const seen = new Set();
82
- let descendantRssKB = 0, descendantGroupLeaderCount = 0; // D-10: a descendant leading its own process group (a background shell's wrapper)
82
+ let descendantRssKB = 0, descendantCpuTimeMs = 0, descendantGroupLeaderCount = 0; // D-10: a descendant leading its own process group (a background shell's wrapper)
83
83
  while (pending.length) {
84
84
  const pid = pending.pop();
85
85
  if (seen.has(pid)) continue;
86
86
  seen.add(pid);
87
87
  const row = byPid.get(pid);
88
88
  if (!row) continue;
89
- descendantRssKB += Number(row.rssKB) || 0; if (row.pgid === row.pid) descendantGroupLeaderCount += 1;
89
+ descendantRssKB += Number(row.rssKB) || 0; descendantCpuTimeMs += Number(row.cpuTimeMs) || 0; if (row.pgid === row.pid) descendantGroupLeaderCount += 1;
90
90
  pending.push(...(byParent.get(pid) || []));
91
91
  }
92
92
  return {
@@ -99,7 +99,7 @@ function splitAgentDescendantRows(rows, agentPid) {
99
99
  // invisible to a pgid-only rollup).
100
100
  descendantPids: [...seen],
101
101
  descendantGroupLeaderCount,
102
- descendantRssMB: Math.round((descendantRssKB / 1024) * 100) / 100,
102
+ descendantRssMB: Math.round((descendantRssKB / 1024) * 100) / 100, descendantCpuTimeMs, // WP-0 (bg-shell-liveness): Σ cpuTimeMs of every visited descendant
103
103
  };
104
104
  }
105
105
 
@@ -468,18 +468,28 @@ class SessionResourceSampler {
468
468
  return { agentPid: Number(agentPid), descendantPids: split.descendantPids, descendantGroupLeaderCount: split.descendantGroupLeaderCount };
469
469
  }
470
470
 
471
+ // D-9 (WP-6, clock-free-tests-followups-2026-10-23 §5 D-9/§8 WP-6): ONE whole-table read a caller
472
+ // can share across every rollup in one sweep. Same command/args/limits as sampleProcessGroup's own
473
+ // read, and the same maxBuffer: `ps -axo …` prints ~1.37 MB on this host, above Node's 1 MiB default.
474
+ async sampleProcessTable() {
475
+ const psCommand = this.platform === 'darwin' ? '/bin/ps' : 'ps';
476
+ const { stdout } = await this.exec(psCommand, PS_ARGS, { timeout: 10_000, maxBuffer: 8 * 1024 * 1024 });
477
+ return parseProcessTable(stdout);
478
+ }
479
+
471
480
  // D-4: the post-kill census. One ps read; the pgid rollup as before, plus every row whose pid is in
472
481
  // `descendantPidsBefore` (and not already in the group). Survivors are merged into pids/procCount/rssMB/cpuPct.
473
482
  // A call with no second argument (every pre-existing caller) behaves exactly as before.
474
- async sampleProcessGroup(pgid, descendantPidsBefore = []) {
475
- const psCommand = this.platform === 'darwin' ? '/bin/ps' : 'ps';
476
- const { stdout } = await this.exec(psCommand, PS_ARGS, { timeout: 10_000, maxBuffer: 8 * 1024 * 1024 });
477
- const rows = parseProcessTable(stdout);
478
- const group = rollupProcessGroup(rows, pgid);
483
+ // D-9: `{ rows }` lets a sweep hand in the ONE table it already read (sampleProcessTable above) —
484
+ // then this call execs nothing. The rollup/survivor logic below is shared verbatim by both paths,
485
+ // so a caller that supplies rows cannot diverge from one that does not.
486
+ async sampleProcessGroup(pgid, descendantPidsBefore = [], { rows = null } = {}) {
487
+ const table = Array.isArray(rows) ? rows : await this.sampleProcessTable();
488
+ const group = rollupProcessGroup(table, pgid);
479
489
  const before = new Set((descendantPidsBefore || []).map(Number));
480
490
  if (!before.size) return group;
481
491
  const have = new Set(group.pids);
482
- const survivors = rows.filter(row => before.has(Number(row.pid)) && !have.has(row.pid)).sort((a, b) => a.pid - b.pid);
492
+ const survivors = table.filter(row => before.has(Number(row.pid)) && !have.has(row.pid)).sort((a, b) => a.pid - b.pid);
483
493
  if (!survivors.length) return group;
484
494
  const rssKB = survivors.reduce((sum, row) => sum + (Number(row.rssKB) || 0), 0);
485
495
  const cpuPct = survivors.reduce((sum, row) => sum + (Number(row.cpuPct) || 0), 0);
@@ -882,6 +882,15 @@ function classifySessionStandup(session, signals = {}, now = Date.now()) {
882
882
  resume: session?.resume?.schemaVersion === 2 ? session.resume : null,
883
883
  priority: LANE_ORDER.get(lane) ?? 99, backgroundWork: session?.backgroundWork || null, statusReason: session?.statusReason || '',
884
884
  verdictAt: Number(session?.verdictAt) || Number(session?.liveStatusAt) || 0,
885
+ // WP-3 (remote-hub-followups D-9): the machine tag rides the card IFF the input session carries
886
+ // it. `_buildStandupSnapshot` stamps local standup sessions EMPTY and remote (box) sessions with
887
+ // the registry values, but ONLY when at least one machine is registered — so a zero-machine hub
888
+ // adds no key here and its snapshot stays byte-identical to the pre-WP-3 output (AC-3a).
889
+ ...(session && Object.prototype.hasOwnProperty.call(session, 'machine') ? {
890
+ machine: String(session.machine == null ? '' : session.machine).trim(),
891
+ machineName: String(session.machineName == null ? '' : session.machineName).trim(),
892
+ machineHealth: String(session.machineHealth == null ? '' : session.machineHealth).trim(),
893
+ } : {}),
885
894
  };
886
895
  // WP1C: the card's bounded own/null authority fields and its shared model label, applied to THIS card
887
896
  // object after it is built (see applyStandupModelAuthority in ./coding-session-model-public).
@@ -970,13 +979,13 @@ function isStandupStartupTaskSeedEnabled(env = process.env) {
970
979
  // existing classifier can run through unmodified, so a "seeded" card gets exactly the same
971
980
  // lane/priority logic as a live one — just starting from persisted metadata instead of a
972
981
  // live stream.
973
- function startupTaskPlaceholderSession(task) {
982
+ function startupTaskPlaceholderSession(task, titleInfo) {
974
983
  if (!task) return null;
975
984
  const id = String(task.session_id || task.ctm_session_id || '').trim();
976
985
  if (!id) return null;
977
986
  const placeholder = {
978
987
  id,
979
- label: task.label || '',
988
+ label: String(titleInfo?.title || '').trim() || task.label || '', // D-13/S-26: durable ctm_sessions title; echo only if no row
980
989
  cmd: task.cmd || '',
981
990
  cwd: task.cwd || '',
982
991
  project: task.cwd || '',
@@ -1015,15 +1024,16 @@ function startupTaskPlaceholderSession(task) {
1015
1024
  // a startup task appearing more than once). Callers union the result onto their live session
1016
1025
  // list; kept separate (rather than mutating/returning a merged array) so the server can push
1017
1026
  // each placeholder through the same standupPairs/attention wiring live sessions use.
1018
- function selectUnmatchedStartupTaskPlaceholders(existingSessions, startupTasks) {
1027
+ function selectUnmatchedStartupTaskPlaceholders(existingSessions, startupTasks, titlesForIds) {
1019
1028
  const seen = new Set();
1020
1029
  for (const session of existingSessions || []) {
1021
1030
  const id = String(session?.id || session?.sessionId || '').trim();
1022
1031
  if (id) seen.add(id);
1023
1032
  }
1033
+ const titlesById = typeof titlesForIds === 'function' ? titlesForIds((startupTasks || []).map((t) => t?.session_id || t?.ctm_session_id).filter(Boolean)) : null; // D-13/S-26: ONE batched read, never per row
1024
1034
  const placeholders = [];
1025
1035
  for (const task of startupTasks || []) {
1026
- const placeholder = startupTaskPlaceholderSession(task);
1036
+ const placeholder = startupTaskPlaceholderSession(task, titlesById?.get?.(task?.session_id || task?.ctm_session_id));
1027
1037
  if (!placeholder || seen.has(placeholder.id)) continue;
1028
1038
  seen.add(placeholder.id);
1029
1039
  placeholders.push(placeholder);
@@ -1031,6 +1041,57 @@ function selectUnmatchedStartupTaskPlaceholders(existingSessions, startupTasks)
1031
1041
  return placeholders;
1032
1042
  }
1033
1043
 
1044
+ // WP-3 (D-9): the statuses a REMOTE (box) session row can carry that mean its PTY is live. A row
1045
+ // whose box reports a parked/exited/stopped look (the sync-mirror or last-good degradation) is not
1046
+ // live. The hub never consults a LOCAL PTY helper for these rows — they are not on this host.
1047
+ const REMOTE_ROW_LIVE_STATUSES = new Set([
1048
+ 'running', 'busy', 'active', 'working', 'thinking',
1049
+ 'waiting_input', 'waiting_for_input', 'waiting',
1050
+ 'resuming', 'restoring', 'restarting',
1051
+ ]);
1052
+
1053
+ /** The box's own liveness for a remote catalog row (never a local PTY check). */
1054
+ function remoteRowTerminalLive(row) {
1055
+ // The row's OWN status decides when it carries one (a live reachable box row, or the at-rest
1056
+ // mirror's 'parked'); the boolean flags are only a fallback for a row that has no status.
1057
+ const status = String(row?.standupStatus || row?.liveStatus || row?.status || row?.lifecycleState || '')
1058
+ .trim().toLowerCase().replace(/[-\s]+/g, '_');
1059
+ if (status) return REMOTE_ROW_LIVE_STATUSES.has(status);
1060
+ return row?.terminalLive === true || row?.terminal_live === true
1061
+ || row?.activeCtmSession === true || row?.active_ctm_session === true
1062
+ || row?.runtimeActive === true || row?.runtime_active === true;
1063
+ }
1064
+
1065
+ /**
1066
+ * WP-3 (D-9): a session-shaped object for one row of `remoteMachineCatalog.remoteRows()`, ready for
1067
+ * the same `classifySessionStandup` the local sessions map goes through (`summary: null` — a box row
1068
+ * has no local stream summary). Liveness comes from the row's OWN status; no local PTY helper runs.
1069
+ *
1070
+ * @param {object} row a remote catalog row (already machine-tagged by remoteRows())
1071
+ * @param {{instanceId?: string, displayName?: string, health?: string}} [machineFacts] fallback tag
1072
+ */
1073
+ function remoteStandupSession(row, machineFacts = {}) {
1074
+ const source = row && typeof row === 'object' ? row : {};
1075
+ const terminalLive = remoteRowTerminalLive(source);
1076
+ const rawStatus = String(source.standupStatus || source.liveStatus || source.status || '').trim();
1077
+ const standupStatus = rawStatus
1078
+ ? rawStatus.toLowerCase().replace(/[-\s]+/g, '_')
1079
+ : (terminalLive ? 'running' : 'parked');
1080
+ return {
1081
+ ...source,
1082
+ machine: String(source.machine || machineFacts.instanceId || '').trim(),
1083
+ machineName: String(source.machineName || machineFacts.displayName || '').trim(),
1084
+ machineHealth: String(source.machineHealth || machineFacts.health || '').trim(),
1085
+ terminalLive,
1086
+ terminal_live: terminalLive,
1087
+ activeCtmSession: terminalLive,
1088
+ active_ctm_session: terminalLive,
1089
+ runtimeActive: terminalLive,
1090
+ runtime_active: terminalLive,
1091
+ standupStatus,
1092
+ };
1093
+ }
1094
+
1034
1095
  function buildSessionStandupSnapshot({ sessions = [], summaries = [], statuses = [], now = Date.now() } = {}) {
1035
1096
  const summaryIndex = buildIndex(summaries);
1036
1097
  const statusIndex = buildIndex(statuses);
@@ -1070,6 +1131,8 @@ module.exports = {
1070
1131
  startupTaskPlaceholderSession,
1071
1132
  selectUnmatchedStartupTaskPlaceholders,
1072
1133
  isStandupStartupTaskSeedEnabled,
1134
+ remoteRowTerminalLive,
1135
+ remoteStandupSession,
1073
1136
  _private: {
1074
1137
  normalizeStatus,
1075
1138
  normalizeProvider,
@@ -72,6 +72,7 @@ const BOUNDARY_CLOSERS = new Set(['end_turn', 'interrupt']);
72
72
  const BG_END_STATUSES = new Set(['completed', 'failed', 'killed', 'stopped']);
73
73
  // renewal span, and the backstop expiry when no process sample arrives (busy-background-shell design D-3); the CLI has no local cap since 2.1.288
74
74
  const BG_SHELL_MAX_MS = 2 * 3600_000 + 5 * 60_000;
75
+ const BG_QUIET_MS = 30 * 60_000;
75
76
  const BG_DECLARED_MAX_MS = 6 * 3600_000;
76
77
  const BG_LABEL_MAX = 160;
77
78
  const BG_VIEW_TASKS_MAX = 8;
@@ -189,6 +190,7 @@ class SessionStatusLedger {
189
190
  next.set(n.id, {
190
191
  ...n, firstSeenAt, expiresAt: keep ? Math.max(Number(prev.expiresAt) || 0, cap) : cap, source: 'hook', count: 1,
191
192
  renewedAt: keep ? (prev.renewedAt || 0) : 0, renewals: keep ? (prev.renewals || 0) : 0, capCrossedAt: keep ? (prev.capCrossedAt || 0) : 0,
193
+ activeAt: keep ? (prev.activeAt || firstSeenAt) : firstSeenAt, outSize: keep ? (prev.outSize || 0) : 0, outMtimeMs: keep ? (prev.outMtimeMs || 0) : 0,
192
194
  });
193
195
  }
194
196
  let changed = next.size !== before || !this.backgroundDeclared;
@@ -222,6 +224,7 @@ class SessionStatusLedger {
222
224
  expiresAt: keep ? Math.max(Number(prev.expiresAt) || 0, computed) : computed,
223
225
  source: src, count,
224
226
  renewedAt: keep ? (prev.renewedAt || 0) : 0, renewals: keep ? (prev.renewals || 0) : 0, capCrossedAt: keep ? (prev.capCrossedAt || 0) : 0,
227
+ activeAt: keep ? (prev.activeAt || firstSeenAt) : firstSeenAt, outSize: keep ? (prev.outSize || 0) : 0, outMtimeMs: keep ? (prev.outMtimeMs || 0) : 0,
225
228
  };
226
229
  if (leaseMs) entry.leased = true;
227
230
  this.background.set(n.id, entry);
@@ -234,16 +237,28 @@ class SessionStatusLedger {
234
237
  return this.background.size > 0;
235
238
  }
236
239
 
240
+ // D-1: quiet = a hook-declared shell with no activity evidence for BG_QUIET_MS. Never kills; only drops park protection.
241
+ backgroundQuiet(entry, now) {
242
+ return entry.type === 'shell' && entry.source === 'hook' && now - (entry.activeAt || 0) >= BG_QUIET_MS;
243
+ }
244
+
245
+ backgroundActive(now = Date.now()) {
246
+ this._bgExpire(now);
247
+ return [...this.background.values()].some(e => !this.backgroundQuiet(e, now));
248
+ }
249
+
237
250
  backgroundView(now = Date.now()) {
238
251
  this._bgExpire(now);
239
252
  if (!this.background.size) return null;
240
253
  const live = [...this.background.values()].sort((a, b) => a.firstSeenAt - b.firstSeenAt);
241
254
  const types = {};
242
255
  let count = 0;
256
+ let quiet = 0;
257
+ for (const e of live) { if (this.backgroundQuiet(e, now)) quiet += 1; }
243
258
  for (const e of live) { types[e.type] = (types[e.type] || 0) + e.count; count += e.count; }
244
259
  return {
245
- count, types,
246
- tasks: live.slice(0, BG_VIEW_TASKS_MAX).map(e => ({ id: e.id, type: e.type, label: e.label, since: e.firstSeenAt })),
260
+ count, types, quiet,
261
+ tasks: live.slice(0, BG_VIEW_TASKS_MAX).map(e => ({ id: e.id, type: e.type, label: e.label, since: e.firstSeenAt, activeAt: e.activeAt || 0, quiet: this.backgroundQuiet(e, now) })),
247
262
  observedAt: this.backgroundObservedAt, declared: this.backgroundDeclared,
248
263
  };
249
264
  }
@@ -257,6 +272,7 @@ class SessionStatusLedger {
257
272
  entries.push({
258
273
  id: e.id, type: e.type, label: e.label, firstSeenAt: e.firstSeenAt, expiresAt: e.expiresAt, source: e.source, count: e.count,
259
274
  renewedAt: e.renewedAt || 0, renewals: e.renewals || 0, capCrossedAt: e.capCrossedAt || 0,
275
+ activeAt: e.activeAt || 0, outSize: e.outSize || 0, outMtimeMs: e.outMtimeMs || 0,
260
276
  });
261
277
  }
262
278
  return { declared: this.backgroundDeclared, observedAt: this.backgroundObservedAt, entries };
@@ -279,6 +295,7 @@ class SessionStatusLedger {
279
295
  firstSeenAt, expiresAt: Math.min(Number(raw.expiresAt), now + bgCapFor(type)),
280
296
  source: String(raw.source || 'hook'), count: Math.max(1, Number(raw.count) || 1),
281
297
  renewedAt: Number(raw.renewedAt) || 0, renewals: Math.max(0, Number(raw.renewals) || 0), capCrossedAt: Number(raw.capCrossedAt) || 0,
298
+ activeAt: Number(raw.activeAt) > 0 ? Number(raw.activeAt) : now, outSize: Number(raw.outSize) || 0, outMtimeMs: Number(raw.outMtimeMs) || 0,
282
299
  });
283
300
  n += 1;
284
301
  }
@@ -325,6 +342,28 @@ class SessionStatusLedger {
325
342
  return renewed;
326
343
  }
327
344
 
345
+ // D-4: silent activity evidence (a process-sample CPU delta). Returns the number of entries whose activeAt moved.
346
+ markBackgroundActive(predicate, at = Date.now()) {
347
+ let moved = 0;
348
+ for (const e of this.background.values()) {
349
+ if (!predicate(e)) continue;
350
+ if (at > (e.activeAt || 0)) { e.activeAt = at; moved += 1; }
351
+ }
352
+ return moved;
353
+ }
354
+
355
+ // D-4: output-file growth evidence; a never-seen entry counts as growth only when size > 0.
356
+ observeBackgroundOutput(id, { size = 0, mtimeMs = 0 } = {}, at = Date.now()) {
357
+ const e = this.background.get(id);
358
+ if (!e) return false;
359
+ const seen = (e.outSize || 0) > 0 || (e.outMtimeMs || 0) > 0;
360
+ const grew = seen ? (size > (e.outSize || 0) || mtimeMs > (e.outMtimeMs || 0)) : size > 0;
361
+ if (!grew) return false;
362
+ e.activeAt = Math.max(e.activeAt || 0, at);
363
+ e.outSize = size; e.outMtimeMs = mtimeMs;
364
+ return true;
365
+ }
366
+
328
367
  record(kind, { at = Date.now(), source = '', meta = {} } = {}) {
329
368
  const prev = this.verdict(at);
330
369
  const bgBefore = this._bgPending.length;
@@ -579,4 +618,4 @@ class SessionStatusLedger {
579
618
  }
580
619
  }
581
620
 
582
- module.exports = { SessionStatusLedger, EVIDENCE, AGENT_OUTPUT, PROVIDER_STATUS_POLICY, BG_END_STATUSES, BG_SHELL_MAX_MS, BG_DECLARED_MAX_MS, BG_LABEL_MAX, normalizeBackgroundTask };
621
+ module.exports = { SessionStatusLedger, EVIDENCE, AGENT_OUTPUT, PROVIDER_STATUS_POLICY, BG_END_STATUSES, BG_SHELL_MAX_MS, BG_QUIET_MS, BG_DECLARED_MAX_MS, BG_LABEL_MAX, normalizeBackgroundTask };
@@ -34,6 +34,7 @@ const UNKNOWN_IDLE_BACKOFF_MS = 60000;
34
34
  const OPEN_TURN_IDLE_PROBE_AFTER_MS = 15000;
35
35
  // D-6: a session tree's CPU-time delta is process evidence at or above this floor AND at or above 5 % of one core
36
36
  // over the sample interval (the park predicate's cpuPct >= 5); the rate keeps a 60 s sample from firing on 0.8 %.
37
+ const BG_OUTPUT_UNREADABLE_DIAG_MS = 10 * 60 * 1000;
37
38
  const CPU_ACTIVE_MIN_DELTA_MS = 500;
38
39
  const CPU_ACTIVE_MIN_CORE_FRACTION = 0.05;
39
40
  // The fast tick fires on a 10 s timer whose jitter must not skip a due sample.
@@ -886,6 +887,17 @@ function createBindings(d) {
886
887
  shape.zeros = 0; // a newer snapshot (or no shell to check) restarts the count
887
888
  shape.observedAt = ledger.backgroundObservedAt;
888
889
  }
890
+ // D-2: a descendant CPU-time delta is silent-activity evidence; the baseline is independent of snapshots.
891
+ const cpuNow = Number(sample.descendantCpuTimeMs);
892
+ if (Number.isFinite(cpuNow)) {
893
+ if (Number.isFinite(shape.cpuTimeMs)) {
894
+ const delta = cpuNow - shape.cpuTimeMs;
895
+ const intervalMs = Number(sample.intervalMs) > 0 ? Number(sample.intervalMs) : (sampledAt - (Number(shape.cpuSampledAt) || 0));
896
+ if (delta >= Math.max(CPU_ACTIVE_MIN_DELTA_MS, CPU_ACTIVE_MIN_CORE_FRACTION * intervalMs)) ledger.markBackgroundActive(isHookShell, sampledAt);
897
+ }
898
+ shape.cpuTimeMs = cpuNow;
899
+ shape.cpuSampledAt = sampledAt;
900
+ }
889
901
  const leaders = Number(sample.descendantGroupLeaderCount);
890
902
  if (leaders > 0 && sampledAt > ledger.backgroundObservedAt && [...ledger.background.values()].some(isHookShell)) {
891
903
  const renewed = ledger.renewBackground(isHookShell, sampledAt); // D-1
@@ -915,6 +927,29 @@ function createBindings(d) {
915
927
  d.markHeartbeatDirty(session);
916
928
  }
917
929
 
930
+ // D-3: the output file of each live hook shell is a second silent-activity signal. Never throws.
931
+ async function observeTaskOutputs(session, sampledAt) {
932
+ try {
933
+ const ledger = session && session._statusLedger;
934
+ if (!ledger || !d.fsp) return;
935
+ const entries = [...ledger.background.values()].filter((e) => e.type === 'shell' && e.source === 'hook');
936
+ for (const e of entries) {
937
+ const p = typeof d.taskOutputPathFor === 'function' ? d.taskOutputPathFor(session, e.id) : '';
938
+ if (!p) continue;
939
+ try {
940
+ const st = await d.fsp.stat(p);
941
+ ledger.observeBackgroundOutput(e.id, { size: st.size, mtimeMs: st.mtimeMs }, sampledAt);
942
+ } catch (err) {
943
+ const at = Number(sampledAt) || Date.now();
944
+ if (!(at - (Number(ledger._bgOutputUnreadableAt) || 0) < BG_OUTPUT_UNREADABLE_DIAG_MS)) {
945
+ ledger._bgOutputUnreadableAt = at;
946
+ d.recordDiagnostic(session.id, 'status-background-output-unreadable', { id: e.id, code: (err && err.code) || '' });
947
+ }
948
+ }
949
+ }
950
+ } catch { /* expected: evidence-only observer must never throw into the sampler */ }
951
+ }
952
+
918
953
  // D-6 cadence: 60 s baseline is the lifecycle sweep's own ps; while any ledger is probable/unknown/near-stall
919
954
  // (or a session was just CPU-active) this tick takes ONE cheap ps at STATUS_FAST_SAMPLE_MS. At baseline it is a no-op.
920
955
  let cpuTickRunning = false;
@@ -979,7 +1014,7 @@ function createBindings(d) {
979
1014
  }
980
1015
  }
981
1016
 
982
- return { timeDrivenTick, inputWaitIsIdle, markHookObserved, ledgerFor, verdictFor, recordEvidence, recordWaitCleared, syncWaitFields, composerMayClose, composerRetryMs, keepBlockingWait, cardGoneTwice, cardSeenTwice, cardKeyFor, scheduleWaitEvaluation, cancelWaitEvaluations, recordPtyChunk, fireStallBackstop, observeTranscriptBoundary, observeTranscriptGrowth, observeCodexRollout, liveStatus, tick, applyCpuSample, applyProcessShape, processSampleTick };
1017
+ return { timeDrivenTick, inputWaitIsIdle, markHookObserved, ledgerFor, verdictFor, recordEvidence, recordWaitCleared, syncWaitFields, composerMayClose, composerRetryMs, keepBlockingWait, cardGoneTwice, cardSeenTwice, cardKeyFor, scheduleWaitEvaluation, cancelWaitEvaluations, recordPtyChunk, fireStallBackstop, observeTranscriptBoundary, observeTranscriptGrowth, observeCodexRollout, liveStatus, tick, applyCpuSample, applyProcessShape, observeTaskOutputs, processSampleTick };
983
1018
  }
984
1019
 
985
1020
  // W-1: under host load shedding the wait-state sweep still owes the sessions whose status truth is at stake: wait fields set,
@@ -998,6 +1033,14 @@ function shedSweepIds(sessionMap, approverBuffers, cap = 4, now = Date.now()) {
998
1033
  return due.slice(0, cap).map(({ id, s }) => { s._reconcileVisitedAt = now; return id; });
999
1034
  }
1000
1035
 
1036
+ // D-3: Claude Code's background-shell output file. Pure; the server passes uid and its canonical project-entry encoder.
1037
+ function claudeTaskOutputPathFor(session, taskId, { uid, encodeClaudeProjectEntry } = {}) {
1038
+ const sid = String((session && session.agentSessionId) || '').trim();
1039
+ const cwd = String((session && session.cwd) || '');
1040
+ if (!sid || !cwd || !/^[\w.-]{1,80}$/.test(String(taskId)) || String(taskId) === '..' || String(taskId) === '.') return '';
1041
+ return require('path').join('/tmp', 'claude-' + uid, encodeClaudeProjectEntry(cwd), sid, 'tasks', taskId + '.output');
1042
+ }
1043
+
1001
1044
  // R-10: the constant-size empty keepalive frame, built on the main thread (never behind a read-pool op that can time out).
1002
1045
  function keepaliveFrame(now) {
1003
1046
  return JSON.stringify({ type: 'session-activity', sessions: [], keepalive: true, statusTimestamp: Number(now) || 0, serverNow: Number(now) || 0 });
@@ -1015,6 +1058,7 @@ function rowFields(v, reason) {
1015
1058
 
1016
1059
  module.exports = {
1017
1060
  createBindings,
1061
+ claudeTaskOutputPathFor,
1018
1062
  movingCardKey,
1019
1063
  shedSweepIds,
1020
1064
  keepaliveFrame,
@@ -76,14 +76,14 @@ async function _mirrorSessionTitleAttempt(o) {
76
76
  const message = (error && error.message) || String(error);
77
77
  try {
78
78
  if (db && typeof db.setSessionTitleMirror === 'function') {
79
- db.setSessionTitleMirror(sessionId, { state: 'failed', error: message, value: '' });
79
+ db.setSessionTitleMirror(sessionId, { state: 'failed', reason: 'exception', error: message, value: '' });
80
80
  }
81
81
  } catch (persistError) {
82
82
  // expected: a persist failure on this catch-all path must not turn a resolved
83
83
  // outcome into a throw either.
84
84
  void persistError;
85
85
  }
86
- return { state: 'failed', reason: '', error: message };
86
+ return { state: 'failed', reason: 'exception', error: message };
87
87
  }
88
88
  }
89
89
 
@@ -91,7 +91,10 @@ async function _mirrorSessionTitleInner({ db, sessionId, adapters, getTitleSpec
91
91
  const logger = typeof log === 'function' ? log : () => {};
92
92
  const persist = (state, reason, error, value) => {
93
93
  try {
94
- db.setSessionTitleMirror(sessionId, { state, error: error || '', value: value || '' });
94
+ // D-12: the REASON travels with the outcome — db.setSessionTitleMirror composes
95
+ // title_mirror_error = reason + (error ? ': ' + error : ''), so an `unsupported` row can no
96
+ // longer sit there with an empty error and no recorded cause (S-24).
97
+ db.setSessionTitleMirror(sessionId, { state, reason: reason || '', error: error || '', value: value || '' });
95
98
  } catch (persistError) {
96
99
  // expected: a persist failure must not turn a resolved outcome into a throw —
97
100
  // the caller still gets the real state/reason/error, just unrecorded in the DB.
@@ -147,10 +150,11 @@ async function _mirrorSessionTitleInner({ db, sessionId, adapters, getTitleSpec
147
150
  outcome = { state: 'unsupported', reason: 'unknown_title_spec_kind', error: '' };
148
151
  }
149
152
 
150
- // Step 7: persist + return.
153
+ // Step 7: persist + return. D-12: a failure that reports no reason of its own still gets one, so
154
+ // `failed`/`unsupported` never lands with an empty reason to compose from.
151
155
  return persist(
152
156
  outcome.state,
153
- outcome.reason || '',
157
+ outcome.reason || (outcome.state === 'failed' ? 'adapter_failed' : ''),
154
158
  outcome.error || '',
155
159
  outcome.state === 'mirrored' ? name : row.value
156
160
  );
@@ -166,6 +170,14 @@ async function _mirrorSessionTitleInner({ db, sessionId, adapters, getTitleSpec
166
170
  // Synchronous by design (setSessionTitleNew is a plain DB write, not provider I/O) — callers on
167
171
  // a live-session sync tick (lib/coding-agent-models.js, workers/db-owner-worker.js) never await
168
172
  // this; a rejection would have nothing to propagate to, so it's log-and-drop and retried next tick.
173
+ // D-9/AC-4c: the LAST agent name a re-assert was fired for, per CTM session. The observed name is
174
+ // re-read on every sync tick (D-10) while the agent's own name only changes when the AGENT renames
175
+ // itself, so a plain per-session memo is what turns "observed on every tick" into "mirrored once per
176
+ // observed name". Convergence clears the entry (the agent's name equals the CTM title again), so a
177
+ // later drift fires again. In-memory by design: it is a coalescing memo for a live process, never a
178
+ // durable state machine — a restart simply re-observes.
179
+ const _lastReassertedName = new Map();
180
+
169
181
  function applyProviderTitle(o = {}) {
170
182
  const { db, sessionId, provider, mirror = mirrorSessionTitle } = o;
171
183
  const agentName = normalizeSessionTitle(o.agentName);
@@ -175,13 +187,20 @@ function applyProviderTitle(o = {}) {
175
187
  if (agentName && agentName !== normalizeSessionTitle(row.title)) {
176
188
  // Fire-and-forget re-assert: the agent has drifted from the user's CTM name: push the
177
189
  // CTM name back out. NEVER write the agent's name over a user rename.
190
+ const key = String(sessionId || '');
191
+ if (_lastReassertedName.get(key) === agentName) return { applied: 'none', reason: 'already_reasserted' };
192
+ _lastReassertedName.set(key, agentName);
178
193
  void mirror({ db, sessionId });
179
194
  return { applied: 'reasserted' };
180
195
  }
196
+ if (agentName) _lastReassertedName.delete(String(sessionId || ''));
181
197
  return { applied: 'none' };
182
198
  }
183
199
 
184
200
  if (agentName) {
201
+ // No write churn: a row already carrying this exact name (tier 2 or a tier-3 title that
202
+ // happens to match) is left alone — no setSessionTitleNew, no updated_at bump.
203
+ if (row && normalizeSessionTitle(row.title) === agentName) return { applied: 'none', reason: 'unchanged' };
185
204
  try {
186
205
  // Explicitly detached: the write is best-effort off a live-session sync tick and this
187
206
  // function is synchronous, so a rejection has no caller to propagate to — the next tick
@@ -202,57 +221,6 @@ function applyProviderTitle(o = {}) {
202
221
  return { applied: 'none' };
203
222
  }
204
223
 
205
- // WP-3 (session-rename-mirror §5 D-1, D-4; AC-3d/AC-3e): resolves the Claude agent's OWN
206
- // session name for feeding applyProviderTitle, on demand for ONE session — never call this from
207
- // a full-catalog scan loop (see AC-3f's "no process spawn" sibling concern for Codex; this one's
208
- // hazard is the SDK round-trip, not a spawn). Guarded by getTitleSpec('claude')?.readsUserName:
209
- // an absent/false spec (the kill switch is `titleSpec: null`) falls straight back to the
210
- // caller-supplied parser result (parsedTitle, e.g. parseSessionFile's tail/head-scan title)
211
- // WITHOUT ever touching the SDK adapter (AC-3e). `customTitle` (info.userName) outranks the
212
- // derived `aiTitle` (info.summary when it differs from the first prompt) — same precedence
213
- // lib/claude-session-title.js's readClaudeSessionTitle already encodes into its own return shape.
214
- async function resolveClaudeAgentName(o = {}) {
215
- const { sessionId, dir, sdk, parsedTitle = '', getTitleSpec = defaultGetTitleSpec, adapter } = o;
216
- const spec = getTitleSpec('claude');
217
- if (!spec || !spec.readsUserName) return normalizeSessionTitle(parsedTitle);
218
- const claude = adapter || _defaultClaudeAdapter();
219
- const info = await claude.readClaudeSessionTitle({ sessionId, dir, sdk });
220
- if (!info) return normalizeSessionTitle(parsedTitle);
221
- const name = info.userName || info.aiTitle || '';
222
- return normalizeSessionTitle(name || parsedTitle);
223
- }
224
-
225
- // Fix round 1 (Finding 1): production wiring for resolveClaudeAgentName — one call per Claude
226
- // session becoming known/linked to CTM (never a scan/batch loop; the SDK round-trip is the
227
- // per-session hazard, matching AC-3f's spawn concern for Codex's sibling). Callers use
228
- // `void fillClaudeAgentTitle(...)` — async, fire-and-forget, never awaited on a hot sync path.
229
- // Cheap guard BEFORE the SDK read: a tier-1 (user_renamed) row never triggers the round-trip.
230
- // Never throws into the caller — any error (a throwing db read, a rejecting resolve/apply) is
231
- // reported via recordSessionDiagnostic (HANDLE) and swallowed; `unsupported`/`sdk_absent` from
232
- // resolveClaudeAgentName is a normal, silent `{applied:'none'}`, not an error.
233
- async function fillClaudeAgentTitle(o = {}) {
234
- const { db, sessionId, provider = 'claude', claudeSessionId, dir, sdk, getTitleSpec, adapter, recordSessionDiagnostic, log } = o;
235
- try {
236
- const row = db.getSessionTitleMirror(sessionId);
237
- if (row && row.userRenamed && row.title) return { applied: 'none' };
238
- const agentName = await resolveClaudeAgentName({ sessionId: claudeSessionId, dir, sdk, getTitleSpec, adapter });
239
- if (!agentName) return { applied: 'none' };
240
- return applyProviderTitle({ db, sessionId, provider, agentName });
241
- } catch (error) {
242
- const message = (error && error.message) || String(error);
243
- try {
244
- if (typeof recordSessionDiagnostic === 'function') {
245
- recordSessionDiagnostic(sessionId, 'claude-agent-title-fill-error', { error: message });
246
- }
247
- } catch (diagError) {
248
- // expected: diagnostics must never turn a swallowed error into a throw.
249
- void diagError;
250
- }
251
- if (typeof log === 'function') log('fillClaudeAgentTitle failed', message);
252
- return { applied: 'none' };
253
- }
254
- }
255
-
256
224
  // F1 (final review, AC-3g): shared guard for the tier-3 "live label" writer — the ONLY
257
225
  // question this answers is "does `existing` already carry a tier that a tier-3 write must
258
226
  // never clobber", NEVER a db access itself (pure, so both an on-thread caller with a
@@ -269,4 +237,4 @@ function canWriteTier3Title(existing) {
269
237
  return true;
270
238
  }
271
239
 
272
- module.exports = { mirrorSessionTitle, isUuidLike, applyProviderTitle, resolveClaudeAgentName, fillClaudeAgentTitle, canWriteTier3Title };
240
+ module.exports = { mirrorSessionTitle, isUuidLike, applyProviderTitle, canWriteTier3Title };
@@ -196,7 +196,14 @@ async function findTrashedTranscript({ trashRoot, fileName, fs: fsImpl = fsPromi
196
196
  } catch { continue; /* expected: missing/corrupt manifest in this trash dir */ }
197
197
  for (const entry of manifest.entries || []) {
198
198
  if (path.basename(entry.originalPath) === fileName) {
199
- return { trashPath: entry.trashPath, originalPath: entry.originalPath, trashedAt: manifest.trashedAt };
199
+ // WP-3 (D-13): the manifest already carries WHY it was trashed and when; the card's "why"
200
+ // sentence is built from these two facts alone, so they travel with the lookup result.
201
+ return {
202
+ trashPath: entry.trashPath,
203
+ originalPath: entry.originalPath,
204
+ trashedAt: manifest.trashedAt,
205
+ reason: String(manifest.reason || ''),
206
+ };
200
207
  }
201
208
  }
202
209
  }
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+ // WP-3 (docs/codex-routed-fork-cold-restore-guard-2026-10-22.html §9 S-11, §10 AC-3a/3b): the
3
+ // startup pointer is written ONLY through db.updateStartupTaskAgentSession, whose
4
+ // `resumesCandidateFork` guard (lib/lead-model-transition-store.js, commit 77b9b3c0e3) keeps the
5
+ // source pointer while a lead-model transition is still active. session-integrity.js used to
6
+ // re-point it with a raw `UPDATE startup_tasks SET args …` that bypassed the guard: the 30-min pass
7
+ // picks the newest transcript row — exactly the uncommitted fork of a half-switched Codex session.
8
+ //
9
+ // Callers hand this the raw better-sqlite3 core handle (workers/db-owner-worker.js
10
+ // 'recoverSessionIntegrity' → db.getDomainDb('core')) or the db module; either way the write goes
11
+ // through the module's guarded writer against the same core table. The writer returns nothing, so
12
+ // `args` is compared before/after to keep the fixed/skipped accounting a raw UPDATE's `.changes` gave.
13
+
14
+ function readStartupPointerArgs(handle, sessionId) {
15
+ try {
16
+ return handle.prepare('SELECT args FROM startup_tasks WHERE ctm_session_id = ?').get(sessionId)?.args;
17
+ } catch {
18
+ /* expected: no startup_tasks/args column in a partial fixture schema — the write below reports the real failure */
19
+ return undefined;
20
+ }
21
+ }
22
+
23
+ // Lazy require: db.js is loaded by the owner process already; nothing in its graph requires this file.
24
+ function startupPointerWriter(handle) {
25
+ if (handle && typeof handle.updateStartupTaskAgentSession === 'function') {
26
+ return handle.updateStartupTaskAgentSession.bind(handle);
27
+ }
28
+ const dbModule = require('../db.js');
29
+ if (typeof dbModule.updateStartupTaskAgentSession !== 'function') {
30
+ throw new Error('db.updateStartupTaskAgentSession is unavailable; refusing a raw startup_tasks pointer write');
31
+ }
32
+ return dbModule.updateStartupTaskAgentSession;
33
+ }
34
+
35
+ // The `agent_project_dir`/`claude_project_dir` value currently on the row — the guarded writer
36
+ // rewrites BOTH columns from one argument, so a caller that only means to move `args` hands back
37
+ // what is already stored instead of clearing the dirs.
38
+ function readStartupTaskProjectDir(handle, sessionId) {
39
+ for (const column of ['agent_project_dir', 'claude_project_dir']) {
40
+ try {
41
+ return handle.prepare(`SELECT ${column} AS dir FROM startup_tasks WHERE ctm_session_id = ?`).get(sessionId)?.dir || '';
42
+ } catch {
43
+ /* expected: column absent on this schema — try the sibling column */
44
+ }
45
+ }
46
+ return '';
47
+ }
48
+
49
+ // Move the startup pointer through the guarded writer; returns 1 when the row changed, else 0.
50
+ function writeStartupPointerGuarded(handle, sessionId, resumeArgs, projectDir) {
51
+ const write = startupPointerWriter(handle);
52
+ const before = readStartupPointerArgs(handle, sessionId);
53
+ write(sessionId, Array.isArray(resumeArgs) ? resumeArgs[1] : '', projectDir, resumeArgs);
54
+ return readStartupPointerArgs(handle, sessionId) === before ? 0 : 1;
55
+ }
56
+
57
+ module.exports = { writeStartupPointerGuarded, readStartupTaskProjectDir };
@@ -27,12 +27,23 @@ function nearestBreaks(breaks, top) {
27
27
  return list.slice().sort((a, b) => Math.abs(top - a.at) - Math.abs(top - b.at)).slice(0, BREAKS_MAX).sort((a, b) => a.at - b.at);
28
28
  }
29
29
 
30
+ function holdOf(h) {
31
+ if (!h || typeof h !== 'object') return null;
32
+ return { kind: typeof h.kind === 'string' ? h.kind : null, rows: isInt(h.rows) && h.rows >= 0 ? h.rows : 0, top: isInt(h.top) ? h.top : null };
33
+ }
34
+
30
35
  // `deps` = { readPainted(id, epoch, from, count), paintedRevisedSince(id, seq) }.
31
36
  // -> an object to spread into the frame: {} or { painted: { epoch, top, pinned, floor, revision, breaks, below[, revisedFrom][, from, rows] } }
32
37
  function paintedFrameField(deps, id, keyframe, ackedGrid, grid) {
33
38
  const now = grid && grid.painted;
34
39
  if (!now || typeof now !== 'object') return {};
35
40
  const state = { epoch: now.epoch, top: now.top, pinned: now.pinned, floor: now.floor, revision: isInt(now.revision) ? now.revision : 0, breaks: nearestBreaks(now.breaks, now.top), below: isInt(now.below) && now.below > 0 ? now.below : 0 }; // R-F2 (reader-seam-proof-followups): rows the app holds below its screen; a host that never measured it (or an at-rest app) sends 0
41
+ // Clock-free tests (WP-6): `samples` (the recorder's monotone observe count) and `hold` (the rejected, uncommitted samples) ride
42
+ // the field so a reader waits on "this write was sampled" instead of a window; neither one decides whether a frame is sent.
43
+ // Each is present only when the recorder published it, so an older recorder's field is unchanged.
44
+ if (isInt(now.samples) && now.samples >= 0) state.samples = now.samples;
45
+ const hold = holdOf(now.hold);
46
+ if (hold) state.hold = hold;
36
47
  const before = ackedGrid && ackedGrid.painted;
37
48
  if (keyframe || !before || before.epoch !== now.epoch) return { painted: state };
38
49
  const beforeRevision = isInt(before.revision) ? before.revision : 0;