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,57 @@
1
+ 'use strict';
2
+ // WP-1 (docs/landing-pipeline-batched-multi-host-2026-10-30.html §5 D-3, §6, §8 WP-1, §9 S-1):
3
+ // the adaptive batch window.
4
+ //
5
+ // The window is how many runnable queued jobs one gate is asked to cover. It is Zuul's rule: +1 on a
6
+ // batch PASS (up to `landing.batch.max`), halve on a batch FAIL (never below 1). A window of 1 is
7
+ // exactly the serial queue that existed before this change, so a repository that never writes the
8
+ // file keeps today's behaviour.
9
+ //
10
+ // State lives at `<landing-dir>/window.json` — the same directory, the same atomic writer and the
11
+ // same `contract` stamp as every other file the queue owns (`state.js`). Nothing here reads git,
12
+ // spawns a process or knows about jobs: it is one integer and one transition.
13
+
14
+ const path = require('path');
15
+
16
+ const state = require('./state');
17
+
18
+ const DEFAULT_SIZE = 1;
19
+ const FILE = 'window.json';
20
+
21
+ function windowFile(root) {
22
+ return path.join(root, FILE);
23
+ }
24
+
25
+ function normalize(raw) {
26
+ if (!raw || typeof raw !== 'object') return DEFAULT_SIZE;
27
+ // Strictly a number: a `"8"` written by hand is a malformed window, and the safe reading of a
28
+ // malformed window is the serial one.
29
+ if (typeof raw.size !== 'number' || !Number.isInteger(raw.size) || raw.size < 1) return DEFAULT_SIZE;
30
+ return raw.size;
31
+ }
32
+
33
+ // `{ size }`, defaulting to `{ size: 1 }` when the file is absent, unreadable or malformed — an
34
+ // unknown window is the conservative one (today's serial behaviour), never a silent batch of eight.
35
+ function readWindow(root) {
36
+ return { size: normalize(state.readJsonSafe(windowFile(root))) };
37
+ }
38
+
39
+ // D-3: the transition, read-modify-write inside the queue's mutex (the runner is the only writer, but
40
+ // the file lives beside every other piece of shared state and gets the same protection). `max` is the
41
+ // ceiling (`landing.batch.max`, ≥ 1): a PASS never grows past it and a FAIL never shrinks below 1.
42
+ function bumpWindow(root, verdict, max) {
43
+ const ceiling = Number.isInteger(max) && max >= 1 ? max : DEFAULT_SIZE;
44
+ return state.withMutex(root, () => {
45
+ const before = readWindow(root).size;
46
+ const size = verdict === 'PASS' ? Math.min(before + 1, ceiling) : Math.max(Math.floor(before / 2), 1);
47
+ state.writeJsonAtomic(windowFile(root), { contract: state.CONTRACT, size });
48
+ return { size, before };
49
+ });
50
+ }
51
+
52
+ module.exports = {
53
+ DEFAULT_SIZE,
54
+ windowFile,
55
+ readWindow,
56
+ bumpWindow,
57
+ };
@@ -0,0 +1,121 @@
1
+ 'use strict';
2
+
3
+ // WP-0 of docs/launch-environment-host-carried-adoption-2026-10-26.html (§5 D-1, §6).
4
+ //
5
+ // ONE rule-versioned capture function for the per-session launch environment. The record it produces is a
6
+ // self-describing OBSERVATION -- { rule, state, values, reason } -- never a verdict about the session: it
7
+ // carries the rule that produced it (LAUNCH_ENV_RULE) so a later reader can convert a record written under
8
+ // an older rule (see lib/adopted-launch-environment.js, WP-2) instead of guessing. `rule` is an ordinal,
9
+ // not a content hash; the hash of the rule's inputs lives in tests/launch-environment.test.js
10
+ // (launchEnvironmentRuleDigest) so changing a projected key, a bound or the credential regex without
11
+ // bumping LAUNCH_ENV_RULE fails loudly.
12
+ //
13
+ // Dependency-free on purpose: node:crypto (rule digest) and node:buffer (byte bounds). No fs, no config.
14
+
15
+ const crypto = require('node:crypto');
16
+ const { Buffer } = require('node:buffer');
17
+
18
+ // Rule 2 = "the projected launch-context keys HOME/CODEX_HOME/TMPDIR are not private" (the rule since
19
+ // 4768eeced3); rule 1 = the pre-2026-10-25 reading.
20
+ const LAUNCH_ENV_RULE = 2;
21
+
22
+ // The keys a lead-model switch re-projects into the launch context (lib/lead-model-restore-reconciliation.js
23
+ // projectDeferredRestoreLaunch: HOME / CODEX_HOME from the launch context, TMPDIR alongside them). They are
24
+ // PROJECTED launch context, not per-session private overrides: a spawn still receives them, but their presence
25
+ // must never turn an otherwise empty override set into a private environment that only a surviving owner can
26
+ // reproduce. Anything else in the env is private and is captured by value.
27
+ const PROJECTED_LAUNCH_CONTEXT_KEYS = Object.freeze(['HOME', 'CODEX_HOME', 'TMPDIR']);
28
+
29
+ // Bounds of a capturable private environment (moved here from lib/lead-model-restore-reconciliation.js).
30
+ const MAX_SOURCE_ENV_ENTRIES = 64;
31
+ const MAX_SOURCE_ENV_BYTES = 32 * 1024;
32
+ const MAX_SOURCE_ENV_VALUE_BYTES = 8 * 1024;
33
+
34
+ // A key whose value is credential-shaped must never be written to a handoff manifest file.
35
+ const CREDENTIAL_KEY_RE = /(?:API_KEY|AUTH_TOKEN|_TOKEN|SECRET|PASSWORD|CREDENTIAL)/i;
36
+
37
+ const LAUNCH_ENV_STATES = Object.freeze(['captured', 'captured-empty', 'unavailable']);
38
+ const LAUNCH_ENV_REASONS = Object.freeze(['not_object', 'too_many_entries', 'bad_key', 'bad_value',
39
+ 'too_many_bytes', 'credential_keys', 'legacy_worker_private_env', 'legacy_no_receipt']);
40
+
41
+ const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
42
+
43
+ /** Returns a NEW object holding only the non-projected entries of `env`; never mutates its input and maps a
44
+ * non-object to `{}`. */
45
+ function privateOverridesFromEnv(env) {
46
+ if (!env || typeof env !== 'object' || Array.isArray(env)) return {};
47
+ const out = {};
48
+ for (const [key, value] of Object.entries(env)) {
49
+ if (!PROJECTED_LAUNCH_CONTEXT_KEYS.includes(key)) out[key] = value;
50
+ }
51
+ return out;
52
+ }
53
+
54
+ function unavailable(reason) {
55
+ return Object.freeze({ rule: LAUNCH_ENV_RULE, state: 'unavailable', values: null, reason });
56
+ }
57
+
58
+ /** Captures a raw per-session private environment. Frozen result; never throws. */
59
+ function captureLaunchEnvironment(privateEnv) {
60
+ if (!privateEnv || typeof privateEnv !== 'object' || Array.isArray(privateEnv)) return unavailable('not_object');
61
+ const entries = Object.entries(privateEnv);
62
+ if (entries.length > MAX_SOURCE_ENV_ENTRIES) return unavailable('too_many_entries');
63
+ const values = {};
64
+ let bytes = 0;
65
+ for (const [key, value] of entries) {
66
+ if (!ENV_KEY_RE.test(key)) return unavailable('bad_key');
67
+ if (typeof value !== 'string' || value.includes('\0')
68
+ || Buffer.byteLength(value, 'utf8') > MAX_SOURCE_ENV_VALUE_BYTES) return unavailable('bad_value');
69
+ bytes += Buffer.byteLength(key, 'utf8') + Buffer.byteLength(value, 'utf8');
70
+ if (bytes > MAX_SOURCE_ENV_BYTES) return unavailable('too_many_bytes');
71
+ values[key] = value;
72
+ }
73
+ const state = entries.length ? 'captured' : 'captured-empty';
74
+ return Object.freeze({ rule: LAUNCH_ENV_RULE, state, values: Object.freeze(values), reason: null });
75
+ }
76
+
77
+ // A record is a NEW frozen object rebuilt from validated fields: a tampered record (extra fields, a
78
+ // non-frozen values object, values that no longer satisfy the bounds) never reaches a caller as itself.
79
+ function normalizeLaunchEnvironment(value) {
80
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
81
+ if (value.rule !== LAUNCH_ENV_RULE || !LAUNCH_ENV_STATES.includes(value.state)) return null;
82
+ if (value.state === 'unavailable') {
83
+ if (typeof value.reason !== 'string' || !LAUNCH_ENV_REASONS.includes(value.reason)) return null;
84
+ if (value.values !== null) return null;
85
+ return Object.freeze({ rule: LAUNCH_ENV_RULE, state: 'unavailable', values: null, reason: value.reason });
86
+ }
87
+ if (value.reason !== null) return null;
88
+ const recaptured = captureLaunchEnvironment(value.values);
89
+ if (recaptured.state !== value.state) return null;
90
+ return Object.freeze({ rule: LAUNCH_ENV_RULE, state: value.state, values: recaptured.values, reason: null });
91
+ }
92
+
93
+ /** Validator in the `modelStorageRootsMetadata` style: `{}` or `{ launchEnvironment: frozenRecord }` so a
94
+ * caller spreads it. Only rule-2 records whose declared state agrees with a re-capture are accepted. With
95
+ * `{ manifest: true }` a record whose values hold a CREDENTIAL_KEY_RE key is REWRITTEN to
96
+ * `{ rule, state:'unavailable', values:null, reason:'credential_keys' }` (so no secret reaches the 0600
97
+ * manifest file); the other record keeps its values. */
98
+ function launchEnvironmentMetadata(value, { manifest = false } = {}) {
99
+ const record = normalizeLaunchEnvironment(value);
100
+ if (!record) return {};
101
+ if (manifest && record.values && Object.keys(record.values).some((key) => CREDENTIAL_KEY_RE.test(key))) {
102
+ return { launchEnvironment: Object.freeze({ rule: LAUNCH_ENV_RULE, state: 'unavailable', values: null,
103
+ reason: 'credential_keys' }) };
104
+ }
105
+ return { launchEnvironment: record };
106
+ }
107
+
108
+ /** sha256 over the rule's inputs: projected keys, bounds and the credential regex. Pinned in
109
+ * tests/launch-environment.test.js -- bump LAUNCH_ENV_RULE and add a converter case when it changes. */
110
+ function launchEnvironmentRuleDigest() {
111
+ return crypto.createHash('sha256').update(JSON.stringify({
112
+ rule: LAUNCH_ENV_RULE,
113
+ projected: PROJECTED_LAUNCH_CONTEXT_KEYS,
114
+ limits: [MAX_SOURCE_ENV_ENTRIES, MAX_SOURCE_ENV_BYTES, MAX_SOURCE_ENV_VALUE_BYTES],
115
+ credentialRe: CREDENTIAL_KEY_RE.source,
116
+ })).digest('hex');
117
+ }
118
+
119
+ module.exports = { LAUNCH_ENV_RULE, PROJECTED_LAUNCH_CONTEXT_KEYS, LAUNCH_ENV_STATES, LAUNCH_ENV_REASONS,
120
+ privateOverridesFromEnv, captureLaunchEnvironment, launchEnvironmentMetadata, launchEnvironmentRuleDigest,
121
+ MAX_SOURCE_ENV_ENTRIES, MAX_SOURCE_ENV_BYTES, MAX_SOURCE_ENV_VALUE_BYTES, CREDENTIAL_KEY_RE };
@@ -23,12 +23,8 @@ const {
23
23
  CLAUDE_OPTION_SIGNATURES, CLAUDE_SHORT_OPTIONS, locateClaudeSettingsArg,
24
24
  } = require('./native-lead-settings-argv');
