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
@@ -20,8 +20,11 @@
20
20
 
21
21
  const crypto = require('crypto');
22
22
  const path = require('path');
23
+ const url = require('node:url');
23
24
 
24
25
  const { WORKER_MODEL_ALIAS } = require('../model-proxy/lead-worker-picks');
26
+ const { compareSemver } = require('../semver-compare');
27
+ const { CODEX_NATIVE_SUBAGENT_DENY_MIN_VERSION } = require('../codex-harness-qualification');
25
28
  // The ONE literal effort grammar CTM accepts for a launch effort (same validator the lead-launch
26
29
  // override and the launch-truth store use), so a malformed value can never be projected as known.
27
30
  const { isValidEffort } = require('../launch-model-override');
@@ -34,12 +37,17 @@ const { isValidEffort } = require('../launch-model-override');
34
37
  const SUBAGENT_PICK_READ_TIMEOUT_MS = 1500;
35
38
 
36
39
  const LANES = new Set(['native', 'routed', 'inherit']);
37
- const AGENTS = new Set(['claude', 'codex']);
40
+ const AGENTS = new Set(['claude', 'codex', 'opencode']);
38
41
  // D-5: a route resolution failure with one of these codes is a first-class 'unservable' pick
39
42
  // (surfaced in the picker, WP-6), never a silent fall-back to another route or model.
40
43
  const UNSERVABLE_ROUTE_CODES = new Set([
41
44
  'route_unknown', 'route_disabled', 'route_not_routable', 'route_not_servable',
42
45
  ]);
46
+ // R-1/R-3 (subagent-pick-unreachable-origin-2026-10-28.html): a route that resolves fine here can
47
+ // still be dialed from THIS machine only if its upstream origin answers. These are the two probe
48
+ // verdicts that mean "no request from this host can succeed", so the pick is refused up front
49
+ // instead of handing every native subagent an HTTP 502 at its first request.
50
+ const ORIGIN_UNREACHABLE_REACH = new Set(['unreachable', 'no_server']);
43
51
 
44
52
  // Codex's built-in subagent role texts, copied VERBATIM from
45
53
  // codex-rs/core/src/agent/role.rs:349-381 @ 322bbf4d848; re-copy on Codex bumps.
@@ -165,8 +173,12 @@ function _originFields(origin, mirroredAt) {
165
173
  }
166
174
 
167
175
  /**
168
- * @param {{ ctmSessionId?: string, agent?: 'claude'|'codex', leadRoute?: object|null }} input
169
- * @param {{ walleClient?: object, readiness?: object, mirror?: object, setTimeout?: Function, clearTimeout?: Function }} deps
176
+ * @param {{ ctmSessionId?: string, agent?: 'claude'|'codex'|'opencode', leadRoute?: object|null }} input
177
+ * @param {{ walleClient?: object, readiness?: object, mirror?: object, setTimeout?: Function, clearTimeout?: Function, originVerdict?: Function }} deps
178
+ * `originVerdict` is OPTIONAL: the credential-less upstream-origin probe
179
+ * (lib/model-availability.js `originVerdict`) consulted for a ROUTED pick, so a route whose origin
180
+ * cannot be reached from this machine is refused as `origin_unreachable` rather than launched
181
+ * into a 502.
170
182
  * `mirror` is OPTIONAL (lib/storage/delegate-pref-mirror-store.js). When present, a successful read
171
183
  * backfills it from the read's ladder[] and a FAILED read folds it (M-1/M-2a) instead of giving up.
172
184
  * @returns {Promise<{kind:'none', reason:'vendor'|'read_failed'|'not_lead', detail?: string}
@@ -221,6 +233,13 @@ async function resolveSubagentPick(input, deps) {
221
233
  }
222
234
 
223
235
  // The lane gate above already dropped everything else, so this is 'routed' (D-4/D-5/D-11).
236
+ if (agent === 'opencode') {
237
+ // §6.5/§9 S-20 (opencode-lead-and-delegate-model-selection-2026-10-28.html): an OpenCode lead
238
+ // reaches a routed worker through the Wall-E MCP server, not through CTM's lead proxy -- the
239
+ // routed lane is WP-5. Until it lands this is a typed, non-fallback refusal, so a saved routed
240
+ // pick is visibly unserved rather than silently projected as something the lead cannot carry.
241
+ return { kind: 'unservable', agent, reason: 'opencode_routed_requires_mcp', source, ..._originFields(origin, mirroredAt) };
242
+ }
224
243
  if (agent === 'codex' && !args.leadRoute) {
225
244
  // D-11: checked BEFORE readiness -- the lead's transport is what makes this unservable, and
226
245
  // there is no point resolving a route we can never reach.
@@ -254,6 +273,25 @@ async function resolveSubagentPick(input, deps) {
254
273
  || metadataRevision.length > 256 || /[\u0000-\u001f\u007f]/.test(metadataRevision)) {
255
274
  return { kind: 'unservable', agent, reason: 'model_metadata_unavailable', source, ..._originFields(origin, mirroredAt) };
256
275
  }
276
+ // R-1/R-3/R-4: the route exists, but it is only servable if THIS machine can dial the
277
+ // upstream origin. Same cached, single-flighted probe buildAvailability uses -- no second
278
+ // probe path, and never a header off the route row.
279
+ const upstreamOrigin = require('../model-availability').originOf(resolved.baseUrl);
280
+ if (upstreamOrigin) {
281
+ const verdictFn = typeof d.originVerdict === 'function'
282
+ ? d.originVerdict
283
+ : require('../model-availability').originVerdict;
284
+ let verdict = null;
285
+ try {
286
+ verdict = await verdictFn(upstreamOrigin);
287
+ } catch (err) {
288
+ /* REPORT: a probe failure is "no gate" (R-3), never a refusal. */
289
+ process.stderr.write('[subagent-pick] origin verdict failed: ' + _detail(err) + '\n');
290
+ }
291
+ if (verdict && ORIGIN_UNREACHABLE_REACH.has(verdict.reach)) {
292
+ return { kind: 'unservable', agent, reason: 'origin_unreachable', source, ..._originFields(origin, mirroredAt) };
293
+ }
294
+ }
257
295
  return {
258
296
  kind: 'routed',
259
297
  agent,
@@ -326,22 +364,397 @@ function _codexPlan(model, effort, dataDir) {
326
364
  codexArgs,
327
365
  leadProxy: null,
328
366
  roleFile: { path: rolePath, contents: _roleFileBody(model, effort) },
367
+ nativeSubagents: '',
329
368
  };
330
369
  }
