@yansigit/opencodex 2.31.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 (826) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/LICENSE +21 -0
  3. package/README.md +303 -0
  4. package/assets/architecture.png +0 -0
  5. package/assets/banner.png +0 -0
  6. package/assets/claude-code-models.gif +0 -0
  7. package/assets/codex-app-picker.png +0 -0
  8. package/bin/ocx.mjs +587 -0
  9. package/bin/package-main.mjs +9 -0
  10. package/gui/dist/assets/index-BNJ7r4Gd.js +102 -0
  11. package/gui/dist/assets/index-CGoDO3uO.css +1 -0
  12. package/gui/dist/favicon.png +0 -0
  13. package/gui/dist/icons.svg +24 -0
  14. package/gui/dist/index.html +25 -0
  15. package/gui/dist/logo.png +0 -0
  16. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  17. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  18. package/gui/dist/provider-icons/claude-color.svg +1 -0
  19. package/gui/dist/provider-icons/cline-color.svg +16 -0
  20. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  21. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  22. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  23. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  24. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  25. package/gui/dist/provider-icons/discord.svg +1 -0
  26. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  27. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  28. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  29. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  30. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  31. package/gui/dist/provider-icons/grok.svg +1 -0
  32. package/gui/dist/provider-icons/groq-color.svg +1 -0
  33. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  34. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  35. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  36. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  37. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  38. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  39. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  40. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  41. package/gui/dist/provider-icons/openai.svg +1 -0
  42. package/gui/dist/provider-icons/opencode.svg +2 -0
  43. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  44. package/gui/dist/provider-icons/pi.svg +21 -0
  45. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  46. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  47. package/gui/dist/provider-icons/telegram.svg +1 -0
  48. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  49. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  50. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  51. package/package.json +108 -0
  52. package/src/AGENTS.md +28 -0
  53. package/src/adapters/anthropic-image-guard.ts +251 -0
  54. package/src/adapters/anthropic-image-normalize.ts +518 -0
  55. package/src/adapters/anthropic-output-schema.ts +137 -0
  56. package/src/adapters/anthropic.ts +1327 -0
  57. package/src/adapters/azure.ts +36 -0
  58. package/src/adapters/base.ts +121 -0
  59. package/src/adapters/client-fingerprint.ts +65 -0
  60. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  61. package/src/adapters/command-code.ts +601 -0
  62. package/src/adapters/cursor/arg-codec.ts +38 -0
  63. package/src/adapters/cursor/arg-normalize.ts +104 -0
  64. package/src/adapters/cursor/checkpoint-store.ts +303 -0
  65. package/src/adapters/cursor/cursor-errors.ts +288 -0
  66. package/src/adapters/cursor/discovery.ts +333 -0
  67. package/src/adapters/cursor/effort-map.ts +151 -0
  68. package/src/adapters/cursor/exec-policy.ts +88 -0
  69. package/src/adapters/cursor/framing.ts +250 -0
  70. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  71. package/src/adapters/cursor/h2-pool.ts +123 -0
  72. package/src/adapters/cursor/http1-bidi.ts +361 -0
  73. package/src/adapters/cursor/images.ts +704 -0
  74. package/src/adapters/cursor/kv-store.ts +52 -0
  75. package/src/adapters/cursor/live-models.ts +269 -0
  76. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  77. package/src/adapters/cursor/live-transport.ts +1653 -0
  78. package/src/adapters/cursor/mcp-config.ts +42 -0
  79. package/src/adapters/cursor/mcp-manager.ts +333 -0
  80. package/src/adapters/cursor/message-mapper.ts +49 -0
  81. package/src/adapters/cursor/native-exec-common.ts +76 -0
  82. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  83. package/src/adapters/cursor/native-exec-fs.ts +332 -0
  84. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  85. package/src/adapters/cursor/native-exec-network.ts +43 -0
  86. package/src/adapters/cursor/native-exec-shell.ts +547 -0
  87. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  88. package/src/adapters/cursor/native-exec.ts +663 -0
  89. package/src/adapters/cursor/protobuf-events.ts +1381 -0
  90. package/src/adapters/cursor/protobuf-request.ts +1032 -0
  91. package/src/adapters/cursor/request-builder.ts +461 -0
  92. package/src/adapters/cursor/thread-continuity.ts +67 -0
  93. package/src/adapters/cursor/tool-definitions.ts +735 -0
  94. package/src/adapters/cursor/tool-result-normalize.ts +92 -0
  95. package/src/adapters/cursor/transport-retry.ts +132 -0
  96. package/src/adapters/cursor/transport.ts +79 -0
  97. package/src/adapters/cursor/types.ts +79 -0
  98. package/src/adapters/cursor.ts +322 -0
  99. package/src/adapters/google-antigravity-hosts.ts +48 -0
  100. package/src/adapters/google-antigravity-replay.ts +827 -0
  101. package/src/adapters/google-antigravity-tools.ts +105 -0
  102. package/src/adapters/google-antigravity-wire.ts +141 -0
  103. package/src/adapters/google-errors.ts +92 -0
  104. package/src/adapters/google-http.ts +460 -0
  105. package/src/adapters/google-tool-schema.ts +238 -0
  106. package/src/adapters/google-truncation.ts +24 -0
  107. package/src/adapters/google-wire-compiler.ts +232 -0
  108. package/src/adapters/google.ts +1377 -0
  109. package/src/adapters/identity.ts +77 -0
  110. package/src/adapters/image.ts +23 -0
  111. package/src/adapters/kiro-constants.ts +16 -0
  112. package/src/adapters/kiro-errors.ts +208 -0
  113. package/src/adapters/kiro-events.ts +197 -0
  114. package/src/adapters/kiro-images.ts +129 -0
  115. package/src/adapters/kiro-retry.ts +312 -0
  116. package/src/adapters/kiro-thinking.ts +112 -0
  117. package/src/adapters/kiro-tool-fallback.ts +36 -0
  118. package/src/adapters/kiro-tools.ts +224 -0
  119. package/src/adapters/kiro-truncation.ts +33 -0
  120. package/src/adapters/kiro-wire.ts +129 -0
  121. package/src/adapters/kiro.ts +1936 -0
  122. package/src/adapters/mimo-free.ts +280 -0
  123. package/src/adapters/openai-chat-url.ts +11 -0
  124. package/src/adapters/openai-chat.ts +1980 -0
  125. package/src/adapters/openai-responses-url.ts +16 -0
  126. package/src/adapters/openai-responses.ts +1911 -0
  127. package/src/adapters/registry.ts +175 -0
  128. package/src/adapters/responses-tool-schema.ts +67 -0
  129. package/src/adapters/run-turn-queue.ts +114 -0
  130. package/src/adapters/tool-call-id.ts +119 -0
  131. package/src/adapters/tool-catalog-nudge.ts +154 -0
  132. package/src/adapters/upstream-http-error.ts +48 -0
  133. package/src/adapters/xai-web-search.ts +185 -0
  134. package/src/bridge.ts +1986 -0
  135. package/src/chat/inbound.ts +319 -0
  136. package/src/chat/outbound.ts +821 -0
  137. package/src/claude/agents-inject.ts +266 -0
  138. package/src/claude/alias.ts +149 -0
  139. package/src/claude/auth-detect.ts +229 -0
  140. package/src/claude/auth-mode-migration.ts +32 -0
  141. package/src/claude/auth-mode.ts +62 -0
  142. package/src/claude/context-windows.ts +205 -0
  143. package/src/claude/desktop-3p-guard.ts +35 -0
  144. package/src/claude/desktop-3p-paths.ts +84 -0
  145. package/src/claude/desktop-3p.ts +615 -0
  146. package/src/claude/desktop-health.ts +26 -0
  147. package/src/claude/desktop-profile.ts +263 -0
  148. package/src/claude/gateway-cache.ts +107 -0
  149. package/src/claude/inbound-debug.ts +163 -0
  150. package/src/claude/inbound.ts +578 -0
  151. package/src/claude/model-info.ts +174 -0
  152. package/src/claude/outbound.ts +926 -0
  153. package/src/cli/access.ts +108 -0
  154. package/src/cli/account-api.ts +302 -0
  155. package/src/cli/account-auth.ts +250 -0
  156. package/src/cli/account-catalog-refresh.ts +14 -0
  157. package/src/cli/account-extended.ts +737 -0
  158. package/src/cli/account-main.ts +317 -0
  159. package/src/cli/account.ts +299 -0
  160. package/src/cli/agent-driven.ts +70 -0
  161. package/src/cli/agent.ts +290 -0
  162. package/src/cli/catalog-prewarm.ts +27 -0
  163. package/src/cli/claude-agent-startup-sync.ts +73 -0
  164. package/src/cli/claude-desktop.ts +213 -0
  165. package/src/cli/claude.ts +355 -0
  166. package/src/cli/codex-log-guard-doctor.ts +103 -0
  167. package/src/cli/codex-shim-autorestore.ts +47 -0
  168. package/src/cli/codex-shim-readiness.ts +76 -0
  169. package/src/cli/combo.ts +127 -0
  170. package/src/cli/config-command.ts +209 -0
  171. package/src/cli/debug.ts +228 -0
  172. package/src/cli/dispatch.ts +585 -0
  173. package/src/cli/doctor.ts +1202 -0
  174. package/src/cli/ensure-desired-integrations.ts +152 -0
  175. package/src/cli/export-command.ts +213 -0
  176. package/src/cli/help.ts +101 -0
  177. package/src/cli/index.ts +973 -0
  178. package/src/cli/init.ts +211 -0
  179. package/src/cli/integrations.ts +260 -0
  180. package/src/cli/interactive-confirm.ts +133 -0
  181. package/src/cli/lab.ts +607 -0
  182. package/src/cli/launcher-context.ts +77 -0
  183. package/src/cli/minimax.ts +497 -0
  184. package/src/cli/models-runtime.ts +245 -0
  185. package/src/cli/models.ts +422 -0
  186. package/src/cli/observe.ts +206 -0
  187. package/src/cli/opencode.ts +588 -0
  188. package/src/cli/provider-replit.ts +232 -0
  189. package/src/cli/provider-runtime.ts +179 -0
  190. package/src/cli/provider.ts +492 -0
  191. package/src/cli/ready.ts +301 -0
  192. package/src/cli/registry.ts +422 -0
  193. package/src/cli/replit-gateway-key-input.ts +138 -0
  194. package/src/cli/root.ts +86 -0
  195. package/src/cli/route-policy.ts +92 -0
  196. package/src/cli/runtime-api.ts +328 -0
  197. package/src/cli/star-prompt.ts +211 -0
  198. package/src/cli/status-oauth.ts +78 -0
  199. package/src/cli/status.ts +328 -0
  200. package/src/cli/system-command.ts +112 -0
  201. package/src/cli/system-restart-client.ts +146 -0
  202. package/src/cli/tray-proxy.ts +199 -0
  203. package/src/cli/v2.ts +268 -0
  204. package/src/cli.ts +10 -0
  205. package/src/clients/config-export.ts +1704 -0
  206. package/src/codex/account-id.ts +34 -0
  207. package/src/codex/account-label.ts +47 -0
  208. package/src/codex/account-lifecycle.ts +172 -0
  209. package/src/codex/account-namespace-match.ts +63 -0
  210. package/src/codex/account-namespaces.ts +195 -0
  211. package/src/codex/account-pause.ts +20 -0
  212. package/src/codex/account-priority.ts +83 -0
  213. package/src/codex/account-runtime-state.ts +31 -0
  214. package/src/codex/account-store.ts +544 -0
  215. package/src/codex/account-usability.ts +43 -0
  216. package/src/codex/admission.ts +256 -0
  217. package/src/codex/affinity-debug.ts +162 -0
  218. package/src/codex/agent-roles-sync.ts +225 -0
  219. package/src/codex/agent-roles.ts +238 -0
  220. package/src/codex/app-server-processes.ts +1143 -0
  221. package/src/codex/app-server-restart-service.ts +232 -0
  222. package/src/codex/auth-api.ts +2147 -0
  223. package/src/codex/auth-collision.ts +109 -0
  224. package/src/codex/auth-context.ts +665 -0
  225. package/src/codex/autostart-health.ts +156 -0
  226. package/src/codex/catalog/account-models.ts +67 -0
  227. package/src/codex/catalog/aggregation.ts +436 -0
  228. package/src/codex/catalog/bundled.ts +549 -0
  229. package/src/codex/catalog/effort.ts +446 -0
  230. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  231. package/src/codex/catalog/kinds.ts +2 -0
  232. package/src/codex/catalog/metadata.ts +664 -0
  233. package/src/codex/catalog/native-models.ts +72 -0
  234. package/src/codex/catalog/parsing.ts +650 -0
  235. package/src/codex/catalog/provider-fetch.ts +2064 -0
  236. package/src/codex/catalog/sync.ts +1883 -0
  237. package/src/codex/catalog-admission.ts +199 -0
  238. package/src/codex/catalog-refresh-status.ts +105 -0
  239. package/src/codex/catalog-write-serialization.ts +242 -0
  240. package/src/codex/catalog.ts +14 -0
  241. package/src/codex/codex-write-lock.ts +384 -0
  242. package/src/codex/convergence-types.ts +614 -0
  243. package/src/codex/convergence.ts +651 -0
  244. package/src/codex/coordinator-doctor.ts +332 -0
  245. package/src/codex/custom-model-catalog-migration.ts +176 -0
  246. package/src/codex/data/upstream-models.json +830 -0
  247. package/src/codex/desired-state.ts +230 -0
  248. package/src/codex/exec-invocation.ts +22 -0
  249. package/src/codex/features.ts +1566 -0
  250. package/src/codex/generation.ts +202 -0
  251. package/src/codex/history-job.ts +407 -0
  252. package/src/codex/history-lock.ts +242 -0
  253. package/src/codex/history-migration-guardian.ts +108 -0
  254. package/src/codex/history-provider.ts +979 -0
  255. package/src/codex/history-transition.ts +105 -0
  256. package/src/codex/history-worker.ts +220 -0
  257. package/src/codex/home.ts +206 -0
  258. package/src/codex/inject-coordination.ts +290 -0
  259. package/src/codex/inject.ts +1733 -0
  260. package/src/codex/injected-marker.ts +106 -0
  261. package/src/codex/integration-record.ts +266 -0
  262. package/src/codex/internal/catalog-writer.ts +203 -0
  263. package/src/codex/internal/history-writer.ts +80 -0
  264. package/src/codex/journal.ts +225 -0
  265. package/src/codex/log-guard/inspect.ts +506 -0
  266. package/src/codex/log-guard/lock.ts +150 -0
  267. package/src/codex/log-guard/maintenance.ts +403 -0
  268. package/src/codex/log-guard/path-safety.ts +88 -0
  269. package/src/codex/log-guard/policy.ts +44 -0
  270. package/src/codex/log-guard/processes.ts +205 -0
  271. package/src/codex/log-guard/protection.ts +489 -0
  272. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  273. package/src/codex/main-account-cache.ts +56 -0
  274. package/src/codex/main-account.ts +68 -0
  275. package/src/codex/management-convergence.ts +167 -0
  276. package/src/codex/model-cache.ts +273 -0
  277. package/src/codex/model-entitlements.ts +353 -0
  278. package/src/codex/native-main-admission.ts +47 -0
  279. package/src/codex/native-main-auth-temp.ts +187 -0
  280. package/src/codex/native-main-claim.ts +178 -0
  281. package/src/codex/native-main-lock-file.ts +162 -0
  282. package/src/codex/native-main-owner.ts +329 -0
  283. package/src/codex/native-profile-api.ts +247 -0
  284. package/src/codex/native-profile-manager.ts +1531 -0
  285. package/src/codex/native-profile-processes.ts +121 -0
  286. package/src/codex/native-profile-recovery.ts +99 -0
  287. package/src/codex/native-profile-stage-store.ts +387 -0
  288. package/src/codex/native-profile-startup.ts +492 -0
  289. package/src/codex/native-profile-store.ts +855 -0
  290. package/src/codex/native-profile-types.ts +120 -0
  291. package/src/codex/native-residue.ts +682 -0
  292. package/src/codex/paths.ts +144 -0
  293. package/src/codex/plan-from-token.ts +140 -0
  294. package/src/codex/plan.ts +40 -0
  295. package/src/codex/plugins-doctor.ts +242 -0
  296. package/src/codex/pool-rotation.ts +295 -0
  297. package/src/codex/project-config-warnings.ts +425 -0
  298. package/src/codex/prompt-journal.ts +352 -0
  299. package/src/codex/prompt-layers.ts +967 -0
  300. package/src/codex/prompt-lock.ts +143 -0
  301. package/src/codex/quota-rejection.ts +298 -0
  302. package/src/codex/quota.ts +573 -0
  303. package/src/codex/refresh.ts +62 -0
  304. package/src/codex/reset-credit-recovery.ts +1044 -0
  305. package/src/codex/routing.ts +1888 -0
  306. package/src/codex/runtime.ts +659 -0
  307. package/src/codex/shim.ts +2170 -0
  308. package/src/codex/subagent-defaults.ts +550 -0
  309. package/src/codex/subagent-model-fallback.ts +784 -0
  310. package/src/codex/sync.ts +319 -0
  311. package/src/codex/transition-state.ts +612 -0
  312. package/src/codex/upstream-host-health.ts +368 -0
  313. package/src/codex/user-identity.ts +557 -0
  314. package/src/codex/warmup.ts +298 -0
  315. package/src/codex/websocket-registry.ts +100 -0
  316. package/src/codex/write-coordination.ts +114 -0
  317. package/src/combos/failover.ts +160 -0
  318. package/src/combos/index.ts +45 -0
  319. package/src/combos/request.ts +94 -0
  320. package/src/combos/resolve.ts +232 -0
  321. package/src/combos/types.ts +398 -0
  322. package/src/config/provider-name.ts +24 -0
  323. package/src/config.ts +4041 -0
  324. package/src/fork/register.ts +3 -0
  325. package/src/generated/compatibility-version.json +3116 -0
  326. package/src/generated/model-metadata.ts +106 -0
  327. package/src/github/star-state.ts +203 -0
  328. package/src/grok/inject.ts +530 -0
  329. package/src/grok/inspect.ts +45 -0
  330. package/src/grok/status.ts +121 -0
  331. package/src/grok/sync.ts +66 -0
  332. package/src/images/artifacts.ts +516 -0
  333. package/src/images/fulfill-video.ts +163 -0
  334. package/src/images/fulfill.ts +149 -0
  335. package/src/images/index.ts +4 -0
  336. package/src/images/loop.ts +955 -0
  337. package/src/images/plan.ts +143 -0
  338. package/src/images/synthetic-tool.ts +133 -0
  339. package/src/images/types.ts +41 -0
  340. package/src/images/xai-client.ts +141 -0
  341. package/src/images/xai-video-client.ts +163 -0
  342. package/src/index.ts +22 -0
  343. package/src/integrations/config-io.ts +269 -0
  344. package/src/integrations/journal.ts +315 -0
  345. package/src/integrations/merge.ts +135 -0
  346. package/src/integrations/mutation-flight.ts +71 -0
  347. package/src/integrations/native/ownership-preflight.ts +202 -0
  348. package/src/integrations/omp-yaml-source.ts +358 -0
  349. package/src/integrations/owned-refresh.ts +74 -0
  350. package/src/integrations/ownership.ts +111 -0
  351. package/src/integrations/registry.ts +159 -0
  352. package/src/integrations/serialize.ts +314 -0
  353. package/src/integrations/state.ts +361 -0
  354. package/src/integrations/store.ts +103 -0
  355. package/src/integrations/writer-lock.ts +98 -0
  356. package/src/integrations/writer.ts +691 -0
  357. package/src/lab/artifacts/sanitize.ts +586 -0
  358. package/src/lab/artifacts/secure-fs.ts +475 -0
  359. package/src/lab/artifacts/store.ts +310 -0
  360. package/src/lab/automation/budgets.ts +78 -0
  361. package/src/lab/automation/config-persistence.ts +256 -0
  362. package/src/lab/automation/constants.ts +39 -0
  363. package/src/lab/automation/cooldown.ts +103 -0
  364. package/src/lab/automation/dispatch.ts +211 -0
  365. package/src/lab/automation/index.ts +13 -0
  366. package/src/lab/automation/orchestrator.ts +499 -0
  367. package/src/lab/automation/persistence.ts +512 -0
  368. package/src/lab/automation/planner.ts +371 -0
  369. package/src/lab/automation/policy.ts +136 -0
  370. package/src/lab/automation/queue.ts +191 -0
  371. package/src/lab/automation/recovery.ts +24 -0
  372. package/src/lab/automation/route-context.ts +21 -0
  373. package/src/lab/automation/run-key.ts +44 -0
  374. package/src/lab/automation/runs-query.ts +34 -0
  375. package/src/lab/automation/types.ts +160 -0
  376. package/src/lab/conformance/assertion.ts +325 -0
  377. package/src/lab/conformance/digest.ts +22 -0
  378. package/src/lab/conformance/executor.ts +741 -0
  379. package/src/lab/conformance/fixture-provider.ts +27 -0
  380. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  381. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  382. package/src/lab/conformance/harness-budget.ts +47 -0
  383. package/src/lab/conformance/index.ts +5 -0
  384. package/src/lab/conformance/jcs.ts +64 -0
  385. package/src/lab/conformance/json-pointer.ts +39 -0
  386. package/src/lab/conformance/manifest.ts +180 -0
  387. package/src/lab/conformance/mcp-stub.ts +179 -0
  388. package/src/lab/conformance/negative-controls.ts +164 -0
  389. package/src/lab/conformance/observation.ts +355 -0
  390. package/src/lab/conformance/runner.ts +68 -0
  391. package/src/lab/conformance/sse-normalize.ts +59 -0
  392. package/src/lab/conformance/suite-manifest.ts +78 -0
  393. package/src/lab/conformance/types.ts +214 -0
  394. package/src/lab/constants.ts +126 -0
  395. package/src/lab/digest.ts +64 -0
  396. package/src/lab/events/errors.ts +9 -0
  397. package/src/lab/events/limits.ts +117 -0
  398. package/src/lab/events/types.ts +229 -0
  399. package/src/lab/events/validate.ts +781 -0
  400. package/src/lab/fabric/constants.ts +40 -0
  401. package/src/lab/fabric/executor.ts +492 -0
  402. package/src/lab/fabric/index.ts +80 -0
  403. package/src/lab/fabric/manifest.ts +222 -0
  404. package/src/lab/fabric/observe.ts +489 -0
  405. package/src/lab/fabric/patch.ts +79 -0
  406. package/src/lab/fabric/producer-child.ts +139 -0
  407. package/src/lab/fabric/producer-isolate.ts +276 -0
  408. package/src/lab/fabric/producer-protocol.ts +61 -0
  409. package/src/lab/fabric/scratch.ts +439 -0
  410. package/src/lab/fabric/subject.ts +106 -0
  411. package/src/lab/fabric/types.ts +134 -0
  412. package/src/lab/fabric/verifier.ts +98 -0
  413. package/src/lab/index.ts +54 -0
  414. package/src/lab/ledger/artifact-refs.ts +127 -0
  415. package/src/lab/ledger/invalidation.ts +136 -0
  416. package/src/lab/ledger/purge.ts +310 -0
  417. package/src/lab/ledger/store.ts +532 -0
  418. package/src/lab/live/credential-lease.ts +53 -0
  419. package/src/lab/live/destination.ts +155 -0
  420. package/src/lab/live/executor.ts +336 -0
  421. package/src/lab/live/inert-tools.ts +56 -0
  422. package/src/lab/live/manifest.ts +85 -0
  423. package/src/lab/live/mcp-loopback.ts +57 -0
  424. package/src/lab/live/runner.ts +19 -0
  425. package/src/lab/live/sandbox.ts +61 -0
  426. package/src/lab/live/suite-manifest.ts +41 -0
  427. package/src/lab/live/transport.ts +118 -0
  428. package/src/lab/live/types.ts +197 -0
  429. package/src/lab/observe/from-conformance.ts +301 -0
  430. package/src/lab/observe/from-live.ts +117 -0
  431. package/src/lab/paths.ts +153 -0
  432. package/src/lab/projection/rebuild.ts +495 -0
  433. package/src/lab/projection/schema.ts +135 -0
  434. package/src/lab/projection/verdicts.ts +474 -0
  435. package/src/lab/projection/verification.ts +412 -0
  436. package/src/lab/public/bundle.ts +217 -0
  437. package/src/lab/public/community-authority.ts +175 -0
  438. package/src/lab/public/community-files.ts +29 -0
  439. package/src/lab/public/community.ts +479 -0
  440. package/src/lab/public/file-safety.ts +155 -0
  441. package/src/lab/public/ids.ts +26 -0
  442. package/src/lab/public/index.ts +16 -0
  443. package/src/lab/public/mutation-lock.ts +424 -0
  444. package/src/lab/public/operator.ts +353 -0
  445. package/src/lab/public/origin-purge.ts +79 -0
  446. package/src/lab/public/origin.ts +203 -0
  447. package/src/lab/public/privacy.ts +143 -0
  448. package/src/lab/public/private-file.ts +261 -0
  449. package/src/lab/public/project.ts +124 -0
  450. package/src/lab/public/purge-test-fault.ts +21 -0
  451. package/src/lab/public/purge.ts +223 -0
  452. package/src/lab/public/registry.ts +44 -0
  453. package/src/lab/public/revocation.ts +252 -0
  454. package/src/lab/public/signature.ts +243 -0
  455. package/src/lab/public/storage.ts +105 -0
  456. package/src/lab/public/strict-json.ts +206 -0
  457. package/src/lab/public/time.ts +26 -0
  458. package/src/lab/public/types.ts +172 -0
  459. package/src/lab/public/validate.ts +391 -0
  460. package/src/lab/query/catalog.ts +101 -0
  461. package/src/lab/query/connection.ts +107 -0
  462. package/src/lab/query/constants.ts +4 -0
  463. package/src/lab/query/cursor.ts +132 -0
  464. package/src/lab/query/dto-map.ts +277 -0
  465. package/src/lab/query/errors.ts +22 -0
  466. package/src/lab/query/freshness.ts +53 -0
  467. package/src/lab/query/index.ts +45 -0
  468. package/src/lab/query/latest-observation.ts +59 -0
  469. package/src/lab/query/passive-production.ts +159 -0
  470. package/src/lab/query/queries.ts +444 -0
  471. package/src/lab/query/types.ts +266 -0
  472. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  473. package/src/lab/subject/installation-salt.ts +112 -0
  474. package/src/lab/subject/protocol-subject.ts +80 -0
  475. package/src/lab/subject/route-subject.ts +74 -0
  476. package/src/lib/abort.ts +146 -0
  477. package/src/lib/admin-secrets.ts +25 -0
  478. package/src/lib/admission.ts +83 -0
  479. package/src/lib/app-owned-memory-stores.ts +195 -0
  480. package/src/lib/app-owned-memory.ts +265 -0
  481. package/src/lib/bounded-body.ts +346 -0
  482. package/src/lib/bun-binary-validator.d.mts +3 -0
  483. package/src/lib/bun-binary-validator.mjs +18 -0
  484. package/src/lib/bun-runtime.ts +184 -0
  485. package/src/lib/bun-stream-caps.ts +130 -0
  486. package/src/lib/codex-restart-contract.ts +120 -0
  487. package/src/lib/config-ownership.ts +364 -0
  488. package/src/lib/crash-guard.ts +344 -0
  489. package/src/lib/debug-log-buffer.ts +83 -0
  490. package/src/lib/debug-settings.ts +108 -0
  491. package/src/lib/debug.ts +31 -0
  492. package/src/lib/destination-policy.ts +380 -0
  493. package/src/lib/errors.ts +406 -0
  494. package/src/lib/eventstream-decoder.ts +253 -0
  495. package/src/lib/fabric-task-execution-authority.ts +7 -0
  496. package/src/lib/fabric-task-host.ts +29 -0
  497. package/src/lib/gcp-adc.ts +341 -0
  498. package/src/lib/injection-debug-log.ts +58 -0
  499. package/src/lib/lab-activation.ts +223 -0
  500. package/src/lib/lab-live-execution-authority.ts +13 -0
  501. package/src/lib/lab-live-host.ts +30 -0
  502. package/src/lib/lab-live-pinned-sender.ts +56 -0
  503. package/src/lib/lab-live-route-production.ts +130 -0
  504. package/src/lib/lab-passive-linker-registration.ts +26 -0
  505. package/src/lib/local-management-attestation.ts +51 -0
  506. package/src/lib/local-management-capability.ts +100 -0
  507. package/src/lib/local-provider-reload-contract.ts +100 -0
  508. package/src/lib/open-url.ts +25 -0
  509. package/src/lib/optional-shutdown-hooks.ts +57 -0
  510. package/src/lib/pinned-http.ts +270 -0
  511. package/src/lib/privacy.ts +20 -0
  512. package/src/lib/process-control.ts +168 -0
  513. package/src/lib/provider-outbound.ts +210 -0
  514. package/src/lib/provider-url.ts +14 -0
  515. package/src/lib/proxy-env.ts +18 -0
  516. package/src/lib/redact.ts +521 -0
  517. package/src/lib/retry-after.ts +55 -0
  518. package/src/lib/self-launch-argv.ts +15 -0
  519. package/src/lib/server-resource-ownership.ts +71 -0
  520. package/src/lib/service-secrets.ts +25 -0
  521. package/src/lib/shadow-call.ts +61 -0
  522. package/src/lib/sidecar-tracker.ts +52 -0
  523. package/src/lib/sse-decoder.ts +364 -0
  524. package/src/lib/state-store-registrations.ts +119 -0
  525. package/src/lib/state-store-sweeper.ts +184 -0
  526. package/src/lib/system-restart-contract.ts +73 -0
  527. package/src/lib/test-home-guard.ts +90 -0
  528. package/src/lib/token-estimate.ts +86 -0
  529. package/src/lib/tool-argument-integers.ts +202 -0
  530. package/src/lib/translator-budget.ts +400 -0
  531. package/src/lib/upstream-http-version.ts +57 -0
  532. package/src/lib/upstream-reachability.ts +95 -0
  533. package/src/lib/upstream-retry.ts +392 -0
  534. package/src/lib/win-exec.ts +115 -0
  535. package/src/lib/win-paths.ts +68 -0
  536. package/src/lib/windows-atomic-replace.ts +156 -0
  537. package/src/lib/windows-elevation.ts +773 -0
  538. package/src/lib/windows-secret-acl.ts +854 -0
  539. package/src/lib/windows-service-wrappers.ts +72 -0
  540. package/src/lib/windows-text.ts +106 -0
  541. package/src/lib/windows-user-principal.ts +341 -0
  542. package/src/lib/winsw.ts +403 -0
  543. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  544. package/src/oauth/account-import/index.ts +15 -0
  545. package/src/oauth/account-import/parser.ts +83 -0
  546. package/src/oauth/account-import/registry.ts +18 -0
  547. package/src/oauth/account-import/service.ts +75 -0
  548. package/src/oauth/account-import/types.ts +91 -0
  549. package/src/oauth/anthropic-routing.ts +594 -0
  550. package/src/oauth/anthropic.ts +188 -0
  551. package/src/oauth/antigravity-routing.ts +151 -0
  552. package/src/oauth/callback-server.ts +300 -0
  553. package/src/oauth/chatgpt.ts +161 -0
  554. package/src/oauth/command-code.ts +239 -0
  555. package/src/oauth/cursor.ts +252 -0
  556. package/src/oauth/github-copilot.ts +428 -0
  557. package/src/oauth/google-antigravity.ts +262 -0
  558. package/src/oauth/health.ts +407 -0
  559. package/src/oauth/index.ts +1504 -0
  560. package/src/oauth/key-providers.ts +124 -0
  561. package/src/oauth/kimi.ts +227 -0
  562. package/src/oauth/kiro-credentials.ts +726 -0
  563. package/src/oauth/kiro.ts +621 -0
  564. package/src/oauth/local-token-detect.ts +130 -0
  565. package/src/oauth/log.ts +50 -0
  566. package/src/oauth/login-cli.ts +223 -0
  567. package/src/oauth/nous.ts +798 -0
  568. package/src/oauth/pkce.ts +15 -0
  569. package/src/oauth/store.ts +728 -0
  570. package/src/oauth/token-guardian.ts +309 -0
  571. package/src/oauth/types.ts +62 -0
  572. package/src/oauth/xai.ts +241 -0
  573. package/src/providers/alibaba-region-backup.ts +75 -0
  574. package/src/providers/alibaba-region-migration.ts +156 -0
  575. package/src/providers/alibaba-region-startup.ts +36 -0
  576. package/src/providers/antigravity-models.ts +695 -0
  577. package/src/providers/antigravity-quota.ts +216 -0
  578. package/src/providers/api-keys.ts +140 -0
  579. package/src/providers/base-url-choices.ts +74 -0
  580. package/src/providers/codex-capacity.ts +292 -0
  581. package/src/providers/command-code-efforts.ts +144 -0
  582. package/src/providers/context-cap.ts +82 -0
  583. package/src/providers/cursor-pool.ts +72 -0
  584. package/src/providers/derive.ts +586 -0
  585. package/src/providers/fastwire.ts +501 -0
  586. package/src/providers/free-directory.ts +187 -0
  587. package/src/providers/github-copilot-transport.ts +56 -0
  588. package/src/providers/google-vertex-location.ts +14 -0
  589. package/src/providers/key-failover.ts +271 -0
  590. package/src/providers/kiro-models.ts +67 -0
  591. package/src/providers/label.ts +19 -0
  592. package/src/providers/model-discovery-limits.ts +16 -0
  593. package/src/providers/model-discovery.ts +449 -0
  594. package/src/providers/model-rename-migration.ts +255 -0
  595. package/src/providers/model-rename-startup.ts +28 -0
  596. package/src/providers/openai-sidecar.ts +243 -0
  597. package/src/providers/openai-tier-startup.ts +56 -0
  598. package/src/providers/openai-tiers.ts +423 -0
  599. package/src/providers/openai-virtual-models.ts +83 -0
  600. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  601. package/src/providers/openrouter-routing.ts +102 -0
  602. package/src/providers/provider-id-rewrite.ts +185 -0
  603. package/src/providers/quota.ts +2345 -0
  604. package/src/providers/registry.ts +2918 -0
  605. package/src/providers/replit/constants.ts +27 -0
  606. package/src/providers/replit/derive.ts +85 -0
  607. package/src/providers/replit/headers.ts +28 -0
  608. package/src/providers/replit/origin.ts +55 -0
  609. package/src/providers/replit/pair-install-response.ts +72 -0
  610. package/src/providers/replit/probe.ts +199 -0
  611. package/src/providers/replit/setup.ts +350 -0
  612. package/src/providers/request-pacing.ts +310 -0
  613. package/src/providers/service-tier.ts +277 -0
  614. package/src/providers/slug-codec.ts +103 -0
  615. package/src/providers/static-model-discovery.ts +86 -0
  616. package/src/providers/xai-responses-opt-in.ts +15 -0
  617. package/src/providers/xai-transport.ts +148 -0
  618. package/src/reasoning-effort.ts +183 -0
  619. package/src/responses/compaction.ts +142 -0
  620. package/src/responses/custom-tool-compat.ts +266 -0
  621. package/src/responses/hosted-tool-policy.ts +9 -0
  622. package/src/responses/namespace-tool-compat.ts +355 -0
  623. package/src/responses/parser.ts +838 -0
  624. package/src/responses/provider-continuation.ts +98 -0
  625. package/src/responses/provider-opaque-metadata.ts +73 -0
  626. package/src/responses/reasoning-envelope.ts +60 -0
  627. package/src/responses/reasoning-replay-cache.ts +426 -0
  628. package/src/responses/schema.ts +165 -0
  629. package/src/responses/spill-store.ts +459 -0
  630. package/src/responses/state.ts +1433 -0
  631. package/src/responses/thought-signature-replay.ts +347 -0
  632. package/src/responses/tool-groups.ts +19 -0
  633. package/src/responses/tool-search-compat.ts +301 -0
  634. package/src/responses/truncated-stop-reason.ts +60 -0
  635. package/src/router.ts +761 -0
  636. package/src/routing/analytics.ts +378 -0
  637. package/src/routing/capability.ts +244 -0
  638. package/src/routing/compatibility/assemble.ts +73 -0
  639. package/src/routing/compatibility/behavior.ts +278 -0
  640. package/src/routing/compatibility/catalog.ts +99 -0
  641. package/src/routing/compatibility/endpoint.ts +52 -0
  642. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  643. package/src/routing/compatibility/policy.ts +181 -0
  644. package/src/routing/compatibility/provider-slot.ts +56 -0
  645. package/src/routing/compatibility/reader.ts +110 -0
  646. package/src/routing/compatibility/subject.ts +191 -0
  647. package/src/routing/compatibility/types.ts +64 -0
  648. package/src/routing/compatibility/version.ts +104 -0
  649. package/src/routing/cost.ts +77 -0
  650. package/src/routing/evaluator.ts +495 -0
  651. package/src/routing/health.ts +412 -0
  652. package/src/routing/history/cursor.ts +43 -0
  653. package/src/routing/history/indexer.ts +605 -0
  654. package/src/routing/history/schema.ts +72 -0
  655. package/src/routing/profile-namespace.ts +15 -0
  656. package/src/routing/profile.ts +547 -0
  657. package/src/routing/quota.ts +145 -0
  658. package/src/routing/request-evidence.ts +45 -0
  659. package/src/routing/trace.ts +776 -0
  660. package/src/server/adapter-resolve.ts +53 -0
  661. package/src/server/auth-cors.ts +751 -0
  662. package/src/server/background-lifecycle.ts +182 -0
  663. package/src/server/chat-completions.ts +442 -0
  664. package/src/server/chat-native-sse.ts +331 -0
  665. package/src/server/chat-native.ts +426 -0
  666. package/src/server/claude-messages.ts +1030 -0
  667. package/src/server/direct-local-http.ts +347 -0
  668. package/src/server/effort-policy.ts +190 -0
  669. package/src/server/github-copilot-responses-repair.ts +338 -0
  670. package/src/server/gui-static.ts +152 -0
  671. package/src/server/image-retry.ts +42 -0
  672. package/src/server/images.ts +568 -0
  673. package/src/server/index.ts +1813 -0
  674. package/src/server/lifecycle.ts +498 -0
  675. package/src/server/live.ts +717 -0
  676. package/src/server/local-management-read-client.ts +90 -0
  677. package/src/server/local-provider-reload-client.ts +137 -0
  678. package/src/server/management/agent-settings-routes.ts +1433 -0
  679. package/src/server/management/api-access.ts +141 -0
  680. package/src/server/management/api-key-usage.ts +193 -0
  681. package/src/server/management/body.ts +41 -0
  682. package/src/server/management/combo-routes.ts +263 -0
  683. package/src/server/management/config-routes.ts +835 -0
  684. package/src/server/management/context.ts +113 -0
  685. package/src/server/management/integration-routes.ts +498 -0
  686. package/src/server/management/lab-automation-routes.ts +206 -0
  687. package/src/server/management/lab-routes.ts +563 -0
  688. package/src/server/management/logs-usage-routes.ts +586 -0
  689. package/src/server/management/model-routes.ts +560 -0
  690. package/src/server/management/model-rows.ts +163 -0
  691. package/src/server/management/native-integration-routes.ts +769 -0
  692. package/src/server/management/oauth-account-routes.ts +637 -0
  693. package/src/server/management/provider-capability-config.ts +48 -0
  694. package/src/server/management/provider-routes.ts +1033 -0
  695. package/src/server/management/replit-provider-routes.ts +86 -0
  696. package/src/server/management/request-history-routes.ts +191 -0
  697. package/src/server/management/routing-analytics-routes.ts +74 -0
  698. package/src/server/management/routing-profile-routes.ts +385 -0
  699. package/src/server/management/shared.ts +286 -0
  700. package/src/server/management/sidebar-routes.ts +106 -0
  701. package/src/server/management/storage-log-guard-routes.ts +186 -0
  702. package/src/server/management/sync-response.ts +69 -0
  703. package/src/server/management/system-restart.ts +435 -0
  704. package/src/server/management/system-routes.ts +194 -0
  705. package/src/server/management/usage-summary-cache.ts +94 -0
  706. package/src/server/management/vision-sidecar-options.ts +167 -0
  707. package/src/server/management/web-search-sidecar-options.ts +120 -0
  708. package/src/server/management-api.ts +314 -0
  709. package/src/server/management-auth.ts +482 -0
  710. package/src/server/memory-watchdog.ts +156 -0
  711. package/src/server/passive-route-linker.ts +66 -0
  712. package/src/server/port-reclaim.ts +307 -0
  713. package/src/server/ports.ts +156 -0
  714. package/src/server/proxy-liveness.ts +328 -0
  715. package/src/server/readiness.ts +99 -0
  716. package/src/server/relay-eager.ts +353 -0
  717. package/src/server/relay.ts +1209 -0
  718. package/src/server/request-decompress.ts +239 -0
  719. package/src/server/request-log-conversation.ts +168 -0
  720. package/src/server/request-log.ts +1259 -0
  721. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  722. package/src/server/responses/agent-task-recovery.ts +465 -0
  723. package/src/server/responses/collaboration.ts +551 -0
  724. package/src/server/responses/compact.ts +771 -0
  725. package/src/server/responses/core.ts +5389 -0
  726. package/src/server/responses/empty-completion-guard.ts +276 -0
  727. package/src/server/responses/encrypted-payload.ts +331 -0
  728. package/src/server/responses/fetch-helpers.ts +232 -0
  729. package/src/server/responses/input-admission.ts +185 -0
  730. package/src/server/responses/pacing-overload.ts +13 -0
  731. package/src/server/responses/passthrough-error.ts +78 -0
  732. package/src/server/responses/policy-fallback.ts +178 -0
  733. package/src/server/responses/responses-field-backfill.ts +251 -0
  734. package/src/server/responses/terminal-guard.ts +251 -0
  735. package/src/server/responses/upstream-error.ts +53 -0
  736. package/src/server/responses/ws-upstream.ts +308 -0
  737. package/src/server/responses-custom-tool-repair.ts +282 -0
  738. package/src/server/responses-image-gen-repair.ts +132 -0
  739. package/src/server/responses-item-id-repair.ts +272 -0
  740. package/src/server/responses-json-events.ts +90 -0
  741. package/src/server/responses-model-rewrite.ts +29 -0
  742. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  743. package/src/server/responses-snapshot-repair.ts +621 -0
  744. package/src/server/responses-terminal-repair.ts +342 -0
  745. package/src/server/responses-tool-search-repair.ts +267 -0
  746. package/src/server/responses-undeclared-tool-guard.ts +153 -0
  747. package/src/server/responses.ts +25 -0
  748. package/src/server/search.ts +201 -0
  749. package/src/server/sse-frame-buffer.ts +292 -0
  750. package/src/server/sse-payload-rewrite.ts +263 -0
  751. package/src/server/startup-action-control.ts +315 -0
  752. package/src/server/startup-health-cache.ts +131 -0
  753. package/src/server/system-env.ts +484 -0
  754. package/src/server/windows-tcp-drop.ts +184 -0
  755. package/src/server/windows-tray-control.ts +41 -0
  756. package/src/server/ws-bridge.ts +472 -0
  757. package/src/service-manager-probe.ts +892 -0
  758. package/src/service.ts +3575 -0
  759. package/src/sidecar/auth.ts +92 -0
  760. package/src/sidecar/candidates.ts +83 -0
  761. package/src/stall-timeout.ts +20 -0
  762. package/src/storage/cleanup-job.ts +57 -0
  763. package/src/storage/cleanup.ts +3085 -0
  764. package/src/storage/policy-job.ts +457 -0
  765. package/src/storage/policy-scheduler.ts +40 -0
  766. package/src/storage/policy-worker.ts +59 -0
  767. package/src/storage/policy.ts +527 -0
  768. package/src/storage/restore-job.ts +299 -0
  769. package/src/storage/restore-worker.ts +58 -0
  770. package/src/storage/scanner.ts +238 -0
  771. package/src/storage/storage-mutation-coordinator.ts +139 -0
  772. package/src/storage/worker-lifecycle.ts +215 -0
  773. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  774. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  775. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  776. package/src/tray/assets/opencodex-tray.png +0 -0
  777. package/src/tray/windows-tray.ps1 +364 -0
  778. package/src/tray/windows.ts +757 -0
  779. package/src/types/accounts.ts +37 -0
  780. package/src/types/config.ts +876 -0
  781. package/src/types/provider.ts +545 -0
  782. package/src/types/request.ts +384 -0
  783. package/src/types/tools.ts +131 -0
  784. package/src/types/wire.ts +80 -0
  785. package/src/types.ts +106 -0
  786. package/src/update/badge.ts +72 -0
  787. package/src/update/index.ts +415 -0
  788. package/src/update/job.ts +1887 -0
  789. package/src/update/notify.ts +263 -0
  790. package/src/update/npm-cache-preflight.d.mts +47 -0
  791. package/src/update/npm-cache-preflight.mjs +201 -0
  792. package/src/update/npm-invocation.d.mts +23 -0
  793. package/src/update/npm-invocation.mjs +94 -0
  794. package/src/update/transactional-install.d.mts +22 -0
  795. package/src/update/transactional-install.mjs +259 -0
  796. package/src/update/tray-update-plan.d.mts +18 -0
  797. package/src/update/tray-update-plan.mjs +38 -0
  798. package/src/usage/cost.ts +625 -0
  799. package/src/usage/debug.ts +97 -0
  800. package/src/usage/expected-prices.ts +416 -0
  801. package/src/usage/log.ts +1223 -0
  802. package/src/usage/summary.ts +753 -0
  803. package/src/usage/totals.ts +14 -0
  804. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  805. package/src/usage/user-cost-overlays.ts +314 -0
  806. package/src/vision/anthropic-describe.ts +189 -0
  807. package/src/vision/backends.ts +97 -0
  808. package/src/vision/describe.ts +131 -0
  809. package/src/vision/eligibility.ts +250 -0
  810. package/src/vision/index.ts +681 -0
  811. package/src/vision/reasoning.ts +55 -0
  812. package/src/vision/routed-describe.ts +175 -0
  813. package/src/vision/timeout-bounds.ts +9 -0
  814. package/src/web-search/anthropic-executor.ts +195 -0
  815. package/src/web-search/backends.ts +108 -0
  816. package/src/web-search/exa-executor.ts +88 -0
  817. package/src/web-search/executor.ts +113 -0
  818. package/src/web-search/format-result.ts +89 -0
  819. package/src/web-search/gemini-executor.ts +141 -0
  820. package/src/web-search/index.ts +331 -0
  821. package/src/web-search/loop.ts +896 -0
  822. package/src/web-search/parse.ts +315 -0
  823. package/src/web-search/progress-stream.ts +342 -0
  824. package/src/web-search/sources.ts +60 -0
  825. package/src/web-search/synthetic-tool.ts +47 -0
  826. package/src/web-search/xai-executor.ts +219 -0
