harness-mix 0.1.0

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