apex-code 0.0.1-alpha.0 → 0.0.1-alpha.2

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 (426) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +66 -681
  3. package/dist/cli/args.d.ts +5 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +45 -6
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/auth-command.d.ts.map +1 -1
  8. package/dist/cli/auth-command.js +3 -3
  9. package/dist/cli/auth-command.js.map +1 -1
  10. package/dist/cli/cost-command.d.ts +19 -0
  11. package/dist/cli/cost-command.d.ts.map +1 -0
  12. package/dist/cli/cost-command.js +107 -0
  13. package/dist/cli/cost-command.js.map +1 -0
  14. package/dist/cli/startup-ui.d.ts +1 -1
  15. package/dist/cli/startup-ui.d.ts.map +1 -1
  16. package/dist/cli/startup-ui.js +1 -2
  17. package/dist/cli/startup-ui.js.map +1 -1
  18. package/dist/cli.d.ts.map +1 -1
  19. package/dist/cli.js +45 -16
  20. package/dist/cli.js.map +1 -1
  21. package/dist/config.d.ts +2 -2
  22. package/dist/config.d.ts.map +1 -1
  23. package/dist/config.js +14 -7
  24. package/dist/config.js.map +1 -1
  25. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  26. package/dist/core/agent-session-runtime.js +2 -0
  27. package/dist/core/agent-session-runtime.js.map +1 -1
  28. package/dist/core/agent-session-services.d.ts +9 -0
  29. package/dist/core/agent-session-services.d.ts.map +1 -1
  30. package/dist/core/agent-session-services.js +30 -4
  31. package/dist/core/agent-session-services.js.map +1 -1
  32. package/dist/core/agent-session.d.ts +46 -0
  33. package/dist/core/agent-session.d.ts.map +1 -1
  34. package/dist/core/agent-session.js +137 -19
  35. package/dist/core/agent-session.js.map +1 -1
  36. package/dist/core/auth-storage.d.ts.map +1 -1
  37. package/dist/core/auth-storage.js +6 -6
  38. package/dist/core/auth-storage.js.map +1 -1
  39. package/dist/core/bash-executor.d.ts.map +1 -1
  40. package/dist/core/bash-executor.js +1 -1
  41. package/dist/core/bash-executor.js.map +1 -1
  42. package/dist/core/context/deferred-schemas.d.ts +92 -0
  43. package/dist/core/context/deferred-schemas.d.ts.map +1 -0
  44. package/dist/core/context/deferred-schemas.js +74 -0
  45. package/dist/core/context/deferred-schemas.js.map +1 -0
  46. package/dist/core/context/eviction.d.ts +47 -0
  47. package/dist/core/context/eviction.d.ts.map +1 -0
  48. package/dist/core/context/eviction.js +83 -0
  49. package/dist/core/context/eviction.js.map +1 -0
  50. package/dist/core/context/pipeline.d.ts +90 -0
  51. package/dist/core/context/pipeline.d.ts.map +1 -0
  52. package/dist/core/context/pipeline.js +109 -0
  53. package/dist/core/context/pipeline.js.map +1 -0
  54. package/dist/core/credential-failover.d.ts +30 -0
  55. package/dist/core/credential-failover.d.ts.map +1 -0
  56. package/dist/core/credential-failover.js +46 -0
  57. package/dist/core/credential-failover.js.map +1 -0
  58. package/dist/core/credential-pool.d.ts +60 -0
  59. package/dist/core/credential-pool.d.ts.map +1 -0
  60. package/dist/core/credential-pool.js +84 -0
  61. package/dist/core/credential-pool.js.map +1 -0
  62. package/dist/core/delegation/agents.d.ts +15 -0
  63. package/dist/core/delegation/agents.d.ts.map +1 -0
  64. package/dist/core/delegation/agents.js +55 -0
  65. package/dist/core/delegation/agents.js.map +1 -0
  66. package/dist/core/delegation/ceiling.d.ts +29 -0
  67. package/dist/core/delegation/ceiling.d.ts.map +1 -0
  68. package/dist/core/delegation/ceiling.js +34 -0
  69. package/dist/core/delegation/ceiling.js.map +1 -0
  70. package/dist/core/delegation/runtime.d.ts +94 -0
  71. package/dist/core/delegation/runtime.d.ts.map +1 -0
  72. package/dist/core/delegation/runtime.js +117 -0
  73. package/dist/core/delegation/runtime.js.map +1 -0
  74. package/dist/core/durable-state/daemon.d.ts +37 -0
  75. package/dist/core/durable-state/daemon.d.ts.map +1 -0
  76. package/dist/core/durable-state/daemon.js +62 -0
  77. package/dist/core/durable-state/daemon.js.map +1 -0
  78. package/dist/core/durable-state/provenance.d.ts +7 -0
  79. package/dist/core/durable-state/provenance.d.ts.map +1 -0
  80. package/dist/core/durable-state/provenance.js +24 -0
  81. package/dist/core/durable-state/provenance.js.map +1 -0
  82. package/dist/core/durable-state/sqlite.d.ts +84 -0
  83. package/dist/core/durable-state/sqlite.d.ts.map +1 -0
  84. package/dist/core/durable-state/sqlite.js +331 -0
  85. package/dist/core/durable-state/sqlite.js.map +1 -0
  86. package/dist/core/environment.d.ts +12 -0
  87. package/dist/core/environment.d.ts.map +1 -0
  88. package/dist/core/environment.js +69 -0
  89. package/dist/core/environment.js.map +1 -0
  90. package/dist/core/evidence-policy.d.ts +17 -0
  91. package/dist/core/evidence-policy.d.ts.map +1 -0
  92. package/dist/core/evidence-policy.js +25 -0
  93. package/dist/core/evidence-policy.js.map +1 -0
  94. package/dist/core/evidence.d.ts +16 -0
  95. package/dist/core/evidence.d.ts.map +1 -0
  96. package/dist/core/evidence.js +51 -0
  97. package/dist/core/evidence.js.map +1 -0
  98. package/dist/core/experimental.d.ts.map +1 -1
  99. package/dist/core/experimental.js +2 -1
  100. package/dist/core/experimental.js.map +1 -1
  101. package/dist/core/extensions/types.d.ts +2 -0
  102. package/dist/core/extensions/types.d.ts.map +1 -1
  103. package/dist/core/extensions/types.js.map +1 -1
  104. package/dist/core/index.d.ts +1 -0
  105. package/dist/core/index.d.ts.map +1 -1
  106. package/dist/core/index.js +1 -0
  107. package/dist/core/index.js.map +1 -1
  108. package/dist/core/model-config.d.ts +8 -0
  109. package/dist/core/model-config.d.ts.map +1 -1
  110. package/dist/core/model-config.js +21 -5
  111. package/dist/core/model-config.js.map +1 -1
  112. package/dist/core/model-resolver.d.ts +20 -0
  113. package/dist/core/model-resolver.d.ts.map +1 -1
  114. package/dist/core/model-resolver.js +59 -0
  115. package/dist/core/model-resolver.js.map +1 -1
  116. package/dist/core/model-runtime.d.ts +73 -0
  117. package/dist/core/model-runtime.d.ts.map +1 -1
  118. package/dist/core/model-runtime.js +225 -8
  119. package/dist/core/model-runtime.js.map +1 -1
  120. package/dist/core/observability/aggregate.d.ts +21 -0
  121. package/dist/core/observability/aggregate.d.ts.map +1 -0
  122. package/dist/core/observability/aggregate.js +36 -0
  123. package/dist/core/observability/aggregate.js.map +1 -0
  124. package/dist/core/observability/otlp.d.ts +66 -0
  125. package/dist/core/observability/otlp.d.ts.map +1 -0
  126. package/dist/core/observability/otlp.js +95 -0
  127. package/dist/core/observability/otlp.js.map +1 -0
  128. package/dist/core/package-manager.d.ts.map +1 -1
  129. package/dist/core/package-manager.js +3 -2
  130. package/dist/core/package-manager.js.map +1 -1
  131. package/dist/core/permissions/gate.d.ts +35 -0
  132. package/dist/core/permissions/gate.d.ts.map +1 -0
  133. package/dist/core/permissions/gate.js +72 -0
  134. package/dist/core/permissions/gate.js.map +1 -0
  135. package/dist/core/permissions/modes.d.ts +18 -0
  136. package/dist/core/permissions/modes.d.ts.map +1 -0
  137. package/dist/core/permissions/modes.js +57 -0
  138. package/dist/core/permissions/modes.js.map +1 -0
  139. package/dist/core/permissions/responder.d.ts +30 -0
  140. package/dist/core/permissions/responder.d.ts.map +1 -0
  141. package/dist/core/permissions/responder.js +32 -0
  142. package/dist/core/permissions/responder.js.map +1 -0
  143. package/dist/core/permissions/rules.d.ts +34 -0
  144. package/dist/core/permissions/rules.d.ts.map +1 -0
  145. package/dist/core/permissions/rules.js +74 -0
  146. package/dist/core/permissions/rules.js.map +1 -0
  147. package/dist/core/permissions/startup.d.ts +33 -0
  148. package/dist/core/permissions/startup.d.ts.map +1 -0
  149. package/dist/core/permissions/startup.js +55 -0
  150. package/dist/core/permissions/startup.js.map +1 -0
  151. package/dist/core/permissions/store.d.ts +114 -0
  152. package/dist/core/permissions/store.d.ts.map +1 -0
  153. package/dist/core/permissions/store.js +229 -0
  154. package/dist/core/permissions/store.js.map +1 -0
  155. package/dist/core/project-trust.d.ts.map +1 -1
  156. package/dist/core/project-trust.js +1 -1
  157. package/dist/core/project-trust.js.map +1 -1
  158. package/dist/core/provider-attribution.d.ts.map +1 -1
  159. package/dist/core/provider-attribution.js +6 -7
  160. package/dist/core/provider-attribution.js.map +1 -1
  161. package/dist/core/remote-catalog-provider.d.ts +2 -2
  162. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  163. package/dist/core/remote-catalog-provider.js +20 -16
  164. package/dist/core/remote-catalog-provider.js.map +1 -1
  165. package/dist/core/sandbox/child-entry.d.ts +2 -0
  166. package/dist/core/sandbox/child-entry.d.ts.map +1 -0
  167. package/dist/core/sandbox/child-entry.js +11 -0
  168. package/dist/core/sandbox/child-entry.js.map +1 -0
  169. package/dist/core/sandbox/cli-launch.d.ts +27 -0
  170. package/dist/core/sandbox/cli-launch.d.ts.map +1 -0
  171. package/dist/core/sandbox/cli-launch.js +133 -0
  172. package/dist/core/sandbox/cli-launch.js.map +1 -0
  173. package/dist/core/sandbox/cli-supervisor.d.ts +25 -0
  174. package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -0
  175. package/dist/core/sandbox/cli-supervisor.js +57 -0
  176. package/dist/core/sandbox/cli-supervisor.js.map +1 -0
  177. package/dist/core/sandbox/linux-backend.d.ts +18 -0
  178. package/dist/core/sandbox/linux-backend.d.ts.map +1 -0
  179. package/dist/core/sandbox/linux-backend.js +186 -0
  180. package/dist/core/sandbox/linux-backend.js.map +1 -0
  181. package/dist/core/sandbox/macos-backend.d.ts +18 -0
  182. package/dist/core/sandbox/macos-backend.d.ts.map +1 -0
  183. package/dist/core/sandbox/macos-backend.js +195 -0
  184. package/dist/core/sandbox/macos-backend.js.map +1 -0
  185. package/dist/core/sandbox/network-proxy.d.ts +19 -0
  186. package/dist/core/sandbox/network-proxy.d.ts.map +1 -0
  187. package/dist/core/sandbox/network-proxy.js +79 -0
  188. package/dist/core/sandbox/network-proxy.js.map +1 -0
  189. package/dist/core/sandbox/policy.d.ts +32 -0
  190. package/dist/core/sandbox/policy.d.ts.map +1 -0
  191. package/dist/core/sandbox/policy.js +38 -0
  192. package/dist/core/sandbox/policy.js.map +1 -0
  193. package/dist/core/sandbox/supervisor.d.ts +31 -0
  194. package/dist/core/sandbox/supervisor.d.ts.map +1 -0
  195. package/dist/core/sandbox/supervisor.js +18 -0
  196. package/dist/core/sandbox/supervisor.js.map +1 -0
  197. package/dist/core/sandbox/violations.d.ts +23 -0
  198. package/dist/core/sandbox/violations.d.ts.map +1 -0
  199. package/dist/core/sandbox/violations.js +30 -0
  200. package/dist/core/sandbox/violations.js.map +1 -0
  201. package/dist/core/sdk.d.ts +20 -36
  202. package/dist/core/sdk.d.ts.map +1 -1
  203. package/dist/core/sdk.js +129 -2
  204. package/dist/core/sdk.js.map +1 -1
  205. package/dist/core/session-manager.d.ts +41 -2
  206. package/dist/core/session-manager.d.ts.map +1 -1
  207. package/dist/core/session-manager.js +57 -0
  208. package/dist/core/session-manager.js.map +1 -1
  209. package/dist/core/session-share.d.ts +16 -0
  210. package/dist/core/session-share.d.ts.map +1 -0
  211. package/dist/core/session-share.js +22 -0
  212. package/dist/core/session-share.js.map +1 -0
  213. package/dist/core/settings-manager.d.ts +38 -9
  214. package/dist/core/settings-manager.d.ts.map +1 -1
  215. package/dist/core/settings-manager.js +39 -24
  216. package/dist/core/settings-manager.js.map +1 -1
  217. package/dist/core/system-prompt.d.ts.map +1 -1
  218. package/dist/core/system-prompt.js +5 -5
  219. package/dist/core/system-prompt.js.map +1 -1
  220. package/dist/core/timings.d.ts +1 -1
  221. package/dist/core/timings.d.ts.map +1 -1
  222. package/dist/core/timings.js +3 -2
  223. package/dist/core/timings.js.map +1 -1
  224. package/dist/core/tools/ask-user.d.ts +25 -0
  225. package/dist/core/tools/ask-user.d.ts.map +1 -0
  226. package/dist/core/tools/ask-user.js +49 -0
  227. package/dist/core/tools/ask-user.js.map +1 -0
  228. package/dist/core/tools/bash-command-segments.d.ts +22 -0
  229. package/dist/core/tools/bash-command-segments.d.ts.map +1 -0
  230. package/dist/core/tools/bash-command-segments.js +128 -0
  231. package/dist/core/tools/bash-command-segments.js.map +1 -0
  232. package/dist/core/tools/bash.d.ts +26 -3
  233. package/dist/core/tools/bash.d.ts.map +1 -1
  234. package/dist/core/tools/bash.js +105 -16
  235. package/dist/core/tools/bash.js.map +1 -1
  236. package/dist/core/tools/contract.d.ts +156 -0
  237. package/dist/core/tools/contract.d.ts.map +1 -0
  238. package/dist/core/tools/contract.js +48 -0
  239. package/dist/core/tools/contract.js.map +1 -0
  240. package/dist/core/tools/delegate.d.ts +38 -0
  241. package/dist/core/tools/delegate.d.ts.map +1 -0
  242. package/dist/core/tools/delegate.js +66 -0
  243. package/dist/core/tools/delegate.js.map +1 -0
  244. package/dist/core/tools/edit.d.ts +2 -2
  245. package/dist/core/tools/edit.d.ts.map +1 -1
  246. package/dist/core/tools/edit.js +24 -0
  247. package/dist/core/tools/edit.js.map +1 -1
  248. package/dist/core/tools/find.d.ts +2 -2
  249. package/dist/core/tools/find.d.ts.map +1 -1
  250. package/dist/core/tools/find.js +12 -0
  251. package/dist/core/tools/find.js.map +1 -1
  252. package/dist/core/tools/grep.d.ts +2 -2
  253. package/dist/core/tools/grep.d.ts.map +1 -1
  254. package/dist/core/tools/grep.js +12 -0
  255. package/dist/core/tools/grep.js.map +1 -1
  256. package/dist/core/tools/index.d.ts +29 -4
  257. package/dist/core/tools/index.d.ts.map +1 -1
  258. package/dist/core/tools/index.js +118 -2
  259. package/dist/core/tools/index.js.map +1 -1
  260. package/dist/core/tools/ls.d.ts +2 -2
  261. package/dist/core/tools/ls.d.ts.map +1 -1
  262. package/dist/core/tools/ls.js +12 -0
  263. package/dist/core/tools/ls.js.map +1 -1
  264. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  265. package/dist/core/tools/output-accumulator.js +1 -1
  266. package/dist/core/tools/output-accumulator.js.map +1 -1
  267. package/dist/core/tools/path-permission.d.ts +18 -0
  268. package/dist/core/tools/path-permission.d.ts.map +1 -0
  269. package/dist/core/tools/path-permission.js +28 -0
  270. package/dist/core/tools/path-permission.js.map +1 -0
  271. package/dist/core/tools/plan-present.d.ts +27 -0
  272. package/dist/core/tools/plan-present.d.ts.map +1 -0
  273. package/dist/core/tools/plan-present.js +55 -0
  274. package/dist/core/tools/plan-present.js.map +1 -0
  275. package/dist/core/tools/read.d.ts +2 -2
  276. package/dist/core/tools/read.d.ts.map +1 -1
  277. package/dist/core/tools/read.js +12 -0
  278. package/dist/core/tools/read.js.map +1 -1
  279. package/dist/core/tools/test.d.ts +34 -0
  280. package/dist/core/tools/test.d.ts.map +1 -0
  281. package/dist/core/tools/test.js +75 -0
  282. package/dist/core/tools/test.js.map +1 -0
  283. package/dist/core/tools/todo-write.d.ts +31 -0
  284. package/dist/core/tools/todo-write.d.ts.map +1 -0
  285. package/dist/core/tools/todo-write.js +51 -0
  286. package/dist/core/tools/todo-write.js.map +1 -0
  287. package/dist/core/tools/tool-definition-wrapper.d.ts +13 -2
  288. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  289. package/dist/core/tools/tool-definition-wrapper.js +11 -1
  290. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  291. package/dist/core/tools/tool-schema.d.ts +22 -0
  292. package/dist/core/tools/tool-schema.d.ts.map +1 -0
  293. package/dist/core/tools/tool-schema.js +45 -0
  294. package/dist/core/tools/tool-schema.js.map +1 -0
  295. package/dist/core/tools/web-fetch.d.ts +26 -0
  296. package/dist/core/tools/web-fetch.d.ts.map +1 -0
  297. package/dist/core/tools/web-fetch.js +62 -0
  298. package/dist/core/tools/web-fetch.js.map +1 -0
  299. package/dist/core/tools/web-search.d.ts +26 -0
  300. package/dist/core/tools/web-search.d.ts.map +1 -0
  301. package/dist/core/tools/web-search.js +53 -0
  302. package/dist/core/tools/web-search.js.map +1 -0
  303. package/dist/core/tools/write.d.ts +6 -2
  304. package/dist/core/tools/write.d.ts.map +1 -1
  305. package/dist/core/tools/write.js +32 -1
  306. package/dist/core/tools/write.js.map +1 -1
  307. package/dist/core/usage-performance-store.d.ts +56 -0
  308. package/dist/core/usage-performance-store.d.ts.map +1 -0
  309. package/dist/core/usage-performance-store.js +85 -0
  310. package/dist/core/usage-performance-store.js.map +1 -0
  311. package/dist/index.d.ts +1 -0
  312. package/dist/index.d.ts.map +1 -1
  313. package/dist/index.js +1 -0
  314. package/dist/index.js.map +1 -1
  315. package/dist/main.d.ts.map +1 -1
  316. package/dist/main.js +48 -6
  317. package/dist/main.js.map +1 -1
  318. package/dist/migrations.d.ts.map +1 -1
  319. package/dist/migrations.js +2 -2
  320. package/dist/migrations.js.map +1 -1
  321. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  322. package/dist/modes/interactive/components/config-selector.js +9 -6
  323. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  324. package/dist/modes/interactive/components/first-time-setup.d.ts +1 -4
  325. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
  326. package/dist/modes/interactive/components/first-time-setup.js +11 -39
  327. package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
  328. package/dist/modes/interactive/components/footer.d.ts +8 -1
  329. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  330. package/dist/modes/interactive/components/footer.js +49 -20
  331. package/dist/modes/interactive/components/footer.js.map +1 -1
  332. package/dist/modes/interactive/components/settings-selector.d.ts +2 -2
  333. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  334. package/dist/modes/interactive/components/settings-selector.js +6 -6
  335. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  336. package/dist/modes/interactive/components/tool-execution.d.ts +2 -1
  337. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  338. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  339. package/dist/modes/interactive/external-editor.d.ts.map +1 -1
  340. package/dist/modes/interactive/external-editor.js +58 -9
  341. package/dist/modes/interactive/external-editor.js.map +1 -1
  342. package/dist/modes/interactive/interactive-mode.d.ts +2 -3
  343. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  344. package/dist/modes/interactive/interactive-mode.js +92 -104
  345. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  346. package/dist/modes/interactive/theme/theme-schema.json +2 -2
  347. package/dist/package-manager-cli.d.ts.map +1 -1
  348. package/dist/package-manager-cli.js +15 -10
  349. package/dist/package-manager-cli.js.map +1 -1
  350. package/dist/rpc-entry.d.ts.map +1 -1
  351. package/dist/rpc-entry.js +2 -1
  352. package/dist/rpc-entry.js.map +1 -1
  353. package/dist/server/create-harness.d.ts +1 -1
  354. package/dist/server/create-harness.d.ts.map +1 -1
  355. package/dist/server/create-harness.js +6 -5
  356. package/dist/server/create-harness.js.map +1 -1
  357. package/dist/testing/replay/metrics.d.ts +24 -0
  358. package/dist/testing/replay/metrics.d.ts.map +1 -0
  359. package/dist/testing/replay/metrics.js +35 -0
  360. package/dist/testing/replay/metrics.js.map +1 -0
  361. package/dist/testing/replay/recorded-provider.d.ts +10 -0
  362. package/dist/testing/replay/recorded-provider.d.ts.map +1 -0
  363. package/dist/testing/replay/recorded-provider.js +108 -0
  364. package/dist/testing/replay/recorded-provider.js.map +1 -0
  365. package/dist/testing/replay/runner.d.ts +56 -0
  366. package/dist/testing/replay/runner.d.ts.map +1 -0
  367. package/dist/testing/replay/runner.js +406 -0
  368. package/dist/testing/replay/runner.js.map +1 -0
  369. package/dist/utils/apex-code-user-agent.d.ts +2 -0
  370. package/dist/utils/apex-code-user-agent.d.ts.map +1 -0
  371. package/dist/utils/apex-code-user-agent.js +5 -0
  372. package/dist/utils/apex-code-user-agent.js.map +1 -0
  373. package/dist/utils/clipboard-image.d.ts.map +1 -1
  374. package/dist/utils/clipboard-image.js +1 -1
  375. package/dist/utils/clipboard-image.js.map +1 -1
  376. package/dist/utils/tools-manager.d.ts +32 -0
  377. package/dist/utils/tools-manager.d.ts.map +1 -1
  378. package/dist/utils/tools-manager.js +198 -100
  379. package/dist/utils/tools-manager.js.map +1 -1
  380. package/dist/utils/version-check.d.ts +5 -5
  381. package/dist/utils/version-check.d.ts.map +1 -1
  382. package/dist/utils/version-check.js +23 -14
  383. package/dist/utils/version-check.js.map +1 -1
  384. package/dist/utils/windows-self-update.d.ts.map +1 -1
  385. package/dist/utils/windows-self-update.js +1 -1
  386. package/dist/utils/windows-self-update.js.map +1 -1
  387. package/docs/compaction.md +21 -21
  388. package/docs/containerization.md +24 -21
  389. package/docs/custom-provider.md +9 -9
  390. package/docs/development.md +11 -11
  391. package/docs/environment-variables.md +36 -77
  392. package/docs/extensions.md +47 -47
  393. package/docs/index.md +12 -24
  394. package/docs/json.md +6 -6
  395. package/docs/keybindings.md +3 -3
  396. package/docs/llama-cpp.md +7 -7
  397. package/docs/models.md +11 -11
  398. package/docs/packages.md +46 -42
  399. package/docs/prompt-templates.md +3 -3
  400. package/docs/providers.md +10 -10
  401. package/docs/quickstart.md +49 -32
  402. package/docs/rpc.md +11 -11
  403. package/docs/sdk.md +4 -4
  404. package/docs/security.md +18 -18
  405. package/docs/session-format.md +9 -9
  406. package/docs/sessions.md +13 -13
  407. package/docs/settings.md +10 -14
  408. package/docs/shell-aliases.md +1 -1
  409. package/docs/skills.md +8 -8
  410. package/docs/terminal-setup.md +12 -12
  411. package/docs/termux.md +4 -4
  412. package/docs/themes.md +8 -8
  413. package/docs/tmux.md +4 -4
  414. package/docs/tui.md +3 -3
  415. package/docs/usage.md +54 -51
  416. package/docs/windows.md +1 -1
  417. package/npm-shrinkwrap.json +5 -5
  418. package/package.json +2 -3
  419. package/dist/core/telemetry.d.ts +0 -3
  420. package/dist/core/telemetry.d.ts.map +0 -1
  421. package/dist/core/telemetry.js +0 -9
  422. package/dist/core/telemetry.js.map +0 -1
  423. package/dist/utils/pi-user-agent.d.ts +0 -2
  424. package/dist/utils/pi-user-agent.d.ts.map +0 -1
  425. package/dist/utils/pi-user-agent.js +0 -5
  426. package/dist/utils/pi-user-agent.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"remote-catalog-provider.d.ts","sourceRoot":"","sources":["../../src/core/remote-catalog-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgC,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAMpF,eAAO,MAAM,kCAAkC,QAAqB,CAAC;AAqCrE,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAChC,QAAQ,EAAE,QAAQ,EAClB,cAAc,GAAE,MAAiC,EACjD,gBAAgB,CAAC,EAAE,MAAM,GACvB,QAAQ,CAmFV","sourcesContent":["import type { Api, Model, ModelsStoreEntry, Provider } from \"@earendil-works/pi-ai\";\nimport { VERSION } from \"../config.ts\";\nimport { fetchWithRetry } from \"../utils/management-http.ts\";\nimport { getPiUserAgent } from \"../utils/pi-user-agent.ts\";\n\nconst DEFAULT_CATALOG_BASE_URL = \"https://pi.dev\";\nexport const REMOTE_CATALOG_REFRESH_INTERVAL_MS = 4 * 60 * 60 * 1000;\n\nfunction mergeModels(baseline: readonly Model<Api>[], dynamic: readonly Model<Api>[]): Model<Api>[] {\n\tconst merged = [...baseline];\n\tfor (const model of dynamic) {\n\t\tconst index = merged.findIndex((entry) => entry.id === model.id);\n\t\tif (index >= 0) merged[index] = model;\n\t\telse merged.push(model);\n\t}\n\treturn merged;\n}\n\nfunction parseCatalog(providerId: string, value: unknown): Model<Api>[] {\n\tconst entries = Array.isArray(value)\n\t\t? value\n\t\t: typeof value === \"object\" && value !== null && \"models\" in value && Array.isArray(value.models)\n\t\t\t? value.models\n\t\t\t: typeof value === \"object\" && value !== null\n\t\t\t\t? Object.values(value)\n\t\t\t\t: undefined;\n\tif (!entries) throw new Error(`Invalid model catalog for provider \"${providerId}\"`);\n\treturn entries\n\t\t.filter((entry): entry is Model<Api> => typeof entry === \"object\" && entry !== null && \"id\" in entry)\n\t\t.map((model) => ({ ...model, provider: providerId }));\n}\n\nfunction remoteModels(\n\tentry: ModelsStoreEntry | undefined,\n\tlocalGeneratedAt: number | undefined,\n): readonly Model<Api>[] {\n\tif (!entry) return [];\n\tif (localGeneratedAt !== undefined && (entry.lastModified === undefined || entry.lastModified <= localGeneratedAt)) {\n\t\treturn [];\n\t}\n\treturn entry.models;\n}\n\n/** Add a persisted pi.dev catalog overlay to a static built-in provider. */\nexport function withRemoteCatalog(\n\tprovider: Provider,\n\tcatalogBaseUrl: string = DEFAULT_CATALOG_BASE_URL,\n\tlocalGeneratedAt?: number,\n): Provider {\n\tlet dynamicModels: readonly Model<Api>[] = [];\n\n\treturn {\n\t\t...provider,\n\t\tgetModels: () => mergeModels(provider.getModels(), dynamicModels),\n\t\trefreshModels: async (context) => {\n\t\t\tconst stored = context.stored;\n\t\t\tconst restored = remoteModels(stored, localGeneratedAt).filter((model) => model.provider === provider.id);\n\t\t\tif (\n\t\t\t\t!(await context.publish({\n\t\t\t\t\tupdate: () => {\n\t\t\t\t\t\tdynamicModels = restored;\n\t\t\t\t\t},\n\t\t\t\t}))\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!context.allowNetwork || context.signal.aborted) return;\n\t\t\tif (\n\t\t\t\t!context.force &&\n\t\t\t\tstored?.checkedAt !== undefined &&\n\t\t\t\tstored.lastModified !== undefined &&\n\t\t\t\tDate.now() - stored.checkedAt < REMOTE_CATALOG_REFRESH_INTERVAL_MS\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Only revalidate when a cached body backs the validator, so a 304 can never\n\t\t\t// leave the overlay empty.\n\t\t\tconst validator = stored?.models.length ? stored.etag : undefined;\n\t\t\tconst url = new URL(`/api/models/providers/${encodeURIComponent(provider.id)}`, catalogBaseUrl);\n\t\t\tconst response = await fetchWithRetry(url, {\n\t\t\t\theaders: {\n\t\t\t\t\taccept: \"application/json\",\n\t\t\t\t\t\"User-Agent\": getPiUserAgent(VERSION),\n\t\t\t\t\t...(validator ? { \"if-none-match\": validator } : {}),\n\t\t\t\t},\n\t\t\t\tsignal: context.signal,\n\t\t\t});\n\t\t\tif (context.signal.aborted) return;\n\t\t\tconst checkedAt = Date.now();\n\t\t\t// Unchanged: dynamicModels already holds the stored overlay, so only the\n\t\t\t// freshness window moves.\n\t\t\tif (response.status === 304 && stored) {\n\t\t\t\tawait context.publish({ persist: { ...stored, checkedAt } });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (response.status === 404 || response.status === 501) {\n\t\t\t\tawait context.publish({\n\t\t\t\t\tpersist: {\n\t\t\t\t\t\t...(stored ?? { models: [] }),\n\t\t\t\t\t\tcheckedAt,\n\t\t\t\t\t\tlastModified: 0,\n\t\t\t\t\t\tetag: undefined,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!response.ok) {\n\t\t\t\t// Transient failure: the cached body and its validator stay valid, so keep the\n\t\t\t\t// etag and let the next refresh revalidate instead of downloading the catalog.\n\t\t\t\tawait context.publish({ persist: { ...(stored ?? { models: [] }), checkedAt } });\n\t\t\t\tthrow new Error(`Model catalog request failed for ${provider.id}: ${response.status}`);\n\t\t\t}\n\t\t\tconst refreshed = parseCatalog(provider.id, await response.json());\n\t\t\tconst lastModified = Date.parse(response.headers.get(\"last-modified\") ?? \"\");\n\t\t\tif (context.signal.aborted) return;\n\t\t\tconst entry = {\n\t\t\t\tmodels: refreshed,\n\t\t\t\tcheckedAt,\n\t\t\t\tlastModified: Number.isNaN(lastModified) ? 0 : lastModified,\n\t\t\t\tetag: response.headers.get(\"etag\") ?? undefined,\n\t\t\t};\n\t\t\tconst published = remoteModels(entry, localGeneratedAt);\n\t\t\tawait context.publish({\n\t\t\t\tpersist: entry,\n\t\t\t\tupdate: () => {\n\t\t\t\t\tdynamicModels = published;\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"remote-catalog-provider.d.ts","sourceRoot":"","sources":["../../src/core/remote-catalog-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgC,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAKpF,eAAO,MAAM,kCAAkC,QAAqB,CAAC;AAuCrE,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,QAAQ,CAwFjH","sourcesContent":["import type { Api, Model, ModelsStoreEntry, Provider } from \"@earendil-works/pi-ai\";\nimport { VERSION } from \"../config.ts\";\nimport { getApexCodeUserAgent } from \"../utils/apex-code-user-agent.ts\";\nimport { fetchWithRetry } from \"../utils/management-http.ts\";\n\nexport const REMOTE_CATALOG_REFRESH_INTERVAL_MS = 4 * 60 * 60 * 1000;\n\nfunction mergeModels(baseline: readonly Model<Api>[], dynamic: readonly Model<Api>[]): Model<Api>[] {\n\tconst merged = [...baseline];\n\tfor (const model of dynamic) {\n\t\tconst index = merged.findIndex((entry) => entry.id === model.id);\n\t\tif (index >= 0) merged[index] = model;\n\t\telse merged.push(model);\n\t}\n\treturn merged;\n}\n\nfunction parseCatalog(providerId: string, value: unknown): Model<Api>[] {\n\tconst entries = Array.isArray(value)\n\t\t? value\n\t\t: typeof value === \"object\" && value !== null && \"models\" in value && Array.isArray(value.models)\n\t\t\t? value.models\n\t\t\t: typeof value === \"object\" && value !== null\n\t\t\t\t? Object.values(value)\n\t\t\t\t: undefined;\n\tif (!entries) throw new Error(`Invalid model catalog for provider \"${providerId}\"`);\n\treturn entries\n\t\t.filter((entry): entry is Model<Api> => typeof entry === \"object\" && entry !== null && \"id\" in entry)\n\t\t.map((model) => ({ ...model, provider: providerId }));\n}\n\nfunction remoteModels(\n\tentry: ModelsStoreEntry | undefined,\n\tlocalGeneratedAt: number | undefined,\n): readonly Model<Api>[] {\n\tif (!entry) return [];\n\tif (localGeneratedAt !== undefined && (entry.lastModified === undefined || entry.lastModified <= localGeneratedAt)) {\n\t\treturn [];\n\t}\n\treturn entry.models;\n}\n\ntype CatalogStoreEntry = ModelsStoreEntry & { sourceBaseUrl?: string };\n\n/** Add a persisted configured catalog overlay to a static built-in provider. */\nexport function withRemoteCatalog(provider: Provider, catalogBaseUrl: string, localGeneratedAt?: number): Provider {\n\tlet dynamicModels: readonly Model<Api>[] = [];\n\tconst sourceBaseUrl = new URL(catalogBaseUrl).href;\n\n\treturn {\n\t\t...provider,\n\t\tgetModels: () => mergeModels(provider.getModels(), dynamicModels),\n\t\trefreshModels: async (context) => {\n\t\t\tconst storedCandidate: CatalogStoreEntry | undefined = context.stored;\n\t\t\tconst stored = storedCandidate?.sourceBaseUrl === sourceBaseUrl ? storedCandidate : undefined;\n\t\t\tconst restored = remoteModels(stored, localGeneratedAt).filter((model) => model.provider === provider.id);\n\t\t\tif (\n\t\t\t\t!(await context.publish({\n\t\t\t\t\tupdate: () => {\n\t\t\t\t\t\tdynamicModels = restored;\n\t\t\t\t\t},\n\t\t\t\t}))\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!context.allowNetwork || context.signal.aborted) return;\n\t\t\tif (\n\t\t\t\t!context.force &&\n\t\t\t\tstored?.checkedAt !== undefined &&\n\t\t\t\tstored.lastModified !== undefined &&\n\t\t\t\tDate.now() - stored.checkedAt < REMOTE_CATALOG_REFRESH_INTERVAL_MS\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Only revalidate when a cached body backs the validator, so a 304 can never\n\t\t\t// leave the overlay empty.\n\t\t\tconst validator = stored?.models.length ? stored.etag : undefined;\n\t\t\tconst url = new URL(`/api/models/providers/${encodeURIComponent(provider.id)}`, catalogBaseUrl);\n\t\t\tconst response = await fetchWithRetry(url, {\n\t\t\t\theaders: {\n\t\t\t\t\taccept: \"application/json\",\n\t\t\t\t\t\"User-Agent\": getApexCodeUserAgent(VERSION),\n\t\t\t\t\t...(validator ? { \"if-none-match\": validator } : {}),\n\t\t\t\t},\n\t\t\t\tsignal: context.signal,\n\t\t\t});\n\t\t\tif (context.signal.aborted) return;\n\t\t\tconst checkedAt = Date.now();\n\t\t\t// Unchanged: dynamicModels already holds the stored overlay, so only the\n\t\t\t// freshness window moves.\n\t\t\tif (response.status === 304 && stored) {\n\t\t\t\tconst persisted: CatalogStoreEntry = { ...stored, checkedAt, sourceBaseUrl };\n\t\t\t\tawait context.publish({ persist: persisted });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (response.status === 404 || response.status === 501) {\n\t\t\t\tconst persisted: CatalogStoreEntry = {\n\t\t\t\t\t...(stored ?? { models: [] }),\n\t\t\t\t\tcheckedAt,\n\t\t\t\t\tsourceBaseUrl,\n\t\t\t\t\tlastModified: 0,\n\t\t\t\t\tetag: undefined,\n\t\t\t\t};\n\t\t\t\tawait context.publish({ persist: persisted });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!response.ok) {\n\t\t\t\t// Transient failure: the cached body and its validator stay valid, so keep the\n\t\t\t\t// etag and let the next refresh revalidate instead of downloading the catalog.\n\t\t\t\tconst persisted: CatalogStoreEntry = { ...(stored ?? { models: [] }), checkedAt, sourceBaseUrl };\n\t\t\t\tawait context.publish({ persist: persisted });\n\t\t\t\tthrow new Error(`Model catalog request failed for ${provider.id}: ${response.status}`);\n\t\t\t}\n\t\t\tconst refreshed = parseCatalog(provider.id, await response.json());\n\t\t\tconst lastModified = Date.parse(response.headers.get(\"last-modified\") ?? \"\");\n\t\t\tif (context.signal.aborted) return;\n\t\t\tconst entry = {\n\t\t\t\tmodels: refreshed,\n\t\t\t\tsourceBaseUrl,\n\t\t\t\tcheckedAt,\n\t\t\t\tlastModified: Number.isNaN(lastModified) ? 0 : lastModified,\n\t\t\t\tetag: response.headers.get(\"etag\") ?? undefined,\n\t\t\t};\n\t\t\tconst published = remoteModels(entry, localGeneratedAt);\n\t\t\tawait context.publish({\n\t\t\t\tpersist: entry,\n\t\t\t\tupdate: () => {\n\t\t\t\t\tdynamicModels = published;\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
@@ -1,7 +1,6 @@
1
1
  import { VERSION } from "../config.js";
2
+ import { getApexCodeUserAgent } from "../utils/apex-code-user-agent.js";
2
3
  import { fetchWithRetry } from "../utils/management-http.js";
3
- import { getPiUserAgent } from "../utils/pi-user-agent.js";
4
- const DEFAULT_CATALOG_BASE_URL = "https://pi.dev";
5
4
  export const REMOTE_CATALOG_REFRESH_INTERVAL_MS = 4 * 60 * 60 * 1000;
6
5
  function mergeModels(baseline, dynamic) {
7
6
  const merged = [...baseline];
@@ -36,14 +35,16 @@ function remoteModels(entry, localGeneratedAt) {
36
35
  }
37
36
  return entry.models;
38
37
  }
39
- /** Add a persisted pi.dev catalog overlay to a static built-in provider. */
40
- export function withRemoteCatalog(provider, catalogBaseUrl = DEFAULT_CATALOG_BASE_URL, localGeneratedAt) {
38
+ /** Add a persisted configured catalog overlay to a static built-in provider. */
39
+ export function withRemoteCatalog(provider, catalogBaseUrl, localGeneratedAt) {
41
40
  let dynamicModels = [];
41
+ const sourceBaseUrl = new URL(catalogBaseUrl).href;
42
42
  return {
43
43
  ...provider,
44
44
  getModels: () => mergeModels(provider.getModels(), dynamicModels),
45
45
  refreshModels: async (context) => {
46
- const stored = context.stored;
46
+ const storedCandidate = context.stored;
47
+ const stored = storedCandidate?.sourceBaseUrl === sourceBaseUrl ? storedCandidate : undefined;
47
48
  const restored = remoteModels(stored, localGeneratedAt).filter((model) => model.provider === provider.id);
48
49
  if (!(await context.publish({
49
50
  update: () => {
@@ -67,7 +68,7 @@ export function withRemoteCatalog(provider, catalogBaseUrl = DEFAULT_CATALOG_BAS
67
68
  const response = await fetchWithRetry(url, {
68
69
  headers: {
69
70
  accept: "application/json",
70
- "User-Agent": getPiUserAgent(VERSION),
71
+ "User-Agent": getApexCodeUserAgent(VERSION),
71
72
  ...(validator ? { "if-none-match": validator } : {}),
72
73
  },
73
74
  signal: context.signal,
@@ -78,24 +79,26 @@ export function withRemoteCatalog(provider, catalogBaseUrl = DEFAULT_CATALOG_BAS
78
79
  // Unchanged: dynamicModels already holds the stored overlay, so only the
79
80
  // freshness window moves.
80
81
  if (response.status === 304 && stored) {
81
- await context.publish({ persist: { ...stored, checkedAt } });
82
+ const persisted = { ...stored, checkedAt, sourceBaseUrl };
83
+ await context.publish({ persist: persisted });
82
84
  return;
83
85
  }
84
86
  if (response.status === 404 || response.status === 501) {
85
- await context.publish({
86
- persist: {
87
- ...(stored ?? { models: [] }),
88
- checkedAt,
89
- lastModified: 0,
90
- etag: undefined,
91
- },
92
- });
87
+ const persisted = {
88
+ ...(stored ?? { models: [] }),
89
+ checkedAt,
90
+ sourceBaseUrl,
91
+ lastModified: 0,
92
+ etag: undefined,
93
+ };
94
+ await context.publish({ persist: persisted });
93
95
  return;
94
96
  }
95
97
  if (!response.ok) {
96
98
  // Transient failure: the cached body and its validator stay valid, so keep the
97
99
  // etag and let the next refresh revalidate instead of downloading the catalog.
98
- await context.publish({ persist: { ...(stored ?? { models: [] }), checkedAt } });
100
+ const persisted = { ...(stored ?? { models: [] }), checkedAt, sourceBaseUrl };
101
+ await context.publish({ persist: persisted });
99
102
  throw new Error(`Model catalog request failed for ${provider.id}: ${response.status}`);
100
103
  }
101
104
  const refreshed = parseCatalog(provider.id, await response.json());
@@ -104,6 +107,7 @@ export function withRemoteCatalog(provider, catalogBaseUrl = DEFAULT_CATALOG_BAS
104
107
  return;
105
108
  const entry = {
106
109
  models: refreshed,
110
+ sourceBaseUrl,
107
111
  checkedAt,
108
112
  lastModified: Number.isNaN(lastModified) ? 0 : lastModified,
109
113
  etag: response.headers.get("etag") ?? undefined,
@@ -1 +1 @@
1
- {"version":3,"file":"remote-catalog-provider.js","sourceRoot":"","sources":["../../src/core/remote-catalog-provider.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Api, Model, ModelsStoreEntry, Provider } from \"@earendil-works/pi-ai\";\nimport { VERSION } from \"../config.ts\";\nimport { fetchWithRetry } from \"../utils/management-http.ts\";\nimport { getPiUserAgent } from \"../utils/pi-user-agent.ts\";\n\nconst DEFAULT_CATALOG_BASE_URL = \"https://pi.dev\";\nexport const REMOTE_CATALOG_REFRESH_INTERVAL_MS = 4 * 60 * 60 * 1000;\n\nfunction mergeModels(baseline: readonly Model<Api>[], dynamic: readonly Model<Api>[]): Model<Api>[] {\n\tconst merged = [...baseline];\n\tfor (const model of dynamic) {\n\t\tconst index = merged.findIndex((entry) => entry.id === model.id);\n\t\tif (index >= 0) merged[index] = model;\n\t\telse merged.push(model);\n\t}\n\treturn merged;\n}\n\nfunction parseCatalog(providerId: string, value: unknown): Model<Api>[] {\n\tconst entries = Array.isArray(value)\n\t\t? value\n\t\t: typeof value === \"object\" && value !== null && \"models\" in value && Array.isArray(value.models)\n\t\t\t? value.models\n\t\t\t: typeof value === \"object\" && value !== null\n\t\t\t\t? Object.values(value)\n\t\t\t\t: undefined;\n\tif (!entries) throw new Error(`Invalid model catalog for provider \"${providerId}\"`);\n\treturn entries\n\t\t.filter((entry): entry is Model<Api> => typeof entry === \"object\" && entry !== null && \"id\" in entry)\n\t\t.map((model) => ({ ...model, provider: providerId }));\n}\n\nfunction remoteModels(\n\tentry: ModelsStoreEntry | undefined,\n\tlocalGeneratedAt: number | undefined,\n): readonly Model<Api>[] {\n\tif (!entry) return [];\n\tif (localGeneratedAt !== undefined && (entry.lastModified === undefined || entry.lastModified <= localGeneratedAt)) {\n\t\treturn [];\n\t}\n\treturn entry.models;\n}\n\n/** Add a persisted pi.dev catalog overlay to a static built-in provider. */\nexport function withRemoteCatalog(\n\tprovider: Provider,\n\tcatalogBaseUrl: string = DEFAULT_CATALOG_BASE_URL,\n\tlocalGeneratedAt?: number,\n): Provider {\n\tlet dynamicModels: readonly Model<Api>[] = [];\n\n\treturn {\n\t\t...provider,\n\t\tgetModels: () => mergeModels(provider.getModels(), dynamicModels),\n\t\trefreshModels: async (context) => {\n\t\t\tconst stored = context.stored;\n\t\t\tconst restored = remoteModels(stored, localGeneratedAt).filter((model) => model.provider === provider.id);\n\t\t\tif (\n\t\t\t\t!(await context.publish({\n\t\t\t\t\tupdate: () => {\n\t\t\t\t\t\tdynamicModels = restored;\n\t\t\t\t\t},\n\t\t\t\t}))\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!context.allowNetwork || context.signal.aborted) return;\n\t\t\tif (\n\t\t\t\t!context.force &&\n\t\t\t\tstored?.checkedAt !== undefined &&\n\t\t\t\tstored.lastModified !== undefined &&\n\t\t\t\tDate.now() - stored.checkedAt < REMOTE_CATALOG_REFRESH_INTERVAL_MS\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Only revalidate when a cached body backs the validator, so a 304 can never\n\t\t\t// leave the overlay empty.\n\t\t\tconst validator = stored?.models.length ? stored.etag : undefined;\n\t\t\tconst url = new URL(`/api/models/providers/${encodeURIComponent(provider.id)}`, catalogBaseUrl);\n\t\t\tconst response = await fetchWithRetry(url, {\n\t\t\t\theaders: {\n\t\t\t\t\taccept: \"application/json\",\n\t\t\t\t\t\"User-Agent\": getPiUserAgent(VERSION),\n\t\t\t\t\t...(validator ? { \"if-none-match\": validator } : {}),\n\t\t\t\t},\n\t\t\t\tsignal: context.signal,\n\t\t\t});\n\t\t\tif (context.signal.aborted) return;\n\t\t\tconst checkedAt = Date.now();\n\t\t\t// Unchanged: dynamicModels already holds the stored overlay, so only the\n\t\t\t// freshness window moves.\n\t\t\tif (response.status === 304 && stored) {\n\t\t\t\tawait context.publish({ persist: { ...stored, checkedAt } });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (response.status === 404 || response.status === 501) {\n\t\t\t\tawait context.publish({\n\t\t\t\t\tpersist: {\n\t\t\t\t\t\t...(stored ?? { models: [] }),\n\t\t\t\t\t\tcheckedAt,\n\t\t\t\t\t\tlastModified: 0,\n\t\t\t\t\t\tetag: undefined,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!response.ok) {\n\t\t\t\t// Transient failure: the cached body and its validator stay valid, so keep the\n\t\t\t\t// etag and let the next refresh revalidate instead of downloading the catalog.\n\t\t\t\tawait context.publish({ persist: { ...(stored ?? { models: [] }), checkedAt } });\n\t\t\t\tthrow new Error(`Model catalog request failed for ${provider.id}: ${response.status}`);\n\t\t\t}\n\t\t\tconst refreshed = parseCatalog(provider.id, await response.json());\n\t\t\tconst lastModified = Date.parse(response.headers.get(\"last-modified\") ?? \"\");\n\t\t\tif (context.signal.aborted) return;\n\t\t\tconst entry = {\n\t\t\t\tmodels: refreshed,\n\t\t\t\tcheckedAt,\n\t\t\t\tlastModified: Number.isNaN(lastModified) ? 0 : lastModified,\n\t\t\t\tetag: response.headers.get(\"etag\") ?? undefined,\n\t\t\t};\n\t\t\tconst published = remoteModels(entry, localGeneratedAt);\n\t\t\tawait context.publish({\n\t\t\t\tpersist: entry,\n\t\t\t\tupdate: () => {\n\t\t\t\t\tdynamicModels = published;\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"remote-catalog-provider.js","sourceRoot":"","sources":["../../src/core/remote-catalog-provider.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Api, Model, ModelsStoreEntry, Provider } from \"@earendil-works/pi-ai\";\nimport { VERSION } from \"../config.ts\";\nimport { getApexCodeUserAgent } from \"../utils/apex-code-user-agent.ts\";\nimport { fetchWithRetry } from \"../utils/management-http.ts\";\n\nexport const REMOTE_CATALOG_REFRESH_INTERVAL_MS = 4 * 60 * 60 * 1000;\n\nfunction mergeModels(baseline: readonly Model<Api>[], dynamic: readonly Model<Api>[]): Model<Api>[] {\n\tconst merged = [...baseline];\n\tfor (const model of dynamic) {\n\t\tconst index = merged.findIndex((entry) => entry.id === model.id);\n\t\tif (index >= 0) merged[index] = model;\n\t\telse merged.push(model);\n\t}\n\treturn merged;\n}\n\nfunction parseCatalog(providerId: string, value: unknown): Model<Api>[] {\n\tconst entries = Array.isArray(value)\n\t\t? value\n\t\t: typeof value === \"object\" && value !== null && \"models\" in value && Array.isArray(value.models)\n\t\t\t? value.models\n\t\t\t: typeof value === \"object\" && value !== null\n\t\t\t\t? Object.values(value)\n\t\t\t\t: undefined;\n\tif (!entries) throw new Error(`Invalid model catalog for provider \"${providerId}\"`);\n\treturn entries\n\t\t.filter((entry): entry is Model<Api> => typeof entry === \"object\" && entry !== null && \"id\" in entry)\n\t\t.map((model) => ({ ...model, provider: providerId }));\n}\n\nfunction remoteModels(\n\tentry: ModelsStoreEntry | undefined,\n\tlocalGeneratedAt: number | undefined,\n): readonly Model<Api>[] {\n\tif (!entry) return [];\n\tif (localGeneratedAt !== undefined && (entry.lastModified === undefined || entry.lastModified <= localGeneratedAt)) {\n\t\treturn [];\n\t}\n\treturn entry.models;\n}\n\ntype CatalogStoreEntry = ModelsStoreEntry & { sourceBaseUrl?: string };\n\n/** Add a persisted configured catalog overlay to a static built-in provider. */\nexport function withRemoteCatalog(provider: Provider, catalogBaseUrl: string, localGeneratedAt?: number): Provider {\n\tlet dynamicModels: readonly Model<Api>[] = [];\n\tconst sourceBaseUrl = new URL(catalogBaseUrl).href;\n\n\treturn {\n\t\t...provider,\n\t\tgetModels: () => mergeModels(provider.getModels(), dynamicModels),\n\t\trefreshModels: async (context) => {\n\t\t\tconst storedCandidate: CatalogStoreEntry | undefined = context.stored;\n\t\t\tconst stored = storedCandidate?.sourceBaseUrl === sourceBaseUrl ? storedCandidate : undefined;\n\t\t\tconst restored = remoteModels(stored, localGeneratedAt).filter((model) => model.provider === provider.id);\n\t\t\tif (\n\t\t\t\t!(await context.publish({\n\t\t\t\t\tupdate: () => {\n\t\t\t\t\t\tdynamicModels = restored;\n\t\t\t\t\t},\n\t\t\t\t}))\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!context.allowNetwork || context.signal.aborted) return;\n\t\t\tif (\n\t\t\t\t!context.force &&\n\t\t\t\tstored?.checkedAt !== undefined &&\n\t\t\t\tstored.lastModified !== undefined &&\n\t\t\t\tDate.now() - stored.checkedAt < REMOTE_CATALOG_REFRESH_INTERVAL_MS\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Only revalidate when a cached body backs the validator, so a 304 can never\n\t\t\t// leave the overlay empty.\n\t\t\tconst validator = stored?.models.length ? stored.etag : undefined;\n\t\t\tconst url = new URL(`/api/models/providers/${encodeURIComponent(provider.id)}`, catalogBaseUrl);\n\t\t\tconst response = await fetchWithRetry(url, {\n\t\t\t\theaders: {\n\t\t\t\t\taccept: \"application/json\",\n\t\t\t\t\t\"User-Agent\": getApexCodeUserAgent(VERSION),\n\t\t\t\t\t...(validator ? { \"if-none-match\": validator } : {}),\n\t\t\t\t},\n\t\t\t\tsignal: context.signal,\n\t\t\t});\n\t\t\tif (context.signal.aborted) return;\n\t\t\tconst checkedAt = Date.now();\n\t\t\t// Unchanged: dynamicModels already holds the stored overlay, so only the\n\t\t\t// freshness window moves.\n\t\t\tif (response.status === 304 && stored) {\n\t\t\t\tconst persisted: CatalogStoreEntry = { ...stored, checkedAt, sourceBaseUrl };\n\t\t\t\tawait context.publish({ persist: persisted });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (response.status === 404 || response.status === 501) {\n\t\t\t\tconst persisted: CatalogStoreEntry = {\n\t\t\t\t\t...(stored ?? { models: [] }),\n\t\t\t\t\tcheckedAt,\n\t\t\t\t\tsourceBaseUrl,\n\t\t\t\t\tlastModified: 0,\n\t\t\t\t\tetag: undefined,\n\t\t\t\t};\n\t\t\t\tawait context.publish({ persist: persisted });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!response.ok) {\n\t\t\t\t// Transient failure: the cached body and its validator stay valid, so keep the\n\t\t\t\t// etag and let the next refresh revalidate instead of downloading the catalog.\n\t\t\t\tconst persisted: CatalogStoreEntry = { ...(stored ?? { models: [] }), checkedAt, sourceBaseUrl };\n\t\t\t\tawait context.publish({ persist: persisted });\n\t\t\t\tthrow new Error(`Model catalog request failed for ${provider.id}: ${response.status}`);\n\t\t\t}\n\t\t\tconst refreshed = parseCatalog(provider.id, await response.json());\n\t\t\tconst lastModified = Date.parse(response.headers.get(\"last-modified\") ?? \"\");\n\t\t\tif (context.signal.aborted) return;\n\t\t\tconst entry = {\n\t\t\t\tmodels: refreshed,\n\t\t\t\tsourceBaseUrl,\n\t\t\t\tcheckedAt,\n\t\t\t\tlastModified: Number.isNaN(lastModified) ? 0 : lastModified,\n\t\t\t\tetag: response.headers.get(\"etag\") ?? undefined,\n\t\t\t};\n\t\t\tconst published = remoteModels(entry, localGeneratedAt);\n\t\t\tawait context.publish({\n\t\t\t\tpersist: entry,\n\t\t\t\tupdate: () => {\n\t\t\t\t\tdynamicModels = published;\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=child-entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"child-entry.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/child-entry.ts"],"names":[],"mappings":"","sourcesContent":["import { APP_NAME } from \"../../config.ts\";\nimport { main } from \"../../main.ts\";\nimport { setApexEnvironment } from \"../environment.ts\";\nimport { configureHttpDispatcher } from \"../http-dispatcher.ts\";\n\nprocess.title = APP_NAME;\nsetApexEnvironment(\"APEX_CODE_CODING_AGENT\", \"true\");\nprocess.env.AI_AGENT = \"apex-code\";\nprocess.emitWarning = (() => {}) as typeof process.emitWarning;\nconfigureHttpDispatcher();\n\nawait main(process.argv.slice(2));\n"]}
@@ -0,0 +1,11 @@
1
+ import { APP_NAME } from "../../config.js";
2
+ import { main } from "../../main.js";
3
+ import { setApexEnvironment } from "../environment.js";
4
+ import { configureHttpDispatcher } from "../http-dispatcher.js";
5
+ process.title = APP_NAME;
6
+ setApexEnvironment("APEX_CODE_CODING_AGENT", "true");
7
+ process.env.AI_AGENT = "apex-code";
8
+ process.emitWarning = (() => { });
9
+ configureHttpDispatcher();
10
+ await main(process.argv.slice(2));
11
+ //# sourceMappingURL=child-entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"child-entry.js","sourceRoot":"","sources":["../../../src/core/sandbox/child-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC;AACzB,kBAAkB,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAAC;AACrD,OAAO,CAAC,GAAG,CAAC,QAAQ,GAAG,WAAW,CAAC;AACnC,OAAO,CAAC,WAAW,GAAG,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAA+B,CAAC;AAC/D,uBAAuB,EAAE,CAAC;AAE1B,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC","sourcesContent":["import { APP_NAME } from \"../../config.ts\";\nimport { main } from \"../../main.ts\";\nimport { setApexEnvironment } from \"../environment.ts\";\nimport { configureHttpDispatcher } from \"../http-dispatcher.ts\";\n\nprocess.title = APP_NAME;\nsetApexEnvironment(\"APEX_CODE_CODING_AGENT\", \"true\");\nprocess.env.AI_AGENT = \"apex-code\";\nprocess.emitWarning = (() => {}) as typeof process.emitWarning;\nconfigureHttpDispatcher();\n\nawait main(process.argv.slice(2));\n"]}
@@ -0,0 +1,27 @@
1
+ import type { SandboxLaunch } from "./supervisor.ts";
2
+ /**
3
+ * OS containment is the normal startup path for every command that can construct an
4
+ * agent session. Commands that only inspect or maintain host configuration do not
5
+ * create a runtime and therefore remain outside this child boundary.
6
+ */
7
+ export declare function requiresSandboxedChild(args: readonly string[]): boolean;
8
+ export interface SandboxedCliLaunch extends SandboxLaunch {
9
+ readonly environment: NodeJS.ProcessEnv;
10
+ }
11
+ /** Read supervisor policy only from global settings; project settings are untrusted here. */
12
+ export declare function resolveSupervisorAllowedHosts(cwd: string, agentDir: string): readonly string[] | undefined;
13
+ /**
14
+ * Allocate agent-owned state under the sole writable workspace mount. The child does
15
+ * not inherit a host home or a host session/config directory, preventing the sandbox
16
+ * from presenting a write boundary while its own state quietly escapes it.
17
+ */
18
+ export declare function buildSandboxedCliLaunch(options: {
19
+ workspace: string;
20
+ command: string;
21
+ args: readonly string[];
22
+ environment: NodeJS.ProcessEnv;
23
+ allowedHosts?: readonly string[];
24
+ readOnlyPaths?: readonly string[];
25
+ authPath?: string;
26
+ }): SandboxedCliLaunch;
27
+ //# sourceMappingURL=cli-launch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-launch.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/cli-launch.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAKrD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAKvE;AAED,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACxD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;CACxC;AAED,6FAA6F;AAC7F,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAE1G;AAgFD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;IAC/B,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,kBAAkB,CAgCrB","sourcesContent":["import { mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { SettingsManager } from \"../settings-manager.ts\";\nimport type { SandboxLaunch } from \"./supervisor.ts\";\n\nconst NON_SESSION_COMMANDS = new Set([\"auth\", \"config\", \"install\", \"remove\", \"uninstall\", \"update\", \"list\"]);\nconst METADATA_FLAGS = new Set([\"--version\", \"-v\", \"--export\", \"--list-models\"]);\n\n/**\n * OS containment is the normal startup path for every command that can construct an\n * agent session. Commands that only inspect or maintain host configuration do not\n * create a runtime and therefore remain outside this child boundary.\n */\nexport function requiresSandboxedChild(args: readonly string[]): boolean {\n\tif (NON_SESSION_COMMANDS.has(args[0] ?? \"\")) return false;\n\tif (args.some((argument) => METADATA_FLAGS.has(argument))) return false;\n\tif (args.includes(\"--help\") || args.includes(\"-h\")) return false;\n\treturn true;\n}\n\nexport interface SandboxedCliLaunch extends SandboxLaunch {\n\treadonly environment: NodeJS.ProcessEnv;\n}\n\n/** Read supervisor policy only from global settings; project settings are untrusted here. */\nexport function resolveSupervisorAllowedHosts(cwd: string, agentDir: string): readonly string[] | undefined {\n\treturn SettingsManager.create(cwd, agentDir, { projectTrusted: false }).getNetworkSettings()?.allowedHosts;\n}\n\nconst SAFE_CHILD_ENVIRONMENT_KEYS = new Set([\n\t\"PATH\",\n\t\"LANG\",\n\t\"LC_ALL\",\n\t\"LC_CTYPE\",\n\t\"TERM\",\n\t\"COLORTERM\",\n\t\"NO_COLOR\",\n\t\"FORCE_COLOR\",\n\t\"TSX_TSCONFIG_PATH\",\n\t\"APEX_CODE_OFFLINE\",\n\t\"APEX_CODE_SKIP_VERSION_CHECK\",\n\t\"APEX_CODE_EXPERIMENTAL\",\n\t\"APEX_CODE_STARTUP_BENCHMARK\",\n\t\"APEX_CODE_TIMING\",\n\t\"APEX_CODE_CLEAR_ON_SHRINK\",\n\t\"APEX_CODE_HARDWARE_CURSOR\",\n\t\"APEX_CODE_MODEL_CATALOG_URL\",\n\t\"APEX_CODE_SHARE_VIEWER_URL\",\n\t\"VISUAL\",\n\t\"EDITOR\",\n]);\n\n// Provider API keys are explicit credential inputs, unlike arbitrary ambient variables.\n// Keep this list in sync with the documented provider environment-variable reference.\nconst SAFE_PROVIDER_CREDENTIAL_KEYS = new Set([\n\t\"ANTHROPIC_API_KEY\",\n\t\"ANTHROPIC_AUTH_TOKEN\",\n\t\"ANTHROPIC_OAUTH_TOKEN\",\n\t\"ANT_LING_API_KEY\",\n\t\"OPENAI_API_KEY\",\n\t\"AZURE_OPENAI_API_KEY\",\n\t\"AZURE_OPENAI_BASE_URL\",\n\t\"AZURE_OPENAI_RESOURCE_NAME\",\n\t\"AZURE_OPENAI_API_VERSION\",\n\t\"AZURE_OPENAI_DEPLOYMENT_NAME_MAP\",\n\t\"DEEPSEEK_API_KEY\",\n\t\"NVIDIA_API_KEY\",\n\t\"GEMINI_API_KEY\",\n\t\"GROQ_API_KEY\",\n\t\"CEREBRAS_API_KEY\",\n\t\"XAI_API_KEY\",\n\t\"FIREWORKS_API_KEY\",\n\t\"TOGETHER_API_KEY\",\n\t\"BASETEN_API_KEY\",\n\t\"OPENROUTER_API_KEY\",\n\t\"AI_GATEWAY_API_KEY\",\n\t\"ZAI_API_KEY\",\n\t\"ZAI_CODING_CN_API_KEY\",\n\t\"MISTRAL_API_KEY\",\n\t\"MINIMAX_API_KEY\",\n\t\"MOONSHOT_API_KEY\",\n\t\"OPENCODE_API_KEY\",\n\t\"KIMI_API_KEY\",\n\t\"CLOUDFLARE_API_KEY\",\n\t\"CLOUDFLARE_ACCOUNT_ID\",\n\t\"CLOUDFLARE_GATEWAY_ID\",\n\t\"QWEN_TOKEN_PLAN_API_KEY\",\n\t\"QWEN_TOKEN_PLAN_CN_API_KEY\",\n\t\"XIAOMI_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_CN_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_AMS_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_SGP_API_KEY\",\n\t\"AWS_PROFILE\",\n\t\"AWS_ACCESS_KEY_ID\",\n\t\"AWS_SECRET_ACCESS_KEY\",\n\t\"AWS_BEARER_TOKEN_BEDROCK\",\n\t\"AWS_REGION\",\n]);\n\nfunction buildChildEnvironment(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n\treturn Object.fromEntries(\n\t\tObject.entries(environment).filter(\n\t\t\t([key]) => SAFE_CHILD_ENVIRONMENT_KEYS.has(key) || SAFE_PROVIDER_CREDENTIAL_KEYS.has(key),\n\t\t),\n\t);\n}\n\n/**\n * Allocate agent-owned state under the sole writable workspace mount. The child does\n * not inherit a host home or a host session/config directory, preventing the sandbox\n * from presenting a write boundary while its own state quietly escapes it.\n */\nexport function buildSandboxedCliLaunch(options: {\n\tworkspace: string;\n\tcommand: string;\n\targs: readonly string[];\n\tenvironment: NodeJS.ProcessEnv;\n\tallowedHosts?: readonly string[];\n\treadOnlyPaths?: readonly string[];\n\tauthPath?: string;\n}): SandboxedCliLaunch {\n\tconst stateDirectory = join(options.workspace, \".apex-code\", \"sandbox-state\");\n\tconst agentDirectory = join(options.workspace, \".apex-code\", \"sandbox-agent\");\n\tconst sessionDirectory = join(options.workspace, \".apex-code\", \"sandbox-sessions\");\n\tconst xdgDirectories = {\n\t\tXDG_CONFIG_HOME: join(stateDirectory, \"config\"),\n\t\tXDG_CACHE_HOME: join(stateDirectory, \"cache\"),\n\t\tXDG_DATA_HOME: join(stateDirectory, \"data\"),\n\t\tXDG_STATE_HOME: join(stateDirectory, \"state\"),\n\t};\n\tfor (const directory of [stateDirectory, agentDirectory, sessionDirectory, ...Object.values(xdgDirectories)]) {\n\t\tmkdirSync(directory, { recursive: true });\n\t}\n\tconst childEnvironment = buildChildEnvironment(options.environment);\n\tconst readOnlyPaths = [...(options.readOnlyPaths ?? [])];\n\tconst readOnlyFiles = options.authPath ? [options.authPath] : [];\n\treturn {\n\t\tcommand: options.command,\n\t\targs: [...options.args],\n\t\tpolicy: { workspace: options.workspace, allowedHosts: options.allowedHosts ?? [] },\n\t\treadOnlyPaths,\n\t\treadOnlyFiles,\n\t\tenvironment: {\n\t\t\t...childEnvironment,\n\t\t\t...(options.authPath ? { APEX_CODE_AUTH_PATH: options.authPath } : {}),\n\t\t\tAPEX_CODE_CODING_AGENT_DIR: agentDirectory,\n\t\t\tAPEX_CODE_CODING_AGENT_SESSION_DIR: sessionDirectory,\n\t\t\tHOME: stateDirectory,\n\t\t\tTMPDIR: stateDirectory,\n\t\t\t...xdgDirectories,\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,133 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { SettingsManager } from "../settings-manager.js";
4
+ const NON_SESSION_COMMANDS = new Set(["auth", "config", "install", "remove", "uninstall", "update", "list"]);
5
+ const METADATA_FLAGS = new Set(["--version", "-v", "--export", "--list-models"]);
6
+ /**
7
+ * OS containment is the normal startup path for every command that can construct an
8
+ * agent session. Commands that only inspect or maintain host configuration do not
9
+ * create a runtime and therefore remain outside this child boundary.
10
+ */
11
+ export function requiresSandboxedChild(args) {
12
+ if (NON_SESSION_COMMANDS.has(args[0] ?? ""))
13
+ return false;
14
+ if (args.some((argument) => METADATA_FLAGS.has(argument)))
15
+ return false;
16
+ if (args.includes("--help") || args.includes("-h"))
17
+ return false;
18
+ return true;
19
+ }
20
+ /** Read supervisor policy only from global settings; project settings are untrusted here. */
21
+ export function resolveSupervisorAllowedHosts(cwd, agentDir) {
22
+ return SettingsManager.create(cwd, agentDir, { projectTrusted: false }).getNetworkSettings()?.allowedHosts;
23
+ }
24
+ const SAFE_CHILD_ENVIRONMENT_KEYS = new Set([
25
+ "PATH",
26
+ "LANG",
27
+ "LC_ALL",
28
+ "LC_CTYPE",
29
+ "TERM",
30
+ "COLORTERM",
31
+ "NO_COLOR",
32
+ "FORCE_COLOR",
33
+ "TSX_TSCONFIG_PATH",
34
+ "APEX_CODE_OFFLINE",
35
+ "APEX_CODE_SKIP_VERSION_CHECK",
36
+ "APEX_CODE_EXPERIMENTAL",
37
+ "APEX_CODE_STARTUP_BENCHMARK",
38
+ "APEX_CODE_TIMING",
39
+ "APEX_CODE_CLEAR_ON_SHRINK",
40
+ "APEX_CODE_HARDWARE_CURSOR",
41
+ "APEX_CODE_MODEL_CATALOG_URL",
42
+ "APEX_CODE_SHARE_VIEWER_URL",
43
+ "VISUAL",
44
+ "EDITOR",
45
+ ]);
46
+ // Provider API keys are explicit credential inputs, unlike arbitrary ambient variables.
47
+ // Keep this list in sync with the documented provider environment-variable reference.
48
+ const SAFE_PROVIDER_CREDENTIAL_KEYS = new Set([
49
+ "ANTHROPIC_API_KEY",
50
+ "ANTHROPIC_AUTH_TOKEN",
51
+ "ANTHROPIC_OAUTH_TOKEN",
52
+ "ANT_LING_API_KEY",
53
+ "OPENAI_API_KEY",
54
+ "AZURE_OPENAI_API_KEY",
55
+ "AZURE_OPENAI_BASE_URL",
56
+ "AZURE_OPENAI_RESOURCE_NAME",
57
+ "AZURE_OPENAI_API_VERSION",
58
+ "AZURE_OPENAI_DEPLOYMENT_NAME_MAP",
59
+ "DEEPSEEK_API_KEY",
60
+ "NVIDIA_API_KEY",
61
+ "GEMINI_API_KEY",
62
+ "GROQ_API_KEY",
63
+ "CEREBRAS_API_KEY",
64
+ "XAI_API_KEY",
65
+ "FIREWORKS_API_KEY",
66
+ "TOGETHER_API_KEY",
67
+ "BASETEN_API_KEY",
68
+ "OPENROUTER_API_KEY",
69
+ "AI_GATEWAY_API_KEY",
70
+ "ZAI_API_KEY",
71
+ "ZAI_CODING_CN_API_KEY",
72
+ "MISTRAL_API_KEY",
73
+ "MINIMAX_API_KEY",
74
+ "MOONSHOT_API_KEY",
75
+ "OPENCODE_API_KEY",
76
+ "KIMI_API_KEY",
77
+ "CLOUDFLARE_API_KEY",
78
+ "CLOUDFLARE_ACCOUNT_ID",
79
+ "CLOUDFLARE_GATEWAY_ID",
80
+ "QWEN_TOKEN_PLAN_API_KEY",
81
+ "QWEN_TOKEN_PLAN_CN_API_KEY",
82
+ "XIAOMI_API_KEY",
83
+ "XIAOMI_TOKEN_PLAN_CN_API_KEY",
84
+ "XIAOMI_TOKEN_PLAN_AMS_API_KEY",
85
+ "XIAOMI_TOKEN_PLAN_SGP_API_KEY",
86
+ "AWS_PROFILE",
87
+ "AWS_ACCESS_KEY_ID",
88
+ "AWS_SECRET_ACCESS_KEY",
89
+ "AWS_BEARER_TOKEN_BEDROCK",
90
+ "AWS_REGION",
91
+ ]);
92
+ function buildChildEnvironment(environment) {
93
+ return Object.fromEntries(Object.entries(environment).filter(([key]) => SAFE_CHILD_ENVIRONMENT_KEYS.has(key) || SAFE_PROVIDER_CREDENTIAL_KEYS.has(key)));
94
+ }
95
+ /**
96
+ * Allocate agent-owned state under the sole writable workspace mount. The child does
97
+ * not inherit a host home or a host session/config directory, preventing the sandbox
98
+ * from presenting a write boundary while its own state quietly escapes it.
99
+ */
100
+ export function buildSandboxedCliLaunch(options) {
101
+ const stateDirectory = join(options.workspace, ".apex-code", "sandbox-state");
102
+ const agentDirectory = join(options.workspace, ".apex-code", "sandbox-agent");
103
+ const sessionDirectory = join(options.workspace, ".apex-code", "sandbox-sessions");
104
+ const xdgDirectories = {
105
+ XDG_CONFIG_HOME: join(stateDirectory, "config"),
106
+ XDG_CACHE_HOME: join(stateDirectory, "cache"),
107
+ XDG_DATA_HOME: join(stateDirectory, "data"),
108
+ XDG_STATE_HOME: join(stateDirectory, "state"),
109
+ };
110
+ for (const directory of [stateDirectory, agentDirectory, sessionDirectory, ...Object.values(xdgDirectories)]) {
111
+ mkdirSync(directory, { recursive: true });
112
+ }
113
+ const childEnvironment = buildChildEnvironment(options.environment);
114
+ const readOnlyPaths = [...(options.readOnlyPaths ?? [])];
115
+ const readOnlyFiles = options.authPath ? [options.authPath] : [];
116
+ return {
117
+ command: options.command,
118
+ args: [...options.args],
119
+ policy: { workspace: options.workspace, allowedHosts: options.allowedHosts ?? [] },
120
+ readOnlyPaths,
121
+ readOnlyFiles,
122
+ environment: {
123
+ ...childEnvironment,
124
+ ...(options.authPath ? { APEX_CODE_AUTH_PATH: options.authPath } : {}),
125
+ APEX_CODE_CODING_AGENT_DIR: agentDirectory,
126
+ APEX_CODE_CODING_AGENT_SESSION_DIR: sessionDirectory,
127
+ HOME: stateDirectory,
128
+ TMPDIR: stateDirectory,
129
+ ...xdgDirectories,
130
+ },
131
+ };
132
+ }
133
+ //# sourceMappingURL=cli-launch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-launch.js","sourceRoot":"","sources":["../../../src/core/sandbox/cli-launch.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { SettingsManager } from \"../settings-manager.ts\";\nimport type { SandboxLaunch } from \"./supervisor.ts\";\n\nconst NON_SESSION_COMMANDS = new Set([\"auth\", \"config\", \"install\", \"remove\", \"uninstall\", \"update\", \"list\"]);\nconst METADATA_FLAGS = new Set([\"--version\", \"-v\", \"--export\", \"--list-models\"]);\n\n/**\n * OS containment is the normal startup path for every command that can construct an\n * agent session. Commands that only inspect or maintain host configuration do not\n * create a runtime and therefore remain outside this child boundary.\n */\nexport function requiresSandboxedChild(args: readonly string[]): boolean {\n\tif (NON_SESSION_COMMANDS.has(args[0] ?? \"\")) return false;\n\tif (args.some((argument) => METADATA_FLAGS.has(argument))) return false;\n\tif (args.includes(\"--help\") || args.includes(\"-h\")) return false;\n\treturn true;\n}\n\nexport interface SandboxedCliLaunch extends SandboxLaunch {\n\treadonly environment: NodeJS.ProcessEnv;\n}\n\n/** Read supervisor policy only from global settings; project settings are untrusted here. */\nexport function resolveSupervisorAllowedHosts(cwd: string, agentDir: string): readonly string[] | undefined {\n\treturn SettingsManager.create(cwd, agentDir, { projectTrusted: false }).getNetworkSettings()?.allowedHosts;\n}\n\nconst SAFE_CHILD_ENVIRONMENT_KEYS = new Set([\n\t\"PATH\",\n\t\"LANG\",\n\t\"LC_ALL\",\n\t\"LC_CTYPE\",\n\t\"TERM\",\n\t\"COLORTERM\",\n\t\"NO_COLOR\",\n\t\"FORCE_COLOR\",\n\t\"TSX_TSCONFIG_PATH\",\n\t\"APEX_CODE_OFFLINE\",\n\t\"APEX_CODE_SKIP_VERSION_CHECK\",\n\t\"APEX_CODE_EXPERIMENTAL\",\n\t\"APEX_CODE_STARTUP_BENCHMARK\",\n\t\"APEX_CODE_TIMING\",\n\t\"APEX_CODE_CLEAR_ON_SHRINK\",\n\t\"APEX_CODE_HARDWARE_CURSOR\",\n\t\"APEX_CODE_MODEL_CATALOG_URL\",\n\t\"APEX_CODE_SHARE_VIEWER_URL\",\n\t\"VISUAL\",\n\t\"EDITOR\",\n]);\n\n// Provider API keys are explicit credential inputs, unlike arbitrary ambient variables.\n// Keep this list in sync with the documented provider environment-variable reference.\nconst SAFE_PROVIDER_CREDENTIAL_KEYS = new Set([\n\t\"ANTHROPIC_API_KEY\",\n\t\"ANTHROPIC_AUTH_TOKEN\",\n\t\"ANTHROPIC_OAUTH_TOKEN\",\n\t\"ANT_LING_API_KEY\",\n\t\"OPENAI_API_KEY\",\n\t\"AZURE_OPENAI_API_KEY\",\n\t\"AZURE_OPENAI_BASE_URL\",\n\t\"AZURE_OPENAI_RESOURCE_NAME\",\n\t\"AZURE_OPENAI_API_VERSION\",\n\t\"AZURE_OPENAI_DEPLOYMENT_NAME_MAP\",\n\t\"DEEPSEEK_API_KEY\",\n\t\"NVIDIA_API_KEY\",\n\t\"GEMINI_API_KEY\",\n\t\"GROQ_API_KEY\",\n\t\"CEREBRAS_API_KEY\",\n\t\"XAI_API_KEY\",\n\t\"FIREWORKS_API_KEY\",\n\t\"TOGETHER_API_KEY\",\n\t\"BASETEN_API_KEY\",\n\t\"OPENROUTER_API_KEY\",\n\t\"AI_GATEWAY_API_KEY\",\n\t\"ZAI_API_KEY\",\n\t\"ZAI_CODING_CN_API_KEY\",\n\t\"MISTRAL_API_KEY\",\n\t\"MINIMAX_API_KEY\",\n\t\"MOONSHOT_API_KEY\",\n\t\"OPENCODE_API_KEY\",\n\t\"KIMI_API_KEY\",\n\t\"CLOUDFLARE_API_KEY\",\n\t\"CLOUDFLARE_ACCOUNT_ID\",\n\t\"CLOUDFLARE_GATEWAY_ID\",\n\t\"QWEN_TOKEN_PLAN_API_KEY\",\n\t\"QWEN_TOKEN_PLAN_CN_API_KEY\",\n\t\"XIAOMI_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_CN_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_AMS_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_SGP_API_KEY\",\n\t\"AWS_PROFILE\",\n\t\"AWS_ACCESS_KEY_ID\",\n\t\"AWS_SECRET_ACCESS_KEY\",\n\t\"AWS_BEARER_TOKEN_BEDROCK\",\n\t\"AWS_REGION\",\n]);\n\nfunction buildChildEnvironment(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n\treturn Object.fromEntries(\n\t\tObject.entries(environment).filter(\n\t\t\t([key]) => SAFE_CHILD_ENVIRONMENT_KEYS.has(key) || SAFE_PROVIDER_CREDENTIAL_KEYS.has(key),\n\t\t),\n\t);\n}\n\n/**\n * Allocate agent-owned state under the sole writable workspace mount. The child does\n * not inherit a host home or a host session/config directory, preventing the sandbox\n * from presenting a write boundary while its own state quietly escapes it.\n */\nexport function buildSandboxedCliLaunch(options: {\n\tworkspace: string;\n\tcommand: string;\n\targs: readonly string[];\n\tenvironment: NodeJS.ProcessEnv;\n\tallowedHosts?: readonly string[];\n\treadOnlyPaths?: readonly string[];\n\tauthPath?: string;\n}): SandboxedCliLaunch {\n\tconst stateDirectory = join(options.workspace, \".apex-code\", \"sandbox-state\");\n\tconst agentDirectory = join(options.workspace, \".apex-code\", \"sandbox-agent\");\n\tconst sessionDirectory = join(options.workspace, \".apex-code\", \"sandbox-sessions\");\n\tconst xdgDirectories = {\n\t\tXDG_CONFIG_HOME: join(stateDirectory, \"config\"),\n\t\tXDG_CACHE_HOME: join(stateDirectory, \"cache\"),\n\t\tXDG_DATA_HOME: join(stateDirectory, \"data\"),\n\t\tXDG_STATE_HOME: join(stateDirectory, \"state\"),\n\t};\n\tfor (const directory of [stateDirectory, agentDirectory, sessionDirectory, ...Object.values(xdgDirectories)]) {\n\t\tmkdirSync(directory, { recursive: true });\n\t}\n\tconst childEnvironment = buildChildEnvironment(options.environment);\n\tconst readOnlyPaths = [...(options.readOnlyPaths ?? [])];\n\tconst readOnlyFiles = options.authPath ? [options.authPath] : [];\n\treturn {\n\t\tcommand: options.command,\n\t\targs: [...options.args],\n\t\tpolicy: { workspace: options.workspace, allowedHosts: options.allowedHosts ?? [] },\n\t\treadOnlyPaths,\n\t\treadOnlyFiles,\n\t\tenvironment: {\n\t\t\t...childEnvironment,\n\t\t\t...(options.authPath ? { APEX_CODE_AUTH_PATH: options.authPath } : {}),\n\t\t\tAPEX_CODE_CODING_AGENT_DIR: agentDirectory,\n\t\t\tAPEX_CODE_CODING_AGENT_SESSION_DIR: sessionDirectory,\n\t\t\tHOME: stateDirectory,\n\t\t\tTMPDIR: stateDirectory,\n\t\t\t...xdgDirectories,\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import { type SandboxBackend } from "./supervisor.ts";
2
+ import { SandboxViolationStore } from "./violations.ts";
3
+ export interface CliSandboxDependencies {
4
+ createBackend: (options: {
5
+ violationStore: SandboxViolationStore;
6
+ }) => SandboxBackend;
7
+ stderr: {
8
+ write(message: string): boolean;
9
+ };
10
+ }
11
+ /**
12
+ * Start a normal CLI runtime beneath the whole-process boundary. The caller returns
13
+ * the result directly so the outer process never continues into `main()` afterward.
14
+ */
15
+ export declare function launchSandboxedCli(options: {
16
+ command: string;
17
+ args: readonly string[];
18
+ environment: NodeJS.ProcessEnv;
19
+ workspace: string;
20
+ allowedHosts?: readonly string[];
21
+ readOnlyPaths?: readonly string[];
22
+ authPath?: string;
23
+ dependencies?: Partial<CliSandboxDependencies>;
24
+ }): Promise<number>;
25
+ //# sourceMappingURL=cli-supervisor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-supervisor.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/cli-supervisor.ts"],"names":[],"mappings":"AAIA,OAAO,EAA2B,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,WAAW,sBAAsB;IACtC,aAAa,EAAE,CAAC,OAAO,EAAE;QAAE,cAAc,EAAE,qBAAqB,CAAA;KAAE,KAAK,cAAc,CAAC;IACtF,MAAM,EAAE;QAAE,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;CAC5C;AAeD;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;CAC/C,GAAG,OAAO,CAAC,MAAM,CAAC,CAiClB","sourcesContent":["import { buildSandboxedCliLaunch } from \"./cli-launch.ts\";\nimport { createLinuxSandboxBackend } from \"./linux-backend.ts\";\nimport { createMacosSandboxBackend } from \"./macos-backend.ts\";\nimport { createSandboxPolicy } from \"./policy.ts\";\nimport { createSandboxSupervisor, type SandboxBackend } from \"./supervisor.ts\";\nimport { SandboxViolationStore } from \"./violations.ts\";\n\nexport interface CliSandboxDependencies {\n\tcreateBackend: (options: { violationStore: SandboxViolationStore }) => SandboxBackend;\n\tstderr: { write(message: string): boolean };\n}\n\n/** Routes to the platform adapter for the running OS; each adapter self-reports\n * `unavailable` on any other platform, so this only needs to pick the one whose\n * enforcement is actually reachable here. */\nfunction createDefaultSandboxBackend(options: { violationStore: SandboxViolationStore }): SandboxBackend {\n\tif (process.platform === \"darwin\") return createMacosSandboxBackend(options);\n\treturn createLinuxSandboxBackend(options);\n}\n\nconst defaultDependencies: CliSandboxDependencies = {\n\tcreateBackend: createDefaultSandboxBackend,\n\tstderr: process.stderr,\n};\n\n/**\n * Start a normal CLI runtime beneath the whole-process boundary. The caller returns\n * the result directly so the outer process never continues into `main()` afterward.\n */\nexport async function launchSandboxedCli(options: {\n\tcommand: string;\n\targs: readonly string[];\n\tenvironment: NodeJS.ProcessEnv;\n\tworkspace: string;\n\tallowedHosts?: readonly string[];\n\treadOnlyPaths?: readonly string[];\n\tauthPath?: string;\n\tdependencies?: Partial<CliSandboxDependencies>;\n}): Promise<number> {\n\tconst dependencies = { ...defaultDependencies, ...options.dependencies };\n\tconst policyResult = createSandboxPolicy({ workspace: options.workspace, allowedHosts: options.allowedHosts });\n\tif (policyResult.kind === \"invalid\") {\n\t\tdependencies.stderr.write(`Error: OS sandbox is not enforcing this agent session: ${policyResult.reason}\\n`);\n\t\treturn 1;\n\t}\n\tconst violationStore = new SandboxViolationStore();\n\tconst backend = dependencies.createBackend({ violationStore });\n\tconst supervisor = createSandboxSupervisor({ backend, policy: policyResult.policy });\n\tconst launch = buildSandboxedCliLaunch({\n\t\tworkspace: policyResult.policy.workspace,\n\t\tcommand: options.command,\n\t\targs: options.args,\n\t\tenvironment: options.environment,\n\t\tallowedHosts: options.allowedHosts,\n\t\treadOnlyPaths: options.readOnlyPaths,\n\t\tauthPath: options.authPath,\n\t});\n\ttry {\n\t\treturn await supervisor.launch(launch);\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : \"Failed to start OS sandbox.\";\n\t\tdependencies.stderr.write(`Error: ${message}\\n`);\n\t\treturn 1;\n\t} finally {\n\t\tfor (const violation of violationStore.list()) {\n\t\t\tdependencies.stderr.write(\n\t\t\t\t`Sandbox violation (${violation.kind}): ${violation.command} — ${violation.detail}\\n`,\n\t\t\t);\n\t\t}\n\t\tawait supervisor.close();\n\t}\n}\n"]}
@@ -0,0 +1,57 @@
1
+ import { buildSandboxedCliLaunch } from "./cli-launch.js";
2
+ import { createLinuxSandboxBackend } from "./linux-backend.js";
3
+ import { createMacosSandboxBackend } from "./macos-backend.js";
4
+ import { createSandboxPolicy } from "./policy.js";
5
+ import { createSandboxSupervisor } from "./supervisor.js";
6
+ import { SandboxViolationStore } from "./violations.js";
7
+ /** Routes to the platform adapter for the running OS; each adapter self-reports
8
+ * `unavailable` on any other platform, so this only needs to pick the one whose
9
+ * enforcement is actually reachable here. */
10
+ function createDefaultSandboxBackend(options) {
11
+ if (process.platform === "darwin")
12
+ return createMacosSandboxBackend(options);
13
+ return createLinuxSandboxBackend(options);
14
+ }
15
+ const defaultDependencies = {
16
+ createBackend: createDefaultSandboxBackend,
17
+ stderr: process.stderr,
18
+ };
19
+ /**
20
+ * Start a normal CLI runtime beneath the whole-process boundary. The caller returns
21
+ * the result directly so the outer process never continues into `main()` afterward.
22
+ */
23
+ export async function launchSandboxedCli(options) {
24
+ const dependencies = { ...defaultDependencies, ...options.dependencies };
25
+ const policyResult = createSandboxPolicy({ workspace: options.workspace, allowedHosts: options.allowedHosts });
26
+ if (policyResult.kind === "invalid") {
27
+ dependencies.stderr.write(`Error: OS sandbox is not enforcing this agent session: ${policyResult.reason}\n`);
28
+ return 1;
29
+ }
30
+ const violationStore = new SandboxViolationStore();
31
+ const backend = dependencies.createBackend({ violationStore });
32
+ const supervisor = createSandboxSupervisor({ backend, policy: policyResult.policy });
33
+ const launch = buildSandboxedCliLaunch({
34
+ workspace: policyResult.policy.workspace,
35
+ command: options.command,
36
+ args: options.args,
37
+ environment: options.environment,
38
+ allowedHosts: options.allowedHosts,
39
+ readOnlyPaths: options.readOnlyPaths,
40
+ authPath: options.authPath,
41
+ });
42
+ try {
43
+ return await supervisor.launch(launch);
44
+ }
45
+ catch (error) {
46
+ const message = error instanceof Error ? error.message : "Failed to start OS sandbox.";
47
+ dependencies.stderr.write(`Error: ${message}\n`);
48
+ return 1;
49
+ }
50
+ finally {
51
+ for (const violation of violationStore.list()) {
52
+ dependencies.stderr.write(`Sandbox violation (${violation.kind}): ${violation.command} — ${violation.detail}\n`);
53
+ }
54
+ await supervisor.close();
55
+ }
56
+ }
57
+ //# sourceMappingURL=cli-supervisor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-supervisor.js","sourceRoot":"","sources":["../../../src/core/sandbox/cli-supervisor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAuB,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAOxD;;6CAE6C;AAC7C,SAAS,2BAA2B,CAAC,OAAkD,EAAkB;IACxG,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,yBAAyB,CAAC,OAAO,CAAC,CAAC;IAC7E,OAAO,yBAAyB,CAAC,OAAO,CAAC,CAAC;AAAA,CAC1C;AAED,MAAM,mBAAmB,GAA2B;IACnD,aAAa,EAAE,2BAA2B;IAC1C,MAAM,EAAE,OAAO,CAAC,MAAM;CACtB,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,OASxC,EAAmB;IACnB,MAAM,YAAY,GAAG,EAAE,GAAG,mBAAmB,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IACzE,MAAM,YAAY,GAAG,mBAAmB,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAC/G,IAAI,YAAY,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACrC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,0DAA0D,YAAY,CAAC,MAAM,IAAI,CAAC,CAAC;QAC7G,OAAO,CAAC,CAAC;IACV,CAAC;IACD,MAAM,cAAc,GAAG,IAAI,qBAAqB,EAAE,CAAC;IACnD,MAAM,OAAO,GAAG,YAAY,CAAC,aAAa,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC;IAC/D,MAAM,UAAU,GAAG,uBAAuB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC;IACrF,MAAM,MAAM,GAAG,uBAAuB,CAAC;QACtC,SAAS,EAAE,YAAY,CAAC,MAAM,CAAC,SAAS;QACxC,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC1B,CAAC,CAAC;IACH,IAAI,CAAC;QACJ,OAAO,MAAM,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,6BAA6B,CAAC;QACvF,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,OAAO,IAAI,CAAC,CAAC;QACjD,OAAO,CAAC,CAAC;IACV,CAAC;YAAS,CAAC;QACV,KAAK,MAAM,SAAS,IAAI,cAAc,CAAC,IAAI,EAAE,EAAE,CAAC;YAC/C,YAAY,CAAC,MAAM,CAAC,KAAK,CACxB,sBAAsB,SAAS,CAAC,IAAI,MAAM,SAAS,CAAC,OAAO,QAAM,SAAS,CAAC,MAAM,IAAI,CACrF,CAAC;QACH,CAAC;QACD,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;AAAA,CACD","sourcesContent":["import { buildSandboxedCliLaunch } from \"./cli-launch.ts\";\nimport { createLinuxSandboxBackend } from \"./linux-backend.ts\";\nimport { createMacosSandboxBackend } from \"./macos-backend.ts\";\nimport { createSandboxPolicy } from \"./policy.ts\";\nimport { createSandboxSupervisor, type SandboxBackend } from \"./supervisor.ts\";\nimport { SandboxViolationStore } from \"./violations.ts\";\n\nexport interface CliSandboxDependencies {\n\tcreateBackend: (options: { violationStore: SandboxViolationStore }) => SandboxBackend;\n\tstderr: { write(message: string): boolean };\n}\n\n/** Routes to the platform adapter for the running OS; each adapter self-reports\n * `unavailable` on any other platform, so this only needs to pick the one whose\n * enforcement is actually reachable here. */\nfunction createDefaultSandboxBackend(options: { violationStore: SandboxViolationStore }): SandboxBackend {\n\tif (process.platform === \"darwin\") return createMacosSandboxBackend(options);\n\treturn createLinuxSandboxBackend(options);\n}\n\nconst defaultDependencies: CliSandboxDependencies = {\n\tcreateBackend: createDefaultSandboxBackend,\n\tstderr: process.stderr,\n};\n\n/**\n * Start a normal CLI runtime beneath the whole-process boundary. The caller returns\n * the result directly so the outer process never continues into `main()` afterward.\n */\nexport async function launchSandboxedCli(options: {\n\tcommand: string;\n\targs: readonly string[];\n\tenvironment: NodeJS.ProcessEnv;\n\tworkspace: string;\n\tallowedHosts?: readonly string[];\n\treadOnlyPaths?: readonly string[];\n\tauthPath?: string;\n\tdependencies?: Partial<CliSandboxDependencies>;\n}): Promise<number> {\n\tconst dependencies = { ...defaultDependencies, ...options.dependencies };\n\tconst policyResult = createSandboxPolicy({ workspace: options.workspace, allowedHosts: options.allowedHosts });\n\tif (policyResult.kind === \"invalid\") {\n\t\tdependencies.stderr.write(`Error: OS sandbox is not enforcing this agent session: ${policyResult.reason}\\n`);\n\t\treturn 1;\n\t}\n\tconst violationStore = new SandboxViolationStore();\n\tconst backend = dependencies.createBackend({ violationStore });\n\tconst supervisor = createSandboxSupervisor({ backend, policy: policyResult.policy });\n\tconst launch = buildSandboxedCliLaunch({\n\t\tworkspace: policyResult.policy.workspace,\n\t\tcommand: options.command,\n\t\targs: options.args,\n\t\tenvironment: options.environment,\n\t\tallowedHosts: options.allowedHosts,\n\t\treadOnlyPaths: options.readOnlyPaths,\n\t\tauthPath: options.authPath,\n\t});\n\ttry {\n\t\treturn await supervisor.launch(launch);\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : \"Failed to start OS sandbox.\";\n\t\tdependencies.stderr.write(`Error: ${message}\\n`);\n\t\treturn 1;\n\t} finally {\n\t\tfor (const violation of violationStore.list()) {\n\t\t\tdependencies.stderr.write(\n\t\t\t\t`Sandbox violation (${violation.kind}): ${violation.command} — ${violation.detail}\\n`,\n\t\t\t);\n\t\t}\n\t\tawait supervisor.close();\n\t}\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import { spawn } from "node:child_process";
2
+ import type { SandboxBackend } from "./supervisor.ts";
3
+ import type { SandboxViolationStore } from "./violations.ts";
4
+ export interface LinuxSandboxBackendOptions {
5
+ /** Injectable only for preflight tests; production uses the running platform. */
6
+ platform?: NodeJS.Platform;
7
+ commandExists?: (command: string) => boolean;
8
+ violationStore?: SandboxViolationStore;
9
+ /** Injectable only to prove crash cleanup in tests; production spawns `bwrap` directly. */
10
+ spawnChild?: typeof spawn;
11
+ }
12
+ /**
13
+ * Linux's deliberately small platform adapter. The sandbox starts from a read-only
14
+ * host root and bind-mounts only the canonical workspace as writable. `--unshare-net`
15
+ * removes all direct network interfaces for the child and every descendant.
16
+ */
17
+ export declare function createLinuxSandboxBackend(options?: LinuxSandboxBackendOptions): SandboxBackend;
18
+ //# sourceMappingURL=linux-backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linux-backend.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/linux-backend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAa,MAAM,oBAAoB,CAAC;AAItD,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,iBAAiB,CAAC;AACrE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE7D,MAAM,WAAW,0BAA0B;IAC1C,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7C,cAAc,CAAC,EAAE,qBAAqB,CAAC;IACvC,2FAA2F;IAC3F,UAAU,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAmCD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,0BAA0B,GAAG,cAAc,CA+J9F","sourcesContent":["import { spawn, spawnSync } from \"node:child_process\";\nimport { closeSync, mkdirSync, openSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { createSandboxNetworkProxy, type SandboxNetworkProxy } from \"./network-proxy.ts\";\nimport type { SandboxBackend, SandboxLaunch } from \"./supervisor.ts\";\nimport type { SandboxViolationStore } from \"./violations.ts\";\n\nexport interface LinuxSandboxBackendOptions {\n\t/** Injectable only for preflight tests; production uses the running platform. */\n\tplatform?: NodeJS.Platform;\n\tcommandExists?: (command: string) => boolean;\n\tviolationStore?: SandboxViolationStore;\n\t/** Injectable only to prove crash cleanup in tests; production spawns `bwrap` directly. */\n\tspawnChild?: typeof spawn;\n}\n\nfunction commandExists(command: string): boolean {\n\tconst result = spawnSync(command, [\"--version\"], { stdio: \"ignore\", timeout: 1_000 });\n\treturn result.status === 0;\n}\n\nfunction waitForExit(child: ReturnType<typeof spawn>): Promise<number> {\n\treturn new Promise((resolve, reject) => {\n\t\tchild.once(\"error\", reject);\n\t\tchild.once(\"exit\", (code) => resolve(code ?? 1));\n\t});\n}\n\nfunction readOnlyMountArguments(path: string, kind: \"directory\" | \"file\" = \"directory\", descriptor = 3): string[] {\n\tconst target = resolve(path);\n\tconst directory = dirname(target);\n\tconst ancestors: string[] = [];\n\tlet current = directory;\n\twhile (current !== \"/\" && current !== \"/home\") {\n\t\tancestors.push(current);\n\t\tcurrent = dirname(current);\n\t}\n\tconst parentArguments = ancestors.reverse().flatMap((ancestor) => [\"--dir\", ancestor]);\n\treturn kind === \"file\"\n\t\t? [...parentArguments, \"--tmpfs\", directory, \"--perms\", \"0400\", \"--file\", String(descriptor), target]\n\t\t: [...parentArguments, \"--ro-bind\", directory, directory];\n}\n\nfunction classifySandboxFailure(stderr: string): \"filesystem\" | \"network\" | \"unknown\" {\n\tif (/Read-only file system|Permission denied|Operation not permitted/i.test(stderr)) return \"filesystem\";\n\tif (/Network is unreachable|ENETUNREACH|EHOSTUNREACH/i.test(stderr)) return \"network\";\n\treturn \"unknown\";\n}\n\n/**\n * Linux's deliberately small platform adapter. The sandbox starts from a read-only\n * host root and bind-mounts only the canonical workspace as writable. `--unshare-net`\n * removes all direct network interfaces for the child and every descendant.\n */\nexport function createLinuxSandboxBackend(options?: LinuxSandboxBackendOptions): SandboxBackend {\n\tconst platform = options?.platform ?? process.platform;\n\tif (platform !== \"linux\") {\n\t\treturn {\n\t\t\tstatus: { kind: \"unavailable\", reason: \"OS sandbox is supported on Linux only.\" },\n\t\t\tasync launch() {\n\t\t\t\tthrow new Error(\"Linux sandbox backend is unavailable.\");\n\t\t\t},\n\t\t\tasync close() {},\n\t\t};\n\t}\n\tconst hasCommand = options?.commandExists ?? commandExists;\n\tconst violationStore = options?.violationStore;\n\tlet proxy: SandboxNetworkProxy | undefined;\n\n\tif (!hasCommand(\"bwrap\")) {\n\t\treturn {\n\t\t\tstatus: { kind: \"unavailable\", reason: \"Bubblewrap (bwrap) is required for OS sandboxing.\" },\n\t\t\tasync launch() {\n\t\t\t\tthrow new Error(\"Linux sandbox backend is unavailable.\");\n\t\t\t},\n\t\t\tasync close() {},\n\t\t};\n\t}\n\treturn {\n\t\tstatus: { kind: \"enforced\" },\n\t\tasync launch(launch: SandboxLaunch): Promise<number> {\n\t\t\tconst stateDirectory = join(launch.policy.workspace, \".apex-code\", \"sandbox-state\");\n\t\t\tmkdirSync(stateDirectory, { recursive: true });\n\n\t\t\tconst violationCountBeforeLaunch = violationStore?.totalCount ?? 0;\n\n\t\t\tconst socketPath = join(stateDirectory, \"proxy.sock\");\n\t\t\tproxy = await createSandboxNetworkProxy({\n\t\t\t\tsocketPath,\n\t\t\t\tallowedHosts: launch.policy.allowedHosts,\n\t\t\t\tviolationStore,\n\t\t\t});\n\n\t\t\tconst relayScriptPath = join(stateDirectory, \"relay.js\");\n\t\t\twriteFileSync(\n\t\t\t\trelayScriptPath,\n\t\t\t\t`\nconst net = require(\"node:net\");\nconst child_process = require(\"node:child_process\");\n\nconst socketPath = process.env.APEX_UDS_PATH;\nconst server = net.createServer((c) => {\n\tconst client = net.connect(socketPath);\n\tc.pipe(client).pipe(c);\n\tc.on(\"error\", () => {});\n\tclient.on(\"error\", () => {});\n});\n\nserver.listen(0, \"127.0.0.1\", () => {\n\tconst port = server.address().port;\n\tconst env = Object.assign({}, process.env, {\n\t\tHTTP_PROXY: \\`http://127.0.0.1:\\${port}\\`,\n\t\tHTTPS_PROXY: \\`http://127.0.0.1:\\${port}\\`\n\t});\n\tconst args = process.argv.slice(2);\n\tconst child = child_process.spawn(args[0], args.slice(1), {\n\t\tstdio: \"inherit\",\n\t\tenv: env,\n\t});\n\tchild.on(\"exit\", (code, signal) => {\n\t\tprocess.exit(code ?? (signal ? 128 : 1));\n\t});\n});\nserver.on(\"error\", (err) => {\n\tconsole.error(\"Relay error:\", err);\n\tprocess.exit(1);\n});\n`.trim(),\n\t\t\t);\n\n\t\t\tconst readOnlyMounts = [process.execPath, launch.command, ...(launch.readOnlyPaths ?? [])].flatMap((path) =>\n\t\t\t\treadOnlyMountArguments(path),\n\t\t\t);\n\t\t\tconst readOnlyFileMounts = (launch.readOnlyFiles ?? []).flatMap((path, index) =>\n\t\t\t\treadOnlyMountArguments(path, \"file\", index + 3),\n\t\t\t);\n\t\t\tconst readOnlyFileDescriptors = (launch.readOnlyFiles ?? []).map((path) => openSync(path, \"r\"));\n\t\t\t// The descriptors above are opened before the child spawns and must be closed\n\t\t\t// on every exit path -- including a spawn/wait rejection -- or a crashed launch\n\t\t\t// leaks an open handle onto a host-owned credential file for the process's life.\n\t\t\ttry {\n\t\t\t\tconst spawnBwrap = options?.spawnChild ?? spawn;\n\t\t\t\tconst child = spawnBwrap(\n\t\t\t\t\t\"bwrap\",\n\t\t\t\t\t[\n\t\t\t\t\t\t\"--new-session\",\n\t\t\t\t\t\t\"--die-with-parent\",\n\t\t\t\t\t\t\"--unshare-user\",\n\t\t\t\t\t\t\"--unshare-pid\",\n\t\t\t\t\t\t\"--unshare-net\",\n\t\t\t\t\t\t\"--ro-bind\",\n\t\t\t\t\t\t\"/\",\n\t\t\t\t\t\t\"/\",\n\t\t\t\t\t\t\"--tmpfs\",\n\t\t\t\t\t\t\"/home\",\n\t\t\t\t\t\t...readOnlyMounts,\n\t\t\t\t\t\t...readOnlyFileMounts,\n\t\t\t\t\t\t\"--bind\",\n\t\t\t\t\t\tlaunch.policy.workspace,\n\t\t\t\t\t\tlaunch.policy.workspace,\n\t\t\t\t\t\t\"--dev\",\n\t\t\t\t\t\t\"/dev\",\n\t\t\t\t\t\t\"--proc\",\n\t\t\t\t\t\t\"/proc\",\n\t\t\t\t\t\t\"--chdir\",\n\t\t\t\t\t\tlaunch.policy.workspace,\n\t\t\t\t\t\t\"--setenv\",\n\t\t\t\t\t\t\"HOME\",\n\t\t\t\t\t\tlaunch.environment?.HOME ?? stateDirectory,\n\t\t\t\t\t\t\"--setenv\",\n\t\t\t\t\t\t\"TMPDIR\",\n\t\t\t\t\t\tlaunch.environment?.TMPDIR ?? stateDirectory,\n\t\t\t\t\t\t\"--setenv\",\n\t\t\t\t\t\t\"APEX_UDS_PATH\",\n\t\t\t\t\t\tsocketPath,\n\t\t\t\t\t\t\"--\",\n\t\t\t\t\t\tprocess.execPath,\n\t\t\t\t\t\trelayScriptPath,\n\t\t\t\t\t\tlaunch.command,\n\t\t\t\t\t\t...launch.args,\n\t\t\t\t\t],\n\t\t\t\t\t{ env: launch.environment, stdio: [\"inherit\", \"inherit\", \"pipe\", ...readOnlyFileDescriptors] },\n\t\t\t\t);\n\t\t\t\tlet stderr = \"\";\n\t\t\t\tchild.stderr?.setEncoding(\"utf8\");\n\t\t\t\tchild.stderr?.on(\"data\", (chunk: string) => {\n\t\t\t\t\tstderr += chunk;\n\t\t\t\t\tprocess.stderr.write(chunk);\n\t\t\t\t});\n\t\t\t\tconst exitCode = await waitForExit(child);\n\t\t\t\tawait proxy?.close();\n\t\t\t\tconst proxyAlreadyRecordedAViolation = (violationStore?.totalCount ?? 0) > violationCountBeforeLaunch;\n\t\t\t\tif (exitCode !== 0 && !proxyAlreadyRecordedAViolation) {\n\t\t\t\t\tconst kind = classifySandboxFailure(stderr);\n\t\t\t\t\tviolationStore?.add({\n\t\t\t\t\t\tkind,\n\t\t\t\t\t\tcommand: [launch.command, ...launch.args].join(\" \"),\n\t\t\t\t\t\tdetail:\n\t\t\t\t\t\t\tkind === \"unknown\"\n\t\t\t\t\t\t\t\t? \"Sandboxed process exited unsuccessfully; inspect its stderr for the OS refusal.\"\n\t\t\t\t\t\t\t\t: stderr.trim(),\n\t\t\t\t\t\ttimestamp: new Date(),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn exitCode;\n\t\t\t} finally {\n\t\t\t\tfor (const descriptor of readOnlyFileDescriptors) closeSync(descriptor);\n\t\t\t}\n\t\t},\n\t\tasync close() {\n\t\t\tawait proxy?.close();\n\t\t},\n\t};\n}\n"]}