@harness-mix/cli 0.1.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 (655) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +14 -0
  4. package/README.md +262 -0
  5. package/Start-Codex.cmd +5 -0
  6. package/config/codex-desktop-compatibility.json +29 -0
  7. package/docs/chatgpt-web-sidebar.md +27 -0
  8. package/docs/cross-platform.md +73 -0
  9. package/docs/harness-management.md +44 -0
  10. package/docs/images/codex-desktop-home.png +0 -0
  11. package/docs/images/codex-desktop-session.png +0 -0
  12. package/docs/kiro-cursor-integration.md +3 -0
  13. package/docs/multi-agent-collaboration.md +89 -0
  14. package/docs/native-acp.md +122 -0
  15. package/docs/native-codex.md +95 -0
  16. package/docs/superpowers/specs/2026-09-13-native-hardening-design.md +326 -0
  17. package/licenses/codex-host-MIT.txt +21 -0
  18. package/output/native-build/desktop-controller.mjs +1466 -0
  19. package/output/native-build/harness-mix-appx.exe +0 -0
  20. package/output/native-build/harness-mix-secret.exe +0 -0
  21. package/output/native-build/harness-mix-shim.exe +0 -0
  22. package/output/native-build/renderer-extension.js +29380 -0
  23. package/package.json +168 -0
  24. package/scripts/acp-capabilities-probe.cjs +100 -0
  25. package/scripts/adapters-test.cjs +17 -0
  26. package/scripts/antigravity-adapter-test.cjs +529 -0
  27. package/scripts/architecture-test.cjs +62 -0
  28. package/scripts/build-native.cjs +56 -0
  29. package/scripts/catalog-test.cjs +25 -0
  30. package/scripts/cdp-eval.cjs +40 -0
  31. package/scripts/cdp-watch.cjs +36 -0
  32. package/scripts/check.cjs +23 -0
  33. package/scripts/codebuddy-migration-test.cjs +47 -0
  34. package/scripts/codex-adapter-test.cjs +83 -0
  35. package/scripts/collaboration-recovery-test.cjs +111 -0
  36. package/scripts/collaboration-test.cjs +108 -0
  37. package/scripts/collaboration-ui-smoke.cjs +57 -0
  38. package/scripts/concurrency-layers-test.cjs +181 -0
  39. package/scripts/contracts-test.cjs +105 -0
  40. package/scripts/core-concurrency-test.cjs +103 -0
  41. package/scripts/core-replay-test.cjs +179 -0
  42. package/scripts/core-runtime-test.cjs +104 -0
  43. package/scripts/core-services-test.cjs +50 -0
  44. package/scripts/determinism-test.cjs +36 -0
  45. package/scripts/diagnostics.cjs +63 -0
  46. package/scripts/docs-command-test.cjs +49 -0
  47. package/scripts/dsh-adapter-test.cjs +90 -0
  48. package/scripts/dsh-collaboration-acp-test.cjs +19 -0
  49. package/scripts/dsh-native.cjs +9 -0
  50. package/scripts/dsh-native.cmd +2 -0
  51. package/scripts/e2e-codex.cjs +82 -0
  52. package/scripts/e2e-collaboration.cjs +60 -0
  53. package/scripts/e2e-core-files.cjs +58 -0
  54. package/scripts/e2e-core-fork.cjs +49 -0
  55. package/scripts/e2e-core-resume.cjs +49 -0
  56. package/scripts/e2e-delegate.cjs +38 -0
  57. package/scripts/e2e-dsh.cjs +23 -0
  58. package/scripts/e2e-native-acp.cjs +42 -0
  59. package/scripts/e2e-native-commands.cjs +54 -0
  60. package/scripts/e2e-native-host.cjs +144 -0
  61. package/scripts/e2e-openclaw-image.cjs +59 -0
  62. package/scripts/e2e-openclaw.cjs +21 -0
  63. package/scripts/e2e-pi.cjs +23 -0
  64. package/scripts/e2e-shadow.cjs +52 -0
  65. package/scripts/fixture-capture.cjs +103 -0
  66. package/scripts/harness-health-check.cjs +72 -0
  67. package/scripts/kiro-cursor-adapters-test.cjs +82 -0
  68. package/scripts/launch-codex.cjs +5 -0
  69. package/scripts/native-acp-depth-test.cjs +189 -0
  70. package/scripts/native-compatibility-test.cjs +42 -0
  71. package/scripts/native-config-test.cjs +25 -0
  72. package/scripts/native-file-change-test.cjs +94 -0
  73. package/scripts/native-host.cjs +8 -0
  74. package/scripts/native-images-test.cjs +56 -0
  75. package/scripts/native-job-object-test.cjs +83 -0
  76. package/scripts/native-protocol-test.cjs +258 -0
  77. package/scripts/native-rollback-test.cjs +46 -0
  78. package/scripts/native-secret-test.cjs +57 -0
  79. package/scripts/native-shim-test.cjs +41 -0
  80. package/scripts/native-sidebar-test.cjs +88 -0
  81. package/scripts/native-ui-smoke.cjs +94 -0
  82. package/scripts/native-update-apply-test.cjs +215 -0
  83. package/scripts/native-updater-test.cjs +109 -0
  84. package/scripts/native-vendor-adapters-test.cjs +88 -0
  85. package/scripts/openclaw-acp-probe.cjs +39 -0
  86. package/scripts/openclaw-adapter-test.cjs +180 -0
  87. package/scripts/openclaw-models-probe.cjs +13 -0
  88. package/scripts/platform-test.cjs +62 -0
  89. package/scripts/projector-test.cjs +144 -0
  90. package/scripts/sequence-test.cjs +57 -0
  91. package/scripts/shadow-test.cjs +154 -0
  92. package/scripts/support/legacy-projection.cjs +95 -0
  93. package/scripts/support/legacy-transcript.cjs +48 -0
  94. package/scripts/support/parity-observer.cjs +27 -0
  95. package/scripts/support/shadow-comparator.cjs +62 -0
  96. package/scripts/support/shadow.cjs +114 -0
  97. package/scripts/switch-harness-test.cjs +145 -0
  98. package/scripts/thread-title-test.cjs +98 -0
  99. package/scripts/tool-concurrency-regression-test.cjs +119 -0
  100. package/scripts/transcript-test.cjs +24 -0
  101. package/scripts/turn-manager-test.cjs +91 -0
  102. package/scripts/workspace-test.cjs +86 -0
  103. package/src/assets/brand-harness-mix.png +0 -0
  104. package/src/assets/icons/antigravity-color.svg +1 -0
  105. package/src/assets/icons/claude-color.svg +1 -0
  106. package/src/assets/icons/codebuddy-color.svg +1 -0
  107. package/src/assets/icons/codex-color.svg +1 -0
  108. package/src/assets/icons/codex-harness.svg +1 -0
  109. package/src/assets/icons/cursor-cli-color.svg +1 -0
  110. package/src/assets/icons/cursor.svg +1 -0
  111. package/src/assets/icons/deepseek-color.svg +1 -0
  112. package/src/assets/icons/grok-color.svg +1 -0
  113. package/src/assets/icons/hermes-color.svg +1 -0
  114. package/src/assets/icons/kiro-cli-color.svg +11 -0
  115. package/src/assets/icons/model-astra.svg +1 -0
  116. package/src/assets/icons/model-claude.svg +1 -0
  117. package/src/assets/icons/model-deepseek.svg +1 -0
  118. package/src/assets/icons/model-gemini.svg +1 -0
  119. package/src/assets/icons/model-grok.svg +1 -0
  120. package/src/assets/icons/model-hunyuan.svg +1 -0
  121. package/src/assets/icons/model-kimi.svg +1 -0
  122. package/src/assets/icons/model-minimax.svg +1 -0
  123. package/src/assets/icons/model-openai.svg +1 -0
  124. package/src/assets/icons/model-qwen-color.svg +1 -0
  125. package/src/assets/icons/model-xiaomimimo.svg +1 -0
  126. package/src/assets/icons/model-zai.svg +1 -0
  127. package/src/assets/icons/omp-color.svg +1 -0
  128. package/src/assets/icons/openclaw-color.svg +1 -0
  129. package/src/assets/icons/opencode-color.svg +1 -0
  130. package/src/assets/icons/pi.svg +1 -0
  131. package/src/assets/icons/pinumber1_80899.svg +48 -0
  132. package/src/assets/icons/qoder-color.svg +1 -0
  133. package/src/assets/icons/trae-color.svg +1 -0
  134. package/src/assets/icons/workbuddy-color.svg +1 -0
  135. package/src/assets/icons/zcode-color.svg +1 -0
  136. package/src/main/adapters/acp-interactions.js +132 -0
  137. package/src/main/adapters/acp.js +191 -0
  138. package/src/main/adapters/antigravity.js +1368 -0
  139. package/src/main/adapters/claude.js +520 -0
  140. package/src/main/adapters/codebuddy-history.js +90 -0
  141. package/src/main/adapters/codebuddy.js +2 -0
  142. package/src/main/adapters/codex-app-server.js +119 -0
  143. package/src/main/adapters/codex.js +559 -0
  144. package/src/main/adapters/cursor.js +2 -0
  145. package/src/main/adapters/dsh-web-host.js +202 -0
  146. package/src/main/adapters/dsh.js +446 -0
  147. package/src/main/adapters/grok.js +277 -0
  148. package/src/main/adapters/hermes.js +24 -0
  149. package/src/main/adapters/index.js +32 -0
  150. package/src/main/adapters/kiro-history.js +43 -0
  151. package/src/main/adapters/kiro.js +2 -0
  152. package/src/main/adapters/native-acp-command.js +47 -0
  153. package/src/main/adapters/native-acp.js +435 -0
  154. package/src/main/adapters/omp.js +12 -0
  155. package/src/main/adapters/openclaw-gateway.js +211 -0
  156. package/src/main/adapters/openclaw.js +337 -0
  157. package/src/main/adapters/opencode-server.js +73 -0
  158. package/src/main/adapters/opencode.js +178 -0
  159. package/src/main/adapters/pi-family.js +342 -0
  160. package/src/main/adapters/pi-usage.js +24 -0
  161. package/src/main/adapters/pi.js +10 -0
  162. package/src/main/adapters/qoder.js +7 -0
  163. package/src/main/adapters/trae.js +7 -0
  164. package/src/main/adapters/workbuddy.js +2 -0
  165. package/src/main/adapters/zcode.js +7 -0
  166. package/src/main/harness-adapter/contract-test.js +53 -0
  167. package/src/main/harness-adapter/contract.js +34 -0
  168. package/src/main/harness-adapter/event-normalizer.js +252 -0
  169. package/src/main/harness-adapter/fixture-recorder.js +56 -0
  170. package/src/main/harness-adapter/index.js +6 -0
  171. package/src/main/harness-adapter/manifest.js +44 -0
  172. package/src/main/host/collaboration-mcp.cjs +35 -0
  173. package/src/main/host/collaboration-pi.mjs +17 -0
  174. package/src/main/host/collaboration-tools.js +16 -0
  175. package/src/main/host/collaboration-worktree.js +151 -0
  176. package/src/main/host/collaboration.js +264 -0
  177. package/src/main/host/core-session.js +81 -0
  178. package/src/main/host/handoff.js +46 -0
  179. package/src/main/host/history-import.js +55 -0
  180. package/src/main/host/jsonl.js +116 -0
  181. package/src/main/host/native-history.js +79 -0
  182. package/src/main/host/runtime.js +1050 -0
  183. package/src/main/host/session-history.js +63 -0
  184. package/src/main/host/store.js +75 -0
  185. package/src/main/native/compatibility.js +56 -0
  186. package/src/main/native/config.js +55 -0
  187. package/src/main/native/host.js +153 -0
  188. package/src/main/native/icons.js +105 -0
  189. package/src/main/native/input.js +43 -0
  190. package/src/main/native/launcher.js +188 -0
  191. package/src/main/native/platform.js +64 -0
  192. package/src/main/native/process-utils.js +57 -0
  193. package/src/main/native/protocol.js +836 -0
  194. package/src/main/native/redact.js +35 -0
  195. package/src/main/native/rs/Cargo.lock +15 -0
  196. package/src/main/native/rs/Cargo.toml +9 -0
  197. package/src/main/native/rs/crates/appx/Cargo.toml +13 -0
  198. package/src/main/native/rs/crates/appx/src/main.rs +309 -0
  199. package/src/main/native/rs/crates/secret/Cargo.toml +10 -0
  200. package/src/main/native/rs/crates/secret/src/main.rs +262 -0
  201. package/src/main/native/rs/crates/shim/Cargo.toml +10 -0
  202. package/src/main/native/rs/crates/shim/src/job.rs +83 -0
  203. package/src/main/native/rs/crates/shim/src/main.rs +257 -0
  204. package/src/main/native/secure-store.js +59 -0
  205. package/src/main/native/thread-list.js +38 -0
  206. package/src/main/native/update-state.js +110 -0
  207. package/src/main/native/updater.js +394 -0
  208. package/src/main/native/usage.js +105 -0
  209. package/src/main/protocol-core/capability-manager.js +16 -0
  210. package/src/main/protocol-core/event-router.js +32 -0
  211. package/src/main/protocol-core/file-change-projector.js +45 -0
  212. package/src/main/protocol-core/index.js +8 -0
  213. package/src/main/protocol-core/interaction-router.js +30 -0
  214. package/src/main/protocol-core/projector.js +238 -0
  215. package/src/main/protocol-core/protocol-core.js +132 -0
  216. package/src/main/protocol-core/sequence-validator.js +44 -0
  217. package/src/main/protocol-core/thread-manager.js +50 -0
  218. package/src/main/protocol-core/turn-manager.js +63 -0
  219. package/src/main/shared-contracts/capability.js +35 -0
  220. package/src/main/shared-contracts/event.js +55 -0
  221. package/src/main/shared-contracts/ids.js +12 -0
  222. package/src/main/shared-contracts/index.js +11 -0
  223. package/src/main/shared-contracts/interaction.js +37 -0
  224. package/src/main/shared-contracts/item.js +51 -0
  225. package/src/main/shared-contracts/native-ref.js +24 -0
  226. package/src/main/shared-contracts/thread.js +36 -0
  227. package/src/main/shared-contracts/turn.js +54 -0
  228. package/src/main/workspace/core-review.js +53 -0
  229. package/src/main/workspace/diff.js +27 -0
  230. package/src/main/workspace/file-changes.js +21 -0
  231. package/src/main/workspace/files.js +76 -0
  232. package/src/main/workspace/review-controller.js +102 -0
  233. package/src/main/workspace/review.js +88 -0
  234. package/src/native-ui/LICENSE +21 -0
  235. package/src/native-ui/README.md +16 -0
  236. package/src/native-ui/desktop-control/dist/cdp-client.d.ts +53 -0
  237. package/src/native-ui/desktop-control/dist/cdp-client.d.ts.map +1 -0
  238. package/src/native-ui/desktop-control/dist/cdp-client.js +258 -0
  239. package/src/native-ui/desktop-control/dist/cdp-client.js.map +1 -0
  240. package/src/native-ui/desktop-control/dist/contract-audit.d.ts +73 -0
  241. package/src/native-ui/desktop-control/dist/contract-audit.d.ts.map +1 -0
  242. package/src/native-ui/desktop-control/dist/contract-audit.js +152 -0
  243. package/src/native-ui/desktop-control/dist/contract-audit.js.map +1 -0
  244. package/src/native-ui/desktop-control/dist/controller-attachment-server.d.ts +10 -0
  245. package/src/native-ui/desktop-control/dist/controller-attachment-server.d.ts.map +1 -0
  246. package/src/native-ui/desktop-control/dist/controller-attachment-server.js +77 -0
  247. package/src/native-ui/desktop-control/dist/controller-attachment-server.js.map +1 -0
  248. package/src/native-ui/desktop-control/dist/index.d.ts +21 -0
  249. package/src/native-ui/desktop-control/dist/index.d.ts.map +1 -0
  250. package/src/native-ui/desktop-control/dist/index.js +14 -0
  251. package/src/native-ui/desktop-control/dist/index.js.map +1 -0
  252. package/src/native-ui/desktop-control/dist/main-process-title-policy.d.ts +24 -0
  253. package/src/native-ui/desktop-control/dist/main-process-title-policy.d.ts.map +1 -0
  254. package/src/native-ui/desktop-control/dist/main-process-title-policy.js +284 -0
  255. package/src/native-ui/desktop-control/dist/main-process-title-policy.js.map +1 -0
  256. package/src/native-ui/desktop-control/dist/production-controller.d.ts +32 -0
  257. package/src/native-ui/desktop-control/dist/production-controller.d.ts.map +1 -0
  258. package/src/native-ui/desktop-control/dist/production-controller.js +294 -0
  259. package/src/native-ui/desktop-control/dist/production-controller.js.map +1 -0
  260. package/src/native-ui/desktop-control/dist/release-main.d.ts +2 -0
  261. package/src/native-ui/desktop-control/dist/release-main.d.ts.map +1 -0
  262. package/src/native-ui/desktop-control/dist/release-main.js +17 -0
  263. package/src/native-ui/desktop-control/dist/release-main.js.map +1 -0
  264. package/src/native-ui/desktop-control/dist/renderer-cdp-control-session.d.ts +49 -0
  265. package/src/native-ui/desktop-control/dist/renderer-cdp-control-session.d.ts.map +1 -0
  266. package/src/native-ui/desktop-control/dist/renderer-cdp-control-session.js +214 -0
  267. package/src/native-ui/desktop-control/dist/renderer-cdp-control-session.js.map +1 -0
  268. package/src/native-ui/desktop-control/dist/renderer-control-session.d.ts +79 -0
  269. package/src/native-ui/desktop-control/dist/renderer-control-session.d.ts.map +1 -0
  270. package/src/native-ui/desktop-control/dist/renderer-control-session.js +366 -0
  271. package/src/native-ui/desktop-control/dist/renderer-control-session.js.map +1 -0
  272. package/src/native-ui/desktop-control/dist/renderer-dom.d.ts +24 -0
  273. package/src/native-ui/desktop-control/dist/renderer-dom.d.ts.map +1 -0
  274. package/src/native-ui/desktop-control/dist/renderer-dom.js +110 -0
  275. package/src/native-ui/desktop-control/dist/renderer-dom.js.map +1 -0
  276. package/src/native-ui/desktop-control/dist/renderer-draft-prewarm-policy.d.ts +20 -0
  277. package/src/native-ui/desktop-control/dist/renderer-draft-prewarm-policy.d.ts.map +1 -0
  278. package/src/native-ui/desktop-control/dist/renderer-draft-prewarm-policy.js +173 -0
  279. package/src/native-ui/desktop-control/dist/renderer-draft-prewarm-policy.js.map +1 -0
  280. package/src/native-ui/desktop-control/dist/renderer-draft-prewarm-runtime.d.ts +38 -0
  281. package/src/native-ui/desktop-control/dist/renderer-draft-prewarm-runtime.d.ts.map +1 -0
  282. package/src/native-ui/desktop-control/dist/renderer-draft-prewarm-runtime.js +637 -0
  283. package/src/native-ui/desktop-control/dist/renderer-draft-prewarm-runtime.js.map +1 -0
  284. package/src/native-ui/desktop-control/dist/tsconfig.tsbuildinfo +1 -0
  285. package/src/native-ui/desktop-control/package.json +22 -0
  286. package/src/native-ui/desktop-control/scripts/build-release.mjs +94 -0
  287. package/src/native-ui/desktop-control/src/cdp-client.ts +347 -0
  288. package/src/native-ui/desktop-control/src/contract-audit.ts +291 -0
  289. package/src/native-ui/desktop-control/src/controller-attachment-server.ts +93 -0
  290. package/src/native-ui/desktop-control/src/index.ts +91 -0
  291. package/src/native-ui/desktop-control/src/main-process-title-policy.ts +372 -0
  292. package/src/native-ui/desktop-control/src/production-controller.ts +347 -0
  293. package/src/native-ui/desktop-control/src/release-main.ts +18 -0
  294. package/src/native-ui/desktop-control/src/renderer-cdp-control-session.ts +358 -0
  295. package/src/native-ui/desktop-control/src/renderer-control-session.ts +595 -0
  296. package/src/native-ui/desktop-control/src/renderer-dom.ts +144 -0
  297. package/src/native-ui/desktop-control/src/renderer-draft-prewarm-policy.ts +217 -0
  298. package/src/native-ui/desktop-control/src/renderer-draft-prewarm-runtime.ts +749 -0
  299. package/src/native-ui/desktop-control/test/cdp-client.test.ts +157 -0
  300. package/src/native-ui/desktop-control/test/contract-audit.test.ts +56 -0
  301. package/src/native-ui/desktop-control/test/controller-attachment-server.test.ts +98 -0
  302. package/src/native-ui/desktop-control/test/index.test.ts +11 -0
  303. package/src/native-ui/desktop-control/test/main-process-title-policy.test.ts +168 -0
  304. package/src/native-ui/desktop-control/test/production-controller.test.ts +392 -0
  305. package/src/native-ui/desktop-control/test/renderer-cdp-control-session.test.ts +205 -0
  306. package/src/native-ui/desktop-control/test/renderer-control-session.test.ts +451 -0
  307. package/src/native-ui/desktop-control/test/renderer-dom.test.ts +62 -0
  308. package/src/native-ui/desktop-control/test/renderer-draft-prewarm-policy.test.ts +1029 -0
  309. package/src/native-ui/desktop-control/tsconfig.json +12 -0
  310. package/src/native-ui/renderer-extension/dist/types/agent-group-preference.d.ts +39 -0
  311. package/src/native-ui/renderer-extension/dist/types/agent-group-preference.d.ts.map +1 -0
  312. package/src/native-ui/renderer-extension/dist/types/agent-selection-state.d.ts +85 -0
  313. package/src/native-ui/renderer-extension/dist/types/agent-selection-state.d.ts.map +1 -0
  314. package/src/native-ui/renderer-extension/dist/types/audit-entry.d.ts +2 -0
  315. package/src/native-ui/renderer-extension/dist/types/audit-entry.d.ts.map +1 -0
  316. package/src/native-ui/renderer-extension/dist/types/codex-locale-adapter.d.ts +23 -0
  317. package/src/native-ui/renderer-extension/dist/types/codex-locale-adapter.d.ts.map +1 -0
  318. package/src/native-ui/renderer-extension/dist/types/collaboration-icon.d.ts +2 -0
  319. package/src/native-ui/renderer-extension/dist/types/collaboration-icon.d.ts.map +1 -0
  320. package/src/native-ui/renderer-extension/dist/types/contract-audit.d.ts +37 -0
  321. package/src/native-ui/renderer-extension/dist/types/contract-audit.d.ts.map +1 -0
  322. package/src/native-ui/renderer-extension/dist/types/harness-mix-icons.d.ts +3 -0
  323. package/src/native-ui/renderer-extension/dist/types/harness-mix-icons.d.ts.map +1 -0
  324. package/src/native-ui/renderer-extension/dist/types/harness-mix-settings.d.ts +3 -0
  325. package/src/native-ui/renderer-extension/dist/types/harness-mix-settings.d.ts.map +1 -0
  326. package/src/native-ui/renderer-extension/dist/types/index.d.ts +51 -0
  327. package/src/native-ui/renderer-extension/dist/types/index.d.ts.map +1 -0
  328. package/src/native-ui/renderer-extension/dist/types/install-renderer-binding.d.ts +4 -0
  329. package/src/native-ui/renderer-extension/dist/types/install-renderer-binding.d.ts.map +1 -0
  330. package/src/native-ui/renderer-extension/dist/types/probe-entry.d.ts +2 -0
  331. package/src/native-ui/renderer-extension/dist/types/probe-entry.d.ts.map +1 -0
  332. package/src/native-ui/renderer-extension/dist/types/production-entry.d.ts +9 -0
  333. package/src/native-ui/renderer-extension/dist/types/production-entry.d.ts.map +1 -0
  334. package/src/native-ui/renderer-extension/dist/types/renderer-agent-icon.d.ts +4 -0
  335. package/src/native-ui/renderer-extension/dist/types/renderer-agent-icon.d.ts.map +1 -0
  336. package/src/native-ui/renderer-extension/dist/types/renderer-agent-picker.d.ts +62 -0
  337. package/src/native-ui/renderer-extension/dist/types/renderer-agent-picker.d.ts.map +1 -0
  338. package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts +70 -0
  339. package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts.map +1 -0
  340. package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-options.d.ts +34 -0
  341. package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-options.d.ts.map +1 -0
  342. package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-state.d.ts +19 -0
  343. package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-state.d.ts.map +1 -0
  344. package/src/native-ui/renderer-extension/dist/types/renderer-collab-cards.d.ts +40 -0
  345. package/src/native-ui/renderer-extension/dist/types/renderer-collab-cards.d.ts.map +1 -0
  346. package/src/native-ui/renderer-extension/dist/types/renderer-composer-dom.d.ts +82 -0
  347. package/src/native-ui/renderer-extension/dist/types/renderer-composer-dom.d.ts.map +1 -0
  348. package/src/native-ui/renderer-extension/dist/types/renderer-credits-control.d.ts +21 -0
  349. package/src/native-ui/renderer-extension/dist/types/renderer-credits-control.d.ts.map +1 -0
  350. package/src/native-ui/renderer-extension/dist/types/renderer-external-steering.d.ts +7 -0
  351. package/src/native-ui/renderer-extension/dist/types/renderer-external-steering.d.ts.map +1 -0
  352. package/src/native-ui/renderer-extension/dist/types/renderer-fork-control.d.ts +34 -0
  353. package/src/native-ui/renderer-extension/dist/types/renderer-fork-control.d.ts.map +1 -0
  354. package/src/native-ui/renderer-extension/dist/types/renderer-harness-command-claim.d.ts +4 -0
  355. package/src/native-ui/renderer-extension/dist/types/renderer-harness-command-claim.d.ts.map +1 -0
  356. package/src/native-ui/renderer-extension/dist/types/renderer-harness-command-control.d.ts +15 -0
  357. package/src/native-ui/renderer-extension/dist/types/renderer-harness-command-control.d.ts.map +1 -0
  358. package/src/native-ui/renderer-extension/dist/types/renderer-harness-localization.d.ts +25 -0
  359. package/src/native-ui/renderer-extension/dist/types/renderer-harness-localization.d.ts.map +1 -0
  360. package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts +23 -0
  361. package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts.map +1 -0
  362. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts +102 -0
  363. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts.map +1 -0
  364. package/src/native-ui/renderer-extension/dist/types/renderer-model-picker-positioning.d.ts +22 -0
  365. package/src/native-ui/renderer-extension/dist/types/renderer-model-picker-positioning.d.ts.map +1 -0
  366. package/src/native-ui/renderer-extension/dist/types/renderer-model-picker.d.ts +54 -0
  367. package/src/native-ui/renderer-extension/dist/types/renderer-model-picker.d.ts.map +1 -0
  368. package/src/native-ui/renderer-extension/dist/types/renderer-new-thread-preference.d.ts +18 -0
  369. package/src/native-ui/renderer-extension/dist/types/renderer-new-thread-preference.d.ts.map +1 -0
  370. package/src/native-ui/renderer-extension/dist/types/renderer-permission-mode-picker.d.ts +42 -0
  371. package/src/native-ui/renderer-extension/dist/types/renderer-permission-mode-picker.d.ts.map +1 -0
  372. package/src/native-ui/renderer-extension/dist/types/renderer-permission-mode-preference.d.ts +9 -0
  373. package/src/native-ui/renderer-extension/dist/types/renderer-permission-mode-preference.d.ts.map +1 -0
  374. package/src/native-ui/renderer-extension/dist/types/renderer-request-sender.d.ts +9 -0
  375. package/src/native-ui/renderer-extension/dist/types/renderer-request-sender.d.ts.map +1 -0
  376. package/src/native-ui/renderer-extension/dist/types/renderer-session-import-client.d.ts +12 -0
  377. package/src/native-ui/renderer-extension/dist/types/renderer-session-import-client.d.ts.map +1 -0
  378. package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts +18 -0
  379. package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts.map +1 -0
  380. package/src/native-ui/renderer-extension/dist/types/renderer-sidebar-agent-icons.d.ts +45 -0
  381. package/src/native-ui/renderer-extension/dist/types/renderer-sidebar-agent-icons.d.ts.map +1 -0
  382. package/src/native-ui/renderer-extension/dist/types/renderer-transcript-dom.d.ts +23 -0
  383. package/src/native-ui/renderer-extension/dist/types/renderer-transcript-dom.d.ts.map +1 -0
  384. package/src/native-ui/renderer-extension/dist/types/renderer-trigger-chip-style.d.ts +3 -0
  385. package/src/native-ui/renderer-extension/dist/types/renderer-trigger-chip-style.d.ts.map +1 -0
  386. package/src/native-ui/renderer-extension/dist/types/renderer-usage-control.d.ts +72 -0
  387. package/src/native-ui/renderer-extension/dist/types/renderer-usage-control.d.ts.map +1 -0
  388. package/src/native-ui/renderer-extension/dist/types/settings/accounts-list.d.ts +24 -0
  389. package/src/native-ui/renderer-extension/dist/types/settings/accounts-list.d.ts.map +1 -0
  390. package/src/native-ui/renderer-extension/dist/types/settings/accounts-page.d.ts +18 -0
  391. package/src/native-ui/renderer-extension/dist/types/settings/accounts-page.d.ts.map +1 -0
  392. package/src/native-ui/renderer-extension/dist/types/settings/accounts-usage.d.ts +27 -0
  393. package/src/native-ui/renderer-extension/dist/types/settings/accounts-usage.d.ts.map +1 -0
  394. package/src/native-ui/renderer-extension/dist/types/settings/connections-page.d.ts +44 -0
  395. package/src/native-ui/renderer-extension/dist/types/settings/connections-page.d.ts.map +1 -0
  396. package/src/native-ui/renderer-extension/dist/types/settings/core.d.ts +38 -0
  397. package/src/native-ui/renderer-extension/dist/types/settings/core.d.ts.map +1 -0
  398. package/src/native-ui/renderer-extension/dist/types/settings/harness-accounts.d.ts +21 -0
  399. package/src/native-ui/renderer-extension/dist/types/settings/harness-accounts.d.ts.map +1 -0
  400. package/src/native-ui/renderer-extension/dist/types/settings/icons.d.ts +6 -0
  401. package/src/native-ui/renderer-extension/dist/types/settings/icons.d.ts.map +1 -0
  402. package/src/native-ui/renderer-extension/dist/types/settings/localization.d.ts +247 -0
  403. package/src/native-ui/renderer-extension/dist/types/settings/localization.d.ts.map +1 -0
  404. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts +23 -0
  405. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts.map +1 -0
  406. package/src/native-ui/renderer-extension/dist/types/settings/release-notes.d.ts +4 -0
  407. package/src/native-ui/renderer-extension/dist/types/settings/release-notes.d.ts.map +1 -0
  408. package/src/native-ui/renderer-extension/dist/types/settings/session-import-list-controls.d.ts +12 -0
  409. package/src/native-ui/renderer-extension/dist/types/settings/session-import-list-controls.d.ts.map +1 -0
  410. package/src/native-ui/renderer-extension/dist/types/settings/session-import-page.d.ts +8 -0
  411. package/src/native-ui/renderer-extension/dist/types/settings/session-import-page.d.ts.map +1 -0
  412. package/src/native-ui/renderer-extension/dist/types/settings/shell.d.ts +24 -0
  413. package/src/native-ui/renderer-extension/dist/types/settings/shell.d.ts.map +1 -0
  414. package/src/native-ui/renderer-extension/dist/types/settings/trigger.d.ts +45 -0
  415. package/src/native-ui/renderer-extension/dist/types/settings/trigger.d.ts.map +1 -0
  416. package/src/native-ui/renderer-extension/dist/types/settings/update-request.d.ts +6 -0
  417. package/src/native-ui/renderer-extension/dist/types/settings/update-request.d.ts.map +1 -0
  418. package/src/native-ui/renderer-extension/dist/types/tsconfig.tsbuildinfo +1 -0
  419. package/src/native-ui/renderer-extension/dist/types/versioned-renderer-adapter.d.ts +143 -0
  420. package/src/native-ui/renderer-extension/dist/types/versioned-renderer-adapter.d.ts.map +1 -0
  421. package/src/native-ui/renderer-extension/package.json +22 -0
  422. package/src/native-ui/renderer-extension/src/agent-group-preference.ts +174 -0
  423. package/src/native-ui/renderer-extension/src/agent-selection-state.ts +627 -0
  424. package/src/native-ui/renderer-extension/src/assets/README.md +13 -0
  425. package/src/native-ui/renderer-extension/src/assets/antigravity-agent.svg +1 -0
  426. package/src/native-ui/renderer-extension/src/assets/codex-agent.png +0 -0
  427. package/src/native-ui/renderer-extension/src/assets/grok-agent.png +0 -0
  428. package/src/native-ui/renderer-extension/src/assets/kiro-agent.svg +11 -0
  429. package/src/native-ui/renderer-extension/src/assets/omp-agent.svg +14 -0
  430. package/src/native-ui/renderer-extension/src/assets/opencode-agent.png +0 -0
  431. package/src/native-ui/renderer-extension/src/assets.d.ts +26 -0
  432. package/src/native-ui/renderer-extension/src/audit-entry.ts +5 -0
  433. package/src/native-ui/renderer-extension/src/codex-locale-adapter.ts +321 -0
  434. package/src/native-ui/renderer-extension/src/collaboration-icon.ts +13 -0
  435. package/src/native-ui/renderer-extension/src/contract-audit.ts +102 -0
  436. package/src/native-ui/renderer-extension/src/harness-mix-icons.ts +31 -0
  437. package/src/native-ui/renderer-extension/src/harness-mix-settings.ts +88 -0
  438. package/src/native-ui/renderer-extension/src/index.ts +275 -0
  439. package/src/native-ui/renderer-extension/src/install-renderer-binding.ts +30 -0
  440. package/src/native-ui/renderer-extension/src/probe-entry.ts +4 -0
  441. package/src/native-ui/renderer-extension/src/production-entry.ts +23 -0
  442. package/src/native-ui/renderer-extension/src/renderer-agent-icon.ts +146 -0
  443. package/src/native-ui/renderer-extension/src/renderer-agent-picker.ts +814 -0
  444. package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +3002 -0
  445. package/src/native-ui/renderer-extension/src/renderer-codex-account-options.ts +281 -0
  446. package/src/native-ui/renderer-extension/src/renderer-codex-account-state.ts +53 -0
  447. package/src/native-ui/renderer-extension/src/renderer-collab-cards.ts +198 -0
  448. package/src/native-ui/renderer-extension/src/renderer-composer-dom.ts +782 -0
  449. package/src/native-ui/renderer-extension/src/renderer-credits-control.ts +520 -0
  450. package/src/native-ui/renderer-extension/src/renderer-external-steering.ts +339 -0
  451. package/src/native-ui/renderer-extension/src/renderer-fork-control.ts +273 -0
  452. package/src/native-ui/renderer-extension/src/renderer-harness-command-claim.ts +64 -0
  453. package/src/native-ui/renderer-extension/src/renderer-harness-command-control.ts +417 -0
  454. package/src/native-ui/renderer-extension/src/renderer-harness-localization.ts +144 -0
  455. package/src/native-ui/renderer-extension/src/renderer-harness-mentions.ts +306 -0
  456. package/src/native-ui/renderer-extension/src/renderer-model-client.ts +572 -0
  457. package/src/native-ui/renderer-extension/src/renderer-model-picker-positioning.ts +65 -0
  458. package/src/native-ui/renderer-extension/src/renderer-model-picker.ts +640 -0
  459. package/src/native-ui/renderer-extension/src/renderer-new-thread-preference.ts +177 -0
  460. package/src/native-ui/renderer-extension/src/renderer-permission-mode-picker.ts +413 -0
  461. package/src/native-ui/renderer-extension/src/renderer-permission-mode-preference.ts +49 -0
  462. package/src/native-ui/renderer-extension/src/renderer-request-sender.ts +43 -0
  463. package/src/native-ui/renderer-extension/src/renderer-session-import-client.ts +68 -0
  464. package/src/native-ui/renderer-extension/src/renderer-settings-lifecycle.ts +239 -0
  465. package/src/native-ui/renderer-extension/src/renderer-sidebar-agent-icons.ts +457 -0
  466. package/src/native-ui/renderer-extension/src/renderer-transcript-dom.ts +47 -0
  467. package/src/native-ui/renderer-extension/src/renderer-trigger-chip-style.ts +52 -0
  468. package/src/native-ui/renderer-extension/src/renderer-usage-control.ts +633 -0
  469. package/src/native-ui/renderer-extension/src/settings/accounts-list.ts +236 -0
  470. package/src/native-ui/renderer-extension/src/settings/accounts-page.ts +585 -0
  471. package/src/native-ui/renderer-extension/src/settings/accounts-usage.ts +221 -0
  472. package/src/native-ui/renderer-extension/src/settings/accounts.css +683 -0
  473. package/src/native-ui/renderer-extension/src/settings/connections-page.ts +1394 -0
  474. package/src/native-ui/renderer-extension/src/settings/core.ts +158 -0
  475. package/src/native-ui/renderer-extension/src/settings/harness-accounts.ts +195 -0
  476. package/src/native-ui/renderer-extension/src/settings/icons.ts +141 -0
  477. package/src/native-ui/renderer-extension/src/settings/localization.ts +800 -0
  478. package/src/native-ui/renderer-extension/src/settings/pages.ts +632 -0
  479. package/src/native-ui/renderer-extension/src/settings/release-notes.ts +222 -0
  480. package/src/native-ui/renderer-extension/src/settings/session-import-list-controls.ts +128 -0
  481. package/src/native-ui/renderer-extension/src/settings/session-import-page.ts +461 -0
  482. package/src/native-ui/renderer-extension/src/settings/shell.css +2238 -0
  483. package/src/native-ui/renderer-extension/src/settings/shell.ts +329 -0
  484. package/src/native-ui/renderer-extension/src/settings/trigger.ts +323 -0
  485. package/src/native-ui/renderer-extension/src/settings/update-request.ts +49 -0
  486. package/src/native-ui/renderer-extension/src/versioned-renderer-adapter.ts +1289 -0
  487. package/src/native-ui/renderer-extension/test/agent-selection-state.test.ts +611 -0
  488. package/src/native-ui/renderer-extension/test/codebuddy-migration.test.ts +22 -0
  489. package/src/native-ui/renderer-extension/test/codex-locale-adapter.test.ts +152 -0
  490. package/src/native-ui/renderer-extension/test/contract-audit.test.ts +71 -0
  491. package/src/native-ui/renderer-extension/test/harness-mix-icons.test.ts +58 -0
  492. package/src/native-ui/renderer-extension/test/index.test.ts +9 -0
  493. package/src/native-ui/renderer-extension/test/renderer-agent-icon.test.ts +98 -0
  494. package/src/native-ui/renderer-extension/test/renderer-agent-picker.test.ts +226 -0
  495. package/src/native-ui/renderer-extension/test/renderer-binding-probe-host-catalog.test.ts +728 -0
  496. package/src/native-ui/renderer-extension/test/renderer-binding-probe.test.ts +1290 -0
  497. package/src/native-ui/renderer-extension/test/renderer-codex-account-state.test.ts +65 -0
  498. package/src/native-ui/renderer-extension/test/renderer-collab-cards.test.ts +45 -0
  499. package/src/native-ui/renderer-extension/test/renderer-composer-permissions.test.ts +75 -0
  500. package/src/native-ui/renderer-extension/test/renderer-credits-control.test.ts +70 -0
  501. package/src/native-ui/renderer-extension/test/renderer-external-steering-rpc.test.ts +184 -0
  502. package/src/native-ui/renderer-extension/test/renderer-external-steering.test.ts +279 -0
  503. package/src/native-ui/renderer-extension/test/renderer-fork-control.test.ts +363 -0
  504. package/src/native-ui/renderer-extension/test/renderer-harness-command-claim.test.ts +167 -0
  505. package/src/native-ui/renderer-extension/test/renderer-harness-command-control.test.ts +54 -0
  506. package/src/native-ui/renderer-extension/test/renderer-model-client.test.ts +709 -0
  507. package/src/native-ui/renderer-extension/test/renderer-model-picker.test.ts +302 -0
  508. package/src/native-ui/renderer-extension/test/renderer-native-thread-ownership.test.ts +84 -0
  509. package/src/native-ui/renderer-extension/test/renderer-new-thread-preference.test.ts +81 -0
  510. package/src/native-ui/renderer-extension/test/renderer-permission-mode-picker.test.ts +269 -0
  511. package/src/native-ui/renderer-extension/test/renderer-permission-mode-preference.test.ts +48 -0
  512. package/src/native-ui/renderer-extension/test/renderer-settings-lifecycle.test.ts +156 -0
  513. package/src/native-ui/renderer-extension/test/renderer-sidebar-agent-icons.test.ts +575 -0
  514. package/src/native-ui/renderer-extension/test/renderer-unsupported-methods.test.ts +167 -0
  515. package/src/native-ui/renderer-extension/test/renderer-usage-control.test.ts +96 -0
  516. package/src/native-ui/renderer-extension/test/settings/accounts-usage.test.ts +208 -0
  517. package/src/native-ui/renderer-extension/test/settings/core.test.ts +145 -0
  518. package/src/native-ui/renderer-extension/test/settings/harness-settings.test.ts +92 -0
  519. package/src/native-ui/renderer-extension/test/settings/localization.test.ts +80 -0
  520. package/src/native-ui/renderer-extension/test/settings/pages.test.ts +2000 -0
  521. package/src/native-ui/renderer-extension/test/settings/release-notes.test.ts +163 -0
  522. package/src/native-ui/renderer-extension/test/settings/shell.test.ts +74 -0
  523. package/src/native-ui/renderer-extension/test/settings/trigger.test.ts +270 -0
  524. package/src/native-ui/renderer-extension/test/settings/update-request.test.ts +48 -0
  525. package/src/native-ui/renderer-extension/test/versioned-renderer-adapter.test.ts +695 -0
  526. package/src/native-ui/renderer-extension/tsconfig.json +15 -0
  527. package/src/native-ui/shared-contracts/dist/codex-accounts.d.ts +246 -0
  528. package/src/native-ui/shared-contracts/dist/codex-accounts.d.ts.map +1 -0
  529. package/src/native-ui/shared-contracts/dist/codex-accounts.js +99 -0
  530. package/src/native-ui/shared-contracts/dist/codex-accounts.js.map +1 -0
  531. package/src/native-ui/shared-contracts/dist/deepseek-modern-sessions.d.ts +37 -0
  532. package/src/native-ui/shared-contracts/dist/deepseek-modern-sessions.d.ts.map +1 -0
  533. package/src/native-ui/shared-contracts/dist/deepseek-modern-sessions.js +36 -0
  534. package/src/native-ui/shared-contracts/dist/deepseek-modern-sessions.js.map +1 -0
  535. package/src/native-ui/shared-contracts/dist/errors.d.ts +17 -0
  536. package/src/native-ui/shared-contracts/dist/errors.d.ts.map +1 -0
  537. package/src/native-ui/shared-contracts/dist/errors.js +14 -0
  538. package/src/native-ui/shared-contracts/dist/errors.js.map +1 -0
  539. package/src/native-ui/shared-contracts/dist/external-thread-fork.d.ts +11 -0
  540. package/src/native-ui/shared-contracts/dist/external-thread-fork.d.ts.map +1 -0
  541. package/src/native-ui/shared-contracts/dist/external-thread-fork.js +14 -0
  542. package/src/native-ui/shared-contracts/dist/external-thread-fork.js.map +1 -0
  543. package/src/native-ui/shared-contracts/dist/harness-accounts.d.ts +78 -0
  544. package/src/native-ui/shared-contracts/dist/harness-accounts.d.ts.map +1 -0
  545. package/src/native-ui/shared-contracts/dist/harness-accounts.js +27 -0
  546. package/src/native-ui/shared-contracts/dist/harness-accounts.js.map +1 -0
  547. package/src/native-ui/shared-contracts/dist/harness-commands.d.ts +46 -0
  548. package/src/native-ui/shared-contracts/dist/harness-commands.d.ts.map +1 -0
  549. package/src/native-ui/shared-contracts/dist/harness-commands.js +61 -0
  550. package/src/native-ui/shared-contracts/dist/harness-commands.js.map +1 -0
  551. package/src/native-ui/shared-contracts/dist/harness-models.d.ts +300 -0
  552. package/src/native-ui/shared-contracts/dist/harness-models.d.ts.map +1 -0
  553. package/src/native-ui/shared-contracts/dist/harness-models.js +318 -0
  554. package/src/native-ui/shared-contracts/dist/harness-models.js.map +1 -0
  555. package/src/native-ui/shared-contracts/dist/harness-permission-modes.d.ts +30 -0
  556. package/src/native-ui/shared-contracts/dist/harness-permission-modes.d.ts.map +1 -0
  557. package/src/native-ui/shared-contracts/dist/harness-permission-modes.js +57 -0
  558. package/src/native-ui/shared-contracts/dist/harness-permission-modes.js.map +1 -0
  559. package/src/native-ui/shared-contracts/dist/harness-plugins.d.ts +56 -0
  560. package/src/native-ui/shared-contracts/dist/harness-plugins.d.ts.map +1 -0
  561. package/src/native-ui/shared-contracts/dist/harness-plugins.js +75 -0
  562. package/src/native-ui/shared-contracts/dist/harness-plugins.js.map +1 -0
  563. package/src/native-ui/shared-contracts/dist/harness-route.d.ts +16 -0
  564. package/src/native-ui/shared-contracts/dist/harness-route.d.ts.map +1 -0
  565. package/src/native-ui/shared-contracts/dist/harness-route.js +42 -0
  566. package/src/native-ui/shared-contracts/dist/harness-route.js.map +1 -0
  567. package/src/native-ui/shared-contracts/dist/harness-session-import.d.ts +54 -0
  568. package/src/native-ui/shared-contracts/dist/harness-session-import.d.ts.map +1 -0
  569. package/src/native-ui/shared-contracts/dist/harness-session-import.js +72 -0
  570. package/src/native-ui/shared-contracts/dist/harness-session-import.js.map +1 -0
  571. package/src/native-ui/shared-contracts/dist/ids.d.ts +12 -0
  572. package/src/native-ui/shared-contracts/dist/ids.d.ts.map +1 -0
  573. package/src/native-ui/shared-contracts/dist/ids.js +10 -0
  574. package/src/native-ui/shared-contracts/dist/ids.js.map +1 -0
  575. package/src/native-ui/shared-contracts/dist/index.d.ts +47 -0
  576. package/src/native-ui/shared-contracts/dist/index.d.ts.map +1 -0
  577. package/src/native-ui/shared-contracts/dist/index.js +29 -0
  578. package/src/native-ui/shared-contracts/dist/index.js.map +1 -0
  579. package/src/native-ui/shared-contracts/dist/json-rpc.d.ts +84 -0
  580. package/src/native-ui/shared-contracts/dist/json-rpc.d.ts.map +1 -0
  581. package/src/native-ui/shared-contracts/dist/json-rpc.js +65 -0
  582. package/src/native-ui/shared-contracts/dist/json-rpc.js.map +1 -0
  583. package/src/native-ui/shared-contracts/dist/json-value.d.ts +13 -0
  584. package/src/native-ui/shared-contracts/dist/json-value.d.ts.map +1 -0
  585. package/src/native-ui/shared-contracts/dist/json-value.js +60 -0
  586. package/src/native-ui/shared-contracts/dist/json-value.js.map +1 -0
  587. package/src/native-ui/shared-contracts/dist/native-refs.d.ts +43 -0
  588. package/src/native-ui/shared-contracts/dist/native-refs.d.ts.map +1 -0
  589. package/src/native-ui/shared-contracts/dist/native-refs.js +35 -0
  590. package/src/native-ui/shared-contracts/dist/native-refs.js.map +1 -0
  591. package/src/native-ui/shared-contracts/dist/reasoning-transcript.d.ts +7 -0
  592. package/src/native-ui/shared-contracts/dist/reasoning-transcript.d.ts.map +1 -0
  593. package/src/native-ui/shared-contracts/dist/reasoning-transcript.js +7 -0
  594. package/src/native-ui/shared-contracts/dist/reasoning-transcript.js.map +1 -0
  595. package/src/native-ui/shared-contracts/dist/thread-delegate.d.ts +22 -0
  596. package/src/native-ui/shared-contracts/dist/thread-delegate.d.ts.map +1 -0
  597. package/src/native-ui/shared-contracts/dist/thread-delegate.js +30 -0
  598. package/src/native-ui/shared-contracts/dist/thread-delegate.js.map +1 -0
  599. package/src/native-ui/shared-contracts/dist/thread-harness-switch.d.ts +16 -0
  600. package/src/native-ui/shared-contracts/dist/thread-harness-switch.d.ts.map +1 -0
  601. package/src/native-ui/shared-contracts/dist/thread-harness-switch.js +19 -0
  602. package/src/native-ui/shared-contracts/dist/thread-harness-switch.js.map +1 -0
  603. package/src/native-ui/shared-contracts/dist/thread-usage.d.ts +106 -0
  604. package/src/native-ui/shared-contracts/dist/thread-usage.d.ts.map +1 -0
  605. package/src/native-ui/shared-contracts/dist/thread-usage.js +103 -0
  606. package/src/native-ui/shared-contracts/dist/thread-usage.js.map +1 -0
  607. package/src/native-ui/shared-contracts/dist/tsconfig.tsbuildinfo +1 -0
  608. package/src/native-ui/shared-contracts/dist/updates.d.ts +128 -0
  609. package/src/native-ui/shared-contracts/dist/updates.d.ts.map +1 -0
  610. package/src/native-ui/shared-contracts/dist/updates.js +58 -0
  611. package/src/native-ui/shared-contracts/dist/updates.js.map +1 -0
  612. package/src/native-ui/shared-contracts/dist/version.d.ts +2 -0
  613. package/src/native-ui/shared-contracts/dist/version.d.ts.map +1 -0
  614. package/src/native-ui/shared-contracts/dist/version.js +2 -0
  615. package/src/native-ui/shared-contracts/dist/version.js.map +1 -0
  616. package/src/native-ui/shared-contracts/package.json +25 -0
  617. package/src/native-ui/shared-contracts/src/codex-accounts.ts +140 -0
  618. package/src/native-ui/shared-contracts/src/deepseek-modern-sessions.ts +67 -0
  619. package/src/native-ui/shared-contracts/src/errors.ts +25 -0
  620. package/src/native-ui/shared-contracts/src/external-thread-fork.ts +20 -0
  621. package/src/native-ui/shared-contracts/src/harness-accounts.ts +32 -0
  622. package/src/native-ui/shared-contracts/src/harness-commands.ts +81 -0
  623. package/src/native-ui/shared-contracts/src/harness-models.ts +406 -0
  624. package/src/native-ui/shared-contracts/src/harness-permission-modes.ts +73 -0
  625. package/src/native-ui/shared-contracts/src/harness-plugins.ts +96 -0
  626. package/src/native-ui/shared-contracts/src/harness-route.ts +47 -0
  627. package/src/native-ui/shared-contracts/src/harness-session-import.ts +90 -0
  628. package/src/native-ui/shared-contracts/src/ids.ts +20 -0
  629. package/src/native-ui/shared-contracts/src/index.ts +320 -0
  630. package/src/native-ui/shared-contracts/src/json-rpc.ts +80 -0
  631. package/src/native-ui/shared-contracts/src/json-value.ts +78 -0
  632. package/src/native-ui/shared-contracts/src/native-refs.ts +58 -0
  633. package/src/native-ui/shared-contracts/src/reasoning-transcript.ts +6 -0
  634. package/src/native-ui/shared-contracts/src/thread-delegate.ts +40 -0
  635. package/src/native-ui/shared-contracts/src/thread-harness-switch.ts +25 -0
  636. package/src/native-ui/shared-contracts/src/thread-usage.ts +126 -0
  637. package/src/native-ui/shared-contracts/src/updates.ts +78 -0
  638. package/src/native-ui/shared-contracts/src/version.ts +1 -0
  639. package/src/native-ui/shared-contracts/test/browser-bundle.test.ts +36 -0
  640. package/src/native-ui/shared-contracts/test/deepseek-modern-sessions.test.ts +120 -0
  641. package/src/native-ui/shared-contracts/test/errors.test.ts +72 -0
  642. package/src/native-ui/shared-contracts/test/harness-commands.test.ts +64 -0
  643. package/src/native-ui/shared-contracts/test/harness-models.test.ts +390 -0
  644. package/src/native-ui/shared-contracts/test/harness-permission-modes.test.ts +134 -0
  645. package/src/native-ui/shared-contracts/test/harness-plugins.test.ts +113 -0
  646. package/src/native-ui/shared-contracts/test/harness-session-import.test.ts +117 -0
  647. package/src/native-ui/shared-contracts/test/ids.test.ts +69 -0
  648. package/src/native-ui/shared-contracts/test/index.test.ts +80 -0
  649. package/src/native-ui/shared-contracts/test/json-rpc.test.ts +177 -0
  650. package/src/native-ui/shared-contracts/test/json-value.test.ts +62 -0
  651. package/src/native-ui/shared-contracts/test/native-refs.test.ts +95 -0
  652. package/src/native-ui/shared-contracts/test/thread-usage.test.ts +115 -0
  653. package/src/native-ui/shared-contracts/test/updates.test.ts +120 -0
  654. package/src/native-ui/shared-contracts/tsconfig.json +10 -0
  655. package/src/tsconfig.base.json +19 -0
