create-walle 0.10.2 → 0.10.3

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 (488) hide show
  1. package/README.md +3 -1
  2. package/lib/release-schema.json +2 -2
  3. package/lib/runtime-closure-policy.json +8 -7
  4. package/package.json +1 -1
  5. package/template/coding-task-manager/api-prompts.js +45 -7
  6. package/template/coding-task-manager/bin/claude-hook.js +55 -1
  7. package/template/coding-task-manager/bin/ctm-relay-client.js +35 -12
  8. package/template/coding-task-manager/bin/land.js +2069 -0
  9. package/template/coding-task-manager/bin/memory-census.js +4 -2
  10. package/template/coding-task-manager/bin/memory-sync-verify.js +151 -0
  11. package/template/coding-task-manager/bin/probe-refusal-scope.js +530 -0
  12. package/template/coding-task-manager/db.js +156 -32
  13. package/template/coding-task-manager/lib/acp/acp-client.js +383 -0
  14. package/template/coding-task-manager/lib/acp/dsh-context-truth.js +176 -0
  15. package/template/coding-task-manager/lib/acp/dsh-launch-model.js +302 -0
  16. package/template/coding-task-manager/lib/acp/dsh-mcp-servers.js +194 -0
  17. package/template/coding-task-manager/lib/acp/dsh-prompt-lane.js +616 -0
  18. package/template/coding-task-manager/lib/acp/dsh-route-patch.js +308 -0
  19. package/template/coding-task-manager/lib/acp/dsh-session-runtime.js +522 -0
  20. package/template/coding-task-manager/lib/acp/dsh-transcript.js +395 -0
  21. package/template/coding-task-manager/lib/acp/dsh-usage-log.js +162 -0
  22. package/template/coding-task-manager/lib/acp/ndjson-stdio-transport.js +225 -0
  23. package/template/coding-task-manager/lib/adopted-host-redial.js +47 -11
  24. package/template/coding-task-manager/lib/adopted-launch-environment.js +123 -0
  25. package/template/coding-task-manager/lib/agent-auto-update-runtime.js +0 -3
  26. package/template/coding-task-manager/lib/agent-auto-update.js +26 -10
  27. package/template/coding-task-manager/lib/agent-bundle/build.js +657 -0
  28. package/template/coding-task-manager/lib/agent-bundle/codex-install.js +558 -0
  29. package/template/coding-task-manager/lib/agent-bundle/drift.js +120 -0
  30. package/template/coding-task-manager/lib/agent-bundle/gc.js +522 -0
  31. package/template/coding-task-manager/lib/agent-bundle/inventory.js +346 -0
  32. package/template/coding-task-manager/lib/agent-bundle/push.js +717 -0
  33. package/template/coding-task-manager/lib/agent-bundle/skill-transfer.js +724 -0
  34. package/template/coding-task-manager/lib/agent-bundle/spawn.js +119 -0
  35. package/template/coding-task-manager/lib/agent-bundle/store.js +568 -0
  36. package/template/coding-task-manager/lib/agent-capabilities.js +142 -12
  37. package/template/coding-task-manager/lib/agent-cli-cache.js +30 -5
  38. package/template/coding-task-manager/lib/agent-definitions.js +19 -0
  39. package/template/coding-task-manager/lib/agent-hooks-codex-toml.js +28 -16
  40. package/template/coding-task-manager/lib/agent-hooks-codex-workspace.js +9 -1
  41. package/template/coding-task-manager/lib/agent-hooks-installer.js +23 -8
  42. package/template/coding-task-manager/lib/agent-hooks-prune.js +37 -11
  43. package/template/coding-task-manager/lib/agent-hooks-shim.js +105 -0
  44. package/template/coding-task-manager/lib/agent-presets.js +84 -1
  45. package/template/coding-task-manager/lib/agent-upgrade-jobs.js +12 -1
  46. package/template/coding-task-manager/lib/agent-version-service.js +74 -11
  47. package/template/coding-task-manager/lib/all-sessions-query.js +6 -2
  48. package/template/coding-task-manager/lib/api/agent-bundles.js +436 -0
  49. package/template/coding-task-manager/lib/api/backup-retention.js +83 -0
  50. package/template/coding-task-manager/lib/api/conversation-sync-routes.js +124 -0
  51. package/template/coding-task-manager/lib/api/diagnostics.js +549 -69
  52. package/template/coding-task-manager/lib/api/fleet.js +118 -0
  53. package/template/coding-task-manager/lib/api/hub-bridge.js +243 -0
  54. package/template/coding-task-manager/lib/api/ingress.js +9 -1
  55. package/template/coding-task-manager/lib/api/landing.js +403 -0
  56. package/template/coding-task-manager/lib/api/machines.js +691 -2
  57. package/template/coding-task-manager/lib/api/memory-sync-routes.js +335 -0
  58. package/template/coding-task-manager/lib/api/misc-ops.js +8 -3
  59. package/template/coding-task-manager/lib/api/remote-sessions.js +1326 -0
  60. package/template/coding-task-manager/lib/api/session-attach.js +623 -0
  61. package/template/coding-task-manager/lib/api/session-messages.js +1384 -0
  62. package/template/coding-task-manager/lib/api/sessions.js +77 -51
  63. package/template/coding-task-manager/lib/api/setup-network.js +544 -16
  64. package/template/coding-task-manager/lib/api/setup-wizard-handlers.js +884 -0
  65. package/template/coding-task-manager/lib/api/setup.js +829 -10
  66. package/template/coding-task-manager/lib/api/walle-bridge.js +9 -1
  67. package/template/coding-task-manager/lib/attachment-reconciler-wiring.js +5 -2
  68. package/template/coding-task-manager/lib/auth-rate-limit.js +23 -0
  69. package/template/coding-task-manager/lib/auth-rules.js +292 -21
  70. package/template/coding-task-manager/lib/background-stop.js +369 -0
  71. package/template/coding-task-manager/lib/best-of-attempts.js +4 -1
  72. package/template/coding-task-manager/lib/best-of-launch-plan.js +52 -0
  73. package/template/coding-task-manager/lib/box-keep-awake.js +89 -0
  74. package/template/coding-task-manager/lib/broadcast-reason-stats.js +152 -0
  75. package/template/coding-task-manager/lib/broadcast-serialize.js +5 -0
  76. package/template/coding-task-manager/lib/build-info.js +4 -2
  77. package/template/coding-task-manager/lib/claude-model-truth.js +13 -2
  78. package/template/coding-task-manager/lib/claude-transcript-terminal-renderer.js +17 -13
  79. package/template/coding-task-manager/lib/client-diag-handler.js +64 -2
  80. package/template/coding-task-manager/lib/codex-app-server-client.js +14 -16
  81. package/template/coding-task-manager/lib/codex-configuration-history.js +55 -0
  82. package/template/coding-task-manager/lib/codex-harness-qualification.js +43 -20
  83. package/template/coding-task-manager/lib/codex-rollout-history.js +743 -0
  84. package/template/coding-task-manager/lib/coding-agent-models.js +27 -12
  85. package/template/coding-task-manager/lib/coding-cli-skill-installer.js +44 -14
  86. package/template/coding-task-manager/lib/coding-session-model-adapters.js +43 -2
  87. package/template/coding-task-manager/lib/coding-session-model-owner.js +1 -1
  88. package/template/coding-task-manager/lib/coding-session-model-public.js +7 -1
  89. package/template/coding-task-manager/lib/coding-session-model-runtime.js +23 -4
  90. package/template/coding-task-manager/lib/coding-session-model-scan.js +31 -0
  91. package/template/coding-task-manager/lib/coding-session-model-state.js +7 -0
  92. package/template/coding-task-manager/lib/conversation-freshness-diagnostics.js +280 -0
  93. package/template/coding-task-manager/lib/conversation-item-clock.js +261 -0
  94. package/template/coding-task-manager/lib/conversation-source-registry.js +5 -0
  95. package/template/coding-task-manager/lib/conversation-store-v2.js +44 -0
  96. package/template/coding-task-manager/lib/conversation-sync.js +355 -0
  97. package/template/coding-task-manager/lib/create/resolve-launch-params.js +96 -12
  98. package/template/coding-task-manager/lib/create/worker-role-profile.js +4 -4
  99. package/template/coding-task-manager/lib/create-intent-lease.js +51 -0
  100. package/template/coding-task-manager/lib/ctm-session-context-api.js +97 -6
  101. package/template/coding-task-manager/lib/db-domain-tables.js +1 -0
  102. package/template/coding-task-manager/lib/db-domains.js +1 -0
  103. package/template/coding-task-manager/lib/db-owner-dispatch-probe.js +32 -0
  104. package/template/coding-task-manager/lib/db-owner-request-counts.js +178 -0
  105. package/template/coding-task-manager/lib/db-owner-worker-client.js +68 -14
  106. package/template/coding-task-manager/lib/db-schema-source-files.js +5 -0
  107. package/template/coding-task-manager/lib/deferred-restore-attempt.js +6 -2
  108. package/template/coding-task-manager/lib/feature-manifest.js +4 -0
  109. package/template/coding-task-manager/lib/fleet-conversation-route.js +97 -0
  110. package/template/coding-task-manager/lib/fleet-fanout.js +78 -0
  111. package/template/coding-task-manager/lib/fleet-hub.js +762 -0
  112. package/template/coding-task-manager/lib/fleet-origin-follower.js +252 -0
  113. package/template/coding-task-manager/lib/fleet-resolver-effects.js +147 -0
  114. package/template/coding-task-manager/lib/fleet-resolver-tick.js +148 -0
  115. package/template/coding-task-manager/lib/fleet-resolver.js +454 -0
  116. package/template/coding-task-manager/lib/fleet-session-list-routes.js +114 -0
  117. package/template/coding-task-manager/lib/fleet-session-lists.js +672 -0
  118. package/template/coding-task-manager/lib/fleet-update.js +798 -0
  119. package/template/coding-task-manager/lib/fs-watcher-root-watch.js +141 -0
  120. package/template/coding-task-manager/lib/fs-watcher.js +58 -42
  121. package/template/coding-task-manager/lib/harness-history-transition.js +50 -9
  122. package/template/coding-task-manager/lib/headless-term-service.js +84 -8
  123. package/template/coding-task-manager/lib/hermetic-agent-launch-guard.js +10 -0
  124. package/template/coding-task-manager/lib/history-transition-read-op.js +175 -8
  125. package/template/coding-task-manager/lib/host-cpu-categories.js +310 -0
  126. package/template/coding-task-manager/lib/host-fseventsd-sampler.js +399 -0
  127. package/template/coding-task-manager/lib/host-handoff.js +16 -19
  128. package/template/coding-task-manager/lib/host-stats.js +150 -3
  129. package/template/coding-task-manager/lib/hosted-sync-worker.js +43 -320
  130. package/template/coding-task-manager/lib/hub-bridge-access.js +798 -0
  131. package/template/coding-task-manager/lib/hub-bridge-box.js +409 -0
  132. package/template/coding-task-manager/lib/hub-bridge-mcp.js +380 -0
  133. package/template/coding-task-manager/lib/hub-bridge-mesh.js +313 -0
  134. package/template/coding-task-manager/lib/hub-bridge-ops.js +721 -0
  135. package/template/coding-task-manager/lib/hub-bridge.js +398 -0
  136. package/template/coding-task-manager/lib/idle-notify.js +491 -0
  137. package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +8 -0
  138. package/template/coding-task-manager/lib/instance-manifest.js +5 -1
  139. package/template/coding-task-manager/lib/instance-sync-status.js +18 -8
  140. package/template/coding-task-manager/lib/landing/agent-rule.txt +1 -0
  141. package/template/coding-task-manager/lib/landing/batch-runner.js +366 -0
  142. package/template/coding-task-manager/lib/landing/config.js +536 -0
  143. package/template/coding-task-manager/lib/landing/ctm-view.js +256 -0
  144. package/template/coding-task-manager/lib/landing/gate.js +360 -0
  145. package/template/coding-task-manager/lib/landing/git.js +344 -0
  146. package/template/coding-task-manager/lib/landing/lease.js +247 -0
  147. package/template/coding-task-manager/lib/landing/merge-delta.js +380 -0
  148. package/template/coding-task-manager/lib/landing/proof.js +223 -0
  149. package/template/coding-task-manager/lib/landing/queue.js +998 -0
  150. package/template/coding-task-manager/lib/landing/radar.js +489 -0
  151. package/template/coding-task-manager/lib/landing/resolve-cli.js +424 -0
  152. package/template/coding-task-manager/lib/landing/state.js +263 -0
  153. package/template/coding-task-manager/lib/landing/stop.js +49 -0
  154. package/template/coding-task-manager/lib/landing/sync-cli.js +475 -0
  155. package/template/coding-task-manager/lib/landing/sync.js +456 -0
  156. package/template/coding-task-manager/lib/landing/train.js +885 -0
  157. package/template/coding-task-manager/lib/landing/window.js +57 -0
  158. package/template/coding-task-manager/lib/launch-environment.js +121 -0
  159. package/template/coding-task-manager/lib/launch-model-override.js +36 -10
  160. package/template/coding-task-manager/lib/launch-plan-pool.js +2 -1
  161. package/template/coding-task-manager/lib/launch-plan-warm-cache.js +6 -0
  162. package/template/coding-task-manager/lib/lead-model-observer.js +57 -1
  163. package/template/coding-task-manager/lib/lead-model-options.js +160 -3
  164. package/template/coding-task-manager/lib/lead-model-restore-reconciliation.js +106 -24
  165. package/template/coding-task-manager/lib/lead-model-switch-runtime.js +352 -68
  166. package/template/coding-task-manager/lib/lead-model-switch.js +114 -10
  167. package/template/coding-task-manager/lib/lead-model-transition-runtime.js +14 -2
  168. package/template/coding-task-manager/lib/lead-model-transition-store.js +24 -6
  169. package/template/coding-task-manager/lib/lead-route-preflight.js +179 -0
  170. package/template/coding-task-manager/lib/lead-switch-history-mode.js +110 -0
  171. package/template/coding-task-manager/lib/lead-wire-agent.js +7 -0
  172. package/template/coding-task-manager/lib/lifecycle-wake-experiment.js +9 -2
  173. package/template/coding-task-manager/lib/live-defer-announce.js +45 -0
  174. package/template/coding-task-manager/lib/machine-registry.js +750 -5
  175. package/template/coding-task-manager/lib/macos-capabilities.js +32 -15
  176. package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +433 -53
  177. package/template/coding-task-manager/lib/mainloop-observability.js +14 -9
  178. package/template/coding-task-manager/lib/media-draft-owner.js +16 -3
  179. package/template/coding-task-manager/lib/memory-join.js +337 -0
  180. package/template/coding-task-manager/lib/memory-sync-verify.js +393 -0
  181. package/template/coding-task-manager/lib/memory-sync.js +565 -0
  182. package/template/coding-task-manager/lib/migrations/v57-delegate-pref-mirror-opencode.js +68 -0
  183. package/template/coding-task-manager/lib/migrations/v58-subagent-launch-native-subagents.js +42 -0
  184. package/template/coding-task-manager/lib/migrations/v59-coding-session-model-state-dsh.js +73 -0
  185. package/template/coding-task-manager/lib/migrations/v60-delegate-pref-mirror-dsh.js +78 -0
  186. package/template/coding-task-manager/lib/migrations/v61-conversation-item-clock.js +71 -0
  187. package/template/coding-task-manager/lib/model-availability.js +727 -0
  188. package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +4 -6
  189. package/template/coding-task-manager/lib/model-host-receipt.js +6 -3
  190. package/template/coding-task-manager/lib/model-id-rule.js +19 -0
  191. package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
  192. package/template/coding-task-manager/lib/model-proxy/auto-mode-review.js +110 -23
  193. package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
  194. package/template/coding-task-manager/lib/model-proxy/generation-probe.js +60 -17
  195. package/template/coding-task-manager/lib/model-proxy/historical-tool-declarations.js +37 -0
  196. package/template/coding-task-manager/lib/model-proxy/lead-request-validation.js +1 -0
  197. package/template/coding-task-manager/lib/model-proxy/lead-token-admin.js +130 -0
  198. package/template/coding-task-manager/lib/model-proxy/refusal-scope.js +151 -0
  199. package/template/coding-task-manager/lib/model-proxy/request-budget-store.js +11 -2
  200. package/template/coding-task-manager/lib/model-proxy/tokens.js +69 -5
  201. package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
  202. package/template/coding-task-manager/lib/native-agent-model-args.js +25 -22
  203. package/template/coding-task-manager/lib/native-history-control.js +6 -4
  204. package/template/coding-task-manager/lib/native-lead-settings-client.js +44 -4
  205. package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +173 -36
  206. package/template/coding-task-manager/lib/online-backup-worker-client.js +2 -0
  207. package/template/coding-task-manager/lib/opencode-cli.js +480 -0
  208. package/template/coding-task-manager/lib/opencode-launch-model.js +471 -0
  209. package/template/coding-task-manager/lib/opencode-v1-plugin/model-carrier.mjs +35 -0
  210. package/template/coding-task-manager/lib/opencode-v2-plugin/server.js +118 -0
  211. package/template/coding-task-manager/lib/owner-action-manifest-data.json +1 -1
  212. package/template/coding-task-manager/lib/park-composer-draft.js +54 -0
  213. package/template/coding-task-manager/lib/park-fresh-relaunch.js +41 -10
  214. package/template/coding-task-manager/lib/perf-phase-trace.js +72 -0
  215. package/template/coding-task-manager/lib/perf-summary.js +7 -0
  216. package/template/coding-task-manager/lib/perf-tracker.js +63 -2
  217. package/template/coding-task-manager/lib/project-config.js +7 -0
  218. package/template/coding-task-manager/lib/prompt-purge-v2.js +52 -1
  219. package/template/coding-task-manager/lib/provider-login-broker.js +72 -15
  220. package/template/coding-task-manager/lib/provider-login-tab.js +10 -2
  221. package/template/coding-task-manager/lib/read-pool-client.js +89 -2
  222. package/template/coding-task-manager/lib/read-pool-worker-handle.js +29 -4
  223. package/template/coding-task-manager/lib/recent-catalog-query.js +15 -0
  224. package/template/coding-task-manager/lib/recent-session-catalog-response.js +67 -10
  225. package/template/coding-task-manager/lib/recovery-evidence.js +26 -11
  226. package/template/coding-task-manager/lib/relay-client.js +57 -3
  227. package/template/coding-task-manager/lib/relink-audit.js +34 -5
  228. package/template/coding-task-manager/lib/remote-machine-catalog.js +321 -26
  229. package/template/coding-task-manager/lib/remote-machine-create.js +576 -36
  230. package/template/coding-task-manager/lib/remote-machine-link.js +546 -59
  231. package/template/coding-task-manager/lib/remote-machine-proxy.js +143 -9
  232. package/template/coding-task-manager/lib/remote-session-forward.js +65 -7
  233. package/template/coding-task-manager/lib/remote-terminal-target.js +9 -2
  234. package/template/coding-task-manager/lib/restart-guard.js +18 -6
  235. package/template/coding-task-manager/lib/restore-seed-modes.js +87 -0
  236. package/template/coding-task-manager/lib/restored-scrollback-hydration-runtime.js +22 -2
  237. package/template/coding-task-manager/lib/resume-admission.js +17 -3
  238. package/template/coding-task-manager/lib/review-repo-registry.js +58 -19
  239. package/template/coding-task-manager/lib/root-delivery-runtime.js +2 -2
  240. package/template/coding-task-manager/lib/root-prompt-flow.js +1 -1
  241. package/template/coding-task-manager/lib/runtime-contract.js +3 -1
  242. package/template/coding-task-manager/lib/schema-indexes.js +1 -0
  243. package/template/coding-task-manager/lib/session-context-locator.js +742 -0
  244. package/template/coding-task-manager/lib/session-create/attachment.js +2 -2
  245. package/template/coding-task-manager/lib/session-create/create-phases.js +118 -0
  246. package/template/coding-task-manager/lib/session-create/delegate-worker.js +86 -16
  247. package/template/coding-task-manager/lib/session-create/dsh-session.js +930 -0
  248. package/template/coding-task-manager/lib/session-create/lead-route-launch.js +186 -13
  249. package/template/coding-task-manager/lib/session-create/native-lead-lifetime.js +55 -3
  250. package/template/coding-task-manager/lib/session-create/opencode-lead-provider.js +50 -0
  251. package/template/coding-task-manager/lib/session-create/prepared.js +162 -38
  252. package/template/coding-task-manager/lib/session-create/provider-process.js +160 -28
  253. package/template/coding-task-manager/lib/session-create/response.js +7 -7
  254. package/template/coding-task-manager/lib/session-create/structured-session.js +27 -0
  255. package/template/coding-task-manager/lib/session-create/subagent-pick-table.js +3 -1
  256. package/template/coding-task-manager/lib/session-create/subagent-pick.js +458 -9
  257. package/template/coding-task-manager/lib/session-create/transcript.js +1 -1
  258. package/template/coding-task-manager/lib/session-create/worker-budget-setup.js +21 -2
  259. package/template/coding-task-manager/lib/session-create/worker-continuation.js +7 -1
  260. package/template/coding-task-manager/lib/session-create/worker-launch-sandbox.js +39 -10
  261. package/template/coding-task-manager/lib/session-create/worker-observability.js +2 -2
  262. package/template/coding-task-manager/lib/session-empty.js +13 -10
  263. package/template/coding-task-manager/lib/session-host-manager.js +60 -0
  264. package/template/coding-task-manager/lib/session-jobs.js +3 -1
  265. package/template/coding-task-manager/lib/session-lifecycle.js +10 -4
  266. package/template/coding-task-manager/lib/session-model-action-preconditions.js +6 -2
  267. package/template/coding-task-manager/lib/session-model-intent-contract.js +11 -8
  268. package/template/coding-task-manager/lib/session-park.js +738 -0
  269. package/template/coding-task-manager/lib/session-path-grant.js +13 -4
  270. package/template/coding-task-manager/lib/session-status-ledger.js +106 -4
  271. package/template/coding-task-manager/lib/session-transport.js +17 -0
  272. package/template/coding-task-manager/lib/session-workspace-projection.js +54 -5
  273. package/template/coding-task-manager/lib/session-workspace-tracking-runtime.js +213 -1
  274. package/template/coding-task-manager/lib/skill-autocomplete-query.js +120 -3
  275. package/template/coding-task-manager/lib/skill-autocomplete.js +15 -1
  276. package/template/coding-task-manager/lib/sqlite-file-set.js +39 -0
  277. package/template/coding-task-manager/lib/standup-search-adapter.js +16 -2
  278. package/template/coding-task-manager/lib/state-sync/client-ledger.js +12 -0
  279. package/template/coding-task-manager/lib/state-sync/frame-emitter.js +319 -100
  280. package/template/coding-task-manager/lib/storage/coding-session-model-store.js +4 -1
  281. package/template/coding-task-manager/lib/storage/delegate-pref-mirror-store.js +32 -9
  282. package/template/coding-task-manager/lib/structured-session-boot-sweep.js +235 -0
  283. package/template/coding-task-manager/lib/submission-ack.js +90 -2
  284. package/template/coding-task-manager/lib/systemd-scope.js +115 -0
  285. package/template/coding-task-manager/lib/telemetry-receiver.js +39 -0
  286. package/template/coding-task-manager/lib/terminal-attach-geometry.js +985 -0
  287. package/template/coding-task-manager/lib/terminal-bootstrap.js +30 -0
  288. package/template/coding-task-manager/lib/terminal-draft-operation.js +357 -0
  289. package/template/coding-task-manager/lib/terminal-input-admission.js +17 -1
  290. package/template/coding-task-manager/lib/terminal-input-prepared.js +563 -0
  291. package/template/coding-task-manager/lib/transcript-cache-temps.js +171 -0
  292. package/template/coding-task-manager/lib/transcript-frame-cache.js +8 -3
  293. package/template/coding-task-manager/lib/transcript-source.js +36 -4
  294. package/template/coding-task-manager/lib/transcript-store.js +9 -0
  295. package/template/coding-task-manager/lib/typing-interaction-trace.js +106 -3
  296. package/template/coding-task-manager/lib/usage-ledger/backfill.js +40 -5
  297. package/template/coding-task-manager/lib/usage-ledger/client.js +4 -1
  298. package/template/coding-task-manager/lib/usage-ledger/dsh-parse.js +152 -0
  299. package/template/coding-task-manager/lib/usage-ledger/dsh-read.js +73 -0
  300. package/template/coding-task-manager/lib/usage-ledger/ingest.js +34 -6
  301. package/template/coding-task-manager/lib/usage-ledger/live.js +35 -8
  302. package/template/coding-task-manager/lib/usage-ledger/read.js +29 -20
  303. package/template/coding-task-manager/lib/usage-ledger/schema.js +61 -1
  304. package/template/coding-task-manager/lib/usage-ledger/task-coordinator.js +4 -2
  305. package/template/coding-task-manager/lib/wake-skip-reasons.js +1 -0
  306. package/template/coding-task-manager/lib/walle/turn.js +1 -1
  307. package/template/coding-task-manager/lib/walle-history-serve.js +838 -0
  308. package/template/coding-task-manager/lib/walle-kill-authority.js +2 -2
  309. package/template/coding-task-manager/lib/walle-log-rotation.js +164 -1
  310. package/template/coding-task-manager/lib/walle-mcp-auto-config.js +56 -2
  311. package/template/coding-task-manager/lib/walle-row-history.js +495 -0
  312. package/template/coding-task-manager/lib/walle-supervisor.js +33 -14
  313. package/template/coding-task-manager/lib/watch-worker-client.js +203 -0
  314. package/template/coding-task-manager/lib/worker-terminal-output.js +5 -2
  315. package/template/coding-task-manager/lib/ws/hub-bridge.js +35 -0
  316. package/template/coding-task-manager/lib/ws/lifecycle.js +6 -11
  317. package/template/coding-task-manager/lib/ws/misc.js +1 -1
  318. package/template/coding-task-manager/lib/ws/state-sync.js +106 -4
  319. package/template/coding-task-manager/lib/ws/terminal-attach.js +91 -0
  320. package/template/coding-task-manager/oauth-proxy.js +112 -115
  321. package/template/coding-task-manager/package-lock.json +10 -94
  322. package/template/coding-task-manager/package.json +4 -3
  323. package/template/coding-task-manager/protocol/message-table.json +10 -0
  324. package/template/coding-task-manager/protocol/messages.js +10 -0
  325. package/template/coding-task-manager/protocol/ws-schema.json +20 -0
  326. package/template/coding-task-manager/protocol/ws-schema.md +2 -0
  327. package/template/coding-task-manager/public/css/app.css +170 -0
  328. package/template/coding-task-manager/public/css/walle-session.css +4 -0
  329. package/template/coding-task-manager/public/index.html +473 -243
  330. package/template/coding-task-manager/public/js/agent-command.js +86 -0
  331. package/template/coding-task-manager/public/js/app/backups-panel.js +111 -5
  332. package/template/coding-task-manager/public/js/app/coding-agent-upgrade.js +103 -4
  333. package/template/coding-task-manager/public/js/app/machines-page.js +1328 -8
  334. package/template/coding-task-manager/public/js/app/models-tab.js +113 -38
  335. package/template/coding-task-manager/public/js/app/restart-banner.js +53 -0
  336. package/template/coding-task-manager/public/js/app/worktrees-panel.js +184 -2
  337. package/template/coding-task-manager/public/js/background-work.js +196 -27
  338. package/template/coding-task-manager/public/js/coding-session-model-presentation.js +138 -10
  339. package/template/coding-task-manager/public/js/core/active-session-groups.js +179 -0
  340. package/template/coding-task-manager/public/js/core/pending-create.js +238 -0
  341. package/template/coding-task-manager/public/js/core/recent-machine-source.js +228 -0
  342. package/template/coding-task-manager/public/js/core/session-commands.js +180 -14
  343. package/template/coding-task-manager/public/js/dashboard-client-function-order.json +8 -0
  344. package/template/coding-task-manager/public/js/document-review-links.js +9 -1
  345. package/template/coding-task-manager/public/js/file-resolution-client.js +52 -1
  346. package/template/coding-task-manager/public/js/history-app-seam.js +22 -2
  347. package/template/coding-task-manager/public/js/machine-pool-shared.js +558 -3
  348. package/template/coding-task-manager/public/js/message-renderer.js +4 -3
  349. package/template/coding-task-manager/public/js/model-picker.js +3 -1
  350. package/template/coding-task-manager/public/js/native-subagent-review.js +15 -2
  351. package/template/coding-task-manager/public/js/new-session-agent-grid.js +346 -0
  352. package/template/coding-task-manager/public/js/pasted-content.js +236 -0
  353. package/template/coding-task-manager/public/js/pe-private-content-lifetime.js +39 -7
  354. package/template/coding-task-manager/public/js/private-model-lifetime.js +6 -2
  355. package/template/coding-task-manager/public/js/prompts.js +11 -10
  356. package/template/coding-task-manager/public/js/protocol.js +10 -0
  357. package/template/coding-task-manager/public/js/restart-banner-policy.js +29 -1
  358. package/template/coding-task-manager/public/js/session-model-picker.js +447 -57
  359. package/template/coding-task-manager/public/js/session-search-utils.js +18 -2
  360. package/template/coding-task-manager/public/js/session-usage-view.js +2 -1
  361. package/template/coding-task-manager/public/js/session-workspace-tracking.js +51 -1
  362. package/template/coding-task-manager/public/js/state-sync-client.js +10 -1
  363. package/template/coding-task-manager/public/js/stream-view.js +6 -5
  364. package/template/coding-task-manager/public/js/terminal/conv-only-session.js +287 -0
  365. package/template/coding-task-manager/public/js/terminal/runtime-09.js +2 -0
  366. package/template/coding-task-manager/public/js/terminal/runtime-10.js +5 -5
  367. package/template/coding-task-manager/public/js/terminal/runtime-12.js +8 -1
  368. package/template/coding-task-manager/public/js/terminal/runtime-16.js +4 -0
  369. package/template/coding-task-manager/public/js/terminal/runtime-20.js +1 -1
  370. package/template/coding-task-manager/public/js/terminal-attachment-draft.js +67 -13
  371. package/template/coding-task-manager/public/js/terminal-image-orchestration.js +52 -6
  372. package/template/coding-task-manager/public/js/terminal-wheel.js +23 -3
  373. package/template/coding-task-manager/public/js/walle/chat-conversation.js +4 -4
  374. package/template/coding-task-manager/public/js/walle.js +6 -0
  375. package/template/coding-task-manager/public/js/ws/live-dispatch.js +10 -0
  376. package/template/coding-task-manager/public/m/app.css +140 -0
  377. package/template/coding-task-manager/public/m/app.js +507 -68
  378. package/template/coding-task-manager/public/m/index.html +17 -0
  379. package/template/coding-task-manager/public/m/machines.js +838 -4
  380. package/template/coding-task-manager/public/m/private-api.js +31 -1
  381. package/template/coding-task-manager/public/usage.html +1 -0
  382. package/template/coding-task-manager/scripts/dev-route-fixture/synthetic-route-upstream.js +104 -0
  383. package/template/coding-task-manager/scripts/dev-route-fixture.js +339 -0
  384. package/template/coding-task-manager/scripts/opencode-lead-route-probe.js +195 -0
  385. package/template/coding-task-manager/scripts/opencode-v2-contract-probe/fake-anthropic.js +168 -0
  386. package/template/coding-task-manager/scripts/opencode-v2-contract-probe/probe-plugin/server.js +95 -0
  387. package/template/coding-task-manager/scripts/opencode-v2-contract-probe.js +673 -0
  388. package/template/coding-task-manager/server.js +1950 -11679
  389. package/template/coding-task-manager/session-integrity.js +64 -4
  390. package/template/coding-task-manager/workers/db-owner-worker.js +19 -2
  391. package/template/coding-task-manager/workers/online-backup-worker.js +1 -0
  392. package/template/coding-task-manager/workers/read-pool-model-collector.js +51 -3
  393. package/template/coding-task-manager/workers/read-pool-worker.js +76 -6
  394. package/template/coding-task-manager/workers/session-host-pool-process.js +6 -6
  395. package/template/coding-task-manager/workers/session-host-process.js +5 -5
  396. package/template/coding-task-manager/workers/watch-worker.js +105 -0
  397. package/template/coding-task-manager/workers/worktree-worker.js +168 -33
  398. package/template/ctm-relay/lib/protocol.js +14 -1
  399. package/template/package-lock.json +2 -2
  400. package/template/package.json +1 -1
  401. package/template/shared/backup-retention.js +82 -0
  402. package/template/shared/conversation-admission.js +2 -0
  403. package/template/shared/conversation-provenance.js +3 -7
  404. package/template/shared/conversation-storage-v2-contract.js +5 -0
  405. package/template/shared/linked-worktree.js +51 -0
  406. package/template/shared/macos-fda-target.js +57 -0
  407. package/template/shared/orphan-parents.js +55 -0
  408. package/template/shared/release-schema.json +2 -2
  409. package/template/shared/storage/contract.js +3 -0
  410. package/template/wall-e/adapters/imessage.js +12 -0
  411. package/template/wall-e/agent.js +71 -4
  412. package/template/wall-e/api-walle.js +21 -1
  413. package/template/wall-e/bin/train-gemma-e4b-tooluse.js +2 -2
  414. package/template/wall-e/bin/walle-mcp-stdio.js +5 -5
  415. package/template/wall-e/brain/brain-join.js +859 -0
  416. package/template/wall-e/brain/brain-sync-membership.js +20 -0
  417. package/template/wall-e/brain/brain-sync-store.js +131 -77
  418. package/template/wall-e/brain/brain-sync-v2-schema.js +203 -0
  419. package/template/wall-e/brain/brain-sync-v2.js +578 -0
  420. package/template/wall-e/brain/delegate-model-prefs-store.js +13 -4
  421. package/template/wall-e/brain/deterministic-id.js +51 -0
  422. package/template/wall-e/brain/memories.js +2 -2
  423. package/template/wall-e/brain/migrations.js +40 -6
  424. package/template/wall-e/brain/model-identity-migration.js +1 -1
  425. package/template/wall-e/brain/owner-call-whitelist.js +23 -7
  426. package/template/wall-e/brain/schema.js +45 -85
  427. package/template/wall-e/brain.js +171 -35
  428. package/template/wall-e/chat.js +3 -3
  429. package/template/wall-e/coding/agent-shell-env.js +7 -2
  430. package/template/wall-e/coding/prompt-bundle.js +66 -21
  431. package/template/wall-e/config/main-loop-exemptions.json +2 -2
  432. package/template/wall-e/http/admin-api.js +37 -0
  433. package/template/wall-e/http/gws-api.js +4 -0
  434. package/template/wall-e/http/helper-rules-api.js +7 -1
  435. package/template/wall-e/http/model-admin.js +8 -1
  436. package/template/wall-e/http/model-delegate-pref-admin.js +3 -3
  437. package/template/wall-e/http/model-proxy-providers.js +174 -1
  438. package/template/wall-e/http/model-route-health.js +157 -0
  439. package/template/wall-e/http/sync-api.js +278 -464
  440. package/template/wall-e/jobs/job-runner.js +28 -1
  441. package/template/wall-e/lib/brain-owner-op-priority.js +9 -1
  442. package/template/wall-e/lib/ctm-data-dir.js +26 -0
  443. package/template/wall-e/lib/derivation-home.js +147 -0
  444. package/template/wall-e/lib/helper-rules/evaluate.js +50 -9
  445. package/template/wall-e/lib/helper-rules/grade.js +51 -8
  446. package/template/wall-e/lib/helper-rules/lead-call-hook.js +38 -5
  447. package/template/wall-e/lib/helper-rules/store.js +4 -2
  448. package/template/wall-e/lib/landing-mcp-tools.js +170 -0
  449. package/template/wall-e/lib/mcp-client-config.js +133 -14
  450. package/template/wall-e/lib/mcp-gateway-migrate.js +28 -64
  451. package/template/wall-e/lib/mcp-integration.js +58 -13
  452. package/template/wall-e/lib/parent-death.js +7 -1
  453. package/template/wall-e/lib/scheduler-worker-jobs.js +6 -4
  454. package/template/wall-e/llm/delegate-lead-prefs.js +16 -1
  455. package/template/wall-e/llm/delegate-vendor-default.js +12 -5
  456. package/template/wall-e/llm/model-roles.js +1 -1
  457. package/template/wall-e/llm/native-delegate-selection.js +56 -8
  458. package/template/wall-e/llm/worker-delegate-args.js +72 -7
  459. package/template/wall-e/llm/worker-mcp-routing.js +66 -17
  460. package/template/wall-e/loops/brain-custom-writes.js +19 -0
  461. package/template/wall-e/loops/ingest.js +17 -1
  462. package/template/wall-e/loops/think.js +19 -4
  463. package/template/wall-e/mcp-contract.js +222 -6
  464. package/template/wall-e/mcp-protocol-contract.js +40 -5
  465. package/template/wall-e/mcp-server.js +128 -29
  466. package/template/wall-e/mcp-worker-schemas.js +5 -5
  467. package/template/wall-e/memory/ctm-context-client.js +85 -0
  468. package/template/wall-e/memory/ctm-session-context.js +9 -3
  469. package/template/wall-e/memory/hub-bridge-client.js +388 -0
  470. package/template/wall-e/memory/replica-origin.js +56 -0
  471. package/template/wall-e/memory/session-pool.js +135 -0
  472. package/template/wall-e/runtime/live-capabilities.js +8 -3
  473. package/template/wall-e/server.js +4 -2
  474. package/template/wall-e/shared/release-schema.json +2 -2
  475. package/template/wall-e/shared/sqlite-file-set.js +39 -0
  476. package/template/wall-e/shared/storage/contract.js +3 -0
  477. package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
  478. package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +7 -1
  479. package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +4 -4
  480. package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +6 -6
  481. package/template/wall-e/skills/_templates/fleet-sync/VERSION +1 -0
  482. package/template/wall-e/skills/_templates/fleet-sync/released-sha256.json +16 -0
  483. package/template/wall-e/skills/_templates/fleet-sync/templates/claude/SKILL.md +64 -0
  484. package/template/wall-e/skills/_templates/fleet-sync/templates/codex/SKILL.md +64 -0
  485. package/template/wall-e/tools/chrome-automation.js +6 -0
  486. package/template/wall-e/training/setup-cuda-train-env.sh +2 -2
  487. package/template/wall-e/workers/brain-owner-worker.js +18 -4
  488. package/template/wall-e/workers/mcp-gateway-process.js +5 -5
