@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,548 @@
1
+ import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from "node:child_process";
2
+ import { resolve } from "node:path";
3
+ import { create } from "@bufbuild/protobuf";
4
+ import {
5
+ BackgroundShellSpawnErrorSchema,
6
+ BackgroundShellSpawnResultSchema,
7
+ BackgroundShellSpawnSuccessSchema,
8
+ ShellFailureSchema,
9
+ ShellResultSchema,
10
+ ShellStreamExitSchema,
11
+ ShellStreamSchema,
12
+ ShellStreamStartSchema,
13
+ ShellStreamStderrSchema,
14
+ ShellStreamStdoutSchema,
15
+ ShellSuccessSchema,
16
+ WriteShellStdinErrorSchema,
17
+ WriteShellStdinResultSchema,
18
+ WriteShellStdinSuccessSchema,
19
+ type ExecServerMessage,
20
+ } from "./gen/agent_pb";
21
+ import { errorText, execBytes, execStreamCloseBytes } from "./native-exec-common";
22
+ import {
23
+ createAdmissionGate,
24
+ type AdmissionLease,
25
+ type AdmissionMetrics,
26
+ } from "../../lib/admission";
27
+
28
+ export const CURSOR_BACKGROUND_SHELL_MAX_LIVE = 8;
29
+ export const CURSOR_BACKGROUND_SHELL_IDLE_MS = 5 * 60_000;
30
+ export const CURSOR_BACKGROUND_SHELL_ABSOLUTE_MS = 30 * 60_000;
31
+ export const CURSOR_BACKGROUND_SHELL_TERM_GRACE_MS = 2_000;
32
+
33
+ type BackgroundShellTerminationReason = "session_close" | "idle" | "absolute" | "shutdown";
34
+ type BackgroundShellTimer = ReturnType<typeof setTimeout>;
35
+
36
+ export interface BackgroundShellTerminationReport {
37
+ attempted: number;
38
+ closed: number;
39
+ unresolved: number;
40
+ killFailures: number;
41
+ }
42
+
43
+ export interface BackgroundShellRuntime {
44
+ spawn: typeof spawn;
45
+ now(): number;
46
+ setTimer(callback: () => void, delayMs: number): BackgroundShellTimer;
47
+ clearTimer(timer: BackgroundShellTimer): void;
48
+ kill(child: ChildProcessWithoutNullStreams, signal?: NodeJS.Signals): boolean;
49
+ }
50
+
51
+ interface BackgroundShellEntry {
52
+ shellId: number;
53
+ sessionId: string;
54
+ child: ChildProcessWithoutNullStreams;
55
+ admissionLease: AdmissionLease;
56
+ outputLength: number;
57
+ startedAt: number;
58
+ lastActivityAt: number;
59
+ idleTimer?: BackgroundShellTimer;
60
+ absoluteTimer?: BackgroundShellTimer;
61
+ terminating: Promise<BackgroundShellTerminationReport> | null;
62
+ closePromise: Promise<void>;
63
+ confirmClose(): void;
64
+ }
65
+
66
+ const defaultBackgroundShellRuntime: BackgroundShellRuntime = {
67
+ spawn,
68
+ now: () => Date.now(),
69
+ setTimer: (callback, delayMs) => setTimeout(callback, delayMs),
70
+ clearTimer: timer => clearTimeout(timer),
71
+ kill: (child, signal) => child.kill(signal),
72
+ };
73
+
74
+ let backgroundShellRuntime = defaultBackgroundShellRuntime;
75
+ let backgroundShellGate = createAdmissionGate("cursor_background_shells", CURSOR_BACKGROUND_SHELL_MAX_LIVE);
76
+ const backgroundShells = new Map<number, BackgroundShellEntry>();
77
+ let nextShellId = 1;
78
+ let backgroundShellShutdownStarted = false;
79
+ let idleTerminations = 0;
80
+ let absoluteTerminations = 0;
81
+ let unresolvedKills = 0;
82
+ let killFailures = 0;
83
+
84
+ /** Rejection text when Cursor-native shell is denied by policy (issue #604). */
85
+ export function nativeShellDisabledMessage(): string {
86
+ // Do not insist on "the same command" — that steers models into replaying bash/CMD
87
+ // idioms through the Codex bridge on Windows PowerShell 5.1 and looping (#604).
88
+ // Keep this host-shell-neutral: OpenCodex may run on a different OS than the Codex
89
+ // client that executes the bridge (LAN/SSH remote-proxy).
90
+ return (
91
+ "Route this through the Codex bridge shell tool from the current catalog (`shell_command` or `exec_command`, including the long `mcp_opencodex-responses_*` display name if listed). "
92
+ + "Do not tell the user shell access is blocked, disabled, or denied — silently call that bridge tool. "
93
+ + "Adapt the command for the Codex client host shell before calling the bridge "
94
+ + "(Windows PowerShell 5.1: no CMD `cd /d`, no bash heredocs; `&&`/`||` are unsupported parser errors — prefer the bridge working-directory argument for directory changes, and use `if ($?) { ... }` for success-gated follow-up steps; do not treat `;` as a substitute for `&&`). "
95
+ + "Make at most one corrected bridge attempt after a failure, then report the error and stop — do not repeat equivalent failing commands."
96
+ );
97
+ }
98
+
99
+ function rejectedShellResult(command: string, cwd: string, started: number) {
100
+ return create(ShellResultSchema, {
101
+ result: {
102
+ case: "failure",
103
+ value: create(ShellFailureSchema, {
104
+ command,
105
+ workingDirectory: cwd,
106
+ exitCode: 1,
107
+ signal: "",
108
+ stdout: "",
109
+ stderr: nativeShellDisabledMessage(),
110
+ executionTime: Date.now() - started,
111
+ aborted: true,
112
+ }),
113
+ },
114
+ });
115
+ }
116
+
117
+ export function rejectShellExecForPolicy(execMsg: ExecServerMessage): Uint8Array {
118
+ if (execMsg.message.case !== "shellArgs") throw new Error("invalid shell exec");
119
+ const args = execMsg.message.value;
120
+ return execBytes(execMsg, "shellResult", rejectedShellResult(args.command, resolve(args.workingDirectory || process.cwd()), Date.now()));
121
+ }
122
+
123
+ export function shellExec(execMsg: ExecServerMessage): Uint8Array {
124
+ if (execMsg.message.case !== "shellArgs") throw new Error("invalid shell exec");
125
+ const args = execMsg.message.value;
126
+ const cwd = resolve(args.workingDirectory || process.cwd());
127
+ const started = Date.now();
128
+ const result = spawnSync(args.command, { cwd, shell: true, encoding: "utf8", timeout: args.hardTimeout || 120_000 });
129
+ const elapsed = Date.now() - started;
130
+ const stdout = String(result.stdout ?? "");
131
+ const stderr = String(result.stderr ?? "");
132
+ const code = typeof result.status === "number" ? result.status : 1;
133
+ if (code === 0) {
134
+ return execBytes(execMsg, "shellResult", create(ShellResultSchema, {
135
+ result: {
136
+ case: "success",
137
+ value: create(ShellSuccessSchema, { command: args.command, workingDirectory: cwd, exitCode: code, signal: "", stdout, stderr, executionTime: elapsed }),
138
+ },
139
+ }));
140
+ }
141
+ return execBytes(execMsg, "shellResult", create(ShellResultSchema, {
142
+ result: {
143
+ case: "failure",
144
+ value: create(ShellFailureSchema, {
145
+ command: args.command,
146
+ workingDirectory: cwd,
147
+ exitCode: code,
148
+ signal: String(result.signal ?? ""),
149
+ stdout,
150
+ stderr,
151
+ executionTime: elapsed,
152
+ aborted: !!result.error,
153
+ }),
154
+ },
155
+ }));
156
+ }
157
+
158
+ export function rejectShellStreamExecForPolicy(execMsg: ExecServerMessage): Uint8Array[] {
159
+ if (execMsg.message.case !== "shellStreamArgs") throw new Error("invalid shell stream exec");
160
+ const args = execMsg.message.value;
161
+ const cwd = resolve(args.workingDirectory || process.cwd());
162
+ const started = Date.now();
163
+ return [
164
+ execBytes(execMsg, "shellStream", create(ShellStreamSchema, {
165
+ event: { case: "start", value: create(ShellStreamStartSchema, { sandboxPolicy: args.requestedSandboxPolicy }) },
166
+ })),
167
+ execBytes(execMsg, "shellStream", create(ShellStreamSchema, {
168
+ event: { case: "stderr", value: create(ShellStreamStderrSchema, { data: nativeShellDisabledMessage() }) },
169
+ })),
170
+ execBytes(execMsg, "shellStream", create(ShellStreamSchema, {
171
+ event: { case: "exit", value: create(ShellStreamExitSchema, { code: 1, cwd, aborted: true }) },
172
+ })),
173
+ execBytes(execMsg, "shellResult", rejectedShellResult(args.command, cwd, started)),
174
+ execStreamCloseBytes(execMsg),
175
+ ];
176
+ }
177
+
178
+ export async function shellStreamExec(execMsg: ExecServerMessage): Promise<Uint8Array[]> {
179
+ if (execMsg.message.case !== "shellStreamArgs") throw new Error("invalid shell stream exec");
180
+ const args = execMsg.message.value;
181
+ const cwd = resolve(args.workingDirectory || process.cwd());
182
+ const started = Date.now();
183
+ const replies = [
184
+ execBytes(execMsg, "shellStream", create(ShellStreamSchema, {
185
+ event: { case: "start", value: create(ShellStreamStartSchema, { sandboxPolicy: args.requestedSandboxPolicy }) },
186
+ })),
187
+ ];
188
+ const result = await new Promise<{ stdout: string; stderr: string; code: number; aborted: boolean }>(resolvePromise => {
189
+ const child = spawn(args.command, { cwd, shell: true });
190
+ let stdout = "";
191
+ let stderr = "";
192
+ let aborted = false;
193
+ const timeout = setTimeout(() => {
194
+ aborted = true;
195
+ child.kill();
196
+ }, args.hardTimeout || 120_000);
197
+ child.stdout.on("data", chunk => {
198
+ stdout += String(chunk);
199
+ });
200
+ child.stderr.on("data", chunk => {
201
+ stderr += String(chunk);
202
+ });
203
+ child.on("close", code => {
204
+ clearTimeout(timeout);
205
+ resolvePromise({ stdout, stderr, code: code ?? 1, aborted });
206
+ });
207
+ child.on("error", err => {
208
+ clearTimeout(timeout);
209
+ resolvePromise({ stdout, stderr: stderr + errorText(err), code: 1, aborted });
210
+ });
211
+ });
212
+ if (result.stdout) {
213
+ replies.push(execBytes(execMsg, "shellStream", create(ShellStreamSchema, {
214
+ event: { case: "stdout", value: create(ShellStreamStdoutSchema, { data: result.stdout }) },
215
+ })));
216
+ }
217
+ if (result.stderr) {
218
+ replies.push(execBytes(execMsg, "shellStream", create(ShellStreamSchema, {
219
+ event: { case: "stderr", value: create(ShellStreamStderrSchema, { data: result.stderr }) },
220
+ })));
221
+ }
222
+ replies.push(execBytes(execMsg, "shellStream", create(ShellStreamSchema, {
223
+ event: { case: "exit", value: create(ShellStreamExitSchema, { code: result.code, cwd, aborted: result.aborted }) },
224
+ })));
225
+ // Cursor keeps the turn pending when it receives only stream deltas/exit: it requires the final
226
+ // structured shellResult as completion acknowledgement, followed by an exec stream close. Without
227
+ // these two frames the server-side agent waits forever (heartbeat-only stall → watchdog
228
+ // upstream_stall_timeout → upstream 502). Mirrors jawcode handleShellStreamArgs.
229
+ const shellResult = result.code === 0 && !result.aborted
230
+ ? create(ShellResultSchema, {
231
+ result: {
232
+ case: "success",
233
+ value: create(ShellSuccessSchema, {
234
+ command: args.command,
235
+ workingDirectory: cwd,
236
+ exitCode: result.code,
237
+ signal: "",
238
+ stdout: result.stdout,
239
+ stderr: result.stderr,
240
+ executionTime: Date.now() - started,
241
+ }),
242
+ },
243
+ })
244
+ : create(ShellResultSchema, {
245
+ result: {
246
+ case: "failure",
247
+ value: create(ShellFailureSchema, {
248
+ command: args.command,
249
+ workingDirectory: cwd,
250
+ exitCode: result.code,
251
+ signal: "",
252
+ stdout: result.stdout,
253
+ stderr: result.stderr,
254
+ executionTime: Date.now() - started,
255
+ aborted: result.aborted,
256
+ }),
257
+ },
258
+ });
259
+ replies.push(execBytes(execMsg, "shellResult", shellResult));
260
+ replies.push(execStreamCloseBytes(execMsg));
261
+ return replies;
262
+ }
263
+
264
+ export function rejectBackgroundShellSpawnExecForPolicy(execMsg: ExecServerMessage): Uint8Array {
265
+ if (execMsg.message.case !== "backgroundShellSpawnArgs") throw new Error("invalid background shell exec");
266
+ const args = execMsg.message.value;
267
+ const cwd = resolve(args.workingDirectory || process.cwd());
268
+ return execBytes(execMsg, "backgroundShellSpawnResult", create(BackgroundShellSpawnResultSchema, {
269
+ result: { case: "error", value: create(BackgroundShellSpawnErrorSchema, { command: args.command, workingDirectory: cwd, error: nativeShellDisabledMessage() }) },
270
+ }));
271
+ }
272
+
273
+ function backgroundShellSpawnError(
274
+ execMsg: ExecServerMessage,
275
+ command: string,
276
+ workingDirectory: string,
277
+ error: string,
278
+ ): Uint8Array {
279
+ return execBytes(execMsg, "backgroundShellSpawnResult", create(BackgroundShellSpawnResultSchema, {
280
+ result: {
281
+ case: "error",
282
+ value: create(BackgroundShellSpawnErrorSchema, { command, workingDirectory, error }),
283
+ },
284
+ }));
285
+ }
286
+
287
+ function clearBackgroundShellTimers(entry: BackgroundShellEntry): void {
288
+ if (entry.idleTimer) backgroundShellRuntime.clearTimer(entry.idleTimer);
289
+ if (entry.absoluteTimer) backgroundShellRuntime.clearTimer(entry.absoluteTimer);
290
+ entry.idleTimer = undefined;
291
+ entry.absoluteTimer = undefined;
292
+ }
293
+
294
+ function confirmBackgroundShellClose(entry: BackgroundShellEntry): void {
295
+ if (backgroundShells.get(entry.shellId) !== entry) return;
296
+ clearBackgroundShellTimers(entry);
297
+ backgroundShells.delete(entry.shellId);
298
+ entry.admissionLease.release();
299
+ entry.confirmClose();
300
+ }
301
+
302
+ function unrefTimer(timer: BackgroundShellTimer): void {
303
+ timer.unref?.();
304
+ }
305
+
306
+ function armBackgroundShellIdleTimer(entry: BackgroundShellEntry): void {
307
+ if (entry.idleTimer) backgroundShellRuntime.clearTimer(entry.idleTimer);
308
+ entry.idleTimer = backgroundShellRuntime.setTimer(() => {
309
+ entry.idleTimer = undefined;
310
+ void terminateBackgroundShell(entry, "idle");
311
+ }, CURSOR_BACKGROUND_SHELL_IDLE_MS);
312
+ unrefTimer(entry.idleTimer);
313
+ }
314
+
315
+ function armBackgroundShellAbsoluteTimer(entry: BackgroundShellEntry): void {
316
+ entry.absoluteTimer = backgroundShellRuntime.setTimer(() => {
317
+ entry.absoluteTimer = undefined;
318
+ void terminateBackgroundShell(entry, "absolute");
319
+ }, CURSOR_BACKGROUND_SHELL_ABSOLUTE_MS);
320
+ unrefTimer(entry.absoluteTimer);
321
+ }
322
+
323
+ function noteBackgroundShellActivity(entry: BackgroundShellEntry, bytes = 0): void {
324
+ if (backgroundShells.get(entry.shellId) !== entry) return;
325
+ entry.outputLength += bytes;
326
+ entry.lastActivityAt = backgroundShellRuntime.now();
327
+ // Once termination has begun the lifecycle timers are cleared for good:
328
+ // late pipe output must update byte accounting only, never re-arm idle,
329
+ // or a quarantined shell gets a fresh five-minute timer that can fire a
330
+ // second kill attempt and distort the lifecycle counters.
331
+ if (!entry.terminating) armBackgroundShellIdleTimer(entry);
332
+ }
333
+
334
+ function waitForBackgroundShellClose(entry: BackgroundShellEntry): Promise<boolean> {
335
+ if (backgroundShells.get(entry.shellId) !== entry) return Promise.resolve(true);
336
+ return new Promise(resolveWait => {
337
+ let settled = false;
338
+ // Deliberately REF'D: this is the bounded kill-grace wait that shutdown
339
+ // drain awaits. Bun on Windows / under `bun test --isolate` can starve
340
+ // unref'd timers when a pending promise is the only other work, which
341
+ // would leave drainAndShutdown waiting forever (same class as oauth
342
+ // serializeMutation wait timers). The timer self-clears within the
343
+ // 2-second grace window (or earlier on close), so a ref cannot keep the
344
+ // process alive beyond that bound.
345
+ const timer = backgroundShellRuntime.setTimer(() => {
346
+ if (settled) return;
347
+ settled = true;
348
+ resolveWait(false);
349
+ }, CURSOR_BACKGROUND_SHELL_TERM_GRACE_MS);
350
+ void entry.closePromise.then(() => {
351
+ if (settled) return;
352
+ settled = true;
353
+ backgroundShellRuntime.clearTimer(timer);
354
+ resolveWait(true);
355
+ });
356
+ });
357
+ }
358
+
359
+ function tryKillBackgroundShell(entry: BackgroundShellEntry, signal?: NodeJS.Signals): boolean {
360
+ try {
361
+ return backgroundShellRuntime.kill(entry.child, signal);
362
+ } catch {
363
+ return false;
364
+ }
365
+ }
366
+
367
+ async function terminateBackgroundShell(
368
+ entry: BackgroundShellEntry,
369
+ reason: BackgroundShellTerminationReason,
370
+ ): Promise<BackgroundShellTerminationReport> {
371
+ if (entry.terminating) return entry.terminating;
372
+ if (backgroundShells.get(entry.shellId) !== entry) {
373
+ return { attempted: 0, closed: 0, unresolved: 0, killFailures: 0 };
374
+ }
375
+ if (reason === "idle") idleTerminations += 1;
376
+ if (reason === "absolute") absoluteTerminations += 1;
377
+ entry.terminating = (async () => {
378
+ clearBackgroundShellTimers(entry);
379
+ try { entry.child.stdin.end(); } catch { /* best-effort direct-child shutdown */ }
380
+ try { entry.child.stdout.resume(); } catch { /* keep draining when supported */ }
381
+ try { entry.child.stderr.resume(); } catch { /* keep draining when supported */ }
382
+
383
+ let attemptKillFailures = 0;
384
+ if (!tryKillBackgroundShell(entry)) attemptKillFailures += 1;
385
+ let closed = await waitForBackgroundShellClose(entry);
386
+ if (!closed && backgroundShells.get(entry.shellId) !== entry) closed = true;
387
+ if (!closed) {
388
+ if (!tryKillBackgroundShell(entry, "SIGKILL")) attemptKillFailures += 1;
389
+ closed = await waitForBackgroundShellClose(entry);
390
+ if (!closed && backgroundShells.get(entry.shellId) !== entry) closed = true;
391
+ }
392
+ killFailures += attemptKillFailures;
393
+ if (!closed) unresolvedKills += 1;
394
+ return {
395
+ attempted: 1,
396
+ closed: closed ? 1 : 0,
397
+ unresolved: closed ? 0 : 1,
398
+ killFailures: attemptKillFailures,
399
+ };
400
+ })();
401
+ try {
402
+ return await entry.terminating;
403
+ } finally {
404
+ if (backgroundShells.get(entry.shellId) === entry) entry.terminating = null;
405
+ }
406
+ }
407
+
408
+ function sumTerminationReports(reports: readonly BackgroundShellTerminationReport[]): BackgroundShellTerminationReport {
409
+ return reports.reduce((total, report) => ({
410
+ attempted: total.attempted + report.attempted,
411
+ closed: total.closed + report.closed,
412
+ unresolved: total.unresolved + report.unresolved,
413
+ killFailures: total.killFailures + report.killFailures,
414
+ }), { attempted: 0, closed: 0, unresolved: 0, killFailures: 0 });
415
+ }
416
+
417
+ export function beginBackgroundShellShutdown(): void {
418
+ backgroundShellShutdownStarted = true;
419
+ }
420
+
421
+ export function backgroundShellAdmissionMetrics(): Readonly<AdmissionMetrics> {
422
+ return backgroundShellGate.metrics();
423
+ }
424
+
425
+ export function backgroundShellLifecycleMetrics(): {
426
+ idleTerminations: number;
427
+ absoluteTerminations: number;
428
+ unresolvedKills: number;
429
+ killFailures: number;
430
+ } {
431
+ return { idleTerminations, absoluteTerminations, unresolvedKills, killFailures };
432
+ }
433
+
434
+ export async function terminateBackgroundShellsForSession(
435
+ sessionId: string,
436
+ ): Promise<BackgroundShellTerminationReport> {
437
+ const owned = [...backgroundShells.values()].filter(entry => entry.sessionId === sessionId);
438
+ return sumTerminationReports(await Promise.all(owned.map(entry => terminateBackgroundShell(entry, "session_close"))));
439
+ }
440
+
441
+ export async function terminateAllBackgroundShells(): Promise<BackgroundShellTerminationReport> {
442
+ const entries = [...backgroundShells.values()];
443
+ return sumTerminationReports(await Promise.all(entries.map(entry => terminateBackgroundShell(entry, "shutdown"))));
444
+ }
445
+
446
+ export function setBackgroundShellRuntimeForTests(runtime: Partial<BackgroundShellRuntime>): void {
447
+ backgroundShellRuntime = { ...defaultBackgroundShellRuntime, ...runtime };
448
+ }
449
+
450
+ export async function resetBackgroundShellStateForTests(): Promise<void> {
451
+ const report = await terminateAllBackgroundShells();
452
+ if (report.unresolved !== 0 || backgroundShells.size !== 0 || backgroundShellGate.metrics().active !== 0) {
453
+ throw new Error("cannot reset background shell state before every direct child confirms close");
454
+ }
455
+ backgroundShellRuntime = defaultBackgroundShellRuntime;
456
+ backgroundShellGate = createAdmissionGate("cursor_background_shells", CURSOR_BACKGROUND_SHELL_MAX_LIVE);
457
+ nextShellId = 1;
458
+ backgroundShellShutdownStarted = false;
459
+ idleTerminations = 0;
460
+ absoluteTerminations = 0;
461
+ unresolvedKills = 0;
462
+ killFailures = 0;
463
+ }
464
+
465
+ export function backgroundShellSpawnExec(execMsg: ExecServerMessage, sessionId: string): Uint8Array {
466
+ if (execMsg.message.case !== "backgroundShellSpawnArgs") throw new Error("invalid background shell exec");
467
+ const args = execMsg.message.value;
468
+ const cwd = resolve(args.workingDirectory || process.cwd());
469
+ if (!sessionId) return backgroundShellSpawnError(execMsg, args.command, cwd, "background shell session owner required");
470
+ if (backgroundShellShutdownStarted) return backgroundShellSpawnError(execMsg, args.command, cwd, "background shell shutdown in progress");
471
+ const admissionLease = backgroundShellGate.tryAcquire();
472
+ if (!admissionLease) return backgroundShellSpawnError(execMsg, args.command, cwd, "background shell limit reached");
473
+ let child: ChildProcessWithoutNullStreams;
474
+ try {
475
+ child = backgroundShellRuntime.spawn(args.command, { cwd, shell: true });
476
+ } catch (err) {
477
+ admissionLease.release();
478
+ return backgroundShellSpawnError(execMsg, args.command, cwd, errorText(err));
479
+ }
480
+
481
+ const shellId = nextShellId++;
482
+ let confirmClose!: () => void;
483
+ const closePromise = new Promise<void>(resolveClose => { confirmClose = resolveClose; });
484
+ const now = backgroundShellRuntime.now();
485
+ const entry: BackgroundShellEntry = {
486
+ shellId,
487
+ sessionId,
488
+ child,
489
+ admissionLease,
490
+ outputLength: 0,
491
+ startedAt: now,
492
+ lastActivityAt: now,
493
+ terminating: null,
494
+ closePromise,
495
+ confirmClose,
496
+ };
497
+ try {
498
+ child.on("close", () => confirmBackgroundShellClose(entry));
499
+ child.on("error", () => { /* error alone never confirms direct-child termination */ });
500
+ backgroundShells.set(shellId, entry);
501
+ child.stdout.on("data", chunk => {
502
+ noteBackgroundShellActivity(entry, Buffer.byteLength(String(chunk)));
503
+ });
504
+ child.stderr.on("data", chunk => {
505
+ noteBackgroundShellActivity(entry, Buffer.byteLength(String(chunk)));
506
+ });
507
+ armBackgroundShellIdleTimer(entry);
508
+ armBackgroundShellAbsoluteTimer(entry);
509
+ return execBytes(execMsg, "backgroundShellSpawnResult", create(BackgroundShellSpawnResultSchema, {
510
+ result: {
511
+ case: "success",
512
+ value: create(BackgroundShellSpawnSuccessSchema, { shellId, command: args.command, workingDirectory: cwd, pid: child.pid }),
513
+ },
514
+ }));
515
+ } catch (err) {
516
+ void terminateBackgroundShell(entry, "session_close");
517
+ return backgroundShellSpawnError(execMsg, args.command, cwd, errorText(err));
518
+ }
519
+ }
520
+
521
+ export function rejectWriteShellStdinExecForPolicy(execMsg: ExecServerMessage): Uint8Array {
522
+ if (execMsg.message.case !== "writeShellStdinArgs") throw new Error("invalid shell stdin exec");
523
+ return execBytes(execMsg, "writeShellStdinResult", create(WriteShellStdinResultSchema, {
524
+ result: { case: "error", value: create(WriteShellStdinErrorSchema, { error: nativeShellDisabledMessage() }) },
525
+ }));
526
+ }
527
+
528
+ export function writeShellStdinExec(execMsg: ExecServerMessage, sessionId: string): Uint8Array {
529
+ if (execMsg.message.case !== "writeShellStdinArgs") throw new Error("invalid shell stdin exec");
530
+ const args = execMsg.message.value;
531
+ const shell = backgroundShells.get(args.shellId);
532
+ if (!shell) {
533
+ return execBytes(execMsg, "writeShellStdinResult", create(WriteShellStdinResultSchema, {
534
+ result: { case: "error", value: create(WriteShellStdinErrorSchema, { error: `Unknown shell id ${args.shellId}` }) },
535
+ }));
536
+ }
537
+ if (shell.sessionId !== sessionId) {
538
+ return execBytes(execMsg, "writeShellStdinResult", create(WriteShellStdinResultSchema, {
539
+ result: { case: "error", value: create(WriteShellStdinErrorSchema, { error: "shell belongs to another session" }) },
540
+ }));
541
+ }
542
+ const before = shell.outputLength;
543
+ shell.child.stdin.write(args.chars);
544
+ noteBackgroundShellActivity(shell);
545
+ return execBytes(execMsg, "writeShellStdinResult", create(WriteShellStdinResultSchema, {
546
+ result: { case: "success", value: create(WriteShellStdinSuccessSchema, { shellId: args.shellId, terminalFileLengthBeforeInputWritten: before }) },
547
+ }));
548
+ }
@@ -0,0 +1,118 @@
1
+ import { create } from "@bufbuild/protobuf";
2
+ import {
3
+ ComputerUseErrorSchema,
4
+ ComputerUseResultSchema,
5
+ ListMcpResourcesErrorSchema,
6
+ ListMcpResourcesExecResultSchema,
7
+ McpErrorSchema,
8
+ McpResultSchema,
9
+ ReadMcpResourceErrorSchema,
10
+ ReadMcpResourceExecResultSchema,
11
+ RecordScreenFailureSchema,
12
+ RecordScreenResultSchema,
13
+ type ComputerUseArgs,
14
+ type ComputerUseResult,
15
+ type ExecServerMessage,
16
+ type ListMcpResourcesExecResult,
17
+ type McpArgs,
18
+ type McpResult,
19
+ type ReadMcpResourceExecArgs,
20
+ type ReadMcpResourceExecResult,
21
+ type RecordScreenArgs,
22
+ type RecordScreenResult,
23
+ } from "./gen/agent_pb";
24
+ import { errorText, execBytes } from "./native-exec-common";
25
+ import { OCX_RESPONSES_TOOL_PROVIDER } from "./tool-definitions";
26
+
27
+ export interface CursorNativeToolDeps {
28
+ mcp?: (args: McpArgs) => McpResult | Promise<McpResult>;
29
+ listMcpResources?: () => ListMcpResourcesExecResult | Promise<ListMcpResourcesExecResult>;
30
+ readMcpResource?: (args: ReadMcpResourceExecArgs) => ReadMcpResourceExecResult | Promise<ReadMcpResourceExecResult>;
31
+ computerUse?: (args: ComputerUseArgs) => ComputerUseResult | Promise<ComputerUseResult>;
32
+ recordScreen?: (args: RecordScreenArgs) => RecordScreenResult | Promise<RecordScreenResult>;
33
+ }
34
+
35
+ export async function mcpExec(execMsg: ExecServerMessage, deps: CursorNativeToolDeps): Promise<Uint8Array> {
36
+ if (execMsg.message.case !== "mcpArgs") throw new Error("invalid mcp exec");
37
+ try {
38
+ if (execMsg.message.value.providerIdentifier === OCX_RESPONSES_TOOL_PROVIDER) {
39
+ return execBytes(execMsg, "mcpResult", create(McpResultSchema, {
40
+ result: { case: "error", value: create(McpErrorSchema, { error: "Responses client tools are surfaced to Codex and must not execute through the local MCP native-exec channel." }) },
41
+ }));
42
+ }
43
+ const result = deps.mcp
44
+ ? await deps.mcp(execMsg.message.value)
45
+ : create(McpResultSchema, { result: { case: "error", value: create(McpErrorSchema, { error: "No local MCP executor is configured inside opencodex." }) } });
46
+ return execBytes(execMsg, "mcpResult", result);
47
+ } catch (err) {
48
+ return execBytes(execMsg, "mcpResult", create(McpResultSchema, {
49
+ result: { case: "error", value: create(McpErrorSchema, { error: errorText(err) }) },
50
+ }));
51
+ }
52
+ }
53
+
54
+ export async function listMcpResourcesExec(execMsg: ExecServerMessage, deps: CursorNativeToolDeps): Promise<Uint8Array> {
55
+ if (execMsg.message.case !== "listMcpResourcesExecArgs") throw new Error("invalid list mcp resources exec");
56
+ // No executor wired (no MCP configured, or prepareMcp() stripped deps after a setup
57
+ // failure): return a typed error, symmetric with readMcpResource. An empty-success here
58
+ // would falsely imply "connected, zero resources" and mask a misconfiguration.
59
+ const result = deps.listMcpResources
60
+ ? await deps.listMcpResources()
61
+ : create(ListMcpResourcesExecResultSchema, {
62
+ result: { case: "error", value: create(ListMcpResourcesErrorSchema, {
63
+ error: "No local MCP resource executor is configured inside opencodex.",
64
+ }) },
65
+ });
66
+ return execBytes(execMsg, "listMcpResourcesExecResult", result);
67
+ }
68
+
69
+ export async function readMcpResourceExec(execMsg: ExecServerMessage, deps: CursorNativeToolDeps): Promise<Uint8Array> {
70
+ if (execMsg.message.case !== "readMcpResourceExecArgs") throw new Error("invalid read mcp resource exec");
71
+ const args = execMsg.message.value;
72
+ try {
73
+ const result = deps.readMcpResource
74
+ ? await deps.readMcpResource(args)
75
+ : create(ReadMcpResourceExecResultSchema, {
76
+ result: { case: "error", value: create(ReadMcpResourceErrorSchema, { uri: args.uri, error: "No local MCP resource executor is configured inside opencodex." }) },
77
+ });
78
+ return execBytes(execMsg, "readMcpResourceExecResult", result);
79
+ } catch (err) {
80
+ return execBytes(execMsg, "readMcpResourceExecResult", create(ReadMcpResourceExecResultSchema, {
81
+ result: { case: "error", value: create(ReadMcpResourceErrorSchema, { uri: args.uri, error: errorText(err) }) },
82
+ }));
83
+ }
84
+ }
85
+
86
+ export async function computerUseExec(execMsg: ExecServerMessage, deps: CursorNativeToolDeps): Promise<Uint8Array> {
87
+ if (execMsg.message.case !== "computerUseArgs") throw new Error("invalid computer use exec");
88
+ const args = execMsg.message.value;
89
+ try {
90
+ const result = deps.computerUse
91
+ ? await deps.computerUse(args)
92
+ : create(ComputerUseResultSchema, {
93
+ result: { case: "error", value: create(ComputerUseErrorSchema, { error: "computer-use is not supported in this headless opencodex proxy. Configure provider.desktopExecutor.computerUseCommand to enable it.", actionCount: args.actions.length, durationMs: 0 }) },
94
+ });
95
+ return execBytes(execMsg, "computerUseResult", result);
96
+ } catch (err) {
97
+ return execBytes(execMsg, "computerUseResult", create(ComputerUseResultSchema, {
98
+ result: { case: "error", value: create(ComputerUseErrorSchema, { error: errorText(err), actionCount: args.actions.length, durationMs: 0 }) },
99
+ }));
100
+ }
101
+ }
102
+
103
+ export async function recordScreenExec(execMsg: ExecServerMessage, deps: CursorNativeToolDeps): Promise<Uint8Array> {
104
+ if (execMsg.message.case !== "recordScreenArgs") throw new Error("invalid record screen exec");
105
+ const args = execMsg.message.value;
106
+ try {
107
+ const result = deps.recordScreen
108
+ ? await deps.recordScreen(args)
109
+ : create(RecordScreenResultSchema, {
110
+ result: { case: "failure", value: create(RecordScreenFailureSchema, { error: "record-screen is not supported in this headless opencodex proxy. Configure provider.desktopExecutor.recordScreenCommand to enable it." }) },
111
+ });
112
+ return execBytes(execMsg, "recordScreenResult", result);
113
+ } catch (err) {
114
+ return execBytes(execMsg, "recordScreenResult", create(RecordScreenResultSchema, {
115
+ result: { case: "failure", value: create(RecordScreenFailureSchema, { error: errorText(err) }) },
116
+ }));
117
+ }
118
+ }