apex-code 0.0.1-alpha.9 → 0.0.4

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 (513) hide show
  1. package/CHANGELOG.md +150 -2
  2. package/README.md +9 -8
  3. package/dist/cli/args.d.ts +25 -1
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +43 -1
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/mcp-command.d.ts +9 -0
  8. package/dist/cli/mcp-command.d.ts.map +1 -0
  9. package/dist/cli/mcp-command.js +53 -0
  10. package/dist/cli/mcp-command.js.map +1 -0
  11. package/dist/cli/project-trust.d.ts.map +1 -1
  12. package/dist/cli/project-trust.js +3 -1
  13. package/dist/cli/project-trust.js.map +1 -1
  14. package/dist/cli/startup-ui.d.ts.map +1 -1
  15. package/dist/cli/startup-ui.js +2 -1
  16. package/dist/cli/startup-ui.js.map +1 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +94 -5
  19. package/dist/cli.js.map +1 -1
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/config.js +24 -5
  22. package/dist/config.js.map +1 -1
  23. package/dist/core/agent-session-services.d.ts +3 -0
  24. package/dist/core/agent-session-services.d.ts.map +1 -1
  25. package/dist/core/agent-session-services.js +1 -0
  26. package/dist/core/agent-session-services.js.map +1 -1
  27. package/dist/core/agent-session.d.ts +157 -8
  28. package/dist/core/agent-session.d.ts.map +1 -1
  29. package/dist/core/agent-session.js +565 -22
  30. package/dist/core/agent-session.js.map +1 -1
  31. package/dist/core/bash-executor.d.ts.map +1 -1
  32. package/dist/core/bash-executor.js +7 -0
  33. package/dist/core/bash-executor.js.map +1 -1
  34. package/dist/core/checkpoints/git-checkpoints.d.ts +39 -0
  35. package/dist/core/checkpoints/git-checkpoints.d.ts.map +1 -0
  36. package/dist/core/checkpoints/git-checkpoints.js +251 -0
  37. package/dist/core/checkpoints/git-checkpoints.js.map +1 -0
  38. package/dist/core/checkpoints/session-checkpoints.d.ts +23 -0
  39. package/dist/core/checkpoints/session-checkpoints.d.ts.map +1 -0
  40. package/dist/core/checkpoints/session-checkpoints.js +36 -0
  41. package/dist/core/checkpoints/session-checkpoints.js.map +1 -0
  42. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  43. package/dist/core/compaction/branch-summarization.js +4 -3
  44. package/dist/core/compaction/branch-summarization.js.map +1 -1
  45. package/dist/core/compaction/compaction.d.ts +5 -0
  46. package/dist/core/compaction/compaction.d.ts.map +1 -1
  47. package/dist/core/compaction/compaction.js +19 -5
  48. package/dist/core/compaction/compaction.js.map +1 -1
  49. package/dist/core/delegation/runtime.d.ts +6 -5
  50. package/dist/core/delegation/runtime.d.ts.map +1 -1
  51. package/dist/core/delegation/runtime.js +4 -3
  52. package/dist/core/delegation/runtime.js.map +1 -1
  53. package/dist/core/exec.d.ts.map +1 -1
  54. package/dist/core/exec.js +5 -2
  55. package/dist/core/exec.js.map +1 -1
  56. package/dist/core/extensions/index.d.ts +1 -1
  57. package/dist/core/extensions/index.d.ts.map +1 -1
  58. package/dist/core/extensions/index.js.map +1 -1
  59. package/dist/core/extensions/loader.d.ts.map +1 -1
  60. package/dist/core/extensions/loader.js +8 -2
  61. package/dist/core/extensions/loader.js.map +1 -1
  62. package/dist/core/extensions/runner.d.ts +5 -0
  63. package/dist/core/extensions/runner.d.ts.map +1 -1
  64. package/dist/core/extensions/runner.js +45 -1
  65. package/dist/core/extensions/runner.js.map +1 -1
  66. package/dist/core/extensions/source-runtime.d.ts +14 -0
  67. package/dist/core/extensions/source-runtime.d.ts.map +1 -0
  68. package/dist/core/extensions/source-runtime.js +56 -0
  69. package/dist/core/extensions/source-runtime.js.map +1 -0
  70. package/dist/core/extensions/types.d.ts +22 -2
  71. package/dist/core/extensions/types.d.ts.map +1 -1
  72. package/dist/core/extensions/types.js.map +1 -1
  73. package/dist/core/formatter-lifecycle.d.ts +69 -0
  74. package/dist/core/formatter-lifecycle.d.ts.map +1 -0
  75. package/dist/core/formatter-lifecycle.js +360 -0
  76. package/dist/core/formatter-lifecycle.js.map +1 -0
  77. package/dist/core/hooks/command-handler.d.ts +19 -0
  78. package/dist/core/hooks/command-handler.d.ts.map +1 -0
  79. package/dist/core/hooks/command-handler.js +112 -0
  80. package/dist/core/hooks/command-handler.js.map +1 -0
  81. package/dist/core/hooks/http-handler.d.ts +10 -0
  82. package/dist/core/hooks/http-handler.d.ts.map +1 -0
  83. package/dist/core/hooks/http-handler.js +36 -0
  84. package/dist/core/hooks/http-handler.js.map +1 -0
  85. package/dist/core/hooks/loader.d.ts +29 -0
  86. package/dist/core/hooks/loader.d.ts.map +1 -0
  87. package/dist/core/hooks/loader.js +139 -0
  88. package/dist/core/hooks/loader.js.map +1 -0
  89. package/dist/core/hooks/runtime.d.ts +13 -0
  90. package/dist/core/hooks/runtime.d.ts.map +1 -0
  91. package/dist/core/hooks/runtime.js +63 -0
  92. package/dist/core/hooks/runtime.js.map +1 -0
  93. package/dist/core/hooks/types.d.ts +83 -0
  94. package/dist/core/hooks/types.d.ts.map +1 -0
  95. package/dist/core/hooks/types.js +24 -0
  96. package/dist/core/hooks/types.js.map +1 -0
  97. package/dist/core/mcp/config.d.ts +16 -0
  98. package/dist/core/mcp/config.d.ts.map +1 -0
  99. package/dist/core/mcp/config.js +166 -0
  100. package/dist/core/mcp/config.js.map +1 -0
  101. package/dist/core/mcp/connector.d.ts +35 -0
  102. package/dist/core/mcp/connector.d.ts.map +1 -0
  103. package/dist/core/mcp/connector.js +105 -0
  104. package/dist/core/mcp/connector.js.map +1 -0
  105. package/dist/core/mcp/contract.d.ts +19 -0
  106. package/dist/core/mcp/contract.d.ts.map +1 -0
  107. package/dist/core/mcp/contract.js +89 -0
  108. package/dist/core/mcp/contract.js.map +1 -0
  109. package/dist/core/mcp/mcp-tool.d.ts +32 -0
  110. package/dist/core/mcp/mcp-tool.d.ts.map +1 -0
  111. package/dist/core/mcp/mcp-tool.js +171 -0
  112. package/dist/core/mcp/mcp-tool.js.map +1 -0
  113. package/dist/core/mcp/metadata-cache.d.ts +29 -0
  114. package/dist/core/mcp/metadata-cache.d.ts.map +1 -0
  115. package/dist/core/mcp/metadata-cache.js +99 -0
  116. package/dist/core/mcp/metadata-cache.js.map +1 -0
  117. package/dist/core/mcp/oauth/authorize.d.ts +21 -0
  118. package/dist/core/mcp/oauth/authorize.d.ts.map +1 -0
  119. package/dist/core/mcp/oauth/authorize.js +32 -0
  120. package/dist/core/mcp/oauth/authorize.js.map +1 -0
  121. package/dist/core/mcp/oauth/discover.d.ts +27 -0
  122. package/dist/core/mcp/oauth/discover.d.ts.map +1 -0
  123. package/dist/core/mcp/oauth/discover.js +116 -0
  124. package/dist/core/mcp/oauth/discover.js.map +1 -0
  125. package/dist/core/mcp/oauth/flow.d.ts +31 -0
  126. package/dist/core/mcp/oauth/flow.d.ts.map +1 -0
  127. package/dist/core/mcp/oauth/flow.js +175 -0
  128. package/dist/core/mcp/oauth/flow.js.map +1 -0
  129. package/dist/core/mcp/oauth/mcp-token.d.ts +51 -0
  130. package/dist/core/mcp/oauth/mcp-token.d.ts.map +1 -0
  131. package/dist/core/mcp/oauth/mcp-token.js +129 -0
  132. package/dist/core/mcp/oauth/mcp-token.js.map +1 -0
  133. package/dist/core/mcp/oauth/pkce.d.ts +12 -0
  134. package/dist/core/mcp/oauth/pkce.d.ts.map +1 -0
  135. package/dist/core/mcp/oauth/pkce.js +11 -0
  136. package/dist/core/mcp/oauth/pkce.js.map +1 -0
  137. package/dist/core/mcp/oauth/register.d.ts +16 -0
  138. package/dist/core/mcp/oauth/register.d.ts.map +1 -0
  139. package/dist/core/mcp/oauth/register.js +42 -0
  140. package/dist/core/mcp/oauth/register.js.map +1 -0
  141. package/dist/core/mcp/oauth/token-client.d.ts +33 -0
  142. package/dist/core/mcp/oauth/token-client.d.ts.map +1 -0
  143. package/dist/core/mcp/oauth/token-client.js +65 -0
  144. package/dist/core/mcp/oauth/token-client.js.map +1 -0
  145. package/dist/core/mcp/runtime.d.ts +20 -0
  146. package/dist/core/mcp/runtime.d.ts.map +1 -0
  147. package/dist/core/mcp/runtime.js +37 -0
  148. package/dist/core/mcp/runtime.js.map +1 -0
  149. package/dist/core/mcp/schema.d.ts +22 -0
  150. package/dist/core/mcp/schema.d.ts.map +1 -0
  151. package/dist/core/mcp/schema.js +20 -0
  152. package/dist/core/mcp/schema.js.map +1 -0
  153. package/dist/core/mcp/server-manager.d.ts +43 -0
  154. package/dist/core/mcp/server-manager.d.ts.map +1 -0
  155. package/dist/core/mcp/server-manager.js +142 -0
  156. package/dist/core/mcp/server-manager.js.map +1 -0
  157. package/dist/core/mcp/types.d.ts +100 -0
  158. package/dist/core/mcp/types.d.ts.map +1 -0
  159. package/dist/core/mcp/types.js +8 -0
  160. package/dist/core/mcp/types.js.map +1 -0
  161. package/dist/core/permissions/gate.d.ts.map +1 -1
  162. package/dist/core/permissions/gate.js +45 -4
  163. package/dist/core/permissions/gate.js.map +1 -1
  164. package/dist/core/permissions/operations.d.ts +20 -0
  165. package/dist/core/permissions/operations.d.ts.map +1 -0
  166. package/dist/core/permissions/operations.js +9 -0
  167. package/dist/core/permissions/operations.js.map +1 -0
  168. package/dist/core/permissions/policy-command.d.ts +42 -0
  169. package/dist/core/permissions/policy-command.d.ts.map +1 -0
  170. package/dist/core/permissions/policy-command.js +44 -0
  171. package/dist/core/permissions/policy-command.js.map +1 -0
  172. package/dist/core/permissions/responder.d.ts +49 -2
  173. package/dist/core/permissions/responder.d.ts.map +1 -1
  174. package/dist/core/permissions/responder.js +20 -8
  175. package/dist/core/permissions/responder.js.map +1 -1
  176. package/dist/core/permissions/rules.d.ts.map +1 -1
  177. package/dist/core/permissions/rules.js +11 -1
  178. package/dist/core/permissions/rules.js.map +1 -1
  179. package/dist/core/permissions/store.d.ts +18 -0
  180. package/dist/core/permissions/store.d.ts.map +1 -1
  181. package/dist/core/permissions/store.js +89 -8
  182. package/dist/core/permissions/store.js.map +1 -1
  183. package/dist/core/policy-executor.d.ts +47 -0
  184. package/dist/core/policy-executor.d.ts.map +1 -0
  185. package/dist/core/policy-executor.js +228 -0
  186. package/dist/core/policy-executor.js.map +1 -0
  187. package/dist/core/policy-loader.d.ts +72 -0
  188. package/dist/core/policy-loader.d.ts.map +1 -0
  189. package/dist/core/policy-loader.js +195 -0
  190. package/dist/core/policy-loader.js.map +1 -0
  191. package/dist/core/project-trust.d.ts +1 -1
  192. package/dist/core/project-trust.d.ts.map +1 -1
  193. package/dist/core/project-trust.js.map +1 -1
  194. package/dist/core/sandbox/bwrap-arguments.d.ts +41 -0
  195. package/dist/core/sandbox/bwrap-arguments.d.ts.map +1 -0
  196. package/dist/core/sandbox/bwrap-arguments.js +136 -0
  197. package/dist/core/sandbox/bwrap-arguments.js.map +1 -0
  198. package/dist/core/sandbox/child-entry.d.ts.map +1 -1
  199. package/dist/core/sandbox/child-entry.js +3 -1
  200. package/dist/core/sandbox/child-entry.js.map +1 -1
  201. package/dist/core/sandbox/cli-launch.d.ts +84 -4
  202. package/dist/core/sandbox/cli-launch.d.ts.map +1 -1
  203. package/dist/core/sandbox/cli-launch.js +115 -16
  204. package/dist/core/sandbox/cli-launch.js.map +1 -1
  205. package/dist/core/sandbox/cli-supervisor.d.ts +2 -0
  206. package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -1
  207. package/dist/core/sandbox/cli-supervisor.js +50 -2
  208. package/dist/core/sandbox/cli-supervisor.js.map +1 -1
  209. package/dist/core/sandbox/full-access.d.ts +28 -0
  210. package/dist/core/sandbox/full-access.d.ts.map +1 -0
  211. package/dist/core/sandbox/full-access.js +63 -0
  212. package/dist/core/sandbox/full-access.js.map +1 -0
  213. package/dist/core/sandbox/git-identity.d.ts +57 -0
  214. package/dist/core/sandbox/git-identity.d.ts.map +1 -0
  215. package/dist/core/sandbox/git-identity.js +78 -0
  216. package/dist/core/sandbox/git-identity.js.map +1 -0
  217. package/dist/core/sandbox/host-approval.d.ts +42 -0
  218. package/dist/core/sandbox/host-approval.d.ts.map +1 -0
  219. package/dist/core/sandbox/host-approval.js +99 -0
  220. package/dist/core/sandbox/host-approval.js.map +1 -0
  221. package/dist/core/sandbox/linux-backend.d.ts +14 -0
  222. package/dist/core/sandbox/linux-backend.d.ts.map +1 -1
  223. package/dist/core/sandbox/linux-backend.js +158 -80
  224. package/dist/core/sandbox/linux-backend.js.map +1 -1
  225. package/dist/core/sandbox/macos-backend.d.ts +5 -0
  226. package/dist/core/sandbox/macos-backend.d.ts.map +1 -1
  227. package/dist/core/sandbox/macos-backend.js +164 -5
  228. package/dist/core/sandbox/macos-backend.js.map +1 -1
  229. package/dist/core/sandbox/network-proxy.d.ts +19 -0
  230. package/dist/core/sandbox/network-proxy.d.ts.map +1 -1
  231. package/dist/core/sandbox/network-proxy.js +93 -19
  232. package/dist/core/sandbox/network-proxy.js.map +1 -1
  233. package/dist/core/sandbox/policy.d.ts +8 -0
  234. package/dist/core/sandbox/policy.d.ts.map +1 -1
  235. package/dist/core/sandbox/policy.js +29 -12
  236. package/dist/core/sandbox/policy.js.map +1 -1
  237. package/dist/core/sandbox/profiles.d.ts +25 -0
  238. package/dist/core/sandbox/profiles.d.ts.map +1 -0
  239. package/dist/core/sandbox/profiles.js +23 -0
  240. package/dist/core/sandbox/profiles.js.map +1 -0
  241. package/dist/core/sandbox/rpc/command-client.d.ts +28 -0
  242. package/dist/core/sandbox/rpc/command-client.d.ts.map +1 -0
  243. package/dist/core/sandbox/rpc/command-client.js +86 -0
  244. package/dist/core/sandbox/rpc/command-client.js.map +1 -0
  245. package/dist/core/sandbox/rpc/command-proxy.d.ts +48 -0
  246. package/dist/core/sandbox/rpc/command-proxy.d.ts.map +1 -0
  247. package/dist/core/sandbox/rpc/command-proxy.js +120 -0
  248. package/dist/core/sandbox/rpc/command-proxy.js.map +1 -0
  249. package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -1
  250. package/dist/core/sandbox/rpc/credential-proxy.js +2 -96
  251. package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -1
  252. package/dist/core/sandbox/rpc/framing.d.ts +35 -0
  253. package/dist/core/sandbox/rpc/framing.d.ts.map +1 -0
  254. package/dist/core/sandbox/rpc/framing.js +114 -0
  255. package/dist/core/sandbox/rpc/framing.js.map +1 -0
  256. package/dist/core/sandbox/rpc/git-credential-helper.d.ts +47 -0
  257. package/dist/core/sandbox/rpc/git-credential-helper.d.ts.map +1 -0
  258. package/dist/core/sandbox/rpc/git-credential-helper.js +222 -0
  259. package/dist/core/sandbox/rpc/git-credential-helper.js.map +1 -0
  260. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts +66 -0
  261. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts.map +1 -0
  262. package/dist/core/sandbox/rpc/git-credential-proxy.js +209 -0
  263. package/dist/core/sandbox/rpc/git-credential-proxy.js.map +1 -0
  264. package/dist/core/sandbox/supervisor-temp.d.ts +18 -0
  265. package/dist/core/sandbox/supervisor-temp.d.ts.map +1 -0
  266. package/dist/core/sandbox/supervisor-temp.js +21 -0
  267. package/dist/core/sandbox/supervisor-temp.js.map +1 -0
  268. package/dist/core/sandbox/supervisor.d.ts +12 -1
  269. package/dist/core/sandbox/supervisor.d.ts.map +1 -1
  270. package/dist/core/sandbox/supervisor.js +15 -1
  271. package/dist/core/sandbox/supervisor.js.map +1 -1
  272. package/dist/core/sandbox/terminal-handoff.d.ts +68 -0
  273. package/dist/core/sandbox/terminal-handoff.d.ts.map +1 -0
  274. package/dist/core/sandbox/terminal-handoff.js +221 -0
  275. package/dist/core/sandbox/terminal-handoff.js.map +1 -0
  276. package/dist/core/sandbox/terminal-size.d.ts +13 -0
  277. package/dist/core/sandbox/terminal-size.d.ts.map +1 -1
  278. package/dist/core/sandbox/terminal-size.js +23 -2
  279. package/dist/core/sandbox/terminal-size.js.map +1 -1
  280. package/dist/core/sdk.d.ts +22 -0
  281. package/dist/core/sdk.d.ts.map +1 -1
  282. package/dist/core/sdk.js +53 -1
  283. package/dist/core/sdk.js.map +1 -1
  284. package/dist/core/session-manager.d.ts +13 -1
  285. package/dist/core/session-manager.d.ts.map +1 -1
  286. package/dist/core/session-manager.js +32 -5
  287. package/dist/core/session-manager.js.map +1 -1
  288. package/dist/core/settings-manager.d.ts +163 -1
  289. package/dist/core/settings-manager.d.ts.map +1 -1
  290. package/dist/core/settings-manager.js +107 -0
  291. package/dist/core/settings-manager.js.map +1 -1
  292. package/dist/core/slash-commands.d.ts.map +1 -1
  293. package/dist/core/slash-commands.js +2 -0
  294. package/dist/core/slash-commands.js.map +1 -1
  295. package/dist/core/tools/background-shell.d.ts +39 -0
  296. package/dist/core/tools/background-shell.d.ts.map +1 -0
  297. package/dist/core/tools/background-shell.js +81 -0
  298. package/dist/core/tools/background-shell.js.map +1 -0
  299. package/dist/core/tools/bash-command-segments.d.ts.map +1 -1
  300. package/dist/core/tools/bash-command-segments.js +2 -0
  301. package/dist/core/tools/bash-command-segments.js.map +1 -1
  302. package/dist/core/tools/bash.d.ts +30 -15
  303. package/dist/core/tools/bash.d.ts.map +1 -1
  304. package/dist/core/tools/bash.js +228 -12
  305. package/dist/core/tools/bash.js.map +1 -1
  306. package/dist/core/tools/contract-snapshot.d.ts +58 -0
  307. package/dist/core/tools/contract-snapshot.d.ts.map +1 -0
  308. package/dist/core/tools/contract-snapshot.js +51 -0
  309. package/dist/core/tools/contract-snapshot.js.map +1 -0
  310. package/dist/core/tools/contract.d.ts +53 -5
  311. package/dist/core/tools/contract.d.ts.map +1 -1
  312. package/dist/core/tools/contract.js +22 -2
  313. package/dist/core/tools/contract.js.map +1 -1
  314. package/dist/core/tools/delegate.d.ts +6 -4
  315. package/dist/core/tools/delegate.d.ts.map +1 -1
  316. package/dist/core/tools/delegate.js +21 -11
  317. package/dist/core/tools/delegate.js.map +1 -1
  318. package/dist/core/tools/edit-diff.d.ts +48 -0
  319. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  320. package/dist/core/tools/edit-diff.js +215 -3
  321. package/dist/core/tools/edit-diff.js.map +1 -1
  322. package/dist/core/tools/edit.d.ts +8 -0
  323. package/dist/core/tools/edit.d.ts.map +1 -1
  324. package/dist/core/tools/edit.js +53 -7
  325. package/dist/core/tools/edit.js.map +1 -1
  326. package/dist/core/tools/find.d.ts.map +1 -1
  327. package/dist/core/tools/find.js +5 -2
  328. package/dist/core/tools/find.js.map +1 -1
  329. package/dist/core/tools/grep.d.ts.map +1 -1
  330. package/dist/core/tools/grep.js +5 -2
  331. package/dist/core/tools/grep.js.map +1 -1
  332. package/dist/core/tools/index.d.ts +10 -1
  333. package/dist/core/tools/index.d.ts.map +1 -1
  334. package/dist/core/tools/index.js +3 -0
  335. package/dist/core/tools/index.js.map +1 -1
  336. package/dist/core/tools/ls.d.ts.map +1 -1
  337. package/dist/core/tools/ls.js +5 -2
  338. package/dist/core/tools/ls.js.map +1 -1
  339. package/dist/core/tools/lsp.d.ts +19 -1
  340. package/dist/core/tools/lsp.d.ts.map +1 -1
  341. package/dist/core/tools/lsp.js +163 -10
  342. package/dist/core/tools/lsp.js.map +1 -1
  343. package/dist/core/tools/path-permission.d.ts +3 -0
  344. package/dist/core/tools/path-permission.d.ts.map +1 -1
  345. package/dist/core/tools/path-permission.js +16 -4
  346. package/dist/core/tools/path-permission.js.map +1 -1
  347. package/dist/core/tools/path-utils.d.ts +27 -2
  348. package/dist/core/tools/path-utils.d.ts.map +1 -1
  349. package/dist/core/tools/path-utils.js +221 -4
  350. package/dist/core/tools/path-utils.js.map +1 -1
  351. package/dist/core/tools/read.d.ts.map +1 -1
  352. package/dist/core/tools/read.js +20 -11
  353. package/dist/core/tools/read.js.map +1 -1
  354. package/dist/core/tools/test.d.ts +54 -9
  355. package/dist/core/tools/test.d.ts.map +1 -1
  356. package/dist/core/tools/test.js +239 -19
  357. package/dist/core/tools/test.js.map +1 -1
  358. package/dist/core/tools/write.d.ts +7 -0
  359. package/dist/core/tools/write.d.ts.map +1 -1
  360. package/dist/core/tools/write.js +64 -9
  361. package/dist/core/tools/write.js.map +1 -1
  362. package/dist/core/verification-lifecycle.d.ts +77 -0
  363. package/dist/core/verification-lifecycle.d.ts.map +1 -0
  364. package/dist/core/verification-lifecycle.js +146 -0
  365. package/dist/core/verification-lifecycle.js.map +1 -0
  366. package/dist/core/workspace/artifacts.d.ts +45 -0
  367. package/dist/core/workspace/artifacts.d.ts.map +1 -0
  368. package/dist/core/workspace/artifacts.js +165 -0
  369. package/dist/core/workspace/artifacts.js.map +1 -0
  370. package/dist/core/workspace/comparison.d.ts +27 -0
  371. package/dist/core/workspace/comparison.d.ts.map +1 -0
  372. package/dist/core/workspace/comparison.js +94 -0
  373. package/dist/core/workspace/comparison.js.map +1 -0
  374. package/dist/core/workspace/git-observer.d.ts +48 -0
  375. package/dist/core/workspace/git-observer.d.ts.map +1 -0
  376. package/dist/core/workspace/git-observer.js +346 -0
  377. package/dist/core/workspace/git-observer.js.map +1 -0
  378. package/dist/core/workspace/projection.d.ts +3 -0
  379. package/dist/core/workspace/projection.d.ts.map +1 -0
  380. package/dist/core/workspace/projection.js +56 -0
  381. package/dist/core/workspace/projection.js.map +1 -0
  382. package/dist/core/workspace/state.d.ts +133 -0
  383. package/dist/core/workspace/state.d.ts.map +1 -0
  384. package/dist/core/workspace/state.js +96 -0
  385. package/dist/core/workspace/state.js.map +1 -0
  386. package/dist/extensions/llama/client.d.ts +6 -0
  387. package/dist/extensions/llama/client.d.ts.map +1 -1
  388. package/dist/extensions/llama/client.js +7 -0
  389. package/dist/extensions/llama/client.js.map +1 -1
  390. package/dist/extensions/llama/provider.d.ts +3 -1
  391. package/dist/extensions/llama/provider.d.ts.map +1 -1
  392. package/dist/extensions/llama/provider.js +27 -6
  393. package/dist/extensions/llama/provider.js.map +1 -1
  394. package/dist/index.d.ts +4 -1
  395. package/dist/index.d.ts.map +1 -1
  396. package/dist/index.js +3 -0
  397. package/dist/index.js.map +1 -1
  398. package/dist/main.d.ts +3 -1
  399. package/dist/main.d.ts.map +1 -1
  400. package/dist/main.js +41 -9
  401. package/dist/main.js.map +1 -1
  402. package/dist/modes/acp/server.d.ts +55 -0
  403. package/dist/modes/acp/server.d.ts.map +1 -0
  404. package/dist/modes/acp/server.js +244 -0
  405. package/dist/modes/acp/server.js.map +1 -0
  406. package/dist/modes/acp/translate.d.ts +37 -0
  407. package/dist/modes/acp/translate.d.ts.map +1 -0
  408. package/dist/modes/acp/translate.js +50 -0
  409. package/dist/modes/acp/translate.js.map +1 -0
  410. package/dist/modes/index.d.ts +1 -0
  411. package/dist/modes/index.d.ts.map +1 -1
  412. package/dist/modes/index.js +1 -0
  413. package/dist/modes/index.js.map +1 -1
  414. package/dist/modes/interactive/components/assistant-message.d.ts +29 -0
  415. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  416. package/dist/modes/interactive/components/assistant-message.js +182 -23
  417. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  418. package/dist/modes/interactive/components/bash-execution.d.ts +7 -0
  419. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  420. package/dist/modes/interactive/components/bash-execution.js +20 -0
  421. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  422. package/dist/modes/interactive/components/custom-editor.d.ts +9 -4
  423. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  424. package/dist/modes/interactive/components/custom-editor.js +20 -9
  425. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  426. package/dist/modes/interactive/components/extension-selector.d.ts +7 -1
  427. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  428. package/dist/modes/interactive/components/extension-selector.js +7 -3
  429. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  430. package/dist/modes/interactive/components/footer.d.ts +19 -0
  431. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  432. package/dist/modes/interactive/components/footer.js +70 -10
  433. package/dist/modes/interactive/components/footer.js.map +1 -1
  434. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  435. package/dist/modes/interactive/components/model-selector.js +6 -6
  436. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  437. package/dist/modes/interactive/components/permission-preview.d.ts +15 -0
  438. package/dist/modes/interactive/components/permission-preview.d.ts.map +1 -0
  439. package/dist/modes/interactive/components/permission-preview.js +33 -0
  440. package/dist/modes/interactive/components/permission-preview.js.map +1 -0
  441. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  442. package/dist/modes/interactive/components/session-selector.js +2 -2
  443. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  444. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  445. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  446. package/dist/modes/interactive/components/settings-selector.js +10 -0
  447. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  448. package/dist/modes/interactive/components/tool-execution.d.ts +23 -1
  449. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  450. package/dist/modes/interactive/components/tool-execution.js +64 -10
  451. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  452. package/dist/modes/interactive/interactive-mode.d.ts +34 -0
  453. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  454. package/dist/modes/interactive/interactive-mode.js +231 -26
  455. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  456. package/dist/modes/interactive/theme/theme.d.ts +12 -0
  457. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  458. package/dist/modes/interactive/theme/theme.js +15 -5
  459. package/dist/modes/interactive/theme/theme.js.map +1 -1
  460. package/dist/modes/print-mode.d.ts.map +1 -1
  461. package/dist/modes/print-mode.js +27 -12
  462. package/dist/modes/print-mode.js.map +1 -1
  463. package/dist/modes/rpc/rpc-client.d.ts +7 -0
  464. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  465. package/dist/modes/rpc/rpc-client.js +7 -0
  466. package/dist/modes/rpc/rpc-client.js.map +1 -1
  467. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  468. package/dist/modes/rpc/rpc-mode.js +3 -0
  469. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  470. package/dist/modes/rpc/rpc-types.d.ts +12 -0
  471. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  472. package/dist/modes/rpc/rpc-types.js.map +1 -1
  473. package/dist/package-manager-cli.d.ts.map +1 -1
  474. package/dist/package-manager-cli.js +0 -6
  475. package/dist/package-manager-cli.js.map +1 -1
  476. package/dist/utils/shell.d.ts.map +1 -1
  477. package/dist/utils/shell.js +6 -4
  478. package/dist/utils/shell.js.map +1 -1
  479. package/dist/utils/version-check.d.ts.map +1 -1
  480. package/dist/utils/version-check.js +6 -4
  481. package/dist/utils/version-check.js.map +1 -1
  482. package/docs/compaction.md +4 -0
  483. package/docs/custom-provider.md +7 -7
  484. package/docs/environment-variables.md +1 -1
  485. package/docs/extensions.md +18 -0
  486. package/docs/keybindings.md +1 -1
  487. package/docs/quickstart.md +1 -1
  488. package/docs/rpc.md +23 -0
  489. package/docs/sdk.md +25 -0
  490. package/docs/settings.md +12 -4
  491. package/docs/terminal-setup.md +12 -0
  492. package/docs/usage.md +4 -3
  493. package/examples/extensions/built-in-tool-renderer.ts +8 -4
  494. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  495. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  496. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  497. package/examples/extensions/git-checkpoint.ts +24 -36
  498. package/examples/extensions/gondolin/package-lock.json +2 -2
  499. package/examples/extensions/gondolin/package.json +1 -1
  500. package/examples/extensions/minimal-mode.ts +2 -2
  501. package/examples/extensions/permission-gate.ts +1 -1
  502. package/examples/extensions/plan-mode/index.ts +1 -1
  503. package/examples/extensions/rpc-demo.ts +1 -1
  504. package/examples/extensions/sandbox/package-lock.json +2 -2
  505. package/examples/extensions/sandbox/package.json +1 -1
  506. package/examples/extensions/with-deps/package-lock.json +2 -2
  507. package/examples/extensions/with-deps/package.json +1 -1
  508. package/npm-shrinkwrap.json +194 -46
  509. package/package.json +14 -12
  510. package/dist/server/create-harness.d.ts +0 -26
  511. package/dist/server/create-harness.d.ts.map +0 -1
  512. package/dist/server/create-harness.js +0 -107
  513. package/dist/server/create-harness.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"project-trust.js","sourceRoot":"","sources":["../../src/core/project-trust.ts"],"names":[],"mappings":"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","sourcesContent":["import { CONFIG_DIR_NAME } from \"../config.ts\";\nimport { emitProjectTrustEvent } from \"./extensions/runner.ts\";\nimport type { LoadExtensionsResult, ProjectTrustContext } from \"./extensions/types.ts\";\nimport type { DefaultProjectTrust } from \"./settings-manager.ts\";\nimport {\n\tgetProjectTrustOptions,\n\thasTrustRequiringProjectResources,\n\ttype ProjectTrustOption,\n\ttype ProjectTrustStore,\n} from \"./trust-manager.ts\";\n\nexport type AppMode = \"interactive\" | \"print\" | \"json\" | \"rpc\";\n\nexport interface ResolveProjectTrustedOptions {\n\tcwd: string;\n\ttrustStore: ProjectTrustStore;\n\ttrustOverride?: boolean;\n\tdefaultProjectTrust?: DefaultProjectTrust;\n\textensionsResult?: LoadExtensionsResult;\n\tprojectTrustContext: ProjectTrustContext;\n\tonExtensionError?: (message: string) => void;\n}\n\nfunction formatProjectTrustPrompt(cwd: string): string {\n\treturn `Trust project folder?\\n${cwd}\\n\\nThis allows Apex Code to load ${CONFIG_DIR_NAME} settings and resources, install missing project packages, execute project extensions, and start configured language servers.`;\n}\n\nasync function selectProjectTrustOption(\n\tcwd: string,\n\tctx: ProjectTrustContext,\n): Promise<ProjectTrustOption | undefined> {\n\tconst options = getProjectTrustOptions(cwd, { includeSessionOnly: true });\n\tconst selected = await ctx.ui.select(\n\t\tformatProjectTrustPrompt(cwd),\n\t\toptions.map((option) => option.label),\n\t);\n\treturn options.find((option) => option.label === selected);\n}\n\nfunction saveProjectTrustPromptResult(trustStore: ProjectTrustStore, result: ProjectTrustOption): void {\n\tif (result.updates.length > 0) {\n\t\ttrustStore.setMany(result.updates);\n\t}\n}\n\nexport async function resolveProjectTrusted(options: ResolveProjectTrustedOptions): Promise<boolean> {\n\tif (options.trustOverride !== undefined) {\n\t\treturn options.trustOverride;\n\t}\n\tif (!hasTrustRequiringProjectResources(options.cwd)) {\n\t\treturn true;\n\t}\n\n\tif (options.extensionsResult) {\n\t\tconst { result, errors } = await emitProjectTrustEvent(\n\t\t\toptions.extensionsResult,\n\t\t\t{ type: \"project_trust\", cwd: options.cwd },\n\t\t\toptions.projectTrustContext,\n\t\t);\n\t\tfor (const error of errors) {\n\t\t\toptions.onExtensionError?.(`Extension \"${error.extensionPath}\" project_trust error: ${error.error}`);\n\t\t}\n\t\tif (result) {\n\t\t\tconst trusted = result.trusted === \"yes\";\n\t\t\tif (result.remember === true) {\n\t\t\t\toptions.trustStore.set(options.cwd, trusted);\n\t\t\t}\n\t\t\treturn trusted;\n\t\t}\n\t}\n\n\tconst decision = options.trustStore.get(options.cwd);\n\tif (decision !== null) {\n\t\treturn decision;\n\t}\n\n\tswitch (options.defaultProjectTrust ?? \"ask\") {\n\t\tcase \"always\":\n\t\t\treturn true;\n\t\tcase \"never\":\n\t\t\treturn false;\n\t\tcase \"ask\":\n\t\t\tbreak;\n\t}\n\n\tif (!options.projectTrustContext.hasUI) {\n\t\treturn false;\n\t}\n\n\tconst selected = await selectProjectTrustOption(options.cwd, options.projectTrustContext);\n\tif (selected !== undefined) {\n\t\tsaveProjectTrustPromptResult(options.trustStore, selected);\n\t\treturn selected.trusted;\n\t}\n\treturn false;\n}\n"]}