@@ -15,8 +15,12 @@ const path = require('path');
15
15
  const fs = require('fs');
16
16
  const os = require('os');
17
17
  const { createMigrationRunner, pragmaVersionStore, MigrationFailedError } = require('./shared/db-migration-runner');
18
+ const { removeSqliteFileSet } = require('./shared/sqlite-file-set');
19
+ // Shared backup-retention policy (R-1..R-6): one pure module both CTM and Wall-E call.
20
+ const backupRetention = require('../shared/backup-retention');
18
21
  const crypto = require('crypto');
19
22
  const { randomUUID: uuidv4 } = require('node:crypto');
23
+ const { deterministicEntityId } = require('./brain/deterministic-id'); // WP-0 §5 D-4: cross-machine entity identity
20
24
  const { isMainThread, threadId } = require('node:worker_threads');
21
25
  const { EventEmitter } = require('events');
22
26
  const { createWriteAuditLog } = require('./db/write-audit');
@@ -107,6 +111,7 @@ function _isProtectedDevContext() {
107
111
  if (process.env.NODE_ENV === 'test') return 'NODE_ENV=test';
108
112
  if (/^test/.test(process.env.npm_lifecycle_event || '')) return 'npm_lifecycle_event';
109
113
  if ([...process.execArgv, ...process.argv.slice(1)].some((a) => a === '--test')) return 'node --test';
114
+ if (require('../shared/linked-worktree').isLinkedWorktreeCheckout(__dirname)) return 'linked git worktree (unmerged branch code)'; // 2026-10-31 v59 incident
110
115
  return null;
111
116
  }
