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
@@ -1,5 +1,8 @@
1
1
  'use strict';
2
2
 
3
+ // S-1 (D-4): the command-to-agent rule lives in ONE place — the UMD module shared with the client.
4
+ const { detectAgentType } = require('../public/js/agent-command');
5
+
3
6
  const CONV_CLI_SURFACES = Object.freeze({
4
7
  conv: Object.freeze({ available: true, live: true, source: 'canonical-rows' }),
5
8
  cli: Object.freeze({ available: true, source: 'pty' }),
@@ -13,9 +16,26 @@ const CLI_ONLY_SURFACES = Object.freeze({
13
16
  conv: Object.freeze({ available: false, live: false, source: 'canonical-rows' }),
14
17
  cli: Object.freeze({ available: true, source: 'pty' }),
15
18
  });
19
+ // D-4 (deepseek-harness-acp-agent-2026-10-28.html §5): an ACP agent driven over pipes has no
20
+ // terminal UI, so its only surface is the Conversation view backed by canonical rows — the client
21
+ // creates no xterm for it (public/js/stream-view.js gates on surfaces.cli.available).
22
+ const CONV_ONLY_SURFACES = Object.freeze({
23
+ conv: Object.freeze({ available: true, live: true, source: 'canonical-rows' }),
24
+ cli: Object.freeze({ available: false, source: 'pty' }),
25
+ });
26
+
27
+ // WP-15 (§15.4 R-15.1): how an agent takes its INITIAL prompt. 'positional' = the CLI's own
28
+ // initial-prompt operand (`claude [prompt]`, `codex [PROMPT]`, `gemini query`, `cursor-agent
29
+ // [prompt...]`, C-15), which is the vendor-supported way to start a session already working.
30
+ // 'queue' = the prompt must go through the session's own transport (walle: a real structured turn,
31
+ // C-5). null = neither is measured to submit, so a server-started session must refuse instead of
32
+ // silently never starting (C-8 dsh; OQ-15.1 opencode).
33
+ const LAUNCH_PROMPT_POSITIONAL = 'positional';
34
+ const LAUNCH_PROMPT_QUEUE = 'queue';
16
35
 
17
36
  const AGENT_CAPABILITIES = Object.freeze({
18
37
  claude: Object.freeze({
38
+ launchPrompt: LAUNCH_PROMPT_POSITIONAL,
19
39
  agentType: 'claude',
20
40
  providerId: 'claude-code',
21
41
  label: 'Claude Code',
@@ -36,12 +56,20 @@ const AGENT_CAPABILITIES = Object.freeze({
36
56
  steer: 'queue-interrupt', rewind: false, branch: false,
37
57
  modelControl: false, branching: false, export: true,
38
58
  promptNavigation: 'transcript',
59
+ // WP-1 (wheel-reader-only-where-history-exists-2026-10-28 §5 D-3/D-4): the continuous reader can
60
+ // only serve a provider whose history CTM owns as a transcript -- the exact set server.js's
61
+ // _transcriptHistoryProvider (:39540) can answer (claude, codex). Every other provider has no
62
+ // reader source, so its wheel-up must forward to the app instead of opening a reader that exits
63
+ // at once (opencode incident 2026-10-28). Flips to 'transcript' only in the same commit that
64
+ // teaches _transcriptHistoryProvider to return that provider.
65
+ historyReader: 'transcript',
39
66
  review: true,
40
67
  resume: true,
41
68
  resumeSpec: (id) => ({ cmd: 'claude', args: ['--resume', id] }),
42
69
  titleSpec: { kind: 'agentSdk', readsUserName: true, readsAiTitle: true, launchArgs: (name) => ['-n', name] },
43
70
  }),
44
71
  codex: Object.freeze({
72
+ launchPrompt: LAUNCH_PROMPT_POSITIONAL,
45
73
  agentType: 'codex',
46
74
  providerId: 'codex',
47
75
  label: 'Codex',
@@ -62,12 +90,15 @@ const AGENT_CAPABILITIES = Object.freeze({
62
90
  steer: 'queue-interrupt', rewind: false, branch: false,
63
91
  modelControl: false, branching: false, export: true,
64
92
  promptNavigation: 'transcript',
93
+ // Codex rollout lanes are a real reader source (_transcriptHistoryProvider, server.js:39540).
94
+ historyReader: 'transcript',
65
95
  review: true,
66
96
  resume: true,
67
97
  resumeSpec: (id) => ({ cmd: 'codex', args: ['resume', id] }),
68
98
  titleSpec: { kind: 'appServerJsonRpc', readsUserName: true, readsAiTitle: false },
69
99
  }),
70
100
  gemini: Object.freeze({
101
+ launchPrompt: LAUNCH_PROMPT_POSITIONAL,
71
102
  agentType: 'gemini',
72
103
  providerId: 'gemini',
73
104
  label: 'Gemini',
@@ -80,6 +111,8 @@ const AGENT_CAPABILITIES = Object.freeze({
80
111
  semanticApprovals: false,
81
112
  steer: false, rewind: false, branch: false, modelControl: false,
82
113
  promptNavigation: 'terminal',
114
+ // No CTM transcript / rollout rows for gemini -- the continuous reader has no source here.
115
+ historyReader: 'none',
83
116
  review: false,
84
117
  resume: true,
85
118
  // Resumes by provider session id, not a CTM-owned JSONL transcript, so restart-restore must
@@ -88,7 +121,49 @@ const AGENT_CAPABILITIES = Object.freeze({
88
121
  resumeSpec: (id) => ({ cmd: 'gemini', args: ['--session', id] }),
89
122
  titleSpec: null,
90
123
  }),
124
+ // WP-2 (D-3/D-4, §9 S-2): DeepSeek Harness is driven over the Agent Client Protocol on pipes
125
+ // (lib/acp/*, a later WP), never through node-pty, so it declares conv-only surfaces and ACP
126
+ // input/control fidelity. dsh resumes through ACP `session/resume` on attach using the stored ACP
127
+ // session id, and CTM's own transcript IS required (resumesWithoutTranscript:false — the session
128
+ // cannot be restored from the provider alone). resumeSpec is null because there is no CLI argv
129
+ // resume form. WP-2 only registers the id; no session can be created yet.
130
+ dsh: Object.freeze({
131
+ // WP-15 (§15.4 R-15.3, C-8): an ACP session has no ptyProcess, so no launch operand exists and
132
+ // the queue lane's sendFn answers `session_not_live` forever -- a server-started dsh session
133
+ // would never start. Declared null so callers refuse instead.
134
+ launchPrompt: null,
135
+ agentType: 'dsh',
136
+ providerId: 'dsh',
137
+ label: 'DeepSeek Harness',
138
+ liveStatus: true,
139
+ stateDetector: false,
140
+ structuredTranscript: true,
141
+ surfaces: CONV_ONLY_SURFACES,
142
+ semanticInput: true,
143
+ semanticQueue: false,
144
+ semanticApprovals: true,
145
+ inputFidelity: 'acp', controlFidelity: 'acp',
146
+ messages: Object.freeze({ stream: true, durable: true, branching: false }),
147
+ input: Object.freeze({ send: true, queue: false, interrupt: true, attachments: false }),
148
+ approvals: Object.freeze({ authoritative: true, resolve: true }),
149
+ work: Object.freeze({ phase: true, tasks: false, subagents: false }),
150
+ controls: Object.freeze({ model: false, cwd: false, sandbox: false }),
151
+ artifacts: Object.freeze({ files: true, diffs: false, tests: false }),
152
+ steer: 'queue-interrupt', rewind: false, branch: false,
153
+ modelControl: false, branching: false, export: true,
154
+ promptNavigation: 'transcript',
155
+ historyReader: 'none',
156
+ review: false,
157
+ resume: true,
158
+ resumeSpec: () => null,
159
+ resumesWithoutTranscript: false,
160
+ titleSpec: null,
161
+ }),
91
162
  opencode: Object.freeze({
163
+ // WP-15 (§15.4 R-15.3, OQ-15.1): `opencode --prompt` exists, but whether the TUI submits it is
164
+ // UNMEASURED, so it is not claimed here. Flips to `{ flag: '--prompt' }` only with a recorded
165
+ // PTY transcript.
166
+ launchPrompt: null,
92
167
  agentType: 'opencode',
93
168
  providerId: 'opencode',
94
169
  label: 'OpenCode',
@@ -111,6 +186,9 @@ const AGENT_CAPABILITIES = Object.freeze({
111
186
  steer: 'queue-interrupt', rewind: false, branch: false,
112
187
  modelControl: false, branching: false, export: true,
113
188
  promptNavigation: 'transcript',
189
+ // OpenCode rows land in session_message_rows (commitProviderSnapshot), never in the
190
+ // transcript_files/transcript_events the reader reads -- no reader source, so wheel-up forwards.
191
+ historyReader: 'none',
114
192
  review: true,
115
193
  resume: true,
116
194
  // OpenCode keeps history in its own SQLite db (no CTM JSONL transcript, file_size 0), and its
@@ -126,6 +204,8 @@ const AGENT_CAPABILITIES = Object.freeze({
126
204
  titleSpec: null,
127
205
  }),
128
206
  walle: Object.freeze({
207
+ // C-5: the queue lane delivers a real structured turn for a walle session, so it does submit.
208
+ launchPrompt: LAUNCH_PROMPT_QUEUE,
129
209
  agentType: 'walle',
130
210
  providerId: 'walle',
131
211
  label: 'Wall-E',
@@ -146,12 +226,16 @@ const AGENT_CAPABILITIES = Object.freeze({
146
226
  steer: true, rewind: true, branch: true,
147
227
  modelControl: true, branching: true, export: true,
148
228
  promptNavigation: 'chat',
229
+ // The reader serves terminal providers; Wall-E's native surface is not one.
230
+ historyReader: 'none',
149
231
  review: true,
150
232
  resume: false,
151
233
  resumeSpec: () => null,
152
234
  titleSpec: null,
153
235
  }),
154
236
  cursor: Object.freeze({
237
+ // C-15: `cursor-agent [options] [command] [prompt...]`.
238
+ launchPrompt: LAUNCH_PROMPT_POSITIONAL,
155
239
  agentType: 'cursor',
156
240
  providerId: 'cursor',
157
241
  label: 'Cursor',
@@ -164,6 +248,8 @@ const AGENT_CAPABILITIES = Object.freeze({
164
248
  semanticApprovals: false,
165
249
  steer: false, rewind: false, branch: false, modelControl: false,
166
250
  promptNavigation: 'terminal',
251
+ // Cursor chats are cloud-backed with no local transcript -- no reader source.
252
+ historyReader: 'none',
167
253
  review: false,
168
254
  resume: true,
169
255
  // Cursor chats are cloud-backed: there's no local transcript (JSONL/SQLite)
@@ -175,6 +261,8 @@ const AGENT_CAPABILITIES = Object.freeze({
175
261
  titleSpec: null,
176
262
  }),
177
263
  shell: Object.freeze({
264
+ // A plain shell has no agent to hand a prompt to.
265
+ launchPrompt: null,
178
266
  agentType: 'shell',
179
267
  providerId: 'shell',
180
268
  label: 'Shell',
@@ -187,6 +275,8 @@ const AGENT_CAPABILITIES = Object.freeze({
187
275
  semanticApprovals: false,
188
276
  steer: false, rewind: false, branch: false, modelControl: false,
189
277
  promptNavigation: 'terminal',
278
+ // A plain shell has no CTM transcript -- the reader has no source, so its wheel-up forwards.
279
+ historyReader: 'none',
190
280
  review: false,
191
281
  resume: false,
192
282
  resumeSpec: () => null,
@@ -194,24 +284,14 @@ const AGENT_CAPABILITIES = Object.freeze({
194
284
  }),
195
285
  });
196
286
 
197
- function detectAgentType(cmd) {
198
- if (!cmd) return null;
199
- const s = String(cmd).toLowerCase();
200
- if (s.includes('claude')) return 'claude';
201
- if (s.includes('codex')) return 'codex';
202
- if (s.includes('gemini')) return 'gemini';
203
- if (s.includes('opencode') || s.includes('open-code')) return 'opencode';
204
- if (s.includes('cursor')) return 'cursor';
205
- if (s === 'walle' || s.includes('wall-e') || s.includes('walle')) return 'walle';
206
- return null;
207
- }
208
-
209
287
  function normalizeAgentType(value) {
210
288
  if (!value) return null;
211
289
  const s = String(value).toLowerCase().replace(/_/g, '-');
212
290
  if (AGENT_CAPABILITIES[s]) return s;
213
291
  if (s === 'claude-code') return 'claude';
214
292
  if (s === 'gemini-cli') return 'gemini';
293
+ // D-3/S-4: the `_`→`-` rewrite above already turns 'deepseek_harness' into this form.
294
+ if (s === 'deepseek-harness') return 'dsh';
215
295
  if (s === 'opencode-cli' || s === 'open-code') return 'opencode';
216
296
  if (s === 'cursor-agent' || s === 'cursor-cli') return 'cursor';
217
297
  return detectAgentType(s);
@@ -222,6 +302,19 @@ function getAgentCapabilities(value) {
222
302
  return AGENT_CAPABILITIES[type] || AGENT_CAPABILITIES.shell;
223
303
  }
224
304
 
305
+ // WP-11 (D-20, docs/deepseek-harness-acp-agent-2026-10-28.html): the PER-SESSION form of the
306
+ // capability record. The static `dsh` row above stays `input.attachments: false` — what a dsh session
307
+ // can actually accept is the LIVE child's own `initialize` answer
308
+ // (`agentCapabilities.promptCapabilities.image`, lib/acp/dsh-session-runtime.js), which is false for a
309
+ // bridge that does not advertise image prompts. `imageInput:true` is therefore the ONLY way a dsh
310
+ // session advertises attachments, and every other agent type is returned unchanged.
311
+ function getSessionAgentCapabilities(value, { imageInput = false } = {}) {
312
+ const type = normalizeAgentType(value) || 'shell';
313
+ const caps = AGENT_CAPABILITIES[type] || AGENT_CAPABILITIES.shell;
314
+ if (type !== 'dsh') return caps;
315
+ return { ...caps, input: Object.freeze({ ...(caps.input || {}), attachments: imageInput === true }) };
316
+ }
317
+
225
318
  function getAgentSurfaceIds(value) {
226
319
  const surfaces = getAgentCapabilities(value).surfaces || CLI_ONLY_SURFACES;
227
320
  return ['cli', 'web', 'conv'].filter((surface) => surfaces[surface]?.available === true);
@@ -251,14 +344,51 @@ function getTitleSpec(agentType) {
251
344
  return caps.titleSpec || null;
252
345
  }
253
346
 
347
+ // WP-15 (§15.4 R-15.1, §15.5 API): the ONE place that knows how to turn (agentType, prompt) into
348
+ // launch argv. `launchPromptMode` is the read-only query; `launchPromptArgv` is the argv builder for
349
+ // the only mode that has an argv form.
350
+ function launchPromptMode(agentType) {
351
+ const mode = getAgentCapabilities(agentType).launchPrompt;
352
+ return mode === LAUNCH_PROMPT_POSITIONAL || mode === LAUNCH_PROMPT_QUEUE ? mode : null;
353
+ }
354
+
355
+ function typedError(code, message) {
356
+ const error = new Error(message);
357
+ error.code = code;
358
+ return error;
359
+ }
360
+
361
+ // R-15.4: a prompt is only safe as a positional operand when it cannot be read as a flag or as a
362
+ // subcommand. Never insert `--`: lib/create/resolve-launch-params.js:327 declines its approval-hook
363
+ // injection for an argv whose terminator is not last, so a `--` here would silently drop the hook.
364
+ function launchPromptArgv(agentType, prompt) {
365
+ const type = normalizeAgentType(agentType) || String(agentType == null ? '' : agentType).toLowerCase() || 'unknown';
366
+ if (launchPromptMode(agentType) !== LAUNCH_PROMPT_POSITIONAL) {
367
+ throw typedError('launch_prompt_unsupported', `${type} does not take its prompt as a launch argument`);
368
+ }
369
+ if (typeof prompt !== 'string' || prompt.trim() === '') {
370
+ throw typedError('prompt_not_argv_safe', 'a launch prompt must be a non-empty string');
371
+ }
372
+ if (prompt[0] === '-') {
373
+ throw typedError('prompt_not_argv_safe', 'a launch prompt must not start with "-" (it would parse as an option)');
374
+ }
375
+ if (!/\s/.test(prompt.trim())) {
376
+ throw typedError('prompt_not_argv_safe', 'a launch prompt must be more than one bare word (it could match a subcommand)');
377
+ }
378
+ return [prompt];
379
+ }
380
+
254
381
  module.exports = {
255
382
  AGENT_CAPABILITIES,
256
383
  detectAgentType,
257
384
  normalizeAgentType,
258
385
  getAgentCapabilities,
386
+ getSessionAgentCapabilities,
259
387
  getAgentSurfaceIds,
260
388
  canResumeAgent,
261
389
  resumesWithoutTranscript,
262
390
  getResumeSpec,
263
391
  getTitleSpec,
392
+ launchPromptMode,
393
+ launchPromptArgv,
264
394
  };
@@ -8,7 +8,9 @@ const { definitionForCli } = require('./agent-definitions');
8
8
  const { canAutoUpgrade } = require('./agent-upgrade-planner');
9
9
 
10
10
  const CACHE_VERSION = 2;
11
- const DEFAULT_AGENTS = Object.freeze(['claude', 'codex', 'gemini', 'opencode', 'cursor-agent']);
11
+ // WP-2 (S-5): the DeepSeek Harness CLI (`dsh`) joins discovery so an install is found on PATH;
12
+ // _cliBinForAgentType maps agentType 'dsh' to the same binary name through its default fallback.
13
+ const DEFAULT_AGENTS = Object.freeze(['claude', 'codex', 'gemini', 'opencode', 'cursor-agent', 'dsh']);
12
14
 
13
15
  function _pathHash(envPath) {
14
16
  return crypto.createHash('sha256').update(String(envPath || '')).digest('hex');
@@ -212,6 +214,29 @@ function _publicEntry(entry, envPath) {
212
214
  };
213
215
  }
214
216
 
217
+ /**
218
+ * WP-13b: the definition's `installIdentity` (agent-definitions.js, the `dsh` entry) is the same
219
+ * rule lib/agent-version-service.js applies at version-check time (_installIdentityHolds), and it
220
+ * exists because the NAME is not unique on this host: homebrew ships the unrelated "Dancer's shell"
221
+ * as `dsh`, so a bare PATH hit would report the DeepSeek Harness as installed when it is not.
222
+ *
223
+ * An agent whose definition carries no `installIdentity` is untouched (claude/codex/opencode/gemini/
224
+ * cursor read exactly as before). An entry that is not `available` at all is also untouched — there
225
+ * is no path to judge, and "not found" stays "not found".
226
+ */
227
+ function _installIdentityHolds(def, entry) {
228
+ if (!def || !def.installIdentity || !entry || !entry.available) return true;
229
+ // The realpath decides when discovery resolved one: a symlink's own path proves nothing.
230
+ return def.installIdentity.test(String(entry.realpath || entry.path || ''));
231
+ }
232
+
233
+ /** `_publicEntry`, plus the install-identity gate: a wrong-named binary is surfaced as unavailable. */
234
+ function _publicEntryForAgent(agent, entry, envPath) {
235
+ const pub = _publicEntry(entry, envPath);
236
+ if (_installIdentityHolds(definitionForCli(agent), entry)) return pub;
237
+ return { ...pub, available: false };
238
+ }
239
+
215
240
  function discoverAgent(agent, { env = process.env, now = Date.now(), extraBinDirs, preferCandidate } = {}) {
216
241
  const envPath = env.PATH || process.env.PATH || '';
217
242
  return _entryFromFingerprint(agent, _findOnPath(agent, envPath, extraBinDirs, preferCandidate), envPath, now, 'path-scan');
@@ -244,8 +269,8 @@ function getAvailableAgents({
244
269
  cache.agents[agent] = entry;
245
270
  changed = true;
246
271
  }
247
- result.agents[agent] = _publicEntry(entry, envPath);
248
- result[agent] = !!entry.available;
272
+ result.agents[agent] = _publicEntryForAgent(agent, entry, envPath);
273
+ result[agent] = !!result.agents[agent].available;
249
274
  }
250
275
 
251
276
  if (changed || !cache.generatedAt) {
@@ -266,8 +291,8 @@ function getCachedAvailableAgents({
266
291
  const result = { ok: true, cacheFile, generatedAt: cache.generatedAt || 0, agents: {} };
267
292
  for (const agent of agents) {
268
293
  const entry = cache.agents[agent];
269
- result.agents[agent] = _publicEntry(entry, envPath);
270
- result[agent] = !!entry?.available && !result.agents[agent].stale;
294
+ result.agents[agent] = _publicEntryForAgent(agent, entry, envPath);
295
+ result[agent] = !!result.agents[agent].available && !result.agents[agent].stale;
271
296
  }
272
297
  return result;
273
298
  }
@@ -55,6 +55,25 @@ const AGENT_DEFINITIONS = Object.freeze({
55
55
  // --method skips opencode's install-method detection and its interactive "Install anyways?" prompt.
56
56
  selfUpdate: { args: ['upgrade', '--method', 'curl'], label: 'opencode upgrade --method curl' },
57
57
  }),
58
+ // WP-12 (AC-12a): the DeepSeek Harness (`dsh`, package @deepseek-ai/dsh). No brew channel: homebrew
59
+ // and homebrew-cask have no formula/cask for it (brew's `dsh` is the unrelated "Dancer's shell"),
60
+ // which tests/agent-version-service.test.js records in its noSourceByDesign table.
61
+ //
62
+ // installIdentity is the same rule lib/hermetic-agent-launch-guard.js applies to the dsh entrypoint,
63
+ // and it exists for the same reason: the name `dsh` is NOT unique on this host. Path discovery finds
64
+ // homebrew's "Dancer's shell", so an install counts only when its realpath is inside the
65
+ // @deepseek-ai/dsh package; anything else reports as not installed instead of probing the wrong
66
+ // binary's `--version`. Enforced by lib/agent-version-service.js (_installIdentityHolds).
67
+ dsh: Object.freeze({
68
+ id: 'dsh',
69
+ cli: 'dsh',
70
+ name: 'DeepSeek Harness',
71
+ versionArgs: ['--version'],
72
+ latest: {
73
+ 'npm-global': { kind: 'npm', package: '@deepseek-ai/dsh' },
74
+ },
75
+ installIdentity: /(?:^|\/)@deepseek-ai\/dsh\//,
76
+ }),
58
77
  cursor: Object.freeze({
59
78
  id: 'cursor',
60
79
  cli: 'cursor-agent',
@@ -10,9 +10,7 @@
10
10
  // table header, and a copy stranded under a table is migrated by migrateCodexNotify().
11
11
 
12
12
  const fs = require('fs');
13
- const path = require('path');
14
-
15
- const HOOK_BIN_DIR = path.join(path.resolve(__dirname, '..'), 'bin');
13
+ const { hookShimPath } = require('./agent-hooks-shim');
16
14
 
17
15
  function parseTomlDottedKeySegments(value) {
18
16
  const src = String(value || '');
@@ -233,7 +231,17 @@ function stripCtmMarkerLines(text) {
233
231
  .join('\n');
234
232
  }
235
233
 
236
- const OWNS_NOTIFY = 'codex-notify.js';
234
+ // The notify marker CTM owns, and the one predicate every ownership site uses. It matches BOTH
235
+ // forms CTM has ever written: the legacy `…/codex-notify.js` script path in the array, and the
236
+ // stable-shim form's quoted array element `"codex-notify"`. A quoted-element match is deliberately
237
+ // exact, so a user notify like `["say", "codex-notify-done"]` is NOT treated as ours.
238
+ const OWNS_NOTIFY = 'codex-notify';
239
+
240
+ /** Does this TOML text (a line, or a whole file) name CTM's Codex notify? */
241
+ function isCtmNotifyText(text) {
242
+ const t = String(text || '');
243
+ return t.includes(`${OWNS_NOTIFY}.js`) || t.includes(`"${OWNS_NOTIFY}"`);
244
+ }
237
245
 
238
246
  /** Index of the line where a top-level key may be inserted: the first real table header, hoisted
239
247
  * above any comment block attached directly to it. Returns lines.length when there is no table. */
@@ -257,8 +265,10 @@ function insertTopLevelKey(text, keyLine) {
257
265
  return out.join('\n');
258
266
  }
259
267
 
260
- function codexNotifyLine(port) {
261
- return `notify = ["node", ${JSON.stringify(path.join(HOOK_BIN_DIR, OWNS_NOTIFY))}, "${port}", "stop"]`;
268
+ /** The top-level `notify` line CTM writes. It names the per-port stable shim (`~/.walle/bin/ctm-hook-<port>`),
269
+ * never a repo path, so a CTM checkout move cannot change a byte Codex hashes for hook trust. */
270
+ function codexNotifyLine(port, { shimPath = hookShimPath(port) } = {}) {
271
+ return `notify = [${JSON.stringify(shimPath)}, "codex-notify", "${port}", "stop"]`;
262
272
  }
263
273
 
264
274
  /** Codex's otlp-http exporter posts to the endpoint verbatim, so it must carry the OTLP logs path. */
@@ -284,7 +294,7 @@ function stripLegacyNotifyTable(text) {
284
294
  scan.statements.forEach((s, idx) => {
285
295
  if (!isPlainTable(s, 'notify')) return;
286
296
  const body = tableBodyStatements(scan.statements, idx);
287
- if (body.some((b) => stmtText(scan.lines, b).includes(OWNS_NOTIFY))) doomed.push(...body);
297
+ if (body.some((b) => isCtmNotifyText(stmtText(scan.lines, b)))) doomed.push(...body);
288
298
  });
289
299
  return doomed.length ? tidyTail(linesWithout(scan.lines, doomed)) : text;
290
300
  }
@@ -308,7 +318,7 @@ function codexPatchRefusal(scan) {
308
318
  for (const s of scan.statements) {
309
319
  if (s.kind === 'key' && s.table === null && s.key === 'notify') {
310
320
  if (s.end > s.start) return 'notify-multiline';
311
- if (!stmtText(scan.lines, s).includes(OWNS_NOTIFY)) return 'notify-user-value';
321
+ if (!isCtmNotifyText(stmtText(scan.lines, s))) return 'notify-user-value';
312
322
  }
313
323
  if (s.kind === 'header' && s.header.segments[0] === 'otel' && s.header.segments[1] === 'exporter') return 'otel-exporter-subtable';
314
324
  }
@@ -322,18 +332,18 @@ function codexPatchRefusal(scan) {
322
332
  */
323
333
  const workspaceHooks = require('./agent-hooks-codex-workspace')({ scanTomlStatements, tableBodyStatements, stmtText, linesWithout });
324
334
 
325
- function patchCodexConfig(text, port) {
335
+ function patchCodexConfig(text, port, { shimPath = hookShimPath(port) } = {}) {
326
336
  const input = String(text || '');
327
337
  let scan = scanTomlStatements(input);
328
338
  const refused = codexPatchRefusal(scan) || workspaceHooks.refusal(scan);
329
339
  if (refused) return { text: input, changed: false, refused };
330
340
 
331
- const notifyLine = codexNotifyLine(port);
341
+ const notifyLine = codexNotifyLine(port, { shimPath });
332
342
  let cur = stripLegacyNotifyTable(input);
333
343
  scan = scanTomlStatements(cur);
334
344
  // A CTM-written notify stranded under [otel] is scoped otel.notify and never fires.
335
345
  const stranded = scan.statements.filter((s) => s.kind === 'key' && s.key === 'notify' && s.table === 'otel'
336
- && stmtText(scan.lines, s).includes(OWNS_NOTIFY));
346
+ && isCtmNotifyText(stmtText(scan.lines, s)));
337
347
  if (stranded.length) cur = dropStatements(scan.lines, stranded).join('\n');
338
348
  cur = stripCtmMarkerLines(cur);
339
349
 
@@ -355,7 +365,8 @@ function patchCodexConfig(text, port) {
355
365
  lines.splice(header.end + 1, 0, exporterLine);
356
366
  cur = lines.join('\n');
357
367
  }
358
- const hookCommand = `node ${JSON.stringify(path.join(HOOK_BIN_DIR, 'codex-workspace-hook.js'))} ${port} PostToolUse`;
368
+ // The command names the stable per-port shim, so Codex's trusted-hook hash survives a checkout move.
369
+ const hookCommand = `${JSON.stringify(shimPath)} codex-workspace-hook ${port} PostToolUse`;
359
370
  cur = workspaceHooks.patch(cur, port, hookCommand);
360
371
  return { text: cur, changed: cur !== input };
361
372
  }
@@ -369,7 +380,7 @@ function unpatchCodexConfig(text, port) {
369
380
  let scan = scanTomlStatements(cur);
370
381
  if (!scan.balanced) return input;
371
382
  const doomed = scan.statements.filter((s) => s.kind === 'key' && s.key === 'notify' && (s.table === null || s.table === 'otel')
372
- && stmtText(scan.lines, s).includes(OWNS_NOTIFY));
383
+ && isCtmNotifyText(stmtText(scan.lines, s)));
373
384
  doomed.push(...scan.statements.filter((s) => s.table === 'otel' && isExporterKey(s) && stmtText(scan.lines, s).includes(`localhost:${port}`)));
374
385
  if (doomed.length) cur = dropStatements(scan.lines, doomed).join('\n');
375
386
  scan = scanTomlStatements(cur);
@@ -387,7 +398,8 @@ function unpatchCodexConfig(text, port) {
387
398
  }
388
399
 
389
400
  /**
390
- * Boot-prune migration: move a CTM `notify = [... codex-notify.js ...]` line stranded under a table
401
+ * Boot-prune migration: move a CTM `notify = [...]` line (legacy `codex-notify.js` path or the shim
402
+ * form's `"codex-notify"` array element) stranded under a table
391
403
  * (the live [otel] shape) to the top level. If a top-level notify already exists the stranded
392
404
  * copy is just dropped. Single-line values only (what the installer writes). Pure.
393
405
  * Returns { text, moved, removed } — `removed` lists the dropped lines for logging.
@@ -398,7 +410,7 @@ function migrateCodexNotify(text) {
398
410
  if (!scan.balanced || otelShapeRefusal(scan)) return { text: input, moved: false, removed: [] };
399
411
  const topLevelNotify = scan.statements.some((s) => s.kind === 'key' && s.table === null && s.key === 'notify');
400
412
  const doomed = scan.statements.filter((s) => s.kind === 'key' && s.key === 'notify' && s.table !== null
401
- && s.end === s.start && scan.lines[s.start].includes(OWNS_NOTIFY));
413
+ && s.end === s.start && isCtmNotifyText(scan.lines[s.start]));
402
414
  if (!doomed.length) return { text: input, moved: false, removed: [] };
403
415
  const removed = doomed.map((s) => ({ table: s.table, line: scan.lines[s.start].trim() }));
404
416
  const cleaned = dropStatements(scan.lines, doomed).join('\n');
@@ -418,7 +430,7 @@ function backupCodexConfig(filePath) {
418
430
  }
419
431
 
420
432
  module.exports = {
421
- codexExporterForm,
433
+ codexExporterForm, codexNotifyLine, isCtmNotifyText,
422
434
  parseTomlTableHeader, stripTomlSection, stripCtmMarkerLines,
423
435
  patchCodexConfig, unpatchCodexConfig, migrateCodexNotify, backupCodexConfig,
424
436
  scanTomlStatements,
@@ -18,6 +18,14 @@ module.exports = function createCodexWorkspaceHooks({ scanTomlStatements, tableB
18
18
  return null;
19
19
  }
20
20
 
21
+ // CTM-owned PostToolUse hook command for <port>, in either form CTM has ever written:
22
+ // legacy — node "<any checkout>/bin/codex-workspace-hook.js" <port> PostToolUse
23
+ // shim — "<home>/.walle/bin/ctm-hook-<port>" codex-workspace-hook <port> PostToolUse
24
+ // Ownership is the command shape (port-matched), never a filename anywhere in the text: a comment
25
+ // that merely names codex-workspace-hook.js is not an entry. Matching both forms is what makes the
26
+ // 2026-11-01 migration REPLACE the legacy group instead of duplicating it (design doc §5 D-6).
27
+ const OWNED_COMMAND = /^(?:node\s+(?:"[^"\n]*codex-workspace-hook\.js"|[^\s]*codex-workspace-hook\.js)|"[^"\n]*\/ctm-hook-\d+")\s+(?:codex-workspace-hook\s+)?(\d+)\s+PostToolUse$/;
28
+
21
29
  function remove(text, port) {
22
30
  const scan = scanTomlStatements(text);
23
31
  if (!scan.balanced) return text;
@@ -38,7 +46,7 @@ module.exports = function createCodexWorkspaceHooks({ scanTomlStatements, tableB
38
46
  const command = body.find(entry => entry.kind === 'key' && entry.key === 'command');
39
47
  const type = body.find(entry => entry.kind === 'key' && entry.key === 'type');
40
48
  const value = command && stringValue(scan.lines, command);
41
- const match = typeof value === 'string' && value.match(/^node\s+(?:"[^"\n]*codex-workspace-hook\.js"|[^\s]*codex-workspace-hook\.js)\s+(\d+)\s+PostToolUse$/);
49
+ const match = typeof value === 'string' && value.match(OWNED_COMMAND);
42
50
  if (match && Number(match[1]) === Number(port) && type && stringValue(scan.lines, type) === 'command') {
43
51
  group.owned += 1;
44
52
  doomed.push(...body);
@@ -25,7 +25,8 @@ const fs = require('fs');
25
25
  const path = require('path');
26
26
  const os = require('os');
27
27
  const { getPreset, PRESETS } = require('./agent-presets');
28
- const { codexExporterForm, patchCodexConfig, unpatchCodexConfig, stripTomlSection, backupCodexConfig } = require('./agent-hooks-codex-toml');
28
+ const { codexExporterForm, isCtmNotifyText, patchCodexConfig, unpatchCodexConfig, stripTomlSection, backupCodexConfig } = require('./agent-hooks-codex-toml');
29
+ const { hookShimPath, ensureHookShim, hookShimState, removeHookShim } = require('./agent-hooks-shim');
29
30
  const { PLUGIN_SOURCE, MANAGED_LINE } = require('./agent-hooks-opencode-plugin');
30
31
 
31
32
  const CTM_ROOT = path.resolve(__dirname, '..');
@@ -375,7 +376,8 @@ function planInstall(agent, port) {
375
376
  ...(patched.refused ? { refused: patched.refused } : {}),
376
377
  changes: [
377
378
  { description: `[otel] block → http://localhost:${port}/v1/logs (OTLP/HTTP JSON)` },
378
- { description: `notify array (top-level, above the first [table]) → bin/codex-notify.js` },
379
+ { description: `notify array (top-level, above the first [table]) → ${hookShimPath(port)} codex-notify ${port} stop` },
380
+ { description: `[[hooks.PostToolUse]] command → ${hookShimPath(port)} codex-workspace-hook ${port} PostToolUse` },
379
381
  ],
380
382
  };
381
383
  }
@@ -442,9 +444,18 @@ function applyPlan(plan) {
442
444
  return { written: true, filePath: plan.filePath, bytes: plan.nextText.length };
443
445
  }
444
446
 
445
- /** Convenience for tests / CLI: install = plan + apply. */
446
- function install(agent, port) { return applyPlan(planInstall(agent, port)); }
447
- function uninstall(agent, port) { return applyPlan(planUninstall(agent, port)); }
447
+ /** Convenience for tests / CLI: install = plan + apply. Codex writes its per-port shim first (a config must
448
+ * never name a shim CTM could not create; a refused plan writes nothing); uninstall drops the shim. */
449
+ function install(agent, port) {
450
+ const plan = planInstall(agent, port);
451
+ if (agent === 'codex' && !plan.refused) ensureHookShim({ port });
452
+ return applyPlan(plan);
453
+ }
454
+ function uninstall(agent, port) {
455
+ const result = applyPlan(planUninstall(agent, port));
456
+ if (agent === 'codex') removeHookShim({ port });
457
+ return result;
458
+ }
448
459
 
449
460
  // ─── hookStatus: structural, never a marker ───────────────────────────────
450
461
 
@@ -483,9 +494,13 @@ function hookStatus(agent, port) {
483
494
  try {
484
495
  if (agent === 'codex') {
485
496
  const { text } = readCodexConfig();
486
- // Key on our notify binary + the OTEL endpoint port (a user-owned [otel] elsewhere is no match); the path-less endpoint is stale.
487
- const form = codexExporterForm(text, port);
488
- return { state: !form || !text.includes('codex-notify.js') ? 'missing' : (form === 'current' ? 'installed' : 'outdated'), ...base };
497
+ // Plan-equality, not a shape probe (design codex-hook-stable-shim-trust-safe-self-repair-2026-11-01 D-2):
498
+ // `outdated` whenever the idempotent patcher would change a byte or the shim body drifted; a
499
+ // refusal carries `error`, which refreshOutdatedHooks never writes.
500
+ if (!isCtmNotifyText(text) || !codexExporterForm(text, port)) return { state: 'missing', ...base };
501
+ const plan = planInstall('codex', port);
502
+ const drift = plan.willWrite || hookShimState({ port }) !== 'current';
503
+ return { state: drift ? 'outdated' : 'installed', ...base, ...(plan.refused ? { error: `refused:${plan.refused}` } : {}) };
489
504
  }
490
505
  if (preset.configFormat === 'plugin') {
491
506
  const { text, managed } = readOpencodePlugin(preset);