@@ -0,0 +1,854 @@
1
+ /**
2
+ * Windows per-user NTFS ACL hardening for secret files and directories.
3
+ *
4
+ * On Windows, `chmod` only controls POSIX-style bits in the ACE list and does NOT remove
5
+ * inherited permissions from other users. Real per-user isolation requires icacls to:
6
+ * 1. Grant the current user full control (icacls path /grant:r "CURRENTUSER:(F)")
7
+ * 2. Disable inheritance (icacls path /inheritance:r)
8
+ * 3. Strip broad explicit grants by SID (Everyone, Users, Authenticated Users)
9
+ *
10
+ * Owner grant MUST precede `/inheritance:r`. That flag is destructive: it drops inherited
11
+ * ACEs immediately. If a later step times out after inheritance is removed but before an
12
+ * explicit owner ACE exists, the temp is left with a protected empty DACL — owned by the
13
+ * current user yet unreadable/ununlinkable until Full Control is restored (issue #596).
14
+ *
15
+ * On non-Windows platforms the helpers fall through to the caller's existing chmod-based
16
+ * behaviour: they return ok:true without invoking any external process.
17
+ *
18
+ * Design:
19
+ * hardenSecretPath(path, { required: false }) — non-fatal read-path mode.
20
+ * Never throws. Returns { ok, diagnostics? }.
21
+ * hardenSecretPath(path, { required: true }) — write-path mode.
22
+ * Throws a sanitized error (no raw path) on every Windows ACL failure,
23
+ * including genuine icacls timeouts. Required secret publication therefore
24
+ * fails closed; only required:false read-path probes may soft-fail.
25
+ * hardenSecretPathAsync / hardenSecretDirAsync — same policy, async icacls
26
+ * runner so the event loop is not held for the child lifetime (#612).
27
+ * HardenOptions.timeoutMemoKey — optional destination-path key for the
28
+ * timeout memo (atomic writers mint unique temps; never a parent directory).
29
+ * hardenSecretDir — same contract for directories.
30
+ */
31
+
32
+ import { existsSync, statSync } from "node:fs";
33
+ import { env, platform } from "node:process";
34
+ import { resolveTrustedWindowsIcaclsExe } from "./windows-elevation";
35
+ import {
36
+ resolveCurrentWindowsPrincipal,
37
+ resolveCurrentWindowsPrincipalAsync,
38
+ setSyntheticWindowsPrincipalForTests,
39
+ } from "./windows-user-principal";
40
+
41
+ const hardenedDirectories = new Map<string, HardenedIdentity>();
42
+ const hardenedPaths = new Map<string, HardenedIdentity>();
43
+ /**
44
+ * Paths whose harden TIMED OUT this process: do not re-stall every loadConfig on them.
45
+ * `false` means one explicitly authorized recovery attempt remains; `true` means
46
+ * that attempt was consumed. Ordinary callers never consume it.
47
+ */
48
+ const timedOutPaths = new Map<string, boolean>();
49
+
50
+ /**
51
+ * The memo value: `object:freshness` for a file a harden was actually attributed
52
+ * to.
53
+ *
54
+ * There is deliberately no null member. An observation that cannot be read is
55
+ * not stored at all — the entry is deleted — because a "recorded as unverifiable"
56
+ * value was dead code the moment attribution became a before/after comparison,
57
+ * and a branch nothing can reach is a branch no test can defend.
58
+ */
59
+ type HardenedIdentity = string;
60
+
61
+ /**
62
+ * What a stat can tell us about WHICH OBJECT is at a path.
63
+ *
64
+ * Two fields, deliberately separated, because conflating them shipped a bug:
65
+ *
66
+ * - `object` — `dev:ino`. Answers "is this the same file". Survives an ACL or
67
+ * permission change, which is exactly what we need across an icacls call.
68
+ * - `freshness` — `ctimeNs`. Answers "has this file's metadata moved since". It
69
+ * distinguishes an unlink/recreate that ext4 gave the same inode back for, and
70
+ * it MOVES when permissions change.
71
+ *
72
+ * The first version used `dev:ino:ctimeNs` for both jobs. Since chmod bumps ctime
73
+ * — probed, `{ctimeChangedByChmod: true}` — and icacls is a permission change, the
74
+ * before/after comparison would have rejected its own successful harden and failed
75
+ * closed on every first harden on Windows. Requiring the identity to be unchanged
76
+ * across an operation whose entire purpose is to change it is not a strict check;
77
+ * it is a broken one.
78
+ */
79
+ interface PathObservation {
80
+ readonly object: string;
81
+ readonly freshness: string;
82
+ }
83
+
84
+ /**
85
+ * Observe which object is at a path, and how fresh it is.
86
+ *
87
+ * `dev:ino` alone is not enough to detect a replacement, which a Linux CI run
88
+ * proved: ext4 reuses the inode of an unlinked file immediately — 100 of 100
89
+ * unlink/recreate cycles produced the SAME `ino`, while macOS reused none in 200
90
+ * and happily reported the earlier fix working. That is why `freshness` exists;
91
+ * `ctimeNs` differed in 100 of 100 of those same cycles.
92
+ *
93
+ * `bigint: true` is used because `ctimeNs` exists only in that variant.
94
+ *
95
+ * Test seam: `setStatForTests` replaces this reader so a test can vary `dev`,
96
+ * `ino`, and `ctimeNs` independently. Mirroring the implementation's string
97
+ * format in test setup proves nothing about which components production uses —
98
+ * an audit removed `dev` and all forty tests still passed.
99
+ *
100
+ * UNVERIFIED: the plain `ino` is reported to be 0 on NTFS while the bigint form
101
+ * carries the file index, and the zero-ino guard exists for that case. Neither
102
+ * Darwin nor Linux CI can confirm it and no pinned-Bun Windows probe has run.
103
+ * It is defensive code, not a demonstrated platform fact.
104
+ */
105
+ type StatReader = (path: string) => { dev: bigint; ino: bigint; ctimeNs: bigint };
106
+
107
+ const defaultStatReader: StatReader = path => {
108
+ const s = statSync(path, { bigint: true });
109
+ return { dev: s.dev, ino: s.ino, ctimeNs: s.ctimeNs };
110
+ };
111
+
112
+ let statReader: StatReader = defaultStatReader;
113
+
114
+ /** Test seam: drive dev / ino / ctime independently. */
115
+ export function setStatForTests(reader: StatReader | null): void {
116
+ statReader = reader ?? defaultStatReader;
117
+ }
118
+
119
+ function observe(targetPath: string): PathObservation | null {
120
+ try {
121
+ const s = statReader(targetPath);
122
+ if (s.ino === 0n) return null;
123
+ return { object: `${s.dev}:${s.ino}`, freshness: `${s.ctimeNs}` };
124
+ } catch {
125
+ return null;
126
+ }
127
+ }
128
+
129
+ function memoValue(seen: PathObservation): HardenedIdentity {
130
+ return `${seen.object}:${seen.freshness}`;
131
+ }
132
+
133
+ /**
134
+ * True only when this exact FILE was hardened, not merely this pathname.
135
+ *
136
+ * The memo used to be a `Set<string>` of paths. A stable destination — such as
137
+ * the coordinator database `hardenStableLockFile` hardens — can be unlinked and
138
+ * recreated at the same name, and the replacement inherited the previous file's
139
+ * hardening while never having been through icacls. Ephemeral temps escaped this
140
+ * only because atomic writers call `forgetEphemeralSecretPath` once the temp is
141
+ * gone; nothing does that for a stable path.
142
+ */
143
+ function memoSatisfied(cache: Map<string, HardenedIdentity>, targetPath: string): boolean {
144
+ const remembered = cache.get(targetPath);
145
+ if (remembered === undefined) return false;
146
+ const current = observe(targetPath);
147
+ // Unreadable now is not "unchanged": re-harden rather than trust a value we
148
+ // cannot confirm still describes what is there.
149
+ //
150
+ // A miss RETIRES the entry rather than leaving it. Keeping it left the cache in
151
+ // a state nothing could justify: after a mismatch and a failed re-harden, the
152
+ // stale value survived, so restoring the old identity would satisfy it again
153
+ // without any ACL work. That needs exact-identity ABA to bite — outside the
154
+ // proof bound this unit claims — but "the consequence is out of scope" is not a
155
+ // reason to keep an entry we have just proven does not describe what is there.
156
+ if (current === null || memoValue(current) !== remembered) {
157
+ cache.delete(targetPath);
158
+ return false;
159
+ }
160
+ return true;
161
+ }
162
+
163
+ /**
164
+ * Record a harden ONLY if the file we hardened is still the file at that path.
165
+ *
166
+ * Reading identity after the ACL sequence returns answers "what is there now",
167
+ * which is not the same question as "what did icacls operate on". A replacement
168
+ * landing mid-sequence — probed by swapping the file during the final
169
+ * `/remove:g` — made the memo remember the REPLACEMENT as hardened, so the next
170
+ * acquisition skipped ACL work on a file that had never seen it:
171
+ *
172
+ * {identityChangedDuringHarden: true, callsForOriginal: 3, totalCalls: 3,
173
+ * replacementWasHardened: false}
174
+ *
175
+ * So the OBJECT is captured before the sequence and compared after it. Only the
176
+ * object — `dev:ino` — because icacls changes permissions, and `ctimeNs` moves
177
+ * when permissions change (probed: `{ctimeChangedByChmod: true}`). Comparing the
178
+ * full identity across the call would have rejected every successful harden and
179
+ * failed closed on the first harden on Windows: the check would have been
180
+ * demanding that an operation not do the thing it exists to do.
181
+ *
182
+ * The memo then stores the object plus the freshness read AFTER hardening, which
183
+ * is the state a later lookup should match.
184
+ *
185
+ * A changed object, or an unreadable observation at either end, means we cannot
186
+ * say what was hardened: the memo is cleared rather than written, and required
187
+ * callers fail closed. An optional caller soft-fails, as it does for every other
188
+ * unproven ACL.
189
+ *
190
+ * Returns true when the harden may be reported successful.
191
+ */
192
+ function recordHarden(
193
+ cache: Map<string, HardenedIdentity>,
194
+ targetPath: string,
195
+ before: PathObservation | null,
196
+ ): boolean {
197
+ const after = observe(targetPath);
198
+ if (before === null || after === null || before.object !== after.object) {
199
+ // Never leave a memo behind for a file we cannot vouch for, including one
200
+ // written by an earlier successful harden of a now-replaced file.
201
+ cache.delete(targetPath);
202
+ return false;
203
+ }
204
+ cache.set(targetPath, memoValue(after));
205
+ return true;
206
+ }
207
+
208
+ const SUBSTITUTED_DIAGNOSTIC =
209
+ "ACL hardening could not be attributed — the file at this path changed during hardening";
210
+
211
+ export interface HardenResult {
212
+ ok: boolean;
213
+ diagnostics?: string;
214
+ }
215
+
216
+ export interface HardenOptions {
217
+ required: boolean;
218
+ /**
219
+ * Optional timeout-memo key distinct from `targetPath` (issue #612).
220
+ * Atomic writers mint a fresh `.tmp` path per write; keying the timeout cache by the
221
+ * final destination path prevents re-stalling the event loop on every subsequent temp.
222
+ * Must NOT be a parent directory — directory ACLs are not authoritative for new files.
223
+ */
224
+ timeoutMemoKey?: string;
225
+ /**
226
+ * Consume the one recovery attempt for a previously timed-out memo key.
227
+ * Only a caller that owns its own single-flight and bounded retry policy should
228
+ * set this. It never clears or bypasses an already-consumed timeout memo.
229
+ */
230
+ retryTimedOutOnce?: boolean;
231
+ }
232
+
233
+ /**
234
+ * Total icacls budget per harden call — ALL steps share it, including the single
235
+ * timeout retry and the diagnostic verification pass (no per-attempt fresh budget:
236
+ * loadConfig hardens dir+config+auth sequentially, so per-attempt budgets stack
237
+ * into multi-minute startup stalls). Override with OPENCODEX_ACL_TIMEOUT_MS
238
+ * (integer ms, clamped to [1000, 60000]; invalid values fall back to 30000).
239
+ *
240
+ * The default was 5s until #1156. One envelope has to cover the whole sequence —
241
+ * `/grant:r`, `/inheritance:r`, `/remove:g`, plus the conditional `/findsid`
242
+ * verification — and on machines where icacls is slow (Defender real-time scanning,
243
+ * roaming profiles, a domain-controller round trip) 5s ran out mid-sequence. The
244
+ * harden then failed closed, the native-main owner published a permanent
245
+ * `unavailable`, and every native request returned 503 until restart. A slow start
246
+ * is recoverable; that is not.
247
+ *
248
+ * The cost is honest and worth stating: because loadConfig hardens three paths
249
+ * sequentially, the timeout-path worst case at load is ~90s, and the owner path
250
+ * (initial call + one recovery) is ~60.25s. Both require icacls to be
251
+ * pathologically slow on every call; a healthy machine finishes in milliseconds
252
+ * and sees no change. Operators who prefer the old bound can set the env override.
253
+ */
254
+ const HARDEN_DEADLINE_DEFAULT_MS = 30_000;
255
+ const HARDEN_DEADLINE_MIN_MS = 1_000;
256
+ const HARDEN_DEADLINE_MAX_MS = 60_000;
257
+
258
+ /** Resolve the total harden budget once per call (env mutation cannot change it midway). */
259
+ function resolveHardenDeadlineMs(): number {
260
+ const raw = env["OPENCODEX_ACL_TIMEOUT_MS"]?.trim();
261
+ if (!raw) return HARDEN_DEADLINE_DEFAULT_MS;
262
+ const parsed = Number(raw);
263
+ if (!Number.isSafeInteger(parsed)) return HARDEN_DEADLINE_DEFAULT_MS;
264
+ return Math.min(HARDEN_DEADLINE_MAX_MS, Math.max(HARDEN_DEADLINE_MIN_MS, parsed));
265
+ }
266
+
267
+ export interface IcaclsResult {
268
+ success: boolean;
269
+ exitCode: number | null;
270
+ timedOut: boolean;
271
+ stdout: string;
272
+ }
273
+
274
+ type IcaclsRunner = (args: string[], timeoutMs: number) => IcaclsResult;
275
+ type AsyncIcaclsRunner = (args: string[], timeoutMs: number) => Promise<IcaclsResult>;
276
+
277
+ function resolveIcaclsExecutable(): string {
278
+ // Same authority as schtasks/powershell: never take icacls from PATH.
279
+ // A bun-shim or stripped PATH makes `icacls.exe` throw ENOENT, which used to
280
+ // surface as "filesystem may not support per-user NTFS ACLs".
281
+ return resolveTrustedWindowsIcaclsExe();
282
+ }
283
+
284
+ function spawnFailedResult(): IcaclsResult {
285
+ return { success: false, exitCode: null, timedOut: false, stdout: "" };
286
+ }
287
+
288
+ /**
289
+ * Spawn icacls asynchronously, or return null when the executable cannot be
290
+ * launched. The pipe/ignore stdio literals stay inferred here so `stdout` keeps
291
+ * its `ReadableStream` type instead of widening to the generic default.
292
+ */
293
+ function trySpawnIcacls(args: string[]) {
294
+ try {
295
+ return Bun.spawn([resolveIcaclsExecutable(), ...args], {
296
+ stdin: "ignore",
297
+ stdout: "pipe",
298
+ stderr: "ignore",
299
+ windowsHide: true,
300
+ });
301
+ } catch {
302
+ return null;
303
+ }
304
+ }
305
+
306
+ function defaultIcaclsRunner(args: string[], timeoutMs: number): IcaclsResult {
307
+ // Bun.spawnSync with windowsHide: Node execFileSync has hung under the GUI/proxy even
308
+ // with windowsHide, and console-subsystem tools flash a visible window otherwise.
309
+ try {
310
+ const result = Bun.spawnSync([resolveIcaclsExecutable(), ...args], {
311
+ stdin: "ignore",
312
+ stdout: "pipe",
313
+ stderr: "ignore",
314
+ timeout: timeoutMs,
315
+ windowsHide: true,
316
+ });
317
+ return {
318
+ success: result.success,
319
+ exitCode: result.exitCode,
320
+ timedOut: result.exitedDueToTimeout ?? false,
321
+ stdout: result.stdout ? result.stdout.toString() : "",
322
+ };
323
+ } catch {
324
+ return spawnFailedResult();
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Async icacls runner (#612): yields the event loop while waiting for the child.
330
+ * Timeout provenance is recorded by our timer (async Subprocess has no exitedDueToTimeout);
331
+ * we still await process exit before classifying so settlement is confirmed.
332
+ */
333
+ async function defaultAsyncIcaclsRunner(args: string[], timeoutMs: number): Promise<IcaclsResult> {
334
+ const proc = trySpawnIcacls(args);
335
+ if (!proc) return spawnFailedResult();
336
+ let timedOutByUs = false;
337
+ const timer = setTimeout(() => {
338
+ timedOutByUs = true;
339
+ try { proc.kill(); } catch { /* already exited */ }
340
+ }, Math.max(1, timeoutMs));
341
+ let exitCode: number | null = null;
342
+ try {
343
+ exitCode = await proc.exited;
344
+ } finally {
345
+ clearTimeout(timer);
346
+ }
347
+ const stdout = proc.stdout
348
+ ? await new Response(proc.stdout).text().catch(() => "")
349
+ : "";
350
+ const timedOut = timedOutByUs;
351
+ return {
352
+ success: !timedOut && exitCode === 0,
353
+ exitCode: timedOut ? null : exitCode,
354
+ timedOut,
355
+ stdout,
356
+ };
357
+ }
358
+
359
+ let icaclsRunner: IcaclsRunner = defaultIcaclsRunner;
360
+ let asyncIcaclsRunner: AsyncIcaclsRunner = defaultAsyncIcaclsRunner;
361
+ let platformOverride: string | null = null;
362
+ let nowFn: () => number = Date.now;
363
+
364
+ /** Test seam: replace the icacls process runner. Pass null to restore the default. */
365
+ export function setIcaclsRunnerForTests(runner: IcaclsRunner | null): void {
366
+ icaclsRunner = runner ?? defaultIcaclsRunner;
367
+ }
368
+
369
+ /** Test seam: replace the async icacls runner. Pass null to restore the default. */
370
+ export function setAsyncIcaclsRunnerForTests(runner: AsyncIcaclsRunner | null): void {
371
+ asyncIcaclsRunner = runner ?? defaultAsyncIcaclsRunner;
372
+ }
373
+
374
+ /**
375
+ * Test seam: force the platform gate (e.g. "win32") so CI on POSIX reaches the runner.
376
+ *
377
+ * Faking win32 on a host without System32 also has to supply a principal, or
378
+ * every forced-branch test would fail on the identity lookup instead of
379
+ * exercising icacls. The synthetic value is registered with the resolver, not
380
+ * chosen here, so a test that injects its own runner still wins.
381
+ */
382
+ const SYNTHETIC_TEST_PRINCIPAL = "*S-1-5-21-1-2-3-1001";
383
+
384
+ export function setPlatformForTests(value: string | null): void {
385
+ platformOverride = value;
386
+ setSyntheticWindowsPrincipalForTests(
387
+ value === "win32" && platform !== "win32" ? SYNTHETIC_TEST_PRINCIPAL : null,
388
+ );
389
+ }
390
+
391
+ /** Test seam: injectable clock for deadline tests (no real sleeps). */
392
+ export function setNowForTests(fn: (() => number) | null): void {
393
+ nowFn = fn ?? Date.now;
394
+ }
395
+
396
+ /** Test seam: clear memo/failure caches between cases. */
397
+ export function resetHardenedStateForTests(): void {
398
+ hardenedDirectories.clear();
399
+ hardenedPaths.clear();
400
+ timedOutPaths.clear();
401
+ }
402
+
403
+ /** Forget a successful harden only after this exact ephemeral path is gone. */
404
+ export function forgetHardenedSecretPath(targetPath: string): void {
405
+ hardenedPaths.delete(targetPath);
406
+ }
407
+
408
+ /**
409
+ * Ephemeral-path lifecycle release: clears the success memo AND any timeout
410
+ * memo keyed by THIS TEMP path in both namespaces. Call only after the temp is
411
+ * proven absent (successful rename, successful unlink, ENOENT, or an explicit
412
+ * non-existence check). Never pass a stable destination: destination-keyed
413
+ * timeout memos are intentional anti-restall state and are not touched here.
414
+ */
415
+ export function forgetEphemeralSecretPath(tempPath: string): void {
416
+ hardenedPaths.delete(tempPath);
417
+ timedOutPaths.delete(`required:${tempPath}`);
418
+ timedOutPaths.delete(`optional:${tempPath}`);
419
+ }
420
+
421
+ /** Directory counterpart for a proven-absent ephemeral staging root. */
422
+ export function forgetEphemeralSecretDir(tempPath: string): void {
423
+ hardenedDirectories.delete(tempPath);
424
+ timedOutPaths.delete(`required:${tempPath}`);
425
+ timedOutPaths.delete(`optional:${tempPath}`);
426
+ }
427
+
428
+ /** Test seam: timeout memo sets return to baseline after ephemeral cleanup. */
429
+ export function timedOutSecretPathCountForTests(): number {
430
+ return timedOutPaths.size;
431
+ }
432
+
433
+ /** Test seam for proving ephemeral success memos do not grow across replacements. */
434
+ export function hardenedSecretPathCountForTests(): number {
435
+ return hardenedPaths.size;
436
+ }
437
+
438
+ /**
439
+ * Directory counterpart. It had no seam, and that absence hid a real gap: a
440
+ * directory-only pathname memo passed every file-based test in this suite.
441
+ */
442
+ export function hardenedSecretDirCountForTests(): number {
443
+ return hardenedDirectories.size;
444
+ }
445
+
446
+ function effectivePlatform(): string {
447
+ return platformOverride ?? platform;
448
+ }
449
+
450
+ /** Test-aware platform predicate for callers that must avoid even a no-op ACL call. */
451
+ export function windowsSecretAclApplies(): boolean {
452
+ return (platformOverride ?? process.platform) === "win32";
453
+ }
454
+
455
+ /** Error carrying an honest code: ETIMEDOUT only for real timeouts, EICACLS otherwise. */
456
+ function icaclsError(step: string, result: IcaclsResult): NodeJS.ErrnoException {
457
+ const err = new Error(
458
+ result.timedOut ? `icacls ${step} timed out` : `icacls ${step} exited ${result.exitCode ?? "null"}`,
459
+ ) as NodeJS.ErrnoException;
460
+ err.code = result.timedOut ? "ETIMEDOUT" : "EICACLS";
461
+ return err;
462
+ }
463
+
464
+ /**
465
+ * The ACL principal is the effective token SID and nothing else.
466
+ *
467
+ * There is no name-shaped fallback here, and that absence is the fix for #1149
468
+ * rather than an omission. `USERDOMAIN\USERNAME` has the right shape but is not
469
+ * evidence of the current token's subject, and both variables are writable by
470
+ * the process that launched us. Granting Full Control to a wrong principal and
471
+ * then running `/inheritance:r` is destructive in both directions: another
472
+ * account can be left holding the secret, or the file can be left with no ACE
473
+ * the current user can use. When the SID cannot be resolved we decline.
474
+ *
475
+ * Non-Windows hosts that force this branch through `setPlatformForTests` get
476
+ * their principal from `setSyntheticWindowsPrincipalForTests`, which lives with
477
+ * the resolver so an injected runner can still take precedence over it.
478
+ */
479
+ function currentWindowsPrincipal(deadline: number): string {
480
+ return resolveCurrentWindowsPrincipal(deadline - nowFn());
481
+ }
482
+
483
+ async function currentWindowsPrincipalAsync(deadline: number): Promise<string> {
484
+ return resolveCurrentWindowsPrincipalAsync(deadline - nowFn());
485
+ }
486
+
487
+ /**
488
+ * Run icacls to harden a single file system entry.
489
+ * Order is intentional (issue #596): grant the owner ACE first so a later
490
+ * `/inheritance:r` or `/remove:g` timeout cannot strand a protected zero-ACE DACL.
491
+ *
492
+ * We do NOT use a shell string; all arguments are passed as an array so no
493
+ * shell injection is possible even for paths with unusual characters.
494
+ *
495
+ * Throws the raw child_process error on failure (caller sanitizes).
496
+ */
497
+ const BROAD_SIDS = ["*S-1-1-0", "*S-1-5-11", "*S-1-5-32-545"] as const;
498
+
499
+ function grantAce(user: string, directory: boolean): string {
500
+ return directory ? `${user}:(OI)(CI)(F)` : `${user}:(F)`;
501
+ }
502
+
503
+ function runIcacls(targetPath: string, directory: boolean, deadline: number): void {
504
+ const principal = currentWindowsPrincipal(deadline);
505
+
506
+ // The deadline is owned by hardenEntry (total budget incl. retry + verification).
507
+ const run = (step: string, args: string[]): IcaclsResult => {
508
+ const remaining = deadline - nowFn();
509
+ if (remaining <= 0) {
510
+ throw icaclsError(step, { success: false, exitCode: null, timedOut: true, stdout: "" });
511
+ }
512
+ return icaclsRunner(args, remaining);
513
+ };
514
+ const runOrThrow = (step: string, args: string[]): void => {
515
+ const result = run(step, args);
516
+ if (!result.success) throw icaclsError(step, result);
517
+ };
518
+
519
+ // Step 1: grant current user full control BEFORE any destructive ACL change.
520
+ // If this fails, inheritance is untouched and the writer keeps inherited access.
521
+ runOrThrow("/grant:r", [targetPath, "/grant:r", grantAce(principal, directory)]);
522
+
523
+ // Step 2: disable inheritance and remove inherited ACEs. The explicit owner ACE
524
+ // from step 1 survives this transition, so a later failure still leaves cleanup access.
525
+ runOrThrow("/inheritance:r", [targetPath, "/inheritance:r"]);
526
+
527
+ // Step 3: remove broad explicit grants using stable SIDs (not localized names).
528
+ // Missing ACEs can yield a non-zero exit; verify with locale-independent /findsid
529
+ // before accepting the failure as harmless — a swallowed real failure would leave
530
+ // Everyone/Users/Authenticated Users grants while reporting hardened.
531
+ // `/remove:g` cannot remove the explicit current-user ACE installed in step 1.
532
+ const removal = run("/remove:g", [targetPath, "/remove:g", ...BROAD_SIDS]);
533
+ if (!removal.success) {
534
+ if (removal.timedOut) throw icaclsError("/remove:g", removal);
535
+ for (const sid of BROAD_SIDS) {
536
+ const found = run("/findsid", [targetPath, "/findsid", sid]);
537
+ if (!found.success) throw icaclsError("/findsid", found);
538
+ // icacls /findsid echoes the target path in its "SID Found" line only when the SID
539
+ // still holds an ACE; the summary lines carry only counts. Matching the path echo —
540
+ // not the (localized) prose — keeps the check locale-independent.
541
+ if (found.stdout.includes(targetPath)) {
542
+ throw icaclsError("/remove:g", removal);
543
+ }
544
+ }
545
+ }
546
+ }
547
+
548
+ /** Async counterpart of runIcacls — same step order and timeout/error classification (#612). */
549
+ async function runIcaclsAsync(targetPath: string, directory: boolean, deadline: number): Promise<void> {
550
+ const principal = await currentWindowsPrincipalAsync(deadline);
551
+
552
+ const run = async (step: string, args: string[]): Promise<IcaclsResult> => {
553
+ const remaining = deadline - nowFn();
554
+ if (remaining <= 0) {
555
+ throw icaclsError(step, { success: false, exitCode: null, timedOut: true, stdout: "" });
556
+ }
557
+ return asyncIcaclsRunner(args, remaining);
558
+ };
559
+ const runOrThrow = async (step: string, args: string[]): Promise<void> => {
560
+ const result = await run(step, args);
561
+ if (!result.success) throw icaclsError(step, result);
562
+ };
563
+
564
+ await runOrThrow("/grant:r", [targetPath, "/grant:r", grantAce(principal, directory)]);
565
+ await runOrThrow("/inheritance:r", [targetPath, "/inheritance:r"]);
566
+
567
+ const removal = await run("/remove:g", [targetPath, "/remove:g", ...BROAD_SIDS]);
568
+ if (!removal.success) {
569
+ if (removal.timedOut) throw icaclsError("/remove:g", removal);
570
+ for (const sid of BROAD_SIDS) {
571
+ const found = await run("/findsid", [targetPath, "/findsid", sid]);
572
+ if (!found.success) throw icaclsError("/findsid", found);
573
+ if (found.stdout.includes(targetPath)) {
574
+ throw icaclsError("/remove:g", removal);
575
+ }
576
+ }
577
+ }
578
+ }
579
+
580
+ /**
581
+ * Sanitize an error from a failed ACL operation into a safe diagnostic string.
582
+ * The raw path must not appear in the returned string (it may contain
583
+ * sensitive username components or PII from the home directory path).
584
+ */
585
+ function sanitizeDiagnostics(error: unknown): string {
586
+ // We do not expose the raw error message or any path-like fragments —
587
+ // just an honest, code-specific cause (issue #160: a transient icacls stall
588
+ // must not read like filesystem non-support).
589
+ const code = error instanceof Error && "code" in error ? String((error as NodeJS.ErrnoException).code) : "";
590
+ switch (code) {
591
+ case "ETIMEDOUT":
592
+ return "ACL hardening timed out (ETIMEDOUT) — transient icacls stall; the volume may still support per-user NTFS ACLs";
593
+ case "EPERM":
594
+ case "EACCES":
595
+ return `ACL hardening failed (${code}) — permission denied running icacls`;
596
+ case "EICACLS":
597
+ return "ACL hardening failed (EICACLS) — icacls command error; filesystem may not support per-user NTFS ACLs";
598
+ case "EACLIDENTITY":
599
+ return "ACL hardening failed (EACLIDENTITY) — the effective Windows account SID could not be resolved";
600
+ default:
601
+ return `ACL hardening failed${code ? ` (${code})` : ""} — filesystem may not support per-user NTFS ACLs`;
602
+ }
603
+ }
604
+
605
+ function isTimeoutError(error: unknown): boolean {
606
+ return error instanceof Error && "code" in error
607
+ && String((error as NodeJS.ErrnoException).code) === "ETIMEDOUT";
608
+ }
609
+
610
+ /** Preserve only the bounded machine-readable cause on a sanitized public error. */
611
+ function sanitizedAclError(diagnostics: string, cause: unknown): NodeJS.ErrnoException {
612
+ const error = new Error(diagnostics) as NodeJS.ErrnoException;
613
+ const code = cause && typeof cause === "object" && "code" in cause
614
+ ? String((cause as { code?: unknown }).code)
615
+ : "";
616
+ // EACLIDENTITY belongs here for the same reason as the rest: a caller that
617
+ // catches a required-mode failure has to tell "the SID could not be resolved"
618
+ // apart from "icacls stalled". Without it the code was dropped and only the
619
+ // message carried the cause, which no caller can branch on.
620
+ if (
621
+ code === "ETIMEDOUT" ||
622
+ code === "EICACLS" ||
623
+ code === "EACCES" ||
624
+ code === "EPERM" ||
625
+ code === "EACLIDENTITY"
626
+ ) {
627
+ error.code = code;
628
+ }
629
+ return error;
630
+ }
631
+
632
+ function previousTimeoutError(retryConsumed: boolean): NodeJS.ErrnoException {
633
+ if (retryConsumed) {
634
+ const error = new Error(
635
+ "ACL hardening skipped — the previous timeout recovery was already consumed",
636
+ ) as NodeJS.ErrnoException;
637
+ error.code = "EACLRETRYEXHAUSTED";
638
+ return error;
639
+ }
640
+ return sanitizedAclError(
641
+ "ACL hardening skipped — previous attempt timed out",
642
+ Object.assign(new Error("timeout"), { code: "ETIMEDOUT" }),
643
+ );
644
+ }
645
+
646
+ /** Consume, but never reset, the single explicit recovery attempt for this key. */
647
+ function timeoutMemoErrorIfBlocked(
648
+ memoKey: string,
649
+ opts: HardenOptions,
650
+ ): NodeJS.ErrnoException | null {
651
+ const retryConsumed = timedOutPaths.get(memoKey);
652
+ if (retryConsumed === undefined) return null;
653
+ if (opts.retryTimedOutOnce && retryConsumed === false) {
654
+ timedOutPaths.set(memoKey, true);
655
+ return null;
656
+ }
657
+ return previousTimeoutError(retryConsumed);
658
+ }
659
+
660
+ function recordTimeout(memoKey: string): void {
661
+ if (!timedOutPaths.has(memoKey)) timedOutPaths.set(memoKey, false);
662
+ }
663
+
664
+ /**
665
+ * Diagnostic-only post-timeout probe (never promotes to ok:true — a clean /findsid
666
+ * does not prove inheritance was disabled or the user grant ran; only a fully
667
+ * completed harden sequence may enter the hardened cache). Bounded by the remaining
668
+ * total budget; returns a short state note for the soft-fail diagnostic.
669
+ */
670
+ function describeAclStateAfterTimeout(targetPath: string, deadline: number): string {
671
+ try {
672
+ for (const sid of BROAD_SIDS) {
673
+ const remaining = deadline - nowFn();
674
+ if (remaining <= 0) return "ACL state unverified (budget exhausted)";
675
+ const found = icaclsRunner([targetPath, "/findsid", sid], remaining);
676
+ if (!found.success) return "ACL state unverified (probe failed)";
677
+ if (found.stdout.includes(targetPath)) return "broad ACL grants still present";
678
+ }
679
+ return "no broad ACL grants detected (hardening still incomplete)";
680
+ } catch {
681
+ return "ACL state unverified (probe failed)";
682
+ }
683
+ }
684
+
685
+ async function describeAclStateAfterTimeoutAsync(targetPath: string, deadline: number): Promise<string> {
686
+ try {
687
+ for (const sid of BROAD_SIDS) {
688
+ const remaining = deadline - nowFn();
689
+ if (remaining <= 0) return "ACL state unverified (budget exhausted)";
690
+ const found = await asyncIcaclsRunner([targetPath, "/findsid", sid], remaining);
691
+ if (!found.success) return "ACL state unverified (probe failed)";
692
+ if (found.stdout.includes(targetPath)) return "broad ACL grants still present";
693
+ }
694
+ return "no broad ACL grants detected (hardening still incomplete)";
695
+ } catch {
696
+ return "ACL state unverified (probe failed)";
697
+ }
698
+ }
699
+
700
+ function timeoutMemoKey(targetPath: string, opts: HardenOptions): string {
701
+ // Destination-path memo only (issue #612). Never a parent directory — directory ACLs
702
+ // are not authoritative for newly created temps.
703
+ //
704
+ // Namespace by required-ness (#766): a soft `required:false` timeout during loadConfig
705
+ // must not poison a later `required:true` management-token harden of the same path.
706
+ const base = opts.timeoutMemoKey ?? targetPath;
707
+ return `${opts.required ? "required" : "optional"}:${base}`;
708
+ }
709
+
710
+ /**
711
+ * Shared harden flow for files and directories: one total budget (env-configurable)
712
+ * covering the initial attempt, ONE timeout retry, and the diagnostic verification.
713
+ * Required paths fail closed for every hardening failure, including genuine
714
+ * timeouts. Optional read paths soft-fail with an honest diagnostic.
715
+ */
716
+ function hardenEntry(
717
+ targetPath: string,
718
+ directory: boolean,
719
+ opts: HardenOptions,
720
+ cache: Map<string, HardenedIdentity>,
721
+ ): HardenResult {
722
+ // Observed absence retires the memo. Leaving it would let a later file at this
723
+ // path satisfy the cache if the filesystem ever hands back a matching identity.
724
+ if (!existsSync(targetPath)) { cache.delete(targetPath); return { ok: true }; }
725
+ if (effectivePlatform() !== "win32") return { ok: true };
726
+ if (memoSatisfied(cache, targetPath)) return { ok: true };
727
+ const memoKey = timeoutMemoKey(targetPath, opts);
728
+ const timeoutMemoError = timeoutMemoErrorIfBlocked(memoKey, opts);
729
+ if (timeoutMemoError) {
730
+ if (opts.required) throw timeoutMemoError;
731
+ return { ok: false, diagnostics: timeoutMemoError.message };
732
+ }
733
+
734
+ const deadline = nowFn() + resolveHardenDeadlineMs();
735
+ let lastErr: unknown;
736
+ for (let attempt = 0; attempt < 2; attempt++) {
737
+ if (attempt > 0 && deadline - nowFn() <= 0) break; // retry only while budget remains
738
+ try {
739
+ // Captured BEFORE the sequence: this is the file we are about to harden.
740
+ const before = observe(targetPath);
741
+ runIcacls(targetPath, directory, deadline);
742
+ if (!recordHarden(cache, targetPath, before)) {
743
+ if (opts.required) throw new Error(SUBSTITUTED_DIAGNOSTIC);
744
+ return { ok: false, diagnostics: SUBSTITUTED_DIAGNOSTIC };
745
+ }
746
+ timedOutPaths.delete(memoKey);
747
+ return { ok: true };
748
+ } catch (err) {
749
+ // A substitution is not a transient icacls stall; do not spend the retry on it.
750
+ if (err instanceof Error && err.message === SUBSTITUTED_DIAGNOSTIC) throw err;
751
+ lastErr = err;
752
+ if (!isTimeoutError(err)) break; // real failures do not retry
753
+ }
754
+ }
755
+
756
+ const diagnostics = sanitizeDiagnostics(lastErr);
757
+ if (isTimeoutError(lastErr)) {
758
+ recordTimeout(memoKey);
759
+ const state = describeAclStateAfterTimeout(targetPath, deadline);
760
+ const annotated = `${diagnostics}; ${state}`;
761
+ if (opts.required) throw sanitizedAclError(annotated, lastErr);
762
+ console.warn(`[opencodex] ${annotated} — continuing without NTFS ACL harden`);
763
+ return { ok: false, diagnostics: annotated };
764
+ }
765
+ if (opts.required) throw sanitizedAclError(diagnostics, lastErr);
766
+ return { ok: false, diagnostics };
767
+ }
768
+
769
+ /** Async counterpart of hardenEntry — yields while waiting on icacls (#612). */
770
+ async function hardenEntryAsync(
771
+ targetPath: string,
772
+ directory: boolean,
773
+ opts: HardenOptions,
774
+ cache: Map<string, HardenedIdentity>,
775
+ ): Promise<HardenResult> {
776
+ if (!existsSync(targetPath)) { cache.delete(targetPath); return { ok: true }; }
777
+ if (effectivePlatform() !== "win32") return { ok: true };
778
+ if (memoSatisfied(cache, targetPath)) return { ok: true };
779
+ const memoKey = timeoutMemoKey(targetPath, opts);
780
+ const timeoutMemoError = timeoutMemoErrorIfBlocked(memoKey, opts);
781
+ if (timeoutMemoError) {
782
+ if (opts.required) throw timeoutMemoError;
783
+ return { ok: false, diagnostics: timeoutMemoError.message };
784
+ }
785
+
786
+ const deadline = nowFn() + resolveHardenDeadlineMs();
787
+ let lastErr: unknown;
788
+ for (let attempt = 0; attempt < 2; attempt++) {
789
+ if (attempt > 0 && deadline - nowFn() <= 0) break;
790
+ try {
791
+ const before = observe(targetPath);
792
+ await runIcaclsAsync(targetPath, directory, deadline);
793
+ if (!recordHarden(cache, targetPath, before)) {
794
+ if (opts.required) throw new Error(SUBSTITUTED_DIAGNOSTIC);
795
+ return { ok: false, diagnostics: SUBSTITUTED_DIAGNOSTIC };
796
+ }
797
+ timedOutPaths.delete(memoKey);
798
+ return { ok: true };
799
+ } catch (err) {
800
+ if (err instanceof Error && err.message === SUBSTITUTED_DIAGNOSTIC) throw err;
801
+ lastErr = err;
802
+ if (!isTimeoutError(err)) break;
803
+ }
804
+ }
805
+
806
+ const diagnostics = sanitizeDiagnostics(lastErr);
807
+ if (isTimeoutError(lastErr)) {
808
+ recordTimeout(memoKey);
809
+ const state = await describeAclStateAfterTimeoutAsync(targetPath, deadline);
810
+ const annotated = `${diagnostics}; ${state}`;
811
+ if (opts.required) throw sanitizedAclError(annotated, lastErr);
812
+ console.warn(`[opencodex] ${annotated} — continuing without NTFS ACL harden`);
813
+ return { ok: false, diagnostics: annotated };
814
+ }
815
+ if (opts.required) throw sanitizedAclError(diagnostics, lastErr);
816
+ return { ok: false, diagnostics };
817
+ }
818
+
819
+ /**
820
+ * Harden a single file path with per-user NTFS ACLs on Windows.
821
+ * On non-Windows platforms, returns ok:true immediately (caller owns chmod).
822
+ *
823
+ * @param targetPath Absolute path to the file to harden.
824
+ * @param opts { required: boolean } — required:true throws on failure.
825
+ */
826
+ export function hardenSecretPath(targetPath: string, opts: HardenOptions): HardenResult {
827
+ return hardenEntry(targetPath, false, opts, hardenedPaths);
828
+ }
829
+
830
+ /**
831
+ * Async harden for write paths that must not block the event loop (#612).
832
+ * Same success/timeout/error policy as hardenSecretPath.
833
+ */
834
+ export function hardenSecretPathAsync(targetPath: string, opts: HardenOptions): Promise<HardenResult> {
835
+ return hardenEntryAsync(targetPath, false, opts, hardenedPaths);
836
+ }
837
+
838
+ /**
839
+ * Harden a directory path with per-user NTFS ACLs on Windows.
840
+ * On non-Windows platforms, returns ok:true immediately (caller owns chmod).
841
+ *
842
+ * @param targetPath Absolute path to the directory to harden.
843
+ * @param opts { required: boolean } — required:true throws on failure.
844
+ */
845
+ export function hardenSecretDir(targetPath: string, opts: HardenOptions): HardenResult {
846
+ return hardenEntry(targetPath, true, opts, hardenedDirectories);
847
+ }
848
+
849
+ /**
850
+ * Async directory harden (#612). Same policy as hardenSecretDir.
851
+ */
852
+ export function hardenSecretDirAsync(targetPath: string, opts: HardenOptions): Promise<HardenResult> {
853
+ return hardenEntryAsync(targetPath, true, opts, hardenedDirectories);
854
+ }