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
@@ -0,0 +1,578 @@
1
+ 'use strict';
2
+
3
+ // WP-1 (docs/shared-memory-every-machine-2026-10-31.html §5 D-3/D-4/D-8/D-9/D-10/D-12, §6.4):
4
+ // the storage half of the v2 change feed — a version-vector addressed feed plus the one
5
+ // transaction that applies a page of it.
6
+ //
7
+ // readVector(db) -> { instanceId, vector, clockSeedDone }
8
+ // changesSince(db, { since, limit }) -> { entries, more, clockSeedDone }
9
+ // applyChanges(db, { sourceInstanceId, entries }) -> { ok, applied, skipped, conflicts, vector }
10
+ // recordMemberVector(db, { memberInstanceId, vector }) -> { ok, ... }
11
+ // pruneTombstones(db, { now, rejoinAfterDays }) -> { ok, pruned }
12
+ // digest(db, { table }) -> { ok, table, count, buckets }
13
+ //
14
+ // Every entry point runs on the OWNER's write connection (workers/brain-owner-worker.js
15
+ // CUSTOM_BRAIN_CALLS → here) — the apply creates the connection-scoped TEMP sentinel the
16
+ // capture triggers test. The route handlers in http/sync-api.js stay thin: they
17
+ // authenticate, bound the body and hand the page to the owner through callLocalBrain.
18
+ //
19
+ // The v1 outbox transport (and its apply/LWW helpers in http/sync-api.js) was retired by
20
+ // WP-6b / brain migration 105; the LWW rule, row write and payload filters below are now the
21
+ // only copy.
22
+
23
+ const crypto = require('node:crypto');
24
+ const {
25
+ REPLICATED_TABLES,
26
+ readReplicatedRow,
27
+ knowledgeRowExists,
28
+ localBrainInstanceId,
29
+ clockSeedState,
30
+ } = require('./brain-sync-store');
31
+ const { createBrainSyncSentinel, dropBrainSyncSentinel } = require('./schema');
32
+
33
+ const DEFAULT_CHANGES_LIMIT = 500; // §6.4: one page is at most 500 clock rows
34
+ const MAX_CHANGES_LIMIT = 2000; // …and the route's hard cap
35
+ const MAX_APPLY_ENTRIES = 2000; // §6.4: one apply call carries at most this many entries
36
+ const MAX_TOMBSTONE_PRUNE = 5000; // per call, so a long backlog drains over several ticks
37
+ const MEMORY_WATCH_COLUMNS = Object.freeze(['content', 'content_raw', 'subject', 'metadata', 'importance', 'archived_at']);
38
+ const DIGEST_PAGE = 5000;
39
+ const MEMORY_HOST_LOCAL_RESET = Object.freeze({ extraction_attempts: 0, last_accessed: null, access_count: 0, embed_failed_count: 0 });
40
+ const MEMORY_HOST_LOCAL_COLUMNS = Object.freeze(Object.keys(MEMORY_HOST_LOCAL_RESET));
41
+ // The digest compares what replication keeps equal: the id, the watch columns and (memories)
42
+ // the immutable identity columns. Everything else may legitimately differ per machine.
43
+ const { BRAIN_SYNC_WATCH_COLUMNS } = require('./brain-sync-v2-schema');
44
+ const DIGEST_COLUMNS = Object.freeze({
45
+ memories: Object.freeze(['id', 'source', 'source_id', 'memory_type', 'timestamp', ...BRAIN_SYNC_WATCH_COLUMNS.memories]),
46
+ knowledge: Object.freeze(['id', ...BRAIN_SYNC_WATCH_COLUMNS.knowledge]),
47
+ entities: Object.freeze(['id', ...BRAIN_SYNC_WATCH_COLUMNS.entities]),
48
+ people: Object.freeze(['id', ...BRAIN_SYNC_WATCH_COLUMNS.people]),
49
+ });
50
+
51
+ function _string(value, max = 4000) {
52
+ if (typeof value === 'string') return value.slice(0, max);
53
+ return value == null ? '' : String(value).slice(0, max);
54
+ }
55
+
56
+ function _parseSeq(value) {
57
+ const n = Number(value);
58
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
59
+ }
60
+
61
+ function _tableColumns(db, table) {
62
+ try {
63
+ return db.prepare(`PRAGMA table_info("${table}")`).all().map((row) => row.name);
64
+ } catch {
65
+ return []; // HANDLE: a missing table yields no columns — the caller treats that as "nothing to write"
66
+ }
67
+ }
68
+
69
+ // Payloads arrive from a peer: identifiers that reach SQL come only from the LOCAL table's
70
+ // column list, values are always bound parameters.
71
+ function _payloadFields(payload, allowedColumns) {
72
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return [];
73
+ const allowed = new Set(allowedColumns);
74
+ return Object.keys(payload).filter((key) => allowed.has(key));
75
+ }
76
+
77
+ function _parsePayload(raw) {
78
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)) return raw;
79
+ try {
80
+ const parsed = JSON.parse(String(raw == null ? '{}' : raw));
81
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
82
+ } catch {
83
+ return null; // HANDLE: an unparseable payload is skipped; the caller records it as skipped, never as a write
84
+ }
85
+ }
86
+
87
+ // R-10/D-10: higher updated_at wins; a tie goes to the lexicographically smaller origin.
88
+ function _lwwWins(db, table, rowId, updatedAt, originInstanceId, localId) {
89
+ const local = db.prepare(
90
+ 'SELECT updated_at, origin_instance_id FROM brain_row_clock WHERE table_name = ? AND row_id = ?'
91
+ ).get(table, rowId);
92
+ if (!local) return true;
93
+ const localAt = _string(local.updated_at, 64);
94
+ const incomingAt = _string(updatedAt, 64);
95
+ if (incomingAt > localAt) return true;
96
+ if (incomingAt < localAt) return false;
97
+ const localOrigin = _string(local.origin_instance_id, 160) || _string(localId, 160);
98
+ return _string(originInstanceId, 160) < localOrigin;
99
+ }
100
+
101
+ // The row write: memories are append-only by id; every other
102
+ // replicated table upserts by id.
103
+ function _writeRow(db, table, fields, payload) {
104
+ const columns = fields.map((column) => `"${column}"`).join(', ');
105
+ const placeholders = fields.map(() => '?').join(', ');
106
+ const values = fields.map((column) => payload[column]);
107
+ if (table === 'memories') {
108
+ return db.prepare(`INSERT OR IGNORE INTO "memories" (${columns}) VALUES (${placeholders})`).run(...values);
109
+ }
110
+ const updates = fields.filter((column) => column !== 'id').map((column) => `"${column}" = excluded."${column}"`).join(', ');
111
+ const conflict = updates ? `ON CONFLICT("id") DO UPDATE SET ${updates}` : 'ON CONFLICT("id") DO NOTHING';
112
+ return db.prepare(`INSERT INTO "${table}" (${columns}) VALUES (${placeholders}) ${conflict}`).run(...values);
113
+ }
114
+
115
+ // The v2 clock row for one applied change: the same (table, row) key the capture trigger
116
+ // writes, carrying the PEER's origin/seq so the row stays addressable in that origin's stream.
117
+ function _stampClock(db, table, rowId, updatedAt, originInstanceId, originSeq, deleted) {
118
+ db.prepare(`
119
+ INSERT INTO brain_row_clock (table_name, row_id, updated_at, origin_instance_id, origin_seq, deleted)
120
+ VALUES (?, ?, ?, ?, ?, ?)
121
+ ON CONFLICT(table_name, row_id) DO UPDATE SET
122
+ updated_at = excluded.updated_at,
123
+ origin_instance_id = excluded.origin_instance_id,
124
+ origin_seq = excluded.origin_seq,
125
+ deleted = excluded.deleted
126
+ `).run(table, rowId, _string(updatedAt, 64), _string(originInstanceId, 160), originSeq, deleted);
127
+ }
128
+
129
+ function _bumpVv(db, originInstanceId, seq) {
130
+ if (!(seq > 0)) return;
131
+ db.prepare(`
132
+ INSERT INTO brain_vv (origin_instance_id, max_seq) VALUES (?, ?)
133
+ ON CONFLICT(origin_instance_id) DO UPDATE SET max_seq = MAX(max_seq, excluded.max_seq)
134
+ `).run(originInstanceId, seq);
135
+ }
136
+
137
+ function _readVv(db) {
138
+ const vector = {};
139
+ for (const row of db.prepare('SELECT origin_instance_id, max_seq FROM brain_vv ORDER BY origin_instance_id ASC').all()) {
140
+ vector[String(row.origin_instance_id)] = Number(row.max_seq) || 0;
141
+ }
142
+ return vector;
143
+ }
144
+
145
+ // ---------------------------------------------------------------------------
146
+ // Feed
147
+ // ---------------------------------------------------------------------------
148
+
149
+ // GET /api/walle/sync/v2/vector — "what do I hold": this node's id, its vector and whether
150
+ // the WP-0 seed walk has finished (a peer must not pull a node whose clocks are incomplete).
151
+ function readVector(db) {
152
+ return {
153
+ instanceId: localBrainInstanceId(db),
154
+ vector: _readVv(db),
155
+ clockSeedDone: clockSeedState(db).done,
156
+ };
157
+ }
158
+
159
+ function _feedEntry(db, row) {
160
+ const deleted = Number(row.deleted) ? 1 : 0;
161
+ let payload = null;
162
+ if (!deleted) {
163
+ payload = readReplicatedRow(db, row.table_name, row.row_id);
164
+ }
165
+ // A clock that says "live" for a row that is gone is a race with a local delete (or a clock
166
+ // seeded before the row was written): ship it as a delete. Nothing is logged — the peer's
167
+ // apply is idempotent, and the clock row it writes is the same either way.
168
+ const isDeleted = deleted === 1 || !payload;
169
+ return {
170
+ table: row.table_name,
171
+ rowId: row.row_id,
172
+ origin: row.origin_instance_id,
173
+ originSeq: Number(row.origin_seq) || 0,
174
+ updatedAt: row.updated_at,
175
+ deleted: isDeleted ? 1 : 0,
176
+ payload: isDeleted ? null : payload,
177
+ };
178
+ }
179
+
180
+ // POST /api/walle/sync/v2/changes — every clock row whose origin_seq is beyond `since`, in
181
+ // ascending (origin, origin_seq) order, filling one page. An origin absent from `since` means
182
+ // 0; origin '' ("this host" before the identity was known, or a v1 row) is never shipped — an
183
+ // unaddressable row has no place in a feed a peer walks by (origin, seq).
184
+ function changesSince(db, { since = {}, limit = DEFAULT_CHANGES_LIMIT } = {}) {
185
+ const requested = Math.floor(Number(limit) || 0) || DEFAULT_CHANGES_LIMIT;
186
+ const budget = Math.max(1, Math.min(MAX_CHANGES_LIMIT, requested));
187
+ const sinceMap = since && typeof since === 'object' && !Array.isArray(since) ? since : {};
188
+ const origins = db.prepare('SELECT origin_instance_id FROM brain_vv ORDER BY origin_instance_id ASC').all()
189
+ .map((row) => String(row.origin_instance_id))
190
+ .filter((origin) => origin !== '');
191
+ const select = db.prepare(`
192
+ SELECT table_name, row_id, origin_instance_id, origin_seq, updated_at, deleted
193
+ FROM brain_row_clock
194
+ WHERE origin_instance_id = ? AND origin_seq > ?
195
+ ORDER BY origin_seq ASC
196
+ LIMIT ?
197
+ `);
198
+ const probe = db.prepare('SELECT 1 AS hit FROM brain_row_clock WHERE origin_instance_id = ? AND origin_seq > ? LIMIT 1');
199
+ const entries = [];
200
+ let pending = null;
201
+ for (const origin of origins) {
202
+ const after = Math.max(0, _parseSeq(sinceMap[origin]));
203
+ const remaining = budget - entries.length;
204
+ if (remaining <= 0) {
205
+ pending = { origin, after };
206
+ break;
207
+ }
208
+ const rows = select.all(origin, after, remaining);
209
+ for (const row of rows) entries.push(_feedEntry(db, row));
210
+ if (rows.length < remaining) continue; // this origin drained
211
+ // The page filled exactly at this origin's boundary: ask whether it holds more.
212
+ const lastSeq = rows.length ? Number(rows[rows.length - 1].origin_seq) : after;
213
+ if (probe.get(origin, lastSeq)) {
214
+ pending = { origin, after: lastSeq };
215
+ break;
216
+ }
217
+ }
218
+ // more = some origin still holds rows past what this page returned (the pending origin, or any
219
+ // origin after it that the exhausted budget never reached).
220
+ let more = false;
221
+ if (pending) {
222
+ const startAt = origins.indexOf(pending.origin);
223
+ for (let index = startAt; index < origins.length && !more; index += 1) {
224
+ const origin = origins[index];
225
+ const after = index === startAt ? pending.after : Math.max(0, _parseSeq(sinceMap[origin]));
226
+ more = !!probe.get(origin, after);
227
+ }
228
+ }
229
+ return {
230
+ entries,
231
+ more,
232
+ clockSeedDone: clockSeedState(db).done,
233
+ };
234
+ }
235
+
236
+ // ---------------------------------------------------------------------------
237
+ // Apply
238
+ // ---------------------------------------------------------------------------
239
+
240
+ // POST /api/walle/sync/v2/apply — one IMMEDIATE transaction for the whole page, sentinel on.
241
+ // Rules (§5 D-10): the clock decides first (same origin → higher seq wins), then LWW for
242
+ // non-memory rows, memories stay append-only by id (an existing memory only takes the six
243
+ // watch columns, and only when the incoming clock wins). Losses land in brain_sync_conflicts.
244
+ // Nothing in a page can stop the transaction: an unknown table is a conflict, a malformed
245
+ // entry is a skip.
246
+ function applyChanges(db, payload = {}) {
247
+ const sourceInstanceId = _string(payload.sourceInstanceId || payload.source_instance_id, 160).trim();
248
+ if (!sourceInstanceId) {
249
+ const error = new Error('brain_sync_source_instance_required');
250
+ error.code = 'BRAIN_SYNC_SOURCE_REQUIRED';
251
+ throw error;
252
+ }
253
+ const list = Array.isArray(payload.entries) ? payload.entries : [];
254
+ if (list.length > MAX_APPLY_ENTRIES) {
255
+ const error = new Error('brain_sync_entries_too_large');
256
+ error.code = 'BRAIN_SYNC_ENTRIES_TOO_LARGE';
257
+ throw error;
258
+ }
259
+ const localId = localBrainInstanceId(db);
260
+ const memberOf = _string(payload.memberOf, 160).trim();
261
+ // The CREATE TEMP TABLE text lives in brain/schema.js (the sanctioned DDL module) — this
262
+ // storage module owns no DDL (bin/ddl-outside-runner-lint.js).
263
+ createBrainSyncSentinel(db);
264
+ try {
265
+ return db.transaction(() => {
266
+ let applied = 0;
267
+ let skipped = 0;
268
+ let conflicts = 0;
269
+ const clockOf = db.prepare('SELECT origin_instance_id, origin_seq, updated_at, deleted FROM brain_row_clock WHERE table_name = ? AND row_id = ?');
270
+ const seenThrough = db.prepare('SELECT max_seq FROM brain_vv WHERE origin_instance_id = ?');
271
+ const recordConflict = db.prepare(`
272
+ INSERT INTO brain_sync_conflicts (at, reason, table_name, row_id, origin_instance_id, origin_seq, detail)
273
+ VALUES (?, ?, ?, ?, ?, ?, ?)
274
+ `);
275
+ const note = (reason, entry, detail = '') => {
276
+ conflicts += 1;
277
+ recordConflict.run(
278
+ new Date().toISOString(), reason,
279
+ _string(entry.table, 40), _string(entry.rowId, 160),
280
+ _string(entry.origin, 160), Number(entry.originSeq) || 0, _string(detail, 500),
281
+ );
282
+ };
283
+ for (const entry of list) {
284
+ const table = _string(entry?.table || entry?.table_name, 40).trim();
285
+ const rowId = _string(entry?.rowId || entry?.row_id, 160).trim();
286
+ const origin = _string(entry?.origin || entry?.origin_instance_id, 160);
287
+ const originSeq = _parseSeq(entry?.originSeq ?? entry?.origin_seq);
288
+ const updatedAt = _string(entry?.updatedAt || entry?.updated_at, 64);
289
+ const deleted = Number(entry?.deleted) ? 1 : 0;
290
+ const shape = { table, rowId, origin, originSeq };
291
+ if (!REPLICATED_TABLES.includes(table)) {
292
+ skipped += 1;
293
+ note('brain_table_unknown', shape);
294
+ continue;
295
+ }
296
+ if (!rowId || !origin) {
297
+ // No id or no origin is not addressable and cannot be LWW-compared; a skip, not a write.
298
+ skipped += 1;
299
+ continue;
300
+ }
301
+ // Echo guard: a row this node produced is never taken back (the peer asked with its own
302
+ // maximum, so this should not happen — when it does, the local copy is authoritative).
303
+ if (origin === localId) {
304
+ skipped += 1;
305
+ continue;
306
+ }
307
+ // 0. already considered: this node's vector says it has seen this origin through a seq at
308
+ // or past this entry. Within the hub star each origin's changes arrive in ascending seq
309
+ // (the hub pulls each origin in order and serves its own clock in order), so a re-offer is
310
+ // never new information — skipping it keeps a decided conflict from being re-recorded.
311
+ const seen = seenThrough.get(origin);
312
+ if (seen && Number(seen.max_seq) >= originSeq) {
313
+ skipped += 1;
314
+ continue;
315
+ }
316
+ const local = clockOf.get(table, rowId);
317
+ const localOrigin = local ? _string(local.origin_instance_id, 160) : '';
318
+ const localSeq = local ? Number(local.origin_seq) || 0 : 0;
319
+ // 1. stale same-origin write (D-3: per-origin seq is the address).
320
+ if (local && localOrigin === origin && localSeq >= originSeq) {
321
+ skipped += 1;
322
+ _bumpVv(db, origin, originSeq); // considered: the vector means "seen through", not "holds"
323
+ continue;
324
+ }
325
+ // 2. LWW against a different origin, non-memory rows only.
326
+ if (table !== 'memories' && local && localOrigin !== origin && !_lwwWins(db, table, rowId, updatedAt, origin, localId)) {
327
+ skipped += 1;
328
+ note('lww_lost', shape);
329
+ _bumpVv(db, origin, originSeq); // a decided loss must not be re-offered every tick
330
+ continue;
331
+ }
332
+ // 3. a tombstone: the row goes, the clock stays.
333
+ if (deleted) {
334
+ db.prepare(`DELETE FROM "${table}" WHERE id = ?`).run(rowId);
335
+ _stampClock(db, table, rowId, updatedAt, origin, originSeq, 1);
336
+ _bumpVv(db, origin, originSeq);
337
+ applied += 1;
338
+ continue;
339
+ }
340
+ const parsed = _parsePayload(entry?.payload);
341
+ if (!parsed) {
342
+ skipped += 1;
343
+ continue;
344
+ }
345
+ const columns = _tableColumns(db, table);
346
+ if (!columns.length) {
347
+ skipped += 1;
348
+ note('brain_table_unknown', shape);
349
+ continue;
350
+ }
351
+ const row = { ...parsed, id: parsed.id || rowId };
352
+ if (table === 'memories') {
353
+ // D-4: the (source, source_id) pair is the memory's real identity. An incoming row
354
+ // that pins the same pair to a different id is a fork, not an update — drop it.
355
+ const sourceId = row.source_id == null ? '' : String(row.source_id);
356
+ if (sourceId) {
357
+ const clash = db.prepare('SELECT id FROM memories WHERE source = ? AND source_id = ?').get(_string(row.source, 200), _string(sourceId, 400));
358
+ if (clash && String(clash.id) !== String(rowId)) {
359
+ skipped += 1;
360
+ note('memory_identity_conflict', shape, `local_id:${_string(clash.id, 160)}`);
361
+ _bumpVv(db, origin, originSeq); // recorded once; re-offering it every tick would grow the ledger forever
362
+ continue;
363
+ }
364
+ }
365
+ const existed = !!db.prepare('SELECT 1 AS hit FROM memories WHERE id = ?').get(rowId);
366
+ if (existed) {
367
+ // An archive/edit travels as an update of the same id under LWW (§5 D-10); only the
368
+ // six watch columns move, so nothing local (extraction_status, embeddings pointers)
369
+ // is clobbered by a peer.
370
+ const wins = !local || localOrigin === origin || _lwwWins(db, table, rowId, updatedAt, origin, localId);
371
+ if (!wins) {
372
+ skipped += 1;
373
+ note('lww_lost', shape);
374
+ _bumpVv(db, origin, originSeq);
375
+ continue;
376
+ }
377
+ const watch = MEMORY_WATCH_COLUMNS.filter((column) => columns.includes(column) && row[column] !== undefined);
378
+ if (watch.length) {
379
+ db.prepare(`UPDATE memories SET ${watch.map((column) => `"${column}" = ?`).join(', ')} WHERE id = ?`)
380
+ .run(...watch.map((column) => row[column]), rowId);
381
+ }
382
+ } else {
383
+ // Follow-up #19 (v1 parity): a memory this host did not ingest is never extracted
384
+ // here — the origin host's extraction produced its knowledge rows, and those
385
+ // replicate on their own. Only the insert path labels the row.
386
+ row.extraction_status = 'replicated';
387
+ // Host-local bookkeeping starts fresh here: the origin's read stamps and retry
388
+ // counters describe ITS work (an origin embed_failed_count would stop this node's
389
+ // embedding backfill from ever trying the row).
390
+ for (const column of MEMORY_HOST_LOCAL_COLUMNS) if (column in row) row[column] = MEMORY_HOST_LOCAL_RESET[column];
391
+ const fields = _payloadFields(row, columns);
392
+ if (!fields.includes('id')) fields.unshift('id');
393
+ _writeRow(db, table, fields, row);
394
+ }
395
+ } else {
396
+ // Same repair the v1 apply makes for a dangling knowledge pointer: the target is not
397
+ // here, so the pointer is dropped (NULL) and the loss is recorded.
398
+ if (table === 'knowledge') {
399
+ const target = _string(row.superseded_by, 160).trim();
400
+ if (target && !knowledgeRowExists(db, target)) {
401
+ row.superseded_by = null;
402
+ note('brain_fk_dropped', shape, `referenced_id:${target}`);
403
+ }
404
+ }
405
+ const fields = _payloadFields(row, columns);
406
+ if (!fields.includes('id')) fields.unshift('id');
407
+ _writeRow(db, table, fields, row);
408
+ }
409
+ _stampClock(db, table, rowId, updatedAt, origin, originSeq, 0);
410
+ _bumpVv(db, origin, originSeq);
411
+ applied += 1;
412
+ }
413
+ if (memberOf) markMembership(db, { homeInstanceId: memberOf });
414
+ return { ok: true, applied, skipped, conflicts, vector: _readVv(db) };
415
+ }).immediate();
416
+ } finally {
417
+ dropBrainSyncSentinel(db);
418
+ }
419
+ }
420
+
421
+ // D-5: the durable "this node is a member" fact. Written when a home's sync round applies changes here
422
+ // (header X-CTM-Memory-Sync-Home), and by the WP-4 join. Host-local kv (never replicated). A node never
423
+ // marks itself.
424
+ const { MEMBERSHIP_KEY, readMembership } = require('./brain-sync-membership');
425
+ function markMembership(db, { homeInstanceId = '' } = {}) {
426
+ const home = _string(homeInstanceId, 160).trim();
427
+ if (!home || home === localBrainInstanceId(db)) return { ok: false, changed: false };
428
+ const current = readMembership(db);
429
+ if (current && current.role === 'member' && current.homeInstanceId === home) return { ok: true, changed: false };
430
+ db.prepare(`INSERT INTO kv_store (key, value, updated_at) VALUES (?, ?, datetime('now'))
431
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`)
432
+ .run(MEMBERSHIP_KEY, JSON.stringify({ role: 'member', homeInstanceId: home, since: new Date().toISOString() }));
433
+ return { ok: true, changed: true };
434
+ }
435
+
436
+ // ---------------------------------------------------------------------------
437
+ // Hub bookkeeping
438
+ // ---------------------------------------------------------------------------
439
+
440
+ // POST /api/walle/sync/v2/member-vector — the hub's record of what a member last reported.
441
+ // Monotonic per (member, origin): a stale or out-of-order report can only ever make the
442
+ // pruning floor more conservative, never less.
443
+ function recordMemberVector(db, { memberInstanceId = '', vector = {} } = {}) {
444
+ const member = _string(memberInstanceId, 160).trim();
445
+ if (!member) {
446
+ const error = new Error('brain_sync_member_instance_required');
447
+ error.code = 'BRAIN_SYNC_MEMBER_REQUIRED';
448
+ throw error;
449
+ }
450
+ const entries = vector && typeof vector === 'object' && !Array.isArray(vector) ? Object.entries(vector) : [];
451
+ const at = new Date().toISOString();
452
+ db.transaction(() => {
453
+ const upsert = db.prepare(`
454
+ INSERT INTO brain_member_vv (member_instance_id, origin_instance_id, max_seq, reported_at)
455
+ VALUES (?, ?, ?, ?)
456
+ ON CONFLICT(member_instance_id, origin_instance_id) DO UPDATE SET
457
+ max_seq = MAX(brain_member_vv.max_seq, excluded.max_seq),
458
+ reported_at = excluded.reported_at
459
+ `);
460
+ for (const [origin, seq] of entries) {
461
+ if (!origin) continue;
462
+ upsert.run(member, _string(origin, 160), Math.max(0, _parseSeq(seq)), at);
463
+ }
464
+ }).immediate();
465
+ return { ok: true, memberInstanceId: member, reportedAt: at };
466
+ }
467
+
468
+ // POST /api/walle/sync/v2/prune-tombstones — §5 D-8. A tombstone goes only once EVERY member
469
+ // whose report is still fresh (within rejoinAfterDays) has a vector covering it: the floor is
470
+ // the MIN over those members for that origin, so one lagging member holds the tombstone back.
471
+ // No members at all → nothing to prune (a node that just joined must not eat the history it
472
+ // has not shipped yet).
473
+ function pruneTombstones(db, { now = Date.now(), rejoinAfterDays = 30 } = {}) {
474
+ const days = Number.isFinite(Number(rejoinAfterDays)) ? Math.max(0, Number(rejoinAfterDays)) : 30;
475
+ const cutoff = new Date(Number(now) - days * 24 * 60 * 60 * 1000).toISOString();
476
+ const hasMembers = !!db.prepare('SELECT 1 AS hit FROM brain_member_vv LIMIT 1').get();
477
+ if (!hasMembers) {
478
+ // A MEMBER (no member vectors: only the hub dials, D-2) prunes by age. Its tombstones matter
479
+ // only until the hub has pulled them, which a member silent for longer than the rejoin window
480
+ // forfeits anyway (D-8: it must re-join by snapshot). updated_at is the origin's ISO clock.
481
+ const aged = db.prepare(`
482
+ DELETE FROM brain_row_clock
483
+ WHERE rowid IN (SELECT rowid FROM brain_row_clock WHERE deleted = 1 AND updated_at < ? LIMIT ?)
484
+ `).run(cutoff, MAX_TOMBSTONE_PRUNE).changes;
485
+ return { ok: true, pruned: aged, mode: 'age' };
486
+ }
487
+ let pruned = 0;
488
+ db.transaction(() => {
489
+ const floors = db.prepare(`
490
+ SELECT origin_instance_id AS origin, MIN(max_seq) AS floor
491
+ FROM brain_member_vv
492
+ WHERE reported_at >= ?
493
+ GROUP BY origin_instance_id
494
+ `).all(cutoff);
495
+ const del = db.prepare(`
496
+ DELETE FROM brain_row_clock
497
+ WHERE rowid IN (
498
+ SELECT rowid FROM brain_row_clock
499
+ WHERE deleted = 1 AND origin_instance_id = ? AND origin_seq <= ?
500
+ LIMIT ?
501
+ )
502
+ `);
503
+ for (const row of floors) {
504
+ if (pruned >= MAX_TOMBSTONE_PRUNE) break;
505
+ pruned += del.run(_string(row.origin, 160), Math.max(0, Number(row.floor) || 0), MAX_TOMBSTONE_PRUNE - pruned).changes;
506
+ }
507
+ }).immediate();
508
+ return { ok: true, pruned };
509
+ }
510
+
511
+ // ---------------------------------------------------------------------------
512
+ // Convergence digest (§6.5) — the comparison WP-6's verifier reads
513
+ // ---------------------------------------------------------------------------
514
+
515
+ function _canonicalJson(table, payload) {
516
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return 'null';
517
+ const ordered = {};
518
+ for (const key of [...(DIGEST_COLUMNS[table] || [])].sort()) ordered[key] = payload[key] === undefined ? null : payload[key];
519
+ return JSON.stringify(ordered);
520
+ }
521
+
522
+ // GET /api/walle/sync/v2/digest?table=memories — bucket = first byte of sha256(rowId);
523
+ // rowHash = first 8 bytes of sha256(`${table}|${rowId}|${deleted}|${canonicalJson(payload)}`),
524
+ // XOR-folded per bucket. Only DIGEST_COLUMNS are hashed: read stamps, retry counters and local
525
+ // classifier output are this host's bookkeeping, not the replicated state. CPU-bound, paged.
526
+ function digest(db, { table } = {}) {
527
+ const name = _string(table, 40).trim();
528
+ if (!REPLICATED_TABLES.includes(name)) {
529
+ const error = new Error(`brain_sync_digest_unknown_table: ${name}`);
530
+ error.code = 'BRAIN_SYNC_TABLE_UNKNOWN';
531
+ throw error;
532
+ }
533
+ const counts = new Array(256).fill(0);
534
+ const folds = new Array(256).fill(null);
535
+ // LIVE rows only (shared-memory WP-4c review): tombstones are bookkeeping that each node prunes on
536
+ // its own schedule (pruneTombstones), so hashing them would make converged nodes differ. A delete
537
+ // that never applied still shows: the lagging node keeps a live row the others do not have.
538
+ const page = db.prepare('SELECT rowid AS rid, row_id, deleted FROM brain_row_clock WHERE table_name = ? AND deleted = 0 AND rowid > ? ORDER BY rowid ASC LIMIT ?');
539
+ let afterRowid = 0;
540
+ let count = 0;
541
+ for (;;) {
542
+ const rows = page.all(name, afterRowid, DIGEST_PAGE);
543
+ if (!rows.length) break;
544
+ for (const clockRow of rows) {
545
+ afterRowid = Number(clockRow.rid);
546
+ const live = Number(clockRow.deleted) ? null : readReplicatedRow(db, name, clockRow.row_id);
547
+ const deleted = live ? 0 : 1;
548
+ const hash = crypto.createHash('sha256').update(`${name}|${clockRow.row_id}|${deleted}|${_canonicalJson(name, live)}`).digest();
549
+ const bucket = crypto.createHash('sha256').update(String(clockRow.row_id)).digest()[0];
550
+ counts[bucket] += 1;
551
+ count += 1;
552
+ const fold = folds[bucket] || Buffer.alloc(8);
553
+ for (let index = 0; index < 8; index += 1) fold[index] ^= hash[index];
554
+ folds[bucket] = fold;
555
+ }
556
+ }
557
+ const buckets = [];
558
+ for (let bucket = 0; bucket < 256; bucket += 1) {
559
+ buckets.push({ b: bucket, count: counts[bucket], xor: (folds[bucket] || Buffer.alloc(8)).toString('hex') });
560
+ }
561
+ return { ok: true, table: name, count, buckets };
562
+ }
563
+
564
+ module.exports = {
565
+ markMembership,
566
+ readMembership,
567
+ MEMBERSHIP_KEY,
568
+ DEFAULT_CHANGES_LIMIT,
569
+ MAX_CHANGES_LIMIT,
570
+ MAX_APPLY_ENTRIES,
571
+ MEMORY_WATCH_COLUMNS,
572
+ readVector,
573
+ changesSince,
574
+ applyChanges,
575
+ recordMemberVector,
576
+ pruneTombstones,
577
+ digest,
578
+ };
@@ -3,7 +3,7 @@
3
3
  // Delegate model preferences (lead-and-delegate-model-picker-ux-2026-10-08.html
