@bitkyc08/opencodex 2.83.0 → 2.86.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 (405) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-BjlttVLy.js +58 -0
  4. package/gui/dist/assets/Tray-DyR9yBee.js +1 -0
  5. package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
  6. package/gui/dist/assets/index-BMssP4k3.js +94 -0
  7. package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
  8. package/gui/dist/index.html +2 -2
  9. package/package.json +2 -3
  10. package/src/adapters/anthropic/beta-allowlist.ts +87 -8
  11. package/src/adapters/anthropic/client-identity.ts +1 -1
  12. package/src/adapters/anthropic/passthrough.ts +33 -4
  13. package/src/adapters/azure.ts +19 -1
  14. package/src/adapters/client-fingerprint.ts +24 -0
  15. package/src/adapters/coding-agent/protocol.ts +15 -8
  16. package/src/adapters/coding-agent/turn.ts +22 -9
  17. package/src/adapters/cursor/arg-codec.ts +17 -5
  18. package/src/adapters/cursor/catalog.ts +8 -0
  19. package/src/adapters/cursor/effort-map.ts +3 -0
  20. package/src/adapters/cursor/freeform-args.ts +75 -0
  21. package/src/adapters/cursor/protobuf-events.ts +36 -23
  22. package/src/adapters/google-antigravity-wire.ts +22 -1
  23. package/src/adapters/google.ts +2 -2
  24. package/src/adapters/ollama-native.ts +29 -1
  25. package/src/adapters/openai-chat/messages.ts +8 -4
  26. package/src/adapters/openai-chat/response-events.ts +59 -1
  27. package/src/adapters/openai-chat.ts +8 -5
  28. package/src/adapters/openai-responses/empty-assistant.ts +32 -0
  29. package/src/adapters/openai-responses/passthrough.ts +3 -0
  30. package/src/adapters/openai-responses/request-strips.ts +10 -2
  31. package/src/adapters/qoder/adapter.ts +56 -15
  32. package/src/advisor/consult.ts +220 -0
  33. package/src/advisor/context.ts +226 -0
  34. package/src/advisor/disclosure.ts +20 -0
  35. package/src/advisor/runtime.ts +305 -0
  36. package/src/advisor/settings.ts +135 -0
  37. package/src/advisor/state.ts +369 -0
  38. package/src/advisor/synthetic-tool.ts +33 -0
  39. package/src/bridge/errors.ts +24 -0
  40. package/src/bridge.ts +1 -1
  41. package/src/chat/inbound.ts +30 -6
  42. package/src/chat/outbound.ts +5 -0
  43. package/src/claude/context-windows.ts +54 -1
  44. package/src/claude/desktop-3p-library.ts +43 -3
  45. package/src/claude/desktop-3p.ts +22 -8
  46. package/src/claude/desktop-picker-profile.ts +11 -4
  47. package/src/claude/desktop-profile.ts +10 -1
  48. package/src/claude/desktop-remote-store-io.ts +5 -3
  49. package/src/claude/inbound.ts +121 -50
  50. package/src/claude/intercept/connect-proxy.ts +10 -3
  51. package/src/claude/intercept/local-ca.ts +8 -1
  52. package/src/claude/intercept/picker-ca-persistence.ts +3 -1
  53. package/src/claude/intercept/picker-listener.ts +49 -1
  54. package/src/claude/intercept/settings-no-proxy.ts +146 -0
  55. package/src/claude/intercept/settings.ts +101 -9
  56. package/src/claude/model-default-settings.ts +154 -0
  57. package/src/claude/model-info.ts +7 -7
  58. package/src/claude/outbound.ts +13 -6
  59. package/src/cli/account-api.ts +65 -0
  60. package/src/cli/account-auth.ts +26 -2
  61. package/src/cli/account.ts +41 -5
  62. package/src/cli/advisor.ts +151 -0
  63. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  64. package/src/cli/capabilities-access-remote.ts +8 -0
  65. package/src/cli/capabilities-agents-routing.ts +18 -0
  66. package/src/cli/capabilities-base.ts +14 -2
  67. package/src/cli/catalog-command-result.ts +2 -1
  68. package/src/cli/chatgpt-command.ts +35 -14
  69. package/src/cli/claude.ts +60 -11
  70. package/src/cli/config-command.ts +8 -2
  71. package/src/cli/debug.ts +8 -0
  72. package/src/cli/dispatch.ts +21 -4
  73. package/src/cli/doctor.ts +9 -0
  74. package/src/cli/ensure-desktop-cold-start.ts +329 -0
  75. package/src/cli/export-command.ts +15 -2
  76. package/src/cli/help.ts +2 -1
  77. package/src/cli/hub-disable.ts +99 -0
  78. package/src/cli/hub.ts +4 -2
  79. package/src/cli/index.ts +26 -8
  80. package/src/cli/integration-plan-dto.ts +2 -0
  81. package/src/cli/link-child.ts +104 -0
  82. package/src/cli/link-enrollment.ts +96 -0
  83. package/src/cli/link.ts +39 -24
  84. package/src/cli/observe-stream.ts +6 -1
  85. package/src/cli/observe.ts +10 -2
  86. package/src/cli/provider.ts +12 -0
  87. package/src/cli/registry.ts +20 -7
  88. package/src/cli/restart-scope.ts +3 -1
  89. package/src/cli/runtime-api.ts +27 -2
  90. package/src/cli/status.ts +104 -20
  91. package/src/client/connect.ts +39 -4
  92. package/src/client/hub-client.ts +14 -7
  93. package/src/client/hub-state.ts +25 -14
  94. package/src/client/link-home.ts +248 -0
  95. package/src/client/link-management-relay.ts +61 -0
  96. package/src/client/link-relay.ts +6 -1
  97. package/src/client/link-status.ts +14 -0
  98. package/src/client/machine-api.ts +14 -7
  99. package/src/client/machine-listener.ts +29 -4
  100. package/src/clients/config-export/contracts.ts +1 -0
  101. package/src/clients/config-export/model-metadata.ts +3 -2
  102. package/src/clients/config-export/qoder.ts +93 -0
  103. package/src/clients/config-export.ts +59 -16
  104. package/src/clients/omo-role-models.ts +6 -3
  105. package/src/codex/agent-role-models.ts +26 -8
  106. package/src/codex/auth-api/account-list.ts +17 -2
  107. package/src/codex/auth-api/account-selection.ts +17 -7
  108. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  109. package/src/codex/auth-api/routes.ts +5 -1
  110. package/src/codex/auth-context.ts +79 -5
  111. package/src/codex/autostart-health.ts +7 -1
  112. package/src/codex/catalog/build-entries.ts +11 -2
  113. package/src/codex/catalog/bundled.ts +5 -1
  114. package/src/codex/catalog/configured-tiers.ts +50 -0
  115. package/src/codex/catalog/effort.ts +6 -3
  116. package/src/codex/catalog/metadata.ts +4 -2
  117. package/src/codex/catalog/model-hints.ts +21 -3
  118. package/src/codex/catalog/native-models.ts +16 -2
  119. package/src/codex/catalog/native-ultrafast.ts +106 -0
  120. package/src/codex/catalog/parsing.ts +5 -0
  121. package/src/codex/catalog/provider-models.ts +4 -0
  122. package/src/codex/catalog/retained-sync.ts +2 -0
  123. package/src/codex/catalog/routed-gather.ts +22 -4
  124. package/src/codex/catalog/vendor-context.ts +82 -0
  125. package/src/codex/config-write-lock.ts +40 -2
  126. package/src/codex/convergence.ts +2 -0
  127. package/src/codex/desktop-app/types.ts +2 -0
  128. package/src/codex/desktop-app/windows.ts +29 -16
  129. package/src/codex/desktop-app-restart.ts +4 -2
  130. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  131. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  132. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  133. package/src/codex/desktop-compatibility/installed-build.ts +55 -0
  134. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  135. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  136. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  137. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  138. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  139. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  140. package/src/codex/desktop-compatibility/runtime.ts +173 -0
  141. package/src/codex/desktop-compatibility/service.ts +30 -0
  142. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  143. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  144. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  145. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  146. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  147. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  148. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  149. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  150. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  151. package/src/codex/desktop-compatibility/windows-key-protection.ts +59 -0
  152. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  153. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  154. package/src/codex/history-provider.ts +1 -1
  155. package/src/codex/inject/config-write-section.ts +4 -1
  156. package/src/codex/inject/remove.ts +18 -6
  157. package/src/codex/journal.ts +16 -1
  158. package/src/codex/main-account.ts +99 -15
  159. package/src/codex/native-main-refresh-mode.ts +217 -0
  160. package/src/codex/native-profile-startup.ts +4 -1
  161. package/src/codex/prompt-journal.ts +20 -2
  162. package/src/codex/prompt-layers/encoding.ts +49 -0
  163. package/src/codex/prompt-layers/import-source.ts +30 -0
  164. package/src/codex/prompt-layers/toml-edit.ts +269 -37
  165. package/src/codex/prompt-layers/toml-read.ts +52 -4
  166. package/src/codex/prompt-layers.ts +383 -20
  167. package/src/codex/prompt-lock-claim.ts +13 -3
  168. package/src/codex/prompt-lock-evidence.ts +3 -3
  169. package/src/codex/prompt-lock-owner.ts +14 -6
  170. package/src/codex/prompt-text-probe.ts +34 -5
  171. package/src/codex/quota.ts +13 -6
  172. package/src/codex/role-auto-assign.ts +2 -5
  173. package/src/codex/routing/active-account.ts +21 -0
  174. package/src/codex/routing/health-store.ts +1 -1
  175. package/src/codex/routing/pool-recovery.ts +79 -0
  176. package/src/codex/routing.ts +1 -1
  177. package/src/codex/runtime.ts +196 -8
  178. package/src/codex/shim-diagnostics.ts +6 -0
  179. package/src/codex/shim-overlay.ts +15 -6
  180. package/src/codex/shim.ts +1 -1
  181. package/src/combos/failover.ts +3 -0
  182. package/src/config/api-key-reconcile.ts +34 -0
  183. package/src/config/diagnostics.ts +18 -1
  184. package/src/config/feature-flags.ts +5 -0
  185. package/src/config/live-reconcile.ts +56 -29
  186. package/src/config/live-topology.ts +29 -0
  187. package/src/config/load-degrade.ts +8 -2
  188. package/src/config/paths.ts +13 -3
  189. package/src/config/salvage.ts +3 -1
  190. package/src/config/schema/config-schema.ts +19 -0
  191. package/src/config/schema/desktop-compatibility.ts +9 -0
  192. package/src/config/schema/leaf-validators.ts +1 -0
  193. package/src/generated/compatibility-version.json +713 -285
  194. package/src/generated/model-metadata.ts +1 -1
  195. package/src/grok/grpc-web.ts +23 -1
  196. package/src/grok/reset-coupon-ledger.ts +75 -5
  197. package/src/grok/reset-coupons.ts +4 -2
  198. package/src/integrations/catalog-refresh.ts +1 -1
  199. package/src/integrations/config-io.ts +20 -2
  200. package/src/integrations/journal.ts +15 -1
  201. package/src/integrations/mutation-plan.ts +14 -7
  202. package/src/integrations/registry.ts +52 -11
  203. package/src/integrations/state.ts +19 -7
  204. package/src/integrations/store.ts +3 -0
  205. package/src/integrations/target.ts +42 -5
  206. package/src/integrations/writer.ts +24 -7
  207. package/src/lib/advisor-activation.ts +16 -0
  208. package/src/lib/app-owned-memory-stores.ts +4 -0
  209. package/src/lib/app-owned-memory.ts +11 -0
  210. package/src/lib/background-work.ts +65 -0
  211. package/src/lib/bounded-file-read.ts +58 -0
  212. package/src/lib/bun-path-runtime.d.mts +10 -1
  213. package/src/lib/bun-path-runtime.mjs +62 -3
  214. package/src/lib/desktop-proxy-route.ts +30 -0
  215. package/src/lib/desktop-upstream-tunnel.ts +107 -0
  216. package/src/lib/errors.ts +7 -0
  217. package/src/lib/json-byte-size.ts +31 -0
  218. package/src/lib/local-internal-call-capability.ts +55 -0
  219. package/src/lib/optional-shutdown-hooks.ts +35 -0
  220. package/src/lib/outbound-credential-registry.ts +210 -0
  221. package/src/lib/package-tree-integrity.ts +2 -2
  222. package/src/lib/passive-read.ts +35 -0
  223. package/src/lib/shadow-call.ts +44 -5
  224. package/src/lib/socks5-fetch.ts +48 -71
  225. package/src/lib/socks5-handshake.ts +112 -0
  226. package/src/lib/translator-budget.ts +7 -1
  227. package/src/link/codex-settings.ts +155 -0
  228. package/src/link/management-routes.ts +89 -0
  229. package/src/link/remote-probe.ts +38 -0
  230. package/src/link/ssh-argv.ts +26 -5
  231. package/src/link/ssh-runner.ts +58 -27
  232. package/src/link/status-projection.ts +22 -4
  233. package/src/link/supervisor.ts +144 -49
  234. package/src/link/tunnel-state.ts +11 -4
  235. package/src/oauth/anthropic-continuity.ts +4 -3
  236. package/src/oauth/anthropic-identity.ts +13 -2
  237. package/src/oauth/anthropic-model-quota.ts +13 -1
  238. package/src/oauth/anthropic-routing.ts +230 -23
  239. package/src/oauth/anthropic.ts +18 -4
  240. package/src/oauth/generic-account-failover.ts +29 -5
  241. package/src/oauth/google-antigravity.ts +24 -7
  242. package/src/oauth/index.ts +28 -17
  243. package/src/oauth/kiro-account-identity.ts +106 -0
  244. package/src/oauth/kiro-device-login.ts +2 -1
  245. package/src/oauth/kiro.ts +55 -6
  246. package/src/oauth/plan.ts +29 -0
  247. package/src/oauth/store.ts +40 -3
  248. package/src/oauth/types.ts +2 -0
  249. package/src/plugins/loader.ts +11 -0
  250. package/src/protocols/codecs/chat-ir.ts +467 -0
  251. package/src/protocols/codecs/chat.ts +10 -1
  252. package/src/protocols/codecs/messages-ir.ts +500 -0
  253. package/src/protocols/features.ts +10 -2
  254. package/src/protocols/path.ts +11 -1
  255. package/src/protocols/plan-snapshot.ts +39 -16
  256. package/src/protocols/plan.ts +9 -1
  257. package/src/protocols/settings.ts +8 -0
  258. package/src/providers/anthropic-billing-ownership.ts +74 -0
  259. package/src/providers/anthropic-billing-recovery.ts +31 -0
  260. package/src/providers/anthropic-billing-refusal.ts +55 -0
  261. package/src/providers/anthropic-reset-admission.ts +62 -0
  262. package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
  263. package/src/providers/antigravity-account-roster.ts +102 -0
  264. package/src/providers/antigravity-models.ts +85 -7
  265. package/src/providers/api-key-resolve.ts +17 -2
  266. package/src/providers/key-failover.ts +52 -5
  267. package/src/providers/model-rename-fields.ts +2 -0
  268. package/src/providers/model-rename-migration.ts +5 -3
  269. package/src/providers/model-rename-startup.ts +5 -3
  270. package/src/providers/quota/account-cache.ts +11 -0
  271. package/src/providers/quota/anthropic-account-quota.ts +4 -2
  272. package/src/providers/quota/report-cache.ts +14 -0
  273. package/src/providers/quota/vendor-probes-oauth.ts +3 -3
  274. package/src/providers/quota.ts +4 -1
  275. package/src/providers/registry/entries-core.ts +21 -20
  276. package/src/providers/registry/entries-extended.ts +19 -1
  277. package/src/providers/registry/model-seeds.ts +17 -0
  278. package/src/providers/request-pacing.ts +9 -0
  279. package/src/providers/stale-no-vision-seed-migration.ts +72 -0
  280. package/src/responses/compaction.ts +14 -0
  281. package/src/responses/parser.ts +37 -20
  282. package/src/responses/state.ts +32 -29
  283. package/src/routing/history/build-file.ts +92 -0
  284. package/src/routing/history/indexer.ts +248 -216
  285. package/src/routing/history/ingest.ts +238 -0
  286. package/src/routing/history/rebuild-job.ts +111 -0
  287. package/src/routing/history/rebuild-protocol.ts +15 -0
  288. package/src/routing/history/rebuild-worker.ts +34 -0
  289. package/src/server/admission-model-scope.ts +4 -1
  290. package/src/server/antigravity-direct-eligibility.ts +208 -0
  291. package/src/server/auth-cors.ts +46 -0
  292. package/src/server/chat-completions.ts +75 -9
  293. package/src/server/claude-messages.ts +110 -20
  294. package/src/server/configured-credentials.ts +194 -0
  295. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  296. package/src/server/index/link-management.ts +58 -0
  297. package/src/server/index/optional-listeners.ts +5 -1
  298. package/src/server/index/serve-options.ts +15 -2
  299. package/src/server/index/startup-warnings.ts +9 -0
  300. package/src/server/index/websocket-handler.ts +9 -0
  301. package/src/server/index.ts +13 -10
  302. package/src/server/inference/client-encoder-delivery.ts +6 -5
  303. package/src/server/lifecycle.ts +2 -1
  304. package/src/server/login-shell-path.ts +146 -0
  305. package/src/server/management/advisor-routes.ts +191 -0
  306. package/src/server/management/agent-settings-routes.ts +50 -14
  307. package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
  308. package/src/server/management/codex-agent-role-routes.ts +19 -6
  309. package/src/server/management/codex-prompt-routes.ts +136 -5
  310. package/src/server/management/codex-role-auto-assign.ts +1 -1
  311. package/src/server/management/companion-routes.ts +4 -0
  312. package/src/server/management/config-routes.ts +2 -2
  313. package/src/server/management/context.ts +4 -0
  314. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  315. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  316. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  317. package/src/server/management/grok-coupon-routes.ts +78 -27
  318. package/src/server/management/link-enrollment.ts +192 -0
  319. package/src/server/management/link-route-state.ts +185 -0
  320. package/src/server/management/link-routes.ts +82 -402
  321. package/src/server/management/model-routes.ts +2 -2
  322. package/src/server/management/model-rows.ts +3 -1
  323. package/src/server/management/protocol-settings-patch.ts +1 -0
  324. package/src/server/management/provider-overwrite-carry.ts +10 -0
  325. package/src/server/management/provider-routes.ts +1 -0
  326. package/src/server/management/route-registry.ts +16 -3
  327. package/src/server/management/shared.ts +3 -0
  328. package/src/server/management/sibling-guard.ts +1 -0
  329. package/src/server/management-api.ts +9 -1
  330. package/src/server/messages-antigravity-direct.ts +195 -0
  331. package/src/server/messages-native.ts +73 -43
  332. package/src/server/relay-eager.ts +2 -0
  333. package/src/server/request-log-filter.ts +6 -1
  334. package/src/server/request-log-terminal-status.ts +63 -6
  335. package/src/server/request-log.ts +7 -0
  336. package/src/server/responses/adapter-continuation.ts +35 -18
  337. package/src/server/responses/adapter-delivery.ts +24 -2
  338. package/src/server/responses/adapter-dispatch.ts +261 -81
  339. package/src/server/responses/advisor-plan-slot.ts +27 -0
  340. package/src/server/responses/advisor-slot.ts +336 -0
  341. package/src/server/responses/code-mode-collab-surface.ts +313 -0
  342. package/src/server/responses/codex-ws-exchange.ts +147 -6
  343. package/src/server/responses/codex-ws-wire.ts +203 -0
  344. package/src/server/responses/collaboration.ts +36 -7
  345. package/src/server/responses/compact.ts +126 -48
  346. package/src/server/responses/context-overflow.ts +13 -1
  347. package/src/server/responses/core-codex-account.ts +105 -1
  348. package/src/server/responses/core-combo-failure.ts +24 -1
  349. package/src/server/responses/core-combo.ts +28 -2
  350. package/src/server/responses/core-errors.ts +29 -1
  351. package/src/server/responses/core-lifetime.ts +2 -0
  352. package/src/server/responses/core-normalize.ts +11 -0
  353. package/src/server/responses/core-options.ts +48 -0
  354. package/src/server/responses/core-replay.ts +7 -4
  355. package/src/server/responses/core.ts +1 -1
  356. package/src/server/responses/deferred-collab-surface.ts +158 -0
  357. package/src/server/responses/deferred-send-reporter.ts +25 -0
  358. package/src/server/responses/fetch-helpers.ts +12 -1
  359. package/src/server/responses/passthrough-delivery.ts +20 -11
  360. package/src/server/responses/passthrough-dispatch.ts +29 -7
  361. package/src/server/responses/request-prepare.ts +74 -9
  362. package/src/server/responses/request-transport.ts +22 -8
  363. package/src/server/responses/run-turn-execution.ts +70 -8
  364. package/src/server/responses/sidecar-execution.ts +89 -5
  365. package/src/server/responses/terminal-guard.ts +2 -1
  366. package/src/server/responses/ws-upstream.ts +9 -1
  367. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  368. package/src/server/startup-action-control.ts +38 -4
  369. package/src/server/startup-health-cache.ts +5 -0
  370. package/src/server/system-env-shell.ts +7 -2
  371. package/src/server/system-env.ts +11 -1
  372. package/src/service/cli.ts +5 -1
  373. package/src/service/desktop-startup.ts +161 -34
  374. package/src/service/desktop-supervision.d.mts +27 -1
  375. package/src/service/desktop-supervision.mjs +111 -5
  376. package/src/service/health-sentinel-launch.ts +176 -0
  377. package/src/service/health-sentinel-main.ts +466 -0
  378. package/src/service/health-sentinel-policy.ts +241 -0
  379. package/src/service/health-sentinel-probe.ts +71 -0
  380. package/src/service/health-sentinel-windows.ts +87 -0
  381. package/src/service/linux-linger.ts +20 -0
  382. package/src/service/logging.ts +72 -0
  383. package/src/tray/windows-tray.ps1 +43 -24
  384. package/src/types/config.ts +68 -6
  385. package/src/types/provider.ts +7 -0
  386. package/src/types/request.ts +15 -0
  387. package/src/types/tools.ts +2 -0
  388. package/src/update/async-check.ts +1 -1
  389. package/src/update/badge.ts +2 -2
  390. package/src/update/index.ts +88 -15
  391. package/src/update/job-supervised-restart.ts +107 -0
  392. package/src/update/job.ts +32 -32
  393. package/src/update/notify.ts +1 -1
  394. package/src/update/refresh-scheduler.ts +2 -2
  395. package/src/update/supervised-recovery.d.mts +27 -0
  396. package/src/update/supervised-recovery.mjs +157 -0
  397. package/src/usage/cache-diagnostic.ts +2 -0
  398. package/src/usage/expected-prices.ts +16 -0
  399. package/src/usage/log.ts +19 -1
  400. package/src/web-search/gemini-executor.ts +2 -2
  401. package/assets/sponsors/packycode.png +0 -0
  402. package/gui/dist/assets/App-Ba8uG696.js +0 -54
  403. package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
  404. package/gui/dist/assets/index-BHRZdJuv.js +0 -94
  405. package/gui/dist/assets/quota-summary-DiJO6sYY.js +0 -1
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Core-owned registration slot for the optional advisor subsystem (src/advisor).
3
+ *
4
+ * This file is the ONLY thing the core Responses path knows about the advisor. It holds the
5
+ * structural plan interface and the event-stream guard — pure protocol machinery over
6
+ * src/types — and imports nothing from src/advisor at runtime. The optional subsystem registers
7
+ * a factory through advisor-plan-slot at host activation; an advisor-disabled install therefore executes no advisor
8
+ * code and imports no advisor module (same seam discipline as src/lab).
9
+ *
10
+ * Guard semantics (mirrors guardTerminalEventStream):
11
+ * - synthetic `advisor` tool-call events are HELD — the Codex client never sees the tool, the
12
+ * call, or its arguments;
13
+ * - on a clean `done`, each held call is consulted through the plan, the advice is appended as a
14
+ * paired assistant-toolCall + toolResult message pair, and the worker is re-dispatched via the
15
+ * SAME continuation machinery the terminal guard uses;
16
+ * - real (non-advisor) tool calls end interception for the leg: the turn belongs to the client;
17
+ * - usage from intercepted legs is merged into the final terminal event so worker accounting
18
+ * stays complete; the advisor's own usage is a separate loopback request and never merges here;
19
+ * - consultations and worker continuations have separate hard per-request bounds. Exhaustion
20
+ * removes the tool; one final limit-result continuation is allowed, then a typed error ends it.
21
+ */
22
+ import type {
23
+ AdapterEvent,
24
+ OcxAssistantContentPart,
25
+ OcxAssistantMessage,
26
+ OcxMessage,
27
+ OcxParsedRequest,
28
+ OcxToolResultMessage,
29
+ OcxUsage,
30
+ } from "../../types";
31
+ import { mergeUsage } from "./terminal-guard";
32
+
33
+ export const ADVISOR_TOOL_NAME = "advisor";
34
+
35
+ /** Hard bound on advisor consultations per worker request (recursion guard). */
36
+ export const MAX_ADVISOR_CONSULTATIONS_PER_REQUEST = 3;
37
+ /** At most one final worker continuation after consultation exhaustion. */
38
+ export const MAX_ADVISOR_CONTINUATIONS_PER_REQUEST = MAX_ADVISOR_CONSULTATIONS_PER_REQUEST + 1;
39
+
40
+ /** Per-leg retention caps for rebuilding the assistant message (see terminal-guard's bounded retention). */
41
+ const MAX_LEG_TEXT_CHARS = 16 * 1_024;
42
+ const MAX_LEG_THINKING_CHARS = 16 * 1_024;
43
+
44
+ /** Outcome of one consultation, as the plan reports it to the guard. */
45
+ export interface AdvisorConsultOutcome {
46
+ ok: boolean;
47
+ /** Formatted, wrapper-marked advice text ready to hand to the worker. */
48
+ content: string;
49
+ isError: boolean;
50
+ /** The consultation was cancelled by the caller (client abort) — not a provider failure. */
51
+ cancelled?: boolean;
52
+ /**
53
+ * Operator config blocked the consultation before any outbound call. Not a provider
54
+ * failure: preflight must release a claim instead of recording a cooldown.
55
+ */
56
+ blocked?: "consent" | "settings";
57
+ }
58
+
59
+ /** The structural plan the optional advisor subsystem registers per request. */
60
+ export interface AdvisorPlan {
61
+ /**
62
+ * Run one consultation for the current conversation state. Implementations own dedup,
63
+ * logging, and usage accounting; the guard only consumes the outcome.
64
+ */
65
+ consult(
66
+ parsed: OcxParsedRequest,
67
+ reason: "manual",
68
+ question: string | undefined,
69
+ ): Promise<AdvisorConsultOutcome>;
70
+ /**
71
+ * Runtime-owned text for a failed or limited consultation. The guard never composes failure
72
+ * prose itself: the implementation neutralizes untrusted text so no guard path can emit
73
+ * something the provenance detector would read as genuine advice.
74
+ */
75
+ formatUnavailable(kind: "manual" | "limit", error: string): string;
76
+ }
77
+
78
+ interface HeldAdvisorCall {
79
+ id: string;
80
+ name: string;
81
+ argsBuf: string;
82
+ closed: boolean;
83
+ providerMetadata?: import("../../types").OcxProviderOpaqueToolCallMetadata;
84
+ }
85
+
86
+ function parseAdvisorArgs(argsBuf: string): { question?: string } {
87
+ if (argsBuf.trim() === "") return {};
88
+ try {
89
+ const parsed: unknown = JSON.parse(argsBuf);
90
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
91
+ const question = (parsed as { question?: unknown }).question;
92
+ if (typeof question === "string" && question.trim() !== "") return { question: question.slice(0, 2_000) };
93
+ }
94
+ } catch { /* malformed args → no focus question */ }
95
+ return {};
96
+ }
97
+
98
+ function assistantMessageFromLeg(
99
+ events: readonly AdapterEvent[],
100
+ held: readonly HeldAdvisorCall[],
101
+ timestamp: number,
102
+ ): OcxAssistantMessage | undefined {
103
+ let text = "";
104
+ let thinking = "";
105
+ let signature: string | undefined;
106
+ const redacted: string[] = [];
107
+ for (const event of events) {
108
+ if (event.type === "text_delta") text += event.text;
109
+ else if (event.type === "thinking_delta") thinking += event.thinking;
110
+ else if (event.type === "thinking_signature") signature = event.signature;
111
+ else if (event.type === "redacted_thinking") redacted.push(event.data);
112
+ }
113
+ if (text.length > MAX_LEG_TEXT_CHARS) text = text.slice(0, MAX_LEG_TEXT_CHARS);
114
+ if (thinking.length > MAX_LEG_THINKING_CHARS) thinking = thinking.slice(0, MAX_LEG_THINKING_CHARS);
115
+ const content: OcxAssistantContentPart[] = [];
116
+ if (thinking || signature || redacted.length > 0) {
117
+ content.push({ type: "thinking", thinking, ...(signature ? { signature } : {}), ...(redacted.length > 0 ? { redacted } : {}) });
118
+ }
119
+ if (text) content.push({ type: "text", text });
120
+ for (const call of held) {
121
+ if (!call.closed) continue;
122
+ content.push({
123
+ type: "toolCall",
124
+ id: call.id,
125
+ name: call.name,
126
+ arguments: parseAdvisorArgs(call.argsBuf),
127
+ ...(call.providerMetadata ? { providerMetadata: call.providerMetadata } : {}),
128
+ });
129
+ }
130
+ if (content.length === 0) return undefined;
131
+ return { role: "assistant", content, timestamp };
132
+ }
133
+
134
+ function advisorToolResult(
135
+ call: HeldAdvisorCall,
136
+ outcome: AdvisorConsultOutcome,
137
+ timestamp: number,
138
+ ): OcxToolResultMessage {
139
+ return {
140
+ role: "toolResult",
141
+ toolCallId: call.id,
142
+ toolName: ADVISOR_TOOL_NAME,
143
+ content: outcome.content,
144
+ isError: outcome.isError,
145
+ timestamp,
146
+ };
147
+ }
148
+
149
+ export interface AdvisorGuardOptions {
150
+ parsed: OcxParsedRequest;
151
+ plan: AdvisorPlan;
152
+ firstEvents: AsyncIterable<AdapterEvent>;
153
+ /** One bounded worker continuation re-dispatch (same machinery as the terminal guard). */
154
+ continuation: (parsed: OcxParsedRequest) => AsyncIterable<AdapterEvent> | Promise<AsyncIterable<AdapterEvent>>;
155
+ }
156
+
157
+ /**
158
+ * Wrap a worker event stream with advisor interception. Registered on the parsed request as
159
+ * `_advisorGuard` by the sidecar planner; adapter delivery applies it when present.
160
+ */
161
+ export function createAdvisorStreamGuard(options: AdvisorGuardOptions): AsyncGenerator<AdapterEvent> {
162
+ const guard = createAdvisorGuard(options.plan);
163
+ return guard(options);
164
+ }
165
+ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequest["_advisorGuard"]> {
166
+ // Shared across invocations of this request's guard, including an empty-completion retry.
167
+ let consultations = 0;
168
+ let continuations = 0;
169
+ let finalContinuationUsed = false;
170
+ return async function* guardAdvisorStream(options: Omit<AdvisorGuardOptions, "plan">): AsyncGenerator<AdapterEvent> {
171
+ const maxConsultations = MAX_ADVISOR_CONSULTATIONS_PER_REQUEST;
172
+ let parsed = options.parsed;
173
+ let accumulatedUsage: OcxUsage | undefined;
174
+ let source: AsyncIterable<AdapterEvent> = options.firstEvents;
175
+
176
+ while (true) {
177
+ const held: HeldAdvisorCall[] = [];
178
+ const legEvents: AdapterEvent[] = [];
179
+ let legTextChars = 0;
180
+ let legThinkingChars = 0;
181
+ let pending: HeldAdvisorCall | null = null;
182
+ // While a tool call is open, its delta/end events belong to that call. Advisor calls are
183
+ // held from the output; real calls pass through untouched.
184
+ let holdingCurrent = false;
185
+ let hasRealToolCall = false;
186
+ let terminalConsumed = false;
187
+ let terminalEvent: Extract<AdapterEvent, { type: "done" }> | undefined;
188
+
189
+ for await (const event of source) {
190
+ if (event.type === "tool_call_start") {
191
+ if (pending) { held.push(pending); pending = null; }
192
+ pending = { id: event.id, name: event.name, argsBuf: "", closed: false, ...(event.providerMetadata ? { providerMetadata: event.providerMetadata } : {}) };
193
+ holdingCurrent = event.name === ADVISOR_TOOL_NAME;
194
+ if (!holdingCurrent) hasRealToolCall = true;
195
+ else continue;
196
+ } else if (event.type === "tool_call_delta") {
197
+ if (pending) pending.argsBuf += event.arguments;
198
+ if (holdingCurrent) continue;
199
+ } else if (event.type === "tool_call_end") {
200
+ if (pending) {
201
+ pending.closed = true;
202
+ if (pending.name === ADVISOR_TOOL_NAME) held.push(pending);
203
+ pending = null;
204
+ }
205
+ if (holdingCurrent) {
206
+ holdingCurrent = false;
207
+ continue;
208
+ }
209
+ } else if (event.type === "done") {
210
+ if (pending) { held.push(pending); pending = null; }
211
+ terminalEvent = event;
212
+ terminalConsumed = true;
213
+ break;
214
+ } else if (event.type === "incomplete" || event.type === "error") {
215
+ // A broken leg cannot safely anchor a continuation: surface the terminal as-is.
216
+ if (pending) { pending = null; }
217
+ const usage = mergeUsage(accumulatedUsage, event.usage);
218
+ yield usage ? { ...event, usage } : event;
219
+ return;
220
+ } else {
221
+ // Retain (bounded) the leg's visible content so the rebuilt assistant message is complete.
222
+ if (event.type === "text_delta") {
223
+ legTextChars += event.text.length;
224
+ if (legTextChars <= MAX_LEG_TEXT_CHARS) legEvents.push(event);
225
+ } else if (event.type === "thinking_delta") {
226
+ legThinkingChars += event.thinking.length;
227
+ if (legThinkingChars <= MAX_LEG_THINKING_CHARS) legEvents.push(event);
228
+ } else if (
229
+ event.type === "thinking_signature"
230
+ || event.type === "redacted_thinking"
231
+ ) {
232
+ legEvents.push(event);
233
+ }
234
+ }
235
+ yield event;
236
+ }
237
+
238
+ const advisorCalls = held.filter(call => call.name === ADVISOR_TOOL_NAME && call.closed);
239
+ const shouldIntercept = terminalConsumed
240
+ && terminalEvent?.stopReason !== "max_tokens"
241
+ && terminalEvent?.stopReason !== "content_filter"
242
+ && advisorCalls.length > 0
243
+ && !hasRealToolCall;
244
+
245
+ if (!shouldIntercept) {
246
+ // Plain leg: surface the terminal with merged usage from any earlier intercepted legs.
247
+ if (terminalEvent) {
248
+ const usage = mergeUsage(accumulatedUsage, terminalEvent.usage);
249
+ yield usage ? { ...terminalEvent, usage } : terminalEvent;
250
+ }
251
+ return;
252
+ }
253
+
254
+ // Consume this leg's done — the continuation replaces it.
255
+ accumulatedUsage = mergeUsage(accumulatedUsage, terminalEvent?.usage);
256
+ if (continuations >= MAX_ADVISOR_CONTINUATIONS_PER_REQUEST || finalContinuationUsed) {
257
+ yield {
258
+ type: "error",
259
+ status: 502,
260
+ errorType: "advisor_continuation_limit",
261
+ message: "Advisor worker continuation limit reached; no further worker calls were sent.",
262
+ ...(accumulatedUsage ? { usage: accumulatedUsage } : {}),
263
+ };
264
+ return;
265
+ }
266
+ // A repeated call after tool removal gets one last paired limit result, never a loop.
267
+ const isFinalContinuation = consultations >= maxConsultations;
268
+ // Reserve before any consultation await, so simultaneous guard entries share the bound.
269
+ continuations += 1;
270
+ finalContinuationUsed ||= isFinalContinuation;
271
+ const timestamp = Date.now();
272
+ const assistant = assistantMessageFromLeg(legEvents, advisorCalls, timestamp);
273
+ const messages: OcxMessage[] = [...parsed.context.messages];
274
+ if (assistant) messages.push(assistant);
275
+
276
+ for (const call of advisorCalls) {
277
+ if (consultations >= maxConsultations) {
278
+ // Runtime-owned limit text: not advice, and never a genuine advice wrapper.
279
+ messages.push(advisorToolResult(call, {
280
+ ok: false,
281
+ isError: true,
282
+ content: plan.formatUnavailable("limit", "consultation limit reached for this request"),
283
+ }, timestamp));
284
+ continue;
285
+ }
286
+ consultations += 1;
287
+ const args = parseAdvisorArgs(call.argsBuf);
288
+ // Later calls in the same leg must see what came before them: the rebuilt worker turn
289
+ // and every earlier advisor result in this leg's accumulated messages.
290
+ const consultParsed: OcxParsedRequest = {
291
+ ...parsed,
292
+ context: { ...parsed.context, messages },
293
+ };
294
+ let outcome: AdvisorConsultOutcome;
295
+ try {
296
+ outcome = await plan.consult(consultParsed, "manual", args.question);
297
+ } catch (error) {
298
+ // Runtime-owned failure text only: the implementation neutralizes untrusted exception
299
+ // text so it can never forge a genuine advice wrapper.
300
+ outcome = {
301
+ ok: false,
302
+ isError: true,
303
+ content: plan.formatUnavailable("manual", error instanceof Error ? error.message : String(error)),
304
+ };
305
+ }
306
+ messages.push(advisorToolResult(call, outcome, timestamp));
307
+ }
308
+
309
+ const exhausted = consultations >= maxConsultations;
310
+ const tools = exhausted
311
+ ? parsed.context.tools?.filter(tool => !tool.advisor && tool.name !== ADVISOR_TOOL_NAME)
312
+ : parsed.context.tools;
313
+ const choice = parsed.options.toolChoice;
314
+ const nextParsed: OcxParsedRequest = {
315
+ ...parsed,
316
+ context: { ...parsed.context, messages, tools },
317
+ options: exhausted && ((typeof choice === "object" && choice !== null && "name" in choice && choice.name === ADVISOR_TOOL_NAME)
318
+ || (choice === "required" && !tools?.length))
319
+ ? { ...parsed.options, toolChoice: "auto" }
320
+ : parsed.options,
321
+ };
322
+ parsed = nextParsed;
323
+ yield { type: "assistant_boundary" };
324
+ try {
325
+ source = await options.continuation(parsed);
326
+ } catch (error) {
327
+ yield {
328
+ type: "error",
329
+ message: error instanceof Error ? error.message : String(error),
330
+ ...(accumulatedUsage ? { usage: accumulatedUsage } : {}),
331
+ };
332
+ return;
333
+ }
334
+ }
335
+ };
336
+ }
@@ -0,0 +1,313 @@
1
+ /**
2
+ * V1 collaboration evidence nested inside Codex's code-mode `exec` tool (#6844).
3
+ *
4
+ * In code-mode-only turns codex-rs does not put the collaboration tools on the wire. When they are
5
+ * ENABLED it renders each one into the custom `exec` description instead
6
+ * (code-mode-protocol/src/description.rs, build_exec_tool_description): a top-level heading line,
7
+ * the tool's own description, then an "exec tool declaration:" ts fence holding
8
+ * "declare const tools: { <name>(args: ...): Promise<...>; };". A namespaced tool is flattened to
9
+ * <namespace>__<name> (tools/src/code_mode.rs, code_mode_name_for_tool_name), and V1 always
10
+ * registers under the fixed namespace multi_agent_v1, so a rendered V1 surface reads
11
+ * multi_agent_v1__spawn_agent.
12
+ *
13
+ * That rendering is the only proof of V1 inside exec itself. When the model supports tool search,
14
+ * codex-rs registers V1 as DEFERRED and leaves it out of the description entirely, and every
15
+ * OpenCodex catalog row advertises tool search. Such a turn carries no V1 evidence here; a bare
16
+ * exec, the ALL_TOOLS mention, and the saved OpenCodex mode prove nothing about the conversation
17
+ * (codex-rs pins the version per session). The deferred case has its own, separate evidence source
18
+ * in deferred-collab-surface.ts, which uses this file's genuine-exec check as a host guard.
19
+ *
20
+ * A tool description is copied into exec verbatim, so one that reproduces a complete top-level V1
21
+ * section is indistinguishable from the host's own rendering. This parser cannot authenticate the
22
+ * host; it only refuses everything short of that exact shape.
23
+ */
24
+ import { CODE_MODE_EXEC_TOOL_NAME, declaresCodeModeExec, type OcxTool } from "../../types";
25
+
26
+ export type CodeModeV1Evidence = "v1" | "ambiguous" | null;
27
+
28
+ /** What a request's code-mode exec proves about collaboration, before any combination policy. */
29
+ export interface CodeModeExecAnalysis {
30
+ /** A codex-rs code-mode exec is declared: bare, freeform, template-led, no bare shell bridge. */
31
+ genuineExec: boolean;
32
+ /** That exec renders a complete multi_agent_v1__spawn_agent section. */
33
+ renderedV1: boolean;
34
+ /** That exec mentions a spawn_agent or V2 companion that is not V1's, quoted or not. */
35
+ foreignCollabClaim: boolean;
36
+ }
37
+
38
+ /** First line of codex-rs EXEC_DESCRIPTION_TEMPLATE; an overridden or foreign exec fails closed. */
39
+ const CODE_MODE_EXEC_TEMPLATE_HEADER = "Run JavaScript code to orchestrate/compose tool calls";
40
+ const MULTI_AGENT_V1_SPAWN = "multi_agent_v1__spawn_agent";
41
+ const SPAWN = "spawn_agent";
42
+ const V2_COMPANIONS = ["send_message", "followup_task", "interrupt_agent", "list_agents"] as const;
43
+ const DECLARATION_LEAD = "exec tool declaration:";
44
+
45
+ const TOOL_HEADING = /^### `([A-Za-z_$][\w$]*)`(?: \(`([^`]+)`\))?$/;
46
+ const DECLARATION_FENCE = "```ts";
47
+ // CommonMark fences: up to three spaces of indentation, three or more backticks or tildes. A fence
48
+ // closes only on the same character, at least as long, with nothing but whitespace after it.
49
+ const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
50
+ const FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
51
+
52
+ interface Fence { char: string; length: number }
53
+
54
+ function openedFence(line: string): Fence | undefined {
55
+ const match = FENCE_OPEN.exec(line);
56
+ if (!match) return undefined;
57
+ const run = match[1]!;
58
+ // A backtick fence's info string may not contain a backtick (that line is inline code).
59
+ if (run[0] === "`" && match[2]!.includes("`")) return undefined;
60
+ return { char: run[0]!, length: run.length };
61
+ }
62
+
63
+ function closesFence(line: string, fence: Fence): boolean {
64
+ const match = FENCE_CLOSE.exec(line);
65
+ return match !== null && match[1]![0] === fence.char && match[1]!.length >= fence.length;
66
+ }
67
+
68
+ function isCodeModeExec(tool: OcxTool): boolean {
69
+ return tool.name === CODE_MODE_EXEC_TOOL_NAME && !tool.namespace && tool.freeform === true
70
+ && typeof tool.description === "string"
71
+ && tool.description.startsWith(CODE_MODE_EXEC_TEMPLATE_HEADER);
72
+ }
73
+
74
+ /** A nested tool rendered as a heading plus its own declaration; `raw` is the pre-normalization name. */
75
+ interface RenderedTool { name: string; raw: string }
76
+
77
+ /** The declaration fence codex-rs opens: three backticks and "ts". */
78
+ const DECLARATION_FENCE_SHAPE: Fence = { char: "`", length: 3 };
79
+
80
+ const DECLARATION_PREFIX = "declare const tools: { ";
81
+ const PROMISE_SEPARATOR = "): Promise<";
82
+ const DECLARATION_SUFFIX = ">; };";
83
+ // Bounds keep a caller-controlled description linear work: a rendered listing is a heading, a
84
+ // description, and a small declaration per tool, so anything this large cannot be the codex-rs
85
+ // shape this parser exists to recognize.
86
+ const MAX_DESCRIPTION_LENGTH = 1 << 20;
87
+ const MAX_DECLARATION_LENGTH = 64 * 1024;
88
+
89
+ const isSpace = (ch: string | undefined): boolean => ch === undefined || /\s/.test(ch);
90
+
91
+ /**
92
+ * The declared tool name when body is a complete declaration as render_code_mode_sample emits it,
93
+ * single- or multi-line, else undefined. Fixed boundaries plus one forward scan that balances the
94
+ * parameter list's brackets and the return type's generics: no backtracking, so a fence body full
95
+ * of candidate separators stays linear work.
96
+ */
97
+ function completeDeclarationName(body: readonly string[]): string | undefined {
98
+ const text = body.join("\n");
99
+ if (text.length > MAX_DECLARATION_LENGTH) return undefined;
100
+ if (!text.startsWith(DECLARATION_PREFIX) || !text.endsWith(DECLARATION_SUFFIX)) return undefined;
101
+ const signature = text.slice(DECLARATION_PREFIX.length, -DECLARATION_SUFFIX.length);
102
+ const openParen = signature.indexOf("(");
103
+ const name = openParen > 0 ? signature.slice(0, openParen) : "";
104
+ if (!/^[A-Za-z_$][\w$]*$/.test(name)) return undefined;
105
+ // A named, typed parameter (`args` for functions, `input` for freeform) and a non-empty return type.
106
+ const rest = signature.slice(openParen + 1);
107
+ const typed = rest.startsWith("args: ") ? 6 : rest.startsWith("input: ") ? 7 : -1;
108
+ if (typed < 0 || isSpace(rest[typed])) return undefined;
109
+ // Quoted text and comments are opaque to delimiter matching. Each skip advances
110
+ // past its text once; an unterminated region cannot contain a closing declaration.
111
+ const isOpaqueTypeText = (at: number): boolean => {
112
+ const ch = rest[at];
113
+ return ch === '"' || ch === "'" || ch === "`"
114
+ || ch === "/" && (rest[at + 1] === "/" || rest[at + 1] === "*");
115
+ };
116
+ const skipTypeText = (at: number): number | undefined => {
117
+ const quote = rest[at]!;
118
+ if (quote === "/" && rest[at + 1] === "/") {
119
+ let i = at + 2;
120
+ while (i < rest.length && rest[i] !== "\n" && rest[i] !== "\r"
121
+ && rest[i] !== "\u2028" && rest[i] !== "\u2029") i += 1;
122
+ return i < rest.length ? i : undefined;
123
+ }
124
+ if (quote === "/" && rest[at + 1] === "*") {
125
+ const end = rest.indexOf("*/", at + 2);
126
+ return end < 0 ? undefined : end + 1;
127
+ }
128
+ let i = at + 1;
129
+ while (i < rest.length) {
130
+ const ch = rest[i]!;
131
+ if (ch === "\\") i += 2;
132
+ else if (ch === quote) return i;
133
+ else i += 1;
134
+ }
135
+ return undefined;
136
+ };
137
+ // The parameter list ends at the ')' that balances the opening '(': a ')' nested inside the
138
+ // parameter's own type (e.g. `args: (a: string) => Promise<void>`) cannot pose as the separator.
139
+ // The type must also say something before that ')': `args: )` or `args: /* note */)` names no type.
140
+ let depth = 1;
141
+ let close = -1;
142
+ let typeContent = false;
143
+ for (let at = typed; at < rest.length; at += 1) {
144
+ const ch = rest[at]!;
145
+ if (isOpaqueTypeText(at)) {
146
+ if (ch !== "/") typeContent = true; // a quoted literal is a type, a comment is not
147
+ const end = skipTypeText(at);
148
+ if (end === undefined) return undefined;
149
+ at = end;
150
+ continue;
151
+ }
152
+ if (ch === "(" || ch === "[" || ch === "{") depth += 1;
153
+ else if (ch === ")" || ch === "]" || ch === "}") depth -= 1;
154
+ if (depth === 0) { close = at; break; }
155
+ if (!isSpace(ch)) typeContent = true;
156
+ }
157
+ if (close < 0 || !typeContent || !rest.startsWith(PROMISE_SEPARATOR, close)) return undefined;
158
+ const head = rest[close + PROMISE_SEPARATOR.length];
159
+ if (head === ">" || isSpace(head)) return undefined;
160
+ // The return type runs to the stripped suffix, which already supplied exactly one '>' for the
161
+ // Promise's '<': generic depth must stay positive to the end and land at exactly 1 — a '>' that
162
+ // reaches zero ('Promise<unknown>; garbage') is surplus text, a depth left above 1
163
+ // ('Promise<Map<string, unknown') never closed its own generic, and a '=>' arrow never counts.
164
+ let generic = 1;
165
+ for (let at = close + PROMISE_SEPARATOR.length; at < rest.length; at += 1) {
166
+ const ch = rest[at]!;
167
+ if (isOpaqueTypeText(at)) {
168
+ const end = skipTypeText(at);
169
+ if (end === undefined) return undefined;
170
+ at = end;
171
+ continue;
172
+ }
173
+ if (ch === "<") generic += 1;
174
+ else if (ch === ">" && rest[at - 1] !== "=") generic -= 1;
175
+ if (generic === 0) return undefined;
176
+ }
177
+ return generic === 1 ? name : undefined;
178
+ }
179
+
180
+ /** codex-rs normalize_code_mode_identifier: invalid identifier characters become `_`. */
181
+ function normalizeCodeModeIdentifier(raw: string): string {
182
+ let identifier = "";
183
+ for (const [index, ch] of [...raw].entries()) {
184
+ const valid = index === 0 ? /[A-Za-z_$]/.test(ch) : /[A-Za-z0-9_$]/.test(ch);
185
+ identifier += valid ? ch : "_";
186
+ }
187
+ return identifier === "" ? "_" : identifier;
188
+ }
189
+
190
+ /** A heading as render_tool_heading emits it: a raw suffix only when normalization changed the name. */
191
+ function renderedHeading(match: RegExpExecArray): RenderedTool | undefined {
192
+ const name = match[1]!;
193
+ const raw = match[2];
194
+ if (raw === undefined) return { name, raw: name };
195
+ return raw !== name && normalizeCodeModeIdentifier(raw) === name ? { name, raw } : undefined;
196
+ }
197
+
198
+ /**
199
+ * Nested tools whose top-level heading is followed by the complete declaration codex-rs renders for
200
+ * the same name. A section ends at its own declaration, not at the next heading, because tool and
201
+ * namespace descriptions (V1 spawn's guidance, a configured usage hint) carry their own Markdown,
202
+ * including example headings and example declarations. Pending headings form a stack: a
203
+ * declaration is owned by the outermost pending heading with its name, which is removed together
204
+ * with anything opened after it; a declaration nobody owns is an example and leaves the stack
205
+ * alone. Lines inside a quoted fence can neither open a section nor declare a tool.
206
+ */
207
+ function renderedNestedTools(description: string): RenderedTool[] {
208
+ if (description.length > MAX_DESCRIPTION_LENGTH) return [];
209
+ const lines = description.split("\n");
210
+ const rendered: RenderedTool[] = [];
211
+ const pending: RenderedTool[] = [];
212
+ // Ascending indexes into `pending` per name, so a declaration finds its owner in one lookup
213
+ // instead of rescanning every pending heading.
214
+ const pendingByName = new Map<string, number[]>();
215
+ const dropPendingTo = (length: number): void => {
216
+ while (pending.length > length) {
217
+ const removed = pending.pop()!;
218
+ const indexes = pendingByName.get(removed.name)!;
219
+ indexes.pop();
220
+ if (indexes.length === 0) pendingByName.delete(removed.name);
221
+ }
222
+ };
223
+ let fence: Fence | undefined;
224
+ for (let i = 0; i < lines.length; i += 1) {
225
+ const line = lines[i]!;
226
+ if (fence) {
227
+ if (closesFence(line, fence)) fence = undefined;
228
+ continue;
229
+ }
230
+ const match = TOOL_HEADING.exec(line);
231
+ if (match) {
232
+ const heading = renderedHeading(match);
233
+ if (heading) {
234
+ const indexes = pendingByName.get(heading.name);
235
+ if (indexes) indexes.push(pending.length); else pendingByName.set(heading.name, [pending.length]);
236
+ pending.push(heading);
237
+ }
238
+ continue;
239
+ }
240
+ if (line === DECLARATION_LEAD && lines[i + 1] === DECLARATION_FENCE) {
241
+ let close = i + 2;
242
+ while (close < lines.length && !closesFence(lines[close]!, DECLARATION_FENCE_SHAPE)) close += 1;
243
+ if (close >= lines.length) return rendered; // unterminated: nothing after it is structure
244
+ const body = lines.slice(i + 2, close);
245
+ // The outermost pending heading with the declared name owns it, so a same-name heading quoted
246
+ // inside the tool's own description cannot take over the genuine section.
247
+ const declared = completeDeclarationName(body);
248
+ const owner = declared === undefined ? -1 : pendingByName.get(declared)?.[0] ?? -1;
249
+ if (owner >= 0) {
250
+ rendered.push(pending[owner]!);
251
+ dropPendingTo(owner);
252
+ }
253
+ i = close;
254
+ continue;
255
+ }
256
+ fence = openedFence(line);
257
+ }
258
+ return rendered;
259
+ }
260
+
261
+ /** A nested name that claims a collaboration surface other than V1's. */
262
+ function isForeignCollabName(name: string): boolean {
263
+ if (name.startsWith("mcp__")) return false; // MCP server tools are never native collaboration
264
+ if (name === MULTI_AGENT_V1_SPAWN) return false;
265
+ if (name === SPAWN || name.endsWith("__" + SPAWN)) return true;
266
+ return V2_COMPANIONS.some(companion => name === companion || name.endsWith("__" + companion));
267
+ }
268
+
269
+ /** Every name a heading or declaration line mentions, quoted or not. */
270
+ function mentionedToolNames(description: string): string[] {
271
+ const names: string[] = [];
272
+ for (const line of description.split("\n")) {
273
+ const heading = TOOL_HEADING.exec(line);
274
+ if (heading) {
275
+ names.push(heading[1]!);
276
+ if (heading[2] !== undefined) names.push(heading[2]);
277
+ }
278
+ for (const match of line.matchAll(/declare const tools: \{ ([A-Za-z_$][\w$]*)\(/g)) names.push(match[1]!);
279
+ }
280
+ return names;
281
+ }
282
+
283
+ /**
284
+ * Reads every genuine code-mode exec once. The two collaboration halves are deliberately
285
+ * asymmetric. V1 evidence needs a rendered section (a heading followed by its complete
286
+ * declaration). The contradiction check reads every heading and declaration line, inside fences or
287
+ * not and whoever "owns" it, so text copied into the listing can only push a turn toward null; it
288
+ * cannot hide another surface. The contradiction is reported even without rendered V1, because
289
+ * deferred V1 evidence must also yield to a rendered foreign surface.
290
+ */
291
+ export function analyzeCodeModeExec(tools: readonly OcxTool[] | undefined): CodeModeExecAnalysis {
292
+ const analysis: CodeModeExecAnalysis = { genuineExec: false, renderedV1: false, foreignCollabClaim: false };
293
+ if (!tools || tools.length === 0) return analysis;
294
+ if (!declaresCodeModeExec(new Set(tools.filter(tool => !tool.namespace).map(tool => tool.name)))) return analysis;
295
+ for (const tool of tools) {
296
+ if (!isCodeModeExec(tool)) continue;
297
+ analysis.genuineExec = true;
298
+ // A rendered V1 spawn heading carries no raw-name suffix: the identifier needs no normalization.
299
+ if (renderedNestedTools(tool.description).some(rendered => rendered.raw === MULTI_AGENT_V1_SPAWN)) analysis.renderedV1 = true;
300
+ if (mentionedToolNames(tool.description).some(isForeignCollabName)) analysis.foreignCollabClaim = true;
301
+ }
302
+ return analysis;
303
+ }
304
+
305
+ /**
306
+ * "v1" when a genuine code-mode exec renders the V1 spawn tool and nothing that claims another
307
+ * collaboration surface; "ambiguous" when it renders both; null when it proves nothing.
308
+ */
309
+ export function codeModeV1Evidence(tools: readonly OcxTool[] | undefined): CodeModeV1Evidence {
310
+ const analysis = analyzeCodeModeExec(tools);
311
+ if (!analysis.renderedV1) return null;
312
+ return analysis.foreignCollabClaim ? "ambiguous" : "v1";
313
+ }