331
370
 
371
+ // WP-1 (codex-native-subagent-pick-enforcement-2026-10-28.html §5 D-2, §10.1): the two ROOT config
372
+ // overrides that make an injected `collaboration.spawn_agent` call answer `unsupported call` on the
373
+ // installed Codex. NOT `--disable multi_agent[_v2]`: for a model whose catalog entry declares
374
+ // `multi_agent_version: "v2"` (every GPT-6 model, §1.4) the session resolves to V2 and the flag form
375
+ // is bypassed. `agents.enabled=false` alone is overridden when `features.multi_agent_v2` is on, so
376
+ // BOTH tokens ship together. The worker deny tokens (lib/session-create/worker-launch-sandbox.js,
377
+ // codexWorkerDenyArgs) are a separate, unchanged mechanism (OQ-1).
378
+ const CODEX_NATIVE_SUBAGENT_DENY_ARGS = Object.freeze([
379
+ '-c', 'agents.enabled=false',
380
+ '-c', 'features.multi_agent_v2.disable_native_tools=true',
381
+ ]);
382
+
383
+ // WP-1b: the deny prefix's own version floor lives with every other Codex version fact
384
+ // (lib/codex-harness-qualification.js, CODEX_NATIVE_SUBAGENT_DENY_MIN_VERSION); see D-2b.
385
+
386
+ /**
387
+ * D-3. True when a Codex lead's native children cannot be redirected to the stored ROUTED pick, so
388
+ * the launch must unregister the native multi-agent tools. An `inherit` pick means "same as the
389
+ * lead", which a native child already satisfies, so nothing is denied; every Claude pick is env-only.
390
+ * PURE.
391
+ * @param {{kind?: string, agent?: string, reason?: string}|null} pick resolveSubagentPick() result
392
+ * @returns {boolean}
393
+ */
394
+ function nativeSubagentsDenied(pick) {
395
+ return !!pick && pick.kind === 'unservable' && pick.agent === 'codex'
396
+ && pick.reason !== 'codex_inherit_unenforceable';
397
+ }
398
+
399
+ // R-6/§6.5 (§5 R-10): OpenCode's per-launch carrier. CTM NEVER writes OpenCode's user config or
400
+ // state -- the pick rides `OPENCODE_CONFIG_CONTENT`, the inline runtime config OpenCode merges OVER
401
+ // its file config for this process only, as an agent-config model for OpenCode's own built-in
402
+ // subagents (`general`, `explore`). PURE, apart from reading the string it is handed.
403
+ //
404
+ // native name the pick's model on agent.general.model and agent.explore.model
405
+ // inherit OpenCode's own default IS "a subagent follows the model of the primary agent that
406
+ // invoked it", so nothing is emitted at all -- and, decisively, no existing
407
+ // OPENCODE_CONFIG_CONTENT in the launch env is rewritten on the way past
408
+ //
409
+ // `existingConfigContent` is the launch env's current OPENCODE_CONFIG_CONTENT, if any. It is parsed
410
+ // as a JSON object and EVERY other key is carried through verbatim. A value that is not a JSON
411
+ // object (unparseable, null, an array, a bare scalar) is replaced by `{}` and reported in `applied`
412
+ // as `existing_config_unparseable`, so a caller can see that something was discarded rather than
413
+ // silently losing it.
414
+ function _asConfigObject(value) {
415
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
416
+ }
417
+
418
+ function opencodeLaunchProjection({ pick, existingConfigContent } = {}) {
419
+ if (!pick || pick.kind !== 'native') return { env: {}, applied: [] };
420
+ const applied = [];
421
+ const raw = existingConfigContent === undefined || existingConfigContent === null ? '' : String(existingConfigContent);
422
+ let config = {};
423
+ if (raw.trim() !== '') {
424
+ let parsed = null;
425
+ try {
426
+ parsed = JSON.parse(raw);
427
+ } catch {
428
+ parsed = null; // expected: an unparseable existing value is replaced and reported below
429
+ }
430
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) config = parsed;
431
+ else applied.push('existing_config_unparseable');
432
+ }
433
+ const model = String(pick.model || '');
434
+ const currentAgent = _asConfigObject(config.agent);
435
+ const next = {
436
+ ...config,
437
+ agent: {
438
+ ...currentAgent,
439
+ general: { ..._asConfigObject(currentAgent.general), model },
440
+ explore: { ..._asConfigObject(currentAgent.explore), model },
441
+ },
442
+ };
443
+ applied.push('agent.general.model', 'agent.explore.model');
444
+ return { env: { OPENCODE_CONFIG_CONTENT: JSON.stringify(next) }, applied };
445
+ }
446
+
447
+ // §5 R-6 / §9 S-21 (opencode-lead-and-delegate-model-selection-2026-10-28.html §6.5, WP-5): the
448
+ // Wall-E MCP server for an OpenCode LEAD, carried inline for THIS launch only.
449
+ //
450
+ // R-6 is absolute: CTM NEVER writes `~/.config/opencode/*`. OpenCode merges `OPENCODE_CONFIG_CONTENT`
451
+ // OVER its file config for this process only, so the server is injected by extending the very same
452
+ // inline document an OpenCode pick already rides (see opencodeLaunchProjection). OpenCode's `mcp`
453
+ // entries are LOCAL (stdio) servers: { type: 'local', command: [executable, ...args], enabled: true }.
454
+ //
455
+ // The entry itself (`walleMcp`) is the canonical stdio-proxy command resolved by Wall-E's own
456
+ // mcp-integration (`{ command, args }`) -- never a hand-built path (see
457
+ // CTM lib/walle-mcp-auto-config.js's opencodeWalleMcpEntry). `command` is the resolved real node
458
+ // binary and `args` is the walle-mcp-stdio.js script; CTM_SESSION_ID / WALL_E_API_TOKEN already ride
459
+ // the lead's own env, so nothing else has to be injected here.
460
+ //
461
+ // PURE: no fs, no env reads. `existingConfigContent` is parsed as a JSON object and EVERY other key
462
+ // is carried through verbatim -- including the other servers of an existing `mcp` map. A value that
463
+ // is not a JSON object (unparseable, null, an array, a bare scalar) is replaced by `{}` and REPORTED
464
+ // to stderr (the same `existing_config_unparseable` fact opencodeLaunchProjection exposes in its
465
+ // `applied` list), so a caller can see that something was discarded rather than silently losing it.
466
+ // `walleMcp` null/absent -> null: nothing to add, and the caller keeps whatever it already had.
467
+ function opencodeWalleMcpProjection({ existingConfigContent, walleMcp } = {}) {
468
+ if (!walleMcp) return null;
469
+ const command = [walleMcp.command, ...(Array.isArray(walleMcp.args) ? walleMcp.args : [])];
470
+ const raw = existingConfigContent === undefined || existingConfigContent === null ? '' : String(existingConfigContent);
471
+ let config = {};
472
+ if (raw.trim() !== '') {
473
+ let parsed = null;
474
+ try {
475
+ parsed = JSON.parse(raw);
476
+ } catch {
477
+ parsed = null; // expected: an unparseable existing value is replaced and reported below
478
+ }
479
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) config = parsed;
480
+ else {
481
+ /* REPORT: the launch's existing inline config could not be parsed and is being discarded. */
482
+ process.stderr.write('[subagent-pick] existing OPENCODE_CONFIG_CONTENT discarded (existing_config_unparseable)\n');
483
+ }
484
+ }
485
+ const next = {
486
+ ...config,
487
+ mcp: {
488
+ ..._asConfigObject(config.mcp),
489
+ 'wall-e': { type: 'local', command, enabled: true },
490
+ },
491
+ };
492
+ return JSON.stringify(next);
493
+ }
494
+
495
+ // WP-2 (opencode-model-selection-round-2-shared-route-logic-2026-10-30.html §5 R-3/R-6, §6.2, §9
496
+ // S-7): a routed OpenCode LEAD's `ctm-lead` provider entry rides the SAME inline
497
+ // OPENCODE_CONFIG_CONTENT document the subagent pick and the Wall-E MCP server ride -- ONE writer,
498
+ // one blob, never ~/.config/opencode/*. `configPatch` is prepareLeadRoute's §6.2 patch
499
+ // (`{ provider: { 'ctm-lead': opencodeLeadProvider(...) }, model: 'ctm-lead/lead' }`).
500
+ //
501
+ // PURE: no fs, no env reads. Same parse/carry-through/report rules as opencodeWalleMcpProjection
502
+ // above: every other key is carried through verbatim, `provider` is DEEP-merged so a launch that
503
+ // already holds other providers keeps them, and the top-level `model` ref is set (a stable ref,
504
+ // R-4 -- the route/wire model live inside the provider entry, not the ref). A value that is not a
505
+ // JSON object (unparseable, null, an array, a bare scalar) is replaced by `{}` and REPORTED to
506
+ // stderr. `configPatch` null/absent -> null: nothing to add, and the caller keeps what it had.
507
+ function opencodeLeadRouteProjection({ existingConfigContent, configPatch } = {}) {
508
+ if (!configPatch) return null;
509
+ const raw = existingConfigContent === undefined || existingConfigContent === null ? '' : String(existingConfigContent);
510
+ let config = {};
511
+ if (raw.trim() !== '') {
512
+ let parsed = null;
513
+ try {
514
+ parsed = JSON.parse(raw);
515
+ } catch {
516
+ parsed = null; // expected: an unparseable existing value is replaced and reported below
517
+ }
518
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) config = parsed;
519
+ else {
520
+ /* REPORT: the launch's existing inline config could not be parsed and is being discarded. */
521
+ process.stderr.write('[subagent-pick] existing OPENCODE_CONFIG_CONTENT discarded (existing_config_unparseable)\n');
522
+ }
523
+ }
524
+ const next = {
525
+ ...config,
526
+ provider: {
527
+ ..._asConfigObject(config.provider),
528
+ ..._asConfigObject(configPatch.provider),
529
+ },
530
+ model: configPatch.model,
531
+ };
532
+ return JSON.stringify(next);
533
+ }
534
+
535
+ // WP-5 (opencode-v2-model-switch-standalone-plugin-2026-10-30.html §5 R-9, §5.2 D-9/D-10/D-11, §6.7):
536
+ // the OpenCode 1.x MODEL CARRIER, added to the launch's inline `plugin` array for the ONE switch
537
+ // relaunch. 1.x plugin entries are just strings, so an entry is a `file://` URL of a repo file and the
538
+ // target rides the spawn env instead (D-10: no installer change, no `~/.config/opencode` write, R-6).
539
+ //
540
+ // The URL is derived from THIS module's location, so it points at the checkout CTM is running from.
541
+ const OPENCODE_V1_CARRIER_URL = url.pathToFileURL(
542
+ path.join(__dirname, '..', 'opencode-v1-plugin', 'model-carrier.mjs'),
543
+ ).href;
544
+
545
+ // PURE: no fs, no env reads. Same parse/carry-through/report rules as opencodeLeadRouteProjection
546
+ // above: every other key of the inline document is carried through verbatim, and a value that is not
547
+ // a JSON object (unparseable, null, an array, a bare scalar) is replaced by `{}` and REPORTED to
548
+ // stderr. `plugin` is the existing array MINUS any entry equal to the carrier URL, PLUS the carrier
549
+ // URL — so two calls leave exactly one carrier entry and any other plugin entry survives. A non-array
550
+ // `plugin` is replaced by `[]` and reported the same way. Always returns a JSON string.
551
+ function opencodeV1CarrierProjection({ existingConfigContent } = {}) {
552
+ const raw = existingConfigContent === undefined || existingConfigContent === null ? '' : String(existingConfigContent);
553
+ let config = {};
554
+ if (raw.trim() !== '') {
555
+ let parsed = null;
556
+ try {
557
+ parsed = JSON.parse(raw);
558
+ } catch {
559
+ parsed = null; // expected: an unparseable existing value is replaced and reported below
560
+ }
561
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) config = parsed;
562
+ else {
563
+ /* REPORT: the launch's existing inline config could not be parsed and is being discarded. */
564
+ process.stderr.write('[subagent-pick] existing OPENCODE_CONFIG_CONTENT discarded (existing_config_unparseable)\n');
565
+ }
566
+ }
567
+ const existingPlugins = Array.isArray(config.plugin) ? config.plugin : null;
568
+ if (existingPlugins === null && config.plugin !== undefined) {
569
+ /* REPORT: a non-array plugin list is discarded so the carrier entry has somewhere to live. */
570
+ process.stderr.write('[subagent-pick] existing OpenCode plugin list discarded (plugin_not_array)\n');
571
+ }
572
+ const next = {
573
+ ...config,
574
+ plugin: [...(existingPlugins || []).filter((entry) => entry !== OPENCODE_V1_CARRIER_URL), OPENCODE_V1_CARRIER_URL],
575
+ };
576
+ return JSON.stringify(next);
577
+ }
578
+
579
+ // PURE: the OpenCode session id a RESTORE resumes, read from the launch argv. The value after
580
+ // `--session` / `-s`, or after `--session=`. '' when there is none (e.g. `--continue`/`-c`, or a fresh
581
+ // launch) — which is exactly the "no carrier" decision the spawn path makes.
582
+ function opencodeResumeSessionId(args) {
583
+ const list = Array.isArray(args) ? args : [];
584
+ for (let i = 0; i < list.length; i += 1) {
585
+ const arg = String(list[i] == null ? '' : list[i]);
586
+ if (arg.startsWith('--session=')) return arg.slice('--session='.length);
587
+ if (arg === '--session' || arg === '-s') {
588
+ const next = list[i + 1];
589
+ return next == null ? '' : String(next);
590
+ }
591
+ }
592
+ return '';
593
+ }
594
+
595
+ // WP-5 (opencode-v2-model-switch-standalone-plugin-2026-10-30.html §5.1 R-9, §6.7): the PURE decision
596
+ // for the OpenCode 1.x model carrier — a prompted v1 tab switches by park + resume relaunch, and the
597
+ // carrier rides the ONE relaunch's inline config + env. Nothing is injected for a fresh tab (its
598
+ // `--model` argv already works), a worker, a v2/unreadable binary, or a restore with no session id.
599
+ // Returns `spawnEnv` UNCHANGED (same object) unless a carrier was armed, so a caller compares identity.
600
+ function opencodeV1CarrierEnv({ agentType, isRestore, agentRole, line, nativeModel, launchLeadRoute, launchArgs, spawnEnv }) {
601
+ if (!spawnEnv || agentType !== 'opencode' || isRestore !== true || agentRole === 'worker' || line !== 'v1') return spawnEnv;
602
+ const target = typeof nativeModel === 'string' && nativeModel
603
+ ? nativeModel
604
+ : (launchLeadRoute && typeof launchLeadRoute === 'object' ? require('./opencode-lead-provider').OPENCODE_LEAD_MODEL_REF : '');
605
+ const sid = opencodeResumeSessionId(launchArgs);
606
+ if (!target || !sid) return spawnEnv;
607
+ return {
608
+ ...spawnEnv,
609
+ OPENCODE_CONFIG_CONTENT: opencodeV1CarrierProjection({ existingConfigContent: spawnEnv.OPENCODE_CONFIG_CONTENT }),
610
+ CTM_OPENCODE_SWITCH_MODEL: target,
611
+ CTM_OPENCODE_SWITCH_SESSION: sid,
612
+ };
613
+ }
614
+
615
+ // WP-2 (opencode-v2-model-switch-standalone-plugin-2026-10-30.html §5.1 R-3, §5.2 D-4/D-6, §6.2): the
616
+ // OpenCode 2.x CTM plugin, carried inline in OPENCODE_CONFIG_CONTENT. 2.x plugin entries are objects
617
+ // `{ package, options }` and the package must be an absolute DIRECTORY (a single file path is skipped,
618
+ // P-S7), so the entry points at this repo directory and OpenCode loads its `server.js`. Nothing is
619
+ // written under ~/.config/opencode (R-6): there is no install step and upgrades ship with CTM.
620
+ const OPENCODE_V2_PLUGIN_DIR = path.join(__dirname, '..', 'opencode-v2-plugin');
621
+
622
+ // PURE: no fs, no env reads. Same parse/carry-through/report rules as opencodeV1CarrierProjection
623
+ // above: every other key of the inline document survives verbatim, and a value that is not a JSON
624
+ // object (unparseable, null, an array, a bare scalar) is replaced by `{}` and REPORTED to stderr.
625
+ // `plugins` is the existing array MINUS any entry whose package is this plugin dir, PLUS one
626
+ // `{ package: OPENCODE_V2_PLUGIN_DIR, options }` — so two calls leave exactly one entry and any other
627
+ // plugin entry survives. A non-array `plugins` is replaced by `[]` and reported the same way. When the
628
+ // caller passes `options.switch_model` (WP-4), the inline `model` is set too (R-4, D-4). Always
629
+ // returns a JSON string.
630
+ function opencodeV2PluginProjection({ existingConfigContent, options } = {}) {
631
+ const raw = existingConfigContent === undefined || existingConfigContent === null ? '' : String(existingConfigContent);
632
+ let config = {};
633
+ if (raw.trim() !== '') {
634
+ let parsed = null;
635
+ try {
636
+ parsed = JSON.parse(raw);
637
+ } catch {
638
+ parsed = null; // expected: an unparseable existing value is replaced and reported below
639
+ }
640
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) config = parsed;
641
+ else {
642
+ /* REPORT: the launch's existing inline config could not be parsed and is being discarded. */
643
+ process.stderr.write('[subagent-pick] existing OPENCODE_CONFIG_CONTENT discarded (existing_config_unparseable)\n');
644
+ }
645
+ }
646
+ const existingPlugins = Array.isArray(config.plugins) ? config.plugins : null;
647
+ if (existingPlugins === null && config.plugins !== undefined) {
648
+ /* REPORT: a non-array plugin list is discarded so the CTM plugin entry has somewhere to live. */
649
+ process.stderr.write('[subagent-pick] existing OpenCode plugins list discarded (plugins_not_array)\n');
650
+ }
651
+ const kept = (existingPlugins || []).filter((entry) => !(entry && typeof entry === 'object' && entry.package === OPENCODE_V2_PLUGIN_DIR));
652
+ const next = { ...config, plugins: [...kept, { package: OPENCODE_V2_PLUGIN_DIR, options }] };
653
+ const sw = options && typeof options === 'object' ? options.switch_model : null;
654
+ if (sw && typeof sw === 'object') next.model = String(sw.model && sw.model.providerID) + '/' + String(sw.model && sw.model.id);
655
+ return JSON.stringify(next);
656
+ }
657
+
658
+ // PURE: the switch target of a v2 RESTORE launch, derived EXACTLY like opencodeV1CarrierEnv's target:
659
+ // a native `provider/model` pick wins, else a routed relaunch's fixed ref ctm-lead/lead, else ''. ''
660
+ // means "no switch" -- a fresh launch, a worker, or a restore with no target at all.
661
+ function opencodeV2SwitchTarget({ isRestore, nativeModel, launchLeadRoute } = {}) {
662
+ if (isRestore !== true) return '';
663
+ if (typeof nativeModel === 'string' && nativeModel) return nativeModel;
664
+ if (launchLeadRoute && typeof launchLeadRoute === 'object') return require('./opencode-lead-provider').OPENCODE_LEAD_MODEL_REF;
665
+ return '';
666
+ }
667
+
668
+ // PURE: split a target ref at the FIRST '/' into the 2.x plugin's `switch_model` argument (R-4). null
669
+ // for a missing ref, no '/', an empty side, or an empty session id -- the cases where nothing may be
670
+ // switched (an unqualified target simply carries no `switch_model`, it is never guessed).
671
+ function opencodeV2SwitchModel(target, sessionID) {
672
+ if (typeof target !== 'string' || !target) return null;
673
+ const sid = String(sessionID == null ? '' : sessionID);
674
+ if (!sid) return null;
675
+ const at = target.indexOf('/');
676
+ if (at <= 0 || at === target.length - 1) return null;
677
+ const providerID = target.slice(0, at);
678
+ const id = target.slice(at + 1);
679
+ if (!providerID || !id) return null;
680
+ return { sessionID: sid, model: { providerID, id } };
681
+ }
682
+
683
+ // PURE: the `switch_model` a v2 plugin env carries (or null), read from the SAME inline blob OpenCode
684
+ // receives -- so the spawn diagnostic can never disagree with what the plugin was handed.
685
+ function opencodeV2SwitchModelOf(spawnEnv) {
686
+ if (!spawnEnv) return null;
687
+ let config = null;
688
+ try {
689
+ config = JSON.parse(String(spawnEnv.OPENCODE_CONFIG_CONTENT || ''));
690
+ } catch {
691
+ /* expected: a blob that is not JSON is one the projection never wrote, so it carries no switch */
692
+ return null;
693
+ }
694
+ const entries = config && typeof config === 'object' && Array.isArray(config.plugins) ? config.plugins : [];
695
+ const mine = entries.find((e) => e && typeof e === 'object' && e.package === OPENCODE_V2_PLUGIN_DIR);
696
+ const sw = mine && mine.options && typeof mine.options === 'object' ? mine.options.switch_model : null;
697
+ return sw && typeof sw === 'object' ? sw : null;
698
+ }
699
+
700
+ // PURE: the OpenCode 2.x spawn-path decision, the v2 twin of opencodeV1CarrierEnv above. Every v2 LEAD
701
+ // launch carries the plugin, so unlike the 1.x carrier this is not gated on a restore. Returns
702
+ // `spawnEnv` UNCHANGED (same object) unless agentType==='opencode', agentRole!=='worker', line==='v2'
703
+ // and the launch argv names a session id (WP-1 puts `--session ses_…` on every v2 launch); then
704
+ // `{ ...spawnEnv, OPENCODE_CONFIG_CONTENT: projection }` carrying identity from the same spawn env.
705
+ // WP-4 (R-4): a RESTORE also carries `switch_model` (native target, else ctm-lead/lead) -- the project-
706
+ // ion then sets the inline `model` too, so a never-prompted tab is seeded and an existing one switched.
707
+ function opencodeV2PluginEnv({ agentType, isRestore, agentRole, line, nativeModel, launchLeadRoute, launchArgs, spawnEnv }) {
708
+ if (!spawnEnv || agentType !== 'opencode' || agentRole === 'worker' || line !== 'v2') return spawnEnv;
709
+ const sessionId = opencodeResumeSessionId(launchArgs);
710
+ if (!sessionId) return spawnEnv;
711
+ const options = {
712
+ ctm_port: String(spawnEnv.CTM_PORT || ''),
713
+ ctm_session_id: String(spawnEnv.CTM_SESSION_ID || ''),
714
+ process_generation: String(spawnEnv.CTM_MODEL_PROCESS_GENERATION || ''),
715
+ session_id: sessionId,
716
+ };
717
+ const switchModel = opencodeV2SwitchModel(opencodeV2SwitchTarget({ isRestore, nativeModel, launchLeadRoute }), sessionId);
718
+ if (switchModel) options.switch_model = switchModel;
719
+ return { ...spawnEnv, OPENCODE_CONFIG_CONTENT: opencodeV2PluginProjection({ existingConfigContent: spawnEnv.OPENCODE_CONFIG_CONTENT, options }) };
720
+ }
721
+
332
722
  /**
333
723
  * PURE: no fs, no env reads. Same input -> same output.
334
- * @param {{ kind: string, agent?: 'claude'|'codex', model?: string, effort?: string }|null} pick
335
- * @param {{ agent?: 'claude'|'codex', leadRoute?: object|null, dataDir?: string }} options
724
+ * @param {{ kind: string, agent?: 'claude'|'codex'|'opencode', model?: string, effort?: string }|null} pick
725
+ * @param {{ agent?: 'claude'|'codex'|'opencode', leadRoute?: object|null, dataDir?: string, codexVersion?: string,
726
+ * existingConfigContent?: string }} options
727
+ * `codexVersion` is the version of the Codex binary THIS launch spawns (''/absent = unknown). The
728
+ * deny prefix carries a config key that older Codex builds cannot load, so it is applied only at or
729
+ * above CODEX_NATIVE_SUBAGENT_DENY_MIN_VERSION; an unknown version never denies.
336
730
  * @returns {{ env: Record<string,string>, deleteEnv: string[], codexArgs: string[],
337
- * leadProxy: null|{alias:string}, roleFile: null|{path:string, contents:string} }}
731
+ * leadProxy: null|{alias:string}, roleFile: null|{path:string, contents:string},
732
+ * nativeSubagents: ''|'disabled' }}
338
733
  */
