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
@@ -282,6 +282,19 @@ function createMainloopObservability(deps) {
282
282
  : _activeSyncSegment;
283
283
  const threshold = _attributedOp ? _lagThreshold : _unknownLagThreshold;
284
284
  const name = _attributedOp ? _attributedOp.name : '(unknown)';
285
+ // WP-23 (D-33): the watchdog worker's own measured suspension, read from shared memory.
286
+ // It is the only in-process observer that is frozen by a suspension but not by a
287
+ // main-thread block, so whenever it has ticked we decide sleep-vs-freeze from it and
288
+ // never from CPU or timed work. Absent (CTM_MAIN_WATCHDOG=0, or no tick yet) the legacy
289
+ // rule below applies unchanged.
290
+ //
291
+ // WP-2 (D-6): read it BEFORE the evidence build so the suspension is available as an
292
+ // input to the classifier — the ladder labels a mostly-suspended block `process-suspended`
293
+ // instead of the inferential `off-cpu-unattributed`/`main_descheduled`.
294
+ const _watchdog = _readWatchdogSuspension(lagIntervalMs);
295
+ const _suspendedMs = _watchdog
296
+ ? Math.max(0, Math.min(_watchdog.suspendedTo, now) - Math.max(_watchdog.suspendedFrom, now - blockedMs))
297
+ : 0;
285
298
  let _blockEvidence = null;
286
299
  try {
287
300
  if (blockedMs >= 200) {
@@ -293,17 +306,12 @@ function createMainloopObservability(deps) {
293
306
  mainThreadCpuDeltaMs: _mainThreadCpuDeltaMs,
294
307
  processCpuDeltaMs: _processCpuDeltaMs,
295
308
  activeSyncEvidence: _activeSyncEvidence,
309
+ suspendedMs: _suspendedMs,
296
310
  },
297
311
  );
298
312
  }
299
313
  _lagEvidenceBaseline = currentEvidence;
300
314
  } catch { /* expected: evidence collection must never break the detector */ }