4
4
  // §5 D-9/D-11, §6.4): one row per scope in delegate_model_prefs --
5
5
  // session:<ctm_session_id> the pick made for ONE lead session
6
- // default:claude | default:codex the per-lead-agent default
6
+ // default:claude | default:codex | default:opencode | default:dsh the per-lead-agent default
7
7
  // Every write to the table lives here. effectiveDelegatePref() walks the
8
8
  // ladder session > default > vendor and names the winning rung as `source`.
9
9
  // Migration 86 also lives here: it folds the retired model_defaults
@@ -11,7 +11,10 @@
11
11
  const { foldDelegateLadder } = require('../llm/delegate-vendor-default');
12
12
 
13
13
  const LANES = Object.freeze(['native', 'routed', 'inherit']);
14
- const AGENTS = Object.freeze(['claude', 'codex']);
14
+ // The lead agents the ladder is keyed on. WP-10/P-5 (deepseek-harness-acp-agent-2026-10-28.html
15
+ // §5b D-24): dsh joins as a first-class delegating lead -- its `default:dsh` rung is exactly as
16
+ // reachable as default:opencode's.
17
+ const AGENTS = Object.freeze(['claude', 'codex', 'opencode', 'dsh']);
15
18
  const SESSION_SCOPE_RE = /^session:[A-Za-z0-9._-]{1,160}$/;