1
+ {"version":3,"file":"project-trust.js","sourceRoot":"","sources":["../../src/core/project-trust.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAG/D,OAAO,EACN,sBAAsB,EACtB,iCAAiC,GAGjC,MAAM,oBAAoB,CAAC;AAc5B,SAAS,wBAAwB,CAAC,GAAW,EAAU;IACtD,OAAO,0BAA0B,GAAG,qCAAqC,eAAe,+HAA+H,CAAC;AAAA,CACxN;AAED,KAAK,UAAU,wBAAwB,CACtC,GAAW,EACX,GAAwB,EACkB;IAC1C,MAAM,OAAO,GAAG,sBAAsB,CAAC,GAAG,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1E,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,MAAM,CACnC,wBAAwB,CAAC,GAAG,CAAC,EAC7B,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CACrC,CAAC;IACF,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC;AAAA,CAC3D;AAED,SAAS,4BAA4B,CAAC,UAA6B,EAAE,MAA0B,EAAQ;IACtG,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;AAAA,CACD;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,OAAqC,EAAoB;IACpG,IAAI,OAAO,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QACzC,OAAO,OAAO,CAAC,aAAa,CAAC;IAC9B,CAAC;IACD,IAAI,CAAC,iCAAiC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACrD,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;QAC9B,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,qBAAqB,CACrD,OAAO,CAAC,gBAAgB,EACxB,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAC3C,OAAO,CAAC,mBAAmB,CAC3B,CAAC;QACF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC5B,OAAO,CAAC,gBAAgB,EAAE,CAAC,cAAc,KAAK,CAAC,aAAa,0BAA0B,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;QACtG,CAAC;QACD,IAAI,MAAM,EAAE,CAAC;YACZ,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,KAAK,KAAK,CAAC;YACzC,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;gBAC9B,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAC9C,CAAC;YACD,OAAO,OAAO,CAAC;QAChB,CAAC;IACF,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACrD,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACvB,OAAO,QAAQ,CAAC;IACjB,CAAC;IAED,QAAQ,OAAO,CAAC,mBAAmB,IAAI,KAAK,EAAE,CAAC;QAC9C,KAAK,QAAQ;YACZ,OAAO,IAAI,CAAC;QACb,KAAK,OAAO;YACX,OAAO,KAAK,CAAC;QACd,KAAK,KAAK;YACT,MAAM;IACR,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,KAAK,EAAE,CAAC;QACxC,OAAO,KAAK,CAAC;IACd,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,wBAAwB,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC1F,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC5B,4BAA4B,CAAC,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC3D,OAAO,QAAQ,CAAC,OAAO,CAAC;IACzB,CAAC;IACD,OAAO,KAAK,CAAC;AAAA,CACb","sourcesContent":["import { CONFIG_DIR_NAME } from \"../config.ts\";\nimport { emitProjectTrustEvent } from \"./extensions/runner.ts\";\nimport type { LoadExtensionsResult, ProjectTrustContext } from \"./extensions/types.ts\";\nimport type { DefaultProjectTrust } from \"./settings-manager.ts\";\nimport {\n\tgetProjectTrustOptions,\n\thasTrustRequiringProjectResources,\n\ttype ProjectTrustOption,\n\ttype ProjectTrustStore,\n} from \"./trust-manager.ts\";\n\nexport type AppMode = \"interactive\" | \"print\" | \"json\" | \"rpc\" | \"acp\";\n\nexport interface ResolveProjectTrustedOptions {\n\tcwd: string;\n\ttrustStore: ProjectTrustStore;\n\ttrustOverride?: boolean;\n\tdefaultProjectTrust?: DefaultProjectTrust;\n\textensionsResult?: LoadExtensionsResult;\n\tprojectTrustContext: ProjectTrustContext;\n\tonExtensionError?: (message: string) => void;\n}\n\nfunction formatProjectTrustPrompt(cwd: string): string {\n\treturn `Trust project folder?\\n${cwd}\\n\\nThis allows Apex Code to load ${CONFIG_DIR_NAME} settings and resources, install missing project packages, execute project extensions, and start configured language servers.`;\n}\n\nasync function selectProjectTrustOption(\n\tcwd: string,\n\tctx: ProjectTrustContext,\n): Promise<ProjectTrustOption | undefined> {\n\tconst options = getProjectTrustOptions(cwd, { includeSessionOnly: true });\n\tconst selected = await ctx.ui.select(\n\t\tformatProjectTrustPrompt(cwd),\n\t\toptions.map((option) => option.label),\n\t);\n\treturn options.find((option) => option.label === selected);\n}\n\nfunction saveProjectTrustPromptResult(trustStore: ProjectTrustStore, result: ProjectTrustOption): void {\n\tif (result.updates.length > 0) {\n\t\ttrustStore.setMany(result.updates);\n\t}\n}\n\nexport async function resolveProjectTrusted(options: ResolveProjectTrustedOptions): Promise<boolean> {\n\tif (options.trustOverride !== undefined) {\n\t\treturn options.trustOverride;\n\t}\n\tif (!hasTrustRequiringProjectResources(options.cwd)) {\n\t\treturn true;\n\t}\n\n\tif (options.extensionsResult) {\n\t\tconst { result, errors } = await emitProjectTrustEvent(\n\t\t\toptions.extensionsResult,\n\t\t\t{ type: \"project_trust\", cwd: options.cwd },\n\t\t\toptions.projectTrustContext,\n\t\t);\n\t\tfor (const error of errors) {\n\t\t\toptions.onExtensionError?.(`Extension \"${error.extensionPath}\" project_trust error: ${error.error}`);\n\t\t}\n\t\tif (result) {\n\t\t\tconst trusted = result.trusted === \"yes\";\n\t\t\tif (result.remember === true) {\n\t\t\t\toptions.trustStore.set(options.cwd, trusted);\n\t\t\t}\n\t\t\treturn trusted;\n\t\t}\n\t}\n\n\tconst decision = options.trustStore.get(options.cwd);\n\tif (decision !== null) {\n\t\treturn decision;\n\t}\n\n\tswitch (options.defaultProjectTrust ?? \"ask\") {\n\t\tcase \"always\":\n\t\t\treturn true;\n\t\tcase \"never\":\n\t\t\treturn false;\n\t\tcase \"ask\":\n\t\t\tbreak;\n\t}\n\n\tif (!options.projectTrustContext.hasUI) {\n\t\treturn false;\n\t}\n\n\tconst selected = await selectProjectTrustOption(options.cwd, options.projectTrustContext);\n\tif (selected !== undefined) {\n\t\tsaveProjectTrustPromptResult(options.trustStore, selected);\n\t\treturn selected.trusted;\n\t}\n\treturn false;\n}\n"]}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The single source of the child's `bwrap` argv.
3
+ *
4
+ * Extracted when per-command escalation needed a second child. Two hand-maintained argv
5
+ * builders is the divergence ADR 0010 exists to prevent for tool contracts, and it is
6
+ * worse here: a mount tightened in the primary child and missed in the escalated one
7
+ * would be invisible until the escalated path was the one that mattered. Everything that
8
+ * differs between the two children is an input to this function, so there is nothing to
9
+ * keep in sync by hand.
10
+ */
11
+ /** A unix socket projected from the host into the child. */
12
+ export interface BwrapSocketMount {
13
+ readonly hostPath: string;
14
+ readonly childPath: string;
15
+ }
16
+ /** A host executable placed read-only at an exact path inside the child. */
17
+ export interface BwrapBinaryMount {
18
+ readonly source: string;
19
+ readonly destination: string;
20
+ }
21
+ export interface BwrapSpec {
22
+ /** The primary writable root, and where the child starts. */
23
+ readonly workspace: string;
24
+ /** Further writable roots, each bound exactly as the workspace is. */
25
+ readonly additionalWritableRoots: readonly string[];
26
+ /** Directories whose contents the child may read but not write. */
27
+ readonly readOnlyPaths: readonly string[];
28
+ /** Individual read-only files, projected by descriptor from index 3 upward. */
29
+ readonly readOnlyFiles: readonly string[];
30
+ readonly readOnlyBinaries: readonly BwrapBinaryMount[];
31
+ readonly sockets: readonly BwrapSocketMount[];
32
+ readonly environment: Readonly<Record<string, string>>;
33
+ readonly command: string;
34
+ readonly args: readonly string[];
35
+ }
36
+ export declare function readOnlyMountArguments(path: string): string[];
37
+ /** Directory paths are mounted from matching fd 3 onward to preserve sibling exclusion. */
38
+ export declare function descriptorBackedReadOnlyPaths(paths: readonly string[]): string[];
39
+ /** Build the complete argv. Mount order below is load-bearing; the comments say why. */
40
+ export declare function buildBwrapArguments(spec: BwrapSpec): string[];
41
+ //# sourceMappingURL=bwrap-arguments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bwrap-arguments.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/bwrap-arguments.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AAEH,4DAA4D;AAC5D,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAED,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,SAAS;IACzB,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,uBAAuB,EAAE,SAAS,MAAM,EAAE,CAAC;IACpD,mEAAmE;IACnE,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,gBAAgB,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACvD,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC9C,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAYD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAI7D;AAED,2FAA2F;AAC3F,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAQhF;AAmED,wFAAwF;AACxF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,EAAE,CA+D7D","sourcesContent":["import { statSync } from \"node:fs\";\nimport { dirname, resolve, sep } from \"node:path\";\n\n/**\n * The single source of the child's `bwrap` argv.\n *\n * Extracted when per-command escalation needed a second child. Two hand-maintained argv\n * builders is the divergence ADR 0010 exists to prevent for tool contracts, and it is\n * worse here: a mount tightened in the primary child and missed in the escalated one\n * would be invisible until the escalated path was the one that mattered. Everything that\n * differs between the two children is an input to this function, so there is nothing to\n * keep in sync by hand.\n */\n\n/** A unix socket projected from the host into the child. */\nexport interface BwrapSocketMount {\n\treadonly hostPath: string;\n\treadonly childPath: string;\n}\n\n/** A host executable placed read-only at an exact path inside the child. */\nexport interface BwrapBinaryMount {\n\treadonly source: string;\n\treadonly destination: string;\n}\n\nexport interface BwrapSpec {\n\t/** The primary writable root, and where the child starts. */\n\treadonly workspace: string;\n\t/** Further writable roots, each bound exactly as the workspace is. */\n\treadonly additionalWritableRoots: readonly string[];\n\t/** Directories whose contents the child may read but not write. */\n\treadonly readOnlyPaths: readonly string[];\n\t/** Individual read-only files, projected by descriptor from index 3 upward. */\n\treadonly readOnlyFiles: readonly string[];\n\treadonly readOnlyBinaries: readonly BwrapBinaryMount[];\n\treadonly sockets: readonly BwrapSocketMount[];\n\treadonly environment: Readonly<Record<string, string>>;\n\treadonly command: string;\n\treadonly args: readonly string[];\n}\n\nfunction ancestorDirectoryArguments(directory: string): string[] {\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\treturn ancestors.reverse().flatMap((ancestor) => [\"--dir\", ancestor]);\n}\n\nexport function readOnlyMountArguments(path: string): string[] {\n\tconst target = resolve(path);\n\tconst parent = dirname(target);\n\treturn [...ancestorDirectoryArguments(parent), \"--ro-bind\", parent, parent];\n}\n\n/** Directory paths are mounted from matching fd 3 onward to preserve sibling exclusion. */\nexport function descriptorBackedReadOnlyPaths(paths: readonly string[]): string[] {\n\treturn paths.filter((path) => {\n\t\ttry {\n\t\t\treturn statSync(resolve(path)).isDirectory();\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t});\n}\n\ninterface DescriptorBackedDirectory {\n\treadonly path: string;\n\treadonly descriptor: number;\n}\n\ninterface DescriptorBackedFile extends DescriptorBackedDirectory {}\n\ninterface ProjectionGroup {\n\treadonly directories: DescriptorBackedDirectory[];\n\treadonly files: DescriptorBackedFile[];\n}\n\nfunction isWithin(path: string, root: string): boolean {\n\tconst target = resolve(path);\n\tconst boundary = resolve(root);\n\treturn target === boundary || target.startsWith(boundary.endsWith(sep) ? boundary : `${boundary}${sep}`);\n}\n\nfunction nestedReadOnlyMountArguments(projections: readonly DescriptorBackedDirectory[]): string[] {\n\treturn projections.flatMap(({ path, descriptor }) => {\n\t\tconst target = resolve(path);\n\t\treturn [\"--ro-bind\", `/proc/self/fd/${descriptor}`, target];\n\t});\n}\n\nfunction externalReadOnlyMountArguments(\n\tdirectories: readonly DescriptorBackedDirectory[],\n\tfiles: readonly DescriptorBackedFile[],\n\twritableRoots: readonly string[],\n): string[] {\n\tconst byParent = new Map<string, ProjectionGroup>();\n\tconst add = (kind: keyof ProjectionGroup, projection: DescriptorBackedDirectory): void => {\n\t\tconst parent = dirname(resolve(projection.path));\n\t\tconst group = byParent.get(parent) ?? { directories: [], files: [] };\n\t\tgroup[kind].push(projection);\n\t\tbyParent.set(parent, group);\n\t};\n\tfor (const directory of directories) add(\"directories\", directory);\n\tfor (const file of files) add(\"files\", file);\n\n\tconst groups = [...byParent].sort(([left], [right]) => left.split(sep).length - right.split(sep).length);\n\tconst laterMountpoints = [...byParent.keys(), ...writableRoots];\n\treturn groups.flatMap(([parent, group]) => [\n\t\t...ancestorDirectoryArguments(parent),\n\t\t\"--tmpfs\",\n\t\tparent,\n\t\t...laterMountpoints\n\t\t\t.filter((mountpoint) => mountpoint !== parent && isWithin(mountpoint, parent))\n\t\t\t.flatMap(ancestorDirectoryArguments),\n\t\t...group.directories.flatMap(({ path, descriptor }) => {\n\t\t\tconst target = resolve(path);\n\t\t\treturn [\"--dir\", target, \"--ro-bind\", `/proc/self/fd/${descriptor}`, target];\n\t\t}),\n\t\t...group.files.flatMap(({ path, descriptor }) => [\n\t\t\t\"--perms\",\n\t\t\t\"0400\",\n\t\t\t\"--file\",\n\t\t\tString(descriptor),\n\t\t\tresolve(path),\n\t\t]),\n\t\t\"--remount-ro\",\n\t\tparent,\n\t]);\n}\n\n/** Build the complete argv. Mount order below is load-bearing; the comments say why. */\nexport function buildBwrapArguments(spec: BwrapSpec): string[] {\n\tconst directoryPaths = descriptorBackedReadOnlyPaths(spec.readOnlyPaths);\n\tconst ordinaryPaths = spec.readOnlyPaths.filter((path) => !directoryPaths.includes(path));\n\tconst writableRoots = [spec.workspace, ...spec.additionalWritableRoots];\n\tconst directoryMounts = directoryPaths.map((path, index) => ({ path, descriptor: 3 + index }));\n\tconst fileMounts = spec.readOnlyFiles.map((path, index) => ({\n\t\tpath,\n\t\tdescriptor: 3 + directoryPaths.length + index,\n\t}));\n\tconst nestedDirectoryMounts = directoryMounts.filter(({ path }) =>\n\t\twritableRoots.some((root) => isWithin(path, root)),\n\t);\n\tconst externalDirectoryMounts = directoryMounts.filter(({ path }) =>\n\t\twritableRoots.every((root) => !isWithin(path, root)),\n\t);\n\tconst nestedFileMounts = fileMounts.filter(({ path }) => writableRoots.some((root) => isWithin(path, root)));\n\tconst externalFileMounts = fileMounts.filter(({ path }) => writableRoots.every((root) => !isWithin(path, root)));\n\treturn [\n\t\t\"--new-session\",\n\t\t\"--die-with-parent\",\n\t\t\"--unshare-user\",\n\t\t\"--unshare-pid\",\n\t\t\"--unshare-net\",\n\t\t\"--ro-bind\",\n\t\t\"/\",\n\t\t\"/\",\n\t\t\"--tmpfs\",\n\t\t\"/home\",\n\t\t// Immediately after the /home tmpfs: that is the only writable mount at this\n\t\t// point, so it is the only place bwrap can create a socket mountpoint.\n\t\t...spec.sockets.flatMap(({ hostPath, childPath }) => [\"--bind\", hostPath, childPath]),\n\t\t// External directory projections shadow their parent to exclude siblings. Do\n\t\t// that before writable roots. The root binds below restore paths that the\n\t\t// projection's parent shadow may have hidden.\n\t\t...externalReadOnlyMountArguments(externalDirectoryMounts, externalFileMounts, writableRoots),\n\t\t...writableRoots.flatMap((root) => [\"--dir\", root]),\n\t\t\"--bind\",\n\t\tspec.workspace,\n\t\tspec.workspace,\n\t\t// Each extra root is bound exactly as the workspace is, and only ever from an\n\t\t// argv-parsed flag: a repository that could name its own writable root would be\n\t\t// granting itself authority (ADR 0016).\n\t\t...spec.additionalWritableRoots.flatMap((root) => [\"--bind\", root, root]),\n\t\t// These mounts must follow the writable roots: otherwise binding a workspace\n\t\t// would mask a read-only projection nested inside it.\n\t\t...ordinaryPaths.flatMap((path) => readOnlyMountArguments(path)),\n\t\t// A directory already inside a writable root needs no parent shadow: that root\n\t\t// already exposes its siblings by policy. Rebind only the requested directory.\n\t\t...nestedReadOnlyMountArguments([...nestedDirectoryMounts, ...nestedFileMounts]),\n\t\t// After the workspace bind: these destinations sit inside it, and an earlier mount\n\t\t// would be masked when the workspace is bound over them.\n\t\t...spec.readOnlyBinaries.flatMap(({ source, destination }) => [\"--ro-bind\", source, destination]),\n\t\t\"--dev\",\n\t\t\"/dev\",\n\t\t\"--proc\",\n\t\t\"/proc\",\n\t\t\"--chdir\",\n\t\tspec.workspace,\n\t\t...Object.entries(spec.environment).flatMap(([name, value]) => [\"--setenv\", name, value]),\n\t\t\"--\",\n\t\tspec.command,\n\t\t...spec.args,\n\t];\n}\n"]}
@@ -0,0 +1,136 @@
1
+ import { statSync } from "node:fs";
2
+ import { dirname, resolve, sep } from "node:path";
3
+ function ancestorDirectoryArguments(directory) {
4
+ const ancestors = [];
5
+ let current = directory;
6
+ while (current !== "/" && current !== "/home") {
7
+ ancestors.push(current);
8
+ current = dirname(current);
9
+ }
10
+ return ancestors.reverse().flatMap((ancestor) => ["--dir", ancestor]);
11
+ }
12
+ export function readOnlyMountArguments(path) {
13
+ const target = resolve(path);
14
+ const parent = dirname(target);
15
+ return [...ancestorDirectoryArguments(parent), "--ro-bind", parent, parent];
16
+ }
17
+ /** Directory paths are mounted from matching fd 3 onward to preserve sibling exclusion. */
18
+ export function descriptorBackedReadOnlyPaths(paths) {
19
+ return paths.filter((path) => {
20
+ try {
21
+ return statSync(resolve(path)).isDirectory();
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ });
27
+ }
28
+ function isWithin(path, root) {
29
+ const target = resolve(path);
30
+ const boundary = resolve(root);
31
+ return target === boundary || target.startsWith(boundary.endsWith(sep) ? boundary : `${boundary}${sep}`);
32
+ }
33
+ function nestedReadOnlyMountArguments(projections) {
34
+ return projections.flatMap(({ path, descriptor }) => {
35
+ const target = resolve(path);
36
+ return ["--ro-bind", `/proc/self/fd/${descriptor}`, target];
37
+ });
38
+ }
39
+ function externalReadOnlyMountArguments(directories, files, writableRoots) {
40
+ const byParent = new Map();
41
+ const add = (kind, projection) => {
42
+ const parent = dirname(resolve(projection.path));
43
+ const group = byParent.get(parent) ?? { directories: [], files: [] };
44
+ group[kind].push(projection);
45
+ byParent.set(parent, group);
46
+ };
47
+ for (const directory of directories)
48
+ add("directories", directory);
49
+ for (const file of files)
50
+ add("files", file);
51
+ const groups = [...byParent].sort(([left], [right]) => left.split(sep).length - right.split(sep).length);
52
+ const laterMountpoints = [...byParent.keys(), ...writableRoots];
53
+ return groups.flatMap(([parent, group]) => [
54
+ ...ancestorDirectoryArguments(parent),
55
+ "--tmpfs",
56
+ parent,
57
+ ...laterMountpoints
58
+ .filter((mountpoint) => mountpoint !== parent && isWithin(mountpoint, parent))
59
+ .flatMap(ancestorDirectoryArguments),
60
+ ...group.directories.flatMap(({ path, descriptor }) => {
61
+ const target = resolve(path);
62
+ return ["--dir", target, "--ro-bind", `/proc/self/fd/${descriptor}`, target];
63
+ }),
64
+ ...group.files.flatMap(({ path, descriptor }) => [
65
+ "--perms",
66
+ "0400",
67
+ "--file",
68
+ String(descriptor),
69
+ resolve(path),
70
+ ]),
71
+ "--remount-ro",
72
+ parent,
73
+ ]);
74
+ }
75
+ /** Build the complete argv. Mount order below is load-bearing; the comments say why. */
76
+ export function buildBwrapArguments(spec) {
77
+ const directoryPaths = descriptorBackedReadOnlyPaths(spec.readOnlyPaths);
78
+ const ordinaryPaths = spec.readOnlyPaths.filter((path) => !directoryPaths.includes(path));
79
+ const writableRoots = [spec.workspace, ...spec.additionalWritableRoots];
80
+ const directoryMounts = directoryPaths.map((path, index) => ({ path, descriptor: 3 + index }));
81
+ const fileMounts = spec.readOnlyFiles.map((path, index) => ({
82
+ path,
83
+ descriptor: 3 + directoryPaths.length + index,
84
+ }));
85
+ const nestedDirectoryMounts = directoryMounts.filter(({ path }) => writableRoots.some((root) => isWithin(path, root)));
86
+ const externalDirectoryMounts = directoryMounts.filter(({ path }) => writableRoots.every((root) => !isWithin(path, root)));
87
+ const nestedFileMounts = fileMounts.filter(({ path }) => writableRoots.some((root) => isWithin(path, root)));
88
+ const externalFileMounts = fileMounts.filter(({ path }) => writableRoots.every((root) => !isWithin(path, root)));
89
+ return [
90
+ "--new-session",
91
+ "--die-with-parent",
92
+ "--unshare-user",
93
+ "--unshare-pid",
94
+ "--unshare-net",
95
+ "--ro-bind",
96
+ "/",
97
+ "/",
98
+ "--tmpfs",
99
+ "/home",
100
+ // Immediately after the /home tmpfs: that is the only writable mount at this
101
+ // point, so it is the only place bwrap can create a socket mountpoint.
102
+ ...spec.sockets.flatMap(({ hostPath, childPath }) => ["--bind", hostPath, childPath]),
103
+ // External directory projections shadow their parent to exclude siblings. Do
104
+ // that before writable roots. The root binds below restore paths that the
105
+ // projection's parent shadow may have hidden.
106
+ ...externalReadOnlyMountArguments(externalDirectoryMounts, externalFileMounts, writableRoots),
107
+ ...writableRoots.flatMap((root) => ["--dir", root]),
108
+ "--bind",
109
+ spec.workspace,
110
+ spec.workspace,
111
+ // Each extra root is bound exactly as the workspace is, and only ever from an
112
+ // argv-parsed flag: a repository that could name its own writable root would be
113
+ // granting itself authority (ADR 0016).
114
+ ...spec.additionalWritableRoots.flatMap((root) => ["--bind", root, root]),
115
+ // These mounts must follow the writable roots: otherwise binding a workspace
116
+ // would mask a read-only projection nested inside it.
117
+ ...ordinaryPaths.flatMap((path) => readOnlyMountArguments(path)),
118
+ // A directory already inside a writable root needs no parent shadow: that root
119
+ // already exposes its siblings by policy. Rebind only the requested directory.
120
+ ...nestedReadOnlyMountArguments([...nestedDirectoryMounts, ...nestedFileMounts]),
121
+ // After the workspace bind: these destinations sit inside it, and an earlier mount
122
+ // would be masked when the workspace is bound over them.
123
+ ...spec.readOnlyBinaries.flatMap(({ source, destination }) => ["--ro-bind", source, destination]),
124
+ "--dev",
125
+ "/dev",
126
+ "--proc",
127
+ "/proc",
128
+ "--chdir",
129
+ spec.workspace,
130
+ ...Object.entries(spec.environment).flatMap(([name, value]) => ["--setenv", name, value]),
131
+ "--",
132
+ spec.command,
133
+ ...spec.args,
134
+ ];
135
+ }
136
+ //# sourceMappingURL=bwrap-arguments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bwrap-arguments.js","sourceRoot":"","sources":["../../../src/core/sandbox/bwrap-arguments.ts"],"names":[],"mappings":"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","sourcesContent":["import { statSync } from \"node:fs\";\nimport { dirname, resolve, sep } from \"node:path\";\n\n/**\n * The single source of the child's `bwrap` argv.\n *\n * Extracted when per-command escalation needed a second child. Two hand-maintained argv\n * builders is the divergence ADR 0010 exists to prevent for tool contracts, and it is\n * worse here: a mount tightened in the primary child and missed in the escalated one\n * would be invisible until the escalated path was the one that mattered. Everything that\n * differs between the two children is an input to this function, so there is nothing to\n * keep in sync by hand.\n */\n\n/** A unix socket projected from the host into the child. */\nexport interface BwrapSocketMount {\n\treadonly hostPath: string;\n\treadonly childPath: string;\n}\n\n/** A host executable placed read-only at an exact path inside the child. */\nexport interface BwrapBinaryMount {\n\treadonly source: string;\n\treadonly destination: string;\n}\n\nexport interface BwrapSpec {\n\t/** The primary writable root, and where the child starts. */\n\treadonly workspace: string;\n\t/** Further writable roots, each bound exactly as the workspace is. */\n\treadonly additionalWritableRoots: readonly string[];\n\t/** Directories whose contents the child may read but not write. */\n\treadonly readOnlyPaths: readonly string[];\n\t/** Individual read-only files, projected by descriptor from index 3 upward. */\n\treadonly readOnlyFiles: readonly string[];\n\treadonly readOnlyBinaries: readonly BwrapBinaryMount[];\n\treadonly sockets: readonly BwrapSocketMount[];\n\treadonly environment: Readonly<Record<string, string>>;\n\treadonly command: string;\n\treadonly args: readonly string[];\n}\n\nfunction ancestorDirectoryArguments(directory: string): string[] {\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\treturn ancestors.reverse().flatMap((ancestor) => [\"--dir\", ancestor]);\n}\n\nexport function readOnlyMountArguments(path: string): string[] {\n\tconst target = resolve(path);\n\tconst parent = dirname(target);\n\treturn [...ancestorDirectoryArguments(parent), \"--ro-bind\", parent, parent];\n}\n\n/** Directory paths are mounted from matching fd 3 onward to preserve sibling exclusion. */\nexport function descriptorBackedReadOnlyPaths(paths: readonly string[]): string[] {\n\treturn paths.filter((path) => {\n\t\ttry {\n\t\t\treturn statSync(resolve(path)).isDirectory();\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t});\n}\n\ninterface DescriptorBackedDirectory {\n\treadonly path: string;\n\treadonly descriptor: number;\n}\n\ninterface DescriptorBackedFile extends DescriptorBackedDirectory {}\n\ninterface ProjectionGroup {\n\treadonly directories: DescriptorBackedDirectory[];\n\treadonly files: DescriptorBackedFile[];\n}\n\nfunction isWithin(path: string, root: string): boolean {\n\tconst target = resolve(path);\n\tconst boundary = resolve(root);\n\treturn target === boundary || target.startsWith(boundary.endsWith(sep) ? boundary : `${boundary}${sep}`);\n}\n\nfunction nestedReadOnlyMountArguments(projections: readonly DescriptorBackedDirectory[]): string[] {\n\treturn projections.flatMap(({ path, descriptor }) => {\n\t\tconst target = resolve(path);\n\t\treturn [\"--ro-bind\", `/proc/self/fd/${descriptor}`, target];\n\t});\n}\n\nfunction externalReadOnlyMountArguments(\n\tdirectories: readonly DescriptorBackedDirectory[],\n\tfiles: readonly DescriptorBackedFile[],\n\twritableRoots: readonly string[],\n): string[] {\n\tconst byParent = new Map<string, ProjectionGroup>();\n\tconst add = (kind: keyof ProjectionGroup, projection: DescriptorBackedDirectory): void => {\n\t\tconst parent = dirname(resolve(projection.path));\n\t\tconst group = byParent.get(parent) ?? { directories: [], files: [] };\n\t\tgroup[kind].push(projection);\n\t\tbyParent.set(parent, group);\n\t};\n\tfor (const directory of directories) add(\"directories\", directory);\n\tfor (const file of files) add(\"files\", file);\n\n\tconst groups = [...byParent].sort(([left], [right]) => left.split(sep).length - right.split(sep).length);\n\tconst laterMountpoints = [...byParent.keys(), ...writableRoots];\n\treturn groups.flatMap(([parent, group]) => [\n\t\t...ancestorDirectoryArguments(parent),\n\t\t\"--tmpfs\",\n\t\tparent,\n\t\t...laterMountpoints\n\t\t\t.filter((mountpoint) => mountpoint !== parent && isWithin(mountpoint, parent))\n\t\t\t.flatMap(ancestorDirectoryArguments),\n\t\t...group.directories.flatMap(({ path, descriptor }) => {\n\t\t\tconst target = resolve(path);\n\t\t\treturn [\"--dir\", target, \"--ro-bind\", `/proc/self/fd/${descriptor}`, target];\n\t\t}),\n\t\t...group.files.flatMap(({ path, descriptor }) => [\n\t\t\t\"--perms\",\n\t\t\t\"0400\",\n\t\t\t\"--file\",\n\t\t\tString(descriptor),\n\t\t\tresolve(path),\n\t\t]),\n\t\t\"--remount-ro\",\n\t\tparent,\n\t]);\n}\n\n/** Build the complete argv. Mount order below is load-bearing; the comments say why. */\nexport function buildBwrapArguments(spec: BwrapSpec): string[] {\n\tconst directoryPaths = descriptorBackedReadOnlyPaths(spec.readOnlyPaths);\n\tconst ordinaryPaths = spec.readOnlyPaths.filter((path) => !directoryPaths.includes(path));\n\tconst writableRoots = [spec.workspace, ...spec.additionalWritableRoots];\n\tconst directoryMounts = directoryPaths.map((path, index) => ({ path, descriptor: 3 + index }));\n\tconst fileMounts = spec.readOnlyFiles.map((path, index) => ({\n\t\tpath,\n\t\tdescriptor: 3 + directoryPaths.length + index,\n\t}));\n\tconst nestedDirectoryMounts = directoryMounts.filter(({ path }) =>\n\t\twritableRoots.some((root) => isWithin(path, root)),\n\t);\n\tconst externalDirectoryMounts = directoryMounts.filter(({ path }) =>\n\t\twritableRoots.every((root) => !isWithin(path, root)),\n\t);\n\tconst nestedFileMounts = fileMounts.filter(({ path }) => writableRoots.some((root) => isWithin(path, root)));\n\tconst externalFileMounts = fileMounts.filter(({ path }) => writableRoots.every((root) => !isWithin(path, root)));\n\treturn [\n\t\t\"--new-session\",\n\t\t\"--die-with-parent\",\n\t\t\"--unshare-user\",\n\t\t\"--unshare-pid\",\n\t\t\"--unshare-net\",\n\t\t\"--ro-bind\",\n\t\t\"/\",\n\t\t\"/\",\n\t\t\"--tmpfs\",\n\t\t\"/home\",\n\t\t// Immediately after the /home tmpfs: that is the only writable mount at this\n\t\t// point, so it is the only place bwrap can create a socket mountpoint.\n\t\t...spec.sockets.flatMap(({ hostPath, childPath }) => [\"--bind\", hostPath, childPath]),\n\t\t// External directory projections shadow their parent to exclude siblings. Do\n\t\t// that before writable roots. The root binds below restore paths that the\n\t\t// projection's parent shadow may have hidden.\n\t\t...externalReadOnlyMountArguments(externalDirectoryMounts, externalFileMounts, writableRoots),\n\t\t...writableRoots.flatMap((root) => [\"--dir\", root]),\n\t\t\"--bind\",\n\t\tspec.workspace,\n\t\tspec.workspace,\n\t\t// Each extra root is bound exactly as the workspace is, and only ever from an\n\t\t// argv-parsed flag: a repository that could name its own writable root would be\n\t\t// granting itself authority (ADR 0016).\n\t\t...spec.additionalWritableRoots.flatMap((root) => [\"--bind\", root, root]),\n\t\t// These mounts must follow the writable roots: otherwise binding a workspace\n\t\t// would mask a read-only projection nested inside it.\n\t\t...ordinaryPaths.flatMap((path) => readOnlyMountArguments(path)),\n\t\t// A directory already inside a writable root needs no parent shadow: that root\n\t\t// already exposes its siblings by policy. Rebind only the requested directory.\n\t\t...nestedReadOnlyMountArguments([...nestedDirectoryMounts, ...nestedFileMounts]),\n\t\t// After the workspace bind: these destinations sit inside it, and an earlier mount\n\t\t// would be masked when the workspace is bound over them.\n\t\t...spec.readOnlyBinaries.flatMap(({ source, destination }) => [\"--ro-bind\", source, destination]),\n\t\t\"--dev\",\n\t\t\"/dev\",\n\t\t\"--proc\",\n\t\t\"/proc\",\n\t\t\"--chdir\",\n\t\tspec.workspace,\n\t\t...Object.entries(spec.environment).flatMap(([name, value]) => [\"--setenv\", name, value]),\n\t\t\"--\",\n\t\tspec.command,\n\t\t...spec.args,\n\t];\n}\n"]}
@@ -1 +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\";\nimport { installSandboxNetworkRefusalMessages } from \"./network-refusal.ts\";\nimport { applyTerminalSize, TERMINAL_SIZE_PATH_VARIABLE } from \"./terminal-size.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// Only the sandboxed child installs this, which is what makes attributing a refused\n// request to the sandbox allowlist accurate rather than a guess about whose proxy replied.\ninstallSandboxNetworkRefusalMessages();\n// bwrap --new-session leaves this process without a controlling terminal, so\n// its stdout reports a window size frozen at sandbox creation. The supervisor\n// publishes the real one; adopt it before any UI is built.\nconst terminalSizePath = process.env[TERMINAL_SIZE_PATH_VARIABLE];\nif (terminalSizePath) applyTerminalSize(terminalSizePath);\n\nawait main(process.argv.slice(2), { sessionLeaseOwner: \"supervisor\" });\n"]}
1
+ {"version":3,"file":"child-entry.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/child-entry.ts"],"names":[],"mappings":"","sourcesContent":["import { parseCliCommand } from \"../../cli/args.ts\";\nimport { APP_NAME } from \"../../config.ts\";\nimport { main } from \"../../main.ts\";\nimport { setApexEnvironment } from \"../environment.ts\";\nimport { configureHttpDispatcher } from \"../http-dispatcher.ts\";\nimport { installSandboxNetworkRefusalMessages } from \"./network-refusal.ts\";\nimport { applyTerminalSize, TERMINAL_SIZE_PATH_VARIABLE } from \"./terminal-size.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// Only the sandboxed child installs this, which is what makes attributing a refused\n// request to the sandbox allowlist accurate rather than a guess about whose proxy replied.\ninstallSandboxNetworkRefusalMessages();\n// bwrap --new-session leaves this process without a controlling terminal, so\n// its stdout reports a window size frozen at sandbox creation. The supervisor\n// publishes the real one; adopt it before any UI is built.\nconst terminalSizePath = process.env[TERMINAL_SIZE_PATH_VARIABLE];\nif (terminalSizePath) applyTerminalSize(terminalSizePath);\n\nconst args = process.argv.slice(2);\nawait main(args, { sessionLeaseOwner: \"supervisor\", parsedCommand: parseCliCommand(args) });\n"]}
@@ -1,3 +1,4 @@
1
+ import { parseCliCommand } from "../../cli/args.js";
1
2
  import { APP_NAME } from "../../config.js";