@@ -0,0 +1,3002 @@
1
+ import {
2
+ decodeHarnessPluginRoute,
3
+ harnessIdSchema,
4
+ hostThreadIdSchema,
5
+ permissionModeFixedAtCreate,
6
+ type HarnessCommandDescriptor,
7
+ type HarnessModelCatalog,
8
+ type HarnessModelRef,
9
+ type HarnessModelSelectionState,
10
+ type HarnessPermissionModeCatalog,
11
+ type HarnessPermissionModeId,
12
+ type HarnessPermissionModeScope,
13
+ type HarnessThinkingOptionId,
14
+ type AccountCreditsSnapshot,
15
+ type ThreadInspection,
16
+ type ThreadUsageInspection,
17
+ type ThreadUsageSnapshot,
18
+ type CodexhostError,
19
+ } from "@codexhost/shared-contracts";
20
+
21
+ import {
22
+ DEFAULT_RENDERER_AGENTS,
23
+ DraftAgentController,
24
+ type ComposerAgentPhase,
25
+ type ExternalRendererAgent,
26
+ type RendererAgent,
27
+ type RendererAgentAvailability,
28
+ } from "./agent-selection-state.js";
29
+ import {
30
+ CODEX_COMPOSER_SELECTOR,
31
+ EDITOR_SELECTOR,
32
+ composerForEditor,
33
+ composerForElement,
34
+ disposeComposerAgentControl,
35
+ editorForElement,
36
+ eventElement,
37
+ isComposerInputIntent,
38
+ isComposerSubmissionKey,
39
+ mountComposerAgentControl,
40
+ reconcileComposerNativeControls,
41
+ renderComposerAgentControl,
42
+ sendButtonWithin,
43
+ type ComposerAgentControl,
44
+ type ExternalModelControlView,
45
+ type ExternalPermissionModeControlView,
46
+ } from "./renderer-composer-dom.js";
47
+ import { rendererHarnessMessages } from "./renderer-harness-localization.js";
48
+ import { RendererCodexAccountState } from "./renderer-codex-account-state.js";
49
+ import {
50
+ decodeAntigravityTransportModelId,
51
+ decodeClaudeTransportModelId,
52
+ decodeDeepSeekHarnessTransportModelId,
53
+ decodeGrokTransportModelId,
54
+ decodeOmpTransportModelId,
55
+ decodeOpenCodeTransportModelId,
56
+ decodePiTransportModelId,
57
+ findComposerModelTarget,
58
+ threadIdFromComposerModelTarget,
59
+ waitForRendererDraftPrewarmPolicy,
60
+ type LockedComposerSelection,
61
+ type RendererAdapterStatus,
62
+ } from "./versioned-renderer-adapter.js";
63
+ import type { RendererModelClient } from "./renderer-model-client.js";
64
+ import { RendererMethodUnavailableError } from "./renderer-request-sender.js";
65
+ import { thinkingOptionsForModel } from "./renderer-model-picker.js";
66
+ import { RENDERER_AGENT_INSTALL_URLS } from "./renderer-agent-picker.js";
67
+ import {
68
+ readClaudePermissionModePreference,
69
+ writeClaudePermissionModePreference,
70
+ } from "./renderer-permission-mode-preference.js";
71
+ import { isPermissionModeControlReady } from "./renderer-permission-mode-picker.js";
72
+ import {
73
+ readNewThreadAgentPreference,
74
+ readNewThreadExternalConfigurationPreference,
75
+ writeNewThreadAgentPreference,
76
+ writeNewThreadExternalConfigurationPreference,
77
+ } from "./renderer-new-thread-preference.js";
78
+ import { installRendererSidebarAgentIcons } from "./renderer-sidebar-agent-icons.js";
79
+ import { installHarnessMentions } from './renderer-harness-mentions.js';
80
+ import { installCollabCards } from './renderer-collab-cards.js';
81
+ import {
82
+ rendererHarnessCommandExecutesDirectly,
83
+ routeRendererHarnessCommandSelection,
84
+ } from "./renderer-harness-command-claim.js";
85
+ import { installRendererSettingsLifecycle } from "./harness-mix-settings.js";
86
+ import { openRendererThread } from "./renderer-fork-control.js";
87
+ import type {
88
+ RendererConnectionDiagnostics,
89
+ RendererConnectionSnapshot,
90
+ } from "./settings/pages.js";
91
+
92
+ const externalHarnessIds = {
93
+ pi: harnessIdSchema.parse("pi"),
94
+ "claude-code": harnessIdSchema.parse("claude-code"),
95
+ "deepseek-harness": harnessIdSchema.parse("deepseek-harness"),
96
+ opencode: harnessIdSchema.parse("opencode"),
97
+ grok: harnessIdSchema.parse("grok"),
98
+ omp: harnessIdSchema.parse("omp"),
99
+ antigravity: harnessIdSchema.parse("antigravity"),
100
+ "kiro-cli": harnessIdSchema.parse("kiro-cli"),
101
+ openclaw: harnessIdSchema.parse("openclaw"),
102
+ hermes: harnessIdSchema.parse("hermes"),
103
+ qoder: harnessIdSchema.parse("qoder"),
104
+ codebuddy: harnessIdSchema.parse("codebuddy"),
105
+ zcode: harnessIdSchema.parse("zcode"),
106
+ trae: harnessIdSchema.parse("trae"),
107
+ 'cursor-cli': harnessIdSchema.parse("cursor-cli"),
108
+ 'codex-harness': harnessIdSchema.parse('codex-harness'),
109
+ } as const;
110
+
111
+ const externalAgents: readonly ExternalRendererAgent[] = [
112
+ "pi",
113
+ "claude-code",
114
+ "deepseek-harness",
115
+ "opencode",
116
+ "grok",
117
+ "omp",
118
+ "antigravity",
119
+ "kiro-cli",
120
+ "openclaw",
121
+ "hermes",
122
+ "qoder",
123
+ "codebuddy",
124
+ "zcode",
125
+ "trae",
126
+ "cursor-cli",
127
+ 'codex-harness',
128
+ ];
129
+ type HarnessAvailability = Partial<Record<ExternalRendererAgent, RendererAgentAvailability>>;
130
+ type HarnessAvailabilityErrors = Record<ExternalRendererAgent, CodexhostError | undefined>;
131
+ type HarnessWebUiAvailability = Record<ExternalRendererAgent, boolean>;
132
+
133
+ function isRetryableHarnessAvailability(
134
+ availability: RendererAgentAvailability | undefined,
135
+ error: CodexhostError | undefined,
136
+ ): boolean {
137
+ return (
138
+ availability !== undefined &&
139
+ availability !== "ready" &&
140
+ availability !== "notInstalled" &&
141
+ error?.retryable === true
142
+ );
143
+ }
144
+
145
+ export function retryableHarnessAvailabilityAgents(
146
+ availability: HarnessAvailability,
147
+ errors: HarnessAvailabilityErrors,
148
+ ): ExternalRendererAgent[] {
149
+ return externalAgents.filter((agent) =>
150
+ isRetryableHarnessAvailability(availability[agent], errors[agent]),
151
+ );
152
+ }
153
+
154
+ export function passiveHarnessAvailabilityAgents(
155
+ availability: HarnessAvailability,
156
+ errors: HarnessAvailabilityErrors,
157
+ ): ExternalRendererAgent[] {
158
+ return externalAgents.filter(
159
+ (agent) =>
160
+ availability[agent] === "checking" ||
161
+ isRetryableHarnessAvailability(availability[agent], errors[agent]),
162
+ );
163
+ }
164
+
165
+ /** Last known availability stays visible while inspect or retry is in flight. */
166
+ export function harnessAvailabilityDuringInspect(
167
+ current: RendererAgentAvailability | undefined,
168
+ ): RendererAgentAvailability {
169
+ return current ?? "checking";
170
+ }
171
+
172
+ export function shouldRefreshCodexAccountsForAdapterState(
173
+ state: RendererAdapterStatus["state"],
174
+ ): boolean {
175
+ return state === "ready";
176
+ }
177
+
178
+ export { resolveCodexAccountSelection } from "./renderer-codex-account-state.js";
179
+
180
+ interface HostHarnessAvailabilityState {
181
+ codexAccounts: RendererCodexAccountState | null;
182
+ availability: HarnessAvailability;
183
+ errors: HarnessAvailabilityErrors;
184
+ webUi: HarnessWebUiAvailability;
185
+ requestGeneration: number;
186
+ request: { client: RendererModelClient; promise: Promise<void> } | null;
187
+ retryTimer: number | null;
188
+ retryAttempt: number;
189
+ }
190
+
191
+ const rendererUsageRefreshDelays = [250, 500, 1000, 2000, 4000, 8000] as const;
192
+
193
+ export function refreshConnectionHosts(
194
+ hostIds: Iterable<string>,
195
+ refreshHost: (hostId: string) => Promise<void>,
196
+ ): Promise<void> {
197
+ return Promise.all([...hostIds].map((hostId) => refreshHost(hostId))).then(() => undefined);
198
+ }
199
+
200
+ export function rendererUsageRefreshDelay(attempt: number): number {
201
+ const index = Math.max(0, Math.min(Math.trunc(attempt), rendererUsageRefreshDelays.length - 1));
202
+ return rendererUsageRefreshDelays[index] ?? rendererUsageRefreshDelays[0];
203
+ }
204
+
205
+ /**
206
+ * Agents that can produce account-wide Credits independently of a Thread
207
+ * Usage snapshot. These are the only ones where it is worth retrying purely
208
+ * to pick up account limits after Usage has already arrived.
209
+ */
210
+ function externalAgentHasAccountCredits(agent: RendererAgent): boolean {
211
+ return (
212
+ agent === "codex" ||
213
+ agent === "grok" ||
214
+ agent === "claude-code" ||
215
+ agent === "antigravity"
216
+ );
217
+ }
218
+
219
+ export function shouldRetryExternalThreadUsage(
220
+ agent: RendererAgent,
221
+ usage: ThreadUsageSnapshot | null,
222
+ accountCredits: AccountCreditsSnapshot | null = null,
223
+ ): boolean {
224
+ if (usage === null) return true;
225
+ return externalAgentHasAccountCredits(agent) && accountCredits === null;
226
+ }
227
+
228
+ export function shouldReloadExternalCatalogAfterAvailabilityRefresh(
229
+ previous: RendererAgentAvailability | undefined,
230
+ next: RendererAgentAvailability,
231
+ configurationReady: boolean,
232
+ explicitRefresh = false,
233
+ ): boolean {
234
+ return explicitRefresh || previous !== next || !configurationReady;
235
+ }
236
+
237
+ function isExternalConfigurationReadyView(
238
+ modelView: ExternalModelControlView,
239
+ permissionModeView: ExternalPermissionModeControlView,
240
+ ): boolean {
241
+ return (
242
+ modelView.status !== "selecting" &&
243
+ modelView.catalog?.models.some((model) => model.ref.id === modelView.selected?.id) === true &&
244
+ isPermissionModeControlReady(permissionModeView)
245
+ );
246
+ }
247
+
248
+ function isExternalConfigurationStable(
249
+ modelView: ExternalModelControlView,
250
+ permissionModeView: ExternalPermissionModeControlView,
251
+ ): boolean {
252
+ return (
253
+ (modelView.status === "empty" && isPermissionModeControlReady(permissionModeView)) ||
254
+ isExternalConfigurationReadyView(modelView, permissionModeView)
255
+ );
256
+ }
257
+
258
+ export interface RendererBindingProbeStatus {
259
+ version: 2;
260
+ mountedComposers: number;
261
+ enabledAgents: RendererAgent[];
262
+ availability: HarnessAvailability;
263
+ selections: Array<{
264
+ composerId: string;
265
+ agent: RendererAgent;
266
+ phase: ComposerAgentPhase;
267
+ }>;
268
+ adapter: RendererAdapterStatus;
269
+ }
270
+
271
+ export interface RendererBindingProbeOptions {
272
+ enabledAgents?: readonly RendererAgent[];
273
+ defaultAgent?: RendererAgent;
274
+ }
275
+
276
+ type ApplyAdapterAgent = (
277
+ agent: RendererAgent,
278
+ model?: HarnessModelRef,
279
+ thinkingOptionId?: HarnessThinkingOptionId,
280
+ permissionModeId?: HarnessPermissionModeId,
281
+ composer?: Element,
282
+ ) => boolean;
283
+
284
+ export interface RendererBindingProbeApi {
285
+ status(): RendererBindingProbeStatus;
286
+ lockedSelection(): LockedComposerSelection | null;
287
+ setAdapter(
288
+ status: RendererAdapterStatus,
289
+ dispose?: () => void,
290
+ applyAgent?: ApplyAdapterAgent,
291
+ modelControl?: RendererModelClient | null,
292
+ ): void;
293
+ dispose(): void;
294
+ }
295
+
296
+ declare global {
297
+ interface Window {
298
+ __codexhostRendererBindingProbeV1?: RendererBindingProbeApi;
299
+ }
300
+ }
301
+
302
+ export type ComposerOwnershipStatus = "not-required" | "loading" | "ready" | "error";
303
+
304
+ export interface RestoredThreadOwnership {
305
+ agent: RendererAgent;
306
+ model?: HarnessModelRef;
307
+ thinkingOptionId?: HarnessThinkingOptionId;
308
+ permissionModeId?: HarnessPermissionModeId;
309
+ }
310
+
311
+ function selectableThinkingOptionId(
312
+ state: HarnessModelSelectionState,
313
+ ): HarnessThinkingOptionId | undefined {
314
+ return state.effectiveThinkingOptionId &&
315
+ state.availableThinkingOptions?.some(({ id }) => id === state.effectiveThinkingOptionId)
316
+ ? state.effectiveThinkingOptionId
317
+ : undefined;
318
+ }
319
+
320
+ export function draftThinkingOptionForModel(
321
+ catalog: HarnessModelCatalog,
322
+ model: HarnessModelRef,
323
+ requested: HarnessThinkingOptionId | undefined,
324
+ ): HarnessThinkingOptionId | undefined {
325
+ const options = thinkingOptionsForModel(catalog, model);
326
+ // 没有显式选择且目录未声明默认档时不预选:让原生会话自身的默认档生效,
327
+ // 避免把目录首档(如 Pi 的 off)静默强加给原生会话。
328
+ return (
329
+ options.find(({ id }) => id === requested)?.id ??
330
+ options.find(({ id }) => id === catalog.defaultThinkingOptionId)?.id
331
+ );
332
+ }
333
+
334
+ export function draftPermissionMode(
335
+ catalog: HarnessPermissionModeCatalog,
336
+ requested: HarnessPermissionModeId | undefined,
337
+ ): HarnessPermissionModeId {
338
+ return (
339
+ catalog.modes.find(({ id }) => id === requested)?.id ??
340
+ catalog.modes.find(({ id }) => id === catalog.defaultModeId)?.id ??
341
+ catalog.defaultModeId
342
+ );
343
+ }
344
+
345
+ export function lockedPermissionMode(
346
+ catalog: HarnessPermissionModeCatalog,
347
+ effective: HarnessPermissionModeId | undefined,
348
+ carrier: HarnessPermissionModeId | undefined,
349
+ ): HarnessPermissionModeId | undefined {
350
+ const restored = effective ?? carrier;
351
+ if (restored && !catalog.modes.some(({ id }) => id === restored)) {
352
+ throw new Error("Existing Thread Permission Mode is absent from the current Catalog");
353
+ }
354
+ return restored;
355
+ }
356
+
357
+ export function permissionModeSelectionLocked(input: {
358
+ phase: ComposerAgentPhase;
359
+ permissionModeScope?: HarnessPermissionModeScope;
360
+ }): boolean {
361
+ return input.phase === "locked" && permissionModeFixedAtCreate(input);
362
+ }
363
+
364
+ export function shouldPersistNewThreadConfigurationSelection(phase: ComposerAgentPhase): boolean {
365
+ return phase === "draft";
366
+ }
367
+
368
+ export function restoredThreadOwnership(inspection: ThreadInspection): RestoredThreadOwnership {
369
+ if (inspection.owner === "codex") return { agent: "codex" };
370
+ if (inspection.harnessId === "pi") {
371
+ const transportSelection = decodePiTransportModelId(inspection.transportModelId);
372
+ if (!transportSelection) {
373
+ throw new Error("Pi Thread reported an incompatible transport Model");
374
+ }
375
+ const model = inspection.effectiveModel ?? transportSelection.model;
376
+ const thinkingOptionId =
377
+ selectableThinkingOptionId(inspection) ?? transportSelection.thinkingOptionId;
378
+ return {
379
+ agent: "pi",
380
+ ...(model ? { model } : {}),
381
+ ...(thinkingOptionId ? { thinkingOptionId } : {}),
382
+ ...(inspection.effectivePermissionModeId
383
+ ? { permissionModeId: inspection.effectivePermissionModeId }
384
+ : {}),
385
+ };
386
+ }
387
+ if (inspection.harnessId === "grok") {
388
+ const transportSelection = decodeGrokTransportModelId(inspection.transportModelId);
389
+ if (!transportSelection) {
390
+ throw new Error("Grok Thread reported an incompatible transport Model");
391
+ }
392
+ const model = inspection.effectiveModel ?? transportSelection.model;
393
+ const thinkingOptionId =
394
+ selectableThinkingOptionId(inspection) ?? transportSelection.thinkingOptionId;
395
+ const permissionModeId =
396
+ inspection.effectivePermissionModeId ?? transportSelection.permissionModeId;
397
+ return {
398
+ agent: "grok",
399
+ ...(model ? { model } : {}),
400
+ ...(thinkingOptionId ? { thinkingOptionId } : {}),
401
+ ...(permissionModeId ? { permissionModeId } : {}),
402
+ };
403
+ }
404
+ if (inspection.harnessId === "omp") {
405
+ const transportSelection = decodeOmpTransportModelId(inspection.transportModelId);
406
+ if (!transportSelection) throw new Error("OMP Thread reported an incompatible transport Model");
407
+ const model = inspection.effectiveModel ?? transportSelection.model;
408
+ const thinkingOptionId =
409
+ selectableThinkingOptionId(inspection) ?? transportSelection.thinkingOptionId;
410
+ const permissionModeId =
411
+ inspection.effectivePermissionModeId ?? transportSelection.permissionModeId;
412
+ return {
413
+ agent: "omp",
414
+ ...(model ? { model } : {}),
415
+ ...(thinkingOptionId ? { thinkingOptionId } : {}),
416
+ ...(permissionModeId ? { permissionModeId } : {}),
417
+ };
418
+ }
419
+ if (inspection.harnessId === "claude-code") {
420
+ const transportSelection = decodeClaudeTransportModelId(inspection.transportModelId);
421
+ if (!transportSelection) {
422
+ throw new Error("Claude Code Thread reported an incompatible transport Model");
423
+ }
424
+ const model = inspection.effectiveModel ?? transportSelection.model;
425
+ const thinkingOptionId =
426
+ selectableThinkingOptionId(inspection) ?? transportSelection.thinkingOptionId;
427
+ const permissionModeId =
428
+ inspection.effectivePermissionModeId ?? transportSelection.permissionModeId;
429
+ return {
430
+ agent: "claude-code",
431
+ ...(model ? { model } : {}),
432
+ ...(thinkingOptionId ? { thinkingOptionId } : {}),
433
+ ...(permissionModeId ? { permissionModeId } : {}),
434
+ };
435
+ }
436
+ if (inspection.harnessId === "deepseek-harness") {
437
+ const transportSelection = decodeDeepSeekHarnessTransportModelId(inspection.transportModelId);
438
+ if (!transportSelection) {
439
+ throw new Error("DeepSeek Harness Thread reported an incompatible transport Model");
440
+ }
441
+ const model = inspection.effectiveModel ?? transportSelection.model;
442
+ const permissionModeId =
443
+ inspection.effectivePermissionModeId ?? transportSelection.permissionModeId;
444
+ return {
445
+ agent: "deepseek-harness",
446
+ ...(model ? { model } : {}),
447
+ ...(permissionModeId ? { permissionModeId } : {}),
448
+ };
449
+ }
450
+ if (inspection.harnessId === "opencode") {
451
+ const transportSelection = decodeOpenCodeTransportModelId(inspection.transportModelId);
452
+ if (!transportSelection) {
453
+ throw new Error("OpenCode Thread reported an incompatible transport Model");
454
+ }
455
+ const model = inspection.effectiveModel ?? transportSelection.model;
456
+ const thinkingOptionId =
457
+ selectableThinkingOptionId(inspection) ?? transportSelection.thinkingOptionId;
458
+ const permissionModeId =
459
+ inspection.effectivePermissionModeId ?? transportSelection.permissionModeId;
460
+ return {
461
+ agent: "opencode",
462
+ ...(model ? { model } : {}),
463
+ ...(thinkingOptionId ? { thinkingOptionId } : {}),
464
+ ...(permissionModeId ? { permissionModeId } : {}),
465
+ };
466
+ }
467
+ if (inspection.harnessId === "antigravity") {
468
+ const transportSelection = decodeAntigravityTransportModelId(inspection.transportModelId);
469
+ if (!transportSelection) {
470
+ throw new Error("Antigravity Thread reported an incompatible transport Model");
471
+ }
472
+ const model = inspection.effectiveModel ?? transportSelection.model;
473
+ const thinkingOptionId =
474
+ selectableThinkingOptionId(inspection) ?? transportSelection.thinkingOptionId;
475
+ const permissionModeId =
476
+ inspection.effectivePermissionModeId ?? transportSelection.permissionModeId;
477
+ return {
478
+ agent: "antigravity",
479
+ ...(model ? { model } : {}),
480
+ ...(thinkingOptionId ? { thinkingOptionId } : {}),
481
+ ...(permissionModeId ? { permissionModeId } : {}),
482
+ };
483
+ }
484
+ if (inspection.harnessId === "kiro-cli") {
485
+ const route = decodeHarnessPluginRoute(inspection.transportModelId);
486
+ if (!route || route.harnessId !== "kiro-cli") {
487
+ throw new Error("Kiro Thread reported an incompatible transport Model");
488
+ }
489
+ const model = inspection.effectiveModel ?? route.model;
490
+ const thinkingOptionId =
491
+ inspection.availableThinkingOptions !== undefined
492
+ ? selectableThinkingOptionId(inspection)
493
+ : (inspection.effectiveThinkingOptionId ?? route.thinkingOptionId);
494
+ const permissionModeId = inspection.effectivePermissionModeId ?? route.permissionModeId;
495
+ return {
496
+ agent: "kiro-cli",
497
+ ...(model ? { model } : {}),
498
+ ...(thinkingOptionId ? { thinkingOptionId } : {}),
499
+ ...(permissionModeId ? { permissionModeId } : {}),
500
+ };
501
+ }
502
+ if (inspection.harnessId === "openclaw" || inspection.harnessId === "hermes" || inspection.harnessId === 'codex-harness' || inspection.harnessId === 'qoder' || inspection.harnessId === 'codebuddy' || inspection.harnessId === 'zcode' || inspection.harnessId === 'trae' || inspection.harnessId === 'cursor-cli') {
503
+ const harnessId = inspection.harnessId;
504
+ const route = decodeHarnessPluginRoute(inspection.transportModelId);
505
+ if (!route || route.harnessId !== harnessId) {
506
+ throw new Error("Thread reported an incompatible transport Model");
507
+ }
508
+ const model = inspection.effectiveModel ?? route.model;
509
+ const thinkingOptionId =
510
+ inspection.availableThinkingOptions !== undefined
511
+ ? selectableThinkingOptionId(inspection)
512
+ : (inspection.effectiveThinkingOptionId ?? route.thinkingOptionId);
513
+ const permissionModeId = inspection.effectivePermissionModeId ?? route.permissionModeId;
514
+ return {
515
+ agent: harnessId,
516
+ ...(model ? { model } : {}),
517
+ ...(thinkingOptionId ? { thinkingOptionId } : {}),
518
+ ...(permissionModeId ? { permissionModeId } : {}),
519
+ };
520
+ }
521
+ throw new Error("Thread owner is not a Renderer Agent");
522
+ }
523
+
524
+ export function isOwnershipSubmissionBlocked(status: ComposerOwnershipStatus): boolean {
525
+ return status === "loading" || status === "error";
526
+ }
527
+
528
+ interface MountedComposer {
529
+ composer: Element;
530
+ composerId: string;
531
+ control: ComposerAgentControl;
532
+ modelTarget: readonly unknown[] | null;
533
+ modelView: ExternalModelControlView;
534
+ permissionModeView: ExternalPermissionModeControlView;
535
+ ownershipStatus: ComposerOwnershipStatus;
536
+ threadConfiguration: HarnessModelSelectionState | undefined;
537
+ usage: ThreadUsageSnapshot | null;
538
+ accountCredits: AccountCreditsSnapshot | null;
539
+ hostId: string | null;
540
+ usageRequestGeneration: number;
541
+ commandRequestGeneration: number;
542
+ }
543
+
544
+ interface PendingComposerReplacement {
545
+ source: MountedComposer;
546
+ sourceModelTarget: readonly unknown[] | null;
547
+ }
548
+
549
+ type SubmissionTrigger = "click" | "enter" | "submit";
550
+
551
+ export function shouldTransferComposerState(
552
+ sourceTarget: readonly unknown[] | null,
553
+ replacementTarget: readonly unknown[] | null,
554
+ sourcePhase: ComposerAgentPhase,
555
+ submissionPending = false,
556
+ ): boolean {
557
+ if (!sourceTarget || !replacementTarget) return false;
558
+ if (sourceTarget === replacementTarget) return true;
559
+ return (
560
+ (sourcePhase === "locked" || submissionPending) &&
561
+ sourceTarget[0] === "default" &&
562
+ replacementTarget[0] === "conversation"
563
+ );
564
+ }
565
+
566
+ export function isLateConversationTarget(
567
+ mountedTarget: readonly unknown[] | null,
568
+ currentTarget: readonly unknown[] | null,
569
+ ): boolean {
570
+ if (currentTarget?.[0] !== "conversation") return false;
571
+ if (mountedTarget === null) return true;
572
+ if (mountedTarget?.[0] === "default") return true;
573
+ if (mountedTarget?.[0] !== "conversation") return false;
574
+ return (
575
+ mountedTarget.length !== currentTarget.length ||
576
+ mountedTarget.some((value, index) => value !== currentTarget[index])
577
+ );
578
+ }
579
+
580
+ export function lateConversationTargetResolution(
581
+ mountedTarget: readonly unknown[] | null,
582
+ currentTarget: readonly unknown[] | null,
583
+ sourcePhase: ComposerAgentPhase,
584
+ submissionPending = false,
585
+ ): "none" | "transfer" | "inspect" {
586
+ if (!isLateConversationTarget(mountedTarget, currentTarget)) return "none";
587
+ return mountedTarget?.[0] === "default" && (sourcePhase === "locked" || submissionPending)
588
+ ? "transfer"
589
+ : "inspect";
590
+ }
591
+
592
+ export function scopedComposerTarget(
593
+ target: readonly unknown[] | null,
594
+ hostId: string | null,
595
+ ): readonly unknown[] | null {
596
+ if (target?.[0] !== "conversation" || !hostId) return target;
597
+ return ["conversation", target[1], hostId];
598
+ }
599
+
600
+ export function isComposerModelWriteAllowed(target: readonly unknown[] | null): boolean {
601
+ return target?.[0] === "default";
602
+ }
603
+
604
+ export function shouldApplyDraftAgentCarrier(
605
+ agent: RendererAgent,
606
+ model: HarnessModelRef | undefined,
607
+ ): boolean {
608
+ return agent === "codex" || model !== undefined;
609
+ }
610
+
611
+ export function applyComposerModelWrite(
612
+ target: readonly unknown[] | null,
613
+ write: () => boolean,
614
+ ): boolean {
615
+ if (target?.[0] === "conversation") return true;
616
+ if (!isComposerModelWriteAllowed(target)) return false;
617
+ return write();
618
+ }
619
+
620
+ function mutationMayChangeComposerTarget(mutation: MutationRecord): boolean {
621
+ const target =
622
+ mutation.target instanceof Element ? mutation.target : mutation.target.parentElement;
623
+ return !target || editorForElement(target) === null;
624
+ }
625
+
626
+ function catalogWithConfigurationState(
627
+ catalog: HarnessModelCatalog,
628
+ model: HarnessModelRef,
629
+ state: HarnessModelSelectionState,
630
+ ): HarnessModelCatalog {
631
+ if (!state.availableThinkingOptions) return catalog;
632
+ const supportedThinkingOptionIds = state.availableThinkingOptions.map(({ id }) => id);
633
+ const models = catalog.models.map((candidate) => {
634
+ const normalized = { ...candidate };
635
+ delete normalized.supportedThinkingOptionIds;
636
+ return candidate.ref.id === model.id
637
+ ? { ...normalized, supportedThinkingOptionIds }
638
+ : normalized;
639
+ });
640
+ const normalized = {
641
+ ...catalog,
642
+ models,
643
+ defaultModel: model,
644
+ thinkingOptions: state.availableThinkingOptions,
645
+ };
646
+ if (state.effectiveThinkingOptionId) {
647
+ normalized.defaultThinkingOptionId = state.effectiveThinkingOptionId;
648
+ } else {
649
+ delete normalized.defaultThinkingOptionId;
650
+ }
651
+ return normalized;
652
+ }
653
+
654
+ export function installRendererBindingProbe(
655
+ options: RendererBindingProbeOptions = {},
656
+ ): RendererBindingProbeApi {
657
+ const existing = window.__codexhostRendererBindingProbeV1;
658
+ if (existing) return existing;
659
+
660
+ const enabledAgents = [...new Set(options.enabledAgents ?? DEFAULT_RENDERER_AGENTS)];
661
+ const enabledAgentSet = new Set(enabledAgents);
662
+ const controller = new DraftAgentController<Element>({
663
+ enabledAgents,
664
+ ...(options.defaultAgent ? { defaultAgent: options.defaultAgent } : {}),
665
+ });
666
+ const mountedByComposer = new Map<Element, MountedComposer>();
667
+ const pendingReplacements = new Map<Element, PendingComposerReplacement>();
668
+ let disposed = false;
669
+ let scanScheduled = false;
670
+ let refreshTargetsOnNextScan = false;
671
+ let adapterDispose: (() => void) | null = null;
672
+ let applyAdapterAgent: ApplyAdapterAgent | null = null;
673
+ let modelControl: RendererModelClient | null = null;
674
+ const activeModelHostId = (): string | null => {
675
+ if (!modelControl) return null;
676
+ return modelControl.currentHostId ? modelControl.currentHostId() : "local";
677
+ };
678
+ const controllerTarget = (target: readonly unknown[] | null, hostId: string | null) =>
679
+ scopedComposerTarget(target, hostId);
680
+ let usageNotificationDispose: (() => void) | null = null;
681
+ const localAgentForSidebarThread = (input: {
682
+ hostId: string;
683
+ threadId: string | null;
684
+ draftId: string | null;
685
+ }): RendererAgent | null => {
686
+ const mountedHostId = modelControl?.currentHostId?.() ?? "local";
687
+ if (input.hostId !== mountedHostId) return null;
688
+ for (const mounted of mountedByComposer.values()) {
689
+ const target = mounted.modelTarget;
690
+ if (target?.[0] === "default" && input.draftId !== null && target[1] === input.draftId) {
691
+ return controller.get(mounted.composer).agent;
692
+ }
693
+ if (
694
+ target?.[0] === "conversation" &&
695
+ input.threadId !== null &&
696
+ target[1] === input.threadId &&
697
+ mounted.ownershipStatus === "ready"
698
+ ) {
699
+ return controller.get(mounted.composer).agent;
700
+ }
701
+ }
702
+ return null;
703
+ };
704
+ const sidebarAgentIcons = installRendererSidebarAgentIcons({
705
+ getClient: (hostId) => modelClientForHost(hostId),
706
+ getLocalAgent: localAgentForSidebarThread,
707
+ });
708
+ const harnessMentions = installHarnessMentions(async (editor, query) => {
709
+ const composer = editor.closest('[data-codex-composer-root]');
710
+ if (!composer) return { agents: [], sessions: [] };
711
+ const state = controller.get(composer);
712
+ // Stock Codex tasks bypass HostRuntime; do not advertise Host tools there.
713
+ if (state.agent === 'codex') return { agents: [], sessions: [] };
714
+ const client = modelClientForHost(modelControl?.currentHostId?.() ?? 'local');
715
+ const [agentResult, sessionResult] = await Promise.allSettled([
716
+ client?.listCollaborationAgents?.() ?? Promise.resolve([]),
717
+ client?.listHarnessSessions?.({ harnessId: harnessIdSchema.parse('all-harnesses'), query, offset: 0, limit: 12 }) ?? Promise.resolve({ candidates: [], total: 0 }),
718
+ ]);
719
+ const agents = agentResult.status === 'fulfilled' ? agentResult.value : [];
720
+ const sessions = sessionResult.status === 'fulfilled' ? sessionResult.value.candidates.map(candidate => {
721
+ const match = /^\[([^\]]+)\]\s*/.exec(candidate.title ?? '');
722
+ return { id: candidate.nativeSessionId, title: (candidate.title ?? '未命名会话').replace(/^\[[^\]]+\]\s*/, ''), harnessId: match?.[1] ?? 'codex', cwd: candidate.cwd, running: candidate.running };
723
+ }) : [];
724
+ return { agents, sessions, canDelegate: agents.some(agent => agent.id === state.agent && agent.lead) };
725
+ });
726
+ const collabCards = installCollabCards({
727
+ openThread: (threadId) => openRendererThread(threadId, { hostId: 'local' }),
728
+ reviewWorkspace: async (threadId) => {
729
+ const client = modelClientForHost('local');
730
+ return (client as any)?.reviewThreadWorkspace?.({ threadId }) ?? { patch: '', digest: '' };
731
+ },
732
+ applyWorkspace: async (threadId, digest) => {
733
+ const client = modelClientForHost('local');
734
+ return (client as any)?.applyThreadWorkspace?.({ threadId, digest }) ?? { patch: '', digest: '' };
735
+ },
736
+ });
737
+ let connectionDiagnostics: RendererConnectionDiagnostics | null = null;
738
+ const settingsLifecycle = installRendererSettingsLifecycle(window, {
739
+ getUpdateClient: () => modelControl,
740
+ getAccountClient: () => modelControl,
741
+ getConnectionDiagnostics: () => connectionDiagnostics,
742
+ getSessionImportClient: () => {
743
+ const client = modelClientForHost("local");
744
+ const sources = client?.listSessionImportSources;
745
+ const list = client?.listHarnessSessions;
746
+ const importSession = client?.importHarnessSession;
747
+ if (!sources || !list || !importSession) return null;
748
+ return {
749
+ listSessionImportSources: () => sources(),
750
+ listHarnessSessions: (input) => list(input),
751
+ importHarnessSession: (input) => importSession(input),
752
+ };
753
+ },
754
+ openImportedThread: (threadId, signal) =>
755
+ openRendererThread(threadId, { hostId: "local", signal }),
756
+ onLocaleChange() {
757
+ for (const mounted of mountedByComposer.values()) renderMounted(mounted);
758
+ },
759
+ });
760
+ let adapterStatus: RendererAdapterStatus = {
761
+ state: "installing",
762
+ reason: "installing",
763
+ modelUpdates: 0,
764
+ hook: null,
765
+ };
766
+ const createHostHarnessAvailabilityState = (): HostHarnessAvailabilityState => ({
767
+ codexAccounts: null,
768
+ availability: Object.fromEntries(
769
+ externalAgents.map((agent) => [agent, "checking"]),
770
+ ) as HarnessAvailability,
771
+ errors: {
772
+ pi: undefined,
773
+ "claude-code": undefined,
774
+ "deepseek-harness": undefined,
775
+ opencode: undefined,
776
+ grok: undefined,
777
+ omp: undefined,
778
+ antigravity: undefined,
779
+ "kiro-cli": undefined,
780
+ openclaw: undefined,
781
+ hermes: undefined,
782
+ qoder: undefined,
783
+ codebuddy: undefined,
784
+ zcode: undefined,
785
+ trae: undefined,
786
+ 'cursor-cli': undefined,
787
+ 'codex-harness': undefined,
788
+ },
789
+ webUi: Object.fromEntries(
790
+ externalAgents.map((agent) => [agent, false]),
791
+ ) as HarnessWebUiAvailability,
792
+ requestGeneration: 0,
793
+ request: null,
794
+ retryTimer: null,
795
+ retryAttempt: 0,
796
+ });
797
+ const harnessAvailabilityByHost = new Map<string, HostHarnessAvailabilityState>();
798
+ const hostHarnessAvailabilityState = (hostId: string): HostHarnessAvailabilityState => {
799
+ let state = harnessAvailabilityByHost.get(hostId);
800
+ if (!state) {
801
+ state = createHostHarnessAvailabilityState();
802
+ harnessAvailabilityByHost.set(hostId, state);
803
+ }
804
+ return state;
805
+ };
806
+ const codexAccountsForHost = (hostId: string | null): RendererCodexAccountState | null => {
807
+ if (!hostId) return null;
808
+ const client = modelClientForHost(hostId);
809
+ if (!client) return null;
810
+ const state = hostHarnessAvailabilityState(hostId);
811
+ if (state.codexAccounts?.client !== client) {
812
+ state.codexAccounts = new RendererCodexAccountState(client);
813
+ }
814
+ return state.codexAccounts;
815
+ };
816
+ const composerCodexAccounts = (composer: Element): RendererCodexAccountState | null =>
817
+ codexAccountsForHost(mountedByComposer.get(composer)?.hostId ?? null);
818
+ let activeAvailabilityHostId = "local";
819
+ const activeHarnessAvailabilityState = (): HostHarnessAvailabilityState =>
820
+ hostHarnessAvailabilityState(activeAvailabilityHostId);
821
+ const connectionListeners = new Set<() => void>();
822
+ const publishConnectionStatus = (): void => {
823
+ for (const listener of connectionListeners) listener();
824
+ };
825
+ const availabilityRetryDelays = [500, 1000, 2000, 4000, 8000] as const;
826
+ const usageRefreshTimers = new Map<Element, number>();
827
+ const usageRefreshAttempts = new Map<Element, number>();
828
+
829
+ const isMountedComposer = (composer: Element): boolean =>
830
+ composer.isConnected &&
831
+ composer.matches(CODEX_COMPOSER_SELECTOR) &&
832
+ mountedByComposer.has(composer);
833
+
834
+ const isCurrentModelRequest = (mounted: MountedComposer, generation: number): boolean =>
835
+ isMountedComposer(mounted.composer) &&
836
+ mountedByComposer.get(mounted.composer) === mounted &&
837
+ controller.isCurrentModelRequest(mounted.composer, generation);
838
+
839
+ const isCurrentOwnershipRequest = (mounted: MountedComposer, generation: number): boolean =>
840
+ mounted.composer.isConnected &&
841
+ mountedByComposer.get(mounted.composer) === mounted &&
842
+ controller.isCurrentOwnershipRequest(mounted.composer, generation);
843
+
844
+ const notifySubmission = (composer: Element, trigger: SubmissionTrigger): void => {
845
+ const accounts = composerCodexAccounts(composer);
846
+ const state = controller.recordSubmission(
847
+ composer,
848
+ accounts?.selection.selectedAccountId ?? undefined,
849
+ );
850
+ if (state.agent === "codex" && accounts) accounts.overrideAccountId = null;
851
+ writeNewThreadAgentPreference(state.agent);
852
+ if (state.agent !== "codex") {
853
+ const model = controller.modelForAgent(composer, state.agent);
854
+ if (model) {
855
+ writeNewThreadExternalConfigurationPreference(
856
+ state.agent,
857
+ model,
858
+ controller.thinkingOptionForAgent(composer, state.agent),
859
+ controller.permissionModeForAgent(composer, state.agent),
860
+ );
861
+ }
862
+ }
863
+ window.dispatchEvent(
864
+ new CustomEvent("codexhost:renderer-submission", {
865
+ detail: {
866
+ composerId: state.composerId,
867
+ agent: state.agent,
868
+ trigger,
869
+ },
870
+ }),
871
+ );
872
+ };
873
+
874
+ const renderMounted = (mounted: MountedComposer): void => {
875
+ const state = controller.get(mounted.composer);
876
+ const accounts = composerCodexAccounts(mounted.composer);
877
+ const selectedCodexAccountId =
878
+ state.phase === "locked" ||
879
+ (controller.isSubmissionPending(mounted.composer) && state.codexAccountId)
880
+ ? state.codexAccountId
881
+ : accounts?.selection.selectedAccountId;
882
+ renderComposerAgentControl(
883
+ mounted.control,
884
+ controller.get(mounted.composer),
885
+ adapterStatus.state,
886
+ accounts?.switching === true ||
887
+ controller.isSwitching(mounted.composer) ||
888
+ mounted.ownershipStatus === "loading",
889
+ activeHarnessAvailabilityState().availability,
890
+ mounted.modelView,
891
+ mounted.permissionModeView,
892
+ mounted.usage,
893
+ mounted.accountCredits,
894
+ settingsLifecycle.locale,
895
+ (accounts?.accounts ?? []).map((account) => ({
896
+ ...account,
897
+ active: account.accountId === selectedCodexAccountId,
898
+ })),
899
+ mounted.ownershipStatus === "error",
900
+ );
901
+ if (mounted.control.usage) {
902
+ mounted.control.usage.onOpen = () => {
903
+ void refreshThreadUsage(
904
+ mounted,
905
+ controller.get(mounted.composer).agent === "codex" ? undefined : "exact",
906
+ );
907
+ };
908
+ }
909
+ };
910
+
911
+ const refreshCommands = async (mounted: MountedComposer): Promise<void> => {
912
+ const generation = ++mounted.commandRequestGeneration;
913
+ const agent = controller.get(mounted.composer).agent;
914
+ const hostId = threadIdFromComposerModelTarget(mounted.modelTarget)
915
+ ? mounted.hostId
916
+ : activeModelHostId();
917
+ const requestControl = modelControl;
918
+ const client = modelClientForHostFrom(requestControl, hostId);
919
+ mounted.control.harnessCommands.setCommands([]);
920
+ if (agent === "codex" || !client) return;
921
+ try {
922
+ const catalog = await client.inspectHarnessCommands({ harnessId: externalHarnessIds[agent] });
923
+ if (
924
+ disposed ||
925
+ mountedByComposer.get(mounted.composer) !== mounted ||
926
+ mounted.commandRequestGeneration !== generation ||
927
+ requestControl !== modelControl ||
928
+ (threadIdFromComposerModelTarget(mounted.modelTarget)
929
+ ? mounted.hostId
930
+ : activeModelHostId()) !== hostId ||
931
+ controller.get(mounted.composer).agent !== agent
932
+ )
933
+ return;
934
+ mounted.control.harnessCommands.setCommands(
935
+ catalog.commands,
936
+ threadIdFromComposerModelTarget(mounted.modelTarget) !== null,
937
+ );
938
+ } catch {
939
+ // Keep the entry unavailable. Never open a Session as a catalog fallback.
940
+ }
941
+ };
942
+
943
+ const executeCommand = async (
944
+ mounted: MountedComposer,
945
+ command: HarnessCommandDescriptor,
946
+ ): Promise<void> => {
947
+ const threadId = threadIdFromComposerModelTarget(mounted.modelTarget);
948
+ if (!threadId || !modelControl || controller.get(mounted.composer).agent === "codex") return;
949
+ mounted.control.harnessCommands.setExecuting(command.id);
950
+ try {
951
+ await modelControl.executeThreadCommand({ threadId, commandId: command.id });
952
+ } catch (error) {
953
+ console.error(
954
+ "codexhost Harness command failed",
955
+ error instanceof Error ? error.message : String(error),
956
+ );
957
+ } finally {
958
+ mounted.control.harnessCommands.setExecuting(null);
959
+ }
960
+ };
961
+
962
+ const selectCommand = (mounted: MountedComposer, command: HarnessCommandDescriptor): void => {
963
+ const threadId = threadIdFromComposerModelTarget(mounted.modelTarget);
964
+ if (!modelControl || controller.get(mounted.composer).agent === "codex") return;
965
+ if (!threadId && rendererHarnessCommandExecutesDirectly(command)) return;
966
+ const editor = mounted.composer.querySelector<HTMLElement>(EDITOR_SELECTOR);
967
+ if (
968
+ routeRendererHarnessCommandSelection(editor, command, () => {
969
+ void executeCommand(mounted, command);
970
+ })
971
+ ) {
972
+ return;
973
+ }
974
+ console.error("codexhost Harness command could not claim the current Composer editor");
975
+ };
976
+
977
+ const applyThreadUsageUpdate = (update: ThreadUsageInspection): void => {
978
+ for (const mounted of mountedByComposer.values()) {
979
+ if (threadIdFromComposerModelTarget(mounted.modelTarget) !== update.threadId) continue;
980
+ mounted.usageRequestGeneration += 1;
981
+ mounted.usage = update.usage;
982
+ mounted.accountCredits = update.accountCredits ?? null;
983
+ usageRefreshAttempts.delete(mounted.composer);
984
+ renderMounted(mounted);
985
+ }
986
+ };
987
+
988
+ const refreshDraftCodexUsage = async (mounted: MountedComposer): Promise<void> => {
989
+ const state = controller.get(mounted.composer);
990
+ if (
991
+ state.agent !== "codex" ||
992
+ state.phase !== "draft" ||
993
+ controller.isSubmissionPending(mounted.composer) ||
994
+ threadIdFromComposerModelTarget(mounted.modelTarget)
995
+ )
996
+ return;
997
+ const accounts = composerCodexAccounts(mounted.composer);
998
+ const client = accounts?.client;
999
+ const accountId = accounts?.selection.selectedAccountId;
1000
+ const hostId = mounted.hostId;
1001
+ const generation = ++mounted.usageRequestGeneration;
1002
+ mounted.usage = null;
1003
+ mounted.accountCredits = null;
1004
+ renderMounted(mounted);
1005
+ if (accounts?.switching || !accountId || !client?.inspectCodexAccountUsage) return;
1006
+ try {
1007
+ const result = await client.inspectCodexAccountUsage({ accountId });
1008
+ if (
1009
+ disposed ||
1010
+ mounted.hostId !== hostId ||
1011
+ composerCodexAccounts(mounted.composer) !== accounts ||
1012
+ !mounted.composer.isConnected ||
1013
+ mountedByComposer.get(mounted.composer) !== mounted ||
1014
+ mounted.usageRequestGeneration !== generation ||
1015
+ controller.get(mounted.composer).agent !== "codex" ||
1016
+ controller.get(mounted.composer).phase !== "draft" ||
1017
+ controller.isSubmissionPending(mounted.composer) ||
1018
+ threadIdFromComposerModelTarget(mounted.modelTarget) ||
1019
+ accounts.selection.selectedAccountId !== accountId ||
1020
+ result.accountId !== accountId
1021
+ )
1022
+ return;
1023
+ mounted.usage = result.usage;
1024
+ mounted.accountCredits = result.accountCredits ?? null;
1025
+ renderMounted(mounted);
1026
+ } catch {
1027
+ // Leave unknown quota empty instead of retaining a different Account's values.
1028
+ }
1029
+ };
1030
+
1031
+ const refreshDraftExternalCredits = async (mounted: MountedComposer): Promise<void> => {
1032
+ const state = controller.get(mounted.composer);
1033
+ if (
1034
+ state.agent === "codex" ||
1035
+ state.phase !== "draft" ||
1036
+ controller.isSubmissionPending(mounted.composer) ||
1037
+ threadIdFromComposerModelTarget(mounted.modelTarget)
1038
+ ) {
1039
+ return;
1040
+ }
1041
+ const hostId = mounted.hostId;
1042
+ const client = modelClientForHostFrom(modelControl, hostId ?? activeModelHostId());
1043
+ if (!client?.listHarnessAccounts) return;
1044
+ const harnessId = externalHarnessIds[state.agent as keyof typeof externalHarnessIds];
1045
+ if (!harnessId) return;
1046
+ const generation = ++mounted.usageRequestGeneration;
1047
+ try {
1048
+ const result = await client.listHarnessAccounts();
1049
+ if (
1050
+ disposed ||
1051
+ !mounted.composer.isConnected ||
1052
+ mountedByComposer.get(mounted.composer) !== mounted ||
1053
+ mounted.usageRequestGeneration !== generation ||
1054
+ controller.get(mounted.composer).agent !== state.agent ||
1055
+ controller.get(mounted.composer).phase !== "draft" ||
1056
+ threadIdFromComposerModelTarget(mounted.modelTarget)
1057
+ ) {
1058
+ return;
1059
+ }
1060
+ const match = result.accounts.find((a) => a.harnessId === harnessId);
1061
+ mounted.accountCredits = match?.credits ?? null;
1062
+ renderMounted(mounted);
1063
+ } catch {
1064
+ // Leave empty if unavailable
1065
+ }
1066
+ };
1067
+
1068
+ const refreshThreadUsage = async (mounted: MountedComposer, refresh?: "exact"): Promise<void> => {
1069
+ const threadId = threadIdFromComposerModelTarget(mounted.modelTarget);
1070
+ if (!threadId) {
1071
+ if (controller.get(mounted.composer).agent === "codex") {
1072
+ await refreshDraftCodexUsage(mounted);
1073
+ } else {
1074
+ await refreshDraftExternalCredits(mounted);
1075
+ }
1076
+ return;
1077
+ }
1078
+ if (!modelControl) {
1079
+ mounted.usage = null;
1080
+ mounted.accountCredits = null;
1081
+ usageRefreshAttempts.delete(mounted.composer);
1082
+ renderMounted(mounted);
1083
+ return;
1084
+ }
1085
+ const generation = ++mounted.usageRequestGeneration;
1086
+ try {
1087
+ const result = await modelControl.inspectThreadUsage({
1088
+ threadId,
1089
+ ...(refresh ? { refresh } : {}),
1090
+ });
1091
+ if (
1092
+ disposed ||
1093
+ mountedByComposer.get(mounted.composer) !== mounted ||
1094
+ !mounted.composer.isConnected ||
1095
+ mounted.usageRequestGeneration !== generation ||
1096
+ threadIdFromComposerModelTarget(mounted.modelTarget) !== threadId ||
1097
+ result.threadId !== threadId
1098
+ ) {
1099
+ return;
1100
+ }
1101
+ mounted.usage = result.usage;
1102
+ mounted.accountCredits = result.accountCredits ?? null;
1103
+ const agent = controller.get(mounted.composer).agent;
1104
+ if (
1105
+ result.usage !== null &&
1106
+ (!externalAgentHasAccountCredits(agent) || result.accountCredits)
1107
+ ) {
1108
+ usageRefreshAttempts.delete(mounted.composer);
1109
+ }
1110
+ renderMounted(mounted);
1111
+ if (
1112
+ shouldRetryExternalThreadUsage(
1113
+ controller.get(mounted.composer).agent,
1114
+ result.usage,
1115
+ result.accountCredits ?? null,
1116
+ )
1117
+ ) {
1118
+ scheduleThreadUsageRefresh(mounted);
1119
+ }
1120
+ } catch (error) {
1121
+ if (
1122
+ mountedByComposer.get(mounted.composer) === mounted &&
1123
+ mounted.usageRequestGeneration === generation
1124
+ ) {
1125
+ renderMounted(mounted);
1126
+ if (
1127
+ !(error instanceof RendererMethodUnavailableError) &&
1128
+ shouldRetryExternalThreadUsage(controller.get(mounted.composer).agent, null, null)
1129
+ ) {
1130
+ scheduleThreadUsageRefresh(mounted);
1131
+ }
1132
+ }
1133
+ }
1134
+ };
1135
+
1136
+ const scheduleThreadUsageRefresh = (mounted: MountedComposer): void => {
1137
+ if (usageRefreshTimers.has(mounted.composer)) return;
1138
+ const attempt = usageRefreshAttempts.get(mounted.composer) ?? 0;
1139
+ usageRefreshAttempts.set(mounted.composer, attempt + 1);
1140
+ const timer = window.setTimeout(() => {
1141
+ usageRefreshTimers.delete(mounted.composer);
1142
+ void refreshThreadUsage(mounted);
1143
+ }, rendererUsageRefreshDelay(attempt));
1144
+ usageRefreshTimers.set(mounted.composer, timer);
1145
+ };
1146
+
1147
+ const isExternalConfigurationReady = (mounted: MountedComposer): boolean => {
1148
+ const current = controller.get(mounted.composer);
1149
+ if (current.agent === "codex") return true;
1150
+ return isExternalConfigurationReadyView(mounted.modelView, mounted.permissionModeView);
1151
+ };
1152
+
1153
+ const clearDraftPrewarm = async (): Promise<void> => {
1154
+ const policy = await waitForRendererDraftPrewarmPolicy(window);
1155
+ await policy.clear();
1156
+ };
1157
+
1158
+ const applyExternalConfiguration = (
1159
+ mounted: MountedComposer,
1160
+ agent: Exclude<RendererAgent, "codex">,
1161
+ model: HarnessModelRef,
1162
+ thinkingOptionId?: HarnessThinkingOptionId,
1163
+ permissionModeId?: HarnessPermissionModeId,
1164
+ ): boolean => {
1165
+ return applyComposerModelWrite(
1166
+ mounted.modelTarget,
1167
+ () =>
1168
+ applyAdapterAgent?.(agent, model, thinkingOptionId, permissionModeId, mounted.composer) ??
1169
+ false,
1170
+ );
1171
+ };
1172
+
1173
+ const loadThreadOwnership = async (mounted: MountedComposer): Promise<void> => {
1174
+ const threadId = threadIdFromComposerModelTarget(mounted.modelTarget);
1175
+ if (!threadId) {
1176
+ mounted.ownershipStatus = "not-required";
1177
+ return;
1178
+ }
1179
+ const requestModelControl = modelControl;
1180
+ const requestHostId = activeModelHostId();
1181
+ const client = modelClientForHostFrom(requestModelControl, requestHostId);
1182
+ const generation = controller.beginOwnershipRequest(mounted.composer);
1183
+ const usageGeneration = mounted.usageRequestGeneration;
1184
+ mounted.ownershipStatus = "loading";
1185
+ renderMounted(mounted);
1186
+ try {
1187
+ if (!client || !requestHostId) throw new Error("Thread ownership control is unavailable");
1188
+ mounted.hostId = requestHostId;
1189
+ const inspection = await client.inspectThread({ threadId });
1190
+ if (
1191
+ !isCurrentOwnershipRequest(mounted, generation) ||
1192
+ mountedByComposer.get(mounted.composer) !== mounted ||
1193
+ threadIdFromComposerModelTarget(mounted.modelTarget) !== threadId ||
1194
+ mounted.hostId !== requestHostId ||
1195
+ modelControl !== requestModelControl ||
1196
+ activeModelHostId() !== requestHostId
1197
+ ) {
1198
+ return;
1199
+ }
1200
+ const { agent, model, thinkingOptionId, permissionModeId } =
1201
+ restoredThreadOwnership(inspection);
1202
+ if (mounted.usageRequestGeneration === usageGeneration) {
1203
+ mounted.usage = inspection.owner === "external" ? (inspection.usage ?? null) : null;
1204
+ mounted.accountCredits =
1205
+ inspection.owner === "external" ? (inspection.accountCredits ?? null) : null;
1206
+ }
1207
+ const restored = controller.restore(
1208
+ mounted.composer,
1209
+ agent,
1210
+ model,
1211
+ thinkingOptionId,
1212
+ permissionModeId,
1213
+ inspection.owner === "codex" ? inspection.accountId : undefined,
1214
+ );
1215
+ if (!restored) {
1216
+ throw new Error("Thread owner could not be applied to the Composer");
1217
+ }
1218
+ mounted.ownershipStatus = "ready";
1219
+ if (agent !== "codex") {
1220
+ if (inspection.owner !== "external") {
1221
+ throw new Error("External Thread inspection did not include configuration");
1222
+ }
1223
+ mounted.threadConfiguration = {
1224
+ ...(inspection.effectiveModel ? { effectiveModel: inspection.effectiveModel } : {}),
1225
+ ...(inspection.resolvedModelLabel
1226
+ ? { resolvedModelLabel: inspection.resolvedModelLabel }
1227
+ : {}),
1228
+ ...(inspection.effectiveThinkingOptionId
1229
+ ? { effectiveThinkingOptionId: inspection.effectiveThinkingOptionId }
1230
+ : {}),
1231
+ ...(inspection.availableThinkingOptions
1232
+ ? { availableThinkingOptions: inspection.availableThinkingOptions }
1233
+ : {}),
1234
+ ...(inspection.effectivePermissionModeId
1235
+ ? { effectivePermissionModeId: inspection.effectivePermissionModeId }
1236
+ : {}),
1237
+ };
1238
+ mounted.modelView = { status: "loading" };
1239
+ mounted.permissionModeView = { status: "loading" };
1240
+ void loadExternalCatalog(mounted);
1241
+ } else {
1242
+ mounted.threadConfiguration = undefined;
1243
+ mounted.modelView = { status: "idle" };
1244
+ mounted.permissionModeView = { status: "idle" };
1245
+ }
1246
+ } catch {
1247
+ if (!isCurrentOwnershipRequest(mounted, generation)) return;
1248
+ mounted.ownershipStatus = "error";
1249
+ } finally {
1250
+ if (isCurrentOwnershipRequest(mounted, generation)) {
1251
+ renderMounted(mounted);
1252
+ if (mounted.ownershipStatus !== "error") void refreshCommands(mounted);
1253
+ sidebarAgentIcons.refresh();
1254
+ if (mounted.ownershipStatus !== "error") {
1255
+ const agent = controller.get(mounted.composer).agent;
1256
+ if (agent === "codex") {
1257
+ void refreshThreadUsage(mounted);
1258
+ } else if (shouldRetryExternalThreadUsage(agent, mounted.usage, mounted.accountCredits)) {
1259
+ scheduleThreadUsageRefresh(mounted);
1260
+ }
1261
+ }
1262
+ }
1263
+ }
1264
+ };
1265
+
1266
+ const refreshMountedConversationTarget = (mounted: MountedComposer): boolean => {
1267
+ const currentTarget = findComposerModelTarget(mounted.composer);
1268
+ const resolution = lateConversationTargetResolution(
1269
+ mounted.modelTarget,
1270
+ currentTarget,
1271
+ controller.get(mounted.composer).phase,
1272
+ controller.isSubmissionPending(mounted.composer),
1273
+ );
1274
+ if (resolution === "none") return false;
1275
+
1276
+ const previousTarget = mounted.modelTarget;
1277
+ const nextHostId = activeModelHostId() ?? mounted.hostId;
1278
+ const nextControllerTarget = controllerTarget(currentTarget, nextHostId);
1279
+ mounted.modelTarget = currentTarget;
1280
+ mounted.hostId = nextHostId;
1281
+ const rebound =
1282
+ resolution === "transfer"
1283
+ ? controller.transfer(mounted.composer, mounted.composer, nextControllerTarget)
1284
+ : controller.rebindConversation(mounted.composer, nextControllerTarget) !== null;
1285
+ if (!rebound) {
1286
+ mounted.ownershipStatus = "error";
1287
+ renderMounted(mounted);
1288
+ return true;
1289
+ }
1290
+ if (resolution === "transfer") {
1291
+ mounted.ownershipStatus = "ready";
1292
+ renderMounted(mounted);
1293
+ if (shouldRetryExternalThreadUsage(controller.get(mounted.composer).agent, null, null)) {
1294
+ scheduleThreadUsageRefresh(mounted);
1295
+ }
1296
+ } else {
1297
+ mounted.composerId = controller.get(mounted.composer).composerId;
1298
+ mounted.modelView = { status: "idle" };
1299
+ mounted.permissionModeView = { status: "idle" };
1300
+ mounted.threadConfiguration = undefined;
1301
+ mounted.ownershipStatus = "loading";
1302
+ mounted.usage = null;
1303
+ mounted.accountCredits = null;
1304
+ mounted.usageRequestGeneration += 1;
1305
+ usageRefreshAttempts.delete(mounted.composer);
1306
+ if (previousTarget?.[0] === "conversation") renderMounted(mounted);
1307
+ void loadThreadOwnership(mounted);
1308
+ }
1309
+ sidebarAgentIcons.refresh();
1310
+ return true;
1311
+ };
1312
+
1313
+ const loadExternalCatalog = async (mounted: MountedComposer): Promise<void> => {
1314
+ void refreshCommands(mounted);
1315
+ const state = controller.get(mounted.composer);
1316
+ if (state.agent === "codex") return;
1317
+ const agent = state.agent;
1318
+ const requestModelControl = modelControl;
1319
+ const isDraft = !threadIdFromComposerModelTarget(mounted.modelTarget);
1320
+ const requestHostId = isDraft ? activeModelHostId() : mounted.hostId;
1321
+ if (!requestHostId) {
1322
+ mounted.modelView = {
1323
+ status: "waitingForAdapter",
1324
+ thinkingSelectionSupported: false,
1325
+ };
1326
+ mounted.permissionModeView = { status: "idle" };
1327
+ renderMounted(mounted);
1328
+ return;
1329
+ }
1330
+ if (isDraft) mounted.hostId = requestHostId;
1331
+ const availability = hostHarnessAvailabilityState(requestHostId).availability[agent];
1332
+ if (availability !== "ready") {
1333
+ mounted.modelView = {
1334
+ status:
1335
+ adapterStatus.state !== "ready" || availability === "checking"
1336
+ ? "waitingForAdapter"
1337
+ : "error",
1338
+ thinkingSelectionSupported: false,
1339
+ ...(availability && availability !== "checking"
1340
+ ? { error: `${agent} runtime is ${availability}` }
1341
+ : {}),
1342
+ };
1343
+ mounted.permissionModeView = { status: "idle" };
1344
+ renderMounted(mounted);
1345
+ if (availability === "checking") void refreshHarnessAvailability();
1346
+ return;
1347
+ }
1348
+ mounted.modelView = {
1349
+ status: adapterStatus.state === "ready" ? "loading" : "waitingForAdapter",
1350
+ thinkingSelectionSupported: false,
1351
+ };
1352
+ mounted.permissionModeView = { status: "idle" };
1353
+ renderMounted(mounted);
1354
+ if (adapterStatus.state !== "ready") return;
1355
+ const generation = controller.beginModelRequest(mounted.composer);
1356
+ try {
1357
+ if (!requestModelControl || !requestHostId) {
1358
+ throw new Error("External configuration control is unavailable");
1359
+ }
1360
+ const client = modelClientForHostFrom(requestModelControl, requestHostId);
1361
+ if (!client) {
1362
+ throw new Error(`Renderer Model request manager is unavailable for Host ${requestHostId}`);
1363
+ }
1364
+ const inspection = await client.inspectHarness({
1365
+ harnessId: externalHarnessIds[agent],
1366
+ });
1367
+ if (
1368
+ !isCurrentModelRequest(mounted, generation) ||
1369
+ controller.get(mounted.composer).agent !== agent ||
1370
+ mounted.hostId !== requestHostId ||
1371
+ modelControl !== requestModelControl ||
1372
+ modelClientForHostFrom(requestModelControl, requestHostId) !== client ||
1373
+ activeModelHostId() !== requestHostId
1374
+ ) {
1375
+ return;
1376
+ }
1377
+ if (inspection.status !== "ready") throw new Error(inspection.error.message);
1378
+ const current = controller.get(mounted.composer);
1379
+ const previousModel = controller.modelForAgent(mounted.composer, agent);
1380
+ const previousModelAvailable =
1381
+ previousModel !== undefined &&
1382
+ inspection.catalog.models.some((model) => model.ref.id === previousModel.id);
1383
+ const preferredConfiguration =
1384
+ current.phase === "draft" && !previousModelAvailable
1385
+ ? readNewThreadExternalConfigurationPreference(
1386
+ agent,
1387
+ inspection.catalog,
1388
+ inspection.permissionModes,
1389
+ )
1390
+ : undefined;
1391
+ const previousPermissionModeId = controller.permissionModeForAgent(mounted.composer, agent);
1392
+ const permissionModeLock = permissionModeSelectionLocked({
1393
+ phase: current.phase,
1394
+ permissionModeScope: inspection.capabilities.configuration.permissionModeScope,
1395
+ })
1396
+ ? {
1397
+ selectionLocked: true as const,
1398
+ selectionLockedReason: rendererHarnessMessages(settingsLifecycle.locale)
1399
+ .permissionModeFixedAtCreate,
1400
+ }
1401
+ : {};
1402
+ let selectedPermissionModeId: HarnessPermissionModeId | undefined;
1403
+ if (inspection.capabilities.configuration.selectPermissionMode) {
1404
+ const permissionModes = inspection.permissionModes;
1405
+ if (!permissionModes) {
1406
+ throw new Error("External Harness omitted its Permission Mode catalog");
1407
+ }
1408
+ mounted.permissionModeView = {
1409
+ status: "loading",
1410
+ catalog: permissionModes,
1411
+ ...permissionModeLock,
1412
+ };
1413
+ const restoredPermissionModeId =
1414
+ current.phase === "locked"
1415
+ ? lockedPermissionMode(
1416
+ permissionModes,
1417
+ mounted.threadConfiguration?.effectivePermissionModeId,
1418
+ previousPermissionModeId,
1419
+ )
1420
+ : undefined;
1421
+ const preferredPermissionModeId =
1422
+ preferredConfiguration?.permissionModeId ??
1423
+ (agent === "claude-code"
1424
+ ? readClaudePermissionModePreference(permissionModes)
1425
+ : undefined);
1426
+ selectedPermissionModeId = draftPermissionMode(
1427
+ permissionModes,
1428
+ restoredPermissionModeId ?? previousPermissionModeId ?? preferredPermissionModeId,
1429
+ );
1430
+ mounted.permissionModeView = {
1431
+ status: "loading",
1432
+ catalog: permissionModes,
1433
+ selected: selectedPermissionModeId,
1434
+ ...permissionModeLock,
1435
+ };
1436
+ } else {
1437
+ mounted.permissionModeView = { status: "unsupported" };
1438
+ }
1439
+
1440
+ if (
1441
+ !inspection.capabilities.configuration.selectModel ||
1442
+ inspection.catalog.models.length === 0
1443
+ ) {
1444
+ mounted.modelView = {
1445
+ status: "empty",
1446
+ catalog: inspection.catalog,
1447
+ thinkingSelectionSupported: false,
1448
+ };
1449
+ if (selectedPermissionModeId && mounted.permissionModeView.catalog) {
1450
+ controller.setExternalPermissionMode(mounted.composer, agent, selectedPermissionModeId);
1451
+ mounted.permissionModeView = {
1452
+ status: "ready",
1453
+ catalog: mounted.permissionModeView.catalog,
1454
+ selected: selectedPermissionModeId,
1455
+ ...permissionModeLock,
1456
+ };
1457
+ }
1458
+ return;
1459
+ }
1460
+ if (current.phase === "locked" && previousModel && !previousModelAvailable) {
1461
+ mounted.modelView = {
1462
+ status: "error",
1463
+ catalog: inspection.catalog,
1464
+ selected: previousModel,
1465
+ thinkingSelectionSupported: inspection.capabilities.configuration.selectThinkingOption,
1466
+ error: "Existing Thread Model is absent from the current Catalog",
1467
+ };
1468
+ if (selectedPermissionModeId && mounted.permissionModeView.catalog) {
1469
+ mounted.permissionModeView = {
1470
+ status: "ready",
1471
+ catalog: mounted.permissionModeView.catalog,
1472
+ selected: selectedPermissionModeId,
1473
+ ...permissionModeLock,
1474
+ };
1475
+ }
1476
+ return;
1477
+ }
1478
+
1479
+ const selected = previousModelAvailable
1480
+ ? previousModel
1481
+ : (preferredConfiguration?.model ?? inspection.catalog.defaultModel);
1482
+ if (!selected) throw new Error("External Harness did not report its default Model");
1483
+ const effectiveCatalog =
1484
+ current.phase === "locked" && mounted.threadConfiguration
1485
+ ? catalogWithConfigurationState(inspection.catalog, selected, mounted.threadConfiguration)
1486
+ : inspection.catalog;
1487
+ const previousThinkingOptionId = controller.thinkingOptionForAgent(mounted.composer, agent);
1488
+ const requestedThinkingOptionId = previousModelAvailable
1489
+ ? previousThinkingOptionId
1490
+ : preferredConfiguration?.thinkingOptionId;
1491
+ const selectedThinkingOptionId = inspection.capabilities.configuration.selectThinkingOption
1492
+ ? draftThinkingOptionForModel(effectiveCatalog, selected, requestedThinkingOptionId)
1493
+ : undefined;
1494
+ if (
1495
+ current.phase === "draft" &&
1496
+ (previousModel?.id !== selected.id ||
1497
+ previousThinkingOptionId !== selectedThinkingOptionId ||
1498
+ previousPermissionModeId !== selectedPermissionModeId)
1499
+ ) {
1500
+ if (
1501
+ !applyExternalConfiguration(
1502
+ mounted,
1503
+ agent,
1504
+ selected,
1505
+ selectedThinkingOptionId,
1506
+ selectedPermissionModeId,
1507
+ )
1508
+ ) {
1509
+ throw new Error("External configuration could not be applied to the Composer");
1510
+ }
1511
+ try {
1512
+ await clearDraftPrewarm();
1513
+ } catch (error) {
1514
+ if (isCurrentModelRequest(mounted, generation)) {
1515
+ applyAdapterAgent?.(
1516
+ agent,
1517
+ previousModel,
1518
+ previousThinkingOptionId,
1519
+ previousPermissionModeId,
1520
+ mounted.composer,
1521
+ );
1522
+ }
1523
+ throw error;
1524
+ }
1525
+ if (!isCurrentModelRequest(mounted, generation)) return;
1526
+ }
1527
+ controller.setExternalModel(mounted.composer, agent, selected);
1528
+ controller.setExternalThinkingOption(mounted.composer, agent, selectedThinkingOptionId);
1529
+ if (selectedPermissionModeId) {
1530
+ controller.setExternalPermissionMode(mounted.composer, agent, selectedPermissionModeId);
1531
+ }
1532
+ mounted.modelView = {
1533
+ status: "ready",
1534
+ catalog: effectiveCatalog,
1535
+ selected,
1536
+ ...(selectedThinkingOptionId ? { selectedThinkingOptionId } : {}),
1537
+ ...(mounted.threadConfiguration?.resolvedModelLabel
1538
+ ? { resolvedModelLabel: mounted.threadConfiguration.resolvedModelLabel }
1539
+ : {}),
1540
+ thinkingSelectionSupported: inspection.capabilities.configuration.selectThinkingOption,
1541
+ };
1542
+ if (selectedPermissionModeId && mounted.permissionModeView.catalog) {
1543
+ mounted.permissionModeView = {
1544
+ status: "ready",
1545
+ catalog: mounted.permissionModeView.catalog,
1546
+ selected: selectedPermissionModeId,
1547
+ ...permissionModeLock,
1548
+ };
1549
+ }
1550
+ } catch (error) {
1551
+ if (!isCurrentModelRequest(mounted, generation)) return;
1552
+ const selected = controller.modelForAgent(mounted.composer, agent);
1553
+ const selectedThinkingOptionId = controller.thinkingOptionForAgent(mounted.composer, agent);
1554
+ const selectedPermissionModeId = controller.permissionModeForAgent(mounted.composer, agent);
1555
+ const message = error instanceof Error ? error.message : String(error);
1556
+ mounted.modelView = {
1557
+ status: "error",
1558
+ ...(mounted.modelView.catalog ? { catalog: mounted.modelView.catalog } : {}),
1559
+ ...(selected ? { selected } : {}),
1560
+ ...(selectedThinkingOptionId ? { selectedThinkingOptionId } : {}),
1561
+ thinkingSelectionSupported: false,
1562
+ error: message,
1563
+ };
1564
+ if (
1565
+ mounted.permissionModeView.status !== "unsupported" &&
1566
+ mounted.permissionModeView.status !== "idle"
1567
+ ) {
1568
+ mounted.permissionModeView = {
1569
+ status: "error",
1570
+ ...(mounted.permissionModeView.catalog
1571
+ ? { catalog: mounted.permissionModeView.catalog }
1572
+ : {}),
1573
+ ...(selectedPermissionModeId ? { selected: selectedPermissionModeId } : {}),
1574
+ error: message,
1575
+ };
1576
+ }
1577
+ } finally {
1578
+ if (isCurrentModelRequest(mounted, generation)) renderMounted(mounted);
1579
+ }
1580
+ };
1581
+
1582
+ const selectExternalModel = async (mounted: MountedComposer, modelId: string): Promise<void> => {
1583
+ controller.clearPendingSubmission(mounted.composer);
1584
+ const current = controller.get(mounted.composer);
1585
+ if (current.agent === "codex") return;
1586
+ const agent = current.agent;
1587
+ const catalog = mounted.modelView.catalog;
1588
+ const selected = catalog?.models.find((model) => model.ref.id === modelId)?.ref;
1589
+ if (!catalog || !selected || !modelControl) return;
1590
+ const previousModel = controller.modelForAgent(mounted.composer, agent);
1591
+ const previousThinking = controller.thinkingOptionForAgent(mounted.composer, agent);
1592
+ const previousPermissionModeId = controller.permissionModeForAgent(mounted.composer, agent);
1593
+ const supportsThinkingSelection = mounted.modelView.thinkingSelectionSupported === true;
1594
+ const generation = controller.beginModelRequest(mounted.composer);
1595
+ mounted.modelView = {
1596
+ status: "selecting",
1597
+ catalog,
1598
+ selected: previousModel ?? selected,
1599
+ ...(previousThinking ? { selectedThinkingOptionId: previousThinking } : {}),
1600
+ thinkingSelectionSupported: supportsThinkingSelection,
1601
+ };
1602
+ renderMounted(mounted);
1603
+ try {
1604
+ let effectiveModel: HarnessModelRef;
1605
+ let effectiveThinkingOptionId: HarnessThinkingOptionId | undefined;
1606
+ let effectiveCatalog: HarnessModelCatalog;
1607
+ let resolvedModelLabel: string | undefined;
1608
+ if (current.phase === "draft") {
1609
+ effectiveModel = selected;
1610
+ effectiveThinkingOptionId = supportsThinkingSelection
1611
+ ? draftThinkingOptionForModel(catalog, selected, previousThinking)
1612
+ : undefined;
1613
+ effectiveCatalog = catalog;
1614
+ if (
1615
+ !applyExternalConfiguration(
1616
+ mounted,
1617
+ agent,
1618
+ effectiveModel,
1619
+ effectiveThinkingOptionId,
1620
+ previousPermissionModeId,
1621
+ )
1622
+ ) {
1623
+ throw new Error("External Model configuration could not be applied to the Composer");
1624
+ }
1625
+ try {
1626
+ await clearDraftPrewarm();
1627
+ } catch (error) {
1628
+ if (previousModel && isCurrentModelRequest(mounted, generation)) {
1629
+ applyExternalConfiguration(
1630
+ mounted,
1631
+ agent,
1632
+ previousModel,
1633
+ previousThinking,
1634
+ previousPermissionModeId,
1635
+ );
1636
+ }
1637
+ throw error;
1638
+ }
1639
+ if (!isCurrentModelRequest(mounted, generation)) return;
1640
+ } else {
1641
+ const threadId = threadIdFromComposerModelTarget(mounted.modelTarget);
1642
+ if (!threadId) {
1643
+ throw new Error("External Thread identity is unavailable for Model selection");
1644
+ }
1645
+ const state = await modelControl.selectThreadModel({ threadId, model: selected });
1646
+ if (
1647
+ !isCurrentModelRequest(mounted, generation) ||
1648
+ controller.get(mounted.composer).agent !== agent
1649
+ ) {
1650
+ return;
1651
+ }
1652
+ if (!state.effectiveModel) {
1653
+ throw new Error("External Harness did not confirm an effective Model");
1654
+ }
1655
+ effectiveModel = state.effectiveModel;
1656
+ if (!catalog.models.some((model) => model.ref.id === effectiveModel.id)) {
1657
+ throw new Error("External Harness activated a Model outside the current catalog");
1658
+ }
1659
+ effectiveThinkingOptionId = supportsThinkingSelection
1660
+ ? selectableThinkingOptionId(state)
1661
+ : undefined;
1662
+ effectiveCatalog = supportsThinkingSelection
1663
+ ? catalogWithConfigurationState(catalog, effectiveModel, state)
1664
+ : catalog;
1665
+ resolvedModelLabel = state.resolvedModelLabel;
1666
+ const effectivePermissionModeId =
1667
+ state.effectivePermissionModeId ?? previousPermissionModeId;
1668
+ if (
1669
+ !applyExternalConfiguration(
1670
+ mounted,
1671
+ agent,
1672
+ effectiveModel,
1673
+ effectiveThinkingOptionId,
1674
+ effectivePermissionModeId,
1675
+ )
1676
+ ) {
1677
+ throw new Error("Confirmed external Model could not be applied to the Composer");
1678
+ }
1679
+ mounted.threadConfiguration = state;
1680
+ }
1681
+ if (!isCurrentModelRequest(mounted, generation)) return;
1682
+ controller.setExternalModel(mounted.composer, agent, effectiveModel);
1683
+ controller.setExternalThinkingOption(mounted.composer, agent, effectiveThinkingOptionId);
1684
+ const effectivePermissionModeId =
1685
+ mounted.threadConfiguration?.effectivePermissionModeId ?? previousPermissionModeId;
1686
+ if (effectivePermissionModeId) {
1687
+ controller.setExternalPermissionMode(mounted.composer, agent, effectivePermissionModeId);
1688
+ }
1689
+ if (shouldPersistNewThreadConfigurationSelection(current.phase)) {
1690
+ writeNewThreadExternalConfigurationPreference(
1691
+ agent,
1692
+ effectiveModel,
1693
+ effectiveThinkingOptionId,
1694
+ effectivePermissionModeId,
1695
+ );
1696
+ }
1697
+ mounted.modelView = {
1698
+ status: "ready",
1699
+ catalog: effectiveCatalog,
1700
+ selected: effectiveModel,
1701
+ ...(effectiveThinkingOptionId
1702
+ ? { selectedThinkingOptionId: effectiveThinkingOptionId }
1703
+ : {}),
1704
+ ...(resolvedModelLabel ? { resolvedModelLabel } : {}),
1705
+ thinkingSelectionSupported: supportsThinkingSelection,
1706
+ };
1707
+ } catch (error) {
1708
+ if (!isCurrentModelRequest(mounted, generation)) return;
1709
+ if (previousModel) {
1710
+ applyExternalConfiguration(
1711
+ mounted,
1712
+ agent,
1713
+ previousModel,
1714
+ previousThinking,
1715
+ previousPermissionModeId,
1716
+ );
1717
+ }
1718
+ mounted.modelView = {
1719
+ status: "error",
1720
+ catalog,
1721
+ ...(previousModel ? { selected: previousModel } : {}),
1722
+ ...(previousThinking ? { selectedThinkingOptionId: previousThinking } : {}),
1723
+ thinkingSelectionSupported: supportsThinkingSelection,
1724
+ error: error instanceof Error ? error.message : String(error),
1725
+ };
1726
+ } finally {
1727
+ if (isCurrentModelRequest(mounted, generation)) renderMounted(mounted);
1728
+ }
1729
+ };
1730
+
1731
+ const selectPermissionMode = async (
1732
+ mounted: MountedComposer,
1733
+ permissionModeId: string,
1734
+ ): Promise<void> => {
1735
+ controller.clearPendingSubmission(mounted.composer);
1736
+ const current = controller.get(mounted.composer);
1737
+ if (current.agent === "codex") return;
1738
+ const agent = current.agent;
1739
+ const catalog = mounted.permissionModeView.catalog;
1740
+ const selectedPermissionModeId = catalog?.modes.find(({ id }) => id === permissionModeId)?.id;
1741
+ const model = controller.modelForAgent(mounted.composer, agent);
1742
+ if (
1743
+ !catalog ||
1744
+ !selectedPermissionModeId ||
1745
+ !model ||
1746
+ !modelControl ||
1747
+ mounted.permissionModeView.selectionLocked
1748
+ ) {
1749
+ return;
1750
+ }
1751
+ const previousPermissionModeId = controller.permissionModeForAgent(mounted.composer, agent);
1752
+ const thinkingOptionId = controller.thinkingOptionForAgent(mounted.composer, agent);
1753
+ const generation = controller.beginModelRequest(mounted.composer);
1754
+ mounted.permissionModeView = {
1755
+ status: "selecting",
1756
+ catalog,
1757
+ selected: previousPermissionModeId ?? selectedPermissionModeId,
1758
+ };
1759
+ renderMounted(mounted);
1760
+ try {
1761
+ let effectivePermissionModeId = selectedPermissionModeId;
1762
+ if (current.phase === "draft") {
1763
+ if (
1764
+ !applyExternalConfiguration(
1765
+ mounted,
1766
+ agent,
1767
+ model,
1768
+ thinkingOptionId,
1769
+ selectedPermissionModeId,
1770
+ )
1771
+ ) {
1772
+ throw new Error("Permission Mode could not be applied to the Composer");
1773
+ }
1774
+ try {
1775
+ await clearDraftPrewarm();
1776
+ } catch (error) {
1777
+ if (isCurrentModelRequest(mounted, generation)) {
1778
+ applyExternalConfiguration(
1779
+ mounted,
1780
+ agent,
1781
+ model,
1782
+ thinkingOptionId,
1783
+ previousPermissionModeId,
1784
+ );
1785
+ }
1786
+ throw error;
1787
+ }
1788
+ if (!isCurrentModelRequest(mounted, generation)) return;
1789
+ } else {
1790
+ const threadId = threadIdFromComposerModelTarget(mounted.modelTarget);
1791
+ if (!threadId) {
1792
+ throw new Error("External Thread identity is unavailable for Permission Mode selection");
1793
+ }
1794
+ const state = await modelControl.selectThreadPermissionMode({
1795
+ threadId,
1796
+ permissionModeId: selectedPermissionModeId,
1797
+ });
1798
+ if (
1799
+ !isCurrentModelRequest(mounted, generation) ||
1800
+ controller.get(mounted.composer).agent !== agent
1801
+ ) {
1802
+ return;
1803
+ }
1804
+ if (
1805
+ !state.effectivePermissionModeId ||
1806
+ !catalog.modes.some(({ id }) => id === state.effectivePermissionModeId)
1807
+ ) {
1808
+ throw new Error("External Harness did not report a selectable Permission Mode");
1809
+ }
1810
+ effectivePermissionModeId = state.effectivePermissionModeId;
1811
+ if (
1812
+ !applyExternalConfiguration(
1813
+ mounted,
1814
+ agent,
1815
+ model,
1816
+ thinkingOptionId,
1817
+ effectivePermissionModeId,
1818
+ )
1819
+ ) {
1820
+ throw new Error("Confirmed Permission Mode could not be applied to the Composer");
1821
+ }
1822
+ mounted.threadConfiguration = state;
1823
+ }
1824
+ if (!isCurrentModelRequest(mounted, generation)) return;
1825
+ controller.setExternalPermissionMode(mounted.composer, agent, effectivePermissionModeId);
1826
+ if (shouldPersistNewThreadConfigurationSelection(current.phase)) {
1827
+ writeNewThreadExternalConfigurationPreference(
1828
+ agent,
1829
+ model,
1830
+ thinkingOptionId,
1831
+ effectivePermissionModeId,
1832
+ );
1833
+ if (agent === "claude-code") {
1834
+ writeClaudePermissionModePreference(effectivePermissionModeId);
1835
+ }
1836
+ }
1837
+ mounted.permissionModeView = {
1838
+ status: "ready",
1839
+ catalog,
1840
+ selected: effectivePermissionModeId,
1841
+ };
1842
+ } catch (error) {
1843
+ if (!isCurrentModelRequest(mounted, generation)) return;
1844
+ if (previousPermissionModeId) {
1845
+ applyExternalConfiguration(
1846
+ mounted,
1847
+ agent,
1848
+ model,
1849
+ thinkingOptionId,
1850
+ previousPermissionModeId,
1851
+ );
1852
+ }
1853
+ mounted.permissionModeView = {
1854
+ status: "error",
1855
+ catalog,
1856
+ ...(previousPermissionModeId ? { selected: previousPermissionModeId } : {}),
1857
+ error: error instanceof Error ? error.message : String(error),
1858
+ };
1859
+ } finally {
1860
+ if (isCurrentModelRequest(mounted, generation)) renderMounted(mounted);
1861
+ }
1862
+ };
1863
+
1864
+ const selectExternalThinking = async (
1865
+ mounted: MountedComposer,
1866
+ thinkingOptionId: string,
1867
+ ): Promise<void> => {
1868
+ controller.clearPendingSubmission(mounted.composer);
1869
+ const current = controller.get(mounted.composer);
1870
+ if (current.agent === "codex") return;
1871
+ const agent = current.agent;
1872
+ const catalog = mounted.modelView.catalog;
1873
+ const model = controller.modelForAgent(mounted.composer, agent);
1874
+ const permissionModeId = controller.permissionModeForAgent(mounted.composer, agent);
1875
+ const selectedThinkingOptionId = catalog?.thinkingOptions.find(
1876
+ ({ id }) => id === thinkingOptionId,
1877
+ )?.id;
1878
+ const catalogModel = catalog?.models.find((candidate) => candidate.ref.id === model?.id);
1879
+ if (
1880
+ !mounted.modelView.thinkingSelectionSupported ||
1881
+ !catalog ||
1882
+ !model ||
1883
+ !selectedThinkingOptionId ||
1884
+ !catalogModel?.supportedThinkingOptionIds?.includes(selectedThinkingOptionId)
1885
+ ) {
1886
+ return;
1887
+ }
1888
+ const previousThinking = controller.thinkingOptionForAgent(mounted.composer, agent);
1889
+ const generation = controller.beginModelRequest(mounted.composer);
1890
+ mounted.modelView = {
1891
+ status: "selecting",
1892
+ catalog,
1893
+ selected: model,
1894
+ ...(previousThinking ? { selectedThinkingOptionId: previousThinking } : {}),
1895
+ thinkingSelectionSupported: true,
1896
+ };
1897
+ renderMounted(mounted);
1898
+ try {
1899
+ let effectiveThinkingOptionId = selectedThinkingOptionId;
1900
+ let effectiveCatalog = catalog;
1901
+ if (current.phase === "draft") {
1902
+ if (
1903
+ !applyExternalConfiguration(
1904
+ mounted,
1905
+ agent,
1906
+ model,
1907
+ selectedThinkingOptionId,
1908
+ permissionModeId,
1909
+ )
1910
+ ) {
1911
+ throw new Error("External Thinking could not be applied to the Composer");
1912
+ }
1913
+ try {
1914
+ await clearDraftPrewarm();
1915
+ } catch (error) {
1916
+ if (isCurrentModelRequest(mounted, generation)) {
1917
+ applyExternalConfiguration(mounted, agent, model, previousThinking, permissionModeId);
1918
+ }
1919
+ throw error;
1920
+ }
1921
+ if (!isCurrentModelRequest(mounted, generation)) return;
1922
+ } else {
1923
+ const threadId = threadIdFromComposerModelTarget(mounted.modelTarget);
1924
+ if (!threadId || !modelControl) {
1925
+ throw new Error("External Thread identity is unavailable for Thinking selection");
1926
+ }
1927
+ const state = await modelControl.selectThreadThinking({
1928
+ threadId,
1929
+ thinkingOptionId: selectedThinkingOptionId,
1930
+ });
1931
+ if (
1932
+ !isCurrentModelRequest(mounted, generation) ||
1933
+ controller.get(mounted.composer).agent !== agent
1934
+ ) {
1935
+ return;
1936
+ }
1937
+ if (state.effectiveModel && state.effectiveModel.id !== model.id) {
1938
+ throw new Error("External Harness changed Model during Thinking selection");
1939
+ }
1940
+ if (!state.effectiveThinkingOptionId) {
1941
+ throw new Error("External Harness did not confirm effective Thinking");
1942
+ }
1943
+ effectiveThinkingOptionId = state.effectiveThinkingOptionId;
1944
+ effectiveCatalog = catalogWithConfigurationState(catalog, model, state);
1945
+ if (
1946
+ !applyExternalConfiguration(
1947
+ mounted,
1948
+ agent,
1949
+ model,
1950
+ effectiveThinkingOptionId,
1951
+ state.effectivePermissionModeId ?? permissionModeId,
1952
+ )
1953
+ ) {
1954
+ throw new Error("Confirmed external Thinking could not be applied to the Composer");
1955
+ }
1956
+ mounted.threadConfiguration = state;
1957
+ }
1958
+ if (!isCurrentModelRequest(mounted, generation)) return;
1959
+ controller.setExternalThinkingOption(mounted.composer, agent, effectiveThinkingOptionId);
1960
+ const effectivePermissionModeId =
1961
+ mounted.threadConfiguration?.effectivePermissionModeId ?? permissionModeId;
1962
+ if (effectivePermissionModeId) {
1963
+ controller.setExternalPermissionMode(mounted.composer, agent, effectivePermissionModeId);
1964
+ }
1965
+ if (shouldPersistNewThreadConfigurationSelection(current.phase)) {
1966
+ writeNewThreadExternalConfigurationPreference(
1967
+ agent,
1968
+ model,
1969
+ effectiveThinkingOptionId,
1970
+ effectivePermissionModeId,
1971
+ );
1972
+ }
1973
+ mounted.modelView = {
1974
+ status: "ready",
1975
+ catalog: effectiveCatalog,
1976
+ selected: model,
1977
+ selectedThinkingOptionId: effectiveThinkingOptionId,
1978
+ thinkingSelectionSupported: true,
1979
+ };
1980
+ } catch (error) {
1981
+ if (!isCurrentModelRequest(mounted, generation)) return;
1982
+ applyExternalConfiguration(mounted, agent, model, previousThinking, permissionModeId);
1983
+ mounted.modelView = {
1984
+ status: "error",
1985
+ catalog,
1986
+ selected: model,
1987
+ ...(previousThinking ? { selectedThinkingOptionId: previousThinking } : {}),
1988
+ thinkingSelectionSupported: true,
1989
+ error: error instanceof Error ? error.message : String(error),
1990
+ };
1991
+ } finally {
1992
+ if (isCurrentModelRequest(mounted, generation)) renderMounted(mounted);
1993
+ }
1994
+ };
1995
+
1996
+ const switchComposerAgent = async (
1997
+ mounted: MountedComposer,
1998
+ agent: RendererAgent,
1999
+ ): Promise<boolean> => {
2000
+ if (agent !== "codex" && activeHarnessAvailabilityState().availability[agent] !== "ready") {
2001
+ return false;
2002
+ }
2003
+ controller.clearPendingSubmission(mounted.composer);
2004
+ const composerId = controller.get(mounted.composer).composerId;
2005
+ controller.invalidateModelRequests(mounted.composer);
2006
+ const switching = controller.switchAgent(mounted.composer, agent, {
2007
+ applyAgent(nextAgent) {
2008
+ const model = controller.modelForAgent(mounted.composer, nextAgent);
2009
+ if (!shouldApplyDraftAgentCarrier(nextAgent, model)) return true;
2010
+ return (
2011
+ applyAdapterAgent?.(
2012
+ nextAgent,
2013
+ model,
2014
+ nextAgent !== "codex"
2015
+ ? controller.thinkingOptionForAgent(mounted.composer, nextAgent)
2016
+ : undefined,
2017
+ nextAgent !== "codex"
2018
+ ? controller.permissionModeForAgent(mounted.composer, nextAgent)
2019
+ : undefined,
2020
+ mounted.composer,
2021
+ ) ?? nextAgent === "codex"
2022
+ );
2023
+ },
2024
+ clearPrewarm: clearDraftPrewarm,
2025
+ });
2026
+ renderMounted(mounted);
2027
+ try {
2028
+ const switched = await switching;
2029
+ if (switched && controller.get(mounted.composer).agent !== "codex") {
2030
+ void loadExternalCatalog(mounted);
2031
+ } else if (controller.get(mounted.composer).agent === "codex") {
2032
+ mounted.modelView = { status: "idle" };
2033
+ mounted.permissionModeView = { status: "idle" };
2034
+ void refreshCommands(mounted);
2035
+ }
2036
+ sidebarAgentIcons.refresh();
2037
+ return switched;
2038
+ } catch {
2039
+ adapterStatus = {
2040
+ ...adapterStatus,
2041
+ state: "unsupported",
2042
+ reason: "draft-prewarm-clear-failed",
2043
+ hook: null,
2044
+ };
2045
+ return false;
2046
+ } finally {
2047
+ for (const candidate of mountedByComposer.values()) {
2048
+ if (controller.get(candidate.composer).composerId === composerId) renderMounted(candidate);
2049
+ }
2050
+ }
2051
+ };
2052
+
2053
+ const loadCodexAccounts = async (): Promise<void> => {
2054
+ const hostId = activeModelHostId();
2055
+ const accounts = codexAccountsForHost(hostId);
2056
+ if (!accounts) return;
2057
+ await accounts.refresh();
2058
+ if (disposed || codexAccountsForHost(hostId) !== accounts) return;
2059
+ for (const mounted of mountedByComposer.values()) {
2060
+ if (mounted.hostId !== hostId) continue;
2061
+ renderMounted(mounted);
2062
+ void refreshDraftCodexUsage(mounted);
2063
+ }
2064
+ };
2065
+
2066
+ const selectCodexAccount = async (mounted: MountedComposer, accountId: string): Promise<void> => {
2067
+ const hostId = mounted.hostId;
2068
+ const accounts = composerCodexAccounts(mounted.composer);
2069
+ if (
2070
+ !accounts ||
2071
+ accounts.switching ||
2072
+ controller.get(mounted.composer).phase === "locked" ||
2073
+ !accounts.accounts.some((account) => account.accountId === accountId)
2074
+ )
2075
+ return;
2076
+ const isCurrent = (): boolean =>
2077
+ !disposed &&
2078
+ mounted.composer.isConnected &&
2079
+ mountedByComposer.get(mounted.composer) === mounted &&
2080
+ controller.get(mounted.composer).phase !== "locked" &&
2081
+ accounts.accounts.some((account) => account.accountId === accountId) &&
2082
+ mounted.hostId === hostId &&
2083
+ activeModelHostId() === hostId &&
2084
+ composerCodexAccounts(mounted.composer) === accounts;
2085
+ accounts.switching = true;
2086
+ for (const candidate of mountedByComposer.values()) {
2087
+ renderMounted(candidate);
2088
+ void refreshDraftCodexUsage(candidate);
2089
+ }
2090
+ try {
2091
+ if (
2092
+ controller.get(mounted.composer).agent !== "codex" &&
2093
+ !(await switchComposerAgent(mounted, "codex"))
2094
+ ) {
2095
+ return;
2096
+ }
2097
+ const policy = await waitForRendererDraftPrewarmPolicy(window);
2098
+ if (!isCurrent() || policy.hostId !== hostId) return;
2099
+ await policy.clear();
2100
+ if (!isCurrent() || window.__codexhostDraftPrewarmPolicyV1 !== policy) return;
2101
+ if (!policy.selectAccount) throw new Error("Codex Account selection is unavailable");
2102
+ const override = accountId === accounts.selection.activeAccountId ? null : accountId;
2103
+ policy.selectAccount(override);
2104
+ accounts.overrideAccountId = override;
2105
+ } catch {
2106
+ if (isCurrent()) void loadCodexAccounts();
2107
+ } finally {
2108
+ accounts.switching = false;
2109
+ for (const candidate of mountedByComposer.values()) {
2110
+ renderMounted(candidate);
2111
+ void refreshDraftCodexUsage(candidate);
2112
+ }
2113
+ }
2114
+ };
2115
+
2116
+ const openInstallPage = (agent: ExternalRendererAgent): void => {
2117
+ const url = RENDERER_AGENT_INSTALL_URLS[agent];
2118
+ window.open(url, "_blank", "noopener,noreferrer");
2119
+ };
2120
+
2121
+ function resetHarnessAvailabilityRetry(hostId: string): void {
2122
+ const state = hostHarnessAvailabilityState(hostId);
2123
+ if (state.retryTimer !== null) {
2124
+ window.clearTimeout(state.retryTimer);
2125
+ state.retryTimer = null;
2126
+ }
2127
+ state.retryAttempt = 0;
2128
+ }
2129
+
2130
+ function scheduleHarnessAvailabilityRetry(hostId: string): void {
2131
+ const state = hostHarnessAvailabilityState(hostId);
2132
+ if (
2133
+ disposed ||
2134
+ state.retryTimer !== null ||
2135
+ state.retryAttempt >= availabilityRetryDelays.length
2136
+ ) {
2137
+ return;
2138
+ }
2139
+ const delay = availabilityRetryDelays[state.retryAttempt];
2140
+ state.retryAttempt += 1;
2141
+ state.retryTimer = window.setTimeout(() => {
2142
+ state.retryTimer = null;
2143
+ void refreshHarnessAvailabilityForHost(hostId, true, true);
2144
+ }, delay);
2145
+ }
2146
+
2147
+ function modelClientForHostFrom(
2148
+ control: RendererModelClient | null,
2149
+ hostId: string | null,
2150
+ ): RendererModelClient | null {
2151
+ if (!control || !hostId) return null;
2152
+ const selected = control.clientForHost?.(hostId);
2153
+ if (selected) return selected;
2154
+ const currentHostId = control.currentHostId?.() ?? "local";
2155
+ return currentHostId === hostId ? control : null;
2156
+ }
2157
+
2158
+ function modelClientForHost(hostId: string): RendererModelClient | null {
2159
+ return modelClientForHostFrom(modelControl, hostId);
2160
+ }
2161
+
2162
+ function refreshHarnessAvailabilityForHost(
2163
+ hostId: string,
2164
+ refresh = false,
2165
+ retry = false,
2166
+ force = false,
2167
+ ): Promise<void> {
2168
+ const state = hostHarnessAvailabilityState(hostId);
2169
+ if (!retry) resetHarnessAvailabilityRetry(hostId);
2170
+ const client = modelClientForHost(hostId);
2171
+ if (!client) {
2172
+ scheduleHarnessAvailabilityRetry(hostId);
2173
+ return Promise.resolve();
2174
+ }
2175
+ if (state.request?.client === client) return state.request.promise;
2176
+ const agentsToInspect = force
2177
+ ? externalAgents
2178
+ : passiveHarnessAvailabilityAgents(state.availability, state.errors);
2179
+ if (agentsToInspect.length === 0) {
2180
+ resetHarnessAvailabilityRetry(hostId);
2181
+ return Promise.resolve();
2182
+ }
2183
+ const nextAvailability = { ...state.availability };
2184
+ for (const agent of agentsToInspect) {
2185
+ nextAvailability[agent] = harnessAvailabilityDuringInspect(nextAvailability[agent]);
2186
+ }
2187
+ state.availability = nextAvailability;
2188
+ if (hostId === activeAvailabilityHostId) {
2189
+ publishConnectionStatus();
2190
+ for (const mounted of mountedByComposer.values()) renderMounted(mounted);
2191
+ }
2192
+ const generation = ++state.requestGeneration;
2193
+ const promise = (async () => {
2194
+ await Promise.all(
2195
+ agentsToInspect.map(async (agent) => {
2196
+ let status: RendererAgentAvailability = "error";
2197
+ let nextError: CodexhostError | undefined;
2198
+ let webUiAvailable = false;
2199
+ try {
2200
+ const inspection = await client.inspectHarness({
2201
+ harnessId: externalHarnessIds[agent],
2202
+ refresh,
2203
+ });
2204
+ status = inspection.status === "ready" ? "ready" : inspection.status;
2205
+ webUiAvailable =
2206
+ hostId === "local" &&
2207
+ inspection.status === "ready" &&
2208
+ inspection.webUi?.open === true;
2209
+ if (inspection.status !== "ready") {
2210
+ const error = inspection.error;
2211
+ nextError = {
2212
+ code: error.code,
2213
+ message: error.message,
2214
+ retryable: error.retryable,
2215
+ ...(error.diagnostic ? { diagnostic: error.diagnostic } : {}),
2216
+ ...(error.stage ? { stage: error.stage } : {}),
2217
+ ...(error.durationMs !== undefined ? { durationMs: error.durationMs } : {}),
2218
+ ...(error.stderrTail ? { stderrTail: error.stderrTail } : {}),
2219
+ };
2220
+ }
2221
+ } catch (error) {
2222
+ status = "error";
2223
+ nextError = {
2224
+ code: "internalError",
2225
+ message: error instanceof Error ? error.message : String(error),
2226
+ retryable: !(error instanceof RendererMethodUnavailableError),
2227
+ stage: "request",
2228
+ };
2229
+ }
2230
+ if (generation !== state.requestGeneration || disposed) return;
2231
+ const previousStatus = state.availability[agent];
2232
+ state.errors[agent] = nextError;
2233
+ state.availability = { ...state.availability, [agent]: status };
2234
+ state.webUi = { ...state.webUi, [agent]: webUiAvailable };
2235
+ if (hostId !== activeAvailabilityHostId) {
2236
+ publishConnectionStatus();
2237
+ return;
2238
+ }
2239
+ for (const mounted of mountedByComposer.values()) {
2240
+ const composerState = controller.get(mounted.composer);
2241
+ if (
2242
+ adapterStatus.state === "ready" &&
2243
+ composerState.phase === "draft" &&
2244
+ composerState.agent === agent &&
2245
+ status !== "ready"
2246
+ ) {
2247
+ await switchComposerAgent(mounted, "codex");
2248
+ }
2249
+ }
2250
+ for (const mounted of mountedByComposer.values()) {
2251
+ const composerState = controller.get(mounted.composer);
2252
+ if (
2253
+ composerState.agent === agent &&
2254
+ shouldReloadExternalCatalogAfterAvailabilityRefresh(
2255
+ previousStatus,
2256
+ status,
2257
+ isExternalConfigurationStable(mounted.modelView, mounted.permissionModeView),
2258
+ refresh && force,
2259
+ )
2260
+ ) {
2261
+ void loadExternalCatalog(mounted);
2262
+ }
2263
+ renderMounted(mounted);
2264
+ }
2265
+ publishConnectionStatus();
2266
+ }),
2267
+ );
2268
+ if (generation !== state.requestGeneration || disposed) return;
2269
+ if (retryableHarnessAvailabilityAgents(state.availability, state.errors).length === 0) {
2270
+ resetHarnessAvailabilityRetry(hostId);
2271
+ } else {
2272
+ scheduleHarnessAvailabilityRetry(hostId);
2273
+ }
2274
+ })();
2275
+ const request = { client, promise };
2276
+ state.request = request;
2277
+ void promise.then(
2278
+ () => {
2279
+ if (state.request === request) state.request = null;
2280
+ },
2281
+ () => {
2282
+ if (state.request === request) state.request = null;
2283
+ },
2284
+ );
2285
+ return promise;
2286
+ }
2287
+
2288
+ const reloadMountedOwnershipForHost = (hostId: string): void => {
2289
+ for (const mounted of mountedByComposer.values()) {
2290
+ if (mounted.hostId === hostId) continue;
2291
+ if (!threadIdFromComposerModelTarget(mounted.modelTarget)) {
2292
+ controller.clearPendingSubmission(mounted.composer);
2293
+ mounted.hostId = hostId;
2294
+ mounted.usage = null;
2295
+ mounted.accountCredits = null;
2296
+ mounted.usageRequestGeneration += 1;
2297
+ continue;
2298
+ }
2299
+ const target = controllerTarget(mounted.modelTarget, hostId);
2300
+ if (controller.rebindConversation(mounted.composer, target) === null) {
2301
+ mounted.ownershipStatus = "error";
2302
+ renderMounted(mounted);
2303
+ continue;
2304
+ }
2305
+ mounted.hostId = hostId;
2306
+ mounted.composerId = controller.get(mounted.composer).composerId;
2307
+ mounted.modelView = { status: "idle" };
2308
+ mounted.permissionModeView = { status: "idle" };
2309
+ mounted.threadConfiguration = undefined;
2310
+ mounted.ownershipStatus = "loading";
2311
+ mounted.usage = null;
2312
+ mounted.accountCredits = null;
2313
+ mounted.usageRequestGeneration += 1;
2314
+ usageRefreshAttempts.delete(mounted.composer);
2315
+ const timer = usageRefreshTimers.get(mounted.composer);
2316
+ if (timer !== undefined) {
2317
+ window.clearTimeout(timer);
2318
+ usageRefreshTimers.delete(mounted.composer);
2319
+ }
2320
+ renderMounted(mounted);
2321
+ void loadThreadOwnership(mounted);
2322
+ }
2323
+ sidebarAgentIcons.refresh();
2324
+ };
2325
+
2326
+ function reconcileHarnessAvailabilityHost(): void {
2327
+ const hostId = activeModelHostId();
2328
+ if (
2329
+ !hostId ||
2330
+ (hostId === activeAvailabilityHostId &&
2331
+ [...mountedByComposer.values()].every((mounted) => mounted.hostId !== null))
2332
+ )
2333
+ return;
2334
+ // The first Composer can mount before the route is ready. The availability
2335
+ // cache starts at "local", but that does not establish the Composer's Host.
2336
+ activeAvailabilityHostId = hostId;
2337
+ hostHarnessAvailabilityState(hostId);
2338
+ reloadMountedOwnershipForHost(hostId);
2339
+ publishConnectionStatus();
2340
+ for (const mounted of mountedByComposer.values()) renderMounted(mounted);
2341
+ void refreshHarnessAvailabilityForHost(hostId);
2342
+ }
2343
+
2344
+ const refreshHarnessAvailability = (refresh = false): Promise<void> => {
2345
+ reconcileHarnessAvailabilityHost();
2346
+ return refreshHarnessAvailabilityForHost(activeAvailabilityHostId, refresh);
2347
+ };
2348
+
2349
+ connectionDiagnostics = {
2350
+ snapshot(): RendererConnectionSnapshot {
2351
+ const hostIds = [
2352
+ "local",
2353
+ ...[...harnessAvailabilityByHost.keys()].filter((hostId) => hostId !== "local").sort(),
2354
+ ];
2355
+ return {
2356
+ adapter: { ...adapterStatus },
2357
+ hosts: hostIds.map((hostId) => {
2358
+ const state = hostHarnessAvailabilityState(hostId);
2359
+ return {
2360
+ hostId,
2361
+ active: hostId === activeAvailabilityHostId,
2362
+ agents: externalAgents.map((agent) => ({
2363
+ agent,
2364
+ availability: state.availability[agent] ?? "checking",
2365
+ error: state.errors[agent] ?? null,
2366
+ ...(state.webUi[agent] ? { webUiAvailable: true as const } : {}),
2367
+ })),
2368
+ };
2369
+ }),
2370
+ };
2371
+ },
2372
+ refresh(): Promise<void> {
2373
+ return refreshConnectionHosts(harnessAvailabilityByHost.keys(), (hostId) =>
2374
+ refreshHarnessAvailabilityForHost(hostId, true, false, true),
2375
+ );
2376
+ },
2377
+ async openWebUi(hostId: string, agent: ExternalRendererAgent): Promise<void> {
2378
+ const state = hostHarnessAvailabilityState(hostId);
2379
+ const client = hostId === "local" ? modelClientForHost(hostId) : null;
2380
+ if (
2381
+ state.availability[agent] !== "ready" ||
2382
+ state.webUi[agent] !== true ||
2383
+ !client?.openHarnessWebUi
2384
+ ) {
2385
+ throw new Error("Harness Web UI is unavailable");
2386
+ }
2387
+ try {
2388
+ await client.openHarnessWebUi({ harnessId: externalHarnessIds[agent] });
2389
+ } catch (error) {
2390
+ state.webUi = { ...state.webUi, [agent]: false };
2391
+ publishConnectionStatus();
2392
+ void refreshHarnessAvailabilityForHost(hostId, true, false, true).catch(() => undefined);
2393
+ throw error;
2394
+ }
2395
+ },
2396
+ inspectHarness(hostId: string, agent: ExternalRendererAgent) {
2397
+ const client = modelClientForHost(hostId);
2398
+ if (!client?.inspectHarness) throw new Error("Harness inspect is unavailable");
2399
+ return client.inspectHarness({ harnessId: externalHarnessIds[agent] });
2400
+ },
2401
+ async installHarness(hostId: string, agent: ExternalRendererAgent, options?: { terminal?: boolean }) {
2402
+ const client = modelClientForHost(hostId);
2403
+ if (!client?.installHarness) throw new Error("Harness install is unavailable");
2404
+ const result = await client.installHarness({ harnessId: externalHarnessIds[agent], terminal: options?.terminal });
2405
+ void refreshHarnessAvailabilityForHost(hostId, true, false, true).catch(() => undefined);
2406
+ return result;
2407
+ },
2408
+ subscribe(listener: () => void): () => void {
2409
+ connectionListeners.add(listener);
2410
+ return () => connectionListeners.delete(listener);
2411
+ },
2412
+ };
2413
+
2414
+ const mount = (composer: Element): void => {
2415
+ if (
2416
+ mountedByComposer.has(composer) ||
2417
+ !composer.isConnected ||
2418
+ !composer.matches(CODEX_COMPOSER_SELECTOR)
2419
+ ) {
2420
+ return;
2421
+ }
2422
+ const allButtons = [...composer.querySelectorAll<HTMLButtonElement>("button")];
2423
+ const sendButton = sendButtonWithin(composer) ?? allButtons.at(-1) ?? null;
2424
+ if (!sendButton) return;
2425
+ const modelTarget = findComposerModelTarget(composer);
2426
+ const hostId = activeModelHostId();
2427
+ const editor = composer.querySelector<HTMLElement>(EDITOR_SELECTOR);
2428
+ if (!editor) return;
2429
+ const state = controller.mount(
2430
+ composer,
2431
+ controllerTarget(modelTarget, hostId),
2432
+ modelTarget?.[0] === "default" ? readNewThreadAgentPreference(enabledAgentSet) : undefined,
2433
+ );
2434
+ const inherited = pendingReplacements.get(composer)?.source;
2435
+ const control = mountComposerAgentControl(
2436
+ composer,
2437
+ state.composerId,
2438
+ sendButton,
2439
+ enabledAgents,
2440
+ (agent) => {
2441
+ const mounted = mountedByComposer.get(composer);
2442
+ if (!composer.isConnected || !mounted) return;
2443
+ void switchComposerAgent(mounted, agent);
2444
+ },
2445
+ openInstallPage,
2446
+ async (accountId) => {
2447
+ const mounted = mountedByComposer.get(composer);
2448
+ if (!composer.isConnected || !mounted) return;
2449
+ await selectCodexAccount(mounted, accountId);
2450
+ },
2451
+ () => {
2452
+ void loadCodexAccounts();
2453
+ },
2454
+ (modelId) => {
2455
+ const mounted = mountedByComposer.get(composer);
2456
+ if (!composer.isConnected || !mounted) return;
2457
+ void selectExternalModel(mounted, modelId);
2458
+ },
2459
+ (thinkingOptionId) => {
2460
+ const mounted = mountedByComposer.get(composer);
2461
+ if (!composer.isConnected || !mounted) return;
2462
+ void selectExternalThinking(mounted, thinkingOptionId);
2463
+ },
2464
+ (permissionModeId) => {
2465
+ const mounted = mountedByComposer.get(composer);
2466
+ if (!composer.isConnected || !mounted) return;
2467
+ void selectPermissionMode(mounted, permissionModeId);
2468
+ },
2469
+ (command) => {
2470
+ const mounted = mountedByComposer.get(composer);
2471
+ if (mounted) selectCommand(mounted, command);
2472
+ },
2473
+ );
2474
+ const mounted: MountedComposer = {
2475
+ composer,
2476
+ composerId: state.composerId,
2477
+ control,
2478
+ modelTarget,
2479
+ modelView: inherited?.modelView ?? { status: "idle" },
2480
+ permissionModeView: inherited?.permissionModeView ?? { status: "idle" },
2481
+ ownershipStatus: threadIdFromComposerModelTarget(modelTarget)
2482
+ ? inherited
2483
+ ? "ready"
2484
+ : "loading"
2485
+ : "not-required",
2486
+ threadConfiguration: inherited?.threadConfiguration,
2487
+ usage: inherited?.usage ?? null,
2488
+ accountCredits: inherited?.accountCredits ?? null,
2489
+ hostId: inherited?.hostId ?? hostId,
2490
+ usageRequestGeneration: 0,
2491
+ commandRequestGeneration: 0,
2492
+ };
2493
+ mountedByComposer.set(composer, mounted);
2494
+ if (isComposerModelWriteAllowed(modelTarget)) {
2495
+ const model = controller.modelForAgent(composer, state.agent);
2496
+ if (shouldApplyDraftAgentCarrier(state.agent, model)) {
2497
+ applyAdapterAgent?.(
2498
+ state.agent,
2499
+ model,
2500
+ state.agent !== "codex"
2501
+ ? controller.thinkingOptionForAgent(composer, state.agent)
2502
+ : undefined,
2503
+ state.agent !== "codex"
2504
+ ? controller.permissionModeForAgent(composer, state.agent)
2505
+ : undefined,
2506
+ composer,
2507
+ );
2508
+ }
2509
+ }
2510
+ renderMounted(mounted);
2511
+ sidebarAgentIcons.refresh();
2512
+ if (threadIdFromComposerModelTarget(modelTarget) && !inherited) {
2513
+ void loadThreadOwnership(mounted);
2514
+ } else if (
2515
+ threadIdFromComposerModelTarget(modelTarget) &&
2516
+ inherited &&
2517
+ shouldRetryExternalThreadUsage(state.agent, mounted.usage, mounted.accountCredits)
2518
+ ) {
2519
+ scheduleThreadUsageRefresh(mounted);
2520
+ } else if (
2521
+ state.agent !== "codex" &&
2522
+ !isExternalConfigurationStable(mounted.modelView, mounted.permissionModeView)
2523
+ ) {
2524
+ void loadExternalCatalog(mounted);
2525
+ }
2526
+ if (!threadIdFromComposerModelTarget(modelTarget)) void refreshDraftCodexUsage(mounted);
2527
+ void refreshCommands(mounted);
2528
+ };
2529
+
2530
+ const scan = (): void => {
2531
+ scanScheduled = false;
2532
+ const refreshTargets = refreshTargetsOnNextScan;
2533
+ refreshTargetsOnNextScan = false;
2534
+ if (disposed) return;
2535
+ settingsLifecycle.refresh();
2536
+ for (const [target, replacement] of pendingReplacements) {
2537
+ const sourceState = controller.get(replacement.source.composer);
2538
+ const replacementTarget = findComposerModelTarget(target);
2539
+ const replacementHostId = activeModelHostId() ?? replacement.source.hostId;
2540
+ if (
2541
+ !shouldTransferComposerState(
2542
+ replacement.sourceModelTarget,
2543
+ replacementTarget,
2544
+ sourceState.phase,
2545
+ controller.isSubmissionPending(replacement.source.composer),
2546
+ ) ||
2547
+ !controller.transfer(
2548
+ replacement.source.composer,
2549
+ target,
2550
+ controllerTarget(replacementTarget, replacementHostId),
2551
+ )
2552
+ ) {
2553
+ pendingReplacements.delete(target);
2554
+ }
2555
+ }
2556
+ for (const [composer, mounted] of mountedByComposer) {
2557
+ if (
2558
+ !composer.isConnected ||
2559
+ !composer.matches(CODEX_COMPOSER_SELECTOR) ||
2560
+ !mounted.control.root.isConnected
2561
+ ) {
2562
+ mounted.usageRequestGeneration += 1;
2563
+ usageRefreshAttempts.delete(composer);
2564
+ const timer = usageRefreshTimers.get(composer);
2565
+ if (timer !== undefined) {
2566
+ window.clearTimeout(timer);
2567
+ usageRefreshTimers.delete(composer);
2568
+ }
2569
+ disposeComposerAgentControl(mounted.control);
2570
+ mountedByComposer.delete(composer);
2571
+ continue;
2572
+ }
2573
+ const state = controller.get(composer);
2574
+ const hideCodexControls = controller.isSwitching(composer) || state.agent !== "codex";
2575
+ reconcileComposerNativeControls(mounted.control, hideCodexControls, hideCodexControls);
2576
+ if (refreshTargets) refreshMountedConversationTarget(mounted);
2577
+ }
2578
+ for (const editor of document.querySelectorAll(EDITOR_SELECTOR)) {
2579
+ const composer = composerForEditor(editor);
2580
+ if (composer) mount(composer);
2581
+ }
2582
+ const localAvailability = hostHarnessAvailabilityState("local").availability;
2583
+ if (externalAgents.some((agent) => localAvailability[agent] === "checking")) {
2584
+ void refreshHarnessAvailabilityForHost("local");
2585
+ }
2586
+ reconcileHarnessAvailabilityHost();
2587
+ if (
2588
+ externalAgents.some(
2589
+ (agent) => activeHarnessAvailabilityState().availability[agent] === "checking",
2590
+ )
2591
+ ) {
2592
+ void refreshHarnessAvailability();
2593
+ }
2594
+ pendingReplacements.clear();
2595
+ };
2596
+
2597
+ const scheduleScan = (refreshTargets = false): void => {
2598
+ refreshTargetsOnNextScan ||= refreshTargets;
2599
+ if (scanScheduled || disposed) return;
2600
+ scanScheduled = true;
2601
+ queueMicrotask(scan);
2602
+ };
2603
+
2604
+ const composerRootsWithin = (node: Node): Element[] => {
2605
+ if (node.nodeType !== Node.ELEMENT_NODE) return [];
2606
+ const element = node as Element;
2607
+ const roots = element.matches(CODEX_COMPOSER_SELECTOR) ? [element] : [];
2608
+ roots.push(...element.querySelectorAll(CODEX_COMPOSER_SELECTOR));
2609
+ return roots;
2610
+ };
2611
+
2612
+ const transferReplacedComposers = (mutations: MutationRecord[]): void => {
2613
+ const replacements = new Map<Node, { removed: Set<Element>; added: Set<Element> }>();
2614
+ for (const mutation of mutations) {
2615
+ if (mutation.type !== "childList") continue;
2616
+ let replacement = replacements.get(mutation.target);
2617
+ if (!replacement) {
2618
+ replacement = { removed: new Set(), added: new Set() };
2619
+ replacements.set(mutation.target, replacement);
2620
+ }
2621
+ for (const removedNode of mutation.removedNodes) {
2622
+ for (const composer of mountedByComposer.keys()) {
2623
+ if (
2624
+ removedNode === composer ||
2625
+ (removedNode.nodeType === Node.ELEMENT_NODE &&
2626
+ (removedNode as Element).contains(composer))
2627
+ ) {
2628
+ replacement.removed.add(composer);
2629
+ }
2630
+ }
2631
+ }
2632
+ for (const addedNode of mutation.addedNodes) {
2633
+ for (const composer of composerRootsWithin(addedNode)) replacement.added.add(composer);
2634
+ }
2635
+ }
2636
+ for (const replacement of replacements.values()) {
2637
+ if (replacement.removed.size !== 1 || replacement.added.size !== 1) continue;
2638
+ const source = replacement.removed.values().next().value as Element;
2639
+ const target = replacement.added.values().next().value as Element;
2640
+ const mounted = mountedByComposer.get(source);
2641
+ if (source !== target && mounted) {
2642
+ pendingReplacements.set(target, {
2643
+ source: mounted,
2644
+ sourceModelTarget: mounted.modelTarget,
2645
+ });
2646
+ }
2647
+ }
2648
+ };
2649
+
2650
+ const applyComposerAgent = (composer: Element): boolean => {
2651
+ const state = controller.get(composer);
2652
+ const mounted = mountedByComposer.get(composer);
2653
+ if (mounted?.modelTarget?.[0] === "conversation") {
2654
+ return state.phase === "locked" && mounted.ownershipStatus === "ready";
2655
+ }
2656
+ if (!mounted || !isComposerModelWriteAllowed(mounted.modelTarget)) return false;
2657
+ const model = controller.modelForAgent(composer, state.agent);
2658
+ if (!shouldApplyDraftAgentCarrier(state.agent, model)) return false;
2659
+ return applyComposerModelWrite(
2660
+ mounted.modelTarget,
2661
+ () =>
2662
+ applyAdapterAgent?.(
2663
+ state.agent,
2664
+ model,
2665
+ state.agent !== "codex"
2666
+ ? controller.thinkingOptionForAgent(composer, state.agent)
2667
+ : undefined,
2668
+ state.agent !== "codex"
2669
+ ? controller.permissionModeForAgent(composer, state.agent)
2670
+ : undefined,
2671
+ composer,
2672
+ ) ?? state.agent === "codex",
2673
+ );
2674
+ };
2675
+ const blockEvent = (event: Event): void => {
2676
+ event.preventDefault();
2677
+ event.stopImmediatePropagation();
2678
+ };
2679
+ const prepareComposer = (composer: Element): boolean | null => {
2680
+ const mounted = mountedByComposer.get(composer);
2681
+ if (!mounted) return null;
2682
+ const current = controller.get(composer);
2683
+ if (
2684
+ composerCodexAccounts(composer)?.switching ||
2685
+ controller.isSwitching(composer) ||
2686
+ isOwnershipSubmissionBlocked(mounted.ownershipStatus)
2687
+ ) {
2688
+ return false;
2689
+ }
2690
+ if (!isExternalConfigurationReady(mounted)) return false;
2691
+ if (current.phase === "locked") return true;
2692
+ if (!applyComposerAgent(composer)) return false;
2693
+ if (current.agent === "codex") {
2694
+ // A Host switch may replace its policy while retaining this Host's draft
2695
+ // override. Apply it to the matching policy at submission, not another Host.
2696
+ const selection = composerCodexAccounts(composer)?.selection;
2697
+ const accountId =
2698
+ controller.isSubmissionPending(composer) && current.codexAccountId
2699
+ ? current.codexAccountId
2700
+ : selection?.selectedAccountId;
2701
+ const override = accountId === selection?.activeAccountId ? null : (accountId ?? null);
2702
+ const policy = window.__codexhostDraftPrewarmPolicyV1;
2703
+ if (override !== null && (policy?.hostId !== mounted.hostId || !policy.selectAccount)) {
2704
+ return false;
2705
+ }
2706
+ try {
2707
+ if (policy?.hostId === mounted.hostId) policy.selectAccount?.(override);
2708
+ } catch {
2709
+ return false;
2710
+ }
2711
+ }
2712
+ controller.markSubmissionPending(composer);
2713
+ renderMounted(mounted);
2714
+ return true;
2715
+ };
2716
+ const composerForTarget = (target: EventTarget | null): Element | null => {
2717
+ const element = eventElement(target);
2718
+ const editor = element ? editorForElement(element) : null;
2719
+ const composer = editor ? composerForEditor(editor) : null;
2720
+ return composer && isMountedComposer(composer) ? composer : null;
2721
+ };
2722
+ const onBeforeInput = (event: InputEvent): void => {
2723
+ const composer = composerForTarget(event.target);
2724
+ if (!composer) return;
2725
+ controller.clearPendingSubmission(composer);
2726
+ const mounted = mountedByComposer.get(composer);
2727
+ if (mounted && isOwnershipSubmissionBlocked(mounted.ownershipStatus)) return;
2728
+ // Draft editing must remain available while the native connection recovers.
2729
+ // Submission still goes through prepareComposer and fails closed.
2730
+ if (!controller.isSwitching(composer)) applyComposerAgent(composer);
2731
+ };
2732
+ const onSubmit = (event: Event): void => {
2733
+ const element = eventElement(event.target);
2734
+ const candidate = element ? composerForElement(element) : null;
2735
+ const composer = candidate && isMountedComposer(candidate) ? candidate : null;
2736
+ if (!composer) return;
2737
+ const prepared = prepareComposer(composer);
2738
+ if (prepared === null) return;
2739
+ if (!prepared) {
2740
+ blockEvent(event);
2741
+ return;
2742
+ }
2743
+ notifySubmission(composer, "submit");
2744
+ };
2745
+ const onKeyDown = (event: KeyboardEvent): void => {
2746
+ const composer = isComposerInputIntent(event) ? composerForTarget(event.target) : null;
2747
+ const mounted = composer ? mountedByComposer.get(composer) : undefined;
2748
+ if (
2749
+ composer &&
2750
+ (composerCodexAccounts(composer)?.switching || controller.isSwitching(composer))
2751
+ ) {
2752
+ if (isComposerSubmissionKey(event)) blockEvent(event);
2753
+ return;
2754
+ }
2755
+ if (composer && mounted && isOwnershipSubmissionBlocked(mounted.ownershipStatus)) {
2756
+ if (isComposerSubmissionKey(event)) blockEvent(event);
2757
+ return;
2758
+ }
2759
+ if (composer && !applyComposerAgent(composer)) {
2760
+ if (isComposerSubmissionKey(event)) blockEvent(event);
2761
+ return;
2762
+ }
2763
+ if (!isComposerSubmissionKey(event) || !composer) return;
2764
+ if (!prepareComposer(composer)) {
2765
+ blockEvent(event);
2766
+ return;
2767
+ }
2768
+ notifySubmission(composer, "enter");
2769
+ };
2770
+ const onClick = (event: MouseEvent): void => {
2771
+ const element = eventElement(event.target);
2772
+ const button = element?.closest<HTMLButtonElement>("button");
2773
+ if (!button) return;
2774
+ const candidate = composerForElement(button);
2775
+ const composer = candidate && isMountedComposer(candidate) ? candidate : null;
2776
+ const mounted = composer ? mountedByComposer.get(composer) : undefined;
2777
+ if (!composer || mounted?.control.sendButton !== button) return;
2778
+ if (!prepareComposer(composer)) {
2779
+ blockEvent(event);
2780
+ return;
2781
+ }
2782
+ notifySubmission(composer, "click");
2783
+ };
2784
+
2785
+ const mutationObserver = new MutationObserver((mutations) => {
2786
+ transferReplacedComposers(mutations);
2787
+ scheduleScan(mutations.some(mutationMayChangeComposerTarget));
2788
+ });
2789
+ const onHostRouteChange = (): void => {
2790
+ sidebarAgentIcons.refresh();
2791
+ reconcileHarnessAvailabilityHost();
2792
+ void loadCodexAccounts();
2793
+ void refreshHarnessAvailability();
2794
+ for (const mounted of mountedByComposer.values()) {
2795
+ const state = controller.get(mounted.composer);
2796
+ if (
2797
+ state.agent !== "codex" &&
2798
+ !threadIdFromComposerModelTarget(mounted.modelTarget) &&
2799
+ !isExternalConfigurationStable(mounted.modelView, mounted.permissionModeView)
2800
+ ) {
2801
+ void loadExternalCatalog(mounted);
2802
+ }
2803
+ }
2804
+ };
2805
+ const onAdapterStatus = () => {
2806
+ publishConnectionStatus();
2807
+ if (shouldRefreshCodexAccountsForAdapterState(adapterStatus.state)) {
2808
+ void loadCodexAccounts();
2809
+ sidebarAgentIcons.refresh();
2810
+ void refreshHarnessAvailabilityForHost("local");
2811
+ void refreshHarnessAvailability();
2812
+ for (const mounted of mountedByComposer.values()) {
2813
+ if (mounted.modelView.status === "waitingForAdapter" && mounted.composer.isConnected) {
2814
+ applyComposerAgent(mounted.composer);
2815
+ void loadExternalCatalog(mounted);
2816
+ }
2817
+ }
2818
+ }
2819
+ for (const mounted of mountedByComposer.values()) renderMounted(mounted);
2820
+ };
2821
+ mutationObserver.observe(document.documentElement, {
2822
+ attributes: true,
2823
+ attributeFilter: ["hidden", "aria-hidden", "data-codex-composer-root"],
2824
+ characterData: true,
2825
+ childList: true,
2826
+ subtree: true,
2827
+ });
2828
+ document.addEventListener("beforeinput", onBeforeInput, true);
2829
+ document.addEventListener("submit", onSubmit, true);
2830
+ document.addEventListener("keydown", onKeyDown, true);
2831
+ document.addEventListener("click", onClick, true);
2832
+ const onWindowFocus = (): void => {
2833
+ reconcileHarnessAvailabilityHost();
2834
+ void loadCodexAccounts();
2835
+ for (const mounted of mountedByComposer.values()) {
2836
+ if (mounted.hostId === activeModelHostId() && mounted.ownershipStatus === "error") {
2837
+ void loadThreadOwnership(mounted);
2838
+ }
2839
+ }
2840
+ const local = hostHarnessAvailabilityState("local");
2841
+ if (externalAgents.some((agent) => local.availability[agent] !== "ready")) {
2842
+ void refreshHarnessAvailabilityForHost("local", true);
2843
+ }
2844
+ const active = activeHarnessAvailabilityState();
2845
+ if (externalAgents.some((agent) => active.availability[agent] !== "ready")) {
2846
+ void refreshHarnessAvailability(true);
2847
+ }
2848
+ };
2849
+ window.addEventListener("codexhost:draft-prewarm-policy-changed", onHostRouteChange);
2850
+ window.addEventListener("codexhost:renderer-adapter-status", onAdapterStatus);
2851
+ window.addEventListener("focus", onWindowFocus);
2852
+
2853
+ const connectedComposers = (): MountedComposer[] =>
2854
+ [...mountedByComposer.values()].filter(
2855
+ (mounted) => mounted.composer.isConnected && mounted.control.root.isConnected,
2856
+ );
2857
+
2858
+ const api: RendererBindingProbeApi = {
2859
+ status() {
2860
+ const selections = connectedComposers().map((mounted) => ({
2861
+ composerId: mounted.composerId,
2862
+ agent: controller.get(mounted.composer).agent,
2863
+ phase: controller.get(mounted.composer).phase,
2864
+ }));
2865
+ return {
2866
+ version: 2,
2867
+ mountedComposers: selections.length,
2868
+ enabledAgents: [...enabledAgents],
2869
+ availability: { ...activeHarnessAvailabilityState().availability },
2870
+ selections,
2871
+ adapter: { ...adapterStatus },
2872
+ };
2873
+ },
2874
+ lockedSelection() {
2875
+ const locked = connectedComposers()
2876
+ .map((mounted) => ({ mounted, state: controller.get(mounted.composer) }))
2877
+ .filter(({ state }) => state.phase === "locked");
2878
+ const entry = locked[0];
2879
+ if (locked.length !== 1 || !entry) return null;
2880
+ const { mounted, state: selection } = entry;
2881
+ const model = controller.modelForAgent(mounted.composer, selection.agent);
2882
+ const thinkingOptionId =
2883
+ selection.agent !== "codex"
2884
+ ? controller.thinkingOptionForAgent(mounted.composer, selection.agent)
2885
+ : undefined;
2886
+ const permissionModeId =
2887
+ selection.agent !== "codex"
2888
+ ? controller.permissionModeForAgent(mounted.composer, selection.agent)
2889
+ : undefined;
2890
+ return {
2891
+ composerId: selection.composerId,
2892
+ agent: selection.agent,
2893
+ phase: "locked",
2894
+ ...(model ? { model } : {}),
2895
+ ...(thinkingOptionId ? { thinkingOptionId } : {}),
2896
+ ...(permissionModeId ? { permissionModeId } : {}),
2897
+ };
2898
+ },
2899
+ setAdapter(status, dispose, applyAgent, nextModelControl) {
2900
+ usageNotificationDispose?.();
2901
+ usageNotificationDispose = null;
2902
+ adapterDispose?.();
2903
+ adapterDispose = dispose ?? null;
2904
+ applyAdapterAgent = applyAgent ?? null;
2905
+ modelControl = nextModelControl ?? null;
2906
+ try {
2907
+ usageNotificationDispose =
2908
+ modelControl?.subscribeThreadUsage?.(applyThreadUsageUpdate) ?? null;
2909
+ } catch {
2910
+ usageNotificationDispose = null;
2911
+ }
2912
+ adapterStatus = status;
2913
+ publishConnectionStatus();
2914
+ const installedModelControl = modelControl;
2915
+ queueMicrotask(() => {
2916
+ if (disposed || modelControl !== installedModelControl) return;
2917
+ try {
2918
+ settingsLifecycle.refresh();
2919
+ } catch {
2920
+ // Auxiliary settings UI must not affect Agent routing compatibility.
2921
+ }
2922
+ });
2923
+ for (const state of harnessAvailabilityByHost.values()) {
2924
+ state.requestGeneration += 1;
2925
+ state.request = null;
2926
+ if (state.retryTimer !== null) window.clearTimeout(state.retryTimer);
2927
+ }
2928
+ harnessAvailabilityByHost.clear();
2929
+ activeAvailabilityHostId = "local";
2930
+ sidebarAgentIcons.refresh();
2931
+ void refreshHarnessAvailabilityForHost("local");
2932
+ reconcileHarnessAvailabilityHost();
2933
+ void loadCodexAccounts();
2934
+ const connected = connectedComposers();
2935
+ if (connected.length === 1) {
2936
+ const mounted = connected[0];
2937
+ if (mounted) {
2938
+ const state = controller.get(mounted.composer);
2939
+ if (!threadIdFromComposerModelTarget(mounted.modelTarget)) {
2940
+ mounted.hostId = activeModelHostId();
2941
+ }
2942
+ if (
2943
+ threadIdFromComposerModelTarget(mounted.modelTarget) &&
2944
+ mounted.ownershipStatus !== "ready"
2945
+ ) {
2946
+ void loadThreadOwnership(mounted);
2947
+ } else if (state.agent !== "codex") {
2948
+ void loadExternalCatalog(mounted);
2949
+ } else if (isComposerModelWriteAllowed(mounted.modelTarget)) {
2950
+ applyComposerAgent(mounted.composer);
2951
+ }
2952
+ }
2953
+ }
2954
+ for (const mounted of mountedByComposer.values()) {
2955
+ renderMounted(mounted);
2956
+ void refreshCommands(mounted);
2957
+ }
2958
+ },
2959
+ dispose() {
2960
+ if (disposed) return;
2961
+ disposed = true;
2962
+ usageNotificationDispose?.();
2963
+ usageNotificationDispose = null;
2964
+ adapterDispose?.();
2965
+ adapterDispose = null;
2966
+ applyAdapterAgent = null;
2967
+ modelControl = null;
2968
+ mutationObserver.disconnect();
2969
+ sidebarAgentIcons.dispose();
2970
+ harnessMentions.dispose();
2971
+ collabCards.dispose();
2972
+ settingsLifecycle.dispose();
2973
+ document.removeEventListener("beforeinput", onBeforeInput, true);
2974
+ document.removeEventListener("submit", onSubmit, true);
2975
+ document.removeEventListener("keydown", onKeyDown, true);
2976
+ document.removeEventListener("click", onClick, true);
2977
+ window.removeEventListener("codexhost:draft-prewarm-policy-changed", onHostRouteChange);
2978
+ window.removeEventListener("codexhost:renderer-adapter-status", onAdapterStatus);
2979
+ window.removeEventListener("focus", onWindowFocus);
2980
+ for (const state of harnessAvailabilityByHost.values()) {
2981
+ state.requestGeneration += 1;
2982
+ if (state.retryTimer !== null) window.clearTimeout(state.retryTimer);
2983
+ }
2984
+ harnessAvailabilityByHost.clear();
2985
+ for (const timer of usageRefreshTimers.values()) window.clearTimeout(timer);
2986
+ usageRefreshTimers.clear();
2987
+ for (const mounted of mountedByComposer.values()) {
2988
+ mounted.usageRequestGeneration += 1;
2989
+ usageRefreshAttempts.delete(mounted.composer);
2990
+ disposeComposerAgentControl(mounted.control);
2991
+ }
2992
+ mountedByComposer.clear();
2993
+ pendingReplacements.clear();
2994
+ connectionListeners.clear();
2995
+ connectionDiagnostics = null;
2996
+ delete window.__codexhostRendererBindingProbeV1;
2997
+ },
2998
+ };
2999
+ window.__codexhostRendererBindingProbeV1 = api;
3000
+ scan();
3001
+ return api;
3002
+ }