339
734
  function planSubagentLaunch(pick, options) {
340
735
  const opts = options || {};
341
- // kind 'none' | 'unservable' (or a missing pick) is the byte-identical baseline: zero env keys,
342
- // zero argv items, no role file (D-1).
736
+ // WP-1 (§5 D-2/D-3): a Codex pick that resolves `unservable` for any reason OTHER than
737
+ // `codex_inherit_unenforceable` is a ROUTED stored lane native children cannot reach, so this
738
+ // launch refuses them: the deny prefix rides the SAME `codexArgs` prepend as a native role file
739
+ // (D-5), with no env, no role file and no lead proxy. WP-1b: only when this launch's own Codex is
740
+ // new enough to know the config key. Unknown = not denied, so the compare's `null` (an unparseable
741
+ // side) is excluded EXPLICITLY: `null >= 0` is true in JavaScript (relational comparison coerces
742
+ // null to 0), so relying on the bare `>=` would deny every unprobed launch.
743
+ const codexVersionRank = compareSemver(opts.codexVersion, CODEX_NATIVE_SUBAGENT_DENY_MIN_VERSION);
744
+ if (nativeSubagentsDenied(pick) && codexVersionRank !== null && codexVersionRank >= 0) {
745
+ return {
746
+ env: {},
747
+ deleteEnv: [],
748
+ codexArgs: [...CODEX_NATIVE_SUBAGENT_DENY_ARGS],
749
+ leadProxy: null,
750
+ roleFile: null,
751
+ nativeSubagents: 'disabled',
752
+ };
753
+ }
754
+ // kind 'none' | `codex_inherit_unenforceable` | any Claude unservable | a missing pick is the
755
+ // byte-identical baseline: zero env keys, zero argv items, no role file (D-1).
343
756
  if (!pick || (pick.kind !== 'native' && pick.kind !== 'inherit' && pick.kind !== 'routed')) {
344
- return { env: {}, deleteEnv: [], codexArgs: [], leadProxy: null, roleFile: null };
757
+ return { env: {}, deleteEnv: [], codexArgs: [], leadProxy: null, roleFile: null, nativeSubagents: '' };
345
758
  }
346
759
 
347
760
  const agent = pick.agent || opts.agent;
@@ -355,6 +768,14 @@ function planSubagentLaunch(pick, options) {
355
768
  return _codexPlan(model, effort, opts.dataDir);
356
769
  }
357
770
 
771
+ if (agent === 'opencode') {
772
+ // §5 R-10/§6.5: the OpenCode lane is per-launch inline config only. A native pick names the
773
+ // model on OpenCode's two built-in subagents; an inherit pick emits nothing (see
774
+ // opencodeLaunchProjection). No role file, no argv, no lead proxy.
775
+ const projection = opencodeLaunchProjection({ pick, existingConfigContent: opts.existingConfigContent });
776
+ return { env: projection.env, deleteEnv: [], codexArgs: [], leadProxy: null, roleFile: null, nativeSubagents: '' };
777
+ }
778
+
358
779
  // D-2/D-3/D-4: the Claude side is env-only. FORCE is what defeats per-call models, Explore and
359
780
  // frontmatter (C5-C7); without it a pick would be advisory only.
360
781
  if (pick.kind === 'inherit') {
@@ -364,6 +785,7 @@ function planSubagentLaunch(pick, options) {
364
785
  codexArgs: [],
365
786
  leadProxy: null,
366
787
  roleFile: null,
788
+ nativeSubagents: '',
367
789
  };
368
790
  }
369
791
  if (pick.kind === 'native') {
@@ -373,6 +795,7 @@ function planSubagentLaunch(pick, options) {
373
795
  codexArgs: [],
374
796
  leadProxy: null,
375
797
  roleFile: null,
798
+ nativeSubagents: '',
376
799
  };
377
800
  }
378
801
 
@@ -383,6 +806,7 @@ function planSubagentLaunch(pick, options) {
383
806
  codexArgs: [],
384
807
  leadProxy: null,
385
808
  roleFile: null,
809
+ nativeSubagents: '',
386
810
  };
387
811
  if (!opts.leadRoute) {
388
812
  // D-4: the lead is not on the proxy, so the launcher has to put it on one. It registers the
@@ -444,6 +868,11 @@ function projectSubagentPick(subagentPick) {
444
868
  source: String(subagentPick.source || ''),
445
869
  origin: String(subagentPick.origin || ''),
446
870
  ...(subagentPick.reason ? { reason: subagentPick.reason } : {}),
871
+ // WP-1 (§5 D-4): the fact the LAUNCH recorded, never a re-derivation from today's rule. The
872
+ // key is additive and only present when this launch actually carried the deny ('' is the
873
+ // separate "known not denied" state, null/absent = unknown) -- a session launched before the
874
+ // deny existed must never be described as denied.
875
+ ...(typeof subagentPick.nativeSubagents === 'string' ? { native_subagents: subagentPick.nativeSubagents } : {}),
447
876
  };
448
877
  }
449
878
  return subagentPick.reason && subagentPick.reason !== 'vendor'
@@ -467,6 +896,10 @@ function projectLaunchRow(row) {
467
896
  source: String(row.source || ''),
468
897
  origin: String(row.origin || ''),
469
898
  ...(row.reason ? { reason: row.reason } : {}),
899
+ // WP-1 (§5 D-4, v58): what the persisted launch DID about native sub-agents. null = a pre-v58
900
+ // row (unknown), '' = a known "not denied", 'disabled' = this launch carried the deny prefix.
901
+ // A malformed stored value never claims a deny -- the WRITE validates, the read does not trust.
902
+ native_subagents: row.native_subagents === '' || row.native_subagents === 'disabled' ? row.native_subagents : null,
470
903
  };
471
904
  }