2
3
  import { main } from "../../main.js";
3
4
  import { setApexEnvironment } from "../environment.js";
@@ -18,5 +19,6 @@ installSandboxNetworkRefusalMessages();
18
19
  const terminalSizePath = process.env[TERMINAL_SIZE_PATH_VARIABLE];
19
20
  if (terminalSizePath)
20
21
  applyTerminalSize(terminalSizePath);
21
- await main(process.argv.slice(2), { sessionLeaseOwner: "supervisor" });
22
+ const args = process.argv.slice(2);
23
+ await main(args, { sessionLeaseOwner: "supervisor", parsedCommand: parseCliCommand(args) });
22
24
  //# sourceMappingURL=child-entry.js.map
@@ -1 +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;AAChE,OAAO,EAAE,oCAAoC,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAEpF,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;AAC1B,oFAAoF;AACpF,2FAA2F;AAC3F,oCAAoC,EAAE,CAAC;AACvC,6EAA6E;AAC7E,8EAA8E;AAC9E,2DAA2D;AAC3D,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;AAClE,IAAI,gBAAgB;IAAE,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;AAE1D,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,iBAAiB,EAAE,YAAY,EAAE,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\";\nimport { installSandboxNetworkRefusalMessages } from \"./network-refusal.ts\";\nimport { applyTerminalSize, TERMINAL_SIZE_PATH_VARIABLE } from \"./terminal-size.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// Only the sandboxed child installs this, which is what makes attributing a refused\n// request to the sandbox allowlist accurate rather than a guess about whose proxy replied.\ninstallSandboxNetworkRefusalMessages();\n// bwrap --new-session leaves this process without a controlling terminal, so\n// its stdout reports a window size frozen at sandbox creation. The supervisor\n// publishes the real one; adopt it before any UI is built.\nconst terminalSizePath = process.env[TERMINAL_SIZE_PATH_VARIABLE];\nif (terminalSizePath) applyTerminalSize(terminalSizePath);\n\nawait main(process.argv.slice(2), { sessionLeaseOwner: \"supervisor\" });\n"]}
1
+ {"version":3,"file":"child-entry.js","sourceRoot":"","sources":["../../../src/core/sandbox/child-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,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;AAChE,OAAO,EAAE,oCAAoC,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAEpF,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;AAC1B,oFAAoF;AACpF,2FAA2F;AAC3F,oCAAoC,EAAE,CAAC;AACvC,6EAA6E;AAC7E,8EAA8E;AAC9E,2DAA2D;AAC3D,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;AAClE,IAAI,gBAAgB;IAAE,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;AAE1D,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,IAAI,CAAC,IAAI,EAAE,EAAE,iBAAiB,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC","sourcesContent":["import { parseCliCommand } from \"../../cli/args.ts\";\nimport { APP_NAME } from \"../../config.ts\";\nimport { main } from \"../../main.ts\";\nimport { setApexEnvironment } from \"../environment.ts\";\nimport { configureHttpDispatcher } from \"../http-dispatcher.ts\";\nimport { installSandboxNetworkRefusalMessages } from \"./network-refusal.ts\";\nimport { applyTerminalSize, TERMINAL_SIZE_PATH_VARIABLE } from \"./terminal-size.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// Only the sandboxed child installs this, which is what makes attributing a refused\n// request to the sandbox allowlist accurate rather than a guess about whose proxy replied.\ninstallSandboxNetworkRefusalMessages();\n// bwrap --new-session leaves this process without a controlling terminal, so\n// its stdout reports a window size frozen at sandbox creation. The supervisor\n// publishes the real one; adopt it before any UI is built.\nconst terminalSizePath = process.env[TERMINAL_SIZE_PATH_VARIABLE];\nif (terminalSizePath) applyTerminalSize(terminalSizePath);\n\nconst args = process.argv.slice(2);\nawait main(args, { sessionLeaseOwner: \"supervisor\", parsedCommand: parseCliCommand(args) });\n"]}
@@ -1,15 +1,84 @@
1
+ import type { ParsedCliCommand } from "../../cli/args.ts";
1
2
  import type { HostToolBinary } from "../../utils/tools-manager.ts";
2
3
  import type { SandboxLaunch } from "./supervisor.ts";
3
4
  /**
4
- * OS containment is the normal startup path for every command that can construct an
5
- * agent session. Commands that only inspect or maintain host configuration do not
6
- * create a runtime and therefore remain outside this child boundary.
5
+ * OS containment is the normal startup path for every parsed command that can
6
+ * construct an agent session. Classification consumes the same typed parse result
7
+ * as execution, so option values and text after `--` cannot impersonate metadata.
7
8
  */
8
- export declare function requiresSandboxedChild(args: readonly string[]): boolean;
9
+ export declare function requiresSandboxedChild(command: ParsedCliCommand): boolean;
9
10
  export interface SandboxedCliLaunch extends SandboxLaunch {
10
11
  readonly environment: NodeJS.ProcessEnv;
12
+ /**
13
+ * The supervisor's own 0700 state directory, outside every writable root the child
14
+ * has.
15
+ */
16
+ readonly supervisorStateDirectory: string;
11
17
  }
12
18
  export declare function getSandboxSessionDirectory(workspace: string): string;
19
+ /**
20
+ * Env var naming the file that carries the host's `policy` and `user` permission scopes
21
+ * into the child (PS.3).
22
+ *
23
+ * Deliberately absent from `SAFE_CHILD_ENVIRONMENT_KEYS`: only the supervisor's own
24
+ * resolution may name it, never the invoking shell and never a project file (ADR 0016).
25
+ */
26
+ export declare const POLICY_SNAPSHOT_PATH_VARIABLE = "APEX_CODE_POLICY_SNAPSHOT_PATH";
27
+ /**
28
+ * The marker asserting that an OS boundary is actually enforcing this process (PS.5).
29
+ *
30
+ * Set only on the supervisor's own launch environment for a child it is about to contain.
31
+ * It is not in the child-environment allowlist, so a value of this name in the invoking
32
+ * shell never survives into a child; the supervisor's own value is the only one that can.
33
+ * `core/sdk.ts` reads it for `sandbox: "required"` and keeps the same two literals -- a
34
+ * marker spelled two ways would be a drift the SDK could not detect at runtime, so
35
+ * `test/sdk-sandbox-contract.test.ts` pins them to each other.
36
+ */
37
+ export declare const SANDBOX_ENFORCEMENT_MARKER_VARIABLE = "APEX_CODE_SANDBOX_ENFORCED";
38
+ export declare const SANDBOX_ENFORCEMENT_MARKER_VALUE = "1";
39
+ export interface SupervisorStateDirectory {
40
+ /** A 0700 directory under the supervisor temp root, outside every child-writable root. */
41
+ readonly path: string;
42
+ readonly dispose: () => void;
43
+ }
44
+ /**
45
+ * Allocate the directory the supervisor keeps its own launch state in.
46
+ *
47
+ * Everything the supervisor writes for a launch -- the terminal handoff latch, the
48
+ * published terminal size, the network relay, the git credential helper, the permission
49
+ * snapshot -- used to live in `workspace/.apex-code/sandbox-state`, which is inside the
50
+ * one writable bind the child has. A child that planted a symlink there redirected the
51
+ * *next* launch's supervisor writes onto arbitrary host paths, and could rewrite the
52
+ * scripts the supervisor was about to run. Under the supervisor temp root the child sees
53
+ * these files through the read-only root bind (Linux) or Seatbelt's write denial outside
54
+ * the workspace (macOS), so it can neither replace them nor plant a link where one goes.
55
+ */
56
+ export declare function createSupervisorStateDirectory(): SupervisorStateDirectory;
57
+ /**
58
+ * The managed policy file this host resolves, read before the child's environment exists.
59
+ *
60
+ * `cli-launch`'s child-environment allowlist filters `APEX_CODE_POLICY_PATH` out, so a
61
+ * host that configured a custom managed policy had it silently disappear at the boundary
62
+ * and the child fell back to the system path -- the config-projection defect PS.3 closes.
63
+ * Resolution here mirrors `core/permissions/store.ts`'s own default exactly.
64
+ */
65
+ export declare function resolveSupervisorPolicyPath(environment?: NodeJS.ProcessEnv): string;
66
+ /**
67
+ * Capture the host `policy` and `user` permission scopes into the supervisor's private
68
+ * directory and return the file's path.
69
+ *
70
+ * Called before `buildSandboxedCliLaunch` repoints `APEX_CODE_CODING_AGENT_DIR` at a
71
+ * workspace directory the child can write. Without it the child's `user` scope came from a
72
+ * file the child itself owns, and its `policy` scope from a path the environment allowlist
73
+ * had already removed.
74
+ */
75
+ export declare function writeSupervisorPolicySnapshot(options: {
76
+ directory: string;
77
+ /** The *host* agent directory, before the launch repoints it. */
78
+ agentDir: string;
79
+ policyPath?: string;
80
+ environment?: NodeJS.ProcessEnv;
81
+ }): string;
13
82
  /**
14
83
  * Read supervisor policy only from global settings; project settings are untrusted here.
15
84
  *
@@ -65,8 +134,15 @@ export declare function buildSandboxedCliLaunch(options: {
65
134
  args: readonly string[];
66
135
  environment: NodeJS.ProcessEnv;
67
136
  allowedHosts?: readonly string[];
137
+ additionalWritableRoots?: readonly string[];
68
138
  readOnlyPaths?: readonly string[];
139
+ sessionPath?: string;
69
140
  authPath?: string;
141
+ /**
142
+ * A synthesized two-key git config, projected read-only so the child can author a
143
+ * commit as the user. Absent when the host had no global identity to resolve.
144
+ */
145
+ gitConfigPath?: string;
70
146
  toolBinaries?: readonly HostToolBinary[];
71
147
  /**
72
148
  * Host user-scope skill directories, pre-filtered by the caller to those that
@@ -78,5 +154,9 @@ export declare function buildSandboxedCliLaunch(options: {
78
154
  skillPaths?: HostSkillPaths;
79
155
  /** The supervisor-owned credential write channel, when one was opened. */
80
156
  credentialChannel?: SandboxLaunch["credentialChannel"];
157
+ /** The supervisor's private 0700 state directory for this launch. */
158
+ supervisorStateDirectory?: string;
159
+ /** The captured host permission scopes, named to the child read-only. */
160
+ policySnapshotPath?: string;
81
161
  }): SandboxedCliLaunch;