112
117
 
@@ -365,6 +370,22 @@ function _resetCloneSensitiveCursors(d) {
365
370
  return reset;
366
371
  }
367
372
 
373
+ // WP-0 (§5 D-4, §6.1): publish this host's own instance id to the v2 clock tables, so the
374
+ // capture triggers can stamp an addressable (origin_instance_id, origin_seq) without a kv
375
+ // lookup on every write (they read brain_local_identity, and fall back to '' — the v1
376
+ // "this host" convention — until this row exists). Host-local by construction: the row is
377
+ // never replicated and never part of a snapshot join (D-7). Runs after the identity step, so
378
+ // the id it reads is the one this boot resolved.
379
+ function _ensureBrainLocalClockIdentity(d) {
380
+ if (!_tableExists(d, 'brain_local_identity')) return;
381
+ const instanceId = require('./brain/brain-sync-store').localBrainInstanceId(d);
382
+ if (!instanceId) return;
383
+ d.prepare(`
384
+ INSERT INTO brain_local_identity (id, instance_id) VALUES (1, ?)
385
+ ON CONFLICT(id) DO UPDATE SET instance_id = excluded.instance_id
386
+ `).run(instanceId);
387
+ }
388
+
368
389
  function _ensureBrainHostIdentity(d, dbPath) {
369
390
  const identityContext = _brainHostIdentityContext(dbPath);
370
391
  const dataDir = identityContext.dataDir;
@@ -546,6 +567,23 @@ function getBackupDirInfo() {
546
567
  };
547
568
  }