16
19
  const FIELD_MAX = 200;
17
20
  // '' = the model's own default. native: the Agent tool / spawn_agent accept up to max; routed:
@@ -54,7 +57,7 @@ function optionalString(value, field) {
54
57
  function putDelegatePref(db, row, { now = () => new Date().toISOString() } = {}) {
55
58
  const v = row && typeof row === 'object' && !Array.isArray(row) ? row : {};
56
59
  if (!isValidScopeKey(v.scope_key)) {
57
- throw prefError('scope_key', 'must be session:<ctm_session_id>, default:claude or default:codex');
60
+ throw prefError('scope_key', 'must be session:<ctm_session_id>, default:claude, default:codex, default:opencode or default:dsh');
58
61
  }
59
62
  if (!LANES.includes(v.lane)) throw prefError('lane', 'must be native, routed or inherit');
60
63
  const model = optionalString(v.model, 'model');
@@ -96,7 +99,10 @@ function deleteDelegatePref(db, scopeKey) {
96
99
  * @returns {{effective:{lane,model,route_id,identity_id,display,effort,source}, ladder:Array}}
97
100
  */
98
101
  function effectiveDelegatePref(db, { ctmSessionId, agent } = {}) {
99
- const leadAgent = agent === 'codex' ? 'codex' : 'claude';
102
+ // AGENTS is the one list of lead agents; an absent/unknown agent keeps the historical claude
103
+ // fallback. Reading `default:${leadAgent}` is what makes default:opencode / default:dsh
104
+ // reachable at all.
105
+ const leadAgent = AGENTS.includes(agent) ? agent : 'claude';
100
106
  const sessionKey = ctmSessionId ? `session:${ctmSessionId}` : '';
101
107
  return foldDelegateLadder({
102
108
  session: isValidScopeKey(sessionKey) ? getDelegatePref(db, sessionKey) : null,
@@ -127,6 +133,9 @@ function migrateDelegatedWorkerDefault(d) {
127
133
  VALUES (?, 'routed', ?, ?, ?, ?, ?)
128
134
  `);
129
135
  const now = new Date().toISOString();
136
+ // AC-4f / WP-10: `agents` comes from servableAgents(), which only ever yields claude/codex,
137
+ // so an 'opencode' or 'dsh' member of AGENTS is always skipped and this migration's
138
+ // behaviour is unchanged by either joining the list.
130
139
  for (const agent of AGENTS) {
131
140
  if (!agents.includes(agent)) continue;
132
141
  const info = insert.run(`default:${agent}`, reg.model_id, identity ? identity.identity_id : '', reg.provider_id, reg.display_name || reg.model_id, now);