82
162
  //# sourceMappingURL=cli-launch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli-launch.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/cli-launch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAInE,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,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAW1G;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC9B,gCAAgC;IAChC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,oCAAoC;IACpC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,oEAAoE;AACpE,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,cAAc,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,SAAS,oBAAoB,EAAE,CAAC;CACnD;AA4CD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,sBAAsB,CAY/F;AA0GD;;;;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;IAClB,YAAY,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACzC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAC;CACvD,GAAG,kBAAkB,CA4DrB","sourcesContent":["import { existsSync, mkdirSync, readdirSync, realpathSync, rmSync, statSync } from \"node:fs\";\nimport { join, sep } from \"node:path\";\nimport type { HostToolBinary } from \"../../utils/tools-manager.ts\";\nimport { SettingsManager } from \"../settings-manager.ts\";\nimport { resolveWebSearchHost } from \"../web-search-provider.ts\";\nimport { resolveDefaultAllowedHosts } from \"./default-hosts.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\nexport function getSandboxSessionDirectory(workspace: string): string {\n\treturn join(workspace, \".apex-code\", \"sandbox-sessions\");\n}\n\n/**\n * Read supervisor policy only from global settings; project settings are untrusted here.\n *\n * The built-in provider hosts are added unless explicitly refused, because a deny-all\n * default made a fresh install unable to reach any model while giving the user no way to\n * learn which host to permit. Configured hosts are additive on top, and\n * `allowDefaultHosts: false` restores the strict behaviour for anyone who wants it.\n */\nexport function resolveSupervisorAllowedHosts(cwd: string, agentDir: string): readonly string[] | undefined {\n\tconst settings = SettingsManager.create(cwd, agentDir, { projectTrusted: false });\n\tconst network = settings.getNetworkSettings();\n\tconst configured = network?.allowedHosts ?? [];\n\tif (network?.allowDefaultHosts === false) return configured;\n\t// The configured `web_search` backend's host, added only when a credential for it\n\t// is present. Without this the tool would be configured and still unreachable,\n\t// which reads as a broken search rather than a missing allowlist entry. Nothing is\n\t// opened for a user who never set the key.\n\tconst webSearchHost = resolveWebSearchHost(settings.getWebSearchSettings());\n\treturn [...new Set([...resolveDefaultAllowedHosts(), ...configured, ...(webSearchHost ? [webSearchHost] : [])])];\n}\n\n/**\n * The host's two user-scope skill roots, each present only when it exists on the\n * host. Kept as two named slots rather than one list because\n * `core/package-manager.ts` discovers them in different modes -- root `.md` files\n * count as skills under `agentSkills` (\"pi\" mode) and are ignored under\n * `agentsHomeSkills` (\"agents\" mode), per `docs/skills.md` -- and a flat list of 0-2\n * paths cannot tell the child which root a lone survivor was.\n */\nexport interface HostSkillPaths {\n\t/** Host `<agentDir>/skills`. */\n\treadonly agentSkills?: string;\n\t/** Host `<home>/.agents/skills`. */\n\treadonly agentsHomeSkills?: string;\n}\n\n/** A candidate skill root that exists but was excluded, and why. */\nexport interface HostSkillPathRefusal {\n\treadonly root: keyof HostSkillPaths;\n\treadonly path: string;\n\treadonly reason: string;\n}\n\nexport interface ResolvedHostSkillPaths {\n\treadonly paths: HostSkillPaths;\n\treadonly refusals: readonly HostSkillPathRefusal[];\n}\n\n/**\n * True when `candidate` is the host home directory itself, or an ancestor of it.\n * Both paths must already be resolved (`realpathSync`), so a symlink can't disguise\n * either side. Mounting such a candidate read-only would re-expose the entire home\n * tree the sandbox's `--tmpfs /home` (Linux) / `(deny file-read* USER_HOME)` (macOS)\n * deliberately hides -- SSH keys, cloud credentials, other projects -- not just a\n * skills subtree.\n */\nfunction isHomeOrAncestorOfHome(candidate: string, home: string): boolean {\n\tif (candidate === home) return true;\n\tconst prefix = candidate.endsWith(sep) ? candidate : candidate + sep;\n\treturn home.startsWith(prefix);\n}\n\n/** Resolve one candidate root: absent, refused (symlinked onto the host home), or usable. */\nfunction resolveHostSkillRoot(\n\troot: keyof HostSkillPaths,\n\tcandidate: string,\n\thomeDir: string,\n): { path?: string; refusal?: HostSkillPathRefusal } {\n\tif (!existsSync(candidate)) return {};\n\tlet realCandidate: string;\n\tlet realHome: string;\n\ttry {\n\t\trealCandidate = realpathSync(candidate);\n\t\trealHome = realpathSync(homeDir);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\treturn { refusal: { root, path: candidate, reason: `could not resolve real path: ${message}` } };\n\t}\n\tif (isHomeOrAncestorOfHome(realCandidate, realHome)) {\n\t\treturn {\n\t\t\trefusal: {\n\t\t\t\troot,\n\t\t\t\tpath: candidate,\n\t\t\t\treason: \"resolves to the host home directory or an ancestor of it\",\n\t\t\t},\n\t\t};\n\t}\n\treturn { path: candidate };\n}\n\n/**\n * Resolve the host's user-scope skill directories, before the sandbox exists to hide\n * them. Read only from the runtime environment and the host agent directory --\n * never from project files -- matching ADR 0016's rule that supervisor policy is\n * trust-first. Mirrors `core/package-manager.ts`'s own user-scope roots so the two\n * sides agree on where a skill lives. A candidate that resolves (directly or via a\n * symlink) onto the host home or an ancestor of it is refused rather than mounted --\n * see `isHomeOrAncestorOfHome` -- and reported so the caller can surface a startup\n * diagnostic instead of silently mounting or silently skipping it.\n */\nexport function resolveHostSkillPaths(agentDir: string, homeDir: string): ResolvedHostSkillPaths {\n\tconst agentSkills = resolveHostSkillRoot(\"agentSkills\", join(agentDir, \"skills\"), homeDir);\n\tconst agentsHomeSkills = resolveHostSkillRoot(\"agentsHomeSkills\", join(homeDir, \".agents\", \"skills\"), homeDir);\n\treturn {\n\t\tpaths: {\n\t\t\t...(agentSkills.path ? { agentSkills: agentSkills.path } : {}),\n\t\t\t...(agentsHomeSkills.path ? { agentsHomeSkills: agentsHomeSkills.path } : {}),\n\t\t},\n\t\trefusals: [agentSkills.refusal, agentsHomeSkills.refusal].filter(\n\t\t\t(refusal): refusal is HostSkillPathRefusal => refusal !== undefined,\n\t\t),\n\t};\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\n// Credentials for tools rather than model providers. Separate from the provider set\n// above so the provider list stays a mirror of the documented provider reference.\nconst SAFE_TOOL_CREDENTIAL_KEYS = new Set([\"EXA_API_KEY\"]);\n\nfunction buildChildEnvironment(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n\treturn Object.fromEntries(\n\t\tObject.entries(environment).filter(\n\t\t\t([key]) =>\n\t\t\t\tSAFE_CHILD_ENVIRONMENT_KEYS.has(key) ||\n\t\t\t\tSAFE_PROVIDER_CREDENTIAL_KEYS.has(key) ||\n\t\t\t\tSAFE_TOOL_CREDENTIAL_KEYS.has(key),\n\t\t),\n\t);\n}\n\n/**\n * Drop empty files left in the child's tools directory by a previous launch.\n *\n * A projected tool is bind-mounted over a file there, and bwrap materialises that\n * mountpoint as an empty file on the host which outlives the namespace. If the host\n * tool later disappears, nothing is projected over the stub and the child would\n * otherwise find a 0-byte file where its binary should be. A real downloaded binary\n * is never empty, so size is a safe discriminator.\n */\nfunction clearStaleToolMountpoints(toolsDirectory: string): void {\n\tfor (const entry of readdirSync(toolsDirectory, { withFileTypes: true })) {\n\t\tif (!entry.isFile()) continue;\n\t\tconst entryPath = join(toolsDirectory, entry.name);\n\t\tif (statSync(entryPath).size === 0) {\n\t\t\trmSync(entryPath, { force: true });\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\ttoolBinaries?: readonly HostToolBinary[];\n\t/**\n\t * Host user-scope skill directories, pre-filtered by the caller to those that\n\t * exist and pass the host-home escape check (SKILL.4). Mounted read-only at their\n\t * original host location -- Seatbelt cannot remap a path, so this must hold for\n\t * both backends -- and named to the child via `APEX_CODE_SKILL_PATH_*` so its\n\t * discovery can find them under the sandbox's own repointed `HOME`/agent dir.\n\t */\n\tskillPaths?: HostSkillPaths;\n\t/** The supervisor-owned credential write channel, when one was opened. */\n\tcredentialChannel?: SandboxLaunch[\"credentialChannel\"];\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 = getSandboxSessionDirectory(options.workspace);\n\t// Mirrors getBinDir() as the child will compute it from APEX_CODE_CODING_AGENT_DIR,\n\t// so a projected tool lands exactly where the child's own lookup already checks.\n\tconst toolsDirectory = join(agentDirectory, \"bin\");\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 [\n\t\tstateDirectory,\n\t\tagentDirectory,\n\t\tsessionDirectory,\n\t\ttoolsDirectory,\n\t\t...Object.values(xdgDirectories),\n\t]) {\n\t\tmkdirSync(directory, { recursive: true });\n\t}\n\tclearStaleToolMountpoints(toolsDirectory);\n\tconst childEnvironment = buildChildEnvironment(options.environment);\n\tconst { agentSkills, agentsHomeSkills } = options.skillPaths ?? {};\n\tconst skillMountPaths = [agentSkills, agentsHomeSkills].filter((path): path is string => path !== undefined);\n\tconst readOnlyPaths = [...(options.readOnlyPaths ?? []), ...skillMountPaths];\n\tconst readOnlyFiles = options.authPath ? [options.authPath] : [];\n\tconst readOnlyBinaries = (options.toolBinaries ?? []).map((binary) => ({\n\t\tsource: binary.path,\n\t\tdestination: join(toolsDirectory, binary.name),\n\t}));\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\treadOnlyBinaries,\n\t\t// Travels on the launch contract so the platform backend projects the socket;\n\t\t// the environment entry above is what tells the child where to find it.\n\t\t...(options.credentialChannel ? { credentialChannel: options.credentialChannel } : {}),\n\t\tenvironment: {\n\t\t\t...childEnvironment,\n\t\t\t...(options.authPath ? { APEX_CODE_AUTH_PATH: options.authPath } : {}),\n\t\t\t...(options.credentialChannel\n\t\t\t\t? { APEX_CREDENTIAL_PROXY_PATH: options.credentialChannel.childSocketPath }\n\t\t\t\t: {}),\n\t\t\t// After childEnvironment: these come only from the supervisor's own\n\t\t\t// resolution, never from the invoking shell, so their value always wins over\n\t\t\t// anything of the same name that childEnvironment's allowlist let through.\n\t\t\t...(agentSkills ? { APEX_CODE_SKILL_PATH_AGENT: agentSkills } : {}),\n\t\t\t...(agentsHomeSkills ? { APEX_CODE_SKILL_PATH_AGENTS_HOME: agentsHomeSkills } : {}),\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"]}