25
25
 
26
- const MODEL_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@+\-[\]]*$/;
27
- const MAX_MODEL_ID_LENGTH = 200;
28
-
29
- function isValidOverrideModel(model) {
30
- return typeof model === 'string' && model.length > 0 && model.length <= MAX_MODEL_ID_LENGTH && MODEL_ID_RE.test(model);
31
- }
26
+ // The ONE model-id rule lives in a leaf module (the session host loads it); re-exported here.
27
+ const { isValidOverrideModel } = require('./model-id-rule');
32
28
 
33
29
  // Drops an existing `--model X` / `--model=X` / `-m X` / `-m=X` so the override is the only model flag.
34
30
  function stripModelFlags(args) {
@@ -42,12 +38,30 @@ function stripModelFlags(args) {
42
38
  return out;
43
39
  }
44
40
 
41
+ // OpenCode's OWN launcher form (docs/opencode-lead-and-delegate-model-selection-2026-10-28.html §6.3,
42
+ // R-8): the TUI's global flags come first and `--model` is spelled the long way only. Kept separate
43
+ // from `stripModelFlags` above so the Claude/Codex argv grammar (where `-m` is the Codex global and
44
+ // the model flag is appended/anchored) is never changed by an OpenCode-only rule.
45
+ function stripOpencodeModelFlags(args) {
46
+ const out = [];
47
+ for (let i = 0; i < args.length; i += 1) {
48
+ const arg = String(args[i]);
49
+ if (arg === '--model' || arg === '-m') { i += 1; continue; }
50
+ if (arg.startsWith('--model=') || arg.startsWith('-m=')) continue;
51
+ out.push(args[i]);
52
+ }
53
+ return out;
54
+ }
55
+
45
56
  /**
46
- * @param {{ agentType: string, args: string[], model: unknown }} input
57
+ * @param {{ agentType: string, args: string[], model: unknown, line?: 'v1'|'v2'|null }} input
58
+ * `line` is the OpenCode binary's shape (lib/opencode-cli.js). It matters ONLY for
59
+ * `agentType === 'opencode'`; every other agent ignores it, and `null`/absent keeps the 1.x
60
+ * behaviour byte-identical.
47
61
  * @returns {{ args: string[], applied: boolean, reason: string }}
48
62
  * applied=false returns the input args untouched (same array) with the reason why.
49
63
  */
50
- function applyLaunchModelOverride({ agentType, args, model } = {}) {
64
+ function applyLaunchModelOverride({ agentType, args, model, line } = {}) {
51
65
  const list = Array.isArray(args) ? args : [];
52
66
  if (model == null || model === '') return { args: list, applied: false, reason: 'none' };
53
67
  if (!isValidOverrideModel(model)) return { args: list, applied: false, reason: 'invalid_model' };
@@ -68,6 +82,18 @@ function applyLaunchModelOverride({ agentType, args, model } = {}) {
68
82
  const head = stripModelFlags(list.slice(0, resumeIdx));
69
83
  return { args: [...head, '-m', model, ...list.slice(resumeIdx)], applied: true, reason: '' };
70
84
  }
85
+ if (agentType === 'opencode') {
86
+ // 2.x (R-4, WP-4): the TUI rejects `--model`, so the target never touches this argv. Strip every
87
+ // prior model flag so a stale one cannot survive either; the model rides the inline config +
88
+ // plugin `switch_model` the spawn path writes (lib/session-create/subagent-pick.js
89
+ // opencodeV2PluginEnv). `applied:true` records that the switch IS carried, just not here.
90
+ if (line === 'v2') return { args: stripOpencodeModelFlags(list), applied: true, reason: 'opencode-v2-plugin-carrier' };
91
+ // 1.x: OpenCode takes its global flags first; strip every prior model flag (`--model X`,
92
+ // `--model=X`, `-m X`, `-m=X`). This rides a FRESH relaunch only (a resumed 1.x conversation
93
+ // keeps the model its last user message recorded -- §5 R-4), so the launcher never has to
94
+ // reconcile a resume.
95
+ return { args: ['--model', model, ...stripOpencodeModelFlags(list)], applied: true, reason: '' };
96
+ }
71
97
  return { args: list, applied: false, reason: 'unsupported_agent' };
72
98
  }
73
99
 
@@ -482,5 +508,5 @@ function applyLaunchEffortOverride(argv, agent, effort) {
482
508
  }
483
509
 
484
510
  module.exports = { applyLaunchModelOverride, isValidOverrideModel, applyLaunchEffortOverride, isValidEffort,
485
- applyLaunchModelPin, stripModelFlags, insertBeforeCodexResume, rebindCodexForkLaunchPlan, ensureCodexResumeTarget,
486
- ensureClaudeResumeTarget };
511
+ applyLaunchModelPin, stripModelFlags, stripOpencodeModelFlags, insertBeforeCodexResume, rebindCodexForkLaunchPlan,
512
+ ensureCodexResumeTarget, ensureClaudeResumeTarget };
@@ -1172,7 +1172,8 @@ function createLaunchPlanCoordinator(options = {}) {
1172
1172
  // (or clearing it themselves) is an implicit "always recompute".
1173
1173
  // An explicit replacement bypasses the cache: the caller is saying the previous plan is no
1174
1174
  // longer valid, so replaying it would defeat the retry it asked for.
1175
- const warmKey = !replacement && warmPlanCacheEnabled && spec?.warmPlanInputs ? buildWarmPlanKey(spec.warmPlanInputs) : '';
1175
+ const warmKey = !replacement && warmPlanCacheEnabled && spec?.warmPlanInputs ? buildWarmPlanKey({ ...spec.warmPlanInputs,
1176
+ requestedLaunch: JSON.stringify([String(spec.cmd || ''), Array.isArray(spec.args) ? spec.args.map(String) : [], String(spec.modelId || '')]) }) : '';
1176
1177
  if (warmKey) {
1177
1178
  const cachedPlan = warmPlanCache.get(sessionId, warmKey);
1178
1179
  if (cachedPlan && states.get(sessionId)?.generation === generation
@@ -32,6 +32,11 @@ function buildWarmPlanKey({
32
32
  // full-MCP argv and silently ignore the setting. Defaults to '' so a caller that never
33
33
  // sets a profile keeps a stable key.
34
34
  mcpProfile = '',
35
+ // The REQUESTED launch (cmd, argv, model) the plan is built from. A plan is a function of the
36
+ // argv it was handed, so a restore asking for `--session <id>` must never be served the plan of
37
+ // the earlier fresh launch: an OpenCode tab has no CTM transcript, so the other inputs never move
38
+ // after its first prompt (opencode-v2-model-switch-standalone-plugin-2026-10-30.html revision 2).
39
+ requestedLaunch = '',
35
40
  } = {}) {
36
41
  return [
37
42
  Number(transcriptMtimeMs) || 0,
@@ -40,6 +45,7 @@ function buildWarmPlanKey({
40
45
  Number(codexConfigMtimeMs) || 0,
41
46
  String(cliVersionStamp || ''),
42
47
  String(mcpProfile || ''),
48
+ String(requestedLaunch || ''),
43
49
  ].join('\u241F');
44
50
  }
45
51
 
@@ -53,10 +53,40 @@ function provenTurnOutcome(turn) {
53
53
  return outcome && SETTLED_OUTCOMES.has(outcome) ? outcome : null;
54
54
  }
55
55
 
56
+ // WP-1: the owner fields every Claude observation derives from its result (`withCapturedRouteOwner`
57
+ // then overrides them from the item's captured owner when it has one).
58
+ function claudeObservedOwner(result) {
59
+ return {
60
+ ...(Number.isSafeInteger(result.processGeneration) ? { processGeneration: result.processGeneration } : {}),
61
+ ...(typeof result.providerSessionId === 'string' ? { providerThreadId: result.providerSessionId } : {}),
62
+ ...(typeof result.profileFingerprint === 'string' ? { profileFingerprint: result.profileFingerprint } : {}),
63
+ ...(typeof result.routeId === 'string' ? { routeId: result.routeId } : {}),
64
+ };
65
+ }
66
+
67
+ /*
68
+ * WP-1/D-1: a REFUSED Claude turn, as the switch consumes it. `model` is EMPTY on purpose -- the
69
+ * refusal names no model, which is exactly why the switch's `no_model` guard used to swallow it. The
70
+ * failure is only open while it is NEWER than the newest successful turn; a good turn after it wins.
71
+ */
72
+ function claudeFailureObservation(failure, observedOwner) {
73
+ const atMs = Number(failure.atMs) || 0;
74
+ const status = Number.isSafeInteger(failure.status) ? failure.status : null;
75
+ return {
76
+ model: '', outcome: 'failed', apiErrorStatus: status, error: `API Error ${status == null ? 'unknown' : status}`,
77
+ source: 'transcript:turn', observedAtMs: atMs,
78
+ ...(failure.turnId ? { turnId: String(failure.turnId) } : {}),
79
+ ...observedOwner,
80
+ };
81
+ }
82
+
56
83
  function turnObservationFrom(item, result) {
57
84
  if (!result) return null;
58
85
  if (item?.agentType === 'claude') {
59
86
  const truth = result.modelTruth;
87
+ if (truth && truth.lastTurnFailure && Number(truth.lastTurnFailure.atMs) > Number(truth.lastTurnAtMs || 0)) {
88
+ return withCapturedRouteOwner(claudeFailureObservation(truth.lastTurnFailure, claudeObservedOwner(result)), item);
89
+ }
60
90
  if (truth && truth.lastTurn && Number(truth.lastTurnAtMs) > 0) {
61
91
  return withCapturedRouteOwner({
62
92
  model: String(truth.lastTurn), effort: truth.effort || null, source: 'transcript:turn', observedAtMs: Number(truth.lastTurnAtMs),
@@ -116,8 +146,20 @@ function nativeTurnObservation(item, result) {
116
146
  const source = item?.agentType === 'claude' ? 'transcript:turn' : item?.agentType === 'codex' ? 'codex:turn-context' : null;
117
147
  if (!source || !Array.isArray(result.observations)) return null;
118
148
  let best = null;
149
+ // WP-1/D-1: a refused turn arrives as its OWN observation kind; it is not a turn, so it never
150
+ // competes for `best` -- it only wins by being newer than the newest completed turn.
151
+ let bestFailure = null;
119
152
  for (const o of result.observations) {
120
- if (!o || o.kind !== 'completed-turn' || o.source !== source) continue;
153
+ if (!o || o.source !== source) continue;
154
+ if (o.kind === 'failed-turn') {
155
+ const at = Number(o.observedAtMs);
156
+ if (!Number.isFinite(at) || at < 0) continue;
157
+ if (!bestFailure || at > bestFailure.at) bestFailure = { at, status: o.apiErrorStatus, turnId: o.sourceRevision || null,
158
+ processGeneration: o.processGeneration, providerThreadId: o.providerSessionId || null,
159
+ profileFingerprint: o.profileFingerprint || null, routeId: o.routeId || null };
160
+ continue;
161
+ }
162
+ if (o.kind !== 'completed-turn') continue;
121
163
  if (typeof o.value !== 'string' || !o.value.length) continue;
122
164
  const at = Number(o.observedAtMs);
123
165
  if (!Number.isFinite(at) || at < 0) continue;
@@ -128,6 +170,20 @@ function nativeTurnObservation(item, result) {
128
170
  profileFingerprint: o.profileFingerprint || null, routeId: o.routeId || null,
129
171
  };
130
172
  }
173
+ // A refusal newer than the newest successful turn is the answer; equal timestamps keep the
174
+ // completed turn, matching the decoder's own (timestamp, index) preference.
175
+ if (bestFailure && (!best || bestFailure.at > best.observedAtMs)) {
176
+ return withCapturedRouteOwner({
177
+ model: '', outcome: 'failed', apiErrorStatus: Number.isSafeInteger(bestFailure.status) ? bestFailure.status : null,
178
+ error: `API Error ${Number.isSafeInteger(bestFailure.status) ? bestFailure.status : 'unknown'}`,
179
+ source, observedAtMs: bestFailure.at,
180
+ ...(bestFailure.turnId ? { turnId: String(bestFailure.turnId) } : {}),
181
+ ...(Number.isSafeInteger(bestFailure.processGeneration) ? { processGeneration: bestFailure.processGeneration } : {}),
182
+ ...(bestFailure.providerThreadId ? { providerThreadId: String(bestFailure.providerThreadId) } : {}),
183
+ ...(bestFailure.profileFingerprint ? { profileFingerprint: String(bestFailure.profileFingerprint) } : {}),
184
+ ...(bestFailure.routeId ? { routeId: String(bestFailure.routeId) } : {}),
185
+ }, item);
186
+ }
131
187
  if (!best) return null;
132
188
  const obs = { model: String(best.model), effort: best.effort, source: best.source, observedAtMs: best.observedAtMs };
133
189
  if (best.turnId) obs.turnId = String(best.turnId);
@@ -34,6 +34,10 @@ const { isValidOverrideModel, isValidEffort } = require('./launch-model-override
34
34
  const { parseClaudeModel, resolveReasoningControl } = require('../../shared/reasoning-control');
35
35
  const { scanTomlStatements } = require('./agent-hooks-codex-toml');
36
36
  const { projectNativeModelArguments } = require('./session-model-launch-policy');
37
+ // WP-8 step 4 (§9b M-12): the ONE ACP config-option readers (lib/session-create/dsh-session.js), so the
38
+ // picker read, the owner bind and the runtime's setModel/setEffort agree on what the bridge offers.
39
+ const { findModelOption, findEffortOption, plainModelIdFromConfigValue, configOptionEntries } = require('./session-create/dsh-session');
40
+ const { servesAnthropicMessages, anthropicDeployments } = require('./acp/dsh-route-patch');
37
41
 
38
42
  const fs = require('fs');
39
43
  const os = require('os');
@@ -41,7 +45,7 @@ const path = require('path');
41
45
  const { codexHome } = require('./codex-paths');
42
46
 
43
47
  const CACHE_TTL_MS = 5 * 60 * 1000;
44
- const SUPPORTED_AGENTS = ['claude', 'codex'];
48
+ const SUPPORTED_AGENTS = ['claude', 'codex', 'opencode', 'dsh'];
45
49
  const ALIAS_LABELS = Object.freeze({ opus: 'Opus', sonnet: 'Sonnet', haiku: 'Haiku', fable: 'Fable' });
46
50
  // C3: the configured global is SMALL. Both the production reader and the injected-reader normalizer
47
51
  // apply this same UTF-8 byte bound, so "over the cap" is one state, not two.
@@ -374,11 +378,84 @@ function codexRows(models) {
374
378
  }));
375
379
  }
376
380
 
381
+ // --- DeepSeek Harness routes (WP-8 step 4, §9b M-12; D-12) ------------------------------------------
382
+ // dsh takes NO argv model (D-12) and has no CLI catalog of its own: its Lead rows are the Wall-E routes
383
+ // that can carry an ACP child, one row per (route, model). The predicate is lib/acp/dsh-route-patch.js's
384
+ // own pair — the route advertises `anthropic-messages` in `upstream_apis` AND has at least one
385
+ // `anthropic-messages` deployment (pi-ai's Bearer path exists only on that wire, D-21) — so the picker,
386
+ // the create card and `prepareDshRoute` can never disagree about which routes exist. Read from the SAME
387
+ // redacted worker-route feed the dsh launch uses (`oauthProxySupervisor.listWorkerRoutes()`, the
388
+ // `dshRouteDeps.listRoutes` wiring in server.js).
389
+ function dshRoutesFromFeed(routes) {
390
+ const out = [];
391
+ for (const route of (Array.isArray(routes) ? routes : [])) {
392
+ if (!route || typeof route.id !== 'string' || !route.id) continue;
393
+ if (route.enabled === false || route.enabled === 0 || route.routable === false) continue;
394
+ if (!servesAnthropicMessages(route)) continue;
395
+ const models = [];
396
+ for (const deployment of anthropicDeployments(route)) {
397
+ const wire = deployment.wireModelId.trim();
398
+ if (wire && !models.includes(wire)) models.push(wire);
399
+ }
400
+ if (!models.length) continue;
401
+ out.push({ routeAlias: String(route.id), name: String(route.name || route.display_name || route.id), models });
402
+ }
403
+ return out;
404
+ }
405
+
406
+ // Production's route reader: the worker-route feed above, projected into `[{routeAlias, name, models}]`.
407
+ // Lazy for the same reason as `listOpencodeModels` below: a caller that injects its own reader (every
408
+ // test, and a host that already holds one) never loads the proxy supervisor.
409
+ async function defaultListDshRoutes() {
410
+ return dshRoutesFromFeed(await require('./oauth-proxy-supervisor').listWorkerRoutes());
411
+ }
412
+
413
+ // The live ACP child's own `configOptions` for this session, or null when it is not running. The
414
+ // production runtime holds them in memory on its own process entry
415
+ // (`lib/acp/dsh-session-runtime.js` `get(id).configOptions`, §9b M-16), so the wiring belongs where
416
+ // that runtime instance is in scope (the `createLeadModelOptions` call site). With no reader the tab
417
+ // still answers (gateway rows only), which is exactly what a parked/childless dsh session gets.
418
+ const noDshConfigOptions = () => null;
419
+
420
+ // WP-8 step 4 (§9b M-12): a dsh tab ALSO offers the models ITS OWN live child advertises on the route
421
+ // it is running right now. Those rows carry NO `route_id` (the same route), so a pick from them is an
422
+ // in-place `session/set_config_option` and never a relaunch (WP-8 step 3). Built fresh on every get —
423
+ // never cached with the route rows, which describe Wall-E's catalog, not this child — and the caller's
424
+ // cached `value` object is never mutated.
425
+ function dshSessionRows(agent, value, sessionId, getDshConfigOptions) {
426
+ if (agent !== 'dsh') return value;
427
+ let configOptions = null;
428
+ try { configOptions = getDshConfigOptions(String(sessionId || '')); }
429
+ catch (error) { void error; /* REPORT: a failing reader means "no live child", i.e. gateway rows only */ }
430
+ if (!Array.isArray(configOptions) || !configOptions.length) return value;
431
+ const modelOption = findModelOption(configOptions);
432
+ const rows = [];
433
+ for (const entry of configOptionEntries(modelOption)) {
434
+ const plain = plainModelIdFromConfigValue(entry.value);
435
+ if (!plain) continue;
436
+ rows.push({ kind: 'model', value: plain, label: entry.name || plain });
437
+ }
438
+ const effortOption = findEffortOption(configOptions);
439
+ const levels = configOptionEntries(effortOption).map((entry) => entry.value);
440
+ return {
441
+ ...value,
442
+ rows: rows.concat(Array.isArray(value.rows) ? value.rows : []),
443
+ efforts: { values: levels, model_default: null },
444
+ };
445
+ }
446
+
377
447
  /**
378
448
  * @param {object} deps
379
449
  * @param {(sessionId: string) => ({ type: string, agent: string|null }|null)} deps.getSessionInfo
380
450
  * @param {() => Promise<object[]>} deps.loadCatalog Wall-E coding-catalog rows (rejects when Wall-E is unreachable)
381
451
  * @param {() => Promise<{ state: string, reason?: string, error?: string, models: object[] }>} deps.listCodexModels
452
+ * @param {(opts?: object) => Promise<object[]>} [deps.listOpencodeModels] `<opencode> models --verbose`
453
+ * rows (lib/opencode-cli.js `opencodeCatalog`). Rejects with `.code === 'opencode_catalog_unavailable'`.
454
+ * @param {() => Promise<object[]>} [deps.listDshRoutes] the Anthropic-messages routes a dsh child may
455
+ * run on, `[{ routeAlias, name, models: string[] }]`. Absent = the real worker-route feed
456
+ * (lib/oauth-proxy-supervisor.js `listWorkerRoutes()`); rejects into `list_error`.
457
+ * @param {(sessionId: string) => (object[]|null)} [deps.getDshConfigOptions] the live dsh child's own
458
+ * ACP `configOptions`, or null/absent when it is not running (gateway rows only).
382
459
  * @param {() => ({ path?: string, text: string }|{ state: string }|null|Promise<object|null>)} [deps.readClaudeSettings]
383
460
  * `$CLAUDE_CONFIG_DIR/settings.json`, else `~/.claude/settings.json`. The legacy `{ path, text }` shape
384
461
  * is still accepted (the path is ignored); `null` means "no config file".
@@ -388,6 +465,9 @@ function codexRows(models) {
388
465
  */
389
466
  function createLeadModelOptions({
390
467
  getSessionInfo, loadCatalog, listCodexModels,
468
+ listOpencodeModels = () => require('./opencode-cli').opencodeCatalog(),
469
+ listDshRoutes = defaultListDshRoutes,
470
+ getDshConfigOptions = noDshConfigOptions,
391
471
  readClaudeSettings = defaultReadClaudeSettings,
392
472
  readCodexConfigToml = defaultReadCodexConfigToml,
393
473
  now = Date.now,
@@ -405,6 +485,13 @@ function createLeadModelOptions({
405
485
  let generation = 0;
406
486
 
407
487
  async function build(agent) {
488
+ // OpenCode (docs/opencode-lead-and-delegate-model-selection-2026-10-28.html §6.2, R-5): the rows
489
+ // are the CLI's OWN `models --verbose` catalog -- Wall-E's coding catalog describes Anthropic
490
+ // routes, not OpenCode providers, so it is neither read nor counted here.
491
+ if (agent === 'opencode') return buildOpencode();
492
+ // dsh (docs/deepseek-harness-acp-agent-2026-10-28.html §9b M-12): its rows are the routes that can
493
+ // carry an ACP child, never Wall-E's coding-catalog rows (which describe Anthropic CLI providers).
494
+ if (agent === 'dsh') return buildDsh();
408
495
  let catalogRows = null;
409
496
  let catalogError = '';
410
497
  let catalogErrorCode = '';
@@ -456,6 +543,66 @@ function createLeadModelOptions({
456
543
  return forAgentOf(entryFor(agent));
457
544
  }
458
545
 
546
+ // The OpenCode rows: whatever the installed CLI lists, or none plus the typed reason (R-5 says a
547
+ // pick must equal a catalog row, so an unreadable catalog yields NO clickable rows -- never a
548
+ // guessed one). `not_switchable_count` is null on purpose: Wall-E's catalog says nothing about
549
+ // OpenCode providers, so there is no honest count to publish.
550
+ async function buildOpencode() {
551
+ let rows = [];
552
+ let listError = '';
553
+ try {
554
+ const listed = await listOpencodeModels();
555
+ rows = Array.isArray(listed) ? listed : [];
556
+ } catch (error) {
557
+ // REPORT: the picker footer shows this (same `list_error` field Codex uses); nothing is thrown.
558
+ const code = String((error && error.code) || 'opencode_catalog_unavailable');
559
+ const message = String((error && error.message) || error || '').trim();
560
+ listError = (message.startsWith(code) ? message : `${code}: ${message}`).slice(0, 200);
561
+ }
562
+ return {
563
+ agent: 'opencode',
564
+ source: 'opencode-cli',
565
+ rows,
566
+ not_switchable_count: null,
567
+ ...(listError ? { list_error: listError } : {}),
568
+ };
569
+ }
570
+
571
+ // The dsh rows: one `gateway` row per (route, model), labelled with the model id and sub-labelled with
572
+ // the route's display name (the same two strings a Claude gateway row shows). `not_switchable_count`
573
+ // is null for the same reason as OpenCode's: Wall-E's coding catalog says nothing about which models a
574
+ // dsh child may run, so there is no honest count of "hidden" rows.
575
+ async function buildDsh() {
576
+ let routes = [];
577
+ let listError = '';
578
+ try {
579
+ const listed = await listDshRoutes();
580
+ routes = Array.isArray(listed) ? listed : [];
581
+ } catch (error) {
582
+ // REPORT: the picker footer shows this (the same `list_error` field Codex and OpenCode use).
583
+ const code = String((error && error.code) || 'dsh_routes_unavailable');
584
+ const message = String((error && error.message) || error || '').trim();
585
+ listError = (message.startsWith(code) ? message : `${code}: ${message}`).slice(0, 200);
586
+ }
587
+ const rows = [];
588
+ for (const route of routes) {
589
+ if (!route || typeof route.routeAlias !== 'string' || !route.routeAlias) continue;
590
+ const name = String(route.name || route.routeAlias);
591
+ for (const model of (Array.isArray(route.models) ? route.models : [])) {
592
+ const value = String(model || '').trim();
593
+ if (!value) continue;
594
+ rows.push({ kind: 'gateway', value, route_id: route.routeAlias, label: value, sublabel: name });
595
+ }
596
+ }
597
+ return {
598
+ agent: 'dsh',
599
+ source: 'dsh-routes',
600
+ rows,
601
+ not_switchable_count: null,
602
+ ...(listError ? { list_error: listError } : {}),
603
+ };
604
+ }
605
+
459
606
  // The per-generation entry holder. It is deliberately ONE object per (agent, generation): a
460
607
  // background revalidation mutates this object in place, so an answer already delivered under the
461
608
  // same generation keeps matching the accepted expensive entry (C5) even while a revalidation runs.
@@ -573,6 +720,13 @@ function createLeadModelOptions({
573
720
  function beginConfigRead(agent, gen) {
574
721
  const ordinal = (configInvocation.get(agent) || 0) + 1;
575
722
  configInvocation.set(agent, ordinal);
723
+ // OpenCode has no configured-global CTM may read: R-6 forbids writing (or inferring from) its
724
+ // user config/state, so `absent` is the one honest record and NEITHER reader above runs. A dsh tab
725
+ // is the same: the DeepSeek Harness has no CLI config file at all (D-12 — CTM never injects a model
726
+ // into dsh), so its record is the state-only `absent` one, published without opening any path.
727
+ if (agent === 'opencode' || agent === 'dsh') {
728
+ return { ordinal, gen, promise: Promise.resolve({ gen, ordinal, record: finalizeConfigRecord(stateOnlyConfigRecord('absent')) }) };
729
+ }
576
730
  const reader = agent === 'claude' ? readClaudeSettings : readCodexConfigToml;
577
731
  const promise = readConfigRecordFor(agent, reader).then(
578
732
  (record) => ({ gen, ordinal, record }),
@@ -672,7 +826,10 @@ function createLeadModelOptions({
672
826
  if (read.gen !== generation) return supersededResponse();
673
827
  const record = acceptConfigRecord(agent, read.ordinal, applyCodexListFallback(read.record, agent, value));
674
828
  if (record === SUPERSEDED) return supersededResponse();
675
- return publish(agent, entry, value, record);
829
+ // WP-8 step 4 (§9b M-12): a dsh tab's answer is the cached route rows PLUS the live child's own
830
+ // in-route rows and effort levels. Only the dsh branch touches the cached object, and it returns a
831
+ // fresh one, so the agent-level cache stays exactly what `buildDsh()` produced.
832
+ return publish(agent, entry, dshSessionRows(agent, value, sessionId, getDshConfigOptions), record);
676
833
  }
677
834
 
678
835
  // D-7: Wall-E POSTs /api/internal/model-registry/changed whenever the model registry or pricing
@@ -692,4 +849,4 @@ function createLeadModelOptions({
692
849
  return { get, invalidate, isCurrent };
693
850
  }
694
851
 
695
- module.exports = { createLeadModelOptions, claudeModelValue, claudeCatalogRows, aliasRows, CACHE_TTL_MS, claudeEfforts, CLI_EFFORTS };
852
+ module.exports = { createLeadModelOptions, claudeModelValue, claudeCatalogRows, aliasRows, CACHE_TTL_MS, claudeEfforts, CLI_EFFORTS, dshRoutesFromFeed };