create-walle 0.10.2 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (488) hide show
  1. package/README.md +3 -1
  2. package/lib/release-schema.json +2 -2
  3. package/lib/runtime-closure-policy.json +8 -7
  4. package/package.json +1 -1
  5. package/template/coding-task-manager/api-prompts.js +45 -7
  6. package/template/coding-task-manager/bin/claude-hook.js +55 -1
  7. package/template/coding-task-manager/bin/ctm-relay-client.js +35 -12
  8. package/template/coding-task-manager/bin/land.js +2069 -0
  9. package/template/coding-task-manager/bin/memory-census.js +4 -2
  10. package/template/coding-task-manager/bin/memory-sync-verify.js +151 -0
  11. package/template/coding-task-manager/bin/probe-refusal-scope.js +530 -0
  12. package/template/coding-task-manager/db.js +156 -32
  13. package/template/coding-task-manager/lib/acp/acp-client.js +383 -0
  14. package/template/coding-task-manager/lib/acp/dsh-context-truth.js +176 -0
  15. package/template/coding-task-manager/lib/acp/dsh-launch-model.js +302 -0
  16. package/template/coding-task-manager/lib/acp/dsh-mcp-servers.js +194 -0
  17. package/template/coding-task-manager/lib/acp/dsh-prompt-lane.js +616 -0
  18. package/template/coding-task-manager/lib/acp/dsh-route-patch.js +308 -0
  19. package/template/coding-task-manager/lib/acp/dsh-session-runtime.js +522 -0
  20. package/template/coding-task-manager/lib/acp/dsh-transcript.js +395 -0
  21. package/template/coding-task-manager/lib/acp/dsh-usage-log.js +162 -0
  22. package/template/coding-task-manager/lib/acp/ndjson-stdio-transport.js +225 -0
  23. package/template/coding-task-manager/lib/adopted-host-redial.js +47 -11
  24. package/template/coding-task-manager/lib/adopted-launch-environment.js +123 -0
  25. package/template/coding-task-manager/lib/agent-auto-update-runtime.js +0 -3
  26. package/template/coding-task-manager/lib/agent-auto-update.js +26 -10
  27. package/template/coding-task-manager/lib/agent-bundle/build.js +657 -0
  28. package/template/coding-task-manager/lib/agent-bundle/codex-install.js +558 -0
  29. package/template/coding-task-manager/lib/agent-bundle/drift.js +120 -0
  30. package/template/coding-task-manager/lib/agent-bundle/gc.js +522 -0
  31. package/template/coding-task-manager/lib/agent-bundle/inventory.js +346 -0
  32. package/template/coding-task-manager/lib/agent-bundle/push.js +717 -0
  33. package/template/coding-task-manager/lib/agent-bundle/skill-transfer.js +724 -0
  34. package/template/coding-task-manager/lib/agent-bundle/spawn.js +119 -0
  35. package/template/coding-task-manager/lib/agent-bundle/store.js +568 -0
  36. package/template/coding-task-manager/lib/agent-capabilities.js +142 -12
  37. package/template/coding-task-manager/lib/agent-cli-cache.js +30 -5
  38. package/template/coding-task-manager/lib/agent-definitions.js +19 -0
  39. package/template/coding-task-manager/lib/agent-hooks-codex-toml.js +28 -16
  40. package/template/coding-task-manager/lib/agent-hooks-codex-workspace.js +9 -1
  41. package/template/coding-task-manager/lib/agent-hooks-installer.js +23 -8
  42. package/template/coding-task-manager/lib/agent-hooks-prune.js +37 -11
  43. package/template/coding-task-manager/lib/agent-hooks-shim.js +105 -0
  44. package/template/coding-task-manager/lib/agent-presets.js +84 -1
  45. package/template/coding-task-manager/lib/agent-upgrade-jobs.js +12 -1
  46. package/template/coding-task-manager/lib/agent-version-service.js +74 -11
  47. package/template/coding-task-manager/lib/all-sessions-query.js +6 -2
  48. package/template/coding-task-manager/lib/api/agent-bundles.js +436 -0
  49. package/template/coding-task-manager/lib/api/backup-retention.js +83 -0
  50. package/template/coding-task-manager/lib/api/conversation-sync-routes.js +124 -0
  51. package/template/coding-task-manager/lib/api/diagnostics.js +549 -69
  52. package/template/coding-task-manager/lib/api/fleet.js +118 -0
  53. package/template/coding-task-manager/lib/api/hub-bridge.js +243 -0
  54. package/template/coding-task-manager/lib/api/ingress.js +9 -1
  55. package/template/coding-task-manager/lib/api/landing.js +403 -0
  56. package/template/coding-task-manager/lib/api/machines.js +691 -2
  57. package/template/coding-task-manager/lib/api/memory-sync-routes.js +335 -0
  58. package/template/coding-task-manager/lib/api/misc-ops.js +8 -3
  59. package/template/coding-task-manager/lib/api/remote-sessions.js +1326 -0
  60. package/template/coding-task-manager/lib/api/session-attach.js +623 -0
  61. package/template/coding-task-manager/lib/api/session-messages.js +1384 -0
  62. package/template/coding-task-manager/lib/api/sessions.js +77 -51
  63. package/template/coding-task-manager/lib/api/setup-network.js +544 -16
  64. package/template/coding-task-manager/lib/api/setup-wizard-handlers.js +884 -0
  65. package/template/coding-task-manager/lib/api/setup.js +829 -10
  66. package/template/coding-task-manager/lib/api/walle-bridge.js +9 -1
  67. package/template/coding-task-manager/lib/attachment-reconciler-wiring.js +5 -2
  68. package/template/coding-task-manager/lib/auth-rate-limit.js +23 -0
  69. package/template/coding-task-manager/lib/auth-rules.js +292 -21
  70. package/template/coding-task-manager/lib/background-stop.js +369 -0
  71. package/template/coding-task-manager/lib/best-of-attempts.js +4 -1
  72. package/template/coding-task-manager/lib/best-of-launch-plan.js +52 -0
  73. package/template/coding-task-manager/lib/box-keep-awake.js +89 -0
  74. package/template/coding-task-manager/lib/broadcast-reason-stats.js +152 -0
  75. package/template/coding-task-manager/lib/broadcast-serialize.js +5 -0
  76. package/template/coding-task-manager/lib/build-info.js +4 -2
  77. package/template/coding-task-manager/lib/claude-model-truth.js +13 -2
  78. package/template/coding-task-manager/lib/claude-transcript-terminal-renderer.js +17 -13
  79. package/template/coding-task-manager/lib/client-diag-handler.js +64 -2
  80. package/template/coding-task-manager/lib/codex-app-server-client.js +14 -16
  81. package/template/coding-task-manager/lib/codex-configuration-history.js +55 -0
  82. package/template/coding-task-manager/lib/codex-harness-qualification.js +43 -20
  83. package/template/coding-task-manager/lib/codex-rollout-history.js +743 -0
  84. package/template/coding-task-manager/lib/coding-agent-models.js +27 -12
  85. package/template/coding-task-manager/lib/coding-cli-skill-installer.js +44 -14
  86. package/template/coding-task-manager/lib/coding-session-model-adapters.js +43 -2
  87. package/template/coding-task-manager/lib/coding-session-model-owner.js +1 -1
  88. package/template/coding-task-manager/lib/coding-session-model-public.js +7 -1
  89. package/template/coding-task-manager/lib/coding-session-model-runtime.js +23 -4
  90. package/template/coding-task-manager/lib/coding-session-model-scan.js +31 -0
  91. package/template/coding-task-manager/lib/coding-session-model-state.js +7 -0
  92. package/template/coding-task-manager/lib/conversation-freshness-diagnostics.js +280 -0
  93. package/template/coding-task-manager/lib/conversation-item-clock.js +261 -0
  94. package/template/coding-task-manager/lib/conversation-source-registry.js +5 -0
  95. package/template/coding-task-manager/lib/conversation-store-v2.js +44 -0
  96. package/template/coding-task-manager/lib/conversation-sync.js +355 -0
  97. package/template/coding-task-manager/lib/create/resolve-launch-params.js +96 -12
  98. package/template/coding-task-manager/lib/create/worker-role-profile.js +4 -4
  99. package/template/coding-task-manager/lib/create-intent-lease.js +51 -0
  100. package/template/coding-task-manager/lib/ctm-session-context-api.js +97 -6
  101. package/template/coding-task-manager/lib/db-domain-tables.js +1 -0
  102. package/template/coding-task-manager/lib/db-domains.js +1 -0
  103. package/template/coding-task-manager/lib/db-owner-dispatch-probe.js +32 -0
  104. package/template/coding-task-manager/lib/db-owner-request-counts.js +178 -0
  105. package/template/coding-task-manager/lib/db-owner-worker-client.js +68 -14
  106. package/template/coding-task-manager/lib/db-schema-source-files.js +5 -0
  107. package/template/coding-task-manager/lib/deferred-restore-attempt.js +6 -2
  108. package/template/coding-task-manager/lib/feature-manifest.js +4 -0
  109. package/template/coding-task-manager/lib/fleet-conversation-route.js +97 -0
  110. package/template/coding-task-manager/lib/fleet-fanout.js +78 -0
  111. package/template/coding-task-manager/lib/fleet-hub.js +762 -0
  112. package/template/coding-task-manager/lib/fleet-origin-follower.js +252 -0
  113. package/template/coding-task-manager/lib/fleet-resolver-effects.js +147 -0
  114. package/template/coding-task-manager/lib/fleet-resolver-tick.js +148 -0
  115. package/template/coding-task-manager/lib/fleet-resolver.js +454 -0
  116. package/template/coding-task-manager/lib/fleet-session-list-routes.js +114 -0
  117. package/template/coding-task-manager/lib/fleet-session-lists.js +672 -0
  118. package/template/coding-task-manager/lib/fleet-update.js +798 -0
  119. package/template/coding-task-manager/lib/fs-watcher-root-watch.js +141 -0
  120. package/template/coding-task-manager/lib/fs-watcher.js +58 -42
  121. package/template/coding-task-manager/lib/harness-history-transition.js +50 -9
  122. package/template/coding-task-manager/lib/headless-term-service.js +84 -8
  123. package/template/coding-task-manager/lib/hermetic-agent-launch-guard.js +10 -0
  124. package/template/coding-task-manager/lib/history-transition-read-op.js +175 -8
  125. package/template/coding-task-manager/lib/host-cpu-categories.js +310 -0
  126. package/template/coding-task-manager/lib/host-fseventsd-sampler.js +399 -0
  127. package/template/coding-task-manager/lib/host-handoff.js +16 -19
  128. package/template/coding-task-manager/lib/host-stats.js +150 -3
  129. package/template/coding-task-manager/lib/hosted-sync-worker.js +43 -320
  130. package/template/coding-task-manager/lib/hub-bridge-access.js +798 -0
  131. package/template/coding-task-manager/lib/hub-bridge-box.js +409 -0
  132. package/template/coding-task-manager/lib/hub-bridge-mcp.js +380 -0
  133. package/template/coding-task-manager/lib/hub-bridge-mesh.js +313 -0
  134. package/template/coding-task-manager/lib/hub-bridge-ops.js +721 -0
  135. package/template/coding-task-manager/lib/hub-bridge.js +398 -0
  136. package/template/coding-task-manager/lib/idle-notify.js +491 -0
  137. package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +8 -0
  138. package/template/coding-task-manager/lib/instance-manifest.js +5 -1
  139. package/template/coding-task-manager/lib/instance-sync-status.js +18 -8
  140. package/template/coding-task-manager/lib/landing/agent-rule.txt +1 -0
  141. package/template/coding-task-manager/lib/landing/batch-runner.js +366 -0
  142. package/template/coding-task-manager/lib/landing/config.js +536 -0
  143. package/template/coding-task-manager/lib/landing/ctm-view.js +256 -0
  144. package/template/coding-task-manager/lib/landing/gate.js +360 -0
  145. package/template/coding-task-manager/lib/landing/git.js +344 -0
  146. package/template/coding-task-manager/lib/landing/lease.js +247 -0
  147. package/template/coding-task-manager/lib/landing/merge-delta.js +380 -0
  148. package/template/coding-task-manager/lib/landing/proof.js +223 -0
  149. package/template/coding-task-manager/lib/landing/queue.js +998 -0
  150. package/template/coding-task-manager/lib/landing/radar.js +489 -0
  151. package/template/coding-task-manager/lib/landing/resolve-cli.js +424 -0
  152. package/template/coding-task-manager/lib/landing/state.js +263 -0
  153. package/template/coding-task-manager/lib/landing/stop.js +49 -0
  154. package/template/coding-task-manager/lib/landing/sync-cli.js +475 -0
  155. package/template/coding-task-manager/lib/landing/sync.js +456 -0
  156. package/template/coding-task-manager/lib/landing/train.js +885 -0
  157. package/template/coding-task-manager/lib/landing/window.js +57 -0
  158. package/template/coding-task-manager/lib/launch-environment.js +121 -0
  159. package/template/coding-task-manager/lib/launch-model-override.js +36 -10
  160. package/template/coding-task-manager/lib/launch-plan-pool.js +2 -1
  161. package/template/coding-task-manager/lib/launch-plan-warm-cache.js +6 -0
  162. package/template/coding-task-manager/lib/lead-model-observer.js +57 -1
  163. package/template/coding-task-manager/lib/lead-model-options.js +160 -3
  164. package/template/coding-task-manager/lib/lead-model-restore-reconciliation.js +106 -24
  165. package/template/coding-task-manager/lib/lead-model-switch-runtime.js +352 -68
  166. package/template/coding-task-manager/lib/lead-model-switch.js +114 -10
  167. package/template/coding-task-manager/lib/lead-model-transition-runtime.js +14 -2
  168. package/template/coding-task-manager/lib/lead-model-transition-store.js +24 -6
  169. package/template/coding-task-manager/lib/lead-route-preflight.js +179 -0
  170. package/template/coding-task-manager/lib/lead-switch-history-mode.js +110 -0
  171. package/template/coding-task-manager/lib/lead-wire-agent.js +7 -0
  172. package/template/coding-task-manager/lib/lifecycle-wake-experiment.js +9 -2
  173. package/template/coding-task-manager/lib/live-defer-announce.js +45 -0
  174. package/template/coding-task-manager/lib/machine-registry.js +750 -5
  175. package/template/coding-task-manager/lib/macos-capabilities.js +32 -15
  176. package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +433 -53
  177. package/template/coding-task-manager/lib/mainloop-observability.js +14 -9
  178. package/template/coding-task-manager/lib/media-draft-owner.js +16 -3
  179. package/template/coding-task-manager/lib/memory-join.js +337 -0
  180. package/template/coding-task-manager/lib/memory-sync-verify.js +393 -0
  181. package/template/coding-task-manager/lib/memory-sync.js +565 -0
  182. package/template/coding-task-manager/lib/migrations/v57-delegate-pref-mirror-opencode.js +68 -0
  183. package/template/coding-task-manager/lib/migrations/v58-subagent-launch-native-subagents.js +42 -0
  184. package/template/coding-task-manager/lib/migrations/v59-coding-session-model-state-dsh.js +73 -0
  185. package/template/coding-task-manager/lib/migrations/v60-delegate-pref-mirror-dsh.js +78 -0
  186. package/template/coding-task-manager/lib/migrations/v61-conversation-item-clock.js +71 -0
  187. package/template/coding-task-manager/lib/model-availability.js +727 -0
  188. package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +4 -6
  189. package/template/coding-task-manager/lib/model-host-receipt.js +6 -3
  190. package/template/coding-task-manager/lib/model-id-rule.js +19 -0
  191. package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
  192. package/template/coding-task-manager/lib/model-proxy/auto-mode-review.js +110 -23
  193. package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
  194. package/template/coding-task-manager/lib/model-proxy/generation-probe.js +60 -17
  195. package/template/coding-task-manager/lib/model-proxy/historical-tool-declarations.js +37 -0
  196. package/template/coding-task-manager/lib/model-proxy/lead-request-validation.js +1 -0
  197. package/template/coding-task-manager/lib/model-proxy/lead-token-admin.js +130 -0
  198. package/template/coding-task-manager/lib/model-proxy/refusal-scope.js +151 -0
  199. package/template/coding-task-manager/lib/model-proxy/request-budget-store.js +11 -2
  200. package/template/coding-task-manager/lib/model-proxy/tokens.js +69 -5
  201. package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
  202. package/template/coding-task-manager/lib/native-agent-model-args.js +25 -22
  203. package/template/coding-task-manager/lib/native-history-control.js +6 -4
  204. package/template/coding-task-manager/lib/native-lead-settings-client.js +44 -4
  205. package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +173 -36
  206. package/template/coding-task-manager/lib/online-backup-worker-client.js +2 -0
  207. package/template/coding-task-manager/lib/opencode-cli.js +480 -0
  208. package/template/coding-task-manager/lib/opencode-launch-model.js +471 -0
  209. package/template/coding-task-manager/lib/opencode-v1-plugin/model-carrier.mjs +35 -0
  210. package/template/coding-task-manager/lib/opencode-v2-plugin/server.js +118 -0
  211. package/template/coding-task-manager/lib/owner-action-manifest-data.json +1 -1
  212. package/template/coding-task-manager/lib/park-composer-draft.js +54 -0
  213. package/template/coding-task-manager/lib/park-fresh-relaunch.js +41 -10
  214. package/template/coding-task-manager/lib/perf-phase-trace.js +72 -0
  215. package/template/coding-task-manager/lib/perf-summary.js +7 -0
  216. package/template/coding-task-manager/lib/perf-tracker.js +63 -2
  217. package/template/coding-task-manager/lib/project-config.js +7 -0
  218. package/template/coding-task-manager/lib/prompt-purge-v2.js +52 -1
  219. package/template/coding-task-manager/lib/provider-login-broker.js +72 -15
  220. package/template/coding-task-manager/lib/provider-login-tab.js +10 -2
  221. package/template/coding-task-manager/lib/read-pool-client.js +89 -2
  222. package/template/coding-task-manager/lib/read-pool-worker-handle.js +29 -4
  223. package/template/coding-task-manager/lib/recent-catalog-query.js +15 -0
  224. package/template/coding-task-manager/lib/recent-session-catalog-response.js +67 -10
  225. package/template/coding-task-manager/lib/recovery-evidence.js +26 -11
  226. package/template/coding-task-manager/lib/relay-client.js +57 -3
  227. package/template/coding-task-manager/lib/relink-audit.js +34 -5
  228. package/template/coding-task-manager/lib/remote-machine-catalog.js +321 -26
  229. package/template/coding-task-manager/lib/remote-machine-create.js +576 -36
  230. package/template/coding-task-manager/lib/remote-machine-link.js +546 -59
  231. package/template/coding-task-manager/lib/remote-machine-proxy.js +143 -9
  232. package/template/coding-task-manager/lib/remote-session-forward.js +65 -7
  233. package/template/coding-task-manager/lib/remote-terminal-target.js +9 -2
  234. package/template/coding-task-manager/lib/restart-guard.js +18 -6
  235. package/template/coding-task-manager/lib/restore-seed-modes.js +87 -0
  236. package/template/coding-task-manager/lib/restored-scrollback-hydration-runtime.js +22 -2
  237. package/template/coding-task-manager/lib/resume-admission.js +17 -3
  238. package/template/coding-task-manager/lib/review-repo-registry.js +58 -19
  239. package/template/coding-task-manager/lib/root-delivery-runtime.js +2 -2
  240. package/template/coding-task-manager/lib/root-prompt-flow.js +1 -1
  241. package/template/coding-task-manager/lib/runtime-contract.js +3 -1
  242. package/template/coding-task-manager/lib/schema-indexes.js +1 -0
  243. package/template/coding-task-manager/lib/session-context-locator.js +742 -0
  244. package/template/coding-task-manager/lib/session-create/attachment.js +2 -2
  245. package/template/coding-task-manager/lib/session-create/create-phases.js +118 -0
  246. package/template/coding-task-manager/lib/session-create/delegate-worker.js +86 -16
  247. package/template/coding-task-manager/lib/session-create/dsh-session.js +930 -0
  248. package/template/coding-task-manager/lib/session-create/lead-route-launch.js +186 -13
  249. package/template/coding-task-manager/lib/session-create/native-lead-lifetime.js +55 -3
  250. package/template/coding-task-manager/lib/session-create/opencode-lead-provider.js +50 -0
  251. package/template/coding-task-manager/lib/session-create/prepared.js +162 -38
  252. package/template/coding-task-manager/lib/session-create/provider-process.js +160 -28
  253. package/template/coding-task-manager/lib/session-create/response.js +7 -7
  254. package/template/coding-task-manager/lib/session-create/structured-session.js +27 -0
  255. package/template/coding-task-manager/lib/session-create/subagent-pick-table.js +3 -1
  256. package/template/coding-task-manager/lib/session-create/subagent-pick.js +458 -9
  257. package/template/coding-task-manager/lib/session-create/transcript.js +1 -1
  258. package/template/coding-task-manager/lib/session-create/worker-budget-setup.js +21 -2
  259. package/template/coding-task-manager/lib/session-create/worker-continuation.js +7 -1
  260. package/template/coding-task-manager/lib/session-create/worker-launch-sandbox.js +39 -10
  261. package/template/coding-task-manager/lib/session-create/worker-observability.js +2 -2
  262. package/template/coding-task-manager/lib/session-empty.js +13 -10
  263. package/template/coding-task-manager/lib/session-host-manager.js +60 -0
  264. package/template/coding-task-manager/lib/session-jobs.js +3 -1
  265. package/template/coding-task-manager/lib/session-lifecycle.js +10 -4
  266. package/template/coding-task-manager/lib/session-model-action-preconditions.js +6 -2
  267. package/template/coding-task-manager/lib/session-model-intent-contract.js +11 -8
  268. package/template/coding-task-manager/lib/session-park.js +738 -0
  269. package/template/coding-task-manager/lib/session-path-grant.js +13 -4
  270. package/template/coding-task-manager/lib/session-status-ledger.js +106 -4
  271. package/template/coding-task-manager/lib/session-transport.js +17 -0
  272. package/template/coding-task-manager/lib/session-workspace-projection.js +54 -5
  273. package/template/coding-task-manager/lib/session-workspace-tracking-runtime.js +213 -1
  274. package/template/coding-task-manager/lib/skill-autocomplete-query.js +120 -3
  275. package/template/coding-task-manager/lib/skill-autocomplete.js +15 -1
  276. package/template/coding-task-manager/lib/sqlite-file-set.js +39 -0
  277. package/template/coding-task-manager/lib/standup-search-adapter.js +16 -2
  278. package/template/coding-task-manager/lib/state-sync/client-ledger.js +12 -0
  279. package/template/coding-task-manager/lib/state-sync/frame-emitter.js +319 -100
  280. package/template/coding-task-manager/lib/storage/coding-session-model-store.js +4 -1
  281. package/template/coding-task-manager/lib/storage/delegate-pref-mirror-store.js +32 -9
  282. package/template/coding-task-manager/lib/structured-session-boot-sweep.js +235 -0
  283. package/template/coding-task-manager/lib/submission-ack.js +90 -2
  284. package/template/coding-task-manager/lib/systemd-scope.js +115 -0
  285. package/template/coding-task-manager/lib/telemetry-receiver.js +39 -0
  286. package/template/coding-task-manager/lib/terminal-attach-geometry.js +985 -0
  287. package/template/coding-task-manager/lib/terminal-bootstrap.js +30 -0
  288. package/template/coding-task-manager/lib/terminal-draft-operation.js +357 -0
  289. package/template/coding-task-manager/lib/terminal-input-admission.js +17 -1
  290. package/template/coding-task-manager/lib/terminal-input-prepared.js +563 -0
  291. package/template/coding-task-manager/lib/transcript-cache-temps.js +171 -0
  292. package/template/coding-task-manager/lib/transcript-frame-cache.js +8 -3
  293. package/template/coding-task-manager/lib/transcript-source.js +36 -4
  294. package/template/coding-task-manager/lib/transcript-store.js +9 -0
  295. package/template/coding-task-manager/lib/typing-interaction-trace.js +106 -3
  296. package/template/coding-task-manager/lib/usage-ledger/backfill.js +40 -5
  297. package/template/coding-task-manager/lib/usage-ledger/client.js +4 -1
  298. package/template/coding-task-manager/lib/usage-ledger/dsh-parse.js +152 -0
  299. package/template/coding-task-manager/lib/usage-ledger/dsh-read.js +73 -0
  300. package/template/coding-task-manager/lib/usage-ledger/ingest.js +34 -6
  301. package/template/coding-task-manager/lib/usage-ledger/live.js +35 -8
  302. package/template/coding-task-manager/lib/usage-ledger/read.js +29 -20
  303. package/template/coding-task-manager/lib/usage-ledger/schema.js +61 -1
  304. package/template/coding-task-manager/lib/usage-ledger/task-coordinator.js +4 -2
  305. package/template/coding-task-manager/lib/wake-skip-reasons.js +1 -0
  306. package/template/coding-task-manager/lib/walle/turn.js +1 -1
  307. package/template/coding-task-manager/lib/walle-history-serve.js +838 -0
  308. package/template/coding-task-manager/lib/walle-kill-authority.js +2 -2
  309. package/template/coding-task-manager/lib/walle-log-rotation.js +164 -1
  310. package/template/coding-task-manager/lib/walle-mcp-auto-config.js +56 -2
  311. package/template/coding-task-manager/lib/walle-row-history.js +495 -0
  312. package/template/coding-task-manager/lib/walle-supervisor.js +33 -14
  313. package/template/coding-task-manager/lib/watch-worker-client.js +203 -0
  314. package/template/coding-task-manager/lib/worker-terminal-output.js +5 -2
  315. package/template/coding-task-manager/lib/ws/hub-bridge.js +35 -0
  316. package/template/coding-task-manager/lib/ws/lifecycle.js +6 -11
  317. package/template/coding-task-manager/lib/ws/misc.js +1 -1
  318. package/template/coding-task-manager/lib/ws/state-sync.js +106 -4
  319. package/template/coding-task-manager/lib/ws/terminal-attach.js +91 -0
  320. package/template/coding-task-manager/oauth-proxy.js +112 -115
  321. package/template/coding-task-manager/package-lock.json +10 -94
  322. package/template/coding-task-manager/package.json +4 -3
  323. package/template/coding-task-manager/protocol/message-table.json +10 -0
  324. package/template/coding-task-manager/protocol/messages.js +10 -0
  325. package/template/coding-task-manager/protocol/ws-schema.json +20 -0
  326. package/template/coding-task-manager/protocol/ws-schema.md +2 -0
  327. package/template/coding-task-manager/public/css/app.css +170 -0
  328. package/template/coding-task-manager/public/css/walle-session.css +4 -0
  329. package/template/coding-task-manager/public/index.html +473 -243
  330. package/template/coding-task-manager/public/js/agent-command.js +86 -0
  331. package/template/coding-task-manager/public/js/app/backups-panel.js +111 -5
  332. package/template/coding-task-manager/public/js/app/coding-agent-upgrade.js +103 -4
  333. package/template/coding-task-manager/public/js/app/machines-page.js +1328 -8
  334. package/template/coding-task-manager/public/js/app/models-tab.js +113 -38
  335. package/template/coding-task-manager/public/js/app/restart-banner.js +53 -0
  336. package/template/coding-task-manager/public/js/app/worktrees-panel.js +184 -2
  337. package/template/coding-task-manager/public/js/background-work.js +196 -27
  338. package/template/coding-task-manager/public/js/coding-session-model-presentation.js +138 -10
  339. package/template/coding-task-manager/public/js/core/active-session-groups.js +179 -0
  340. package/template/coding-task-manager/public/js/core/pending-create.js +238 -0
  341. package/template/coding-task-manager/public/js/core/recent-machine-source.js +228 -0
  342. package/template/coding-task-manager/public/js/core/session-commands.js +180 -14
  343. package/template/coding-task-manager/public/js/dashboard-client-function-order.json +8 -0
  344. package/template/coding-task-manager/public/js/document-review-links.js +9 -1
  345. package/template/coding-task-manager/public/js/file-resolution-client.js +52 -1
  346. package/template/coding-task-manager/public/js/history-app-seam.js +22 -2
  347. package/template/coding-task-manager/public/js/machine-pool-shared.js +558 -3
  348. package/template/coding-task-manager/public/js/message-renderer.js +4 -3
  349. package/template/coding-task-manager/public/js/model-picker.js +3 -1
  350. package/template/coding-task-manager/public/js/native-subagent-review.js +15 -2
  351. package/template/coding-task-manager/public/js/new-session-agent-grid.js +346 -0
  352. package/template/coding-task-manager/public/js/pasted-content.js +236 -0
  353. package/template/coding-task-manager/public/js/pe-private-content-lifetime.js +39 -7
  354. package/template/coding-task-manager/public/js/private-model-lifetime.js +6 -2
  355. package/template/coding-task-manager/public/js/prompts.js +11 -10
  356. package/template/coding-task-manager/public/js/protocol.js +10 -0
  357. package/template/coding-task-manager/public/js/restart-banner-policy.js +29 -1
  358. package/template/coding-task-manager/public/js/session-model-picker.js +447 -57
  359. package/template/coding-task-manager/public/js/session-search-utils.js +18 -2
  360. package/template/coding-task-manager/public/js/session-usage-view.js +2 -1
  361. package/template/coding-task-manager/public/js/session-workspace-tracking.js +51 -1
  362. package/template/coding-task-manager/public/js/state-sync-client.js +10 -1
  363. package/template/coding-task-manager/public/js/stream-view.js +6 -5
  364. package/template/coding-task-manager/public/js/terminal/conv-only-session.js +287 -0
  365. package/template/coding-task-manager/public/js/terminal/runtime-09.js +2 -0
  366. package/template/coding-task-manager/public/js/terminal/runtime-10.js +5 -5
  367. package/template/coding-task-manager/public/js/terminal/runtime-12.js +8 -1
  368. package/template/coding-task-manager/public/js/terminal/runtime-16.js +4 -0
  369. package/template/coding-task-manager/public/js/terminal/runtime-20.js +1 -1
  370. package/template/coding-task-manager/public/js/terminal-attachment-draft.js +67 -13
  371. package/template/coding-task-manager/public/js/terminal-image-orchestration.js +52 -6
  372. package/template/coding-task-manager/public/js/terminal-wheel.js +23 -3
  373. package/template/coding-task-manager/public/js/walle/chat-conversation.js +4 -4
  374. package/template/coding-task-manager/public/js/walle.js +6 -0
  375. package/template/coding-task-manager/public/js/ws/live-dispatch.js +10 -0
  376. package/template/coding-task-manager/public/m/app.css +140 -0
  377. package/template/coding-task-manager/public/m/app.js +507 -68
  378. package/template/coding-task-manager/public/m/index.html +17 -0
  379. package/template/coding-task-manager/public/m/machines.js +838 -4
  380. package/template/coding-task-manager/public/m/private-api.js +31 -1
  381. package/template/coding-task-manager/public/usage.html +1 -0
  382. package/template/coding-task-manager/scripts/dev-route-fixture/synthetic-route-upstream.js +104 -0
  383. package/template/coding-task-manager/scripts/dev-route-fixture.js +339 -0
  384. package/template/coding-task-manager/scripts/opencode-lead-route-probe.js +195 -0
  385. package/template/coding-task-manager/scripts/opencode-v2-contract-probe/fake-anthropic.js +168 -0
  386. package/template/coding-task-manager/scripts/opencode-v2-contract-probe/probe-plugin/server.js +95 -0
  387. package/template/coding-task-manager/scripts/opencode-v2-contract-probe.js +673 -0
  388. package/template/coding-task-manager/server.js +1950 -11679
  389. package/template/coding-task-manager/session-integrity.js +64 -4
  390. package/template/coding-task-manager/workers/db-owner-worker.js +19 -2
  391. package/template/coding-task-manager/workers/online-backup-worker.js +1 -0
  392. package/template/coding-task-manager/workers/read-pool-model-collector.js +51 -3
  393. package/template/coding-task-manager/workers/read-pool-worker.js +76 -6
  394. package/template/coding-task-manager/workers/session-host-pool-process.js +6 -6
  395. package/template/coding-task-manager/workers/session-host-process.js +5 -5
  396. package/template/coding-task-manager/workers/watch-worker.js +105 -0
  397. package/template/coding-task-manager/workers/worktree-worker.js +168 -33
  398. package/template/ctm-relay/lib/protocol.js +14 -1
  399. package/template/package-lock.json +2 -2
  400. package/template/package.json +1 -1
  401. package/template/shared/backup-retention.js +82 -0
  402. package/template/shared/conversation-admission.js +2 -0
  403. package/template/shared/conversation-provenance.js +3 -7
  404. package/template/shared/conversation-storage-v2-contract.js +5 -0
  405. package/template/shared/linked-worktree.js +51 -0
  406. package/template/shared/macos-fda-target.js +57 -0
  407. package/template/shared/orphan-parents.js +55 -0
  408. package/template/shared/release-schema.json +2 -2
  409. package/template/shared/storage/contract.js +3 -0
  410. package/template/wall-e/adapters/imessage.js +12 -0
  411. package/template/wall-e/agent.js +71 -4
  412. package/template/wall-e/api-walle.js +21 -1
  413. package/template/wall-e/bin/train-gemma-e4b-tooluse.js +2 -2
  414. package/template/wall-e/bin/walle-mcp-stdio.js +5 -5
  415. package/template/wall-e/brain/brain-join.js +859 -0
  416. package/template/wall-e/brain/brain-sync-membership.js +20 -0
  417. package/template/wall-e/brain/brain-sync-store.js +131 -77
  418. package/template/wall-e/brain/brain-sync-v2-schema.js +203 -0
  419. package/template/wall-e/brain/brain-sync-v2.js +578 -0
  420. package/template/wall-e/brain/delegate-model-prefs-store.js +13 -4
  421. package/template/wall-e/brain/deterministic-id.js +51 -0
  422. package/template/wall-e/brain/memories.js +2 -2
  423. package/template/wall-e/brain/migrations.js +40 -6
  424. package/template/wall-e/brain/model-identity-migration.js +1 -1
  425. package/template/wall-e/brain/owner-call-whitelist.js +23 -7
  426. package/template/wall-e/brain/schema.js +45 -85
  427. package/template/wall-e/brain.js +171 -35
  428. package/template/wall-e/chat.js +3 -3
  429. package/template/wall-e/coding/agent-shell-env.js +7 -2
  430. package/template/wall-e/coding/prompt-bundle.js +66 -21
  431. package/template/wall-e/config/main-loop-exemptions.json +2 -2
  432. package/template/wall-e/http/admin-api.js +37 -0
  433. package/template/wall-e/http/gws-api.js +4 -0
  434. package/template/wall-e/http/helper-rules-api.js +7 -1
  435. package/template/wall-e/http/model-admin.js +8 -1
  436. package/template/wall-e/http/model-delegate-pref-admin.js +3 -3
  437. package/template/wall-e/http/model-proxy-providers.js +174 -1
  438. package/template/wall-e/http/model-route-health.js +157 -0
  439. package/template/wall-e/http/sync-api.js +278 -464
  440. package/template/wall-e/jobs/job-runner.js +28 -1
  441. package/template/wall-e/lib/brain-owner-op-priority.js +9 -1
  442. package/template/wall-e/lib/ctm-data-dir.js +26 -0
  443. package/template/wall-e/lib/derivation-home.js +147 -0
  444. package/template/wall-e/lib/helper-rules/evaluate.js +50 -9
  445. package/template/wall-e/lib/helper-rules/grade.js +51 -8
  446. package/template/wall-e/lib/helper-rules/lead-call-hook.js +38 -5
  447. package/template/wall-e/lib/helper-rules/store.js +4 -2
  448. package/template/wall-e/lib/landing-mcp-tools.js +170 -0
  449. package/template/wall-e/lib/mcp-client-config.js +133 -14
  450. package/template/wall-e/lib/mcp-gateway-migrate.js +28 -64
  451. package/template/wall-e/lib/mcp-integration.js +58 -13
  452. package/template/wall-e/lib/parent-death.js +7 -1
  453. package/template/wall-e/lib/scheduler-worker-jobs.js +6 -4
  454. package/template/wall-e/llm/delegate-lead-prefs.js +16 -1
  455. package/template/wall-e/llm/delegate-vendor-default.js +12 -5
  456. package/template/wall-e/llm/model-roles.js +1 -1
  457. package/template/wall-e/llm/native-delegate-selection.js +56 -8
  458. package/template/wall-e/llm/worker-delegate-args.js +72 -7
  459. package/template/wall-e/llm/worker-mcp-routing.js +66 -17
  460. package/template/wall-e/loops/brain-custom-writes.js +19 -0
  461. package/template/wall-e/loops/ingest.js +17 -1
  462. package/template/wall-e/loops/think.js +19 -4
  463. package/template/wall-e/mcp-contract.js +222 -6
  464. package/template/wall-e/mcp-protocol-contract.js +40 -5
  465. package/template/wall-e/mcp-server.js +128 -29
  466. package/template/wall-e/mcp-worker-schemas.js +5 -5
  467. package/template/wall-e/memory/ctm-context-client.js +85 -0
  468. package/template/wall-e/memory/ctm-session-context.js +9 -3
  469. package/template/wall-e/memory/hub-bridge-client.js +388 -0
  470. package/template/wall-e/memory/replica-origin.js +56 -0
  471. package/template/wall-e/memory/session-pool.js +135 -0
  472. package/template/wall-e/runtime/live-capabilities.js +8 -3
  473. package/template/wall-e/server.js +4 -2
  474. package/template/wall-e/shared/release-schema.json +2 -2
  475. package/template/wall-e/shared/sqlite-file-set.js +39 -0
  476. package/template/wall-e/shared/storage/contract.js +3 -0
  477. package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
  478. package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +7 -1
  479. package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +4 -4
  480. package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +6 -6
  481. package/template/wall-e/skills/_templates/fleet-sync/VERSION +1 -0
  482. package/template/wall-e/skills/_templates/fleet-sync/released-sha256.json +16 -0
  483. package/template/wall-e/skills/_templates/fleet-sync/templates/claude/SKILL.md +64 -0
  484. package/template/wall-e/skills/_templates/fleet-sync/templates/codex/SKILL.md +64 -0
  485. package/template/wall-e/tools/chrome-automation.js +6 -0
  486. package/template/wall-e/training/setup-cuda-train-env.sh +2 -2
  487. package/template/wall-e/workers/brain-owner-worker.js +18 -4
  488. package/template/wall-e/workers/mcp-gateway-process.js +5 -5