548
569
 
570
+ function getBackupRetentionInfo() {
571
+ let stored = null;
572
+ try { if (db) stored = getKv(backupRetention.BACKUP_RETENTION_SETTING_KEY); } catch { /* expected: kv_store not yet migrated on a fresh/old brain — the default retention applies */ }
573
+ return {
574
+ retention_days: backupRetention.normalizeRetentionDays(stored),
575
+ default_retention_days: backupRetention.DEFAULT_BACKUP_RETENTION_DAYS,
576
+ min: backupRetention.MIN_BACKUP_RETENTION_DAYS,
577
+ max: backupRetention.MAX_BACKUP_RETENTION_DAYS,
578
+ source: (stored !== null && stored !== undefined && stored !== '') ? 'settings' : 'default',
579
+ };
580
+ }
581
+
582
+ function setBackupRetentionDays(days) {
583
+ setKv(backupRetention.BACKUP_RETENTION_SETTING_KEY, String(backupRetention.normalizeRetentionDays(days)));
584
+ return getBackupRetentionInfo();
585
+ }
586
+
549
587
  function _applyPersistedBackupDirSetting() {
550
588
  try {
551
589
  if (!db) return;
@@ -1990,6 +2028,8 @@ function initDb(dbPath) {
1990
2028
  _bootSchemaVersion = Number(newDb.pragma('user_version', { simple: true }));
1991
2029
  }
1992
2030
  boot.measure('initDb.hostIdentity', () => _retryBusySync(() => _ensureBrainHostIdentity(newDb, dbPath), bootLockWaitMs));
2031
+ // WP-0 §6.1: the v2 clock's copy of the identity this boot just resolved (host-local row).
2032
+ boot.measure('initDb.brainLocalIdentity', () => _retryBusySync(() => _ensureBrainLocalClockIdentity(newDb), bootLockWaitMs));
1993
2033
  // R-6: a responder mode configured BEFORE the db was open (agent.js reads the config
1994
2034
  // ahead of initDb) lands AFTER the identity step — so explicit config overrides a
1995
2035
  // clone verdict made in this same boot, and the owner sees the row on its next read.
@@ -3016,37 +3056,115 @@ function _quickCheckBackupFile(filePath) {
3016
3056
  return _quickCheckFile(filePath);
3017
3057
  }
3018
3058
 
3019
- async function createBackup(label) {
3020
- getDb(); // throws if not initialized
3021
- const backupDir = _backupDirForCurrentDb();
3059
+ // A backup runs for minutes, but shutdown forces the DB closed after 1 s and terminates the
3060
+ // process after 1.5 s (agent.js:2069-2070 via workers/brain-owner-worker.js:637-638), so a
3061
+ // backup killed mid-flight runs NO cleanup at all. The dot-name is invisible to listBackups()
3062
+ // and cleanOldBackups() (both filter on the non-dot `wall-e-brain-` prefix), so those strands
3063
+ // accumulated unseen — 6.6 GB observed on 2026-10-25. The owner pid in the name is the whole
3064
+ // ownership record: a dead pid's set is garbage no reader can use, and this process's own
3065
+ // leftovers are stale unless they are the caller's live tmpPath. Non-dot names are never touched.
3066
+ const STALE_BACKUP_TEMP_RE = /^\.wall-e-brain-.+\.db\.(\d+)\.tmp(-journal|-wal|-shm)?$/;
3022
3067
 
3023
- const timestamp = new Date().toISOString().replace(/:/g, '-');
3024
- const safeLabel = String(label || 'manual').replace(/[^a-z0-9_.-]+/gi, '-').replace(/^-+|-+$/g, '') || 'manual';
3025
- const backupName = `wall-e-brain-${timestamp}-${safeLabel}.db`;
3026
- const backupPath = path.join(backupDir, backupName);
3027
- const tmpPath = path.join(backupDir, `.${backupName}.${process.pid}.tmp`);
3068
+ // Temps this process is writing RIGHT NOW. createBackup registers its tmpPath here for the whole
3069
+ // write (added before the sweep, removed in a finally), so an overlapping createBackup in the same
3070
+ // process — ensureDailyBackup plus a prompt-purge or a manual backup — cannot sweep the other
3071
+ // call's live file: unlinking it would let better-sqlite3 keep copying into a dead inode and the
3072
+ // in-flight backup would then fail at its rename.
3073
+ const _inFlightBackupTemps = new Set();
3028
3074
 
3029
- if (!fs.existsSync(backupDir)) fs.mkdirSync(backupDir, { recursive: true });
3030
- fs.rmSync(tmpPath, { force: true });
3075
+ function _isProcessAliveByPid(pid) {
3031
3076
  try {
3032
- // Online incremental backup. better-sqlite3's db.backup() copies the database in
3033
- // page batches and YIELDS to the event loop between them (default 100 pages/batch),
3034
- // so a multi-GB brain never freezes the loop — unlike the old synchronous
3035
- // `VACUUM INTO`, which blocked for 30-120s. Like VACUUM INTO it reads a consistent
3036
- // snapshot via a read transaction and does not force a WAL checkpoint that would
3037
- // perturb peer processes; SQLite transparently re-copies any pages a peer writes
3038
- // mid-backup. The quick_check below validates the produced image before we keep it.
3077
+ process.kill(pid, 0);
3078
+ return true;
3079
+ } catch (err) {
3080
+ // EPERM means the pid exists but is owned by another user — alive, not reclaimable.
3081
+ return err && err.code === 'EPERM';
3082
+ }
3083
+ }
3084
+
3085
+ /** Reclaim dot-temps stranded in `backupDir` by an earlier backup run.
3086
+ * `keepPath` (this call's own tmpPath) is exempted; everything else whose name carries a dead
3087
+ * pid, or this process's pid, is unlinked. Returns the number of files removed. */
3088
+ function sweepStaleBackupTemps(backupDir, { keepPath = null, isProcessAlive } = {}) {
3089
+ if (!backupDir || !fs.existsSync(backupDir)) return 0;
3090
+ const alive = typeof isProcessAlive === 'function' ? isProcessAlive : _isProcessAliveByPid;
3091
+ const keepBase = keepPath ? path.basename(keepPath) : null;
3092
+ let removed = 0;
3093
+ for (const name of fs.readdirSync(backupDir)) {
3094
+ const match = STALE_BACKUP_TEMP_RE.exec(name);
3095
+ if (!match) continue;
3096
+ const pid = Number(match[1]);
3097
+ const base = name.slice(0, name.length - (match[2] || '').length);
3098
+ if (pid === process.pid) {
3099
+ // This process's own temps: never unlink `keepPath`, and never unlink a temp another
3100
+ // createBackup call in this process is still writing (see _inFlightBackupTemps).
3101
+ if (keepBase !== null && base === keepBase) continue;
3102
+ if (_inFlightBackupTemps.has(path.resolve(backupDir, base))) continue;
3103
+ } else if (alive(pid)) {
3104
+ continue; // a live peer may still be writing it
3105
+ }
3106
+ try {
3107
+ fs.unlinkSync(path.join(backupDir, name));
3108
+ removed += 1;
3109
+ } catch (err) {
3110
+ if (err && err.code === 'ENOENT') continue; // HANDLE: a peer swept the same temp first
3111
+ throw err; // RETHROW: a real unlink failure must not be swallowed
3112
+ }
3113
+ }
3114
+ return removed;
3115
+ }
3116
+
3117
+ // WP-4 (§5 D-6, §6.7 P-1): the consistent-snapshot primitive shared by the daily/manual backup
3118
+ // and the join snapshot route. Copies the LIVE brain to `destPath` through better-sqlite3's online
3119
+ // backup API — page batches that yield to the event loop (default 100 pages/batch), so a multi-GB
3120
+ // brain never freezes the loop, unlike the old synchronous `VACUUM INTO` (30-120s blocked). Like
3121
+ // VACUUM INTO it reads a consistent snapshot via a read transaction and does not force a WAL
3122
+ // checkpoint that would perturb peer processes; SQLite transparently re-copies any pages a peer
3123
+ // writes mid-backup. The image is written to a dot-temp beside the destination and validated with
3124
+ // quick_check before it is renamed into place, so a killed run strands a dot-temp (reclaimed by
3125
+ // sweepStaleBackupTemps), never a half-written snapshot. Returns { path }.
3126
+ async function createConsistentSnapshot(destPath) {
3127
+ getDb(); // throws if not initialized
3128
+ const dest = path.resolve(destPath);
3129
+ const dir = path.dirname(dest);
3130
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
3131
+ const tmpPath = path.join(dir, `.${path.basename(dest)}.${process.pid}.tmp`);
3132
+
3133
+ // Claim this tmpPath before anything touches the dir: the sweep below (and any overlapping
3134
+ // snapshot's own sweep) must skip a temp this process is still writing.
3135
+ _inFlightBackupTemps.add(path.resolve(tmpPath));
3136
+ try {
3137
+ // Reclaim anything an earlier run stranded here BEFORE writing: a temp left by a killed run
3138
+ // is the leak this exists for (D-6), and this run's own tmpPath is removed either way.
3139
+ sweepStaleBackupTemps(dir, { keepPath: null });
3140
+ removeSqliteFileSet(tmpPath);
3039
3141
  await getDb().backup(tmpPath);
3040
3142
  const check = _quickCheckBackupFile(tmpPath);
3041
3143
  if (!check.ok) {
3042
3144
  throw new Error(`backup quick_check failed: ${check.error}`);
3043
3145
  }
3044
- fs.renameSync(tmpPath, backupPath);
3146
+ fs.renameSync(tmpPath, dest);
3045
3147
  } catch (err) {
3046
- try { fs.rmSync(tmpPath, { force: true }); } catch { /* expected: tmp backup never materialised when getDb().backup() failed before writing; the backup error is rethrown */ }
3148
+ try { removeSqliteFileSet(tmpPath); } catch { /* expected: tmp snapshot never materialised when getDb().backup() failed before writing; the snapshot error is rethrown */ }
3047
3149
  throw err;
3150
+ } finally {
3151
+ _inFlightBackupTemps.delete(path.resolve(tmpPath));
3048
3152
  }
3049
3153
 
3154
+ return { path: dest };
3155
+ }
3156
+
3157
+ async function createBackup(label) {
3158
+ getDb(); // throws if not initialized
3159
+ const backupDir = _backupDirForCurrentDb();
3160
+
3161
+ const timestamp = new Date().toISOString().replace(/:/g, '-');
3162
+ const safeLabel = String(label || 'manual').replace(/[^a-z0-9_.-]+/gi, '-').replace(/^-+|-+$/g, '') || 'manual';
3163
+ const backupName = `wall-e-brain-${timestamp}-${safeLabel}.db`;
3164
+ const backupPath = path.join(backupDir, backupName);
3165
+
3166
+ await createConsistentSnapshot(backupPath);
3167
+
3050
3168
  cleanOldBackups(backupDir);
3051
3169
 
3052
3170
  return { backupName, backupPath, timestamp };
@@ -3065,8 +3183,8 @@ function listBackups() {
3065
3183
  }
3066
3184
 
3067
3185
  function deleteBackup(backupName) {
3068
- const p = path.join(_backupDirForCurrentDb(), path.basename(backupName));
3069
- if (fs.existsSync(p)) fs.unlinkSync(p);
3186
+ // The whole file set goes: a kept `-wal`/`-shm` beside a deleted base is an unreadable orphan.
3187
+ removeSqliteFileSet(path.join(_backupDirForCurrentDb(), path.basename(backupName)));
3070
3188
  }
3071
3189
 
3072
3190
  function _assertPromptPurgeBackupInventoryIsLocal() {
@@ -3142,17 +3260,18 @@ function restoreAndVerifyPromptPurgeBackupAbsent(baseline = {}, closure = {}) {
3142
3260
 
3143
3261
  function cleanOldBackups(backupDir = BACKUP_DIR) {
3144
3262
  if (!fs.existsSync(backupDir)) return;
3145
- const files = fs.readdirSync(backupDir);
3146
- const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;
3147
-
3148
- // Always keep at least 3
3149
- const brainFiles = files.filter(f => f.startsWith('wall-e-brain-') && f.endsWith('.db')).sort().reverse();
3150
- for (const file of brainFiles.slice(3)) {
3151
- const filePath = path.join(backupDir, file);
3152
- const stat = fs.statSync(filePath);
3153
- if (stat.mtimeMs < cutoff) {
3154
- fs.unlinkSync(filePath);
3155
- }
3263
+ const rows = [];
3264
+ for (const name of fs.readdirSync(backupDir)) {
3265
+ // Non-dot `wall-e-brain-*.db` only, the same filter listBackups() uses (dot names are -tmp).
3266
+ if (!name.startsWith('wall-e-brain-') || !name.endsWith('.db')) continue;
3267
+ try {
3268
+ rows.push({ name, createdAt: fs.statSync(path.join(backupDir, name)).mtime });
3269
+ } catch { /* expected: the base vanished between readdir and stat — a concurrent backup/delete; a missing row is simply not a prune candidate */ }
3270
+ }
3271
+ const days = getBackupRetentionInfo().retention_days;
3272
+ // R-1..R-3: keep EVERY backup from the N most recent UTC days that have one; remove the rest as a set.
3273
+ for (const row of backupRetention.selectExpiredBackups(rows, days)) {
3274
+ removeSqliteFileSet(path.join(backupDir, row.name));
3156
3275
  }
3157
3276
  }
3158
3277
 
@@ -3232,11 +3351,21 @@ function updatePerson(id, updates) {
3232
3351
  function insertEntity({ canonical_name, entity_type, aliases, properties }) {
3233
3352
  if (!canonical_name) throw new Error('Entity requires canonical_name');
3234
3353
  if (!entity_type) throw new Error('Entity requires entity_type');
3235
- const id = uuidv4();
3236
- getDb().prepare(`
3237
- INSERT INTO entities (id, canonical_name, entity_type, aliases, properties)
3354
+ // WP-0 (§5 D-4): the id is derived from (lower(canonical_name), entity_type) — the same
3355
+ // entity extracted on two machines is the same row, and two spellings that differ only in
3356
+ // case are the same entity by construction. INSERT OR IGNORE makes the second insert a
3357
+ // no-op instead of a primary-key violation; the caller still gets the id of the live row.
3358
+ const id = deterministicEntityId(canonical_name, entity_type);
3359
+ const result = getDb().prepare(`
3360
+ INSERT OR IGNORE INTO entities (id, canonical_name, entity_type, aliases, properties)
3238
3361
  VALUES (?, ?, ?, ?, ?)
3239
3362
  `).run(id, canonical_name, entity_type, aliases || null, properties || null);
3363
+ if (result.changes === 0) {
3364
+ // The row is already there (this id, or a pre-WP-0 row that normalized to it). Returning
3365
+ // silently keeps the write log honest: nothing was inserted.
3366
+ const existing = getEntity(id) || findEntity(canonical_name);
3367
+ return { id: existing ? existing.id : id };
3368
+ }
3240
3369
  logWrite('insert', 'entities', { id, subject: canonical_name, category: entity_type });
3241
3370
  return { id };
3242
3371
  }
@@ -5187,6 +5316,8 @@ module.exports = {
5187
5316
  get BACKUP_DIR() { return _backupDirForCurrentDb(); },
5188
5317
  getBackupDirInfo,
5189
5318
  setBackupDir,
5319
+ getBackupRetentionInfo,
5320
+ setBackupRetentionDays,
5190
5321
  // Owner
5191
5322
  setOwner,
5192
5323
  getOwner,
@@ -5369,6 +5500,8 @@ module.exports = {
5369
5500
  // Backup
5370
5501
  checkpointWalOrThrow,
5371
5502
  createBackup,
5503
+ createConsistentSnapshot,
5504
+ sweepStaleBackupTemps,
5372
5505
  listBackups,
5373
5506
  deleteBackup,
5374
5507
  listPromptPurgeBackupVersions,
@@ -5575,6 +5708,9 @@ module.exports = {
5575
5708
  // underscore-prefixed, individual top-level exports.
5576
5709
  __testing: {
5577
5710
  _isProtectedDevContext, // WP-18: widened live-brain guard trigger, unit-tested directly
5711
+ // WP-2 (backup-retention-days): the prune is internal to createBackup; the retention tests
5712
+ // drive it directly against a real temp backup dir (the production call site is brain.js itself).
5713
+ cleanOldBackups,
5578
5714
  __wp7RunMigration54ForTest: MIGRATIONS[54], // D-9 rule-host hygiene, WP-7
5579
5715
  __wp2RunMigration23ForTest: MIGRATIONS[23], // rerunSafe:false both-ways proof, WP-2 db-migration-consolidation
5580
5716
  __wp3RunMigration58ForTest: MIGRATIONS[58], // rerunSafe:false both-ways proof, WP-3 db-migration-consolidation
@@ -2302,9 +2302,9 @@ async function chatUnaccounted(message, opts = {}) {
2302
2302
  }
2303
2303
  } catch (error) { errorSink.report('chat', 'user-signal-evaluation', error); }
2304
2304
 
2305
- // Configure permission checker with CTM connection if available
2306
- const ctmPort = process.env.CTM_PORT || require('./lib/ctm-endpoint').ctmPort();
2307
- configurePermissions({ ctmUrl: `http://localhost:${ctmPort}`, ctmToken: process.env.CTM_TOKEN });
2305
+ // Configure permission checker with CTM connection if available (R-7: shared resolver;
2306
+ // WALLE_CTM_API=0 disables the bridge and an explicit base URL still wins).
2307
+ configurePermissions({ ctmUrl: ctmContextClient.resolveApiBaseUrl(process.env) || null, ctmToken: process.env.CTM_TOKEN });
2308
2308
 
2309
2309
  let totalInputTokens = 0;
2310
2310
  let totalOutputTokens = 0;
@@ -47,10 +47,15 @@ function prepareClaudeAgentShellEnv(overrides = {}, { baseEnv = process.env, tmp
47
47
  const inheritedPrefix = env.CLAUDE_CODE_SHELL_PREFIX || '';
48
48
  // Plugins add their bin directories after Claude starts. Its documented shell
49
49
  // prefix runs at the final Bash execution boundary, after that plugin setup.
50
+ // The re-exec'd text overrides `eval()` and calls `builtin` (bash/zsh, not POSIX
51
+ // sh), so it runs in the user's SHELL only when that is bash or zsh, else bash:
52
+ // /bin/sh is dash on Debian/Ubuntu ("Syntax error: Bad function name", nightly
53
+ // 2026-10-27), and a service-launched process commonly has SHELL=/bin/sh.
50
54
  fs.writeFileSync(wrapperFile, [
51
55
  '#!/bin/sh',
56
+ 'case "${SHELL:-}" in */bash|*/zsh) WALLE_AGENT_EVAL_SHELL="$SHELL" ;; *) WALLE_AGENT_EVAL_SHELL=/bin/bash ;; esac',
52
57
  'if [ "${1:-}" = "--walle-run-original" ]; then',
53
- ` exec "${'${SHELL:-/bin/sh}'}" -c ${shellQuote([
58
+ ` exec "$WALLE_AGENT_EVAL_SHELL" -c ${shellQuote([
54
59
  `export PATH=${shellQuote(walleBin)}:"$PATH"`,
55
60
  `eval() { export PATH=${shellQuote(walleBin)}:"$PATH"; builtin eval "$@"; }`,
56
61
  'builtin eval "$WALLE_CLAUDE_ORIGINAL_COMMAND"',
@@ -59,7 +64,7 @@ function prepareClaudeAgentShellEnv(overrides = {}, { baseEnv = process.env, tmp
59
64
  '[ "$#" -eq 1 ] || exit 64',
60
65
  'export WALLE_CLAUDE_ORIGINAL_COMMAND="$1"',
61
66
  'if [ -n "${WALLE_INHERITED_CLAUDE_SHELL_PREFIX:-}" ]; then',
62
- ` exec "${'${SHELL:-/bin/sh}'}" -c ${shellQuote('builtin eval "$WALLE_INHERITED_CLAUDE_SHELL_PREFIX \\"$1\\""')} walle-shell-prefix ${shellQuote(`${wrapperFile} --walle-run-original`)}`,
67
+ ` exec "$WALLE_AGENT_EVAL_SHELL" -c ${shellQuote('builtin eval "$WALLE_INHERITED_CLAUDE_SHELL_PREFIX \\"$1\\""')} walle-shell-prefix ${shellQuote(`${wrapperFile} --walle-run-original`)}`,
63
68
  'fi',
64
69
  `exec ${shellQuote(wrapperFile)} --walle-run-original`,
65
70
  '',
@@ -89,53 +89,69 @@ function fitProjectInstructions(instructions, budgetBytes = PROJECT_INSTRUCTION_
89
89
  for (const input of instructions || []) {
90
90
  if (remaining <= 0) break;
91
91
  if (!input?.content) continue;
92
+ const ordinal = fitted.length + 1;
92
93
  if (input.renderedContent) {
93
- const emittedBytes = Buffer.byteLength(input.renderedContent, 'utf8');
94
- if (emittedBytes > remaining) break;
95
- fitted.push({ ...input, includedBytes: emittedBytes });
96
- remaining -= emittedBytes;
97
- continue;
94
+ // Already-fitted input (instruction-service hands its fitted frames back
95
+ // through here). Budget it on what rendering transmits, like any other
96
+ // frame; when it no longer fits the remaining cumulative budget, fall
97
+ // through and re-fit it rather than dropping the instruction.
98
+ const reused = measureTransmittedFrame(input.renderedContent, ordinal);
99
+ if (reused.bytes <= remaining && reused.intact) {
100
+ fitted.push({ ...input, includedBytes: reused.bytes });
101
+ remaining -= reused.bytes;
102
+ continue;
103
+ }
98
104
  }
99
- const ordinal = fitted.length + 1;
100
105
  const neutralized = neutralizeInstructionFrameContent(input.content, ordinal);
101
- const originalContentBytes = Buffer.byteLength(neutralized, 'utf8');
106
+ const contentBytes = Buffer.byteLength(neutralized, 'utf8');
107
+ // A re-fitted already-rendered input knows its own pre-truncation length:
108
+ // report that rather than the length of content an earlier fit cropped.
109
+ const declaredOriginalBytes = Math.floor(Number(input.originalContentBytes));
110
+ const originalContentBytes = Number.isFinite(declaredOriginalBytes) && declaredOriginalBytes > contentBytes
111
+ ? declaredOriginalBytes
112
+ : contentBytes;
102
113
  const sourceTruncated = Boolean(input.sourceTruncated || input.truncated);
103
114
  const full = buildProjectInstructionFrame(input, ordinal, neutralized, {
104
- contentBytes: originalContentBytes,
115
+ contentBytes,
105
116
  originalContentBytes,
106
117
  truncated: sourceTruncated,
107
118
  });
108
- const fullBytes = Buffer.byteLength(full, 'utf8');
109
- if (fullBytes <= remaining) {
119
+ const fullTransmitted = measureTransmittedFrame(full, ordinal);
120
+ if (fullTransmitted.bytes <= remaining && fullTransmitted.intact) {
110
121
  fitted.push({
111
122
  ...input,
112
123
  content: neutralized,
113
- contentBytes: originalContentBytes,
124
+ contentBytes,
114
125
  originalContentBytes,
115
- includedBytes: fullBytes,
116
- originalBytes: fullBytes,
126
+ includedBytes: fullTransmitted.bytes,
127
+ originalBytes: fullTransmitted.bytes,
117
128
  truncated: sourceTruncated,
118
129
  renderedContent: full,
119
130
  });
120
- remaining -= fullBytes;
131
+ remaining -= fullTransmitted.bytes;
121
132
  continue;
122
133
  }
123
134
 
124
135
  let low = 0;
125
- let high = Math.min(originalContentBytes, remaining);
136
+ let high = Math.min(contentBytes, remaining);
126
137
  let best = null;
127
138
  while (low <= high) {
128
139
  const target = Math.floor((low + high) / 2);
129
140
  const content = truncateUtf8(neutralized, target);
130
- const contentBytes = Buffer.byteLength(content, 'utf8');
141
+ const candidateBytes = Buffer.byteLength(content, 'utf8');
131
142
  const renderedContent = buildProjectInstructionFrame(input, ordinal, content, {
132
- contentBytes,
143
+ contentBytes: candidateBytes,
133
144
  originalContentBytes,
134
145
  truncated: true,
135
146
  });
136
- const emittedBytes = Buffer.byteLength(renderedContent, 'utf8');
137
- if (emittedBytes <= remaining) {
138
- best = { content, contentBytes, renderedContent, emittedBytes };
147
+ // The transmitted cost is not monotone in `target` — a cut inside a
148
+ // redaction marker is re-expanded by the renderer — so this is a bounded
149
+ // search for *a* fitting prefix, never a proof that it is the largest.
150
+ // Correctness only needs the accepted candidate to fit, which is checked
151
+ // on the measured bytes below.
152
+ const measured = measureTransmittedFrame(renderedContent, ordinal);
153
+ if (measured.bytes <= remaining && measured.intact) {
154
+ best = { content, contentBytes: candidateBytes, renderedContent, emittedBytes: measured.bytes };
139
155
  low = target + 1;
140
156
  } else {
141
157
  high = target - 1;
@@ -148,7 +164,7 @@ function fitProjectInstructions(instructions, budgetBytes = PROJECT_INSTRUCTION_
148
164
  contentBytes: best.contentBytes,
149
165
  originalContentBytes,
150
166
  includedBytes: best.emittedBytes,
151
- originalBytes: fullBytes,
167
+ originalBytes: fullTransmitted.bytes,
152
168
  truncated: true,
153
169
  renderedContent: best.renderedContent,
154
170
  });
@@ -157,6 +173,35 @@ function fitProjectInstructions(instructions, budgetBytes = PROJECT_INSTRUCTION_
157
173
  return fitted;
158
174
  }
159
175
 
176
+ /**
177
+ * What a project-instruction frame costs *in transmission*.
178
+ *
179
+ * `renderPromptEnvelope` re-applies each section's `redactionPolicy` on the way
180
+ * out (prompt-runtime.js renders `redactPromptContent(section.content, policy)`
181
+ * and section-creation here sets `redactionPolicy: 'secrets'`), so the bytes a
182
+ * frame actually spends are those of `redactPromptContent(frame)`, not those of
183
+ * the frame this module stored. That second pass is not a no-op on a truncated
184
+ * frame:
185
+ *
186
+ * - a cut inside a redaction marker leaves a fragment (`token=[REDAC`) that is
187
+ * not recognised as already redacted and is re-expanded (+4 bytes), and
188
+ * - a cut immediately after an assignment operator (`token=`) matches a value
189
+ * across the following newline and swallows the frame's own `--- END …`
190
+ * delimiter (secret-redaction.js's `\s*` crosses lines).
191
+ *
192
+ * Both are how a frame fitted to its pre-render byte count could be transmitted
193
+ * over budget or lose its framing, so the budget is enforced on this text and a
194
+ * candidate whose transmitted form has lost either delimiter is refused.
195
+ */
196
+ function measureTransmittedFrame(frame, ordinal) {
197
+ const transmitted = redactPromptContent(frame, 'secrets');
198
+ return {
199
+ bytes: Buffer.byteLength(transmitted, 'utf8'),
200
+ intact: transmitted.startsWith(`--- BEGIN PROJECT INSTRUCTION ${ordinal} ---`)
201
+ && transmitted.endsWith(`--- END PROJECT INSTRUCTION ${ordinal} ---`),
202
+ };
203
+ }
204
+
160
205
  function buildProjectInstructionFrame(item, ordinal, content, {
161
206
  contentBytes,
162
207
  originalContentBytes,
@@ -7,10 +7,10 @@
7
7
  "function": "telemetry child-process route implementation",
8
8
  "capability": "isolated_telemetry_sync_io",
9
9
  "owner": "wall-e-runtime",
10
- "reason": "These exact calls initialize and inspect the optional telemetry database after the daemon transfers telemetry and feedback requests to walle-telemetry-process. Re-reviewed 2026-10-14: handleWalleApi proxies daemon telemetry/feedback routes before local dispatch; TelemetryProcessClient forks telemetry-worker with the isolated role. The unchanged child implementation has exactly 3 synchronous filesystem sites (getTelemetryDb exists/mkdir and telemetryFileStats stat) and 1 SQLite import; direct DB and unbounded brain read allowances stay zero. The next review expires in 14 days; no allowance count was increased.",
10
+ "reason": "These exact calls initialize and inspect the optional telemetry database after the daemon transfers telemetry and feedback requests to walle-telemetry-process. Re-reviewed 2026-10-27: handleWalleApi still proxies daemon telemetry/feedback routes before local dispatch; TelemetryProcessClient forks telemetry-worker with the isolated role. The unchanged child implementation has exactly 3 synchronous filesystem sites (getTelemetryDb exists/mkdir and telemetryFileStats stat) and 1 SQLite import; direct DB and unbounded brain read allowances stay zero. The next review expires in 14 days; no allowance count was increased.",
11
11
  "max_duration_ms": 25,
12
12
  "telemetry": "telemetry_process.request",
13
- "expires": "2026-10-28T00:00:00.000Z",
13
+ "expires": "2026-11-10T00:00:00.000Z",
14
14
  "allow": {
15
15
  "sync_io": 3,
16
16
  "direct_db": 0,
@@ -17,6 +17,8 @@
17
17
  // in http/.
18
18
  const path = require('path');
19
19
  const { jsonResponse, readBody } = require('./api-utils');
20
+ // Shared backup-retention policy (R-1..R-6) — the same pure module brain.js prunes with.
21
+ const backupRetention = require('../../shared/backup-retention');
20
22
 
21
23
  function handleAdminApi(req, res, url, deps) {
22
24
  const {
@@ -183,6 +185,41 @@ function handleAdminApi(req, res, url, deps) {
183
185
  return true;
184
186
  }
185
187
 
188
+ // GET /api/wall-e/storage/backup-retention — the retention window plus the backups it expires.
189
+ // Read-only: never deletes a file (the scheduler's cleanOldBackups owns deletion).
190
+ if (p === '/api/wall-e/storage/backup-retention' && m === 'GET') {
191
+ if (!brain) return jsonResponse(res, { error: 'Brain not available' }, 503), true;
192
+ (async () => {
193
+ try {
194
+ if (ensureBrainInit) ensureBrainInit();
195
+ const info = brain.getBackupRetentionInfo();
196
+ const dirInfo = typeof brain.getBackupDirInfo === 'function' ? brain.getBackupDirInfo() : {};
197
+ const rows = await listBackupFiles(dirInfo.backup_dir || brain.BACKUP_DIR);
198
+ jsonResponse(res, { data: { ...info, expiring: backupRetention.selectExpiredBackups(rows, info.retention_days).map((r) => r.name) } });
199
+ } catch (e) { jsonResponse(res, { error: e.message }, 500); }
200
+ })();
201
+ return true;
202
+ }
203
+
204
+ // PUT /api/wall-e/storage/backup-retention — persist a validated retention window (R-6).
205
+ if (p === '/api/wall-e/storage/backup-retention' && m === 'PUT') {
206
+ if (!brain || !ensureBrainInit()) return jsonResponse(res, { error: 'Brain not available' }, 503), true;
207
+ readBody(req, 32 * 1024)
208
+ .then(async (body) => {
209
+ const parsed = backupRetention.parseRetentionDaysInput(body.retention_days);
210
+ if (!parsed.ok) {
211
+ return jsonResponse(res, { error: 'retention_days_invalid', min: 1, max: 365 }, 400);
212
+ }
213
+ await writeBrain('setBackupRetentionDays', [parsed.days]);
214
+ const info = brain.getBackupRetentionInfo();
215
+ const dirInfo = typeof brain.getBackupDirInfo === 'function' ? brain.getBackupDirInfo() : {};
216
+ const rows = await listBackupFiles(dirInfo.backup_dir || brain.BACKUP_DIR);
217
+ jsonResponse(res, { data: { ...info, expiring: backupRetention.selectExpiredBackups(rows, info.retention_days).map((r) => r.name) } });
218
+ })
219
+ .catch((e) => jsonResponse(res, { error: e.message }, 400));
220
+ return true;
221
+ }
222
+
186
223
  // POST /api/wall-e/backups — create brain backup
187
224
  if (p === '/api/wall-e/backups' && m === 'POST') {
188
225
  if (!brain || !ensureBrainInit()) return jsonResponse(res, { error: 'Brain not available' }, 503), true;
@@ -427,6 +427,9 @@ async function buildIMessageStatus(brain, imessageCapabilitiesOverride) {
427
427
  return {
428
428
  dbPresent: imessage.dbPresent,
429
429
  fullDiskAccess: imessage.fullDiskAccess,
430
+ // signin-flow-restart-settle-and-fda-target-identity-2026-10-26 D-8: the exec the FDA grant
431
+ // must name, so the CTM banner can show the exact file (null on an older live-capabilities).
432
+ fdaTarget: imessage.fdaTarget || null,
430
433
  lastIngestAt: imessage.lastIngestAt,
431
434
  maxRowid: imessage.maxRowid,
432
435
  count,
@@ -438,4 +441,5 @@ async function buildIMessageStatus(brain, imessageCapabilitiesOverride) {
438
441
 
439
442
  module.exports = {
440
443
  handleGwsApi,
444
+ buildIMessageStatus,
441
445
  };
@@ -121,9 +121,10 @@ function _cellView(d, row) {
121
121
  const cellKey = String(row.cell_key);
122
122
  const window = readTrailingOutcomes(d, cellKey);
123
123
  const metrics = _parseMetrics(row.metrics_json);
124
+ const kind = row.kind || kindFromCellKey(cellKey);
124
125
  return {
125
126
  cell_key: cellKey,
126
- kind: row.kind || kindFromCellKey(cellKey),
127
+ kind,
127
128
  state: _cellState(row),
128
129
  user_veto: Number(row.user_veto) === 1 ? 1 : 0,
129
130
  n_window: window.length,
@@ -131,6 +132,11 @@ function _cellView(d, row) {
131
132
  metrics,
132
133
  collecting: typeof metrics.collecting === 'boolean' ? metrics.collecting : window.length < COLLECTING_MIN,
133
134
  last_event: _lastEvent(d, cellKey),
135
+ // D-8 (round 3): only a wait-watch cell can fail open into a full-rate watcher, so the
136
+ // per-reason fallback counts ride that view alone -- the explore switch starts no watcher.
137
+ ...(kind === 'wait-watch'
138
+ ? { watch_fallbacks: require('../lib/helper-rules/lead-call-hook').watchFallbackCounts() }
139
+ : {}),
134
140
  };
135
141
  }
136
142
 
@@ -39,6 +39,7 @@ const { workerRegistryResponse } = require('./model-registry-worker-projection')
39
39
  const { pagedResult, catalogRouteInputs, codingCatalogRequestError, codingCatalogResponse, codingCatalogTokenSnap, registryListResponse, pricingListResponse, factsListResponse, scorecardPayload } = require('./model-registry-list');
40
40
  const { buildWorkerRoute } = require('../llm/worker-route');
41
41
  const { handleModelIdentityRoute } = require('./model-identity-routes');
42
+ const { handleModelRouteHealthRoute, annotateCatalogRouteCircuits } = require('./model-route-health');
42
43
  const { handleWorkerCapabilityAdminRoute } = require('./model-worker-capability-admin');
43
44
  const { handleExecProfileAdminRoute } = require('./model-exec-profile-admin');
44
45
  const { handleDelegatePrefAdminRoute } = require('./model-delegate-pref-admin');
@@ -1196,6 +1197,10 @@ async function handleModelAdminApi(req, res, url) {
1196
1197
  const identityRoute = await handleModelIdentityRoute({ req, res, p, m, brainApi, url });
1197
1198
  if (identityRoute) return identityRoute;
1198
1199
 
1200
+ // WP-2 (D-3/D-4): CTM's routed-Lead probe verdict — see model-route-health.js.
1201
+ const routeHealth = await handleModelRouteHealthRoute({ req, res, p, m, brainApi });
1202
+ if (routeHealth) return routeHealth;
1203
+
1199
1204
  const workerCapabilityRoute = await handleWorkerCapabilityAdminRoute({ req, res, p, m, brainApi, url });
1200
1205
  if (workerCapabilityRoute) return workerCapabilityRoute;
1201
1206
 
@@ -1758,7 +1763,9 @@ async function handleModelAdminApi(req, res, url) {
1758
1763
  } else {
1759
1764
  ({ snapshotId, payload } = await _codingCatalogBuildInFlight(brainApi, key, providerId, includeLive));
1760
1765
  }
1761
- const r = codingCatalogResponse(payload, url, { snapshotId }); // paged read: http/model-registry-list.js
1766
+ // WP-2 (D-6): annotate the pinned payload (a copy) BEFORE paging, so the
1767
+ // page-byte budget measures the circuit fields; pages key on catalog_id.
1768
+ const r = codingCatalogResponse(annotateCatalogRouteCircuits(payload), url, { snapshotId }); // paged read: http/model-registry-list.js
1762
1769
  return jsonResponse(res, r.status, r.body);
1763
1770
  } catch (e) {
1764
1771
  if (e.code === 'WALL_E_HTTP_RESPONSE_TOO_LARGE') throw e; // the proxy maps it to 413