301
- // WP-23 (D-33): the watchdog worker's own measured suspension, read from shared memory.
302
- // It is the only in-process observer that is frozen by a suspension but not by a
303
- // main-thread block, so whenever it has ticked we decide sleep-vs-freeze from it and
304
- // never from CPU or timed work. Absent (CTM_MAIN_WATCHDOG=0, or no tick yet) the legacy
305
- // rule below applies unchanged.
306
- const _watchdog = _readWatchdogSuspension(lagIntervalMs);
307
315
  const _suspectedSleep = perfTracker.isSuspectedSleepBlock(blockedMs, {
308
316
  idleDuringGap: _idleDuringGap,
309
317
  recentSyncEvidence: _recentSyncEvidence,
@@ -313,9 +321,6 @@ function createMainloopObservability(deps) {
313
321
  watchdog: _watchdog || undefined,
314
322
  gapStartMs: now - blockedMs,
315
323
  });
316
- const _suspendedMs = _watchdog
317
- ? Math.max(0, Math.min(_watchdog.suspendedTo, now) - Math.max(_watchdog.suspendedFrom, now - blockedMs))
318
- : 0;
319
324
  let lagSample = null;
320
325
  try {
321
326
  lagSample = perfTracker.recordEventLoopLagSample(name, delta, {
@@ -498,7 +498,11 @@ function createMediaDraftOwner(deps) {
498
498
  const next = stampDraftMutation(item, {
499
499
  ...item,
500
500
  state: input.state,
501
- submittedInputId: String(input.submittedInputId || item.submittedInputId || ''),
501
+ // WP-2 (composer-image-chip-stuck-submission-recovery-2026-10-30.html §6): `''` still
502
+ // means "keep the current id" (unchanged default); the explicit clearSubmittedInputId
503
+ // flag is the ONLY way to drop a submittedInputId, and it exists for the stale-draft
504
+ // recovery that returns a frozen draft to attached.
505
+ submittedInputId: input.clearSubmittedInputId === true ? '' : String(input.submittedInputId || item.submittedInputId || ''),
502
506
  deliveryAttemptId: String(input.attemptId || item.deliveryAttemptId || ''),
503
507
  stateReason: String(input.reason || (input.state === 'ready' ? '' : item.stateReason || '')),
504
508
  updatedAt: new Date().toISOString(),
@@ -694,12 +698,21 @@ function createMediaDraftOwner(deps) {
694
698
  && Date.parse(item.updatedAt || item.createdAt || 0) < before);
695
699
  const recovered = [];
696
700
  for (const candidate of candidates) {
701
+ // R-3 (WP-2): a `frozen` draft whose delivery was CONFIRMED was interrupted before Enter,
702
+ // so the image is still in the agent composer -- `failed` would lie about it (and would
703
+ // re-deliver a token that is already there). It returns to `attached` with the stale
704
+ // submittedInputId cleared, so the next send freezes it again. A `delivery_pending`
705
+ // draft, or a `frozen` one the provider never acknowledged, keeps today's failure.
706
+ const interruptedBeforeEnter = candidate.state === 'frozen' && Boolean(candidate.deliveryConfirmedAt);
697
707
  const item = await setDraftState({
698
708
  sessionId,
699
709
  attachmentId: candidate.attachmentId,
700
710
  expectedRevision: candidate.revision,
701
- state: 'failed',
702
- reason: 'Image delivery was interrupted. Retry or remove it.',
711
+ state: interruptedBeforeEnter ? 'attached' : 'failed',
712
+ clearSubmittedInputId: interruptedBeforeEnter,
713
+ reason: interruptedBeforeEnter
714
+ ? 'Send was interrupted before Enter; image is still in the composer.'
715
+ : 'Image delivery was interrupted. Retry or remove it.',
703
716
  });
704
717
  recovered.push(item);
705
718
  }
@@ -0,0 +1,337 @@
1
+ 'use strict';
2
+
3
+ // WP-4b (docs/shared-memory-every-machine-2026-10-31.html §5 D-6, §6.7): the HUB side of the join
4
+ // state machine — a member whose vector does not name the hub origin (WP-3's 'join_required') takes
5
+ // the hub's consistent snapshot in 4 MiB sha256-verified chunks and imports it, instead of idling.
6
+ //
7
+ // advanceMemberJoin(member, ctx) -> { state, detail }
8
+ // state ∈ 'seed_pending' | 'join_snapshot' | 'join_transfer' | 'join_import' | 'join_done'
9
+ // | 'join_failed'
10
+ //
11
+ // D-6: (1) the hub takes a consistent snapshot of its own brain, (2) reads the snapshot's own
12
+ // vector, (3) ships it in 4 MiB chunks, (4) the member imports the replicated tables, (5) the
13
+ // member's vector becomes the snapshot's, so hub changes written during the transfer arrive through
14
+ // the ordinary D-3 feed. P-1: the vector travels FROM THE SNAPSHOT FILE, never the live DB.
15
+ //
16
+ // The per-member progress Map is MODULE state by design, exactly like memory-sync.js's backoff
17
+ // bookkeeping: a hub restart loses it and the transfer RESUMES from the member's own `received[]`
18
+ // (P-3, checkpoints on both sides) — never from the hub's memory. A member restart mid-import is
19
+ // Wall-E's job (WP-4a): this side only polls /join/status until it reads 'done' or 'failed'.
20
+
21
+ const fsp = require('node:fs/promises');
22
+
23
+ // §6.7: the transfer chunk size. Shared with lib/api/memory-sync-routes.js — the sender writes at
24
+ // offset index*CHUNK_BYTES and the receiver reads at the same offset, so the two must never drift.
25
+ const CHUNK_BYTES = 4 * 1024 * 1024;
26
+ const DEFAULT_CHUNKS_PER_TICK = 32;
27
+ // §6.7/D-6: a failed join is retried from step 1 after this long, never on every tick.
28
+ const FAILED_RETRY_MS = 60 * 60 * 1000;
29
+ const JOIN_ID_RE = /^[A-Za-z0-9._:-]{1,200}$/;
30
+ const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
31
+
32
+ // The hub's LOCAL Wall-E join route (loopback + CTM token, like LOCAL_PATHS in memory-sync.js).
33
+ const SNAPSHOT_PATH = '/api/walle/sync/v2/join/snapshot';
34
+ // The member's CTM front routes (D-12): the hub dials the member's CTM, which forwards to its Wall-E.
35
+ const MEMBER_JOIN_PATHS = Object.freeze({
36
+ chunk: '/api/sync/brain/v2/join/chunk',
37
+ transfer: '/api/sync/brain/v2/join/transfer',
38
+ import: '/api/sync/brain/v2/join/import',
39
+ status: '/api/sync/brain/v2/join/status',
40
+ });
41
+
42
+ // instanceId -> { joinId, snapshot, phase, memberPath, imported, restarted, error, nextAttemptAt }.
43
+ const _progress = new Map();
44
+
45
+ function _string(value, max = 200) {
46
+ if (typeof value === 'string') return value.trim().slice(0, max);
47
+ return value == null ? '' : String(value).trim().slice(0, max);
48
+ }
49
+
50
+ function _nowOf(ctx = {}) {
51
+ return typeof ctx.now === 'function' ? Number(ctx.now()) || Date.now() : Date.now();
52
+ }
53
+
54
+ function _chunkCount(bytes) {
55
+ const size = Math.max(0, Number(bytes) || 0);
56
+ return Math.max(1, Math.ceil(size / CHUNK_BYTES));
57
+ }
58
+
59
+ function _joinUrl(base, pathname) {
60
+ return `${String(base).replace(/\/+$/, '')}${pathname}`;
61
+ }
62
+
63
+ // One local Wall-E call. requestJson resolves { status, json } for any answer and rejects only on a
64
+ // transport fault — the caller maps both (memory-sync.js's _walleRequest shape).
65
+ async function _walleRequest(walle, pathname, { method = 'GET', body = null } = {}) {
66
+ try {
67
+ const res = await walle.requestJson(pathname, body == null ? { method } : { method, body });
68
+ return { status: Number(res && res.status) || 0, json: (res && res.json) || null };
69
+ } catch (err) {
70
+ return { status: 0, json: null, error: err?.message || String(err) };
71
+ }
72
+ }
73
+
74
+ // One member request, with the AbortController + unref'd timer shape memory-sync.js uses so a hung
75
+ // member can never hold the tick open past the timeout. Never throws: a transport fault is reported
76
+ // as `{ status: 0 }` and mapped by the caller.
77
+ async function _memberRequest(fetchImpl, url, pathname, { method = 'GET', body = null, headers = {} } = {}) {
78
+ const fetchFn = fetchImpl || global.fetch;
79
+ if (typeof fetchFn !== 'function') return { status: 0, json: null, error: 'fetch_unavailable' };
80
+ const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
81
+ const timer = controller ? setTimeout(() => controller.abort(), DEFAULT_REQUEST_TIMEOUT_MS) : null;
82
+ if (timer && typeof timer.unref === 'function') timer.unref();
83
+ try {
84
+ const payload = body == null ? null : JSON.stringify(body);
85
+ const res = await fetchFn(_joinUrl(url, pathname), {
86
+ method,
87
+ headers: {
88
+ ...headers,
89
+ ...(payload ? { 'Content-Type': 'application/json' } : {}),
90
+ Accept: 'application/json',
91
+ },
92
+ ...(payload ? { body: payload } : {}),
93
+ signal: controller ? controller.signal : undefined,
94
+ });
95
+ let json = null;
96
+ try { json = await res.json(); } catch { json = null; /* expected: a non-JSON body is degraded, mapped by the caller */ }
97
+ return { status: Number(res && res.status) || 0, json };
98
+ } catch (err) {
99
+ return { status: 0, json: null, error: err?.message || String(err) };
100
+ } finally {
101
+ if (timer) clearTimeout(timer);
102
+ }
103
+ }
104
+
105
+ // The production chunk reader (the `readChunk` seam): a bounded read at an absolute offset. A short
106
+ // read is a hard error — a truncated chunk would assemble to a wrong sha256 on the member, not to a
107
+ // short file.
108
+ async function _readChunkDefault(filePath, offset, length) {
109
+ const handle = await fsp.open(filePath, 'r');
110
+ try {
111
+ const buffer = Buffer.alloc(length);
112
+ const { bytesRead } = await handle.read(buffer, 0, length, offset);
113
+ if (bytesRead !== length) throw new Error(`short_read_${bytesRead}_of_${length}`);
114
+ return buffer;
115
+ } finally {
116
+ try { await handle.close(); } catch { /* expected: a close fault must not mask the read verdict */ }
117
+ }
118
+ }
119
+
120
+ // R-5 (§6.6): `freshSnapshot` rides on the failed record so the retry after the backoff asks the hub's
121
+ // own Wall-E for a NEW snapshot instead of reusing the 6 h window's file the member already refused.
122
+ function _fail(instanceId, nowMs, error, { freshSnapshot = false } = {}) {
123
+ _progress.set(instanceId, { phase: 'failed', error, nextAttemptAt: nowMs + FAILED_RETRY_MS, freshSnapshot });
124
+ return { state: 'join_failed', detail: { error } };
125
+ }
126
+
127
+ /**
128
+ * Advance one member's join by one tick. Called by memory-sync.js's `syncMemberBrainOnce` exactly
129
+ * where it used to answer the idle 'join_required' state.
130
+ *
131
+ * @param {{instanceId: string, urls?: string[], url?: string}} member
132
+ * @param {object} ctx { localInstanceId, walle, fetchImpl, headers, memberUrl, now, chunksPerTick, readChunk }
133
+ */
134
+ async function advanceMemberJoin(member = {}, ctx = {}) {
135
+ const instanceId = _string(member.instanceId);
136
+ const walle = ctx.walle;
137
+ const fetchImpl = ctx.fetchImpl || global.fetch;
138
+ const headers = ctx.headers || {};
139
+ const memberUrl = _string(ctx.memberUrl, 1000);
140
+ const localInstanceId = _string(ctx.localInstanceId);
141
+ const chunksPerTick = Math.max(1, Number(ctx.chunksPerTick) || DEFAULT_CHUNKS_PER_TICK);
142
+ const readChunk = typeof ctx.readChunk === 'function' ? ctx.readChunk : _readChunkDefault;
143
+ const nowMs = _nowOf(ctx);
144
+
145
+ if (!instanceId) return { state: 'join_failed', detail: { error: 'member_instance_required' } };
146
+ if (!memberUrl) return { state: 'join_failed', detail: { error: 'member_has_no_url' } };
147
+ if (!walle || typeof walle.requestJson !== 'function') return { state: 'join_failed', detail: { error: 'walle_client_missing' } };
148
+
149
+ let p = _progress.get(instanceId) || null;
150
+ let fresh = false;
151
+ // A failed join is retried from step 1 only after the 1 h backoff (D-6/§6.7).
152
+ if (p && p.phase === 'failed') {
153
+ if (nowMs < p.nextAttemptAt) return { state: 'join_failed', detail: { error: p.error, retryAt: p.nextAttemptAt } };
154
+ // R-5: the member refused the previous snapshot — remember to ask for a fresh one (§6.6).
155
+ fresh = p.freshSnapshot === true;
156
+ _progress.delete(instanceId);
157
+ p = null;
158
+ }
159
+
160
+ // 1. snapshot — once per joinId. 409 clock_seed_pending (D-9: the hub's own clock walk is not
161
+ // done) parks the member for the next tick with no partial state. A retry after a refused import
162
+ // carries {fresh:true} so createJoinSnapshot skips the 6 h reuse instead of re-offering the same file.
163
+ if (!p) {
164
+ const snap = await _walleRequest(walle, SNAPSHOT_PATH, { method: 'POST', body: fresh ? { fresh: true } : {} });
165
+ if (snap.status === 409) return { state: 'seed_pending', detail: { error: 'clock_seed_pending' } };
166
+ if (snap.status === 0 || !snap.json || snap.status < 200 || snap.status >= 300) {
167
+ return { state: 'join_snapshot', detail: { error: snap.error || `snapshot_http_${snap.status}` } };
168
+ }
169
+ const joinId = _string(snap.json.joinId, 200);
170
+ const snapshotPath = _string(snap.json.path, 1000);
171
+ const bytes = Number(snap.json.bytes);
172
+ if (!JOIN_ID_RE.test(joinId) || !snapshotPath || !Number.isFinite(bytes) || bytes < 0) {
173
+ return { state: 'join_snapshot', detail: { error: 'snapshot_bad_reply' } };
174
+ }
175
+ p = {
176
+ joinId,
177
+ snapshot: { path: snapshotPath, bytes, sha256: _string(snap.json.sha256, 64), vector: (snap.json.vector && typeof snap.json.vector === 'object') ? snap.json.vector : {} },
178
+ phase: 'transfer',
179
+ memberPath: '',
180
+ imported: false,
181
+ restarted: false,
182
+ };
183
+ _progress.set(instanceId, p);
184
+ }
185
+
186
+ // 2. transfer — ask the member what it already holds (P-3: resume after a hub restart), then send
187
+ // up to `chunksPerTick` missing chunks in index order.
188
+ if (p.phase === 'transfer') {
189
+ const transfer = await _memberRequest(fetchImpl, memberUrl, `${MEMBER_JOIN_PATHS.transfer}?joinId=${encodeURIComponent(p.joinId)}`, { method: 'GET', headers });
190
+ if (transfer.status === 0) return { state: 'join_transfer', detail: { error: transfer.error || 'member_unreachable' } };
191
+ if (transfer.status < 200 || transfer.status >= 300 || !transfer.json) {
192
+ return { state: 'join_transfer', detail: { error: `transfer_http_${transfer.status}` } };
193
+ }
194
+ const declared = Number(transfer.json.total);
195
+ const total = Number.isInteger(declared) && declared > 0 ? declared : _chunkCount(p.snapshot.bytes);
196
+ if (transfer.json.complete === true && _string(transfer.json.path, 1000)) {
197
+ p.memberPath = _string(transfer.json.path, 1000);
198
+ p.phase = 'import';
199
+ } else {
200
+ const received = new Set((Array.isArray(transfer.json.received) ? transfer.json.received : []).map(Number).filter((n) => Number.isInteger(n) && n >= 0));
201
+ let sent = 0;
202
+ for (let index = 0; index < total && sent < chunksPerTick; index += 1) {
203
+ if (received.has(index)) continue;
204
+ const offset = index * CHUNK_BYTES;
205
+ const length = Math.min(CHUNK_BYTES, p.snapshot.bytes - offset);
206
+ if (length <= 0) continue;
207
+ let data;
208
+ try {
209
+ data = await readChunk(p.snapshot.path, offset, length, ctx);
210
+ } catch (err) {
211
+ return { state: 'join_transfer', detail: { error: `snapshot_read_failed: ${err?.message || String(err)}` } };
212
+ }
213
+ if (!Buffer.isBuffer(data) || data.length !== length) {
214
+ return { state: 'join_transfer', detail: { error: `snapshot_chunk_short_${index}` } };
215
+ }
216
+ const res = await _memberRequest(fetchImpl, memberUrl, MEMBER_JOIN_PATHS.chunk, {
217
+ method: 'POST',
218
+ headers,
219
+ body: {
220
+ joinId: p.joinId,
221
+ index,
222
+ total,
223
+ chunkBytes: length,
224
+ fileBytes: p.snapshot.bytes,
225
+ fileSha256: p.snapshot.sha256,
226
+ dataBase64: data.toString('base64'),
227
+ },
228
+ });
229
+ sent += 1;
230
+ if (res.status === 507 || (res.json && res.json.error === 'insufficient_storage')) {
231
+ return _fail(instanceId, nowMs, 'insufficient_storage');
232
+ }
233
+ if (res.status === 422 || (res.json && res.json.error === 'sha256_mismatch')) {
234
+ // The member deleted its .part + sidecar; restart the transfer from 0 exactly once.
235
+ if (!p.restarted) {
236
+ p.restarted = true;
237
+ p.memberPath = '';
238
+ return { state: 'join_transfer', detail: { error: 'sha256_mismatch', restarted: true } };
239
+ }
240
+ return _fail(instanceId, nowMs, 'sha256_mismatch');
241
+ }
242
+ if (res.status === 0) return { state: 'join_transfer', detail: { error: res.error || 'member_unreachable' } };
243
+ if (res.status < 200 || res.status >= 300 || !res.json) {
244
+ return { state: 'join_transfer', detail: { error: `chunk_http_${res.status}` } };
245
+ }
246
+ if (res.json.complete === true && _string(res.json.path, 1000)) {
247
+ p.memberPath = _string(res.json.path, 1000);
248
+ p.phase = 'import';
249
+ break;
250
+ }
251
+ }
252
+ if (p.phase !== 'import') return { state: 'join_transfer', detail: { sent, total } };
253
+ }
254
+ }
255
+
256
+ // 3. import + 4. poll — the member hands the file to its own brain owner (WP-4a); this side only
257
+ // drives and observes. The import POST is issued at most once per joinId.
258
+ if (p.phase === 'import') {
259
+ if (!p.imported) {
260
+ if (!p.memberPath) {
261
+ const again = await _memberRequest(fetchImpl, memberUrl, `${MEMBER_JOIN_PATHS.transfer}?joinId=${encodeURIComponent(p.joinId)}`, { method: 'GET', headers });
262
+ const path = _string(again.json && again.json.path, 1000);
263
+ if (again.status >= 200 && again.status < 300 && again.json && again.json.complete === true && path) {
264
+ p.memberPath = path;
265
+ } else {
266
+ p.phase = 'transfer';
267
+ return { state: 'join_transfer', detail: { error: 'member_path_missing' } };
268
+ }
269
+ }
270
+ const posted = await _memberRequest(fetchImpl, memberUrl, MEMBER_JOIN_PATHS.import, {
271
+ method: 'POST',
272
+ headers,
273
+ body: { joinId: p.joinId, path: p.memberPath, sha256: p.snapshot.sha256, homeInstanceId: localInstanceId },
274
+ });
275
+ if (posted.status === 0) return { state: 'join_import', detail: { error: posted.error || 'member_unreachable' } };
276
+ // 409 join_in_progress: a previous tick's import is already running — poll it, never re-post.
277
+ if (posted.status === 409) p.imported = true;
278
+ // R-9: a 4xx import reply is terminal for THIS attempt — fail with the status + the member's own
279
+ // error code instead of re-POSTing the same refused import on every round with no backoff.
280
+ else if (posted.status >= 400 && posted.status < 500) {
281
+ return _fail(instanceId, nowMs, `import_http_${posted.status}:${_string(posted.json && posted.json.error, 160)}`);
282
+ // 5xx / odd replies stay non-terminal — a member restart or a transient fault is retried next tick.
283
+ } else if (posted.status < 200 || posted.status >= 300 || !posted.json) {
284
+ return { state: 'join_import', detail: { error: `import_http_${posted.status}` } };
285
+ // R-4/R-5: a 2xx whose state is 'failed' means the member cannot resume THIS snapshot — ask for a
286
+ // fresh one on the retry instead of polling a file it has already refused.
287
+ } else if (_string(posted.json.state, 40) === 'failed') {
288
+ return _fail(instanceId, nowMs, 'member_join_not_resumable', { freshSnapshot: true });
289
+ } else p.imported = true;
290
+ }
291
+ const status = await _memberRequest(fetchImpl, memberUrl, MEMBER_JOIN_PATHS.status, { method: 'GET', headers });
292
+ if (status.status === 0) return { state: 'join_import', detail: { error: status.error || 'member_unreachable' } };
293
+ if (status.status < 200 || status.status >= 300 || !status.json) {
294
+ return { state: 'join_import', detail: { error: `status_http_${status.status}` } };
295
+ }
296
+ const memberState = _string(status.json.state, 40);
297
+ if (memberState === 'done') {
298
+ _progress.delete(instanceId);
299
+ return {
300
+ state: 'join_done',
301
+ detail: {
302
+ joinId: p.joinId,
303
+ memberPath: p.memberPath,
304
+ imported: Number(status.json.imported) || 0,
305
+ rekeyed: Number(status.json.rekeyed) || 0,
306
+ vector: p.snapshot.vector,
307
+ },
308
+ };
309
+ }
310
+ if (memberState === 'failed') return _fail(instanceId, nowMs, _string(status.json.error, 200) || 'join_import_failed');
311
+ return { state: 'join_import', detail: { joinId: p.joinId, memberState: memberState || 'none' } };
312
+ }
313
+
314
+ return { state: 'join_failed', detail: { error: 'join_phase_unknown' } };
315
+ }
316
+
317
+ /** Test seam: forget every member's join progress (module state survives a process). */
318
+ function _resetMemberJoinStateForTests() {
319
+ _progress.clear();
320
+ }
321
+
322
+ /** Observability/test seam: the raw per-member progress record (never mutated by callers). */
323
+ function memberJoinProgress(instanceId) {
324
+ const p = _progress.get(_string(instanceId));
325
+ return p ? { ...p } : null;
326
+ }
327
+
328
+ module.exports = {
329
+ advanceMemberJoin,
330
+ memberJoinProgress,
331
+ _resetMemberJoinStateForTests,
332
+ CHUNK_BYTES,
333
+ MEMBER_JOIN_PATHS,
334
+ SNAPSHOT_PATH,
335
+ FAILED_RETRY_MS,
336
+ JOIN_ID_RE,
337
+ };