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,565 @@
1
+ 'use strict';
2
+
3
+ // WP-3 (docs/shared-memory-every-machine-2026-10-31.html §5 D-1/D-2/D-9/D-12, §6.6): the hub side
4
+ // of the v2 brain replica in the existing star.
5
+ //
6
+ // syncMemberBrainOnce(member, deps) -> { ok, member, state, pulled, pushed, error? }
7
+ // runBrainMemberRound(deps) -> { ok, members: [...], pruned }
8
+ // memorySyncStatus() -> { lastRoundAt, members: [...] }
9
+ //
10
+ // D-1: membership is PAIRING, not `CTM_SYNC_PEERS`. The members are this host's machine-registry
11
+ // rows with `enabled = 1` and `health === 'reachable'` (the hub owns liveness, machine-registry
12
+ // D-4) — the same credential the probe uses reaches them (`peerHeadersFor`, the pool secret for a
13
+ // member). D-2: only the hub dials; each round exchanges both directions with every reachable
14
+ // member and there is no box↔box link. D-12: the member's CTM fronts its own loopback Wall-E at
15
+ // `/api/sync/brain/v2/*`; the local leg talks to THIS host's Wall-E with the local CTM token.
16
+ //
17
+ // The per-member state Map (failure backoff, last verdict) is MODULE state by design, exactly like
18
+ // machine-registry's probe bookkeeping: it describes the last round this process ran, dies with the
19
+ // process, and a stale copy must never look like durable truth.
20
+
21
+ const { boolFlag, createEnvFlags } = require('./env-flags');
22
+ const machineRegistry = require('./machine-registry');
23
+ const { advanceMemberJoin } = require('./memory-join');
24
+ const conversationStore = require('./conversation-store-v2');
25
+ const conversationSync = require('./conversation-sync');
26
+
27
+ const DEFAULT_PAGE_LIMIT = 500; // D-9: one page is at most 500 clock rows
28
+ const DEFAULT_MAX_PAGES = 20; // D-9: at most 20 pages per member per round
29
+ const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
30
+ const DEFAULT_FAILURE_BACKOFF_MS = 15_000;
31
+ const DEFAULT_FAILURE_BACKOFF_MAX_MS = 60_000;
32
+
33
+ // WP-4 (§5 D-6, §6.7): the non-error join states a member can report while it is taking the hub's
34
+ // snapshot. 'join_failed' is deliberately absent — a broken join is a fault, not progress.
35
+ const JOIN_PROGRESS_STATES = new Set(['join_snapshot', 'join_transfer', 'join_import', 'join_done']);
36
+
37
+ // The box CTM's front routes (D-12). Each forwards verbatim to the box's own Wall-E.
38
+ const MEMBER_PATHS = Object.freeze({
39
+ vector: '/api/sync/brain/v2/vector',
40
+ changes: '/api/sync/brain/v2/changes',
41
+ apply: '/api/sync/brain/v2/apply',
42
+ });
43
+
44
+ // The hub's LOCAL Wall-E v2 routes (already loopback + CTM token).
45
+ const LOCAL_PATHS = Object.freeze({
46
+ vector: '/api/walle/sync/v2/vector',
47
+ changes: '/api/walle/sync/v2/changes',
48
+ apply: '/api/walle/sync/v2/apply',
49
+ memberVector: '/api/walle/sync/v2/member-vector',
50
+ pruneTombstones: '/api/walle/sync/v2/prune-tombstones',
51
+ });
52
+
53
+ // WP-5 (§5 D-3, §6.8): the conversation-history front routes a member's CTM serves itself — no
54
+ // Wall-E hop, because transcription history lives in task-manager.db.
55
+ const CONVERSATION_PATHS = Object.freeze({
56
+ vector: '/api/sync/conversation/v2/vector',
57
+ changes: '/api/sync/conversation/v2/changes',
58
+ apply: '/api/sync/conversation/v2/apply',
59
+ });
60
+
61
+ // instanceId -> { attempts, nextAt, state }. Module state (see the header).
62
+ const _memberState = new Map();
63
+ // The one in-flight round, or null. A second call joins/reports it, never starts a second round.
64
+ let _inFlight = null;
65
+ let _lastRound = null;
66
+
67
+ function _string(value, max = 160) {
68
+ if (typeof value === 'string') return value.trim().slice(0, max);
69
+ return value == null ? '' : String(value).trim().slice(0, max);
70
+ }
71
+
72
+ function _now(deps = {}) {
73
+ return typeof deps.now === 'function' ? Number(deps.now()) || Date.now() : Date.now();
74
+ }
75
+
76
+ // D-1 flag. Default ON; only '0'/'false'/'no'/'off' disables. `deps.env` is injected so a test can
77
+ // flip it without touching process.env (H-1), and so a member Wall-E's own kill switch is honoured
78
+ // on the local leg (503 brain_sync_disabled -> 'disabled').
79
+ function _brainSyncEnabled(env) {
80
+ if (!env || env === process.env) return boolFlag('CTM_SYNC_BRAIN_ENABLED', true);
81
+ return createEnvFlags({ env }).boolFlag('CTM_SYNC_BRAIN_ENABLED', true);
82
+ }
83
+
84
+ function _memberUrls(member = {}) {
85
+ const list = (Array.isArray(member.urls) ? member.urls : []).map((u) => _string(u, 1000)).filter(Boolean);
86
+ if (list.length) return list;
87
+ const pinned = _string(member.url, 1000);
88
+ return pinned ? [pinned] : [];
89
+ }
90
+
91
+ function _joinUrl(base, pathname) {
92
+ return `${String(base).replace(/\/+$/, '')}${pathname}`;
93
+ }
94
+
95
+ // One member request, with the AbortController + unref'd timer shape machine-registry's _getJsonBox
96
+ // uses, so a hung box can never hold the tick open past the timeout. Never throws: a transport fault
97
+ // is reported as `{ status: 0 }` and mapped by the caller to 'unreachable'.
98
+ async function _memberRequest(fetchImpl, url, pathname, { method = 'GET', body = null, headers = {} } = {}) {
99
+ const fetchFn = fetchImpl || global.fetch;
100
+ if (typeof fetchFn !== 'function') return { status: 0, json: null, error: 'fetch_unavailable' };
101
+ const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
102
+ const timer = controller ? setTimeout(() => controller.abort(), DEFAULT_REQUEST_TIMEOUT_MS) : null;
103
+ if (timer && typeof timer.unref === 'function') timer.unref();
104
+ try {
105
+ const payload = body == null ? null : JSON.stringify(body);
106
+ const res = await fetchFn(_joinUrl(url, pathname), {
107
+ method,
108
+ headers: {
109
+ ...headers,
110
+ ...(payload ? { 'Content-Type': 'application/json' } : {}),
111
+ Accept: 'application/json',
112
+ },
113
+ ...(payload ? { body: payload } : {}),
114
+ signal: controller ? controller.signal : undefined,
115
+ });
116
+ let json = null;
117
+ try { json = await res.json(); } catch { json = null; /* expected: a non-JSON body is degraded, mapped by the caller */ }
118
+ return { status: Number(res && res.status) || 0, json };
119
+ } catch (err) {
120
+ return { status: 0, json: null, error: err?.message || String(err) };
121
+ } finally {
122
+ if (timer) clearTimeout(timer);
123
+ }
124
+ }
125
+
126
+ // One local Wall-E call. walle.requestJson resolves { status, json } for any answer and rejects only
127
+ // on a transport fault — the caller maps both.
128
+ async function _walleRequest(walle, pathname, { method = 'GET', body = null } = {}) {
129
+ try {
130
+ const res = await walle.requestJson(pathname, body == null ? { method } : { method, body });
131
+ return { status: Number(res && res.status) || 0, json: (res && res.json) || null };
132
+ } catch (err) {
133
+ return { status: 0, json: null, error: err?.message || String(err) };
134
+ }
135
+ }
136
+
137
+ function _isDisabled(res) {
138
+ return res && res.status === 503 && res.json && res.json.error === 'brain_sync_disabled';
139
+ }
140
+
141
+ // The first URL that ANSWERS (any HTTP status) owns the rest of the exchange, so a member is never
142
+ // half-dialled across two origins inside one round. No answer at all -> status 0 ('unreachable').
143
+ async function _dialMember(fetchImpl, urls, pathname, options) {
144
+ let last = { status: 0, json: null, error: 'member_has_no_url' };
145
+ for (const url of urls) {
146
+ const res = await _memberRequest(fetchImpl, url, pathname, options);
147
+ if (res.status > 0) return { ...res, url };
148
+ last = { ...res, url };
149
+ }
150
+ return last;
151
+ }
152
+
153
+ /**
154
+ * One bidirectional exchange with one member (D-2, D-9). Returns a state from
155
+ * 'synced' | 'seed_pending' | 'disabled' | 'unreachable' | 'error', or — for a member that has
156
+ * never taken the hub's origin — one of lib/memory-join.js's join states ('seed_pending' |
157
+ * 'join_snapshot' | 'join_transfer' | 'join_import' | 'join_done' | 'join_failed').
158
+ *
159
+ * @param {{instanceId: string, urls?: string[], url?: string}} member
160
+ * @param {object} deps { fetchImpl, peerHeadersFor, walle, env, now, pageLimit, maxPages,
161
+ * chunksPerTick, readChunk }
162
+ */
163
+ async function syncMemberBrainOnce(member = {}, deps = {}) {
164
+ const fetchImpl = deps.fetchImpl || global.fetch;
165
+ const walle = deps.walle;
166
+ const env = deps.env || process.env;
167
+ const pageLimit = Number(deps.pageLimit) || DEFAULT_PAGE_LIMIT;
168
+ const maxPages = Number(deps.maxPages) || DEFAULT_MAX_PAGES;
169
+ const instanceId = _string(member.instanceId);
170
+ const base = { ok: true, member: instanceId, state: 'error', pulled: 0, pushed: 0 };
171
+ if (!walle || typeof walle.requestJson !== 'function') return { ...base, ok: false, error: 'walle_client_missing' };
172
+ if (!instanceId) return { ...base, ok: false, error: 'member_instance_required' };
173
+ const urls = _memberUrls(member);
174
+ if (!urls.length) return { ...base, ok: false, state: 'unreachable', error: 'member_has_no_url' };
175
+ const headers = typeof deps.peerHeadersFor === 'function' ? (deps.peerHeadersFor(instanceId) || {}) : {};
176
+
177
+ // 1. both ends' vectors.
178
+ const localRes = await _walleRequest(walle, LOCAL_PATHS.vector);
179
+ if (_isDisabled(localRes)) return { ...base, ok: false, state: 'disabled' };
180
+ if (localRes.status === 0 || !localRes.json) return { ...base, ok: false, state: 'unreachable', error: localRes.error || 'local_walle_unreachable' };
181
+ const local = localRes.json;
182
+ const memberRes = await _dialMember(fetchImpl, urls, MEMBER_PATHS.vector, { method: 'GET', headers });
183
+ if (_isDisabled(memberRes)) return { ...base, ok: false, state: 'disabled' };
184
+ if (memberRes.status === 0 || !memberRes.json) return { ...base, ok: false, state: 'unreachable', error: memberRes.error || 'member_unreachable' };
185
+ const memberUrl = memberRes.url;
186
+ const mv = memberRes.json;
187
+
188
+ // 2. D-9: a node whose WP-0 clock walk has not finished cannot exchange a feed. No traffic.
189
+ if (local.clockSeedDone !== true || mv.clockSeedDone !== true) {
190
+ return { ...base, ok: false, state: 'seed_pending' };
191
+ }
192
+
193
+ const localInstanceId = _string(local.instanceId) || _string(env.CTM_INSTANCE_ID || env.CTM_INSTANCE_TAG);
194
+ const localVector = (local.vector && typeof local.vector === 'object') ? { ...local.vector } : {};
195
+ const memberVector = (mv.vector && typeof mv.vector === 'object') ? { ...mv.vector } : {};
196
+
197
+ // 3. D-1/§6.6: a member that has never taken the hub's origin cannot exchange a feed yet —
198
+ // pushing its orphaned legacy rows at the hub now would flood identity conflicts. WP-4 (§5 D-6,
199
+ // §6.7) turns that idle 'join_required' into the join state machine: the hub ships its consistent
200
+ // snapshot in chunks, the member imports it, and only then does the normal feed resume.
201
+ if (Object.keys(localVector).length > 0 && localInstanceId && !Object.prototype.hasOwnProperty.call(memberVector, localInstanceId)) {
202
+ const join = await advanceMemberJoin(member, {
203
+ localInstanceId,
204
+ walle,
205
+ fetchImpl,
206
+ headers,
207
+ memberUrl,
208
+ now: deps.now,
209
+ chunksPerTick: deps.chunksPerTick,
210
+ readChunk: deps.readChunk,
211
+ });
212
+ return {
213
+ ...base,
214
+ ok: join.state === 'join_done',
215
+ state: join.state,
216
+ pulled: 0,
217
+ pushed: 0,
218
+ ...(join.detail ? { detail: join.detail } : {}),
219
+ };
220
+ }
221
+
222
+ let pulled = 0;
223
+ let pushed = 0;
224
+ let pullVector = localVector;
225
+ let pushVector = memberVector;
226
+
227
+ // 4. pull: member /changes -> local /apply, page by page.
228
+ for (let page = 0; page < maxPages; page += 1) {
229
+ const changes = await _memberRequest(fetchImpl, memberUrl, MEMBER_PATHS.changes, {
230
+ method: 'POST', headers, body: { since: pullVector, limit: pageLimit },
231
+ });
232
+ if (_isDisabled(changes)) return { ...base, ok: false, state: 'disabled' };
233
+ if (changes.status === 0) return { ...base, ok: false, state: 'unreachable', pulled, pushed, error: changes.error || 'member_unreachable' };
234
+ if (changes.status === 409) return { ...base, ok: false, state: 'seed_pending', pulled, pushed };
235
+ if (changes.status < 200 || changes.status >= 300 || !changes.json) {
236
+ return { ...base, ok: false, state: 'error', pulled, pushed, error: `member_changes_http_${changes.status}` };
237
+ }
238
+ const entries = Array.isArray(changes.json.entries) ? changes.json.entries : [];
239
+ if (entries.length) {
240
+ const applied = await _walleRequest(walle, LOCAL_PATHS.apply, { method: 'POST', body: { sourceInstanceId: instanceId, entries } });
241
+ if (_isDisabled(applied)) return { ...base, ok: false, state: 'disabled' };
242
+ if (applied.status === 0) return { ...base, ok: false, state: 'unreachable', pulled, pushed, error: 'local_walle_unreachable' };
243
+ if (applied.status < 200 || applied.status >= 300 || !applied.json) {
244
+ return { ...base, ok: false, state: 'error', pulled, pushed, error: `local_apply_http_${applied.status}` };
245
+ }
246
+ pulled += Number(applied.json.applied) || entries.length;
247
+ pullVector = (applied.json.vector && typeof applied.json.vector === 'object') ? applied.json.vector : pullVector;
248
+ }
249
+ if (changes.json.more !== true) break;
250
+ }
251
+
252
+ // 5. push: local /changes -> member /apply, page by page.
253
+ for (let page = 0; page < maxPages; page += 1) {
254
+ const changes = await _walleRequest(walle, LOCAL_PATHS.changes, { method: 'POST', body: { since: pushVector, limit: pageLimit } });
255
+ if (_isDisabled(changes)) return { ...base, ok: false, state: 'disabled', pulled, pushed };
256
+ if (changes.status === 0) return { ...base, ok: false, state: 'unreachable', pulled, pushed, error: 'local_walle_unreachable' };
257
+ if (changes.status === 409) return { ...base, ok: false, state: 'seed_pending', pulled, pushed };
258
+ if (changes.status < 200 || changes.status >= 300 || !changes.json) {
259
+ return { ...base, ok: false, state: 'error', pulled, pushed, error: `local_changes_http_${changes.status}` };
260
+ }
261
+ const entries = Array.isArray(changes.json.entries) ? changes.json.entries : [];
262
+ if (entries.length) {
263
+ const applied = await _memberRequest(fetchImpl, memberUrl, MEMBER_PATHS.apply, {
264
+ // D-5: name this node as the member's home so the member stops running derived producers.
265
+ method: 'POST', headers: { ...headers, 'X-CTM-Memory-Sync-Home': localInstanceId }, body: { sourceInstanceId: localInstanceId, entries },
266
+ });
267
+ if (_isDisabled(applied)) return { ...base, ok: false, state: 'disabled', pulled, pushed };
268
+ if (applied.status === 0) return { ...base, ok: false, state: 'unreachable', pulled, pushed, error: applied.error || 'member_unreachable' };
269
+ if (applied.status < 200 || applied.status >= 300 || !applied.json) {
270
+ return { ...base, ok: false, state: 'error', pulled, pushed, error: `member_apply_http_${applied.status}` };
271
+ }
272
+ pushed += Number(applied.json.applied) || entries.length;
273
+ pushVector = (applied.json.vector && typeof applied.json.vector === 'object') ? applied.json.vector : pushVector;
274
+ }
275
+ if (changes.json.more !== true) break;
276
+ }
277
+
278
+ // 6. tell the hub's Wall-E what the member last reported (feeds tombstone pruning).
279
+ const recorded = await _walleRequest(walle, LOCAL_PATHS.memberVector, {
280
+ method: 'POST', body: { memberInstanceId: instanceId, vector: pushVector },
281
+ });
282
+ if (_isDisabled(recorded)) return { ...base, ok: false, state: 'disabled', pulled, pushed };
283
+ if (recorded.status === 0) return { ...base, ok: false, state: 'unreachable', pulled, pushed, error: 'local_walle_unreachable' };
284
+ if (recorded.status < 200 || recorded.status >= 300) {
285
+ return { ...base, ok: false, state: 'error', pulled, pushed, error: `local_member_vector_http_${recorded.status}` };
286
+ }
287
+
288
+ return { ok: true, member: instanceId, state: 'synced', pulled, pushed };
289
+ }
290
+
291
+ // WP-5 (§5 D-3, §6.8) flag. Its own switch: the brain leg's CTM_SYNC_BRAIN_ENABLED must not take
292
+ // transcription history down with it (and vice versa). Default ON.
293
+ function _conversationSyncEnabled(env) {
294
+ if (!env || env === process.env) return boolFlag('CTM_SYNC_CONVERSATION_ENABLED', true);
295
+ return createEnvFlags({ env }).boolFlag('CTM_SYNC_CONVERSATION_ENABLED', true);
296
+ }
297
+
298
+ /**
299
+ * The raw statement interface for CTM's own store. The worker is handed the db MODULE
300
+ * (server.js passes `db-owner-client`), whose proxies expose `getDb()` rather than
301
+ * `prepare`/`exec`; a db object with no readable handle at all — a push-only worker stub — has no
302
+ * conversation store to exchange, so the leg is skipped rather than reported as a member error.
303
+ *
304
+ * @param {object|null} db the round's injected db module
305
+ * @returns {object|null} a better-sqlite3 handle, or null when this node cannot serve the feed
306
+ */
307
+ function _conversationDb(db) {
308
+ if (!db) return null;
309
+ if (typeof db.prepare === 'function') return db;
310
+ if (typeof db.getDb === 'function') {
311
+ try { return db.getDb() || null; } catch { return null; /* REPORT: no readable store handle — the leg is skipped */ }
312
+ }
313
+ return null;
314
+ }
315
+
316
+ /**
317
+ * One bidirectional exchange of conversation-clock changes with one member (D-3 over transcription
318
+ * history). Runs AFTER the brain leg and independently of its verdict: the conversation feed needs
319
+ * no join snapshot, so a member mid-join still exchanges items. Returns
320
+ * { ok, state, pulled, pushed } with a state from 'synced' | 'seed_pending' | 'disabled' |
321
+ * 'unreachable' | 'error'.
322
+ *
323
+ * @param {{instanceId: string, urls?: string[], url?: string}} member
324
+ * @param {object} deps { db, fetchImpl, peerHeadersFor, env, pageLimit, maxPages }
325
+ */
326
+ async function syncMemberConversationOnce(member = {}, deps = {}) {
327
+ const db = _conversationDb(deps.db);
328
+ const fetchImpl = deps.fetchImpl || global.fetch;
329
+ const env = deps.env || process.env;
330
+ const pageLimit = Number(deps.pageLimit) || DEFAULT_PAGE_LIMIT;
331
+ const maxPages = Number(deps.maxPages) || DEFAULT_MAX_PAGES;
332
+ const instanceId = _string(member.instanceId);
333
+ const base = { ok: true, state: 'error', pulled: 0, pushed: 0 };
334
+ if (!db) return { ...base, ok: false, error: 'db_required' };
335
+ if (!instanceId) return { ...base, ok: false, error: 'member_instance_required' };
336
+ const urls = _memberUrls(member);
337
+ if (!urls.length) return { ...base, ok: false, state: 'unreachable', error: 'member_has_no_url' };
338
+ const headers = typeof deps.peerHeadersFor === 'function' ? (deps.peerHeadersFor(instanceId) || {}) : {};
339
+
340
+ // 1. this node's own seed advances one bounded page per round (never at boot, §6.8).
341
+ try {
342
+ conversationStore.seedConversationClockPage(db, {});
343
+ } catch {
344
+ return { ...base, ok: false, state: 'error', error: 'conversation_seed_failed' };
345
+ }
346
+ const local = conversationSync.readConversationVector(db);
347
+ if (local.seedDone !== true) return { ...base, ok: true, state: 'seed_pending' };
348
+
349
+ // 2. the member's vector. Its /vector call advances the member's own seed page too.
350
+ const memberRes = await _dialMember(fetchImpl, urls, CONVERSATION_PATHS.vector, { method: 'GET', headers });
351
+ if (memberRes.status === 0 || !memberRes.json) {
352
+ return { ...base, ok: false, state: 'unreachable', error: memberRes.error || 'member_unreachable' };
353
+ }
354
+ if (memberRes.status < 200 || memberRes.status >= 300) {
355
+ return { ...base, ok: false, state: 'error', error: `member_vector_http_${memberRes.status}` };
356
+ }
357
+ const memberUrl = memberRes.url;
358
+ if (memberRes.json.seedDone !== true) return { ...base, ok: true, state: 'seed_pending' };
359
+
360
+ const localInstanceId = _string(local.instanceId)
361
+ || _string(env.CTM_INSTANCE_ID || env.CTM_INSTANCE_TAG);
362
+ let pullVector = (local.vector && typeof local.vector === 'object') ? { ...local.vector } : {};
363
+ let pushVector = (memberRes.json.vector && typeof memberRes.json.vector === 'object') ? { ...memberRes.json.vector } : {};
364
+
365
+ // 3. pull: member /changes -> local apply.
366
+ let pulled = 0;
367
+ for (let page = 0; page < maxPages; page += 1) {
368
+ const changes = await _memberRequest(fetchImpl, memberUrl, CONVERSATION_PATHS.changes, {
369
+ method: 'POST', headers, body: { since: pullVector, limit: pageLimit },
370
+ });
371
+ if (changes.status === 0) return { ...base, ok: false, state: 'unreachable', pulled, pushed: 0, error: changes.error || 'member_unreachable' };
372
+ if (changes.status < 200 || changes.status >= 300 || !changes.json) {
373
+ return { ...base, ok: false, state: 'error', pulled, pushed: 0, error: `member_changes_http_${changes.status}` };
374
+ }
375
+ const entries = Array.isArray(changes.json.entries) ? changes.json.entries : [];
376
+ if (entries.length) {
377
+ try {
378
+ const result = conversationSync.applyConversationChanges(db, { sourceInstanceId: instanceId, entries });
379
+ pulled += Number(result.applied) || 0;
380
+ pullVector = result.vector || pullVector;
381
+ } catch {
382
+ return { ...base, ok: false, state: 'error', pulled, pushed: 0, error: 'local_apply_failed' };
383
+ }
384
+ }
385
+ if (changes.json.more !== true) break;
386
+ }
387
+
388
+ // 4. push: local changes -> member /apply, page by page.
389
+ let pushed = 0;
390
+ for (let page = 0; page < maxPages; page += 1) {
391
+ const page2 = conversationSync.conversationChangesSince(db, { since: pushVector, limit: pageLimit });
392
+ const entries = page2.entries;
393
+ if (entries.length) {
394
+ const applied = await _memberRequest(fetchImpl, memberUrl, CONVERSATION_PATHS.apply, {
395
+ method: 'POST', headers, body: { sourceInstanceId: localInstanceId, entries },
396
+ });
397
+ if (applied.status === 0) return { ...base, ok: false, state: 'unreachable', pulled, pushed, error: applied.error || 'member_unreachable' };
398
+ if (applied.status < 200 || applied.status >= 300 || !applied.json) {
399
+ return { ...base, ok: false, state: 'error', pulled, pushed, error: `member_apply_http_${applied.status}` };
400
+ }
401
+ pushed += Number(applied.json.applied) || entries.length;
402
+ pushVector = (applied.json.vector && typeof applied.json.vector === 'object') ? applied.json.vector : pushVector;
403
+ }
404
+ if (page2.more !== true) break;
405
+ }
406
+
407
+ return { ...base, ok: true, state: 'synced', pulled, pushed };
408
+ }
409
+
410
+ // Exponential backoff with jitter, mirroring hosted-sync-worker's _failureDelayMs so a member that
411
+ // just failed is not re-dialled every tick.
412
+ function _failureDelayMs(env, attempt, options = {}) {
413
+ const base = Math.max(1000, Number(env.CTM_SYNC_FAILURE_BACKOFF_MS || options.failureBackoffMs || DEFAULT_FAILURE_BACKOFF_MS) || DEFAULT_FAILURE_BACKOFF_MS);
414
+ const max = Math.max(base, Number(env.CTM_SYNC_FAILURE_BACKOFF_MAX_MS || options.failureBackoffMaxMs || DEFAULT_FAILURE_BACKOFF_MAX_MS) || DEFAULT_FAILURE_BACKOFF_MAX_MS);
415
+ const exp = Math.min(max, base * Math.max(1, 2 ** Math.max(0, attempt - 1)));
416
+ const jitter = Math.floor(exp * 0.2 * Math.random());
417
+ return Math.min(max, exp + jitter);
418
+ }
419
+
420
+ function _resolveSelfInstanceId(deps = {}, env = process.env) {
421
+ const source = deps.selfInstanceId;
422
+ if (typeof source === 'function') {
423
+ try { return _string(source()); } catch { return ''; }
424
+ }
425
+ if (source) return _string(source);
426
+ return _string(env.CTM_INSTANCE_ID || env.CTM_INSTANCE_TAG || '');
427
+ }
428
+
429
+ function _candidateMembers(db, selfInstanceId) {
430
+ const { machines } = machineRegistry.listMachines({ db });
431
+ return (machines || []).filter((m) => m
432
+ && m.enabled
433
+ && m.health === 'reachable'
434
+ && m.instanceId
435
+ && m.instanceId !== selfInstanceId);
436
+ }
437
+
438
+ /**
439
+ * One hub round: every reachable registered member, sequentially (D-2 — only the hub dials, and a
440
+ * slow member must not fan out into concurrent dials). A member inside its failure-backoff window is
441
+ * skipped (and its last state reported) so it never delays the others. After the round, tombstones
442
+ * are pruned only when every member that was attempted ended 'synced'.
443
+ *
444
+ * @param {object} deps { db, fetchImpl, env, peerHeadersFor, walle, now, selfInstanceId }
445
+ */
446
+ async function runBrainMemberRound(deps = {}) {
447
+ if (_inFlight) return { ok: true, skipped: 'in_flight', members: [], pruned: 0 };
448
+ const env = deps.env || process.env;
449
+ if (!_brainSyncEnabled(env)) {
450
+ return { ok: true, disabled: true, members: [], pruned: 0 };
451
+ }
452
+ _inFlight = _runRound(deps).finally(() => { _inFlight = null; });
453
+ return _inFlight;
454
+ }
455
+
456
+ async function _runRound(deps = {}) {
457
+ const env = deps.env || process.env;
458
+ const nowMs = _now(deps);
459
+ const selfInstanceId = _resolveSelfInstanceId(deps, env);
460
+ const walle = deps.walle || require('./walle-client');
461
+ const candidates = _candidateMembers(deps.db, selfInstanceId);
462
+ // WP-5: resolved once per round — the CTM store handle the conversation feed reads (see
463
+ // _conversationDb). Null means this node has no conversation store to exchange, and the leg is
464
+ // skipped without a per-member `conversation` verdict.
465
+ const conversationEnabled = _conversationSyncEnabled(env);
466
+ const conversationDb = conversationEnabled ? _conversationDb(deps.db) : null;
467
+ const members = [];
468
+ for (const member of candidates) {
469
+ const instanceId = _string(member.instanceId);
470
+ const prior = _memberState.get(instanceId);
471
+ if (prior && prior.nextAt > nowMs) {
472
+ members.push({ instanceId, state: prior.state || 'unreachable', pulled: 0, pushed: 0, backoff: true });
473
+ continue;
474
+ }
475
+ let result;
476
+ try {
477
+ result = await syncMemberBrainOnce(member, {
478
+ fetchImpl: deps.fetchImpl,
479
+ peerHeadersFor: deps.peerHeadersFor,
480
+ walle,
481
+ env,
482
+ now: deps.now,
483
+ pageLimit: deps.pageLimit,
484
+ maxPages: deps.maxPages,
485
+ // WP-4 (§5 D-6): the join state machine's own seams. Absent -> memory-join's defaults.
486
+ chunksPerTick: deps.chunksPerTick,
487
+ readChunk: deps.readChunk,
488
+ });
489
+ } catch (err) {
490
+ result = { ok: false, member: instanceId, state: 'error', pulled: 0, pushed: 0, error: err?.message || String(err) };
491
+ }
492
+ // WP-5 (§5 D-3, §6.8): the conversation-history leg runs AFTER the brain leg and is
493
+ // independent of its verdict — a member mid-join still exchanges items (no snapshot needed),
494
+ // and a broken conversation leg must never turn a green brain leg red.
495
+ let conversation = null;
496
+ if (conversationDb) {
497
+ try {
498
+ conversation = await syncMemberConversationOnce(member, {
499
+ db: conversationDb,
500
+ fetchImpl: deps.fetchImpl,
501
+ peerHeadersFor: deps.peerHeadersFor,
502
+ env,
503
+ pageLimit: deps.pageLimit,
504
+ maxPages: deps.maxPages,
505
+ });
506
+ } catch (err) {
507
+ conversation = { ok: false, state: 'error', pulled: 0, pushed: 0, error: err?.message || String(err) };
508
+ }
509
+ } else if (!conversationEnabled) {
510
+ conversation = { ok: true, state: 'disabled', pulled: 0, pushed: 0 };
511
+ }
512
+ members.push({
513
+ instanceId,
514
+ state: result.state,
515
+ pulled: result.pulled,
516
+ pushed: result.pushed,
517
+ ...(result.error ? { error: result.error } : {}),
518
+ ...(result.detail ? { detail: result.detail } : {}),
519
+ ...(conversation ? { conversation } : {}),
520
+ });
521
+ if (result.state === 'unreachable' || result.state === 'error') {
522
+ const attempt = ((prior && Number(prior.attempts)) || 0) + 1;
523
+ _memberState.set(instanceId, { attempts: attempt, nextAt: nowMs + _failureDelayMs(env, attempt, deps), state: result.state });
524
+ } else {
525
+ _memberState.delete(instanceId);
526
+ }
527
+ }
528
+
529
+ let pruned = 0;
530
+ if (members.length > 0 && members.every((m) => m.state === 'synced')) {
531
+ try {
532
+ const res = await _walleRequest(walle, LOCAL_PATHS.pruneTombstones, { method: 'POST', body: {} });
533
+ if (res.json && Number.isFinite(Number(res.json.pruned))) pruned = Number(res.json.pruned) || 0;
534
+ } catch {
535
+ pruned = 0; // REPORT: prune is best-effort; a failure leaves the tombstones and the next round retries
536
+ }
537
+ }
538
+
539
+ _lastRound = { at: new Date(nowMs).toISOString(), members };
540
+ // A member mid-join is not an error (it is making progress); only a broken exchange flips `ok`.
541
+ return { ok: members.every((m) => JOIN_PROGRESS_STATES.has(m.state) || m.state === 'synced' || m.state === 'seed_pending'), members, pruned };
542
+ }
543
+
544
+ /** Observability only (no route in this WP): the last round's verdict per member. */
545
+ function memorySyncStatus() {
546
+ return {
547
+ lastRoundAt: _lastRound ? _lastRound.at : null,
548
+ members: _lastRound ? _lastRound.members : [],
549
+ };
550
+ }
551
+
552
+ /** Test seam: forget the per-member backoff + last round (module state survives a process). */
553
+ function _resetMemorySyncStateForTests() {
554
+ _memberState.clear();
555
+ _lastRound = null;
556
+ _inFlight = null;
557
+ }
558
+
559
+ module.exports = {
560
+ syncMemberBrainOnce,
561
+ syncMemberConversationOnce,
562
+ runBrainMemberRound,
563
+ memorySyncStatus,
564
+ _resetMemorySyncStateForTests,
565
+ };
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ // v57 (opencode-lead-and-delegate-model-selection-2026-10-28.html §6.4, §8 WP-4, §9 S-17/S-18):
4
+ // `delegate_pref_mirror` and `session_subagent_pick_launch` each gain 'opencode' as a valid
5
+ // `agent`. Both are CHECK constraints, and SQLite cannot ALTER a CHECK -- so an existing table is
6
+ // REBUILT: rename, recreate, copy every row and every column, drop, all inside ONE transaction.
7
+ //
8
+ // v48 is untouched: it stays the published record of the schema IT created. This is the forward
9
+ // step a database stamped v48..v56 needs, and the read path (lib/storage/delegate-pref-mirror-store)
10
+ // is the only consumer of either table.
11
+ //
12
+ // Idempotence is decided by the SCHEMA, never by the version counter: a table whose sqlite_master.sql
13
+ // already admits 'opencode' is left byte-for-byte alone, so a re-run of the ladder body (a restored
14
+ // snapshot, a partially upgraded file, or a database that already took this step) writes nothing.
15
+ //
16
+ // The recreate text is the table's OWN stored DDL with only the `agent` CHECK widened, so no column,
17
+ // default, ordinal position or other constraint can drift from what the database already has -- the
18
+ // v49 owner columns and v50 `effort` column ride along automatically.
19
+
20
+ // The exact v48 text of the agent CHECK, in both tables. v48 is frozen, so this literal is stable;
21
+ // a table whose DDL does not match it is refused rather than silently rebuilt with a guessed shape.
22
+ const AGENT_CHECK = "CHECK (agent IN ('claude','codex'))";
23
+ const AGENT_CHECK_OPENCODE = "CHECK (agent IN ('claude','codex','opencode'))";
24
+
25
+ const MIRROR_TABLE = 'delegate_pref_mirror';
26
+ const LAUNCH_TABLE = 'session_subagent_pick_launch';
27
+
28
+ function _tableSql(db, table) {
29
+ const row = db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?").get(table);
30
+ return row && typeof row.sql === 'string' ? row.sql : null;
31
+ }
32
+
33
+ // Widen ONE table's agent CHECK. Returns true when the table was rebuilt, false when there was
34
+ // nothing to do (absent table, or a table that already admits opencode).
35
+ function _widenAgentCheck(db, table) {
36
+ const sql = _tableSql(db, table);
37
+ if (!sql) return false;
38
+ if (sql.includes("'opencode'")) return false;
39
+ if (!sql.includes(AGENT_CHECK)) {
40
+ throw new Error(`v57: ${table} has no claude/codex agent CHECK to widen`);
41
+ }
42
+ const widened = sql.split(AGENT_CHECK).join(AGENT_CHECK_OPENCODE);
43
+ const staging = `${table}__v57`;
44
+ // ALTER TABLE RENAME keeps the row data; the fresh CREATE below carries the widened CHECK. The
45
+ // INSERT copies by position, which is exactly why the recreate text must be the stored DDL (the
46
+ // two tables have different column counts at the head of the ladder).
47
+ db.exec(`ALTER TABLE ${table} RENAME TO ${staging}`);
48
+ db.exec(widened);
49
+ db.exec(`INSERT INTO ${table} SELECT * FROM ${staging}`);
50
+ db.exec(`DROP TABLE ${staging}`);
51
+ return true;
52
+ }
53
+
54
+ function migrateToV57({ getDb, setSchemaVersion, log = console } = {}) {
55
+ if (typeof getDb !== 'function' || typeof setSchemaVersion !== 'function') throw new TypeError('migration dependencies are required');
56
+ const db = getDb();
57
+ const run = db.transaction(() => {
58
+ const mirrored = _widenAgentCheck(db, MIRROR_TABLE);
59
+ const launched = _widenAgentCheck(db, LAUNCH_TABLE);
60
+ return (mirrored ? 1 : 0) + (launched ? 1 : 0);
61
+ });
62
+ const rebuilt = run();
63
+ setSchemaVersion(57);
64
+ log.log(`[db] Schema migrated to v57 (delegate pref mirror + subagent pick launch admit opencode): ${rebuilt} table(s) rebuilt`);
65
+ return { rebuilt };
66
+ }
67
+
68
+ module.exports = { migrateToV57, AGENT_CHECK, AGENT_CHECK_OPENCODE };