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,727 @@
1
+ 'use strict';
2
+
3
+ // WP-1 (docs/remote-box-model-access-2026-10-26.html §6.1, §5.1 R-3, §5.3 D-2/D-3/D-9):
4
+ // "the box answers what it can serve" — this module is the BOX side of that answer.
5
+ //
6
+ // It makes ONE unauthenticated `GET <origin>/` per distinct origin a configured route points at,
7
+ // and reports the network verdict. It is deliberately NOT the proxy's authenticated generation
8
+ // probe (D-3): an authenticated probe would send the key to that host and spend tokens, and it
9
+ // could not separate "the network path is dead" (the Spark's corporate gateway: 8 s timeout) from
10
+ // "the host answered and refused us" (401 in 0.12 s) — the second is exactly what a live vendor
11
+ // looks like to a credential-less caller.
12
+ //
13
+ // Three rules are load-bearing and enforced by tests/model-availability.test.js:
14
+ // * NOTHING from a route row is ever sent. No authorization / x-api-key / x-portkey-* header, no
15
+ // header copied off the row — the probe carries no headers at all (node adds Host, nothing else).
16
+ // * "reachable" means the network path, never "the key works". Any HTTP status is reachable; the
17
+ // key is still checked at launch time by the route-readiness probe.
18
+ // * Timeouts are the signal, so the caps are part of the contract: connect+TLS within
19
+ // PROBE_CONNECT_MS, the whole probe within PROBE_TOTAL_MS.
20
+ //
21
+ // Results are cached per origin for ORIGIN_TTL_MS, single-flighted per origin, and run at most
22
+ // PROBE_CONCURRENCY at a time (D-9: one probe per origin per 10 min, never a poll).
23
+ const http = require('http');
24
+ const https = require('https');
25
+
26
+ /** Connect + TLS cap. The Spark's unreachable case needs > 3 s to time out (observed 8.0 s). */
27
+ const PROBE_CONNECT_MS = 3000;
28
+ /** Whole-probe cap, connect included. */
29
+ const PROBE_TOTAL_MS = 5000;
30
+ /** How long one origin's verdict is reused. */
31
+ const ORIGIN_TTL_MS = 10 * 60 * 1000;
32
+ /** At most this many probes are in flight at once. */
33
+ const PROBE_CONCURRENCY = 4;
34
+ /** `?refresh=1` may drop one caller's cache at most once per this window (§6.1). */
35
+ const REFRESH_MIN_INTERVAL_MS = 60 * 1000;
36
+
37
+ // The adapter default each provider type resolves to when its row carries no `base_url` (§4: the
38
+ // deepseek/moonshot constants are the modules' own export, asserted equal in the unit test; ollama's
39
+ // is wall-e/llm/ollama.js:254). Keys are the `type` values in model_providers.
40
+ const DEFAULT_ORIGINS = Object.freeze({
41
+ deepseek: 'https://api.deepseek.com',
42
+ moonshot: 'https://api.moonshot.ai',
43
+ ollama: 'http://127.0.0.1:11434',
44
+ openai: 'https://api.openai.com',
45
+ anthropic: 'https://api.anthropic.com',
46
+ google: 'https://generativelanguage.googleapis.com',
47
+ });
48
+
49
+ const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '[::1]']);
50
+
51
+ // Performance.now()-free wall clock: every stamp is taken through the injected `now` so a test can
52
+ // assert the TTL arithmetic without sleeping.
53
+ function _ms(now) {
54
+ return typeof now === 'function' ? Number(now()) || 0 : Date.now();
55
+ }
56
+
57
+ /** scheme://host[:port] of `value`, or null when it is not an http(s) URL. */
58
+ function originOf(value) {
59
+ const raw = String(value == null ? '' : value).trim();
60
+ if (!raw) return null;
61
+ let url = null;
62
+ try {
63
+ url = new URL(raw);
64
+ } catch {
65
+ return null; // expected: a malformed base_url is "unknown origin", never a probe target
66
+ }
67
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') return null;
68
+ if (!url.hostname) return null;
69
+ return url.origin;
70
+ }
71
+
72
+ function _urlOf(origin) {
73
+ const normalized = originOf(origin);
74
+ if (!normalized) return null;
75
+ try {
76
+ return new URL(normalized);
77
+ } catch {
78
+ return null; // expected: originOf already validated the shape; belt and braces
79
+ }
80
+ }
81
+
82
+ function _isLoopback(hostname) {
83
+ return LOOPBACK_HOSTS.has(String(hostname || '').toLowerCase());
84
+ }
85
+
86
+ function _result(reach, status, startedAt, now) {
87
+ return {
88
+ reach,
89
+ status: status == null ? null : Number(status),
90
+ connectMs: Math.max(0, _ms(now) - startedAt),
91
+ probedAt: new Date(startedAt).toISOString(),
92
+ };
93
+ }
94
+
95
+ /**
96
+ * ONE unauthenticated GET against `origin`'s root, with the two caps. NEVER throws and always
97
+ * resolves with `{ reach, status, connectMs, probedAt }`.
98
+ *
99
+ * `reach`: 'reachable' (any HTTP status came back) | 'unreachable' | 'no_server' (a loopback origin
100
+ * refused the connection — nothing is listening there).
101
+ */
102
+ function _probeOnce(url, requestFn, now) {
103
+ return new Promise((resolve) => {
104
+ const startedAt = _ms(now);
105
+ let settled = false;
106
+ let req = null;
107
+ let totalTimer = null;
108
+ const settle = (reach, status) => {
109
+ if (settled) return;
110
+ settled = true;
111
+ if (totalTimer) clearTimeout(totalTimer);
112
+ resolve(_result(reach, status, startedAt, now));
113
+ };
114
+ const abandon = () => {
115
+ try {
116
+ if (req && typeof req.destroy === 'function') req.destroy();
117
+ } catch {
118
+ /* expected: the socket is already gone — this is a probe being abandoned */
119
+ }
120
+ };
121
+ try {
122
+ totalTimer = setTimeout(() => {
123
+ abandon();
124
+ settle('unreachable', null);
125
+ }, PROBE_TOTAL_MS);
126
+ if (totalTimer && typeof totalTimer.unref === 'function') totalTimer.unref();
127
+
128
+ // No headers are copied from any route row and no credential header is ever added: the only
129
+ // header on the wire is the Host node itself must send.
130
+ req = requestFn({
131
+ protocol: url.protocol,
132
+ hostname: url.hostname,
133
+ port: url.port || (url.protocol === 'https:' ? 443 : 80),
134
+ path: '/',
135
+ method: 'GET',
136
+ timeout: PROBE_CONNECT_MS,
137
+ headers: {},
138
+ // A fresh socket per probe: a pooled keep-alive socket would skip the connect/TLS events
139
+ // below (so the 3 s cap could not be lifted) and would outlive the probe.
140
+ agent: false,
141
+ }, (res) => {
142
+ // Any status — 200, 401, 404, 403 — proves the network path. The body is drained and the
143
+ // socket released at once; a probe never reads a payload.
144
+ const status = res && res.statusCode != null ? res.statusCode : null;
145
+ try {
146
+ if (res && typeof res.resume === 'function') res.resume();
147
+ } catch {
148
+ /* expected: draining a response we deliberately never read */
149
+ }
150
+ abandon();
151
+ settle('reachable', status);
152
+ });
153
+ if (!req || typeof req.on !== 'function') {
154
+ settle('unreachable', null);
155
+ return;
156
+ }
157
+ req.on('error', (err) => {
158
+ // A refused loopback port is "nothing is listening here" (the Ollama rows), not
159
+ // "the network cannot get there" — the two render differently and mean different things.
160
+ const code = err && err.code;
161
+ if (code === 'ECONNREFUSED' && _isLoopback(url.hostname)) {
162
+ settle('no_server', null);
163
+ return;
164
+ }
165
+ settle('unreachable', null);
166
+ });
167
+ req.on('timeout', () => {
168
+ abandon();
169
+ settle('unreachable', null);
170
+ });
171
+ // `timeout` is a socket IDLE timeout, so on its own it would also cut a host that connected
172
+ // and is slow to answer. PROBE_CONNECT_MS caps connect + TLS only: once the socket is
173
+ // connected (https: TLS done) the idle timeout is lifted and PROBE_TOTAL_MS alone bounds the
174
+ // wait for the status line.
175
+ req.on('socket', (socket) => {
176
+ if (!socket || typeof socket.once !== 'function') return;
177
+ socket.once(url.protocol === 'https:' ? 'secureConnect' : 'connect', () => {
178
+ if (typeof req.setTimeout === 'function') req.setTimeout(0);
179
+ });
180
+ });
181
+ req.end();
182
+ } catch {
183
+ // expected: a synchronous throw from a malformed request is "the probe did not answer"
184
+ settle('unreachable', null);
185
+ }
186
+ });
187
+ }
188
+
189
+ // ---- at most PROBE_CONCURRENCY probes at once -------------------------------------------------
190
+ let _active = 0;
191
+ const _waiting = [];
192
+
193
+ function _acquire() {
194
+ if (_active < PROBE_CONCURRENCY) {
195
+ _active += 1;
196
+ return Promise.resolve();
197
+ }
198
+ // The slot is handed over by _release (the count never drops), so a caller arriving between the
199
+ // release and this waiter's continuation cannot take the same slot and exceed the cap.
200
+ return new Promise((resolve) => {
201
+ _waiting.push(resolve);
202
+ });
203
+ }
204
+
205
+ function _release() {
206
+ const next = _waiting.shift();
207
+ if (typeof next === 'function') {
208
+ next();
209
+ return;
210
+ }
211
+ _active = Math.max(0, _active - 1);
212
+ }
213
+
214
+ /**
215
+ * ONE unauthenticated probe of `origin`. Never throws; `request` and `now` are injectable so a test
216
+ * drives it with a fake socket and a fake clock (and can prove the header rule by recording).
217
+ *
218
+ * @returns {Promise<{reach: string, status: number|null, connectMs: number|null, probedAt: string|null}>}
219
+ */
220
+ function probeOrigin(origin, { request, now = Date.now } = {}) {
221
+ const url = _urlOf(origin);
222
+ if (!url) return Promise.resolve({ reach: 'unknown', status: null, connectMs: null, probedAt: null });
223
+ const requestFn = typeof request === 'function'
224
+ ? request
225
+ : (url.protocol === 'https:' ? https.request : http.request);
226
+ return _probeOnce(url, requestFn, now);
227
+ }
228
+
229
+ // ---- per-origin cache + single-flight ---------------------------------------------------------
230
+ /** origin -> { result, at }. Process state by design: a verdict is about NOW, and a restart may. */
231
+ const _cache = new Map();
232
+ /** origin -> in-flight promise, so N concurrent callers cost ONE probe. */
233
+ const _inflight = new Map();
234
+ /** caller key -> the hub CLOCK stamp of its last honoured `?refresh=1`. */
235
+ const _refreshAt = new Map();
236
+
237
+ /**
238
+ * One probe of `origin` behind the 10 min cache, the per-origin single flight AND the at-most-
239
+ * PROBE_CONCURRENCY gate — every probe this module makes on behalf of a caller goes through here, so
240
+ * the cap and the single flight are properties of the probing path, not of one implementation of it.
241
+ * `cache` is injectable (a Map) so a test isolates one case from the next; production uses the
242
+ * module map.
243
+ */
244
+ function _probeThrough(origin, probeFn, { request, now, cache, bypassCache }) {
245
+ const key = String(origin);
246
+ if (!bypassCache) {
247
+ const entry = cache.get(key);
248
+ if (entry && _ms(now) - Number(entry.at || 0) < ORIGIN_TTL_MS) return Promise.resolve(entry.result);
249
+ }
250
+ const pending = _inflight.get(key);
251
+ if (pending) return pending;
252
+ const run = _acquire()
253
+ .then(() => probeFn(origin, { request, now }))
254
+ .then((result) => {
255
+ _release();
256
+ cache.set(key, { result, at: _ms(now) });
257
+ return result;
258
+ }, (error) => {
259
+ _release();
260
+ throw error;
261
+ })
262
+ .finally(() => {
263
+ _inflight.delete(key);
264
+ });
265
+ _inflight.set(key, run);
266
+ return run;
267
+ }
268
+
269
+ /**
270
+ * The cached, single-flighted, concurrency-capped verdict for ONE origin -- the same path
271
+ * buildAvailability uses (R-4: no second probe, never a header from a route row).
272
+ * @returns {Promise<{reach:'reachable'|'unreachable'|'no_server'|'unknown', status:number|null, connectMs:number|null, probedAt:string|null}>}
273
+ */
274
+ function originVerdict(origin, { probe = probeOrigin, cache = _cache, now = Date.now, request } = {}) {
275
+ const normalized = originOf(origin);
276
+ if (!normalized) return Promise.resolve({ reach: 'unknown', status: null, connectMs: null, probedAt: null });
277
+ return _probeThrough(normalized, probe, { request, now, cache, bypassCache: false });
278
+ }
279
+
280
+ /** The `?refresh=1` gate: honest at most once per caller per window (§6.1). */
281
+ function _mayRefresh(callerKey, now) {
282
+ const key = String(callerKey || 'local');
283
+ const t = _ms(now);
284
+ const last = Number(_refreshAt.get(key) || 0);
285
+ if (last && t - last < REFRESH_MIN_INTERVAL_MS) return false;
286
+ _refreshAt.set(key, t);
287
+ return true;
288
+ }
289
+
290
+ function _isEnabled(row) {
291
+ return !(row && (row.enabled === 0 || row.enabled === false));
292
+ }
293
+
294
+ /** `keyed` follows the redacted list's own `has_credential` (model-admin.js:946). */
295
+ function _isKeyed(row) {
296
+ if (!row) return false;
297
+ if (Object.prototype.hasOwnProperty.call(row, 'has_credential')) return !!row.has_credential;
298
+ return !!row.has_key;
299
+ }
300
+
301
+ function _rowOrigin(row) {
302
+ const base = row && (row.base_url || row.baseUrl);
303
+ const fromBase = originOf(base);
304
+ if (fromBase) return fromBase;
305
+ const type = String((row && row.type) || '').trim().toLowerCase();
306
+ return DEFAULT_ORIGINS[type] || null;
307
+ }
308
+
309
+ /** The row list inside a `/api/models/providers` body — [] for any other shape. */
310
+ function _providersOf(body) {
311
+ if (!body || typeof body !== 'object') return [];
312
+ return Array.isArray(body.providers) ? body.providers.filter((row) => row && typeof row === 'object') : [];
313
+ }
314
+
315
+ /** The box's own route rows, from its Wall-E `GET /api/models/providers` over loopback. */
316
+ async function _readProviderRows(walleClient) {
317
+ const client = walleClient || require('./walle-client');
318
+ const response = await client.requestJson('/api/models/providers');
319
+ return _providersOf(response && response.json);
320
+ }
321
+
322
+ /** `{ claude: { signedIn }, codex: { signedIn } }` from either the wire shape or a normalized one. */
323
+ function _normalizeNative(loginState) {
324
+ const providers = loginState && typeof loginState === 'object' && loginState.providers
325
+ ? loginState.providers
326
+ : loginState;
327
+ const signedInOf = (agent) => {
328
+ const entry = providers && providers[agent];
329
+ if (!entry || typeof entry !== 'object') return false;
330
+ if (typeof entry.signedIn === 'boolean') return entry.signedIn;
331
+ const account = entry.account;
332
+ return !!(account && typeof account === 'object' && account.signedIn === true);
333
+ };
334
+ return { claude: { signedIn: signedInOf('claude') }, codex: { signedIn: signedInOf('codex') } };
335
+ }
336
+
337
+ /**
338
+ * §6.1's 200 body. Rows come from the box's own Wall-E (`/api/models/providers`, key already
339
+ * stripped), one probe per distinct origin, and NOTHING but a verdict leaves this function: no key
340
+ * bytes, no header values, no header names off a row.
341
+ *
342
+ * @param {object} deps
343
+ * @param {object} deps.walleClient loopback Wall-E client (`{ requestJson }`)
344
+ * @param {object} deps.loginState `GET /api/providers/login-state`'s body (or a normalized one)
345
+ * @param {Array} deps.delegatePrefs the box's `default:claude` / `default:codex` rows
346
+ * @param {Function} [deps.probe] probe implementation (defaults to probeOrigin)
347
+ * @param {Map} [deps.cache] per-origin cache (defaults to the module cache)
348
+ */
349
+ async function buildAvailability({
350
+ walleClient,
351
+ loginState,
352
+ delegatePrefs,
353
+ probe = probeOrigin,
354
+ cache = _cache,
355
+ refresh = false,
356
+ now = Date.now,
357
+ instanceId = '',
358
+ callerKey = 'local',
359
+ request,
360
+ } = {}) {
361
+ const checkedAt = _ms(now);
362
+ const rows = await _readProviderRows(walleClient);
363
+ const bypassCache = refresh === true && _mayRefresh(callerKey, now);
364
+
365
+ const origins = [];
366
+ for (const row of rows) {
367
+ const origin = _rowOrigin(row);
368
+ if (origin && !origins.includes(origin)) origins.push(origin);
369
+ }
370
+ const verdicts = new Map();
371
+ await Promise.all(origins.map(async (origin) => {
372
+ const result = await _probeThrough(origin, probe, { request, now, cache, bypassCache });
373
+ verdicts.set(origin, result);
374
+ }));
375
+
376
+ const routes = rows.map((row) => {
377
+ const origin = _rowOrigin(row);
378
+ const verdict = origin ? verdicts.get(origin) : null;
379
+ return {
380
+ id: String(row.id == null ? '' : row.id),
381
+ type: String(row.type == null ? '' : row.type),
382
+ enabled: _isEnabled(row),
383
+ keyed: _isKeyed(row),
384
+ origin,
385
+ reach: verdict ? verdict.reach : 'unknown',
386
+ status: verdict ? verdict.status : null,
387
+ connectMs: verdict ? verdict.connectMs : null,
388
+ probedAt: verdict ? verdict.probedAt : null,
389
+ };
390
+ });
391
+
392
+ const byId = new Map(routes.map((route) => [route.id, route]));
393
+ const prefs = Array.isArray(delegatePrefs) ? delegatePrefs : [];
394
+ const delegateDefaults = prefs
395
+ .filter((pref) => pref && typeof pref === 'object')
396
+ .map((pref) => {
397
+ const routeId = String(pref.routeId == null ? '' : pref.routeId);
398
+ const row = byId.get(routeId);
399
+ return {
400
+ agent: String(pref.agent == null ? '' : pref.agent),
401
+ lane: String(pref.lane == null ? '' : pref.lane),
402
+ routeId,
403
+ model: String(pref.model == null ? '' : pref.model),
404
+ reach: row ? row.reach : 'unknown',
405
+ };
406
+ });
407
+
408
+ return {
409
+ ok: true,
410
+ instanceId: String(instanceId || ''),
411
+ checkedAt: new Date(checkedAt).toISOString(),
412
+ routes,
413
+ delegateDefaults,
414
+ native: _normalizeNative(loginState),
415
+ };
416
+ }
417
+
418
+ // ------------------------------------------------------------------------------------------------
419
+ // WP-4 §6.4 (docs/remote-box-model-access-2026-10-26.html): REMOVE a box's own route rows.
420
+ //
421
+ // The hub never writes a box's rows directly (R-2: the box owns its rows), so the box exposes the
422
+ // removal and the box's own Wall-E is the only thing that deletes. This module owns the two things
423
+ // that make that safe: the request shape is validated HERE (both the box route and the hub route
424
+ // call `normalizeRouteIds`, so a malformed body is one 400 with one message), and the DELETE is
425
+ // aimed only at ids the box actually has — Wall-E answers 200 for an unknown id (wall-e/http/
426
+ // model-admin.js), so "not in the list" would otherwise be reported as removed.
427
+ //
428
+ // Nothing in this path reads, copies or returns a credential: only ids cross the wire, and both
429
+ // halves of the answer are id lists.
430
+ // ------------------------------------------------------------------------------------------------
431
+
432
+ /** `{ routeIds }` bound, shared by the box route and the hub route (§6.4: 1..64). */
433
+ const MAX_ROUTE_IDS = 64;
434
+ /** Longest accepted route id (§6.4: 200 chars). */
435
+ const MAX_ROUTE_ID_CHARS = 200;
436
+
437
+ /**
438
+ * Validate a `routeIds` value: an array of 1..64 non-empty strings, each at most 200 chars, deduped
439
+ * preserving first-seen order. Anything else is `{ ok: false, message }` — one wording for both
440
+ * routes, so the two 400s cannot drift.
441
+ *
442
+ * @returns {{ok: true, ids: string[]} | {ok: false, message: string}}
443
+ */
444
+ function normalizeRouteIds(value) {
445
+ if (!Array.isArray(value)) return { ok: false, message: 'routeIds must be an array of strings' };
446
+ if (value.length < 1 || value.length > MAX_ROUTE_IDS) {
447
+ return { ok: false, message: `routeIds must hold between 1 and ${MAX_ROUTE_IDS} ids` };
448
+ }
449
+ const ids = [];
450
+ for (const raw of value) {
451
+ if (typeof raw !== 'string') return { ok: false, message: 'routeIds must contain only strings' };
452
+ if (!raw.trim()) return { ok: false, message: 'routeIds must not contain an empty id' };
453
+ if (raw.length > MAX_ROUTE_ID_CHARS) return { ok: false, message: `a route id is longer than ${MAX_ROUTE_ID_CHARS} characters` };
454
+ if (!ids.includes(raw)) ids.push(raw);
455
+ }
456
+ return { ok: true, ids };
457
+ }
458
+
459
+ /**
460
+ * Remove route ids from the box's own Wall-E (§6.4): ONE pre-read of `GET /api/models/providers`
461
+ * decides what exists, then exactly ONE `DELETE /api/models/providers/<id>` per present distinct id.
462
+ * An id the box does not have lands in `missing` and is NEVER deleted (Wall-E's DELETE answers 200
463
+ * even for an unknown id, so a blind delete could not be told apart from a real removal).
464
+ *
465
+ * A Wall-E non-2xx on the read or on any delete is `walle_unavailable` — the caller's 502, never a
466
+ * partial success dressed up as one.
467
+ *
468
+ * @param {object} deps
469
+ * @param {object} [deps.walleClient] loopback Wall-E client (`{ requestJson }`); defaults to the real one
470
+ * @param {string[]} deps.routeIds
471
+ * @returns {Promise<{removed: string[], missing: string[]}>}
472
+ */
473
+ async function removeRoutes({ walleClient, routeIds } = {}) {
474
+ const client = walleClient || require('./walle-client');
475
+ const normalized = normalizeRouteIds(routeIds);
476
+ if (!normalized.ok) {
477
+ const error = new Error(normalized.message);
478
+ error.code = 'invalid_request';
479
+ throw error;
480
+ }
481
+ const read = await client.requestJson('/api/models/providers');
482
+ const readStatus = Number(read && read.status);
483
+ if (!Number.isFinite(readStatus) || readStatus < 200 || readStatus >= 300) {
484
+ const error = new Error('Wall-E did not answer the provider read');
485
+ error.code = 'walle_unavailable';
486
+ throw error;
487
+ }
488
+ const present = new Set(_providersOf(read && read.json).map((row) => String(row.id == null ? '' : row.id)));
489
+ const removed = [];
490
+ const missing = [];
491
+ for (const id of normalized.ids) {
492
+ if (!present.has(id)) {
493
+ missing.push(id);
494
+ continue;
495
+ }
496
+ const answer = await client.requestJson(`/api/models/providers/${encodeURIComponent(id)}`, { method: 'DELETE' });
497
+ const status = Number(answer && answer.status);
498
+ if (!Number.isFinite(status) || status < 200 || status >= 300) {
499
+ const error = new Error(`Wall-E refused the delete (${status || 0})`);
500
+ error.code = 'walle_unavailable';
501
+ throw error;
502
+ }
503
+ removed.push(id);
504
+ }
505
+ return { removed, missing };
506
+ }
507
+
508
+ // ------------------------------------------------------------------------------------------------
509
+ // WP-5 §6.4 (docs/remote-box-model-access-2026-10-26.html): COPY one hub route onto a box.
510
+ //
511
+ // The direction is deliberately the same as the remove leg's — the box owns its rows, so the hub
512
+ // never writes them (R-2) and asks the box to import — but with one difference that is the whole of
513
+ // R-5/R-6: this is the only path where a CREDENTIAL crosses a machine boundary, and only because
514
+ // the user clicked "Use on <box>" for ONE route. Three rules make that safe:
515
+ // * the hub reads the row (key included) from its OWN Wall-E over loopback, through
516
+ // `GET /api/wall-e/internal/provider-export` — a loopback-only leaf, never a browser route;
517
+ // * the row crosses to the box over the hub's own peer credential, and is a strict ALLOWLIST
518
+ // (`normalizeImportRow`), so a sender can never push an unvetted column (custom headers, a
519
+ // usage/health counter, a derived field) into the box's table — and an unknown key is a 400,
520
+ // never a silent drop;
521
+ // * nothing on either side logs or returns the key: both halves answer `{ ok, routeId }`, and the
522
+ // request body is the only carrier.
523
+ // ------------------------------------------------------------------------------------------------
524
+
525
+ /** The import row's fields — exactly what a hub export can carry, and nothing else (§6.4). */
526
+ const IMPORT_ROW_FIELDS = Object.freeze([
527
+ 'id', 'name', 'type', 'base_url', 'auth_method', 'api_key', 'enabled',
528
+ 'max_output_tokens', 'reasoning_field', 'reasoning_effort_default', 'ca_cert_pem',
529
+ ]);
530
+ const IMPORT_ROW_FIELD_SET = new Set(IMPORT_ROW_FIELDS);
531
+ /** Per-field bounds. `api_key` is bounded only as a sanity cap — never truncated (a cut key would
532
+ * be a silently broken route); `ca_cert_pem` matches wall-e/http/model-provider-ca.js's 64 KB PEM. */
533
+ const IMPORT_FIELD_CHARS = Object.freeze({
534
+ id: 200, name: 200, type: 60, base_url: 500, auth_method: 60, api_key: 512, reasoning_field: 60,
535
+ reasoning_effort_default: 40, ca_cert_pem: 70000,
536
+ });
537
+ /** The reasoning-effort values the brain's own upsert accepts (brain/models.js). */
538
+ const IMPORT_REASONING_EFFORTS = new Set(['low', 'medium', 'xhigh', 'off']);
539
+
540
+ /** One required/optional import string: `{ value }` or `{ message }` — the message is constant. */
541
+ function _importString(value, field, { required = false } = {}) {
542
+ if (value === undefined || value === null) {
543
+ return required ? { message: `row.${field} is required` } : { value: null };
544
+ }
545
+ if (typeof value !== 'string') return { message: `row.${field} must be a string` };
546
+ const trimmed = value.trim();
547
+ if (!trimmed) return required ? { message: `row.${field} is required` } : { value: null };
548
+ if (trimmed.length > IMPORT_FIELD_CHARS[field]) {
549
+ return { message: `row.${field} is longer than ${IMPORT_FIELD_CHARS[field]} characters` };
550
+ }
551
+ return { value: trimmed };
552
+ }
553
+
554
+ /**
555
+ * Validate ONE imported route row (§6.4). The allowlist is the point: an unknown key is a 400 —
556
+ * never dropped — because a key the sender thought mattered and that this route quietly ignored
557
+ * would be a route that looks copied and is not.
558
+ *
559
+ * @returns {{ok: true, row: object} | {ok: false, message: string}}
560
+ */
561
+ function normalizeImportRow(value) {
562
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
563
+ return { ok: false, message: 'row must be an object' };
564
+ }
565
+ for (const key of Object.keys(value)) {
566
+ if (!IMPORT_ROW_FIELD_SET.has(key)) return { ok: false, message: 'row carries a field this route does not accept' };
567
+ }
568
+ const row = {};
569
+ for (const field of ['id', 'name', 'type', 'base_url']) {
570
+ const parsed = _importString(value[field], field, { required: field === 'id' || field === 'name' || field === 'type' });
571
+ if (parsed.message) return { ok: false, message: parsed.message };
572
+ if (parsed.value !== null) row[field] = parsed.value;
573
+ }
574
+ for (const field of ['auth_method', 'api_key', 'reasoning_field', 'ca_cert_pem']) {
575
+ const parsed = _importString(value[field], field);
576
+ if (parsed.message) return { ok: false, message: parsed.message };
577
+ if (parsed.value !== null) row[field] = parsed.value;
578
+ }
579
+ if (value.enabled !== undefined && value.enabled !== null && typeof value.enabled !== 'boolean' && value.enabled !== 0 && value.enabled !== 1) {
580
+ return { ok: false, message: 'row.enabled must be a boolean' };
581
+ }
582
+ row.enabled = !(value.enabled === false || value.enabled === 0);
583
+ if (value.max_output_tokens !== undefined && value.max_output_tokens !== null) {
584
+ if (!Number.isInteger(value.max_output_tokens) || value.max_output_tokens <= 0) {
585
+ return { ok: false, message: 'row.max_output_tokens must be a positive integer' };
586
+ }
587
+ row.max_output_tokens = value.max_output_tokens;
588
+ }
589
+ const effort = _importString(value.reasoning_effort_default, 'reasoning_effort_default');
590
+ if (effort.message) return { ok: false, message: effort.message };
591
+ if (effort.value !== null) {
592
+ if (!IMPORT_REASONING_EFFORTS.has(effort.value)) return { ok: false, message: 'row.reasoning_effort_default is not a valid effort level' };
593
+ row.reasoning_effort_default = effort.value;
594
+ }
595
+ return { ok: true, row };
596
+ }
597
+
598
+ /** Longest accepted `expectOrigin` — a `scheme://host[:port]` far below any real origin. */
599
+ const MAX_COPY_ORIGIN_CHARS = 300;
600
+
601
+ /**
602
+ * Validate a §6.4 copy request: `{ routeId, expectOrigin }` — BOTH present, and nothing else.
603
+ * `expectOrigin` is the origin the `copyable` list showed the user, compared exactly — it is what turns a list the client
604
+ * read a minute ago into a refusal (`origin_changed`) rather than a copy of a row that has since
605
+ * been repointed at a different host.
606
+ *
607
+ * @returns {{ok: true, routeId: string, expectOrigin: string} | {ok: false, message: string}}
608
+ */
609
+ function normalizeCopyRequest(value) {
610
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
611
+ return { ok: false, message: 'the request body must be an object' };
612
+ }
613
+ for (const key of Object.keys(value)) {
614
+ if (key !== 'routeId' && key !== 'expectOrigin') return { ok: false, message: 'the body carries a field this route does not accept' };
615
+ }
616
+ const routeId = typeof value.routeId === 'string' ? value.routeId.trim() : '';
617
+ if (!routeId) return { ok: false, message: 'routeId is required' };
618
+ if (routeId.length > MAX_ROUTE_ID_CHARS) return { ok: false, message: `routeId is longer than ${MAX_ROUTE_ID_CHARS} characters` };
619
+ // BOTH fields are required: an absent `expectOrigin` would silently degrade the stale-UI guard
620
+ // into nothing, where the client that never checked an origin should hear about it instead. An
621
+ // EMPTY string is a real value — it is what the list shows for a row with no resolvable origin.
622
+ if (typeof value.expectOrigin !== 'string') return { ok: false, message: 'expectOrigin is required' };
623
+ const expectOrigin = value.expectOrigin.trim();
624
+ if (expectOrigin.length > MAX_COPY_ORIGIN_CHARS) {
625
+ return { ok: false, message: `expectOrigin is longer than ${MAX_COPY_ORIGIN_CHARS} characters` };
626
+ }
627
+ return { ok: true, routeId, expectOrigin };
628
+ }
629
+
630
+ /** The origin a hub row is copied FROM: its own `base_url`, else its type's adapter default. */
631
+ function rowOrigin(row) {
632
+ return _rowOrigin(row);
633
+ }
634
+
635
+ /**
636
+ * The raw upsert's wire body for a validated import row (wall-e/http/model-admin.js's
637
+ * `POST /api/models/providers` → brain/models.js's `upsertModelProvider`): the three columns whose
638
+ * wire names differ from the row's, and nothing else. `auth_method` is NOT accepted by that
639
+ * destructuring, so it is not forwarded — see the module's report note (WP-5a observation).
640
+ */
641
+ function _upsertBodyFor(row) {
642
+ const body = { id: row.id, name: row.name, type: row.type, enabled: row.enabled };
643
+ if (row.base_url) body.baseUrl = row.base_url;
644
+ if (row.api_key) body.apiKeyEncrypted = row.api_key;
645
+ if (row.ca_cert_pem) body.caCert = row.ca_cert_pem;
646
+ if (row.max_output_tokens) body.max_output_tokens = row.max_output_tokens;
647
+ if (row.reasoning_field) body.reasoning_field = row.reasoning_field;
648
+ if (row.reasoning_effort_default) body.reasoning_effort_default = row.reasoning_effort_default;
649
+ return body;
650
+ }
651
+
652
+ /** `{status, json}` of one loopback call, or throws `walle_unavailable` for anything unusable. */
653
+ function _requireWalleAnswer(answer, what) {
654
+ const status = Number(answer && answer.status);
655
+ if (!Number.isFinite(status) || status < 200 || status >= 300) {
656
+ const error = new Error(`Wall-E did not answer the ${what}`);
657
+ error.code = 'walle_unavailable';
658
+ throw error;
659
+ }
660
+ return answer;
661
+ }
662
+
663
+ /**
664
+ * §6.4: create/replace ONE route on the box's own Wall-E from an already-validated import row.
665
+ * The row is validated AGAIN here (the box route calls both, like the remove leg's two callers), so
666
+ * a direct caller of this function can never reach Wall-E with an unvetted row.
667
+ *
668
+ * @returns {Promise<{routeId: string}>} — never the row, never the key
669
+ */
670
+ async function importRoute({ walleClient, row } = {}) {
671
+ const client = walleClient || require('./walle-client');
672
+ const normalized = normalizeImportRow(row);
673
+ if (!normalized.ok) {
674
+ const error = new Error(normalized.message);
675
+ error.code = 'invalid_request';
676
+ throw error;
677
+ }
678
+ const answer = await client.requestJson('/api/models/providers', { method: 'POST', body: _upsertBodyFor(normalized.row) });
679
+ _requireWalleAnswer(answer, 'provider write');
680
+ return { routeId: normalized.row.id };
681
+ }
682
+
683
+ /** The HUB's own route rows (`GET /api/models/providers` on this machine's Wall-E; keys already
684
+ * stripped by that list). Throws `walle_unavailable` when the read is not a usable answer. */
685
+ async function readOwnProviderRows({ walleClient } = {}) {
686
+ const client = walleClient || require('./walle-client');
687
+ const answer = _requireWalleAnswer(await client.requestJson('/api/models/providers'), 'provider read');
688
+ return _providersOf(answer.json);
689
+ }
690
+
691
+ /**
692
+ * §6.4/R-5: ONE hub route WITH its key, read over loopback through Wall-E's export leaf — the only
693
+ * read in this codebase that returns a credential. `null` means "no such route" (the leaf's 404);
694
+ * anything else non-2xx is `walle_unavailable`.
695
+ *
696
+ * @returns {Promise<{row: object}|null>}
697
+ */
698
+ async function exportOwnRoute({ walleClient, routeId } = {}) {
699
+ const client = walleClient || require('./walle-client');
700
+ const path = `/api/wall-e/internal/provider-export?id=${encodeURIComponent(String(routeId || ''))}`;
701
+ const answer = await client.requestJson(path);
702
+ if (Number(answer && answer.status) === 404) return null;
703
+ _requireWalleAnswer(answer, 'provider export');
704
+ const row = answer.json && answer.json.row;
705
+ return row && typeof row === 'object' ? { row } : null;
706
+ }
707
+
708
+ module.exports = {
709
+ DEFAULT_ORIGINS,
710
+ probeOrigin,
711
+ buildAvailability,
712
+ normalizeRouteIds,
713
+ removeRoutes,
714
+ normalizeImportRow,
715
+ normalizeCopyRequest,
716
+ importRoute,
717
+ readOwnProviderRows,
718
+ exportOwnRoute,
719
+ rowOrigin,
720
+ originOf,
721
+ originVerdict,
722
+ PROBE_CONNECT_MS,
723
+ PROBE_TOTAL_MS,
724
+ ORIGIN_TTL_MS,
725
+ PROBE_CONCURRENCY,
726
+ REFRESH_MIN_INTERVAL_MS,
727
+ };