@iislee/opencodex 2.11.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 (476) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +225 -0
  3. package/assets/architecture.png +0 -0
  4. package/assets/banner.png +0 -0
  5. package/assets/claude-code-models.gif +0 -0
  6. package/assets/codex-app-picker.png +0 -0
  7. package/bin/ocx.mjs +451 -0
  8. package/bin/package-main.mjs +9 -0
  9. package/gui/dist/assets/index-DTpMHS4F.js +67 -0
  10. package/gui/dist/assets/index-ZNVDE3C7.css +1 -0
  11. package/gui/dist/favicon.png +0 -0
  12. package/gui/dist/icons.svg +24 -0
  13. package/gui/dist/index.html +25 -0
  14. package/gui/dist/logo.png +0 -0
  15. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  16. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  17. package/gui/dist/provider-icons/antigravity.svg +1 -0
  18. package/gui/dist/provider-icons/claude-color.svg +1 -0
  19. package/gui/dist/provider-icons/claude.svg +1 -0
  20. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  21. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  22. package/gui/dist/provider-icons/copilot.svg +1 -0
  23. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  24. package/gui/dist/provider-icons/cursor.svg +2 -0
  25. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  26. package/gui/dist/provider-icons/discord.svg +1 -0
  27. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  28. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  29. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  30. package/gui/dist/provider-icons/gemini.svg +1 -0
  31. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  32. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  33. package/gui/dist/provider-icons/grok-color.svg +1 -0
  34. package/gui/dist/provider-icons/grok.svg +1 -0
  35. package/gui/dist/provider-icons/groq-color.svg +1 -0
  36. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  37. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  38. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  39. package/gui/dist/provider-icons/kiro.svg +14 -0
  40. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  41. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  42. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  43. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  44. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  45. package/gui/dist/provider-icons/openai.svg +1 -0
  46. package/gui/dist/provider-icons/opencode.svg +1 -0
  47. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  48. package/gui/dist/provider-icons/pi.svg +21 -0
  49. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  50. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  51. package/gui/dist/provider-icons/telegram.svg +1 -0
  52. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  53. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  54. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  55. package/package.json +102 -0
  56. package/src/AGENTS.md +28 -0
  57. package/src/adapters/anthropic-image-guard.ts +251 -0
  58. package/src/adapters/anthropic-image-normalize.ts +518 -0
  59. package/src/adapters/anthropic.ts +1003 -0
  60. package/src/adapters/azure.ts +36 -0
  61. package/src/adapters/base.ts +72 -0
  62. package/src/adapters/client-fingerprint.ts +59 -0
  63. package/src/adapters/cursor/arg-codec.ts +38 -0
  64. package/src/adapters/cursor/arg-normalize.ts +104 -0
  65. package/src/adapters/cursor/cursor-errors.ts +165 -0
  66. package/src/adapters/cursor/discovery.ts +276 -0
  67. package/src/adapters/cursor/effort-map.ts +127 -0
  68. package/src/adapters/cursor/exec-policy.ts +88 -0
  69. package/src/adapters/cursor/framing.ts +211 -0
  70. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  71. package/src/adapters/cursor/kv-store.ts +52 -0
  72. package/src/adapters/cursor/live-models.ts +153 -0
  73. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  74. package/src/adapters/cursor/live-transport.ts +1214 -0
  75. package/src/adapters/cursor/mcp-config.ts +42 -0
  76. package/src/adapters/cursor/mcp-manager.ts +333 -0
  77. package/src/adapters/cursor/message-mapper.ts +49 -0
  78. package/src/adapters/cursor/native-exec-common.ts +55 -0
  79. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  80. package/src/adapters/cursor/native-exec-fs.ts +329 -0
  81. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  82. package/src/adapters/cursor/native-exec-network.ts +43 -0
  83. package/src/adapters/cursor/native-exec-shell.ts +548 -0
  84. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  85. package/src/adapters/cursor/native-exec.ts +576 -0
  86. package/src/adapters/cursor/protobuf-events.ts +563 -0
  87. package/src/adapters/cursor/protobuf-request.ts +714 -0
  88. package/src/adapters/cursor/request-builder.ts +255 -0
  89. package/src/adapters/cursor/thread-continuity.ts +67 -0
  90. package/src/adapters/cursor/tool-definitions.ts +505 -0
  91. package/src/adapters/cursor/transport-retry.ts +132 -0
  92. package/src/adapters/cursor/transport.ts +57 -0
  93. package/src/adapters/cursor/types.ts +52 -0
  94. package/src/adapters/cursor.ts +196 -0
  95. package/src/adapters/google-antigravity-replay.ts +303 -0
  96. package/src/adapters/google-antigravity-wire.ts +108 -0
  97. package/src/adapters/google-errors.ts +85 -0
  98. package/src/adapters/google-http.ts +100 -0
  99. package/src/adapters/google-tool-schema.ts +173 -0
  100. package/src/adapters/google-truncation.ts +13 -0
  101. package/src/adapters/google-wire-compiler.ts +232 -0
  102. package/src/adapters/google.ts +758 -0
  103. package/src/adapters/identity.ts +44 -0
  104. package/src/adapters/image.ts +23 -0
  105. package/src/adapters/kiro-constants.ts +16 -0
  106. package/src/adapters/kiro-errors.ts +197 -0
  107. package/src/adapters/kiro-events.ts +179 -0
  108. package/src/adapters/kiro-images.ts +129 -0
  109. package/src/adapters/kiro-retry.ts +312 -0
  110. package/src/adapters/kiro-thinking.ts +96 -0
  111. package/src/adapters/kiro-tool-fallback.ts +36 -0
  112. package/src/adapters/kiro-tools.ts +215 -0
  113. package/src/adapters/kiro-truncation.ts +33 -0
  114. package/src/adapters/kiro-wire.ts +129 -0
  115. package/src/adapters/kiro.ts +1898 -0
  116. package/src/adapters/mimo-free.ts +263 -0
  117. package/src/adapters/openai-chat.ts +1005 -0
  118. package/src/adapters/openai-responses.ts +1137 -0
  119. package/src/adapters/run-turn-queue.ts +114 -0
  120. package/src/adapters/tool-catalog-nudge.ts +71 -0
  121. package/src/adapters/upstream-http-error.ts +48 -0
  122. package/src/bridge.ts +1619 -0
  123. package/src/chat/inbound.ts +295 -0
  124. package/src/chat/outbound.ts +765 -0
  125. package/src/claude/agents-inject.ts +243 -0
  126. package/src/claude/alias.ts +149 -0
  127. package/src/claude/auth-detect.ts +229 -0
  128. package/src/claude/auth-mode-migration.ts +32 -0
  129. package/src/claude/auth-mode.ts +62 -0
  130. package/src/claude/context-windows.ts +189 -0
  131. package/src/claude/desktop-3p-guard.ts +35 -0
  132. package/src/claude/desktop-3p-paths.ts +84 -0
  133. package/src/claude/desktop-3p.ts +381 -0
  134. package/src/claude/desktop-health.ts +26 -0
  135. package/src/claude/desktop-profile.ts +263 -0
  136. package/src/claude/gateway-cache.ts +70 -0
  137. package/src/claude/inbound-debug.ts +163 -0
  138. package/src/claude/inbound.ts +509 -0
  139. package/src/claude/model-info.ts +151 -0
  140. package/src/claude/outbound.ts +872 -0
  141. package/src/cli/access.ts +108 -0
  142. package/src/cli/account-api.ts +268 -0
  143. package/src/cli/account-auth.ts +223 -0
  144. package/src/cli/account-extended.ts +350 -0
  145. package/src/cli/account.ts +275 -0
  146. package/src/cli/agent-driven.ts +70 -0
  147. package/src/cli/agent.ts +184 -0
  148. package/src/cli/catalog-prewarm.ts +27 -0
  149. package/src/cli/claude-desktop.ts +188 -0
  150. package/src/cli/claude.ts +286 -0
  151. package/src/cli/codex-shim-autorestore.ts +45 -0
  152. package/src/cli/combo.ts +119 -0
  153. package/src/cli/config-command.ts +145 -0
  154. package/src/cli/debug.ts +228 -0
  155. package/src/cli/doctor.ts +930 -0
  156. package/src/cli/export-command.ts +187 -0
  157. package/src/cli/help.ts +354 -0
  158. package/src/cli/index.ts +1113 -0
  159. package/src/cli/init.ts +224 -0
  160. package/src/cli/integrations.ts +142 -0
  161. package/src/cli/interactive-confirm.ts +133 -0
  162. package/src/cli/internal-dispatch.ts +20 -0
  163. package/src/cli/models-runtime.ts +212 -0
  164. package/src/cli/models.ts +336 -0
  165. package/src/cli/observe.ts +117 -0
  166. package/src/cli/opencode.ts +586 -0
  167. package/src/cli/pi.ts +188 -0
  168. package/src/cli/provider-runtime.ts +162 -0
  169. package/src/cli/provider.ts +463 -0
  170. package/src/cli/runtime-api.ts +325 -0
  171. package/src/cli/star-prompt.ts +155 -0
  172. package/src/cli/status-oauth.ts +78 -0
  173. package/src/cli/status.ts +321 -0
  174. package/src/cli/sync-cloud.ts +283 -0
  175. package/src/cli/system-command.ts +112 -0
  176. package/src/cli/tray-proxy.ts +52 -0
  177. package/src/cli/v2.ts +173 -0
  178. package/src/cli.ts +10 -0
  179. package/src/clients/config-export.ts +377 -0
  180. package/src/clients/effective-status.ts +385 -0
  181. package/src/clients/probes/agy.ts +55 -0
  182. package/src/clients/probes/cc-switch.ts +110 -0
  183. package/src/clients/probes/claude.ts +90 -0
  184. package/src/clients/probes/codex.ts +125 -0
  185. package/src/clients/probes/grok.ts +29 -0
  186. package/src/clients/probes/opencode.ts +109 -0
  187. package/src/clients/probes/paseo.ts +55 -0
  188. package/src/clients/probes/pi.ts +55 -0
  189. package/src/cloud/onedrive-auth.ts +666 -0
  190. package/src/cloud/onedrive-graph.ts +108 -0
  191. package/src/cloud/settings.ts +75 -0
  192. package/src/cloud/sync.ts +212 -0
  193. package/src/cloud/types.ts +56 -0
  194. package/src/cloud/vault.ts +89 -0
  195. package/src/codex/account-id.ts +34 -0
  196. package/src/codex/account-label.ts +34 -0
  197. package/src/codex/account-lifecycle.ts +55 -0
  198. package/src/codex/account-namespace-match.ts +63 -0
  199. package/src/codex/account-namespaces.ts +149 -0
  200. package/src/codex/account-pause.ts +20 -0
  201. package/src/codex/account-runtime-state.ts +31 -0
  202. package/src/codex/account-store.ts +517 -0
  203. package/src/codex/account-usability.ts +20 -0
  204. package/src/codex/app-server-processes.ts +756 -0
  205. package/src/codex/auth-api.ts +1540 -0
  206. package/src/codex/auth-collision.ts +107 -0
  207. package/src/codex/auth-context.ts +352 -0
  208. package/src/codex/autostart-health.ts +149 -0
  209. package/src/codex/catalog/aggregation.ts +378 -0
  210. package/src/codex/catalog/bundled.ts +251 -0
  211. package/src/codex/catalog/effort.ts +355 -0
  212. package/src/codex/catalog/metadata.ts +180 -0
  213. package/src/codex/catalog/parsing.ts +456 -0
  214. package/src/codex/catalog/provider-fetch.ts +902 -0
  215. package/src/codex/catalog/sync.ts +620 -0
  216. package/src/codex/catalog.ts +12 -0
  217. package/src/codex/data/upstream-models.json +830 -0
  218. package/src/codex/exec-invocation.ts +22 -0
  219. package/src/codex/features.ts +969 -0
  220. package/src/codex/history-migration-guardian.ts +102 -0
  221. package/src/codex/history-provider.ts +776 -0
  222. package/src/codex/home.ts +206 -0
  223. package/src/codex/inject.ts +799 -0
  224. package/src/codex/injected-marker.ts +72 -0
  225. package/src/codex/journal.ts +163 -0
  226. package/src/codex/main-account-cache.ts +32 -0
  227. package/src/codex/main-account.ts +40 -0
  228. package/src/codex/model-cache.ts +227 -0
  229. package/src/codex/paths.ts +65 -0
  230. package/src/codex/plugins-doctor.ts +242 -0
  231. package/src/codex/pool-rotation.ts +225 -0
  232. package/src/codex/project-config-warnings.ts +411 -0
  233. package/src/codex/quota.ts +411 -0
  234. package/src/codex/refresh.ts +53 -0
  235. package/src/codex/routing.ts +1477 -0
  236. package/src/codex/runtime.ts +538 -0
  237. package/src/codex/shim.ts +1189 -0
  238. package/src/codex/subagent-defaults.ts +550 -0
  239. package/src/codex/subagent-model-fallback.ts +469 -0
  240. package/src/codex/sync.ts +130 -0
  241. package/src/codex/warmup.ts +192 -0
  242. package/src/codex/websocket-registry.ts +100 -0
  243. package/src/combos/failover.ts +140 -0
  244. package/src/combos/index.ts +41 -0
  245. package/src/combos/request.ts +62 -0
  246. package/src/combos/resolve.ts +232 -0
  247. package/src/combos/types.ts +326 -0
  248. package/src/config.ts +2356 -0
  249. package/src/generated/jawcode-model-metadata.ts +104 -0
  250. package/src/github/star-state.ts +203 -0
  251. package/src/grok/inject.ts +545 -0
  252. package/src/grok/status.ts +121 -0
  253. package/src/grok/sync.ts +103 -0
  254. package/src/grok/usage-hook/report.mjs +348 -0
  255. package/src/grok/usage-hook.ts +278 -0
  256. package/src/images/artifacts.ts +516 -0
  257. package/src/images/fulfill-video.ts +163 -0
  258. package/src/images/fulfill.ts +149 -0
  259. package/src/images/index.ts +4 -0
  260. package/src/images/loop.ts +829 -0
  261. package/src/images/plan.ts +133 -0
  262. package/src/images/synthetic-tool.ts +133 -0
  263. package/src/images/types.ts +41 -0
  264. package/src/images/xai-client.ts +141 -0
  265. package/src/images/xai-video-client.ts +163 -0
  266. package/src/index.ts +22 -0
  267. package/src/lib/abort.ts +146 -0
  268. package/src/lib/admin-secrets.ts +25 -0
  269. package/src/lib/admission.ts +83 -0
  270. package/src/lib/app-owned-memory-stores.ts +173 -0
  271. package/src/lib/app-owned-memory.ts +265 -0
  272. package/src/lib/bounded-body.ts +202 -0
  273. package/src/lib/bun-binary-validator.d.mts +3 -0
  274. package/src/lib/bun-binary-validator.mjs +18 -0
  275. package/src/lib/bun-runtime.ts +71 -0
  276. package/src/lib/bun-stream-caps.ts +126 -0
  277. package/src/lib/config-ownership.ts +360 -0
  278. package/src/lib/crash-guard.ts +344 -0
  279. package/src/lib/debug-log-buffer.ts +83 -0
  280. package/src/lib/debug-settings.ts +108 -0
  281. package/src/lib/debug.ts +31 -0
  282. package/src/lib/destination-policy.ts +316 -0
  283. package/src/lib/errors.ts +364 -0
  284. package/src/lib/eventstream-decoder.ts +253 -0
  285. package/src/lib/gcp-adc.ts +341 -0
  286. package/src/lib/injection-debug-log.ts +58 -0
  287. package/src/lib/open-url.ts +25 -0
  288. package/src/lib/pinned-http.ts +151 -0
  289. package/src/lib/privacy.ts +20 -0
  290. package/src/lib/process-control.ts +165 -0
  291. package/src/lib/provider-outbound.ts +170 -0
  292. package/src/lib/provider-url.ts +14 -0
  293. package/src/lib/proxy-env.ts +18 -0
  294. package/src/lib/redact.ts +105 -0
  295. package/src/lib/retry-after.ts +55 -0
  296. package/src/lib/service-secrets.ts +25 -0
  297. package/src/lib/shadow-call.ts +30 -0
  298. package/src/lib/sidecar-tracker.ts +52 -0
  299. package/src/lib/sse-decoder.ts +323 -0
  300. package/src/lib/state-store-registrations.ts +109 -0
  301. package/src/lib/state-store-sweeper.ts +184 -0
  302. package/src/lib/test-home-guard.ts +90 -0
  303. package/src/lib/token-estimate.ts +69 -0
  304. package/src/lib/translator-budget.ts +356 -0
  305. package/src/lib/upstream-retry.ts +239 -0
  306. package/src/lib/win-exec.ts +115 -0
  307. package/src/lib/win-paths.ts +68 -0
  308. package/src/lib/windows-elevation.ts +705 -0
  309. package/src/lib/windows-secret-acl.ts +514 -0
  310. package/src/lib/winsw.ts +375 -0
  311. package/src/oauth/anthropic-routing.ts +594 -0
  312. package/src/oauth/anthropic.ts +177 -0
  313. package/src/oauth/callback-server.ts +294 -0
  314. package/src/oauth/chatgpt.ts +150 -0
  315. package/src/oauth/cursor.ts +211 -0
  316. package/src/oauth/github-copilot.ts +428 -0
  317. package/src/oauth/google-antigravity.ts +230 -0
  318. package/src/oauth/health.ts +399 -0
  319. package/src/oauth/index.ts +1174 -0
  320. package/src/oauth/key-providers.ts +108 -0
  321. package/src/oauth/kimi.ts +213 -0
  322. package/src/oauth/kiro-credentials.ts +726 -0
  323. package/src/oauth/kiro.ts +577 -0
  324. package/src/oauth/local-token-detect.ts +121 -0
  325. package/src/oauth/log.ts +48 -0
  326. package/src/oauth/login-cli.ts +163 -0
  327. package/src/oauth/pkce.ts +15 -0
  328. package/src/oauth/store.ts +630 -0
  329. package/src/oauth/token-guardian.ts +303 -0
  330. package/src/oauth/types.ts +62 -0
  331. package/src/oauth/xai.ts +241 -0
  332. package/src/pi/extensions.ts +72 -0
  333. package/src/pi/home.ts +42 -0
  334. package/src/pi/index.ts +40 -0
  335. package/src/pi/models.ts +278 -0
  336. package/src/pi/packages.ts +219 -0
  337. package/src/pi/settings.ts +365 -0
  338. package/src/pi/status.ts +68 -0
  339. package/src/pi/sync.ts +75 -0
  340. package/src/providers/alibaba-region-backup.ts +75 -0
  341. package/src/providers/alibaba-region-migration.ts +156 -0
  342. package/src/providers/alibaba-region-startup.ts +36 -0
  343. package/src/providers/antigravity-models.ts +205 -0
  344. package/src/providers/api-keys.ts +140 -0
  345. package/src/providers/base-url-choices.ts +64 -0
  346. package/src/providers/context-cap.ts +65 -0
  347. package/src/providers/derive.ts +339 -0
  348. package/src/providers/free-directory.ts +184 -0
  349. package/src/providers/github-copilot-transport.ts +56 -0
  350. package/src/providers/key-failover.ts +203 -0
  351. package/src/providers/kiro-models.ts +67 -0
  352. package/src/providers/label.ts +19 -0
  353. package/src/providers/model-discovery.ts +356 -0
  354. package/src/providers/openai-sidecar.ts +175 -0
  355. package/src/providers/openai-tier-startup.ts +27 -0
  356. package/src/providers/openai-tiers.ts +301 -0
  357. package/src/providers/openai-virtual-models.ts +82 -0
  358. package/src/providers/openrouter-routing.ts +102 -0
  359. package/src/providers/provider-id-rewrite.ts +150 -0
  360. package/src/providers/quota.ts +1260 -0
  361. package/src/providers/registry.ts +1600 -0
  362. package/src/providers/slug-codec.ts +67 -0
  363. package/src/providers/xai-transport.ts +141 -0
  364. package/src/reasoning-effort.ts +135 -0
  365. package/src/responses/compaction.ts +117 -0
  366. package/src/responses/parser.ts +656 -0
  367. package/src/responses/reasoning-envelope.ts +52 -0
  368. package/src/responses/schema.ts +159 -0
  369. package/src/responses/spill-store.ts +394 -0
  370. package/src/responses/state.ts +895 -0
  371. package/src/responses/tool-groups.ts +19 -0
  372. package/src/router.ts +425 -0
  373. package/src/server/adapter-resolve.ts +80 -0
  374. package/src/server/auth-cors.ts +530 -0
  375. package/src/server/chat-completions.ts +368 -0
  376. package/src/server/claude-messages.ts +914 -0
  377. package/src/server/effort-policy.ts +172 -0
  378. package/src/server/gui-static.ts +123 -0
  379. package/src/server/image-retry.ts +42 -0
  380. package/src/server/images.ts +476 -0
  381. package/src/server/index.ts +1126 -0
  382. package/src/server/lifecycle.ts +227 -0
  383. package/src/server/live.ts +598 -0
  384. package/src/server/management/agent-settings-routes.ts +1169 -0
  385. package/src/server/management/api-access.ts +141 -0
  386. package/src/server/management/api-key-usage.ts +167 -0
  387. package/src/server/management/body.ts +35 -0
  388. package/src/server/management/clients-routes.ts +63 -0
  389. package/src/server/management/cloud-sync-routes.ts +266 -0
  390. package/src/server/management/combo-routes.ts +220 -0
  391. package/src/server/management/config-routes.ts +422 -0
  392. package/src/server/management/context.ts +31 -0
  393. package/src/server/management/logs-usage-routes.ts +707 -0
  394. package/src/server/management/model-routes.ts +525 -0
  395. package/src/server/management/oauth-account-routes.ts +563 -0
  396. package/src/server/management/provider-routes.ts +556 -0
  397. package/src/server/management/shared.ts +277 -0
  398. package/src/server/management/sidebar-routes.ts +90 -0
  399. package/src/server/management/system-restart.ts +179 -0
  400. package/src/server/management/system-routes.ts +117 -0
  401. package/src/server/management/usage-summary-cache.ts +86 -0
  402. package/src/server/management-api.ts +215 -0
  403. package/src/server/management-auth.ts +267 -0
  404. package/src/server/memory-watchdog.ts +156 -0
  405. package/src/server/port-reclaim.ts +307 -0
  406. package/src/server/ports.ts +116 -0
  407. package/src/server/proxy-liveness.ts +201 -0
  408. package/src/server/relay-eager.ts +313 -0
  409. package/src/server/relay.ts +1049 -0
  410. package/src/server/request-decompress.ts +132 -0
  411. package/src/server/request-log-conversation.ts +168 -0
  412. package/src/server/request-log.ts +1046 -0
  413. package/src/server/responses/collaboration.ts +354 -0
  414. package/src/server/responses/compact.ts +384 -0
  415. package/src/server/responses/core.ts +2758 -0
  416. package/src/server/responses/encrypted-payload.ts +308 -0
  417. package/src/server/responses/fetch-helpers.ts +157 -0
  418. package/src/server/responses/passthrough-error.ts +78 -0
  419. package/src/server/responses/terminal-guard.ts +230 -0
  420. package/src/server/responses/upstream-error.ts +48 -0
  421. package/src/server/responses-image-gen-repair.ts +132 -0
  422. package/src/server/responses-item-id-repair.ts +224 -0
  423. package/src/server/responses.ts +9 -0
  424. package/src/server/search.ts +136 -0
  425. package/src/server/sse-payload-rewrite.ts +175 -0
  426. package/src/server/startup-action-control.ts +308 -0
  427. package/src/server/startup-health-cache.ts +113 -0
  428. package/src/server/system-env.ts +413 -0
  429. package/src/server/windows-tcp-drop.ts +184 -0
  430. package/src/server/windows-tray-control.ts +41 -0
  431. package/src/server/ws-bridge.ts +471 -0
  432. package/src/service.ts +2554 -0
  433. package/src/stall-timeout.ts +20 -0
  434. package/src/storage/cleanup-job.ts +57 -0
  435. package/src/storage/cleanup.ts +3085 -0
  436. package/src/storage/policy-job.ts +457 -0
  437. package/src/storage/policy-scheduler.ts +40 -0
  438. package/src/storage/policy-worker.ts +59 -0
  439. package/src/storage/policy.ts +527 -0
  440. package/src/storage/restore-job.ts +299 -0
  441. package/src/storage/restore-worker.ts +58 -0
  442. package/src/storage/scanner.ts +238 -0
  443. package/src/storage/storage-mutation-coordinator.ts +139 -0
  444. package/src/storage/worker-lifecycle.ts +215 -0
  445. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  446. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  447. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  448. package/src/tray/assets/opencodex-tray.png +0 -0
  449. package/src/tray/windows-tray.ps1 +290 -0
  450. package/src/tray/windows.ts +730 -0
  451. package/src/types.ts +1237 -0
  452. package/src/update/badge.ts +72 -0
  453. package/src/update/index.ts +407 -0
  454. package/src/update/job.ts +1520 -0
  455. package/src/update/notify.ts +257 -0
  456. package/src/update/npm-invocation.d.mts +23 -0
  457. package/src/update/npm-invocation.mjs +94 -0
  458. package/src/update/tray-update-plan.d.mts +18 -0
  459. package/src/update/tray-update-plan.mjs +38 -0
  460. package/src/usage/cost.ts +0 -0
  461. package/src/usage/debug.ts +97 -0
  462. package/src/usage/expected-prices.ts +164 -0
  463. package/src/usage/log.ts +658 -0
  464. package/src/usage/summary.ts +585 -0
  465. package/src/usage/totals.ts +14 -0
  466. package/src/vision/anthropic-describe.ts +185 -0
  467. package/src/vision/describe.ts +125 -0
  468. package/src/vision/index.ts +467 -0
  469. package/src/web-search/anthropic-executor.ts +189 -0
  470. package/src/web-search/executor.ts +105 -0
  471. package/src/web-search/format-result.ts +89 -0
  472. package/src/web-search/index.ts +196 -0
  473. package/src/web-search/loop.ts +664 -0
  474. package/src/web-search/parse.ts +220 -0
  475. package/src/web-search/progress-stream.ts +342 -0
  476. package/src/web-search/synthetic-tool.ts +47 -0