472
905
 
@@ -474,6 +907,22 @@ module.exports = {
474
907
  resolveSubagentPick,
475
908
  reconcileMirrorDefaults,
476
909
  planSubagentLaunch,
910
+ nativeSubagentsDenied,
911
+ CODEX_NATIVE_SUBAGENT_DENY_ARGS,
912
+ CODEX_NATIVE_SUBAGENT_DENY_MIN_VERSION,
913
+ opencodeLaunchProjection,
914
+ opencodeWalleMcpProjection,
915
+ opencodeLeadRouteProjection,
916
+ opencodeV1CarrierProjection,
917
+ opencodeResumeSessionId,
918
+ opencodeV1CarrierEnv,
919
+ OPENCODE_V1_CARRIER_URL,
920
+ opencodeV2PluginProjection,
921
+ opencodeV2PluginEnv,
922
+ opencodeV2SwitchModel,
923
+ opencodeV2SwitchTarget,
924
+ opencodeV2SwitchModelOf,
925
+ OPENCODE_V2_PLUGIN_DIR,
477
926
  appliedNativeEffort,
478
927
  projectSubagentPick,
479
928
  projectLaunchRow,
@@ -148,7 +148,7 @@ async function createWalleSession(state) {
148
148
  deps.sendWalleSessionHistory(ws, session, { source: 'walle-resume' })
149
149
  .catch((error) => deps.sendWalleHistoryErrorFrame(ws, id, {}, error));
150
150
  }
151
- deps.broadcastSessionList(true);
151
+ deps.broadcastSessionList(true, { reason: 'walle-session-created' });
152
152
  if (initialMessage) {
153
153
  setImmediate(() => {
154
154
  deps.handleWalleMessage(ws, {
@@ -10,14 +10,24 @@ function isProxiedRecipe(recipe) {
10
10
  }
11
11
 
12
12
  // Copy the proxy's journaled stop record into the worker row, so every later status read sees it.
13
+ // C7-B2-P1 (docs/worker-deferred-stop-record-generation-2026-10-27.html §5 D1/D3, §6): the copy is
14
+ // fenced by the numeric generation this call was HANDED -- the one the exit path read BEFORE the awaited
15
+ // journal read -- against the row's existing `Number(row.generation) || 1` interpretation. A row that is
16
+ // absent, or whose number moved while the read was in flight, is observed only: no merge and no write.
17
+ // This is a fence across one await on a numeric column, not a claim of physical launch identity and not
18
+ // a claim that a numeric generation can never be reused (D4).
13
19
  async function persistStopRecord(db, ctmSessionId, generation) {
14
20
  const record = await readStopRecord({ ctmSessionId, generation });
15
21
  if (!record) return null;
16
22
  try {
17
23
  const row = db.getWorkerSession(ctmSessionId);
24
+ // Observation only: nothing is copied into a row this call cannot confirm still owns `generation`.
25
+ if (!row || (Number(row.generation) || 1) !== generation) return record;
18
26
  let runtime = {};
19
27
  try { runtime = JSON.parse(row.runtime_json) || {}; } catch { /* expected: a malformed runtime row is treated as empty */ }
20
- db.updateWorkerSession(ctmSessionId, { runtime_json: JSON.stringify({ ...runtime, requestBudget: { ...(runtime.requestBudget || {}), stop: record, terminalCode: record.kind } }) });
28
+ // The merge is unchanged; the captured generation travels beside the patch as the existing helper's
29
+ // storage predicate, so a row that advanced since the read above matches zero rows.
30
+ db.updateWorkerSession(ctmSessionId, { runtime_json: JSON.stringify({ ...runtime, requestBudget: { ...(runtime.requestBudget || {}), stop: record, terminalCode: record.kind } }) }, { expectedGeneration: generation });
21
31
  } catch (error) {
22
32
  console.warn(`[worker-budget-setup] stop record not persisted (${String(error && error.code || 'persist_failed').slice(0, 48)})`);
23
33
  }
@@ -46,11 +56,20 @@ function initialSnapshot(generation, policy, capabilityRevision = '') {
46
56
  }
47
57
 
48
58
  // Proxied: persist the journaled stop (if any), then run(); proxyless (or no readable row): run() synchronously, as before.
59
+ // C7-B2-P1 (§5 D2/D3, §6): the numeric generation read BEFORE the await is retained across it and handed
60
+ // to run(), which never re-derives its target from a post-await status or runtime. The post-await recheck
61
+ // sits INSIDE the existing catch boundary (it is part of the returned chain), so an unreadable current row
62
+ // reports on the existing path and no callback runs -- including when the journal read returned null.
49
63
  function retireAfterStopRecord(getDb, ctmSessionId, readRecipe, run) {
50
64
  let db = null, row = null;
51
65
  try { db = getDb(); row = db.getWorkerSession(ctmSessionId); } catch { row = null; /* expected: an unreadable row is handled as proxyless */ }
52
66
  if (!row || !isProxiedRecipe(readRecipe(row))) return run();
53
- return persistStopRecord(db, ctmSessionId, Number(row.generation) || 1).then(run)
67
+ const generation = Number(row.generation) || 1;
68
+ return persistStopRecord(db, ctmSessionId, generation).then(() => {
69
+ const current = db.getWorkerSession(ctmSessionId);
70
+ if (!current || (Number(current.generation) || 1) !== generation) return;
71
+ return run(generation);
72
+ })
54
73
  .catch((error) => { console.warn(`[worker-budget-setup] retire after exit failed (${String(error && error.code || 'retire_failed').slice(0, 48)})`); /* REPORT: the row stays un-stamped; the boot reload retries */ });
55
74
  }
56
75
 
@@ -128,7 +128,7 @@ function _validRecipe(row) {
128
128
  const agent = String(row.agent || recipe.agent || '');
129
129
  const route = String(row.route_alias || recipe.route || '');
130
130
  const model = String(row.model || recipe.model || '');
131
- if (!['claude', 'codex'].includes(agent) || !route || !model) return null;
131
+ if (!['claude', 'codex', 'dsh'].includes(agent) || !route || !model) return null;
132
132
  if (recipe.route && recipe.route !== route) return null;
133
133
  if (recipe.model && recipe.model !== model) return null;
134
134
  return { ...recipe, briefPath: recipe.renderedBriefPath || recipe.briefPath, agent, route, model };
@@ -326,6 +326,12 @@ function createWorkerContinuation({ getDeps, admission = admissionDefault, helpe
326
326
 
327
327
  const recipe = _validRecipe(row);
328
328
  if (!recipe) return blocked('worker_recipe_missing', 'The durable worker launch recipe is unavailable.', { ctm_session_id: id });
329
+ // WP-14 (deepseek-harness-acp-agent-2026-10-28 D-21): a dsh worker is an ACP child, and this path
330
+ // relaunches an argv CLI against its provider transcript. Until ACP session/resume is wired here,
331
+ // refuse explicitly instead of launching the wrong process.
332
+ if (recipe.agent === 'dsh') {
333
+ return blocked('worker_resume_unsupported', 'A DeepSeek Harness worker cannot be resumed yet; delegate a new worker with the correction.', { ctm_session_id: id, retryable: false });
334
+ }
329
335
  const oldProfile = recipe.workerProfile || 'legacy';
330
336
  // D-11 (routed-worker-visibility-and-review-workers-2026-10-16.html): a review recipe is
331
337
  // capability-pinned exactly like an implementation one.