@@ -0,0 +1,673 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // WP-0 contract probe for OpenCode 2.x
5
+ // (coding-task-manager/docs/opencode-v2-model-switch-standalone-plugin-2026-10-30.html §3.2, §6.3, §8 WP-0).
6
+ //
7
+ // Proves, on a real 2.x binary, the five vendor facts the 2.x model-carrier design depends on:
8
+ // P-S1 a CTM-chosen session id is honoured and the inline config model drives the first turn
9
+ // P-S3a a resumed session keeps its own model: the inline config does NOT switch it
10
+ // P-S3b a `plugins` entry whose setup calls ctx.session.switchModel() DOES switch it
11
+ // P-S4 the shared `service` ignores the tab's inline config (0 requests reach the tab listener)
12
+ // P-S6 a fresh ses_<32 hex> id is accepted and persisted in session_v2
13
+ //
14
+ // WP-4 adds three more (docs/opencode-followups-agent-word-v2-waiting-dev-route-2026-10-31.html §6.4):
15
+ // P-S9 a tool whose permission is "ask" emits permission.asked then permission.replied (OQ-2)
16
+ // P-S10 the planted 1.x ~/.config/opencode/plugins/ctm-status.js: load error logged, turn done (OQ-3)
17
+ // P-S11 a subagent child's permission ask: child id or parent id? (OQ-5; never fails the run)
18
+ //
19
+ // Isolation: every child runs with XDG_{CONFIG,DATA,STATE,CACHE}_HOME under a fresh mkdtemp root and
20
+ // HOME pointing there too, so the user's ~/.config/opencode, ~/.local/share/opencode and
21
+ // ~/.local/state/opencode are never read or written; credential/endpoint env vars are stripped, so
22
+ // the probe cannot authenticate anywhere; the temp root is removed at the end unless --keep.
23
+ // Process hygiene: the TUI runs under a pty via `script -q /dev/null <bin> ...`; only pids this
24
+ // script started (the `script` pid and its `pgrep -P` descendants) are ever killed -- never pkill by
25
+ // pattern. Each probe has a 90 s deadline.
26
+ //
27
+ // node scripts/opencode-v2-contract-probe.js --opencode <2.x bin> [--json] [--keep] [--verbose]
28
+ // -> one JSON line per probe: {"id","expected","observed","pass"}; exit 0 iff every probe passed,
29
+ // 1 when a probe failed, 2 when --opencode is missing or its version line is not v2.x.
30
+
31
+ const fs = require('node:fs');
32
+ const os = require('node:os');
33
+ const path = require('node:path');
34
+ const net = require('node:net');
35
+ const crypto = require('node:crypto');
36
+ const { spawn, execFileSync } = require('node:child_process');
37
+
38
+ const { startFakeAnthropic, requestsForPrompt, lastModelForPrompt } = require('./opencode-v2-contract-probe/fake-anthropic');
39
+
40
+ const PROBE_PLUGIN_DIR = path.join(__dirname, 'opencode-v2-contract-probe', 'probe-plugin');
41
+ const SCRIPT_BIN = fs.existsSync('/usr/bin/script') ? '/usr/bin/script' : 'script';
42
+ const PGREP_BIN = fs.existsSync('/usr/bin/pgrep') ? '/usr/bin/pgrep' : 'pgrep';
43
+ const LSOF_BIN = fs.existsSync('/usr/sbin/lsof') ? '/usr/sbin/lsof' : 'lsof';
44
+ const PROBE_TIMEOUT_MS = 90000; // per probe, per the design (§8 WP-0)
45
+ const NEGATIVE_WAIT_MS = 20000; // how long a probe that expects ZERO requests observes
46
+ const EXPECTED_PROBES = 8; // P-S1, P-S3a, P-S3b, P-S4, P-S6, P-S9, P-S10, P-S11
47
+
48
+ // Never let the probe reach anything authenticated: the scratch runs inherited whatever the shell
49
+ // had, but a probe must not be able to spend the user's credentials or read their real config.
50
+ const STRIPPED_ENV = [
51
+ /^ANTHROPIC_/, /^OPENAI_/, /^GEMINI_/, /^GOOGLE_/, /^MOONSHOT/, /^ZAI_/, /^Z_AI/, /^OPENROUTER_/,
52
+ /^DEEPSEEK_/, /^FIREWORKS_/, /^PORTKEY_/, /^AWS_/, /^AZURE_/, /^COPILOT_/, /^CTM_LEAD_TOKEN$/,
53
+ /^OPENCODE_CONFIG$/, /^OPENCODE_CONFIG_CONTENT$/, /^OPENCODE_CONFIG_DIR$/,
54
+ ];
55
+
56
+ const state = { verbose: false, tempRoot: null, entries: null, listener: null, results: [], startedPids: new Set(), opencodeBin: null, only: null };
57
+
58
+ // ---- process tree -------------------------------------------------------------------------------
59
+
60
+ function sleep(ms) {
61
+ return new Promise((resolve) => { setTimeout(resolve, ms); });
62
+ }
63
+
64
+ function sleepSync(ms) {
65
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
66
+ }
67
+
68
+ function childPidsOf(pid) {
69
+ try {
70
+ return execFileSync(PGREP_BIN, ['-P', String(pid)], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
71
+ .split('\n')
72
+ .map((line) => parseInt(line.trim(), 10))
73
+ .filter((value) => Number.isInteger(value) && value > 0);
74
+ } catch { return []; /* expected: pgrep exits 1 when the pid has no children */ }
75
+ }
76
+
77
+ // Deepest descendants first, the root last: a child is signalled before its parent can re-parent it.
78
+ function treePostOrder(rootPid) {
79
+ const order = [];
80
+ const walk = (pid) => {
81
+ for (const child of childPidsOf(pid)) walk(child);
82
+ order.push(pid);
83
+ };
84
+ walk(rootPid);
85
+ return order;
86
+ }
87
+
88
+ function killTree(rootPid) {
89
+ const pids = treePostOrder(rootPid);
90
+ for (const pid of pids) { try { process.kill(pid, 'SIGTERM'); } catch { /* expected: pid already exited */ } }
91
+ if (pids.length > 1) sleepSync(500);
92
+ for (const pid of pids) { try { process.kill(pid, 'SIGKILL'); } catch { /* expected: pid already exited */ } }
93
+ return pids;
94
+ }
95
+
96
+ function waitExit(child, ms) {
97
+ if (child.exitCode !== null || child.signalCode !== null) return Promise.resolve();
98
+ return new Promise((resolve) => {
99
+ const timer = setTimeout(resolve, ms);
100
+ child.once('exit', () => { clearTimeout(timer); resolve(); });
101
+ });
102
+ }
103
+
104
+ function portIsListening(port) {
105
+ return new Promise((resolve) => {
106
+ const socket = net.connect({ host: '127.0.0.1', port });
107
+ const done = (listening) => { socket.destroy(); resolve(listening); };
108
+ socket.once('connect', () => done(true));
109
+ socket.once('error', () => done(false));
110
+ socket.setTimeout(1000, () => done(false));
111
+ });
112
+ }
113
+
114
+ function listenerPidsOnPort(port) {
115
+ try {
116
+ return execFileSync(LSOF_BIN, ['-nP', '-iTCP:' + port, '-sTCP:LISTEN', '-t'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
117
+ .split('\n')
118
+ .map((line) => parseInt(line.trim(), 10))
119
+ .filter((value) => Number.isInteger(value) && value > 0);
120
+ } catch { return []; /* expected: lsof exits 1 when nothing listens on the port */ }
121
+ }
122
+
123
+ // ---- probe scaffolding --------------------------------------------------------------------------
124
+
125
+ function parseArgs(argv) {
126
+ const args = { opencode: null, json: false, keep: false, verbose: false, only: null, help: false };
127
+ for (let i = 0; i < argv.length; i += 1) {
128
+ const arg = argv[i];
129
+ if (arg === '--opencode') { args.opencode = argv[i + 1] || null; i += 1; } else if (arg === '--json') { args.json = true; } else if (arg === '--keep') { args.keep = true; } else if (arg === '--verbose') { args.verbose = true; } else if (arg === '--only') { args.only = (argv[i + 1] || '').split(',').map((id) => id.trim()).filter(Boolean); i += 1; } else if (arg === '--help' || arg === '-h') { args.help = true; }
130
+ }
131
+ return args;
132
+ }
133
+
134
+ // `--only P-S9,P-S11` runs a subset while iterating on a probe; unset means the whole suite.
135
+ function wanted(id) {
136
+ return !state.only || state.only.includes(id);
137
+ }
138
+
139
+ function note(message, verboseOnly = false) {
140
+ if (verboseOnly && !state.verbose) return;
141
+ process.stderr.write('[probe] ' + message + '\n');
142
+ }
143
+
144
+ function record(id, expected, observed, pass) {
145
+ const row = { id, expected, observed, pass: !!pass };
146
+ state.results.push(row);
147
+ process.stdout.write(JSON.stringify(row) + '\n');
148
+ return row;
149
+ }
150
+
151
+ function filteredEnv() {
152
+ const env = {};
153
+ for (const [key, value] of Object.entries(process.env)) {
154
+ if (value === undefined) continue;
155
+ if (STRIPPED_ENV.some((pattern) => pattern.test(key))) continue;
156
+ env[key] = value;
157
+ }
158
+ return env;
159
+ }
160
+
161
+ function xdgEnv(extra, root = state.tempRoot) {
162
+ const env = filteredEnv();
163
+ Object.assign(env, {
164
+ HOME: root,
165
+ XDG_CONFIG_HOME: path.join(root, 'config'),
166
+ XDG_DATA_HOME: path.join(root, 'data'),
167
+ XDG_STATE_HOME: path.join(root, 'state'),
168
+ XDG_CACHE_HOME: path.join(root, 'cache'),
169
+ OPENCODE_DISABLE_AUTOUPDATE: '1',
170
+ OPENCODE_DISABLE_TELEMETRY: '1',
171
+ OQ2_LOG: path.join(root, 'probe-plugin.log'),
172
+ });
173
+ return Object.assign(env, extra || {});
174
+ }
175
+
176
+ function inlineConfig({ model, port, plugins, permissions }) {
177
+ const config = {
178
+ model: 'probe/' + model,
179
+ provider: {
180
+ probe: {
181
+ npm: '@ai-sdk/anthropic',
182
+ options: { baseURL: 'http://127.0.0.1:' + port + '/v1', apiKey: 'x' },
183
+ models: { m1: { name: 'm1' }, m2: { name: 'm2' }, m3: { name: 'm3' } },
184
+ },
185
+ },
186
+ };
187
+ if (plugins) config.plugins = plugins;
188
+ // 2.2.4 config key (verified in the binary's Config.Info schema, not assumed): top-level
189
+ // `permissions` is an ordered ruleset of { action, resource, effect } appended to every agent's
190
+ // own rules; evaluation takes the LAST matching rule. The shell tool's permission action is
191
+ // `shell` (Agent.Info defaults use `read`/`shell`/`edit`/`webfetch`/...), so an `ask` rule for
192
+ // `shell`/`*` outranks the default `{ action: '*', resource: '*', effect: 'allow' }`.
193
+ if (permissions) config.permissions = permissions;
194
+ return JSON.stringify(config);
195
+ }
196
+
197
+ // The one permission rule P-S9/P-S11 need: the shell tool asks before it runs.
198
+ const ASK_SHELL_PERMISSIONS = [{ action: 'shell', resource: '*', effect: 'ask' }];
199
+
200
+ function pluginLog() {
201
+ const file = path.join(state.tempRoot, 'probe-plugin.log');
202
+ try {
203
+ return fs.readFileSync(file, 'utf8').split('\n').filter(Boolean).map((line) => {
204
+ try { return JSON.parse(line); } catch { return { ev: 'unparsed', raw: line.slice(0, 200) }; }
205
+ });
206
+ } catch { return []; /* expected: no plugin log when no plugin entry ran */ }
207
+ }
208
+
209
+ // ---- opencode invocations -----------------------------------------------------------------------
210
+
211
+ // The version preflight runs under its own throwaway XDG/HOME root too: opencode's logger appends
212
+ // <XDG_DATA_HOME>/opencode/log/opencode.log on EVERY invocation, `--version` included, so a bare run
213
+ // would write into the user's ~/.local/share/opencode (AC-4d: nothing outside the OS temp dir).
214
+ function opencodeVersionLine(bin) {
215
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'oc2-probe-version-'));
216
+ try {
217
+ for (const dir of ['config', 'data', 'state', 'cache']) fs.mkdirSync(path.join(root, dir), { recursive: true });
218
+ return { text: execFileSync(bin, ['--version'], { encoding: 'utf8', timeout: 30000, env: xdgEnv({}, root) }).trim(), error: null };
219
+ } catch (err) {
220
+ const stdout = err && err.stdout ? String(err.stdout).trim() : '';
221
+ return { text: stdout, error: String((err && (err.message || err)) || err) };
222
+ } finally {
223
+ try { fs.rmSync(root, { recursive: true, force: true }); } catch { /* expected: best-effort temp cleanup */ }
224
+ }
225
+ }
226
+
227
+ // The TUI needs a pty, so it runs under `script -q /dev/null <bin> ...`. `--prompt <text>` submits
228
+ // one turn, but the TUI itself stays up: the probe waits for the listener, then kills its own tree.
229
+ // `prompt: null` means "expect no request at all" and observes for NEGATIVE_WAIT_MS instead.
230
+ // `until` (P-S9/P-S11) replaces the request-count stop with a predicate over probe-owned state
231
+ // (the plugin log), bounded by `deadlineMs` so a never-answered ask still ends the probe.
232
+ async function runTui({ env, args, prompt, until, deadlineMs }) {
233
+ const child = spawn(SCRIPT_BIN, ['-q', '/dev/null', state.opencodeBin, ...args], {
234
+ cwd: state.tempRoot,
235
+ env,
236
+ stdio: ['ignore', 'pipe', 'pipe'],
237
+ });
238
+ state.startedPids.add(child.pid);
239
+ const out = { stdout: '', stderr: '' };
240
+ child.stdout.on('data', (chunk) => { out.stdout += chunk; });
241
+ child.stderr.on('data', (chunk) => { out.stderr += chunk; });
242
+
243
+ let exited = false;
244
+ child.once('exit', () => { exited = true; });
245
+
246
+ const started = Date.now();
247
+ const deadline = started + (deadlineMs || PROBE_TIMEOUT_MS);
248
+ while (Date.now() < deadline) {
249
+ if (until) {
250
+ if (until()) { await sleep(1000); break; }
251
+ if (exited) { await sleep(500); break; }
252
+ } else if (prompt) {
253
+ if (requestsForPrompt(state.entries, prompt).length > 0) { await sleep(1500); break; }
254
+ if (exited) { await sleep(500); break; }
255
+ } else if (Date.now() - started >= NEGATIVE_WAIT_MS) {
256
+ break;
257
+ }
258
+ await sleep(250);
259
+ }
260
+
261
+ killTree(child.pid);
262
+ await waitExit(child, 5000);
263
+ return { exitCode: child.exitCode, elapsedMs: Date.now() - started, stdout: out.stdout, stderr: out.stderr };
264
+ }
265
+
266
+ function serviceRun(sub) {
267
+ try {
268
+ const stdout = execFileSync(state.opencodeBin, ['service', sub], {
269
+ cwd: state.tempRoot,
270
+ env: xdgEnv(),
271
+ encoding: 'utf8',
272
+ timeout: 60000,
273
+ stdio: ['ignore', 'pipe', 'pipe'],
274
+ });
275
+ return { ok: true, stdout: String(stdout) };
276
+ } catch (err) {
277
+ return { ok: false, stdout: String((err && err.stdout) || ''), error: String((err && (err.message || err)) || err) };
278
+ }
279
+ }
280
+
281
+ function servicePortOf(stdout) {
282
+ const match = /(?:127\.0\.0\.1|localhost):(\d+)/.exec(stdout || '');
283
+ return match ? parseInt(match[1], 10) : null;
284
+ }
285
+
286
+ // `service start` double-forks, so the daemon is NOT a descendant of ours: `service stop` is the
287
+ // only honest way to end it. If the port is still bound afterwards, the listening pid is resolved
288
+ // BY PORT -- a pid that can only be the service our own `service start` just created.
289
+ async function stopService(port) {
290
+ const stopped = serviceRun('stop');
291
+ if (!stopped.ok) note(`service stop reported: ${stopped.error}`, true);
292
+ if (!port) return stopped;
293
+ for (let i = 0; i < 10 && (await portIsListening(port)); i += 1) await sleep(500);
294
+ if (await portIsListening(port)) {
295
+ // Only OUR service is killed: OpenCode's service chdirs to HOME, which the probe set to its temp
296
+ // root, so a listener whose cwd is anything else (e.g. the user's own service) is left alone.
297
+ const root = fs.realpathSync(state.tempRoot);
298
+ const pids = listenerPidsOnPort(port).filter((pid) => {
299
+ try {
300
+ const out = execFileSync(LSOF_BIN, ['-a', '-p', String(pid), '-d', 'cwd', '-Fn'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
301
+ return out.split('\n').some((line) => line === 'n' + root);
302
+ } catch { return false; /* expected: the pid exited between the two lsof calls */ }
303
+ });
304
+ note(`service port ${port} still bound after stop; killing ${pids.join(', ') || '(no probe-owned pid)'}`);
305
+ for (const pid of pids) killTree(pid);
306
+ await sleep(500);
307
+ }
308
+ note(`P-S4 service port ${port}: listening after stop = ${await portIsListening(port)}`, true);
309
+ return stopped;
310
+ }
311
+
312
+ // ---- probes -------------------------------------------------------------------------------------
313
+
314
+ async function probeSessionSwitch(port, firstSid) {
315
+ if (!wanted('P-S1') && !wanted('P-S3a') && !wanted('P-S3b')) return;
316
+ // P-S1 -- the inline config model drives the first turn of a CTM-chosen session id.
317
+ const run1 = await runTui({
318
+ env: xdgEnv({ OPENCODE_CONFIG_CONTENT: inlineConfig({ model: 'm1', port }) }),
319
+ args: ['--standalone', '-s', firstSid, '--prompt', 'p1'],
320
+ prompt: 'p1',
321
+ });
322
+ const requests1 = requestsForPrompt(state.entries, 'p1');
323
+ const model1 = lastModelForPrompt(state.entries, 'p1');
324
+ const firstToolRequest = state.entries.find((entry) => Array.isArray(entry.tools) && entry.tools.length);
325
+ note('tools offered by the 2.x agent: ' + JSON.stringify(firstToolRequest ? firstToolRequest.tools : []), true);
326
+ record('P-S1',
327
+ 'inline config probe/m1 drives the first turn of -s ' + firstSid,
328
+ 'model=' + (model1 || 'none') + ' (requests for p1=' + requests1.length + ', tui exit=' + run1.exitCode + ', ' + Math.round(run1.elapsedMs / 1000) + 's)',
329
+ model1 === 'm1');
330
+
331
+ // P-S3a -- resume keeps the session's own model: the inline config does NOT switch it.
332
+ const run2 = await runTui({
333
+ env: xdgEnv({ OPENCODE_CONFIG_CONTENT: inlineConfig({ model: 'm2', port }) }),
334
+ args: ['--standalone', '-s', firstSid, '--prompt', 'p2'],
335
+ prompt: 'p2',
336
+ });
337
+ const requests2 = requestsForPrompt(state.entries, 'p2');
338
+ const model2 = lastModelForPrompt(state.entries, 'p2');
339
+ record('P-S3a',
340
+ 'a resumed session keeps its own model (m1); inline config probe/m2 does not switch it',
341
+ 'model=' + (model2 || 'none') + ' (requests for p2=' + requests2.length + ', tui exit=' + run2.exitCode + ', ' + Math.round(run2.elapsedMs / 1000) + 's)',
342
+ model2 === 'm1');
343
+
344
+ // P-S3b -- a `plugins` entry whose setup calls ctx.session.switchModel() DOES switch it.
345
+ const plugins = [{
346
+ package: PROBE_PLUGIN_DIR,
347
+ options: { sessionID: firstSid, model: { providerID: 'probe', id: 'm3' } },
348
+ }];
349
+ const run3 = await runTui({
350
+ env: xdgEnv({ OPENCODE_CONFIG_CONTENT: inlineConfig({ model: 'm1', port, plugins }), CTM_SESSION_ID: 'tab-E' }),
351
+ args: ['--standalone', '-s', firstSid, '--prompt', 'p3'],
352
+ prompt: 'p3',
353
+ });
354
+ const requests3 = requestsForPrompt(state.entries, 'p3');
355
+ const model3 = lastModelForPrompt(state.entries, 'p3');
356
+ const switchRows = pluginLog().filter((row) => row.ev === 'switch.ok' || row.ev === 'switch.err');
357
+ const switchNote = switchRows.length
358
+ ? switchRows.map((row) => row.ev + (row.err ? ':' + row.err : '')).join(',')
359
+ : 'plugin did not run (no log rows)';
360
+ record('P-S3b',
361
+ 'a `plugins` entry calling ctx.session.switchModel() moves the resumed session to probe/m3',
362
+ 'model=' + (model3 || 'none') + ' (plugin=' + switchNote + ', requests for p3=' + requests3.length + ', tui exit=' + run3.exitCode + ', ' + Math.round(run3.elapsedMs / 1000) + 's)',
363
+ model3 === 'm3' && switchRows.some((row) => row.ev === 'switch.ok'));
364
+ }
365
+
366
+ async function probeSharedService(port, sid) {
367
+ if (!wanted('P-S4')) return;
368
+ const started = serviceRun('start');
369
+ const servicePort = servicePortOf(started.stdout);
370
+ note(`P-S4 shared service start: ${started.ok ? 'ok' : 'FAILED ' + started.error}${servicePort ? ' port=' + servicePort : ''}`, true);
371
+
372
+ let observed;
373
+ let pass = false;
374
+ try {
375
+ if (!started.ok) {
376
+ observed = 'service start failed: ' + started.error;
377
+ } else {
378
+ const run = await runTui({
379
+ env: xdgEnv({ OPENCODE_CONFIG_CONTENT: inlineConfig({ model: 'm1', port }) }),
380
+ args: ['-s', sid, '--prompt', 'p4'],
381
+ prompt: null,
382
+ });
383
+ const hits = requestsForPrompt(state.entries, 'p4').length;
384
+ observed = 'listener requests for p4=' + hits + ' (observed ' + Math.round(NEGATIVE_WAIT_MS / 1000) + 's, tui exit=' + run.exitCode + ')';
385
+ pass = hits === 0;
386
+ }
387
+ } finally {
388
+ await stopService(servicePort);
389
+ }
390
+ record('P-S4',
391
+ 'the shared service ignores the tab inline config: no request reaches the tab listener for p4',
392
+ observed,
393
+ pass);
394
+ }
395
+
396
+ async function probeFreshSession(port) {
397
+ if (!wanted('P-S6')) return;
398
+ const sid = 'ses_' + crypto.randomBytes(16).toString('hex');
399
+ const run = await runTui({
400
+ env: xdgEnv({ OPENCODE_CONFIG_CONTENT: inlineConfig({ model: 'm2', port }) }),
401
+ args: ['--standalone', '--session', sid, '--prompt', 'p6'],
402
+ prompt: 'p6',
403
+ });
404
+ const model = lastModelForPrompt(state.entries, 'p6');
405
+
406
+ let row = { exists: false, dbError: null };
407
+ const deadline = Date.now() + 5000;
408
+ do {
409
+ row = sessionRowExists(sid);
410
+ if (row.exists || row.dbError) break;
411
+ await sleep(500);
412
+ } while (Date.now() < deadline);
413
+
414
+ record('P-S6',
415
+ 'a fresh --session ses_<32 hex> id is accepted, runs on config probe/m2 and is persisted in session_v2',
416
+ 'model=' + (model || 'none') + '; session_v2 has ' + sid + '=' + row.exists + (row.dbError ? ' (db error: ' + row.dbError + ')' : '') + ' (tui exit=' + run.exitCode + ', ' + Math.round(run.elapsedMs / 1000) + 's)',
417
+ model === 'm2' && row.exists);
418
+ }
419
+
420
+ // ---- WP-4 probes: permission events (OQ-2), the 1.x plugin file (OQ-3), subagent asks (OQ-5) ----
421
+
422
+ // The plugin log is append-only across probes: truncate it so a probe never reads an earlier
423
+ // probe's events (P-S9's ask must not be mistaken for P-S11's child ask).
424
+ function resetPluginLog() {
425
+ try { fs.writeFileSync(path.join(state.tempRoot, 'probe-plugin.log'), ''); } catch { /* expected: no log yet */ }
426
+ }
427
+
428
+ function probeEvents(sid) {
429
+ return pluginLog().filter((row) => row.ev === 'event' && (sid === undefined || row.sessionID === sid));
430
+ }
431
+
432
+ // A compact, order-preserving summary of the event types a run produced.
433
+ function eventTypeSummary(rows) {
434
+ const seen = [];
435
+ for (const row of rows) if (!seen.includes(row.type)) seen.push(row.type);
436
+ return seen;
437
+ }
438
+
439
+ async function probePermissionEvents(port, sid) {
440
+ if (!wanted('P-S9')) return;
441
+ resetPluginLog();
442
+ // P-S9 (OQ-2) -- a turn calls the `shell` tool while its config permission is "ask". The probe
443
+ // plugin logs every subscribed event and answers the ask through the 2.x plugin-visible
444
+ // ctx.permission.reply({ sessionID, requestID, decision: 'once' }).
445
+ state.entries.length = 0;
446
+ state.listener.setScript([
447
+ { when: 'p9', toolUse: { name: 'shell', input: { command: 'true' } } },
448
+ { text: 'probe-ok' },
449
+ ]);
450
+ const plugins = [{
451
+ package: PROBE_PLUGIN_DIR,
452
+ options: { log_events: true, reply_permission: true, probe_session: sid },
453
+ }];
454
+ const run = await runTui({
455
+ env: xdgEnv({ OPENCODE_CONFIG_CONTENT: inlineConfig({ model: 'm1', port, plugins, permissions: ASK_SHELL_PERMISSIONS }) }),
456
+ args: ['--standalone', '-s', sid, '--prompt', 'p9'],
457
+ until: () => probeEvents(sid).some((row) => row.type === 'permission.replied'),
458
+ deadlineMs: 60000,
459
+ });
460
+ const rows = probeEvents(sid);
461
+ const types = eventTypeSummary(rows);
462
+ const asked = rows.find((row) => row.type === 'permission.asked');
463
+ const replied = rows.find((row) => row.type === 'permission.replied');
464
+ const askRows = pluginLog().filter((row) => row.ev === 'ask');
465
+ const replyRows = pluginLog().filter((row) => row.ev === 'reply.ok' || row.ev === 'reply.err');
466
+ const replyNote = askRows.concat(replyRows).map((row) => row.ev + (row.err ? ':' + row.err : '')).join(',') || 'none';
467
+ const ask = askRows[0] || {};
468
+ record('P-S9',
469
+ 'a "shell" permission ask emits permission.asked for the probe session, then permission.replied after ctx.permission.reply',
470
+ 'session=' + sid + ' events=' + JSON.stringify(types)
471
+ + ' asked.sessionID=' + (asked ? JSON.stringify(asked.sessionID) : 'none')
472
+ + ' asked.action=' + JSON.stringify(ask.action === undefined ? null : ask.action)
473
+ + ' asked.resources=' + JSON.stringify(ask.resources === undefined ? null : ask.resources)
474
+ + ' requestID=' + JSON.stringify(ask.requestID === undefined ? null : ask.requestID)
475
+ + ' replied=' + (replied ? 'seen' : 'not-seen')
476
+ + ' pluginReply=' + replyNote
477
+ + ' tui exit=' + run.exitCode + ', ' + Math.round(run.elapsedMs / 1000) + 's',
478
+ !!asked && asked.sessionID === sid);
479
+ }
480
+
481
+ // Load-error lines the 2.x private server printed while loading the planted 1.x plugin file.
482
+ // 2.2.4 writes: level=WARN message="failed to load plugin" target=<path> cause="… PluginModule.LoadError: …"
483
+ function loadErrorLines(text) {
484
+ return String(text || '').split('\n')
485
+ .map((line) => line.trim())
486
+ .filter((line) => /failed to load plugin|PluginModule\.LoadError|LoadError/i.test(line));
487
+ }
488
+
489
+ async function probeLegacyPluginFile(port, sid) {
490
+ if (!wanted('P-S10')) return;
491
+ resetPluginLog();
492
+ // P-S10 (OQ-3) -- plant the CTM-owned 1.x plugin EXACTLY as the installer writes it
493
+ // (lib/agent-hooks-opencode-plugin.js PLUGIN_SOURCE, never a hand copy) into this run's
494
+ // isolated config dir and check whether 2.x logs a load error and whether a turn still completes.
495
+ const { PLUGIN_SOURCE } = require('../lib/agent-hooks-opencode-plugin');
496
+ const pluginDir = path.join(state.tempRoot, 'config', 'opencode', 'plugins');
497
+ fs.mkdirSync(pluginDir, { recursive: true });
498
+ const planted = path.join(pluginDir, 'ctm-status.js');
499
+ fs.writeFileSync(planted, PLUGIN_SOURCE);
500
+
501
+ state.entries.length = 0;
502
+ state.listener.setScript(null);
503
+ const run = await runTui({
504
+ env: xdgEnv({ OPENCODE_CONFIG_CONTENT: inlineConfig({ model: 'm1', port }) }),
505
+ args: ['--standalone', '-s', sid, '--prompt', 'p10'],
506
+ prompt: 'p10',
507
+ deadlineMs: 60000,
508
+ });
509
+ const turnCompleted = requestsForPrompt(state.entries, 'p10').length > 0;
510
+
511
+ // The load error may land on stderr or in the isolated log dirs; count both.
512
+ const logs = [];
513
+ for (const dir of ['state', 'data', 'cache']) {
514
+ const root = path.join(state.tempRoot, dir);
515
+ const walk = (at) => {
516
+ let items = [];
517
+ try { items = fs.readdirSync(at, { withFileTypes: true }); } catch { return; /* expected: dir absent */ }
518
+ for (const item of items) {
519
+ const full = path.join(at, item.name);
520
+ if (item.isDirectory()) walk(full);
521
+ else if (/\.log$|log$/i.test(item.name)) {
522
+ try { logs.push({ file: full, text: fs.readFileSync(full, 'utf8') }); } catch { /* expected: log rotated away */ }
523
+ }
524
+ }
525
+ };
526
+ walk(root);
527
+ }
528
+ const sources = [{ file: '(stderr)', text: run.stderr }].concat(logs);
529
+ const lines = sources.flatMap((source) => loadErrorLines(source.text));
530
+ // The interesting part of the log line: the loader's own reason, without the log envelope.
531
+ const firstCause = lines.length
532
+ ? ((/PluginModule\.LoadError: ([^)\\]{0,200})/.exec(lines[0]) || /"cause":"?([^"]{0,200})/.exec(lines[0]) || [null, lines[0]])[1] || '').trim()
533
+ : null;
534
+ note('P-S10 log files: ' + sources.map((source) => source.file).join(', '), true);
535
+ record('P-S10',
536
+ 'the planted 1.x ~/.config/opencode/plugins/ctm-status.js makes 2.x log a load error but a turn still completes',
537
+ 'planted=' + planted + ' loadErrorLines=' + lines.length
538
+ + (firstCause ? ' cause=' + JSON.stringify(String(firstCause).slice(0, 220)) : '')
539
+ + ' turnCompleted=' + turnCompleted + ' requests=' + requestsForPrompt(state.entries, 'p10').length
540
+ + ' logFiles=' + logs.map((log) => path.basename(log.file)).join('|')
541
+ + ' tui exit=' + run.exitCode + ', ' + Math.round(run.elapsedMs / 1000) + 's',
542
+ turnCompleted);
543
+ }
544
+
545
+ async function probeSubagentPermission(port, sid) {
546
+ if (!wanted('P-S11')) return;
547
+ resetPluginLog();
548
+ // P-S11 (OQ-5) -- turn 1 calls `subagent`; the child's first turn calls `shell` under "ask".
549
+ // Record session.created (with parentID) and permission.asked ids: is the ask's sessionID the
550
+ // child's or the parent's? No expected value; this probe never fails the run.
551
+ state.entries.length = 0;
552
+ state.listener.setScript([
553
+ { when: 'p11', toolUse: { name: 'subagent', input: { agent: 'general', description: 'probe child', prompt: 'p11child run the true command' } } },
554
+ { when: 'p11child', toolUse: { name: 'shell', input: { command: 'true' } } },
555
+ { text: 'probe-ok' },
556
+ ]);
557
+ const plugins = [{
558
+ package: PROBE_PLUGIN_DIR,
559
+ options: { log_events: true, reply_permission: true, probe_session: sid },
560
+ }];
561
+ const run = await runTui({
562
+ env: xdgEnv({ OPENCODE_CONFIG_CONTENT: inlineConfig({ model: 'm1', port, plugins, permissions: ASK_SHELL_PERMISSIONS }) }),
563
+ args: ['--standalone', '-s', sid, '--prompt', 'p11'],
564
+ until: () => pluginLog().some((row) => row.ev === 'ask' && row.sessionID !== sid),
565
+ deadlineMs: 60000,
566
+ });
567
+ const rows = pluginLog().filter((row) => row.ev === 'event');
568
+ const created = rows.filter((row) => row.type === 'session.created')
569
+ .map((row) => ({ sessionID: row.sessionID, parentID: row.parentID }));
570
+ const asks = pluginLog().filter((row) => row.ev === 'ask')
571
+ .map((row) => ({ sessionID: row.sessionID, action: row.action, resources: row.resources }));
572
+ const childIds = created.filter((row) => row.parentID).map((row) => row.sessionID);
573
+
574
+ // The answer OQ-5 asks for: is the ask's sessionID the child's or the parent's?
575
+ let verdict = 'unmeasured: no permission.asked reached the plugin in ' + Math.round(run.elapsedMs / 1000) + 's';
576
+ if (asks.length) {
577
+ const ids = asks.map((ask) => ask.sessionID);
578
+ if (ids.every((id) => id === sid)) verdict = 'parent-id (the ask carries the ROOT session id ' + sid + ')';
579
+ else if (ids.every((id) => childIds.includes(id))) verdict = 'child-id (the ask carries the CHILD session id ' + JSON.stringify(ids) + ')';
580
+ else verdict = 'mixed/other: asked=' + JSON.stringify(ids);
581
+ }
582
+ record('P-S11',
583
+ 'record which sessionID a subagent child permission.asked carries (child id vs parent), plus session.created parentID — no expected value',
584
+ 'answer=' + verdict
585
+ + ' parent=' + sid
586
+ + ' session.created=' + JSON.stringify(created)
587
+ + ' permission.asked=' + JSON.stringify(asks)
588
+ + ' childIds=' + JSON.stringify(childIds)
589
+ + ' tui exit=' + run.exitCode + ', ' + Math.round(run.elapsedMs / 1000) + 's',
590
+ true);
591
+ }
592
+
593
+ // sqlite3-free read of the isolated DB, through the repo's own better-sqlite3 (readonly).
594
+ function sessionRowExists(sid) {
595
+ const dbFile = path.join(state.tempRoot, 'data', 'opencode', 'opencode.db');
596
+ try {
597
+ const Database = require('better-sqlite3'); // resolves coding-task-manager/node_modules from scripts/
598
+ const db = new Database(dbFile, { readonly: true, fileMustExist: true });
599
+ try {
600
+ const row = db.prepare('SELECT id FROM session_v2 WHERE id = ?').get(sid);
601
+ return { exists: !!(row && row.id === sid), dbError: null };
602
+ } finally { db.close(); }
603
+ } catch (err) {
604
+ return { exists: false, dbError: String((err && (err.message || err)) || err) };
605
+ }
606
+ }
607
+
608
+ // ---- main ---------------------------------------------------------------------------------------
609
+
610
+ async function main() {
611
+ const args = parseArgs(process.argv.slice(2));
612
+ state.verbose = args.verbose;
613
+ state.only = args.only;
614
+
615
+ if (args.help || !args.opencode) {
616
+ process.stderr.write('usage: node scripts/opencode-v2-contract-probe.js --opencode <2.x bin> [--json] [--keep] [--verbose]\n');
617
+ return 2;
618
+ }
619
+ state.opencodeBin = args.opencode;
620
+
621
+ const version = opencodeVersionLine(args.opencode);
622
+ if (!/v2\./.test(version.text)) {
623
+ process.stdout.write(JSON.stringify({ error: 'not_v2', version: version.text, ...(version.error ? { detail: version.error } : {}) }) + '\n');
624
+ return 2;
625
+ }
626
+ note(`opencode ${version.text} at ${args.opencode}`);
627
+
628
+ state.tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'oc2-probe-'));
629
+ for (const dir of ['config', 'data', 'state', 'cache']) fs.mkdirSync(path.join(state.tempRoot, dir), { recursive: true });
630
+ note(`temp root ${state.tempRoot}`);
631
+
632
+ const listener = await startFakeAnthropic();
633
+ state.entries = listener.entries;
634
+ state.listener = listener;
635
+ note(`fake Anthropic listener on 127.0.0.1:${listener.port}`);
636
+
637
+ const switchSid = 'ses_ctmprobe' + crypto.randomBytes(10).toString('hex');
638
+ const serviceSid = 'ses_ctmprobe' + crypto.randomBytes(10).toString('hex');
639
+ const permissionSid = 'ses_ctmprobe' + crypto.randomBytes(10).toString('hex');
640
+ const legacySid = 'ses_ctmprobe' + crypto.randomBytes(10).toString('hex');
641
+ const subagentSid = 'ses_ctmprobe' + crypto.randomBytes(10).toString('hex');
642
+ note(`probe sessions: ${switchSid} (P-S1/P-S3a/P-S3b), ${serviceSid} (P-S4), ${permissionSid} (P-S9), ${legacySid} (P-S10), ${subagentSid} (P-S11)`);
643
+
644
+ try {
645
+ await probeSessionSwitch(listener.port, switchSid);
646
+ await probeSharedService(listener.port, serviceSid);
647
+ await probeFreshSession(listener.port);
648
+ await probePermissionEvents(listener.port, permissionSid);
649
+ await probeLegacyPluginFile(listener.port, legacySid);
650
+ await probeSubagentPermission(listener.port, subagentSid);
651
+ } catch (err) {
652
+ note(`fatal: ${(err && err.stack) || err}`);
653
+ process.exitCode = 1;
654
+ } finally {
655
+ for (const pid of state.startedPids) { if (pid) killTree(pid); }
656
+ try { await listener.stop(); } catch { /* expected: the listener socket is already closed */ }
657
+ if (args.keep) {
658
+ note(`temp root kept: ${state.tempRoot}`);
659
+ } else {
660
+ try { fs.rmSync(state.tempRoot, { recursive: true, force: true }); } catch (err) { note(`temp root cleanup failed: ${err}`); }
661
+ }
662
+ }
663
+
664
+ const failed = state.results.filter((row) => !row.pass);
665
+ const expected = state.only ? state.only.length : EXPECTED_PROBES;
666
+ note(`${state.results.length} probes, ${failed.length} failed (expected ${expected})`);
667
+ return failed.length === 0 && state.results.length === expected ? 0 : 1;
668
+ }
669
+
670
+ main().then((code) => { process.exitCode = code; }).catch((err) => {
671
+ process.stderr.write('[probe] fatal: ' + ((err && err.stack) || err) + '\n');
672
+ process.exitCode = 1;
673
+ });