@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,726 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { chmodSync, closeSync, existsSync, fsyncSync, linkSync, openSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { isAbsolute, join, posix, win32 } from "node:path";
5
+ import { Database } from "bun:sqlite";
6
+
7
+ const DEFAULT_EXPIRES_MS = 3600_000;
8
+ const KIRO_CLI_RECOVERY_SUFFIX = ".opencodex-recovery";
9
+ const KIRO_CLI_RECOVERY_HEADER_V1 = Buffer.from("opencodex-kiro-session-v1\n", "utf8");
10
+ const KIRO_CLI_RECOVERY_HEADER = Buffer.from("opencodex-kiro-session-v2\n", "utf8");
11
+ const KIRO_CLI_RECOVERY_PROCESS_INSTANCE = randomUUID();
12
+ const KIRO_CLI_RECOVERY_PROCESS_INSTANCE_PATTERN = /^[A-Za-z0-9._-]{1,128}$/;
13
+ const SQLITE_DATABASE_HEADER = Buffer.from("SQLite format 3\0", "binary");
14
+ const KIRO_REGION_PATTERN = /^[a-z]{2}(?:-[a-z]+)+-\d$/;
15
+ const CLIENT_ID_HASH_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
16
+ const TOKEN_KEYS = [
17
+ "kirocli:odic:token",
18
+ "kirocli:oidc:token",
19
+ "kirocli:social:token",
20
+ "codewhisperer:odic:token",
21
+ ];
22
+ const REGISTRATION_KEYS = [
23
+ "kirocli:odic:device-registration",
24
+ "kirocli:oidc:device-registration",
25
+ "codewhisperer:odic:device-registration",
26
+ ];
27
+
28
+ export type KiroAuthType = "kiro_desktop" | "aws_sso_oidc";
29
+ export type KiroCredentialSource = "json" | "sqlite";
30
+ export type KiroDiagnosticStatus =
31
+ | "missing"
32
+ | "unreadable"
33
+ | "schema_mismatch"
34
+ | "invalid_json"
35
+ | "token_missing"
36
+ | "token_ambiguous"
37
+ | "token_key_missing"
38
+ | "token_found"
39
+ | "registration_found";
40
+
41
+ export interface KiroImportDiagnostic {
42
+ location:
43
+ | "kiro-creds-file"
44
+ | "kiro-cli-db-env"
45
+ | "kiro-cli-data"
46
+ | "kiro-cli-linux-data"
47
+ | "kiro-cli-windows-data"
48
+ | "amazon-q-data"
49
+ | "kiro-sso-cache";
50
+ status: KiroDiagnosticStatus;
51
+ }
52
+
53
+ export interface ImportedKiroCredential {
54
+ access: string;
55
+ refresh: string;
56
+ expires: number;
57
+ source: KiroCredentialSource;
58
+ authType: KiroAuthType;
59
+ profileArn?: string;
60
+ ssoRegion?: string;
61
+ apiRegion?: string;
62
+ clientId?: string;
63
+ clientSecret?: string;
64
+ }
65
+
66
+ /** Opaque backup retained on disk while a forced Kiro CLI login is pending persistence. */
67
+ export interface KiroCliSessionSnapshot {
68
+ readonly path: string;
69
+ readonly database: Buffer;
70
+ readonly recoveryPath: string;
71
+ }
72
+
73
+ type JsonObject = Record<string, unknown>;
74
+
75
+ function userHome(): string {
76
+ return process.env.HOME || homedir();
77
+ }
78
+
79
+ function expandPath(path: string): string {
80
+ if (path.startsWith("~/")) return join(userHome(), path.slice(2));
81
+ return isAbsolute(path) ? path : join(process.cwd(), path);
82
+ }
83
+
84
+ function stringField(data: JsonObject, ...keys: string[]): string | undefined {
85
+ for (const key of keys) {
86
+ const value = data[key];
87
+ if (typeof value === "string" && value.length > 0) return value;
88
+ }
89
+ return undefined;
90
+ }
91
+
92
+ function parseExpires(value: unknown, present: boolean, hasRefreshToken: boolean): number {
93
+ if (typeof value === "number" && Number.isFinite(value)) return value < 10_000_000_000 ? value * 1000 : value;
94
+ if (typeof value === "string" && value.length > 0) {
95
+ const parsed = Date.parse(value);
96
+ if (Number.isFinite(parsed)) return parsed;
97
+ }
98
+ if (present) {
99
+ console.warn(
100
+ `[ocx:kiro:credentials] credential expiry is present but unparseable; ${
101
+ hasRefreshToken ? "treating credential as expired" : "using the default TTL because no refresh token is available"
102
+ }`,
103
+ );
104
+ if (hasRefreshToken) return 0;
105
+ }
106
+ return Date.now() + DEFAULT_EXPIRES_MS;
107
+ }
108
+
109
+ export function inferRegionFromProfileArn(arn: string | undefined): string | undefined {
110
+ if (!arn) return undefined;
111
+ const region = arn.split(":")[3];
112
+ return normalizeKiroRegion(region);
113
+ }
114
+
115
+ export function normalizeKiroRegion(region: string | undefined): string | undefined {
116
+ const trimmed = region?.trim();
117
+ return trimmed && KIRO_REGION_PATTERN.test(trimmed) ? trimmed : undefined;
118
+ }
119
+
120
+ export function requireKiroRegion(region: string | undefined): string {
121
+ const normalized = normalizeKiroRegion(region);
122
+ if (!normalized) throw new Error("Kiro: invalid region value.");
123
+ return normalized;
124
+ }
125
+
126
+ function jsonCredentialPaths(): string[] {
127
+ return [process.env.KIRO_CREDS_FILE, process.env.KIRO_CREDENTIALS_FILE]
128
+ .filter((value): value is string => !!value)
129
+ .map(expandPath);
130
+ }
131
+
132
+ export type KiroCliNativeLocation = "kiro-cli-data" | "kiro-cli-linux-data" | "kiro-cli-windows-data";
133
+
134
+ export interface KiroCliNativeInputs {
135
+ env: Record<string, string | undefined>;
136
+ platform: NodeJS.Platform;
137
+ home: string;
138
+ }
139
+
140
+ /**
141
+ * Native kiro-cli session stores, as a pure function of (env, platform, home).
142
+ *
143
+ * Pure + parameterized following `src/claude/desktop-3p-paths.ts`: `process.platform` is stubbable
144
+ * in this repo, but `os.platform()` does NOT follow it under Bun, so a pure resolver is the reliable
145
+ * way to exercise the win32 branch (and its env fallbacks) on a macOS/Linux host.
146
+ *
147
+ * Windows (issue #710): the official installer stores the auth DB at
148
+ * `%LOCALAPPDATA%\Kiro-Cli\data.sqlite3`. When LOCALAPPDATA is unset or blank, fall back to
149
+ * `%USERPROFILE%\AppData\Local`, then to the injected platform-native home. Deliberately NOT
150
+ * `userHome()`: that is `HOME || homedir()` and Windows shells (Git Bash / MSYS / CI) routinely
151
+ * export a POSIX-style `HOME`, which would point this at a non-native path.
152
+ *
153
+ * One entry per platform on purpose: this list also drives forced-login snapshot/rollback, so it
154
+ * must name the database the LOCAL kiro-cli actually mutates, never a foreign platform's path.
155
+ */
156
+ export function resolveKiroCliNativeSessionEntries(
157
+ inputs: KiroCliNativeInputs,
158
+ ): Array<{ location: KiroCliNativeLocation; path: string }> {
159
+ const { env, platform, home } = inputs;
160
+ if (platform === "win32") {
161
+ const base = env.LOCALAPPDATA?.trim()
162
+ || (env.USERPROFILE?.trim() ? win32.join(env.USERPROFILE.trim(), "AppData", "Local") : "")
163
+ || win32.join(home, "AppData", "Local");
164
+ return [{ location: "kiro-cli-windows-data", path: win32.join(base, "Kiro-Cli", "data.sqlite3") }];
165
+ }
166
+ if (platform === "darwin") {
167
+ return [{ location: "kiro-cli-data", path: posix.join(home, "Library", "Application Support", "kiro-cli", "data.sqlite3") }];
168
+ }
169
+ return [{ location: "kiro-cli-linux-data", path: posix.join(home, ".local", "share", "kiro-cli", "data.sqlite3") }];
170
+ }
171
+
172
+ /**
173
+ * Resolve the absolute kiro-cli executable for spawn/login helpers.
174
+ *
175
+ * Pure + parameterized like `resolveKiroCliNativeSessionEntries` so Windows install layouts can be
176
+ * covered from any host. PATH remains the first choice; only when bare `kiro-cli` is missing do we
177
+ * fall back to the platform-native install directories next to the session database.
178
+ *
179
+ * Windows: official MSI installs to `C:\Program Files\Kiro-Cli\kiro-cli.exe`, while some local
180
+ * installs keep the binary next to `%LOCALAPPDATA%\Kiro-Cli\data.sqlite3`.
181
+ * macOS/Linux: prefer PATH, then the usual user-local bin directories.
182
+ */
183
+ export function resolveKiroCliExecutable(
184
+ inputs: KiroCliNativeInputs & {
185
+ pathEntries?: string[];
186
+ exists?: (path: string) => boolean;
187
+ isFile?: (path: string) => boolean;
188
+ },
189
+ ): string {
190
+ const exists = inputs.exists ?? existsSync;
191
+ // A directory named `kiro-cli` on PATH satisfies existsSync and would then be handed to
192
+ // spawn(), which fails with EACCES at login instead of falling through to the next candidate.
193
+ // When a caller injects `exists` it owns the whole filesystem view, so the real stat would
194
+ // reject every synthetic path; such callers inject `isFile` too when they care about it.
195
+ const isFile = inputs.isFile ?? (inputs.exists ? () => true : ((path: string) => {
196
+ try {
197
+ return statSync(path).isFile();
198
+ } catch {
199
+ return false;
200
+ }
201
+ }));
202
+ const pathEntries = inputs.pathEntries
203
+ ?? (inputs.env.PATH ?? inputs.env.Path ?? "").split(inputs.platform === "win32" ? ";" : ":")
204
+ .map(entry => entry.trim())
205
+ .filter(Boolean);
206
+
207
+ const pathCandidates = inputs.platform === "win32"
208
+ ? pathEntries.flatMap(entry => [
209
+ win32.join(entry, "kiro-cli.exe"),
210
+ win32.join(entry, "kiro-cli"),
211
+ ])
212
+ : pathEntries.map(entry => posix.join(entry, "kiro-cli"));
213
+
214
+ const installCandidates: string[] = [];
215
+ if (inputs.platform === "win32") {
216
+ const localBase = inputs.env.LOCALAPPDATA?.trim()
217
+ || (inputs.env.USERPROFILE?.trim() ? win32.join(inputs.env.USERPROFILE.trim(), "AppData", "Local") : "")
218
+ || win32.join(inputs.home, "AppData", "Local");
219
+ const programFiles = inputs.env["ProgramFiles"]?.trim() || "C:\\Program Files";
220
+ installCandidates.push(
221
+ win32.join(localBase, "Kiro-Cli", "kiro-cli.exe"),
222
+ win32.join(programFiles, "Kiro-Cli", "kiro-cli.exe"),
223
+ );
224
+ } else if (inputs.platform === "darwin") {
225
+ installCandidates.push(
226
+ posix.join(inputs.home, ".local", "bin", "kiro-cli"),
227
+ "/usr/local/bin/kiro-cli",
228
+ "/opt/homebrew/bin/kiro-cli",
229
+ );
230
+ } else {
231
+ installCandidates.push(
232
+ posix.join(inputs.home, ".local", "bin", "kiro-cli"),
233
+ "/usr/local/bin/kiro-cli",
234
+ );
235
+ }
236
+
237
+ for (const candidate of [...pathCandidates, ...installCandidates]) {
238
+ if (exists(candidate) && isFile(candidate)) return candidate;
239
+ }
240
+ return inputs.platform === "win32" ? "kiro-cli.exe" : "kiro-cli";
241
+ }
242
+
243
+ function nativeKiroCliSessionEntries(): Array<{ location: KiroCliNativeLocation; path: string }> {
244
+ // Only the stores that `kiro-cli logout` / `kiro-cli login` themselves mutate. Import fallbacks
245
+ // (Amazon Q / SSO cache) and KIROCLI_DB_PATH selectors must not be snapshotted for rollback.
246
+ return resolveKiroCliNativeSessionEntries({
247
+ env: process.env,
248
+ platform: process.platform,
249
+ // POSIX keeps HOME-first userHome() so existing HOME-based fixtures still resolve; win32 prefers
250
+ // LOCALAPPDATA/USERPROFILE and only falls back to this platform-native home.
251
+ home: process.platform === "win32" ? homedir() : userHome(),
252
+ });
253
+ }
254
+
255
+ function sqliteEntries(): Array<{ location: KiroImportDiagnostic["location"]; path: string }> {
256
+ const configured = process.env.KIROCLI_DB_PATH?.trim() || process.env.KIRO_CLI_DB_FILE?.trim();
257
+ if (configured) return [{ location: "kiro-cli-db-env", path: expandPath(configured) }];
258
+ return [
259
+ ...nativeKiroCliSessionEntries(),
260
+ { location: "amazon-q-data", path: join(userHome(), ".local", "share", "amazon-q", "data.sqlite3") },
261
+ { location: "kiro-sso-cache", path: join(userHome(), ".kiro", "sso", "cache.db") },
262
+ ];
263
+ }
264
+
265
+ function kiroCliImportSelectorConfigured(): boolean {
266
+ return Boolean(process.env.KIROCLI_DB_PATH?.trim() || process.env.KIRO_CLI_DB_FILE?.trim());
267
+ }
268
+
269
+ function selectTokenRow(db: Database): { value: string } | null | "ambiguous" | "selected_missing" {
270
+ const rows = db.query("SELECT key, value FROM auth_kv WHERE key LIKE ? ORDER BY key ASC").all("%:token") as Array<{ key: string; value: string }>;
271
+ const selectedKey = process.env.KIROCLI_TOKEN_KEY?.trim();
272
+ if (selectedKey) return rows.find(row => row.key === selectedKey) ?? "selected_missing";
273
+ for (const preferred of TOKEN_KEYS) {
274
+ const row = rows.find(candidate => candidate.key === preferred);
275
+ if (row) return row;
276
+ }
277
+ if (rows.length === 0) return null;
278
+ if (rows.length === 1) return rows[0];
279
+ return "ambiguous";
280
+ }
281
+
282
+ function credentialFromJson(data: JsonObject, source: KiroCredentialSource): ImportedKiroCredential | undefined {
283
+ const access = stringField(data, "accessToken", "access_token");
284
+ if (!access) return undefined;
285
+ const profileArn = stringField(data, "profileArn", "profile_arn");
286
+ const ssoRegion = stringField(data, "region");
287
+ const apiRegion = stringField(data, "apiRegion", "api_region") || inferRegionFromProfileArn(profileArn) || ssoRegion;
288
+ const clientId = stringField(data, "clientId", "client_id");
289
+ const clientSecret = stringField(data, "clientSecret", "client_secret");
290
+ const refresh = stringField(data, "refreshToken", "refresh_token") || "";
291
+ const expiryPresent = Object.hasOwn(data, "expiresAt") || Object.hasOwn(data, "expires_at");
292
+ return {
293
+ access,
294
+ refresh,
295
+ expires: parseExpires(data.expiresAt ?? data.expires_at, expiryPresent, refresh.length > 0),
296
+ source,
297
+ authType: clientId && clientSecret ? "aws_sso_oidc" : "kiro_desktop",
298
+ ...(profileArn ? { profileArn } : {}),
299
+ ...(ssoRegion ? { ssoRegion } : {}),
300
+ ...(apiRegion ? { apiRegion } : {}),
301
+ ...(clientId ? { clientId } : {}),
302
+ ...(clientSecret ? { clientSecret } : {}),
303
+ };
304
+ }
305
+
306
+ function loadEnterpriseRegistration(data: JsonObject): JsonObject | undefined {
307
+ const hash = stringField(data, "clientIdHash");
308
+ if (!hash) return undefined;
309
+ if (!CLIENT_ID_HASH_PATTERN.test(hash)) return undefined;
310
+ const path = join(userHome(), ".aws", "sso", "cache", `${hash}.json`);
311
+ if (!existsSync(path)) return undefined;
312
+ try {
313
+ return JSON.parse(readFileSync(path, "utf8")) as JsonObject;
314
+ } catch {
315
+ return undefined;
316
+ }
317
+ }
318
+
319
+ function readJsonCredentials(diagnostics: KiroImportDiagnostic[]): ImportedKiroCredential | undefined {
320
+ for (const path of jsonCredentialPaths()) {
321
+ if (!existsSync(path)) {
322
+ diagnostics.push({ location: "kiro-creds-file", status: "missing" });
323
+ continue;
324
+ }
325
+ let data: JsonObject;
326
+ try {
327
+ const raw = readFileSync(path, "utf8");
328
+ try {
329
+ data = JSON.parse(raw) as JsonObject;
330
+ } catch {
331
+ diagnostics.push({ location: "kiro-creds-file", status: "invalid_json" });
332
+ continue;
333
+ }
334
+ } catch {
335
+ diagnostics.push({ location: "kiro-creds-file", status: "unreadable" });
336
+ continue;
337
+ }
338
+ const registration = loadEnterpriseRegistration(data);
339
+ const merged = registration ? { ...data, ...registration } : data;
340
+ const credential = credentialFromJson(merged, "json");
341
+ diagnostics.push({ location: "kiro-creds-file", status: credential ? "token_found" : "token_missing" });
342
+ if (credential) return credential;
343
+ }
344
+ return undefined;
345
+ }
346
+
347
+ function readStateProfile(db: Database): { profileArn?: string; apiRegion?: string } {
348
+ try {
349
+ const row = db.query("SELECT value FROM state WHERE key = ?").get("api.codewhisperer.profile") as { value: string } | null;
350
+ if (!row) return {};
351
+ const data = JSON.parse(row.value) as JsonObject;
352
+ const profileArn = stringField(data, "arn", "profileArn", "profile_arn");
353
+ return { ...(profileArn ? { profileArn } : {}), ...(profileArn ? { apiRegion: inferRegionFromProfileArn(profileArn) } : {}) };
354
+ } catch {
355
+ return {};
356
+ }
357
+ }
358
+
359
+ interface LocatedKiroCliCredential {
360
+ credential: ImportedKiroCredential;
361
+ path: string;
362
+ database?: Buffer;
363
+ }
364
+
365
+ function readSqliteCredentials(
366
+ diagnostics: KiroImportDiagnostic[],
367
+ includeSnapshot = false,
368
+ ): LocatedKiroCliCredential | undefined {
369
+ for (const { location, path } of sqliteEntries()) {
370
+ if (!existsSync(path)) {
371
+ diagnostics.push({ location, status: "missing" });
372
+ continue;
373
+ }
374
+ let db: Database | undefined;
375
+ try {
376
+ db = new Database(path, { readonly: true });
377
+ try { db.exec("PRAGMA busy_timeout = 5000"); } catch { /* read-only best effort */ }
378
+ } catch {
379
+ diagnostics.push({ location, status: "unreadable" });
380
+ continue;
381
+ }
382
+ try {
383
+ const row = selectTokenRow(db);
384
+ if (row === "ambiguous") {
385
+ diagnostics.push({ location, status: "token_ambiguous" });
386
+ throw new Error("Kiro CLI credential database contains multiple tokens; set KIROCLI_TOKEN_KEY to select one");
387
+ }
388
+ if (row === "selected_missing") {
389
+ diagnostics.push({ location, status: "token_key_missing" });
390
+ throw new Error("The KIROCLI_TOKEN_KEY selection was not found in the Kiro CLI credential database");
391
+ }
392
+ if (!row) {
393
+ diagnostics.push({ location, status: "token_missing" });
394
+ continue;
395
+ }
396
+ let tokenData: JsonObject;
397
+ try {
398
+ tokenData = JSON.parse(row.value) as JsonObject;
399
+ } catch {
400
+ diagnostics.push({ location, status: "invalid_json" });
401
+ continue;
402
+ }
403
+ let registrationData: JsonObject = {};
404
+ for (const key of REGISTRATION_KEYS) {
405
+ const row = db.query("SELECT value FROM auth_kv WHERE key = ?").get(key) as { value: string } | null;
406
+ if (!row) continue;
407
+ try {
408
+ registrationData = JSON.parse(row.value) as JsonObject;
409
+ diagnostics.push({ location, status: "registration_found" });
410
+ } catch {
411
+ diagnostics.push({ location, status: "invalid_json" });
412
+ }
413
+ break;
414
+ }
415
+ const profile = readStateProfile(db);
416
+ const merged = { ...registrationData, ...tokenData, ...profile };
417
+ const credential = credentialFromJson(merged, "sqlite");
418
+ diagnostics.push({ location, status: credential ? "token_found" : "token_missing" });
419
+ if (credential) {
420
+ return {
421
+ credential,
422
+ path,
423
+ ...(includeSnapshot ? { database: db.serialize() } : {}),
424
+ };
425
+ }
426
+ } catch (error) {
427
+ if (error instanceof Error && error.message.includes("KIROCLI_TOKEN_KEY")) throw error;
428
+ diagnostics.push({ location, status: "schema_mismatch" });
429
+ } finally {
430
+ db.close();
431
+ }
432
+ }
433
+ return undefined;
434
+ }
435
+
436
+ export function inspectKiroCredentialSources(): { credential: ImportedKiroCredential | null; diagnostics: KiroImportDiagnostic[] } {
437
+ const diagnostics: KiroImportDiagnostic[] = [];
438
+ const json = readJsonCredentials(diagnostics);
439
+ if (json) return { credential: json, diagnostics };
440
+ const sqlite = readSqliteCredentials(diagnostics);
441
+ return { credential: sqlite?.credential ?? null, diagnostics };
442
+ }
443
+
444
+ export function inspectKiroCliSqliteSources(): { credential: ImportedKiroCredential | null; diagnostics: KiroImportDiagnostic[] } {
445
+ const diagnostics: KiroImportDiagnostic[] = [];
446
+ return { credential: readSqliteCredentials(diagnostics)?.credential ?? null, diagnostics };
447
+ }
448
+
449
+ export function readImportedKiroCredential(): ImportedKiroCredential | null {
450
+ return inspectKiroCredentialSources().credential;
451
+ }
452
+
453
+ export function readKiroCliSqliteCredential(): ImportedKiroCredential | null {
454
+ return inspectKiroCliSqliteSources().credential;
455
+ }
456
+
457
+ /** Capture the complete active CLI database so a failed account switch can restore it exactly. */
458
+ export function snapshotKiroCliSession(): KiroCliSessionSnapshot | null {
459
+ return inspectKiroCliSessionSnapshot().snapshot;
460
+ }
461
+
462
+ /**
463
+ * A store that exists but cannot be captured must never be logged out of: the recovery contract
464
+ * promises an exact restore, and without a snapshot a later failure would destroy the session for
465
+ * good. `missing`/`token_missing` are the only statuses that mean "there is nothing to lose".
466
+ */
467
+ const KIRO_UNSNAPSHOTTABLE_SESSION_STATUSES: ReadonlySet<KiroDiagnosticStatus> = new Set([
468
+ "unreadable",
469
+ "schema_mismatch",
470
+ "invalid_json",
471
+ "token_ambiguous",
472
+ "token_key_missing",
473
+ "token_found",
474
+ ]);
475
+
476
+ /**
477
+ * Capture the active CLI session and report why capture failed. `blocked` is true when a session
478
+ * store is present but could not be snapshotted (unreadable / schema-mismatched / ambiguous), so
479
+ * callers can abort before mutating it.
480
+ *
481
+ * Forced-login rollback must target the exact database `kiro-cli` mutates. Custom import selectors
482
+ * and lower-priority Amazon Q / SSO caches are never used here: snapshotting the wrong file would
483
+ * leave the real CLI session switched or lost after failure.
484
+ */
485
+ export function inspectKiroCliSessionSnapshot(): {
486
+ snapshot: KiroCliSessionSnapshot | null;
487
+ diagnostics: KiroImportDiagnostic[];
488
+ blocked: boolean;
489
+ } {
490
+ const diagnostics: KiroImportDiagnostic[] = [];
491
+ if (kiroCliImportSelectorConfigured()) {
492
+ diagnostics.push({ location: "kiro-cli-db-env", status: "token_ambiguous" });
493
+ return { snapshot: null, diagnostics, blocked: true };
494
+ }
495
+
496
+ for (const { location, path } of nativeKiroCliSessionEntries()) {
497
+ if (!existsSync(path)) {
498
+ diagnostics.push({ location, status: "missing" });
499
+ continue;
500
+ }
501
+
502
+ let db: Database | undefined;
503
+ try {
504
+ db = new Database(path, { readonly: true });
505
+ try { db.exec("PRAGMA busy_timeout = 5000"); } catch { /* read-only best effort */ }
506
+ } catch {
507
+ diagnostics.push({ location, status: "unreadable" });
508
+ return { snapshot: null, diagnostics, blocked: true };
509
+ }
510
+
511
+ try {
512
+ const row = selectTokenRow(db);
513
+ if (row === "ambiguous") {
514
+ diagnostics.push({ location, status: "token_ambiguous" });
515
+ return { snapshot: null, diagnostics, blocked: true };
516
+ }
517
+ if (row === "selected_missing") {
518
+ diagnostics.push({ location, status: "token_key_missing" });
519
+ return { snapshot: null, diagnostics, blocked: true };
520
+ }
521
+ if (!row) {
522
+ // An existing CLI database with no recognized token is still what logout mutates. Falling
523
+ // through to Amazon Q / another platform path would snapshot the wrong store.
524
+ diagnostics.push({ location, status: "token_missing" });
525
+ return { snapshot: null, diagnostics, blocked: true };
526
+ }
527
+ try {
528
+ JSON.parse(row.value);
529
+ } catch {
530
+ diagnostics.push({ location, status: "invalid_json" });
531
+ return { snapshot: null, diagnostics, blocked: true };
532
+ }
533
+ const database = db.serialize();
534
+ diagnostics.push({ location, status: "token_found" });
535
+ return {
536
+ snapshot: {
537
+ path,
538
+ database,
539
+ recoveryPath: `${path}${KIRO_CLI_RECOVERY_SUFFIX}`,
540
+ },
541
+ diagnostics,
542
+ blocked: false,
543
+ };
544
+ } catch (error) {
545
+ if (error instanceof Error && error.message.includes("KIROCLI_TOKEN_KEY")) {
546
+ diagnostics.push({ location, status: "token_key_missing" });
547
+ return { snapshot: null, diagnostics, blocked: true };
548
+ }
549
+ diagnostics.push({ location, status: "schema_mismatch" });
550
+ return { snapshot: null, diagnostics, blocked: true };
551
+ } finally {
552
+ db.close();
553
+ }
554
+ }
555
+
556
+ return {
557
+ snapshot: null,
558
+ diagnostics,
559
+ blocked: diagnostics.some(entry => KIRO_UNSNAPSHOTTABLE_SESSION_STATUSES.has(entry.status)),
560
+ };
561
+ }
562
+
563
+ /** Persist a complete, private recovery image before kiro-cli is allowed to mutate its store. */
564
+ export function persistKiroCliSessionRecovery(snapshot: KiroCliSessionSnapshot): void {
565
+ if (existsSync(snapshot.recoveryPath)) {
566
+ throw new Error("Kiro CLI session recovery is already pending.");
567
+ }
568
+ const nonce = `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
569
+ const staged = `${snapshot.recoveryPath}.${nonce}.tmp`;
570
+ try {
571
+ writeFileSync(staged, Buffer.concat([
572
+ KIRO_CLI_RECOVERY_HEADER,
573
+ Buffer.from(`${process.pid}\n`, "utf8"),
574
+ Buffer.from(`${KIRO_CLI_RECOVERY_PROCESS_INSTANCE}\n`, "utf8"),
575
+ snapshot.database,
576
+ ]), { flag: "wx", mode: 0o600 });
577
+ try { chmodSync(staged, 0o600); } catch { /* platform may ignore chmod */ }
578
+ const fd = openSync(staged, "r+");
579
+ try { fsyncSync(fd); } finally { closeSync(fd); }
580
+ // A hard link publishes the fsynced inode atomically and, unlike rename, can never replace
581
+ // another process's live recovery transaction if both raced past the initial existence check.
582
+ linkSync(staged, snapshot.recoveryPath);
583
+ } finally {
584
+ rmSync(staged, { force: true });
585
+ }
586
+ }
587
+
588
+ /** Remove recovery data only after the corresponding login transaction has settled. */
589
+ export function discardKiroCliSessionRecovery(snapshot: KiroCliSessionSnapshot): void {
590
+ rmSync(snapshot.recoveryPath, { force: true });
591
+ }
592
+
593
+ interface ParsedKiroCliSessionRecovery {
594
+ ownerPid: number;
595
+ ownerProcessInstance?: string;
596
+ database: Buffer;
597
+ }
598
+
599
+ function parseKiroCliSessionRecovery(payload: Buffer): ParsedKiroCliSessionRecovery | null {
600
+ const isV2 = payload.subarray(0, KIRO_CLI_RECOVERY_HEADER.length).equals(KIRO_CLI_RECOVERY_HEADER);
601
+ const isV1 = payload.subarray(0, KIRO_CLI_RECOVERY_HEADER_V1.length).equals(KIRO_CLI_RECOVERY_HEADER_V1);
602
+ if (!isV2 && !isV1) return null;
603
+ const headerLength = isV2 ? KIRO_CLI_RECOVERY_HEADER.length : KIRO_CLI_RECOVERY_HEADER_V1.length;
604
+ const ownerEnd = payload.indexOf(0x0a, headerLength);
605
+ if (ownerEnd <= headerLength) return null;
606
+ const ownerPid = Number(payload.subarray(headerLength, ownerEnd).toString("utf8"));
607
+ let databaseStart = ownerEnd + 1;
608
+ let ownerProcessInstance: string | undefined;
609
+ if (isV2) {
610
+ const instanceEnd = payload.indexOf(0x0a, databaseStart);
611
+ if (instanceEnd <= databaseStart) return null;
612
+ ownerProcessInstance = payload.subarray(databaseStart, instanceEnd).toString("utf8");
613
+ if (!KIRO_CLI_RECOVERY_PROCESS_INSTANCE_PATTERN.test(ownerProcessInstance)) return null;
614
+ databaseStart = instanceEnd + 1;
615
+ }
616
+ const database = payload.subarray(databaseStart);
617
+ if (
618
+ !Number.isSafeInteger(ownerPid) || ownerPid <= 0 ||
619
+ !database.subarray(0, SQLITE_DATABASE_HEADER.length).equals(SQLITE_DATABASE_HEADER)
620
+ ) {
621
+ return null;
622
+ }
623
+ return { ownerPid, ...(ownerProcessInstance ? { ownerProcessInstance } : {}), database };
624
+ }
625
+
626
+ function isKiroRecoveryOwnerAlive(ownerPid: number, ownerProcessInstance?: string): boolean {
627
+ // A restarted supervisor may reuse the exact same PID (commonly PID 1). Only this process
628
+ // instance's nonce proves that a same-PID recovery transaction is still live.
629
+ if (ownerPid === process.pid) return ownerProcessInstance === KIRO_CLI_RECOVERY_PROCESS_INSTANCE;
630
+ try {
631
+ process.kill(ownerPid, 0);
632
+ return true;
633
+ } catch (error) {
634
+ if (error && typeof error === "object" && "code" in error) return error.code !== "ESRCH";
635
+ return true;
636
+ }
637
+ }
638
+
639
+ /** Restore a previously captured CLI database after every kiro-cli child process has exited. */
640
+ export function restoreKiroCliSession(snapshot: KiroCliSessionSnapshot): void {
641
+ const nonce = `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
642
+ const staged = `${snapshot.path}.ocx-restore.${nonce}.tmp`;
643
+ const displacedBase = `${snapshot.path}.ocx-restore.${nonce}.new`;
644
+ const displaced: Array<{ current: string; backup: string }> = [];
645
+ let published = false;
646
+ try {
647
+ writeFileSync(staged, snapshot.database, { mode: 0o600 });
648
+ try { chmodSync(staged, 0o600); } catch { /* platform may ignore chmod */ }
649
+ for (const suffix of ["", "-wal", "-shm", "-journal"]) {
650
+ const current = `${snapshot.path}${suffix}`;
651
+ if (!existsSync(current)) continue;
652
+ const backup = `${displacedBase}${suffix || ".db"}`;
653
+ renameSync(current, backup);
654
+ displaced.push({ current, backup });
655
+ }
656
+ renameSync(staged, snapshot.path);
657
+ published = true;
658
+ } catch (error) {
659
+ const recoveryErrors: unknown[] = [];
660
+ for (const { current, backup } of [...displaced].reverse()) {
661
+ if (!existsSync(backup) || existsSync(current)) continue;
662
+ try { renameSync(backup, current); } catch (recoveryError) { recoveryErrors.push(recoveryError); }
663
+ }
664
+ if (recoveryErrors.length > 0) throw new AggregateError([error, ...recoveryErrors], "Kiro CLI session restore failed during rollback.");
665
+ throw error;
666
+ } finally {
667
+ rmSync(staged, { force: true });
668
+ if (published) {
669
+ for (const { backup } of displaced) {
670
+ try { rmSync(backup, { force: true }); } catch { /* restored database is already published */ }
671
+ }
672
+ }
673
+ }
674
+ }
675
+
676
+ /** Restore a transaction abandoned by a crashed process before starting another forced login. */
677
+ export function restoreStaleKiroCliSessionRecovery(): boolean {
678
+ for (const { path } of nativeKiroCliSessionEntries()) {
679
+ const recoveryPath = `${path}${KIRO_CLI_RECOVERY_SUFFIX}`;
680
+ if (!existsSync(recoveryPath)) continue;
681
+ const payload = readFileSync(recoveryPath);
682
+ const recovery = parseKiroCliSessionRecovery(payload);
683
+ if (!recovery) {
684
+ throw new Error(
685
+ `Kiro CLI session recovery data is invalid: ${recoveryPath}. Remove this file to continue.`,
686
+ );
687
+ }
688
+ if (isKiroRecoveryOwnerAlive(recovery.ownerPid, recovery.ownerProcessInstance)) {
689
+ throw new Error(
690
+ `Another Kiro CLI login transaction is still in progress (pid ${recovery.ownerPid}, ${recoveryPath}).`,
691
+ );
692
+ }
693
+ // Atomically claim the marker before restoring so a concurrent process cannot restore the
694
+ // same stale image (and later delete a newer recovery file published by the winner).
695
+ const claimedPath = `${recoveryPath}.claimed.${process.pid}.${Date.now()}`;
696
+ try {
697
+ renameSync(recoveryPath, claimedPath);
698
+ } catch {
699
+ continue;
700
+ }
701
+ try {
702
+ const claimed = readFileSync(claimedPath);
703
+ if (!claimed.equals(payload)) {
704
+ // Put the unexpected claim back so an operator / later process can inspect it.
705
+ try { renameSync(claimedPath, recoveryPath); } catch { /* keep claimed path for manual recovery */ }
706
+ throw new Error(
707
+ `Kiro CLI session recovery data changed while claiming ${recoveryPath}. Remove leftover claim files under the kiro-cli data directory and retry.`,
708
+ );
709
+ }
710
+ restoreKiroCliSession({
711
+ path,
712
+ database: recovery.database,
713
+ recoveryPath: claimedPath,
714
+ });
715
+ rmSync(claimedPath, { force: true });
716
+ return true;
717
+ } catch (error) {
718
+ // Preserve the only copy of the prior database when restore fails (disk full, permissions).
719
+ if (existsSync(claimedPath) && !existsSync(recoveryPath)) {
720
+ try { renameSync(claimedPath, recoveryPath); } catch { /* claimed file remains for manual recovery */ }
721
+ }
722
+ throw error;
723
+ }
724
+ }
725
+ return false;
726
+ }