@@ -0,0 +1,113 @@
1
+ import { execFile } from "node:child_process";
2
+ import { join } from "node:path";
3
+ import { codexAutoStartEnabled } from "../config";
4
+ import { deriveStartupHealth, type StartupHealth } from "../codex/autostart-health";
5
+ import { getCodexRoutingKind } from "../codex/inject";
6
+ import { diagnoseCodexShim } from "../codex/shim";
7
+ import { durableBunPath } from "../lib/bun-runtime";
8
+ import type { OcxConfig } from "../types";
9
+ import { truncateRetainedUtf8 } from "../lib/admission";
10
+
11
+ const CACHE_TTL_MS = 30_000;
12
+ const PROBE_TIMEOUT_MS = 5_000;
13
+ const MAX_DIAGNOSTIC_VALUE_BYTES = 8 * 1024;
14
+ let cached: { timestamp: number; value: StartupHealth } | null = null;
15
+ let inflight: Promise<StartupHealth> | null = null;
16
+ let generation = 0;
17
+
18
+ export function markStartupHealthDiagnosticStale(value: StartupHealth): StartupHealth {
19
+ if (!value.localRoutingDependency) return { ...value, diagnosticStale: true };
20
+ return {
21
+ ...value,
22
+ status: "at-risk",
23
+ rebootSafe: false,
24
+ protection: "none",
25
+ diagnosticStale: true,
26
+ recommendedCommand: value.routingKind === "custom-local" || value.routingKind === "unknown"
27
+ ? value.commands.restoreNative
28
+ : value.commands.installService,
29
+ };
30
+ }
31
+
32
+ function conservativeFallback(config: Pick<OcxConfig, "codexAutoStart">): StartupHealth {
33
+ const shim = diagnoseCodexShim();
34
+ return deriveStartupHealth({
35
+ routingKind: getCodexRoutingKind(),
36
+ autostartEnabled: codexAutoStartEnabled(config),
37
+ serviceInstalled: false,
38
+ serviceViable: false,
39
+ serviceEnabled: false,
40
+ serviceRunning: false,
41
+ serviceStale: false,
42
+ serviceConflict: false,
43
+ serviceSupported: process.platform === "win32" || process.platform === "darwin" || process.platform === "linux",
44
+ shimInstalled: shim.installed,
45
+ shimHealthy: shim.healthy,
46
+ platform: process.platform,
47
+ diagnosticStale: true,
48
+ });
49
+ }
50
+
51
+ function runProbe(config: Pick<OcxConfig, "codexAutoStart">): Promise<StartupHealth> {
52
+ const bun = durableBunPath();
53
+ const cli = join(import.meta.dir, "..", "cli", "index.ts");
54
+ return new Promise(resolve => {
55
+ execFile(bun, [cli, "__startup-health"], {
56
+ encoding: "utf8",
57
+ env: process.env,
58
+ timeout: PROBE_TIMEOUT_MS,
59
+ windowsHide: true,
60
+ maxBuffer: 256 * 1024,
61
+ }, (error, stdout) => {
62
+ if (!error) {
63
+ const lines = stdout.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
64
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
65
+ try {
66
+ const parsed = JSON.parse(lines[index]) as StartupHealth;
67
+ if (["native", "protected", "at-risk"].includes(parsed.status) && typeof parsed.rebootSafe === "boolean") {
68
+ resolve({
69
+ ...parsed,
70
+ recommendedCommand: parsed.recommendedCommand === null
71
+ ? null
72
+ : truncateRetainedUtf8(parsed.recommendedCommand, MAX_DIAGNOSTIC_VALUE_BYTES),
73
+ commands: {
74
+ installService: truncateRetainedUtf8(parsed.commands.installService, MAX_DIAGNOSTIC_VALUE_BYTES),
75
+ installShim: truncateRetainedUtf8(parsed.commands.installShim, MAX_DIAGNOSTIC_VALUE_BYTES),
76
+ restoreNative: truncateRetainedUtf8(parsed.commands.restoreNative, MAX_DIAGNOSTIC_VALUE_BYTES),
77
+ },
78
+ diagnosticStale: false,
79
+ });
80
+ return;
81
+ }
82
+ } catch { /* scan earlier output; config repair messages may precede JSON */ }
83
+ }
84
+ }
85
+ resolve(cached ? markStartupHealthDiagnosticStale(cached.value) : conservativeFallback(config));
86
+ });
87
+ });
88
+ }
89
+
90
+ function refreshInBackground(config: Pick<OcxConfig, "codexAutoStart">): void {
91
+ if (inflight) return;
92
+ const startedGeneration = generation;
93
+ const probe = runProbe(config).then(value => {
94
+ if (startedGeneration === generation) cached = { timestamp: Date.now(), value };
95
+ return value;
96
+ });
97
+ inflight = probe.finally(() => {
98
+ if (inflight === probe || startedGeneration === generation) inflight = null;
99
+ });
100
+ }
101
+
102
+ /** Stale-while-revalidate: service-manager probes never hold open a model/UI request. */
103
+ export async function getCachedStartupHealth(config: Pick<OcxConfig, "codexAutoStart">): Promise<StartupHealth> {
104
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) return cached.value;
105
+ refreshInBackground(config);
106
+ return cached ? markStartupHealthDiagnosticStale(cached.value) : conservativeFallback(config);
107
+ }
108
+
109
+ export function invalidateStartupHealthCache(): void {
110
+ generation += 1;
111
+ cached = null;
112
+ inflight = null;
113
+ }
@@ -0,0 +1,413 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { readFileSync, writeFileSync, unlinkSync, mkdirSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { getConfigDir } from "../config";
5
+ import { resolveAutoContext, type AutoContextMode } from "../claude/context-windows";
6
+ import { PROXY_MARKER, defaultAuthDetectDeps, detectClaudeAuth, ownAdmissionTokens } from "../claude/auth-detect";
7
+ import { resolveClaudeAuthMode } from "../claude/auth-mode";
8
+ import type { OcxConfig } from "../types";
9
+ import { recordOwnedConfigPath } from "../lib/config-ownership";
10
+
11
+ /**
12
+ * Does the opencodex dummy marker belong in the system environment?
13
+ *
14
+ * Keyed on the SAME resolver `ocx claude` uses, so an auto config with no Claude auth
15
+ * also reaches plain `claude` launches — before this, auto-absent users got nothing
16
+ * from auto-connect and the feature looked broken for exactly the people it helps
17
+ * (devlog 260726_claude_auth_auto/035).
18
+ *
19
+ * NOTE this is a SNAPSHOT: the file only changes when this runs (proxy start, `ocx
20
+ * ensure`, or a settings save). `ocx claude` re-resolves live on every launch.
21
+ */
22
+ function systemEnvMarkerMode(config: OcxConfig): "proxy" | "subscription" {
23
+ return resolveClaudeAuthMode(config, detectClaudeAuth(defaultAuthDetectDeps(process.env, ownAdmissionTokens(config)))).markerMode;
24
+ }
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Shell-hook env file: written on inject, sourced by the shell hook in .zshrc.
28
+ // This works for ALL new shells immediately, unlike launchctl setenv which only
29
+ // reaches processes launched directly by launchd (not Terminal.app children).
30
+ // ---------------------------------------------------------------------------
31
+
32
+ export function getShellEnvFilePath(): string {
33
+ return join(getConfigDir(), "claude-env.sh");
34
+ }
35
+
36
+ function shellValue(value: string): string {
37
+ return `'${value.replaceAll("'", `'\\''`)}'`;
38
+ }
39
+
40
+ function writeShellEnvFile(port: number, config: OcxConfig, modelEnv: Record<string, string> = {}, auto?: AutoContextMode): void {
41
+ const lines = [
42
+ `# Generated by opencodex — do not edit manually`,
43
+ `export ANTHROPIC_BASE_URL=${shellValue(`http://127.0.0.1:${port}`)}`,
44
+ `export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=${shellValue("1")}`,
45
+ ];
46
+ // New lever keys are CONDITIONAL exports (audit 139 R2#1): a value the user already
47
+ // exported in their shell wins even though launchctl knows nothing about it.
48
+ const conditional = (name: string, value: string) =>
49
+ `[ -z "\${${name}+x}" ] && export ${name}=${shellValue(value)}`;
50
+ if (config.apiKeys?.length) {
51
+ lines.push(`export ANTHROPIC_AUTH_TOKEN=${shellValue(config.apiKeys[0].key)}`);
52
+ } else if (systemEnvMarkerMode(config) === "proxy") {
53
+ lines.push(conditional("ANTHROPIC_AUTH_TOKEN", PROXY_MARKER));
54
+ }
55
+ // Model slots (default + tiers + legacy small-fast) with [1m] applied (devlog 260712 B2).
56
+ if (modelEnv.ANTHROPIC_MODEL) {
57
+ lines.push(`export ANTHROPIC_MODEL=${shellValue(modelEnv.ANTHROPIC_MODEL)}`);
58
+ } else if (config.claudeCode?.model) {
59
+ lines.push(`export ANTHROPIC_MODEL=${shellValue(config.claudeCode.model)}`);
60
+ }
61
+ for (const [name, value] of Object.entries(modelEnv)) {
62
+ if (name === "ANTHROPIC_MODEL") continue;
63
+ lines.push(conditional(name, value));
64
+ }
65
+ const maxCtx = config.claudeCode?.maxContextTokens;
66
+ if (typeof maxCtx === "number" && Number.isFinite(maxCtx) && maxCtx > 0) {
67
+ lines.push(conditional("CLAUDE_CODE_MAX_CONTEXT_TOKENS", String(Math.floor(maxCtx))));
68
+ lines.push(conditional("DISABLE_COMPACT", "1"));
69
+ }
70
+ // Auto-context (devlog 260712 020): same contract as `ocx claude` / launchctl.
71
+ const autoShell = auto ?? resolveAutoContext(config.claudeCode);
72
+ if (autoShell.enabled) lines.push(conditional("CLAUDE_CODE_AUTO_COMPACT_WINDOW", String(autoShell.compactWindow)));
73
+ if (config.claudeCode?.alwaysEnableEffort === true) {
74
+ lines.push(conditional("CLAUDE_CODE_ALWAYS_ENABLE_EFFORT", "1"));
75
+ }
76
+ const shellEnvPath = getShellEnvFilePath();
77
+ recordOwnedConfigPath(getConfigDir(), shellEnvPath);
78
+ mkdirSync(getConfigDir(), { recursive: true, mode: 0o700 });
79
+ writeFileSync(shellEnvPath, lines.join("\n") + "\n", { encoding: "utf8", mode: 0o600 });
80
+ }
81
+
82
+ function removeShellEnvFile(): void {
83
+ try { unlinkSync(getShellEnvFilePath()); } catch { /* already gone */ }
84
+ }
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // .zshrc hook auto-install: adds a one-liner that sources claude-env.sh.
88
+ // Idempotent — skips if the hook line already exists.
89
+ // ---------------------------------------------------------------------------
90
+
91
+ const SHELL_HOOK_MARKER = "# opencodex claude-env hook";
92
+ const SHELL_HOOK_LINE = `${SHELL_HOOK_MARKER}\n[ -f ~/.opencodex/claude-env.sh ] && source ~/.opencodex/claude-env.sh`;
93
+
94
+ export function installShellHook(): { installed: boolean; reason?: string } {
95
+ if (process.platform !== "darwin") return { installed: false, reason: "not macOS" };
96
+ const home = process.env.HOME;
97
+ if (!home) return { installed: false, reason: "no HOME" };
98
+ const zshrcPath = join(home, ".zshrc");
99
+ try {
100
+ let content = "";
101
+ try { content = readFileSync(zshrcPath, "utf8"); } catch { /* file doesn't exist yet */ }
102
+ if (content.includes(SHELL_HOOK_MARKER)) return { installed: false, reason: "already installed" };
103
+ const addition = `\n${SHELL_HOOK_LINE}\n`;
104
+ writeFileSync(zshrcPath, content + addition, { encoding: "utf8", mode: 0o644 });
105
+ return { installed: true };
106
+ } catch (err) {
107
+ return { installed: false, reason: `write failed: ${err instanceof Error ? err.message : String(err)}` };
108
+ }
109
+ }
110
+
111
+ export function uninstallShellHook(): { removed: boolean; reason?: string } {
112
+ if (process.platform !== "darwin") return { removed: false, reason: "not macOS" };
113
+ const home = process.env.HOME;
114
+ if (!home) return { removed: false, reason: "no HOME" };
115
+ const zshrcPath = join(home, ".zshrc");
116
+ try {
117
+ const content = readFileSync(zshrcPath, "utf8");
118
+ if (!content.includes(SHELL_HOOK_MARKER)) return { removed: false, reason: "not installed" };
119
+ // Remove the hook block (marker line + source line + surrounding newlines)
120
+ const cleaned = content.replace(/\n?# opencodex claude-env hook\n\[.*claude-env\.sh.*\n?/g, "\n");
121
+ writeFileSync(zshrcPath, cleaned, { encoding: "utf8", mode: 0o644 });
122
+ return { removed: true };
123
+ } catch {
124
+ return { removed: false, reason: "read/write failed" };
125
+ }
126
+ }
127
+
128
+ const SYSTEM_ENV_NAMES = [
129
+ "ANTHROPIC_BASE_URL",
130
+ "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY",
131
+ "ANTHROPIC_AUTH_TOKEN",
132
+ ] as const;
133
+
134
+ const MANAGED_SYSTEM_ENV_NAMES = new Set<string>([
135
+ ...SYSTEM_ENV_NAMES,
136
+ "ANTHROPIC_DEFAULT_OPUS_MODEL",
137
+ "ANTHROPIC_DEFAULT_SONNET_MODEL",
138
+ "ANTHROPIC_DEFAULT_FABLE_MODEL",
139
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL",
140
+ "ANTHROPIC_SMALL_FAST_MODEL",
141
+ "CLAUDE_CODE_MAX_CONTEXT_TOKENS",
142
+ "DISABLE_COMPACT",
143
+ "CLAUDE_CODE_AUTO_COMPACT_WINDOW",
144
+ "CLAUDE_CODE_ALWAYS_ENABLE_EFFORT",
145
+ ]);
146
+
147
+ interface SystemEnvTracking {
148
+ pid: number;
149
+ port: number;
150
+ injectedAt: string;
151
+ /** Keys that were actually set by injection (revert only unsets these). */
152
+ injectedKeys?: string[];
153
+ }
154
+
155
+ type SystemEnvResult = { injected: boolean; reason?: string };
156
+ type RevertResult = { reverted: boolean; reason?: string };
157
+ type CleanupResult = { cleaned: boolean; reason?: string };
158
+
159
+ export function getSystemEnvTrackingPath(): string {
160
+ return join(getConfigDir(), "system-env-port");
161
+ }
162
+
163
+ export function launchctlGetenv(name: string): string | undefined {
164
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) return undefined;
165
+ try {
166
+ const value = execFileSync("/bin/launchctl", ["getenv", name], { encoding: "utf8" }).trim();
167
+ return value || undefined;
168
+ } catch {
169
+ return undefined;
170
+ }
171
+ }
172
+
173
+ function readTracking(): SystemEnvTracking | undefined {
174
+ try {
175
+ const tracking = JSON.parse(readFileSync(getSystemEnvTrackingPath(), "utf8")) as Partial<SystemEnvTracking>;
176
+ if (!Number.isInteger(tracking.port) || typeof tracking.pid !== "number" || typeof tracking.injectedAt !== "string") {
177
+ return undefined;
178
+ }
179
+ const injectedKeys = Array.isArray(tracking.injectedKeys)
180
+ ? [...new Set(tracking.injectedKeys.filter(
181
+ (name): name is string => typeof name === "string" && MANAGED_SYSTEM_ENV_NAMES.has(name),
182
+ ))]
183
+ : undefined;
184
+ return { ...tracking, injectedKeys } as SystemEnvTracking;
185
+ } catch {
186
+ return undefined;
187
+ }
188
+ }
189
+
190
+ function setLaunchctlEnv(name: string, value: string): void {
191
+ execFileSync("/bin/launchctl", ["setenv", name, value]);
192
+ }
193
+
194
+ function unsetLaunchctlEnv(name: string): void {
195
+ execFileSync("/bin/launchctl", ["unsetenv", name]);
196
+ }
197
+
198
+ function ownedBaseUrl(port: number): string {
199
+ return `http://127.0.0.1:${port}`;
200
+ }
201
+
202
+ function writeTracking(port: number, injectedKeys: string[]): void {
203
+ recordOwnedConfigPath(getConfigDir(), getSystemEnvTrackingPath());
204
+ mkdirSync(getConfigDir(), { recursive: true, mode: 0o700 });
205
+ writeFileSync(getSystemEnvTrackingPath(), JSON.stringify({
206
+ pid: process.pid,
207
+ port,
208
+ injectedAt: new Date().toISOString(),
209
+ injectedKeys,
210
+ }), { encoding: "utf8", mode: 0o600 });
211
+ }
212
+
213
+ function rollbackInjectedKeys(port: number, injectedKeys: string[]): void {
214
+ const rollbackFailed: string[] = [];
215
+ for (const name of [...injectedKeys].reverse()) {
216
+ try {
217
+ unsetLaunchctlEnv(name);
218
+ } catch {
219
+ rollbackFailed.unshift(name);
220
+ }
221
+ }
222
+
223
+ if (rollbackFailed.length > 0) {
224
+ writeTracking(port, rollbackFailed);
225
+ return;
226
+ }
227
+
228
+ try { unlinkSync(getSystemEnvTrackingPath()); } catch { /* already gone */ }
229
+ }
230
+
231
+ /**
232
+ * In-process effective model-env (default + tier slots, [1m] applied) under the shared
233
+ * 3s bound (audit R4#3). Returns {} on timeout/failure so injection degrades safely.
234
+ */
235
+ async function computeEffectiveModelEnv(config: OcxConfig, auto?: AutoContextMode): Promise<{ modelEnv: Record<string, string>; windows: Record<string, number> }> {
236
+ const { boundedContextWindows, buildClaudeContextWindows, effectiveModelEnv } = await import("../claude/context-windows");
237
+ const windows = await boundedContextWindows(async () => {
238
+ const { gatherRoutedModels, visibleNativeSlugs } = await import("../codex/catalog");
239
+ try {
240
+ return buildClaudeContextWindows([...visibleNativeSlugs(config)], await gatherRoutedModels(config));
241
+ } catch (error) {
242
+ if (error && typeof error === "object" && (error as { code?: unknown }).code === "catalog_busy") {
243
+ return buildClaudeContextWindows([...visibleNativeSlugs(config)], []);
244
+ }
245
+ throw error;
246
+ }
247
+ });
248
+ return { modelEnv: effectiveModelEnv(config.claudeCode, windows ?? {}, auto), windows: windows ?? {} };
249
+ }
250
+
251
+ export async function injectSystemEnv(port: number, config: OcxConfig): Promise<SystemEnvResult> {
252
+ if (process.platform !== "darwin") return { injected: false, reason: "not macOS" };
253
+ if (config.claudeCode?.enabled === false) return { injected: false, reason: "claude disabled" };
254
+
255
+ // Default OFF — only inject when explicitly enabled by the user.
256
+ if (config.claudeCode?.systemEnv !== true) return { injected: false, reason: "systemEnv disabled" };
257
+
258
+ await cleanStaleSystemEnv();
259
+
260
+ const currentBaseUrl = launchctlGetenv("ANTHROPIC_BASE_URL");
261
+ if (currentBaseUrl && !/^http:\/\/127\.0\.0\.1:\d+$/.test(currentBaseUrl)) {
262
+ return { injected: false, reason: "user has custom ANTHROPIC_BASE_URL" };
263
+ }
264
+ // After stale cleanup, if a tracking file still exists with a DIFFERENT port,
265
+ // another live instance owns the env — don't overwrite it.
266
+ const existingTracking = readTracking();
267
+ if (existingTracking && existingTracking.port !== port) {
268
+ return { injected: false, reason: `another instance owns env (port ${existingTracking.port})` };
269
+ }
270
+
271
+ const injectedKeys: string[] = existingTracking
272
+ ? [...(existingTracking.injectedKeys ?? SYSTEM_ENV_NAMES)]
273
+ : [];
274
+ const inject = (name: string, value: string) => {
275
+ setLaunchctlEnv(name, value);
276
+ if (!injectedKeys.includes(name)) injectedKeys.push(name);
277
+ writeTracking(port, injectedKeys);
278
+ };
279
+
280
+ try {
281
+ inject("ANTHROPIC_BASE_URL", ownedBaseUrl(port));
282
+ inject("CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY", "1");
283
+ if (config.apiKeys?.length) {
284
+ inject("ANTHROPIC_AUTH_TOKEN", config.apiKeys[0].key);
285
+ } else if (systemEnvMarkerMode(config) === "proxy" && launchctlGetenv("ANTHROPIC_AUTH_TOKEN") === undefined) {
286
+ inject("ANTHROPIC_AUTH_TOKEN", PROXY_MARKER);
287
+ } else if (systemEnvMarkerMode(config) !== "proxy"
288
+ && injectedKeys.includes("ANTHROPIC_AUTH_TOKEN")
289
+ && launchctlGetenv("ANTHROPIC_AUTH_TOKEN") === PROXY_MARKER) {
290
+ // Subscription switch-back (devlog 260720_claude_authmode_persist): remove ONLY
291
+ // the opencodex-owned dummy token so a launchd-started Claude regains its own
292
+ // claude.ai OAuth. User-set tokens (not tracked in injectedKeys, or carrying a
293
+ // different value) are never touched.
294
+ unsetLaunchctlEnv("ANTHROPIC_AUTH_TOKEN");
295
+ const dummyIdx = injectedKeys.indexOf("ANTHROPIC_AUTH_TOKEN");
296
+ if (dummyIdx >= 0) injectedKeys.splice(dummyIdx, 1);
297
+ writeTracking(port, injectedKeys);
298
+ }
299
+ // Lever keys (devlog 136 B6): user-wins — skip any key the user already set in the
300
+ // launchd domain, and track ONLY the keys we actually injected so revert cannot
301
+ // delete a pre-existing user value (audit 139 #3).
302
+ const injectLever = (name: string, value: string) => {
303
+ if (launchctlGetenv(name) !== undefined) return;
304
+ inject(name, value);
305
+ };
306
+ // Model slots (default + tier defaults + legacy small-fast) with [1m] auto-marking
307
+ // (devlog 260712 B2, audit R2#3/R4#3): in-process context-window computation under
308
+ // the same 3s bound; on timeout the tier keys are simply not injected this run.
309
+ // Auto-context: a user-owned launchd value drives the marking predicate so the
310
+ // marker and threshold never separate (audit 021 #2); injectLever's user-wins
311
+ // check below keeps that value untouched.
312
+ const userAutoCompact = launchctlGetenv("CLAUDE_CODE_AUTO_COMPACT_WINDOW");
313
+ const auto = resolveAutoContext(config.claudeCode, userAutoCompact);
314
+ const { modelEnv, windows } = await computeEffectiveModelEnv(config, auto);
315
+ for (const [name, value] of Object.entries(modelEnv)) {
316
+ if (name === "ANTHROPIC_MODEL") continue; // legacy slot handled by shell file only (back-compat)
317
+ injectLever(name, value);
318
+ }
319
+ const maxCtx = config.claudeCode?.maxContextTokens;
320
+ if (typeof maxCtx === "number" && Number.isFinite(maxCtx) && maxCtx > 0) {
321
+ injectLever("CLAUDE_CODE_MAX_CONTEXT_TOKENS", String(Math.floor(maxCtx)));
322
+ injectLever("DISABLE_COMPACT", "1");
323
+ }
324
+ // Auto-context (devlog 260712 020): user-wins lever, inert when maxContextTokens set.
325
+ if (auto.enabled) injectLever("CLAUDE_CODE_AUTO_COMPACT_WINDOW", String(auto.compactWindow));
326
+ if (config.claudeCode?.alwaysEnableEffort === true) {
327
+ injectLever("CLAUDE_CODE_ALWAYS_ENABLE_EFFORT", "1");
328
+ }
329
+
330
+ // Shell-hook env file: works for new shells in already-running Terminal.app.
331
+ writeShellEnvFile(port, config, modelEnv, auto);
332
+
333
+ // Gateway-model cache pre-write (devlog 030): plain `claude` sessions read the
334
+ // picker list from ~/.claude/cache/gateway-models.json and cannot refresh it
335
+ // without a token — keep it in sync with this proxy's /v1/models. Best-effort.
336
+ try {
337
+ const { refreshGatewayModelCacheFromProxy } = await import("../claude/gateway-cache");
338
+ await refreshGatewayModelCacheFromProxy(port);
339
+ } catch { /* best-effort */ }
340
+
341
+ // Roster agent definitions (devlog 070): same launch-time sync for plain `claude`.
342
+ // Reuses the window map computed above (audit 071 #5 — no second acquisition).
343
+ try {
344
+ const { injectClaudeAgentDefs } = await import("../claude/agents-inject");
345
+ injectClaudeAgentDefs(config, windows);
346
+ } catch { /* best-effort */ }
347
+
348
+ writeTracking(port, injectedKeys);
349
+ } catch (error) {
350
+ rollbackInjectedKeys(port, injectedKeys);
351
+ removeShellEnvFile();
352
+ console.error("Failed to inject system environment; rolled back launchctl changes:", error);
353
+ throw error;
354
+ }
355
+
356
+ return { injected: true };
357
+ }
358
+
359
+ export async function applySystemEnvToggle(config: OcxConfig, port: number): Promise<SystemEnvResult | RevertResult> {
360
+ if (config.claudeCode?.systemEnv === true) return injectSystemEnv(port, config);
361
+ return revertSystemEnv();
362
+ }
363
+
364
+ export function revertSystemEnv(): RevertResult {
365
+ if (process.platform !== "darwin") return { reverted: false, reason: "not macOS" };
366
+
367
+ const tracking = readTracking();
368
+ if (!tracking) return { reverted: false, reason: "no tracking file" };
369
+
370
+ try {
371
+ const tracksBaseUrl = tracking.injectedKeys?.includes("ANTHROPIC_BASE_URL") ?? true;
372
+ if (tracksBaseUrl && launchctlGetenv("ANTHROPIC_BASE_URL") !== ownedBaseUrl(tracking.port)) {
373
+ return { reverted: false, reason: "ownership mismatch" };
374
+ }
375
+
376
+ // Only unset keys that were actually injected (preserves pre-existing user tokens).
377
+ const keysToUnset = tracking.injectedKeys ?? SYSTEM_ENV_NAMES as unknown as string[];
378
+ for (const name of keysToUnset) {
379
+ try {
380
+ unsetLaunchctlEnv(name);
381
+ } catch {
382
+ // Continue removing the remaining variables during shutdown.
383
+ }
384
+ }
385
+ removeShellEnvFile();
386
+ try {
387
+ unlinkSync(getSystemEnvTrackingPath());
388
+ } catch {
389
+ // The environment was reverted even if the tracking file disappeared concurrently.
390
+ }
391
+ return { reverted: true };
392
+ } catch {
393
+ return { reverted: false, reason: "revert failed" };
394
+ }
395
+ }
396
+
397
+ export async function cleanStaleSystemEnv(): Promise<CleanupResult> {
398
+ const tracking = readTracking();
399
+ if (!tracking) return { cleaned: false, reason: "no tracking file" };
400
+
401
+ try {
402
+ const response = await fetch(`${ownedBaseUrl(tracking.port)}/healthz`, {
403
+ signal: AbortSignal.timeout(1_000),
404
+ });
405
+ if (response.ok) return { cleaned: false, reason: "proxy still alive" };
406
+ } catch {
407
+ // A failed or timed-out health check means the tracked proxy is stale.
408
+ }
409
+
410
+ const reverted = revertSystemEnv();
411
+ if (!reverted.reverted) return { cleaned: false, reason: reverted.reason };
412
+ return { cleaned: true };
413
+ }