1
+ {"version":3,"file":"cli-launch.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/cli-launch.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAInE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAEzE;AAED,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACxD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;IACxC;;;OAGG;IACH,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;CAC1C;AAED,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,mCAAmC,CAAC;AAE9E;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC,+BAA+B,CAAC;AAChF,eAAO,MAAM,gCAAgC,MAAM,CAAC;AAEpD,MAAM,WAAW,wBAAwB;IACxC,0FAA0F;IAC1F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,8BAA8B,IAAI,wBAAwB,CAczE;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,WAAW,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAKhG;AA0BD;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CAChC,GAAG,MAAM,CAWT;AAED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAW1G;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC9B,gCAAgC;IAChC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,oCAAoC;IACpC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,oEAAoE;AACpE,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,cAAc,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,SAAS,oBAAoB,EAAE,CAAC;CACnD;AA4CD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,sBAAsB,CAY/F;AA0GD;;;;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,uBAAuB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACzC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAC;IACvD,qEAAqE;IACrE,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,yEAAyE;IACzE,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B,GAAG,kBAAkB,CA0ErB","sourcesContent":["import {\n\tchmodSync,\n\texistsSync,\n\tmkdirSync,\n\tmkdtempSync,\n\treaddirSync,\n\treadFileSync,\n\trealpathSync,\n\trmSync,\n\tstatSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { join, sep } from \"node:path\";\nimport type { ParsedCliCommand } from \"../../cli/args.ts\";\nimport type { HostToolBinary } from \"../../utils/tools-manager.ts\";\nimport { SettingsManager } from \"../settings-manager.ts\";\nimport { resolveWebSearchHost } from \"../web-search-provider.ts\";\nimport { resolveDefaultAllowedHosts } from \"./default-hosts.ts\";\nimport type { SandboxLaunch } from \"./supervisor.ts\";\nimport { supervisorTempDirectory } from \"./supervisor-temp.ts\";\n\n/**\n * OS containment is the normal startup path for every parsed command that can\n * construct an agent session. Classification consumes the same typed parse result\n * as execution, so option values and text after `--` cannot impersonate metadata.\n */\nexport function requiresSandboxedChild(command: ParsedCliCommand): boolean {\n\treturn command.kind === \"session\";\n}\n\nexport interface SandboxedCliLaunch extends SandboxLaunch {\n\treadonly environment: NodeJS.ProcessEnv;\n\t/**\n\t * The supervisor's own 0700 state directory, outside every writable root the child\n\t * has.\n\t */\n\treadonly supervisorStateDirectory: string;\n}\n\nexport function getSandboxSessionDirectory(workspace: string): string {\n\treturn join(workspace, \".apex-code\", \"sandbox-sessions\");\n}\n\n/**\n * Env var naming the file that carries the host's `policy` and `user` permission scopes\n * into the child (PS.3).\n *\n * Deliberately absent from `SAFE_CHILD_ENVIRONMENT_KEYS`: only the supervisor's own\n * resolution may name it, never the invoking shell and never a project file (ADR 0016).\n */\nexport const POLICY_SNAPSHOT_PATH_VARIABLE = \"APEX_CODE_POLICY_SNAPSHOT_PATH\";\n\n/**\n * The marker asserting that an OS boundary is actually enforcing this process (PS.5).\n *\n * Set only on the supervisor's own launch environment for a child it is about to contain.\n * It is not in the child-environment allowlist, so a value of this name in the invoking\n * shell never survives into a child; the supervisor's own value is the only one that can.\n * `core/sdk.ts` reads it for `sandbox: \"required\"` and keeps the same two literals -- a\n * marker spelled two ways would be a drift the SDK could not detect at runtime, so\n * `test/sdk-sandbox-contract.test.ts` pins them to each other.\n */\nexport const SANDBOX_ENFORCEMENT_MARKER_VARIABLE = \"APEX_CODE_SANDBOX_ENFORCED\";\nexport const SANDBOX_ENFORCEMENT_MARKER_VALUE = \"1\";\n\nexport interface SupervisorStateDirectory {\n\t/** A 0700 directory under the supervisor temp root, outside every child-writable root. */\n\treadonly path: string;\n\treadonly dispose: () => void;\n}\n\n/**\n * Allocate the directory the supervisor keeps its own launch state in.\n *\n * Everything the supervisor writes for a launch -- the terminal handoff latch, the\n * published terminal size, the network relay, the git credential helper, the permission\n * snapshot -- used to live in `workspace/.apex-code/sandbox-state`, which is inside the\n * one writable bind the child has. A child that planted a symlink there redirected the\n * *next* launch's supervisor writes onto arbitrary host paths, and could rewrite the\n * scripts the supervisor was about to run. Under the supervisor temp root the child sees\n * these files through the read-only root bind (Linux) or Seatbelt's write denial outside\n * the workspace (macOS), so it can neither replace them nor plant a link where one goes.\n */\nexport function createSupervisorStateDirectory(): SupervisorStateDirectory {\n\tconst path = mkdtempSync(join(supervisorTempDirectory(), `apex-supervisor-${process.pid}-`), { encoding: \"utf8\" });\n\tchmodSync(path, 0o700);\n\treturn {\n\t\tpath,\n\t\tdispose: () => {\n\t\t\ttry {\n\t\t\t\trmSync(path, { force: true, recursive: true });\n\t\t\t} catch {\n\t\t\t\t// A leftover directory under the supervisor's own temp root is not worth\n\t\t\t\t// failing a session teardown over.\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * The managed policy file this host resolves, read before the child's environment exists.\n *\n * `cli-launch`'s child-environment allowlist filters `APEX_CODE_POLICY_PATH` out, so a\n * host that configured a custom managed policy had it silently disappear at the boundary\n * and the child fell back to the system path -- the config-projection defect PS.3 closes.\n * Resolution here mirrors `core/permissions/store.ts`'s own default exactly.\n */\nexport function resolveSupervisorPolicyPath(environment: NodeJS.ProcessEnv = process.env): string {\n\tif (environment.APEX_CODE_POLICY_PATH) return environment.APEX_CODE_POLICY_PATH;\n\treturn process.platform === \"win32\"\n\t\t? join(environment.ProgramData ?? \"C:\\\\ProgramData\", \"apex-code\", \"policy.json\")\n\t\t: \"/etc/apex-code/policy.json\";\n}\n\n/**\n * The two host-owned permission scopes, captured verbatim.\n *\n * Raw file text rather than parsed rules, so the child's store applies exactly the same\n * validation -- and reports exactly the same malformed-file error -- as it would have on\n * the host. A snapshot that pre-parsed would have to decide what a malformed host policy\n * means, and the only safe answer to that lives in the store.\n */\ninterface SupervisorPolicySnapshotFile {\n\treadonly version: 1;\n\treadonly policy: string | null;\n\treadonly user: string | null;\n}\n\nfunction readHostScopeText(path: string): string | null {\n\ttry {\n\t\treturn readFileSync(path, \"utf8\");\n\t} catch {\n\t\t// Absent is the ordinary case for both files; an unreadable one projects as absent\n\t\t// rather than as authority the child cannot see the shape of.\n\t\treturn null;\n\t}\n}\n\n/**\n * Capture the host `policy` and `user` permission scopes into the supervisor's private\n * directory and return the file's path.\n *\n * Called before `buildSandboxedCliLaunch` repoints `APEX_CODE_CODING_AGENT_DIR` at a\n * workspace directory the child can write. Without it the child's `user` scope came from a\n * file the child itself owns, and its `policy` scope from a path the environment allowlist\n * had already removed.\n */\nexport function writeSupervisorPolicySnapshot(options: {\n\tdirectory: string;\n\t/** The *host* agent directory, before the launch repoints it. */\n\tagentDir: string;\n\tpolicyPath?: string;\n\tenvironment?: NodeJS.ProcessEnv;\n}): string {\n\tconst directory = join(options.directory, \"policy\");\n\tmkdirSync(directory, { recursive: true, mode: 0o700 });\n\tconst snapshot: SupervisorPolicySnapshotFile = {\n\t\tversion: 1,\n\t\tpolicy: readHostScopeText(options.policyPath ?? resolveSupervisorPolicyPath(options.environment)),\n\t\tuser: readHostScopeText(join(options.agentDir, \"permissions.json\")),\n\t};\n\tconst path = join(directory, \"permission-snapshot.json\");\n\twriteFileSync(path, JSON.stringify(snapshot), { mode: 0o600 });\n\treturn path;\n}\n\n/**\n * Read supervisor policy only from global settings; project settings are untrusted here.\n *\n * The built-in provider hosts are added unless explicitly refused, because a deny-all\n * default made a fresh install unable to reach any model while giving the user no way to\n * learn which host to permit. Configured hosts are additive on top, and\n * `allowDefaultHosts: false` restores the strict behaviour for anyone who wants it.\n */\nexport function resolveSupervisorAllowedHosts(cwd: string, agentDir: string): readonly string[] | undefined {\n\tconst settings = SettingsManager.create(cwd, agentDir, { projectTrusted: false });\n\tconst network = settings.getNetworkSettings();\n\tconst configured = network?.allowedHosts ?? [];\n\tif (network?.allowDefaultHosts === false) return configured;\n\t// The configured `web_search` backend's host, added only when a credential for it\n\t// is present. Without this the tool would be configured and still unreachable,\n\t// which reads as a broken search rather than a missing allowlist entry. Nothing is\n\t// opened for a user who never set the key.\n\tconst webSearchHost = resolveWebSearchHost(settings.getWebSearchSettings());\n\treturn [...new Set([...resolveDefaultAllowedHosts(), ...configured, ...(webSearchHost ? [webSearchHost] : [])])];\n}\n\n/**\n * The host's two user-scope skill roots, each present only when it exists on the\n * host. Kept as two named slots rather than one list because\n * `core/package-manager.ts` discovers them in different modes -- root `.md` files\n * count as skills under `agentSkills` (\"pi\" mode) and are ignored under\n * `agentsHomeSkills` (\"agents\" mode), per `docs/skills.md` -- and a flat list of 0-2\n * paths cannot tell the child which root a lone survivor was.\n */\nexport interface HostSkillPaths {\n\t/** Host `<agentDir>/skills`. */\n\treadonly agentSkills?: string;\n\t/** Host `<home>/.agents/skills`. */\n\treadonly agentsHomeSkills?: string;\n}\n\n/** A candidate skill root that exists but was excluded, and why. */\nexport interface HostSkillPathRefusal {\n\treadonly root: keyof HostSkillPaths;\n\treadonly path: string;\n\treadonly reason: string;\n}\n\nexport interface ResolvedHostSkillPaths {\n\treadonly paths: HostSkillPaths;\n\treadonly refusals: readonly HostSkillPathRefusal[];\n}\n\n/**\n * True when `candidate` is the host home directory itself, or an ancestor of it.\n * Both paths must already be resolved (`realpathSync`), so a symlink can't disguise\n * either side. Mounting such a candidate read-only would re-expose the entire home\n * tree the sandbox's `--tmpfs /home` (Linux) / `(deny file-read* USER_HOME)` (macOS)\n * deliberately hides -- SSH keys, cloud credentials, other projects -- not just a\n * skills subtree.\n */\nfunction isHomeOrAncestorOfHome(candidate: string, home: string): boolean {\n\tif (candidate === home) return true;\n\tconst prefix = candidate.endsWith(sep) ? candidate : candidate + sep;\n\treturn home.startsWith(prefix);\n}\n\n/** Resolve one candidate root: absent, refused (symlinked onto the host home), or usable. */\nfunction resolveHostSkillRoot(\n\troot: keyof HostSkillPaths,\n\tcandidate: string,\n\thomeDir: string,\n): { path?: string; refusal?: HostSkillPathRefusal } {\n\tif (!existsSync(candidate)) return {};\n\tlet realCandidate: string;\n\tlet realHome: string;\n\ttry {\n\t\trealCandidate = realpathSync(candidate);\n\t\trealHome = realpathSync(homeDir);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\treturn { refusal: { root, path: candidate, reason: `could not resolve real path: ${message}` } };\n\t}\n\tif (isHomeOrAncestorOfHome(realCandidate, realHome)) {\n\t\treturn {\n\t\t\trefusal: {\n\t\t\t\troot,\n\t\t\t\tpath: candidate,\n\t\t\t\treason: \"resolves to the host home directory or an ancestor of it\",\n\t\t\t},\n\t\t};\n\t}\n\treturn { path: candidate };\n}\n\n/**\n * Resolve the host's user-scope skill directories, before the sandbox exists to hide\n * them. Read only from the runtime environment and the host agent directory --\n * never from project files -- matching ADR 0016's rule that supervisor policy is\n * trust-first. Mirrors `core/package-manager.ts`'s own user-scope roots so the two\n * sides agree on where a skill lives. A candidate that resolves (directly or via a\n * symlink) onto the host home or an ancestor of it is refused rather than mounted --\n * see `isHomeOrAncestorOfHome` -- and reported so the caller can surface a startup\n * diagnostic instead of silently mounting or silently skipping it.\n */\nexport function resolveHostSkillPaths(agentDir: string, homeDir: string): ResolvedHostSkillPaths {\n\tconst agentSkills = resolveHostSkillRoot(\"agentSkills\", join(agentDir, \"skills\"), homeDir);\n\tconst agentsHomeSkills = resolveHostSkillRoot(\"agentsHomeSkills\", join(homeDir, \".agents\", \"skills\"), homeDir);\n\treturn {\n\t\tpaths: {\n\t\t\t...(agentSkills.path ? { agentSkills: agentSkills.path } : {}),\n\t\t\t...(agentsHomeSkills.path ? { agentsHomeSkills: agentsHomeSkills.path } : {}),\n\t\t},\n\t\trefusals: [agentSkills.refusal, agentsHomeSkills.refusal].filter(\n\t\t\t(refusal): refusal is HostSkillPathRefusal => refusal !== undefined,\n\t\t),\n\t};\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\n// Credentials for tools rather than model providers. Separate from the provider set\n// above so the provider list stays a mirror of the documented provider reference.\nconst SAFE_TOOL_CREDENTIAL_KEYS = new Set([\"EXA_API_KEY\"]);\n\nfunction buildChildEnvironment(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n\treturn Object.fromEntries(\n\t\tObject.entries(environment).filter(\n\t\t\t([key]) =>\n\t\t\t\tSAFE_CHILD_ENVIRONMENT_KEYS.has(key) ||\n\t\t\t\tSAFE_PROVIDER_CREDENTIAL_KEYS.has(key) ||\n\t\t\t\tSAFE_TOOL_CREDENTIAL_KEYS.has(key),\n\t\t),\n\t);\n}\n\n/**\n * Drop empty files left in the child's tools directory by a previous launch.\n *\n * A projected tool is bind-mounted over a file there, and bwrap materialises that\n * mountpoint as an empty file on the host which outlives the namespace. If the host\n * tool later disappears, nothing is projected over the stub and the child would\n * otherwise find a 0-byte file where its binary should be. A real downloaded binary\n * is never empty, so size is a safe discriminator.\n */\nfunction clearStaleToolMountpoints(toolsDirectory: string): void {\n\tfor (const entry of readdirSync(toolsDirectory, { withFileTypes: true })) {\n\t\tif (!entry.isFile()) continue;\n\t\tconst entryPath = join(toolsDirectory, entry.name);\n\t\tif (statSync(entryPath).size === 0) {\n\t\t\trmSync(entryPath, { force: true });\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\tadditionalWritableRoots?: readonly string[];\n\treadOnlyPaths?: readonly string[];\n\tsessionPath?: string;\n\tauthPath?: string;\n\t/**\n\t * A synthesized two-key git config, projected read-only so the child can author a\n\t * commit as the user. Absent when the host had no global identity to resolve.\n\t */\n\tgitConfigPath?: string;\n\ttoolBinaries?: readonly HostToolBinary[];\n\t/**\n\t * Host user-scope skill directories, pre-filtered by the caller to those that\n\t * exist and pass the host-home escape check (SKILL.4). Mounted read-only at their\n\t * original host location -- Seatbelt cannot remap a path, so this must hold for\n\t * both backends -- and named to the child via `APEX_CODE_SKILL_PATH_*` so its\n\t * discovery can find them under the sandbox's own repointed `HOME`/agent dir.\n\t */\n\tskillPaths?: HostSkillPaths;\n\t/** The supervisor-owned credential write channel, when one was opened. */\n\tcredentialChannel?: SandboxLaunch[\"credentialChannel\"];\n\t/** The supervisor's private 0700 state directory for this launch. */\n\tsupervisorStateDirectory?: string;\n\t/** The captured host permission scopes, named to the child read-only. */\n\tpolicySnapshotPath?: string;\n}): SandboxedCliLaunch {\n\tconst supervisorStateDirectory = options.supervisorStateDirectory ?? createSupervisorStateDirectory().path;\n\tconst stateDirectory = join(options.workspace, \".apex-code\", \"sandbox-state\");\n\tconst agentDirectory = join(options.workspace, \".apex-code\", \"sandbox-agent\");\n\tconst sessionDirectory = getSandboxSessionDirectory(options.workspace);\n\t// Mirrors getBinDir() as the child will compute it from APEX_CODE_CODING_AGENT_DIR,\n\t// so a projected tool lands exactly where the child's own lookup already checks.\n\tconst toolsDirectory = join(agentDirectory, \"bin\");\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 [\n\t\tstateDirectory,\n\t\tagentDirectory,\n\t\tsessionDirectory,\n\t\ttoolsDirectory,\n\t\t...Object.values(xdgDirectories),\n\t]) {\n\t\tmkdirSync(directory, { recursive: true });\n\t}\n\tclearStaleToolMountpoints(toolsDirectory);\n\tconst childEnvironment = buildChildEnvironment(options.environment);\n\tconst { agentSkills, agentsHomeSkills } = options.skillPaths ?? {};\n\tconst skillMountPaths = [agentSkills, agentsHomeSkills].filter((path): path is string => path !== undefined);\n\tconst readOnlyPaths = [...(options.readOnlyPaths ?? []), ...skillMountPaths];\n\tconst readOnlyFiles = [options.sessionPath, options.authPath, options.gitConfigPath].filter(\n\t\t(path): path is string => path !== undefined,\n\t);\n\tconst readOnlyBinaries = (options.toolBinaries ?? []).map((binary) => ({\n\t\tsource: binary.path,\n\t\tdestination: join(toolsDirectory, binary.name),\n\t}));\n\treturn {\n\t\tcommand: options.command,\n\t\targs: [...options.args],\n\t\tpolicy: {\n\t\t\tworkspace: options.workspace,\n\t\t\tallowedHosts: options.allowedHosts ?? [],\n\t\t\tadditionalWritableRoots: options.additionalWritableRoots ?? [],\n\t\t},\n\t\treadOnlyPaths,\n\t\treadOnlyFiles,\n\t\treadOnlyBinaries,\n\t\t// Travels on the launch contract so the platform backend projects the socket;\n\t\t// the environment entry above is what tells the child where to find it.\n\t\t...(options.credentialChannel ? { credentialChannel: options.credentialChannel } : {}),\n\t\tsupervisorStateDirectory,\n\t\tenvironment: {\n\t\t\t...childEnvironment,\n\t\t\t...(options.authPath ? { APEX_CODE_AUTH_PATH: options.authPath } : {}),\n\t\t\t...(options.credentialChannel\n\t\t\t\t? { APEX_CREDENTIAL_PROXY_PATH: options.credentialChannel.childSocketPath }\n\t\t\t\t: {}),\n\t\t\t// After childEnvironment: these come only from the supervisor's own\n\t\t\t// resolution, never from the invoking shell, so their value always wins over\n\t\t\t// anything of the same name that childEnvironment's allowlist let through.\n\t\t\t...(options.gitConfigPath ? { GIT_CONFIG_GLOBAL: options.gitConfigPath } : {}),\n\t\t\t...(agentSkills ? { APEX_CODE_SKILL_PATH_AGENT: agentSkills } : {}),\n\t\t\t...(agentsHomeSkills ? { APEX_CODE_SKILL_PATH_AGENTS_HOME: agentsHomeSkills } : {}),\n\t\t\t// The host's own permission scopes, and the assertion that a boundary is\n\t\t\t// enforcing this process. Both are supervisor-resolved for exactly the reason\n\t\t\t// `agentDirectory` below is: the child's copy is writable by the child.\n\t\t\t...(options.policySnapshotPath ? { [POLICY_SNAPSHOT_PATH_VARIABLE]: options.policySnapshotPath } : {}),\n\t\t\t[SANDBOX_ENFORCEMENT_MARKER_VARIABLE]: SANDBOX_ENFORCEMENT_MARKER_VALUE,\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"]}