@machina.ai/cell-cli-core 1.38.1-rc2 → 1.40.1-rc2

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 (822) hide show
  1. package/dist/docs/AFTER_MERGE_PROMPT.md +1 -1
  2. package/dist/docs/admin/enterprise-controls.md +1 -1
  3. package/dist/docs/changelogs/index.md +42 -0
  4. package/dist/docs/changelogs/latest.md +254 -361
  5. package/dist/docs/changelogs/preview.md +237 -406
  6. package/dist/docs/cli/acp-mode.md +6 -6
  7. package/dist/docs/cli/auto-memory.md +143 -0
  8. package/dist/docs/cli/checkpointing.md +5 -5
  9. package/dist/docs/cli/cli-reference.md +12 -11
  10. package/dist/docs/cli/creating-skills.md +2 -2
  11. package/dist/docs/cli/custom-commands.md +15 -14
  12. package/dist/docs/cli/enterprise.md +17 -14
  13. package/dist/docs/cli/gemini-ignore.md +2 -2
  14. package/dist/docs/cli/generation-settings.md +21 -20
  15. package/dist/docs/cli/model-routing.md +2 -2
  16. package/dist/docs/cli/model-steering.md +1 -1
  17. package/dist/docs/cli/plan-mode.md +11 -6
  18. package/dist/docs/cli/sandbox.md +7 -5
  19. package/dist/docs/cli/settings.md +32 -28
  20. package/dist/docs/cli/system-prompt.md +8 -8
  21. package/dist/docs/cli/telemetry.md +18 -11
  22. package/dist/docs/cli/themes.md +2 -2
  23. package/dist/docs/cli/trusted-folders.md +41 -13
  24. package/dist/docs/cli/tutorials/mcp-setup.md +1 -1
  25. package/dist/docs/cli/tutorials/memory-management.md +3 -1
  26. package/dist/docs/cli/tutorials/plan-mode-steering.md +2 -2
  27. package/dist/docs/cli/tutorials/session-management.md +1 -1
  28. package/dist/docs/cli/tutorials/shell-commands.md +1 -1
  29. package/dist/docs/cli/tutorials/task-planning.md +3 -3
  30. package/dist/docs/core/index.md +5 -6
  31. package/dist/docs/core/local-model-routing.md +1 -1
  32. package/dist/docs/core/remote-agents.md +1 -1
  33. package/dist/docs/core/subagents.md +38 -8
  34. package/dist/docs/extensions/best-practices.md +5 -4
  35. package/dist/docs/extensions/reference.md +6 -5
  36. package/dist/docs/extensions/releasing.md +6 -5
  37. package/dist/docs/extensions/writing-extensions.md +11 -11
  38. package/dist/docs/get-started/{authentication.md → authentication.mdx} +139 -93
  39. package/dist/docs/get-started/gemini-3.md +1 -1
  40. package/dist/docs/get-started/index.md +4 -4
  41. package/dist/docs/get-started/installation.mdx +201 -0
  42. package/dist/docs/hooks/best-practices.md +18 -17
  43. package/dist/docs/hooks/index.md +10 -8
  44. package/dist/docs/hooks/reference.md +10 -10
  45. package/dist/docs/ide-integration/ide-companion-spec.md +14 -14
  46. package/dist/docs/ide-integration/index.md +4 -4
  47. package/dist/docs/index.md +2 -2
  48. package/dist/docs/integration-tests.md +84 -2
  49. package/dist/docs/issue-and-pr-automation.md +8 -7
  50. package/dist/docs/npm.md +2 -2
  51. package/dist/docs/reference/commands.md +11 -11
  52. package/dist/docs/reference/configuration.md +150 -47
  53. package/dist/docs/reference/keyboard-shortcuts.md +79 -2
  54. package/dist/docs/reference/memport.md +2 -3
  55. package/dist/docs/reference/policy-engine.md +60 -26
  56. package/dist/docs/reference/tools.md +38 -4
  57. package/dist/docs/release-confidence.md +1 -1
  58. package/dist/docs/releases.md +19 -19
  59. package/dist/docs/resources/faq.md +5 -5
  60. package/dist/docs/resources/tos-privacy.md +10 -9
  61. package/dist/docs/resources/troubleshooting.md +17 -16
  62. package/dist/docs/resources/uninstall.md +5 -4
  63. package/dist/docs/sidebar.json +13 -1
  64. package/dist/docs/tools/ask-user.md +3 -3
  65. package/dist/docs/tools/file-system.md +7 -7
  66. package/dist/docs/tools/mcp-resources.md +44 -0
  67. package/dist/docs/tools/mcp-server.md +42 -39
  68. package/dist/docs/tools/shell.md +5 -5
  69. package/dist/docs/tools/tracker.md +61 -0
  70. package/dist/package.json +5 -4
  71. package/dist/src/agent/content-utils.d.ts +0 -6
  72. package/dist/src/agent/content-utils.js +0 -14
  73. package/dist/src/agent/content-utils.js.map +1 -1
  74. package/dist/src/agent/content-utils.test.js +1 -18
  75. package/dist/src/agent/content-utils.test.js.map +1 -1
  76. package/dist/src/agent/event-translator.js +8 -3
  77. package/dist/src/agent/event-translator.js.map +1 -1
  78. package/dist/src/agent/event-translator.test.js +14 -9
  79. package/dist/src/agent/event-translator.test.js.map +1 -1
  80. package/dist/src/agent/legacy-agent-session.js +9 -3
  81. package/dist/src/agent/legacy-agent-session.js.map +1 -1
  82. package/dist/src/agent/legacy-agent-session.test.js +4 -3
  83. package/dist/src/agent/legacy-agent-session.test.js.map +1 -1
  84. package/dist/src/agent/tool-display-utils.d.ts +30 -0
  85. package/dist/src/agent/tool-display-utils.js +69 -0
  86. package/dist/src/agent/tool-display-utils.js.map +1 -0
  87. package/dist/src/agent/tool-display-utils.test.js +101 -0
  88. package/dist/src/agent/tool-display-utils.test.js.map +1 -0
  89. package/dist/src/agent/types.d.ts +25 -5
  90. package/dist/src/agents/a2aUtils.js +28 -15
  91. package/dist/src/agents/a2aUtils.js.map +1 -1
  92. package/dist/src/agents/a2aUtils.test.js +43 -0
  93. package/dist/src/agents/a2aUtils.test.js.map +1 -1
  94. package/dist/src/agents/agent-tool.d.ts +31 -0
  95. package/dist/src/agents/agent-tool.js +155 -0
  96. package/dist/src/agents/agent-tool.js.map +1 -0
  97. package/dist/src/agents/agent-tool.test.js +110 -0
  98. package/dist/src/agents/agent-tool.test.js.map +1 -0
  99. package/dist/src/agents/agentLoader.d.ts +79 -4
  100. package/dist/src/agents/agentLoader.js +40 -2
  101. package/dist/src/agents/agentLoader.js.map +1 -1
  102. package/dist/src/agents/agentLoader.test.js +32 -0
  103. package/dist/src/agents/agentLoader.test.js.map +1 -1
  104. package/dist/src/agents/browser/analyzeScreenshot.js +1 -1
  105. package/dist/src/agents/browser/analyzeScreenshot.js.map +1 -1
  106. package/dist/src/agents/browser/analyzeScreenshot.test.js +19 -7
  107. package/dist/src/agents/browser/analyzeScreenshot.test.js.map +1 -1
  108. package/dist/src/agents/browser/browserAgentInvocation.d.ts +2 -2
  109. package/dist/src/agents/browser/browserAgentInvocation.js +2 -1
  110. package/dist/src/agents/browser/browserAgentInvocation.js.map +1 -1
  111. package/dist/src/agents/browser/browserAgentInvocation.test.js +61 -17
  112. package/dist/src/agents/browser/browserAgentInvocation.test.js.map +1 -1
  113. package/dist/src/agents/browser/mcpToolWrapper.js +1 -1
  114. package/dist/src/agents/browser/mcpToolWrapper.js.map +1 -1
  115. package/dist/src/agents/browser/mcpToolWrapper.test.js +22 -10
  116. package/dist/src/agents/browser/mcpToolWrapper.test.js.map +1 -1
  117. package/dist/src/agents/codebase-investigator.js +2 -2
  118. package/dist/src/agents/codebase-investigator.js.map +1 -1
  119. package/dist/src/agents/generalist-agent.js +3 -2
  120. package/dist/src/agents/generalist-agent.js.map +1 -1
  121. package/dist/src/agents/generalist-agent.test.js +1 -0
  122. package/dist/src/agents/generalist-agent.test.js.map +1 -1
  123. package/dist/src/agents/local-executor.d.ts +1 -1
  124. package/dist/src/agents/local-executor.js +10 -7
  125. package/dist/src/agents/local-executor.js.map +1 -1
  126. package/dist/src/agents/local-executor.test.js +5 -3
  127. package/dist/src/agents/local-executor.test.js.map +1 -1
  128. package/dist/src/agents/local-invocation.d.ts +2 -2
  129. package/dist/src/agents/local-invocation.js +8 -2
  130. package/dist/src/agents/local-invocation.js.map +1 -1
  131. package/dist/src/agents/local-invocation.test.js +29 -13
  132. package/dist/src/agents/local-invocation.test.js.map +1 -1
  133. package/dist/src/agents/registry.d.ts +2 -0
  134. package/dist/src/agents/registry.js +20 -19
  135. package/dist/src/agents/registry.js.map +1 -1
  136. package/dist/src/agents/registry.test.js +19 -30
  137. package/dist/src/agents/registry.test.js.map +1 -1
  138. package/dist/src/agents/remote-invocation.d.ts +3 -4
  139. package/dist/src/agents/remote-invocation.js +2 -1
  140. package/dist/src/agents/remote-invocation.js.map +1 -1
  141. package/dist/src/agents/remote-invocation.test.js +45 -18
  142. package/dist/src/agents/remote-invocation.test.js.map +1 -1
  143. package/dist/src/agents/skill-extraction-agent.d.ts +3 -2
  144. package/dist/src/agents/skill-extraction-agent.js +99 -56
  145. package/dist/src/agents/skill-extraction-agent.js.map +1 -1
  146. package/dist/src/agents/skill-extraction-agent.test.js +54 -0
  147. package/dist/src/agents/skill-extraction-agent.test.js.map +1 -0
  148. package/dist/src/availability/policyCatalog.js +1 -1
  149. package/dist/src/availability/policyCatalog.js.map +1 -1
  150. package/dist/src/availability/policyCatalog.test.js +1 -1
  151. package/dist/src/availability/policyCatalog.test.js.map +1 -1
  152. package/dist/src/code_assist/oauth2.js +14 -4
  153. package/dist/src/code_assist/oauth2.js.map +1 -1
  154. package/dist/src/commands/memory.d.ts +77 -0
  155. package/dist/src/commands/memory.js +494 -0
  156. package/dist/src/commands/memory.js.map +1 -1
  157. package/dist/src/commands/memory.test.js +720 -1
  158. package/dist/src/commands/memory.test.js.map +1 -1
  159. package/dist/src/config/config-agents-reload.test.js +26 -31
  160. package/dist/src/config/config-agents-reload.test.js.map +1 -1
  161. package/dist/src/config/config.d.ts +24 -10
  162. package/dist/src/config/config.js +148 -82
  163. package/dist/src/config/config.js.map +1 -1
  164. package/dist/src/config/config.test.js +373 -10
  165. package/dist/src/config/config.test.js.map +1 -1
  166. package/dist/src/config/constants.d.ts +1 -0
  167. package/dist/src/config/constants.js +2 -0
  168. package/dist/src/config/constants.js.map +1 -1
  169. package/dist/src/config/defaultModelConfigs.js +7 -7
  170. package/dist/src/config/defaultModelConfigs.js.map +1 -1
  171. package/dist/src/config/memory.js +1 -1
  172. package/dist/src/config/memory.js.map +1 -1
  173. package/dist/src/config/path-validation.test.js +15 -6
  174. package/dist/src/config/path-validation.test.js.map +1 -1
  175. package/dist/src/config/projectRegistry.js +113 -32
  176. package/dist/src/config/projectRegistry.js.map +1 -1
  177. package/dist/src/config/projectRegistry.test.js +51 -0
  178. package/dist/src/config/projectRegistry.test.js.map +1 -1
  179. package/dist/src/config/storage.d.ts +5 -1
  180. package/dist/src/config/storage.js +14 -1
  181. package/dist/src/config/storage.js.map +1 -1
  182. package/dist/src/config/storage.test.js +12 -0
  183. package/dist/src/config/storage.test.js.map +1 -1
  184. package/dist/src/confirmation-bus/message-bus.d.ts +4 -1
  185. package/dist/src/confirmation-bus/message-bus.js +39 -1
  186. package/dist/src/confirmation-bus/message-bus.js.map +1 -1
  187. package/dist/src/confirmation-bus/message-bus.test.js +43 -0
  188. package/dist/src/confirmation-bus/message-bus.test.js.map +1 -1
  189. package/dist/src/context/config/configLoader.d.ts +13 -0
  190. package/dist/src/context/config/configLoader.js +65 -0
  191. package/dist/src/context/config/configLoader.js.map +1 -0
  192. package/dist/src/context/config/configLoader.test.d.ts +6 -0
  193. package/dist/src/context/config/configLoader.test.js +79 -0
  194. package/dist/src/context/config/configLoader.test.js.map +1 -0
  195. package/dist/src/context/config/profiles.d.ts +17 -0
  196. package/dist/src/context/config/profiles.js +93 -0
  197. package/dist/src/context/config/profiles.js.map +1 -0
  198. package/dist/src/context/config/registry.d.ts +21 -0
  199. package/dist/src/context/config/registry.js +32 -0
  200. package/dist/src/context/config/registry.js.map +1 -0
  201. package/dist/src/context/config/schema.d.ts +45 -0
  202. package/dist/src/context/config/schema.js +47 -0
  203. package/dist/src/context/config/schema.js.map +1 -0
  204. package/dist/src/context/config/types.d.ts +39 -0
  205. package/dist/src/context/config/types.js +7 -0
  206. package/dist/src/context/config/types.js.map +1 -0
  207. package/dist/src/context/contextManager.barrier.test.d.ts +6 -0
  208. package/dist/src/context/contextManager.barrier.test.js +56 -0
  209. package/dist/src/context/contextManager.barrier.test.js.map +1 -0
  210. package/dist/src/context/contextManager.d.ts +49 -0
  211. package/dist/src/context/contextManager.js +120 -0
  212. package/dist/src/context/contextManager.js.map +1 -0
  213. package/dist/src/context/eventBus.d.ts +28 -0
  214. package/dist/src/context/eventBus.js +27 -0
  215. package/dist/src/context/eventBus.js.map +1 -0
  216. package/dist/src/context/graph/behaviorRegistry.d.ts +28 -0
  217. package/dist/src/context/graph/behaviorRegistry.js +14 -0
  218. package/dist/src/context/graph/behaviorRegistry.js.map +1 -0
  219. package/dist/src/context/graph/builtinBehaviors.d.ts +11 -0
  220. package/dist/src/context/graph/builtinBehaviors.js +145 -0
  221. package/dist/src/context/graph/builtinBehaviors.js.map +1 -0
  222. package/dist/src/context/graph/fromGraph.d.ts +9 -0
  223. package/dist/src/context/graph/fromGraph.js +34 -0
  224. package/dist/src/context/graph/fromGraph.js.map +1 -0
  225. package/dist/src/context/graph/mapper.d.ts +16 -0
  226. package/dist/src/context/graph/mapper.js +16 -0
  227. package/dist/src/context/graph/mapper.js.map +1 -0
  228. package/dist/src/context/graph/render.d.ts +15 -0
  229. package/dist/src/context/graph/render.js +72 -0
  230. package/dist/src/context/graph/render.js.map +1 -0
  231. package/dist/src/context/graph/toGraph.d.ts +10 -0
  232. package/dist/src/context/graph/toGraph.js +172 -0
  233. package/dist/src/context/graph/toGraph.js.map +1 -0
  234. package/dist/src/context/graph/types.d.ts +139 -0
  235. package/dist/src/context/graph/types.js +36 -0
  236. package/dist/src/context/graph/types.js.map +1 -0
  237. package/dist/src/context/historyObserver.d.ts +27 -0
  238. package/dist/src/context/historyObserver.js +64 -0
  239. package/dist/src/context/historyObserver.js.map +1 -0
  240. package/dist/src/context/pipeline/contextWorkingBuffer.d.ts +33 -0
  241. package/dist/src/context/pipeline/contextWorkingBuffer.js +197 -0
  242. package/dist/src/context/pipeline/contextWorkingBuffer.js.map +1 -0
  243. package/dist/src/context/pipeline/contextWorkingBuffer.test.d.ts +6 -0
  244. package/dist/src/context/pipeline/contextWorkingBuffer.test.js +89 -0
  245. package/dist/src/context/pipeline/contextWorkingBuffer.test.js.map +1 -0
  246. package/dist/src/context/pipeline/environment.d.ts +27 -0
  247. package/dist/src/context/pipeline/environment.js +2 -0
  248. package/dist/src/context/pipeline/environment.js.map +1 -0
  249. package/dist/src/context/pipeline/environmentImpl.d.ts +28 -0
  250. package/dist/src/context/pipeline/environmentImpl.js +40 -0
  251. package/dist/src/context/pipeline/environmentImpl.js.map +1 -0
  252. package/dist/src/context/pipeline/environmentImpl.test.d.ts +1 -0
  253. package/dist/src/context/pipeline/environmentImpl.test.js +32 -0
  254. package/dist/src/context/pipeline/environmentImpl.test.js.map +1 -0
  255. package/dist/src/context/pipeline/inbox.d.ts +15 -0
  256. package/dist/src/context/pipeline/inbox.js +52 -0
  257. package/dist/src/context/pipeline/inbox.js.map +1 -0
  258. package/dist/src/context/pipeline/inbox.test.d.ts +1 -0
  259. package/dist/src/context/pipeline/inbox.test.js +36 -0
  260. package/dist/src/context/pipeline/inbox.test.js.map +1 -0
  261. package/dist/src/context/pipeline/orchestrator.d.ts +22 -0
  262. package/dist/src/context/pipeline/orchestrator.js +126 -0
  263. package/dist/src/context/pipeline/orchestrator.js.map +1 -0
  264. package/dist/src/context/pipeline/orchestrator.test.d.ts +6 -0
  265. package/dist/src/context/pipeline/orchestrator.test.js +154 -0
  266. package/dist/src/context/pipeline/orchestrator.test.js.map +1 -0
  267. package/dist/src/context/pipeline.d.ts +52 -0
  268. package/dist/src/context/pipeline.js +7 -0
  269. package/dist/src/context/pipeline.js.map +1 -0
  270. package/dist/src/context/processors/blobDegradationProcessor.d.ts +6 -0
  271. package/dist/src/context/processors/blobDegradationProcessor.js +127 -0
  272. package/dist/src/context/processors/blobDegradationProcessor.js.map +1 -0
  273. package/dist/src/context/processors/blobDegradationProcessor.test.d.ts +6 -0
  274. package/dist/src/context/processors/blobDegradationProcessor.test.js +72 -0
  275. package/dist/src/context/processors/blobDegradationProcessor.test.js.map +1 -0
  276. package/dist/src/context/processors/historyTruncationProcessor.d.ts +11 -0
  277. package/dist/src/context/processors/historyTruncationProcessor.js +61 -0
  278. package/dist/src/context/processors/historyTruncationProcessor.js.map +1 -0
  279. package/dist/src/context/processors/nodeDistillationProcessor.d.ts +8 -0
  280. package/dist/src/context/processors/nodeDistillationProcessor.js +167 -0
  281. package/dist/src/context/processors/nodeDistillationProcessor.js.map +1 -0
  282. package/dist/src/context/processors/nodeDistillationProcessor.test.d.ts +6 -0
  283. package/dist/src/context/processors/nodeDistillationProcessor.test.js +77 -0
  284. package/dist/src/context/processors/nodeDistillationProcessor.test.js.map +1 -0
  285. package/dist/src/context/processors/nodeTruncationProcessor.d.ts +8 -0
  286. package/dist/src/context/processors/nodeTruncationProcessor.js +109 -0
  287. package/dist/src/context/processors/nodeTruncationProcessor.js.map +1 -0
  288. package/dist/src/context/processors/nodeTruncationProcessor.test.d.ts +6 -0
  289. package/dist/src/context/processors/nodeTruncationProcessor.test.js +71 -0
  290. package/dist/src/context/processors/nodeTruncationProcessor.test.js.map +1 -0
  291. package/dist/src/context/processors/rollingSummaryProcessor.d.ts +8 -0
  292. package/dist/src/context/processors/rollingSummaryProcessor.js +129 -0
  293. package/dist/src/context/processors/rollingSummaryProcessor.js.map +1 -0
  294. package/dist/src/context/processors/rollingSummaryProcessor.test.d.ts +1 -0
  295. package/dist/src/context/processors/rollingSummaryProcessor.test.js +60 -0
  296. package/dist/src/context/processors/rollingSummaryProcessor.test.js.map +1 -0
  297. package/dist/src/context/processors/stateSnapshotAsyncProcessor.d.ts +9 -0
  298. package/dist/src/context/processors/stateSnapshotAsyncProcessor.js +75 -0
  299. package/dist/src/context/processors/stateSnapshotAsyncProcessor.js.map +1 -0
  300. package/dist/src/context/processors/stateSnapshotAsyncProcessor.test.d.ts +1 -0
  301. package/dist/src/context/processors/stateSnapshotAsyncProcessor.test.js +80 -0
  302. package/dist/src/context/processors/stateSnapshotAsyncProcessor.test.js.map +1 -0
  303. package/dist/src/context/processors/stateSnapshotProcessor.d.ts +9 -0
  304. package/dist/src/context/processors/stateSnapshotProcessor.js +130 -0
  305. package/dist/src/context/processors/stateSnapshotProcessor.js.map +1 -0
  306. package/dist/src/context/processors/stateSnapshotProcessor.test.d.ts +1 -0
  307. package/dist/src/context/processors/stateSnapshotProcessor.test.js +91 -0
  308. package/dist/src/context/processors/stateSnapshotProcessor.test.js.map +1 -0
  309. package/dist/src/context/processors/toolMaskingProcessor.d.ts +8 -0
  310. package/dist/src/context/processors/toolMaskingProcessor.js +194 -0
  311. package/dist/src/context/processors/toolMaskingProcessor.js.map +1 -0
  312. package/dist/src/context/processors/toolMaskingProcessor.test.d.ts +1 -0
  313. package/dist/src/context/processors/toolMaskingProcessor.test.js +50 -0
  314. package/dist/src/context/processors/toolMaskingProcessor.test.js.map +1 -0
  315. package/dist/src/context/system-tests/lifecycle.golden.test.d.ts +6 -0
  316. package/dist/src/context/system-tests/lifecycle.golden.test.js +195 -0
  317. package/dist/src/context/system-tests/lifecycle.golden.test.js.map +1 -0
  318. package/dist/src/context/system-tests/simulationHarness.d.ts +41 -0
  319. package/dist/src/context/system-tests/simulationHarness.js +88 -0
  320. package/dist/src/context/system-tests/simulationHarness.js.map +1 -0
  321. package/dist/src/context/testing/contextTestUtils.d.ts +44 -0
  322. package/dist/src/context/testing/contextTestUtils.js +176 -0
  323. package/dist/src/context/testing/contextTestUtils.js.map +1 -0
  324. package/dist/src/context/testing/testProfile.d.ts +7 -0
  325. package/dist/src/context/testing/testProfile.js +20 -0
  326. package/dist/src/context/testing/testProfile.js.map +1 -0
  327. package/dist/src/context/tracer.d.ts +19 -0
  328. package/dist/src/context/tracer.js +79 -0
  329. package/dist/src/context/tracer.js.map +1 -0
  330. package/dist/src/context/tracer.test.d.ts +6 -0
  331. package/dist/src/context/tracer.test.js +71 -0
  332. package/dist/src/context/tracer.test.js.map +1 -0
  333. package/dist/src/context/utils/contextTokenCalculator.d.ts +53 -0
  334. package/dist/src/context/utils/contextTokenCalculator.js +97 -0
  335. package/dist/src/context/utils/contextTokenCalculator.js.map +1 -0
  336. package/dist/src/context/utils/snapshotGenerator.d.ts +12 -0
  337. package/dist/src/context/utils/snapshotGenerator.js +43 -0
  338. package/dist/src/context/utils/snapshotGenerator.js.map +1 -0
  339. package/dist/src/core/agentChatHistory.d.ts +26 -0
  340. package/dist/src/core/agentChatHistory.js +50 -0
  341. package/dist/src/core/agentChatHistory.js.map +1 -0
  342. package/dist/src/core/client.js +3 -1
  343. package/dist/src/core/client.js.map +1 -1
  344. package/dist/src/core/client.test.js +4 -0
  345. package/dist/src/core/client.test.js.map +1 -1
  346. package/dist/src/core/contentGenerator.d.ts +8 -1
  347. package/dist/src/core/contentGenerator.js +46 -4
  348. package/dist/src/core/contentGenerator.js.map +1 -1
  349. package/dist/src/core/contentGenerator.test.js +174 -8
  350. package/dist/src/core/contentGenerator.test.js.map +1 -1
  351. package/dist/src/core/coreToolHookTriggers.d.ts +1 -1
  352. package/dist/src/core/coreToolHookTriggers.js +5 -1
  353. package/dist/src/core/coreToolHookTriggers.js.map +1 -1
  354. package/dist/src/core/coreToolHookTriggers.test.js +1 -1
  355. package/dist/src/core/coreToolHookTriggers.test.js.map +1 -1
  356. package/dist/src/core/geminiChat.d.ts +2 -1
  357. package/dist/src/core/geminiChat.js +7 -2
  358. package/dist/src/core/geminiChat.js.map +1 -1
  359. package/dist/src/core/geminiChat.test.js +19 -6
  360. package/dist/src/core/geminiChat.test.js.map +1 -1
  361. package/dist/src/core/geminiChat_network_retry.test.js +42 -0
  362. package/dist/src/core/geminiChat_network_retry.test.js.map +1 -1
  363. package/dist/src/core/localLiteRtLmClient.js +2 -0
  364. package/dist/src/core/localLiteRtLmClient.js.map +1 -1
  365. package/dist/src/core/localLiteRtLmClient.test.js +7 -0
  366. package/dist/src/core/localLiteRtLmClient.test.js.map +1 -1
  367. package/dist/src/core/loggingContentGenerator.js +19 -6
  368. package/dist/src/core/loggingContentGenerator.js.map +1 -1
  369. package/dist/src/core/loggingContentGenerator.test.js +55 -0
  370. package/dist/src/core/loggingContentGenerator.test.js.map +1 -1
  371. package/dist/src/core/prompts-substitution.test.js +1 -0
  372. package/dist/src/core/prompts-substitution.test.js.map +1 -1
  373. package/dist/src/core/prompts.d.ts +1 -1
  374. package/dist/src/core/prompts.js +2 -2
  375. package/dist/src/core/prompts.js.map +1 -1
  376. package/dist/src/core/prompts.test.js +39 -8
  377. package/dist/src/core/prompts.test.js.map +1 -1
  378. package/dist/src/generated/git-commit.d.ts +2 -2
  379. package/dist/src/generated/git-commit.js +2 -2
  380. package/dist/src/hooks/hookRunner.js +8 -0
  381. package/dist/src/hooks/hookRunner.js.map +1 -1
  382. package/dist/src/hooks/hookRunner.test.js +23 -0
  383. package/dist/src/hooks/hookRunner.test.js.map +1 -1
  384. package/dist/src/ide/ide-client.js +3 -4
  385. package/dist/src/ide/ide-client.js.map +1 -1
  386. package/dist/src/index.d.ts +7 -3
  387. package/dist/src/index.js +7 -2
  388. package/dist/src/index.js.map +1 -1
  389. package/dist/src/mcp/mcpLauncher.js +1 -1
  390. package/dist/src/mcp/mcpLauncher.js.map +1 -1
  391. package/dist/src/mcp/oauth-provider.test.js +24 -17
  392. package/dist/src/mcp/oauth-provider.test.js.map +1 -1
  393. package/dist/src/policy/config.d.ts +2 -0
  394. package/dist/src/policy/config.js +67 -12
  395. package/dist/src/policy/config.js.map +1 -1
  396. package/dist/src/policy/core-tools-mapping.test.d.ts +6 -0
  397. package/dist/src/policy/core-tools-mapping.test.js +44 -0
  398. package/dist/src/policy/core-tools-mapping.test.js.map +1 -0
  399. package/dist/src/policy/policies/agents.toml +10 -0
  400. package/dist/src/policy/policies/plan.toml +17 -43
  401. package/dist/src/policy/policies/read-only.toml +24 -38
  402. package/dist/src/policy/policy-engine.d.ts +1 -1
  403. package/dist/src/policy/policy-engine.js +72 -67
  404. package/dist/src/policy/policy-engine.js.map +1 -1
  405. package/dist/src/policy/policy-engine.test.js +71 -4
  406. package/dist/src/policy/policy-engine.test.js.map +1 -1
  407. package/dist/src/policy/sandboxPolicyManager.js +4 -4
  408. package/dist/src/policy/sandboxPolicyManager.js.map +1 -1
  409. package/dist/src/policy/shell-safety-regression.test.d.ts +6 -0
  410. package/dist/src/policy/shell-safety-regression.test.js +86 -0
  411. package/dist/src/policy/shell-safety-regression.test.js.map +1 -0
  412. package/dist/src/policy/shell-safety.test.js +24 -0
  413. package/dist/src/policy/shell-safety.test.js.map +1 -1
  414. package/dist/src/policy/shell-substitution.test.d.ts +6 -0
  415. package/dist/src/policy/shell-substitution.test.js +75 -0
  416. package/dist/src/policy/shell-substitution.test.js.map +1 -0
  417. package/dist/src/policy/toml-loader.test.js +25 -11
  418. package/dist/src/policy/toml-loader.test.js.map +1 -1
  419. package/dist/src/policy/types.d.ts +6 -2
  420. package/dist/src/policy/types.js +4 -2
  421. package/dist/src/policy/types.js.map +1 -1
  422. package/dist/src/prompts/promptProvider.d.ts +1 -1
  423. package/dist/src/prompts/promptProvider.js +41 -24
  424. package/dist/src/prompts/promptProvider.js.map +1 -1
  425. package/dist/src/prompts/promptProvider.test.js +36 -2
  426. package/dist/src/prompts/promptProvider.test.js.map +1 -1
  427. package/dist/src/prompts/snippets-memory-v2.test.d.ts +6 -0
  428. package/dist/src/prompts/snippets-memory-v2.test.js +94 -0
  429. package/dist/src/prompts/snippets-memory-v2.test.js.map +1 -0
  430. package/dist/src/prompts/snippets.d.ts +19 -1
  431. package/dist/src/prompts/snippets.js +33 -6
  432. package/dist/src/prompts/snippets.js.map +1 -1
  433. package/dist/src/prompts/snippets.legacy.d.ts +6 -1
  434. package/dist/src/prompts/snippets.legacy.js +14 -7
  435. package/dist/src/prompts/snippets.legacy.js.map +1 -1
  436. package/dist/src/prompts/utils.test.js +1 -0
  437. package/dist/src/prompts/utils.test.js.map +1 -1
  438. package/dist/src/routing/modelRouterService.js +1 -1
  439. package/dist/src/routing/modelRouterService.js.map +1 -1
  440. package/dist/src/sandbox/linux/LinuxSandboxManager.d.ts +2 -0
  441. package/dist/src/sandbox/linux/LinuxSandboxManager.js +43 -19
  442. package/dist/src/sandbox/linux/LinuxSandboxManager.js.map +1 -1
  443. package/dist/src/sandbox/linux/LinuxSandboxManager.test.js +16 -0
  444. package/dist/src/sandbox/linux/LinuxSandboxManager.test.js.map +1 -1
  445. package/dist/src/sandbox/linux/bwrapArgsBuilder.d.ts +3 -7
  446. package/dist/src/sandbox/linux/bwrapArgsBuilder.js +96 -105
  447. package/dist/src/sandbox/linux/bwrapArgsBuilder.js.map +1 -1
  448. package/dist/src/sandbox/linux/bwrapArgsBuilder.test.js +144 -41
  449. package/dist/src/sandbox/linux/bwrapArgsBuilder.test.js.map +1 -1
  450. package/dist/src/sandbox/macos/MacOsSandboxManager.js +19 -10
  451. package/dist/src/sandbox/macos/MacOsSandboxManager.js.map +1 -1
  452. package/dist/src/sandbox/macos/MacOsSandboxManager.test.js +24 -37
  453. package/dist/src/sandbox/macos/MacOsSandboxManager.test.js.map +1 -1
  454. package/dist/src/sandbox/macos/seatbeltArgsBuilder.d.ts +3 -9
  455. package/dist/src/sandbox/macos/seatbeltArgsBuilder.js +129 -96
  456. package/dist/src/sandbox/macos/seatbeltArgsBuilder.js.map +1 -1
  457. package/dist/src/sandbox/macos/seatbeltArgsBuilder.test.js +78 -77
  458. package/dist/src/sandbox/macos/seatbeltArgsBuilder.test.js.map +1 -1
  459. package/dist/src/sandbox/utils/fsUtils.d.ts +2 -3
  460. package/dist/src/sandbox/utils/fsUtils.js +12 -27
  461. package/dist/src/sandbox/utils/fsUtils.js.map +1 -1
  462. package/dist/src/sandbox/utils/fsUtils.test.js +87 -29
  463. package/dist/src/sandbox/utils/fsUtils.test.js.map +1 -1
  464. package/dist/src/sandbox/windows/GeminiSandbox.cs +186 -77
  465. package/dist/src/sandbox/windows/WindowsSandboxManager.d.ts +4 -16
  466. package/dist/src/sandbox/windows/WindowsSandboxManager.js +138 -204
  467. package/dist/src/sandbox/windows/WindowsSandboxManager.js.map +1 -1
  468. package/dist/src/sandbox/windows/WindowsSandboxManager.test.js +105 -122
  469. package/dist/src/sandbox/windows/WindowsSandboxManager.test.js.map +1 -1
  470. package/dist/src/scheduler/policy.js +1 -2
  471. package/dist/src/scheduler/policy.js.map +1 -1
  472. package/dist/src/scheduler/policy.test.js +58 -2
  473. package/dist/src/scheduler/policy.test.js.map +1 -1
  474. package/dist/src/scheduler/scheduler.d.ts +2 -1
  475. package/dist/src/scheduler/scheduler.js +13 -14
  476. package/dist/src/scheduler/scheduler.js.map +1 -1
  477. package/dist/src/scheduler/scheduler.test.js +66 -0
  478. package/dist/src/scheduler/scheduler.test.js.map +1 -1
  479. package/dist/src/scheduler/scheduler_hooks.test.js +1 -0
  480. package/dist/src/scheduler/scheduler_hooks.test.js.map +1 -1
  481. package/dist/src/scheduler/scheduler_parallel.test.js +2 -0
  482. package/dist/src/scheduler/scheduler_parallel.test.js.map +1 -1
  483. package/dist/src/scheduler/tool-executor.js +2 -0
  484. package/dist/src/scheduler/tool-executor.js.map +1 -1
  485. package/dist/src/services/chatRecordingService.d.ts +12 -153
  486. package/dist/src/services/chatRecordingService.js +444 -350
  487. package/dist/src/services/chatRecordingService.js.map +1 -1
  488. package/dist/src/services/chatRecordingService.test.js +174 -128
  489. package/dist/src/services/chatRecordingService.test.js.map +1 -1
  490. package/dist/src/services/chatRecordingTypes.d.ts +111 -0
  491. package/dist/src/services/chatRecordingTypes.js +10 -0
  492. package/dist/src/services/chatRecordingTypes.js.map +1 -0
  493. package/dist/src/services/gitService.d.ts +2 -0
  494. package/dist/src/services/gitService.js +10 -1
  495. package/dist/src/services/gitService.js.map +1 -1
  496. package/dist/src/services/gitService.test.js +6 -2
  497. package/dist/src/services/gitService.test.js.map +1 -1
  498. package/dist/src/services/keychainService.d.ts +2 -2
  499. package/dist/src/services/keychainService.js +9 -9
  500. package/dist/src/services/keychainService.js.map +1 -1
  501. package/dist/src/services/keychainService.test.js +7 -7
  502. package/dist/src/services/keychainService.test.js.map +1 -1
  503. package/dist/src/services/keychainTypes.d.ts +1 -1
  504. package/dist/src/services/memoryPatchUtils.d.ts +42 -0
  505. package/dist/src/services/memoryPatchUtils.js +216 -0
  506. package/dist/src/services/memoryPatchUtils.js.map +1 -0
  507. package/dist/src/services/memoryService.d.ts +21 -1
  508. package/dist/src/services/memoryService.js +405 -64
  509. package/dist/src/services/memoryService.js.map +1 -1
  510. package/dist/src/services/memoryService.test.js +686 -2
  511. package/dist/src/services/memoryService.test.js.map +1 -1
  512. package/dist/src/services/sandboxManager.d.ts +33 -19
  513. package/dist/src/services/sandboxManager.integration.test.js +728 -266
  514. package/dist/src/services/sandboxManager.integration.test.js.map +1 -1
  515. package/dist/src/services/sandboxManager.js +65 -62
  516. package/dist/src/services/sandboxManager.js.map +1 -1
  517. package/dist/src/services/sandboxManager.test.js +17 -114
  518. package/dist/src/services/sandboxManager.test.js.map +1 -1
  519. package/dist/src/services/sandboxedFileSystemService.js +72 -62
  520. package/dist/src/services/sandboxedFileSystemService.js.map +1 -1
  521. package/dist/src/services/sessionSummaryUtils.d.ts +1 -1
  522. package/dist/src/services/sessionSummaryUtils.js +111 -38
  523. package/dist/src/services/sessionSummaryUtils.js.map +1 -1
  524. package/dist/src/services/sessionSummaryUtils.test.js +204 -51
  525. package/dist/src/services/sessionSummaryUtils.test.js.map +1 -1
  526. package/dist/src/services/shellExecutionService.d.ts +19 -0
  527. package/dist/src/services/shellExecutionService.js +88 -34
  528. package/dist/src/services/shellExecutionService.js.map +1 -1
  529. package/dist/src/services/shellExecutionService.test.js +38 -4
  530. package/dist/src/services/shellExecutionService.test.js.map +1 -1
  531. package/dist/src/telemetry/activity-monitor.js +1 -0
  532. package/dist/src/telemetry/activity-monitor.js.map +1 -1
  533. package/dist/src/telemetry/config.js +3 -0
  534. package/dist/src/telemetry/config.js.map +1 -1
  535. package/dist/src/telemetry/conseca-logger.js +18 -20
  536. package/dist/src/telemetry/conseca-logger.js.map +1 -1
  537. package/dist/src/telemetry/conseca-logger.test.js +100 -0
  538. package/dist/src/telemetry/conseca-logger.test.js.map +1 -1
  539. package/dist/src/telemetry/event-loop-monitor.d.ts +17 -0
  540. package/dist/src/telemetry/event-loop-monitor.js +76 -0
  541. package/dist/src/telemetry/event-loop-monitor.js.map +1 -0
  542. package/dist/src/telemetry/index.d.ts +2 -1
  543. package/dist/src/telemetry/index.js +2 -1
  544. package/dist/src/telemetry/index.js.map +1 -1
  545. package/dist/src/telemetry/llmRole.d.ts +2 -1
  546. package/dist/src/telemetry/llmRole.js +1 -0
  547. package/dist/src/telemetry/llmRole.js.map +1 -1
  548. package/dist/src/telemetry/loggers.test.js +184 -8
  549. package/dist/src/telemetry/loggers.test.js.map +1 -1
  550. package/dist/src/telemetry/memory-monitor.d.ts +1 -0
  551. package/dist/src/telemetry/memory-monitor.js +8 -1
  552. package/dist/src/telemetry/memory-monitor.js.map +1 -1
  553. package/dist/src/telemetry/memory-monitor.test.js +6 -1
  554. package/dist/src/telemetry/memory-monitor.test.js.map +1 -1
  555. package/dist/src/telemetry/metrics.d.ts +12 -0
  556. package/dist/src/telemetry/metrics.js +19 -0
  557. package/dist/src/telemetry/metrics.js.map +1 -1
  558. package/dist/src/telemetry/sdk.js +20 -1
  559. package/dist/src/telemetry/sdk.js.map +1 -1
  560. package/dist/src/telemetry/trace.d.ts +23 -6
  561. package/dist/src/telemetry/trace.js +71 -22
  562. package/dist/src/telemetry/trace.js.map +1 -1
  563. package/dist/src/telemetry/trace.test.js +79 -15
  564. package/dist/src/telemetry/trace.test.js.map +1 -1
  565. package/dist/src/telemetry/types.js +61 -15
  566. package/dist/src/telemetry/types.js.map +1 -1
  567. package/dist/src/test-utils/mock-tool.d.ts +3 -2
  568. package/dist/src/test-utils/mock-tool.js +4 -3
  569. package/dist/src/test-utils/mock-tool.js.map +1 -1
  570. package/dist/src/tools/activate-skill.js +1 -1
  571. package/dist/src/tools/activate-skill.js.map +1 -1
  572. package/dist/src/tools/activate-skill.test.js +6 -2
  573. package/dist/src/tools/activate-skill.test.js.map +1 -1
  574. package/dist/src/tools/ask-user.d.ts +2 -2
  575. package/dist/src/tools/ask-user.js +1 -1
  576. package/dist/src/tools/ask-user.js.map +1 -1
  577. package/dist/src/tools/ask-user.test.js +9 -3
  578. package/dist/src/tools/ask-user.test.js.map +1 -1
  579. package/dist/src/tools/complete-task.d.ts +2 -2
  580. package/dist/src/tools/complete-task.js +1 -1
  581. package/dist/src/tools/complete-task.js.map +1 -1
  582. package/dist/src/tools/complete-task.test.js +9 -3
  583. package/dist/src/tools/complete-task.test.js.map +1 -1
  584. package/dist/src/tools/definitions/base-declarations.d.ts +2 -0
  585. package/dist/src/tools/definitions/base-declarations.js +3 -0
  586. package/dist/src/tools/definitions/base-declarations.js.map +1 -1
  587. package/dist/src/tools/definitions/coreTools.d.ts +3 -1
  588. package/dist/src/tools/definitions/coreTools.js +13 -1
  589. package/dist/src/tools/definitions/coreTools.js.map +1 -1
  590. package/dist/src/tools/definitions/model-family-sets/default-legacy.js +29 -1
  591. package/dist/src/tools/definitions/model-family-sets/default-legacy.js.map +1 -1
  592. package/dist/src/tools/definitions/model-family-sets/gemini-3.js +29 -1
  593. package/dist/src/tools/definitions/model-family-sets/gemini-3.js.map +1 -1
  594. package/dist/src/tools/definitions/types.d.ts +2 -0
  595. package/dist/src/tools/edit.js +20 -4
  596. package/dist/src/tools/edit.js.map +1 -1
  597. package/dist/src/tools/edit.test.js +41 -18
  598. package/dist/src/tools/edit.test.js.map +1 -1
  599. package/dist/src/tools/enter-plan-mode.d.ts +2 -2
  600. package/dist/src/tools/enter-plan-mode.js +1 -1
  601. package/dist/src/tools/enter-plan-mode.js.map +1 -1
  602. package/dist/src/tools/enter-plan-mode.test.js +10 -4
  603. package/dist/src/tools/enter-plan-mode.test.js.map +1 -1
  604. package/dist/src/tools/exit-plan-mode.d.ts +2 -2
  605. package/dist/src/tools/exit-plan-mode.js +9 -12
  606. package/dist/src/tools/exit-plan-mode.js.map +1 -1
  607. package/dist/src/tools/exit-plan-mode.test.js +44 -17
  608. package/dist/src/tools/exit-plan-mode.test.js.map +1 -1
  609. package/dist/src/tools/get-internal-docs.js +6 -3
  610. package/dist/src/tools/get-internal-docs.js.map +1 -1
  611. package/dist/src/tools/get-internal-docs.test.js +4 -4
  612. package/dist/src/tools/get-internal-docs.test.js.map +1 -1
  613. package/dist/src/tools/glob.js +1 -1
  614. package/dist/src/tools/glob.js.map +1 -1
  615. package/dist/src/tools/glob.test.js +16 -16
  616. package/dist/src/tools/glob.test.js.map +1 -1
  617. package/dist/src/tools/grep.js +21 -12
  618. package/dist/src/tools/grep.js.map +1 -1
  619. package/dist/src/tools/grep.test.js +18 -18
  620. package/dist/src/tools/grep.test.js.map +1 -1
  621. package/dist/src/tools/line-endings.test.js +3 -3
  622. package/dist/src/tools/line-endings.test.js.map +1 -1
  623. package/dist/src/tools/list-mcp-resources.d.ts +24 -0
  624. package/dist/src/tools/list-mcp-resources.js +74 -0
  625. package/dist/src/tools/list-mcp-resources.js.map +1 -0
  626. package/dist/src/tools/list-mcp-resources.test.d.ts +6 -0
  627. package/dist/src/tools/list-mcp-resources.test.js +79 -0
  628. package/dist/src/tools/list-mcp-resources.test.js.map +1 -0
  629. package/dist/src/tools/ls.js +2 -2
  630. package/dist/src/tools/ls.js.map +1 -1
  631. package/dist/src/tools/ls.test.js +21 -21
  632. package/dist/src/tools/ls.test.js.map +1 -1
  633. package/dist/src/tools/mcp-client-manager.d.ts +3 -1
  634. package/dist/src/tools/mcp-client-manager.js +24 -1
  635. package/dist/src/tools/mcp-client-manager.js.map +1 -1
  636. package/dist/src/tools/mcp-client-manager.test.js +43 -0
  637. package/dist/src/tools/mcp-client-manager.test.js.map +1 -1
  638. package/dist/src/tools/mcp-client.js +10 -12
  639. package/dist/src/tools/mcp-client.js.map +1 -1
  640. package/dist/src/tools/mcp-client.test.js +14 -2
  641. package/dist/src/tools/mcp-client.test.js.map +1 -1
  642. package/dist/src/tools/mcp-tool.d.ts +2 -2
  643. package/dist/src/tools/mcp-tool.js +1 -1
  644. package/dist/src/tools/mcp-tool.js.map +1 -1
  645. package/dist/src/tools/mcp-tool.test.js +51 -21
  646. package/dist/src/tools/mcp-tool.test.js.map +1 -1
  647. package/dist/src/tools/memoryTool.d.ts +4 -3
  648. package/dist/src/tools/memoryTool.js +43 -14
  649. package/dist/src/tools/memoryTool.js.map +1 -1
  650. package/dist/src/tools/memoryTool.test.js +29 -9
  651. package/dist/src/tools/memoryTool.test.js.map +1 -1
  652. package/dist/src/tools/read-file.js +1 -1
  653. package/dist/src/tools/read-file.js.map +1 -1
  654. package/dist/src/tools/read-file.test.js +17 -17
  655. package/dist/src/tools/read-file.test.js.map +1 -1
  656. package/dist/src/tools/read-many-files.js +4 -4
  657. package/dist/src/tools/read-many-files.js.map +1 -1
  658. package/dist/src/tools/read-many-files.test.js +70 -24
  659. package/dist/src/tools/read-many-files.test.js.map +1 -1
  660. package/dist/src/tools/read-mcp-resource.d.ts +25 -0
  661. package/dist/src/tools/read-mcp-resource.js +120 -0
  662. package/dist/src/tools/read-mcp-resource.js.map +1 -0
  663. package/dist/src/tools/read-mcp-resource.test.d.ts +6 -0
  664. package/dist/src/tools/read-mcp-resource.test.js +110 -0
  665. package/dist/src/tools/read-mcp-resource.test.js.map +1 -0
  666. package/dist/src/tools/ripGrep.d.ts +3 -2
  667. package/dist/src/tools/ripGrep.js +26 -55
  668. package/dist/src/tools/ripGrep.js.map +1 -1
  669. package/dist/src/tools/ripGrep.test.js +113 -167
  670. package/dist/src/tools/ripGrep.test.js.map +1 -1
  671. package/dist/src/tools/shell.d.ts +2 -2
  672. package/dist/src/tools/shell.js +51 -21
  673. package/dist/src/tools/shell.js.map +1 -1
  674. package/dist/src/tools/shell.test.js +479 -76
  675. package/dist/src/tools/shell.test.js.map +1 -1
  676. package/dist/src/tools/shellBackgroundTools.d.ts +3 -3
  677. package/dist/src/tools/shellBackgroundTools.integration.test.js +6 -2
  678. package/dist/src/tools/shellBackgroundTools.integration.test.js.map +1 -1
  679. package/dist/src/tools/shellBackgroundTools.js +2 -2
  680. package/dist/src/tools/shellBackgroundTools.js.map +1 -1
  681. package/dist/src/tools/shellBackgroundTools.test.js +30 -10
  682. package/dist/src/tools/shellBackgroundTools.test.js.map +1 -1
  683. package/dist/src/tools/tool-error.d.ts +1 -0
  684. package/dist/src/tools/tool-error.js +1 -0
  685. package/dist/src/tools/tool-error.js.map +1 -1
  686. package/dist/src/tools/tool-names.d.ts +5 -4
  687. package/dist/src/tools/tool-names.js +8 -2
  688. package/dist/src/tools/tool-names.js.map +1 -1
  689. package/dist/src/tools/tool-registry.js +137 -114
  690. package/dist/src/tools/tool-registry.js.map +1 -1
  691. package/dist/src/tools/tool-registry.test.js +3 -1
  692. package/dist/src/tools/tool-registry.test.js.map +1 -1
  693. package/dist/src/tools/tools.d.ts +6 -6
  694. package/dist/src/tools/tools.js +6 -2
  695. package/dist/src/tools/tools.js.map +1 -1
  696. package/dist/src/tools/topicTool.d.ts +2 -2
  697. package/dist/src/tools/topicTool.js +1 -1
  698. package/dist/src/tools/topicTool.js.map +1 -1
  699. package/dist/src/tools/topicTool.test.js +6 -2
  700. package/dist/src/tools/topicTool.test.js.map +1 -1
  701. package/dist/src/tools/trackerTools.d.ts +7 -7
  702. package/dist/src/tools/trackerTools.js +6 -6
  703. package/dist/src/tools/trackerTools.js.map +1 -1
  704. package/dist/src/tools/web-fetch.js +1 -1
  705. package/dist/src/tools/web-fetch.js.map +1 -1
  706. package/dist/src/tools/web-fetch.test.js +59 -23
  707. package/dist/src/tools/web-fetch.test.js.map +1 -1
  708. package/dist/src/tools/web-search.js +1 -1
  709. package/dist/src/tools/web-search.js.map +1 -1
  710. package/dist/src/tools/web-search.test.js +5 -5
  711. package/dist/src/tools/web-search.test.js.map +1 -1
  712. package/dist/src/tools/write-file.js +22 -4
  713. package/dist/src/tools/write-file.js.map +1 -1
  714. package/dist/src/tools/write-file.test.js +29 -11
  715. package/dist/src/tools/write-file.test.js.map +1 -1
  716. package/dist/src/tools/write-todos.js +1 -1
  717. package/dist/src/tools/write-todos.js.map +1 -1
  718. package/dist/src/utils/compatibility.js +6 -1
  719. package/dist/src/utils/compatibility.js.map +1 -1
  720. package/dist/src/utils/compatibility.test.js +23 -0
  721. package/dist/src/utils/compatibility.test.js.map +1 -1
  722. package/dist/src/utils/errors.d.ts +3 -0
  723. package/dist/src/utils/errors.js +6 -0
  724. package/dist/src/utils/errors.js.map +1 -1
  725. package/dist/src/utils/fileUtils.d.ts +1 -2
  726. package/dist/src/utils/fileUtils.js +80 -40
  727. package/dist/src/utils/fileUtils.js.map +1 -1
  728. package/dist/src/utils/fileUtils.test.js +61 -0
  729. package/dist/src/utils/fileUtils.test.js.map +1 -1
  730. package/dist/src/utils/filesearch/fileSearch.d.ts +2 -0
  731. package/dist/src/utils/filesearch/fileSearch.js +97 -6
  732. package/dist/src/utils/filesearch/fileSearch.js.map +1 -1
  733. package/dist/src/utils/filesearch/fileSearch.test.js +54 -0
  734. package/dist/src/utils/filesearch/fileSearch.test.js.map +1 -1
  735. package/dist/src/utils/filesearch/fileWatcher.d.ts +25 -0
  736. package/dist/src/utils/filesearch/fileWatcher.js +86 -0
  737. package/dist/src/utils/filesearch/fileWatcher.js.map +1 -0
  738. package/dist/src/utils/filesearch/fileWatcher.test.js +142 -0
  739. package/dist/src/utils/filesearch/fileWatcher.test.js.map +1 -0
  740. package/dist/src/utils/getFolderStructure.js +4 -2
  741. package/dist/src/utils/getFolderStructure.js.map +1 -1
  742. package/dist/src/utils/gitIgnoreParser.js +1 -1
  743. package/dist/src/utils/gitIgnoreParser.js.map +1 -1
  744. package/dist/src/utils/googleQuotaErrors.d.ts +2 -1
  745. package/dist/src/utils/googleQuotaErrors.js +30 -35
  746. package/dist/src/utils/googleQuotaErrors.js.map +1 -1
  747. package/dist/src/utils/googleQuotaErrors.test.js +24 -0
  748. package/dist/src/utils/googleQuotaErrors.test.js.map +1 -1
  749. package/dist/src/utils/ignoreFileParser.js +1 -1
  750. package/dist/src/utils/ignoreFileParser.js.map +1 -1
  751. package/dist/src/utils/memoryDiscovery.js +15 -5
  752. package/dist/src/utils/memoryDiscovery.js.map +1 -1
  753. package/dist/src/utils/oauth-flow.js +17 -5
  754. package/dist/src/utils/oauth-flow.js.map +1 -1
  755. package/dist/src/utils/oauth-flow.test.js +20 -0
  756. package/dist/src/utils/oauth-flow.test.js.map +1 -1
  757. package/dist/src/utils/paths.d.ts +9 -0
  758. package/dist/src/utils/paths.js +37 -0
  759. package/dist/src/utils/paths.js.map +1 -1
  760. package/dist/src/utils/paths.test.js +45 -1
  761. package/dist/src/utils/paths.test.js.map +1 -1
  762. package/dist/src/utils/planUtils.d.ts +11 -2
  763. package/dist/src/utils/planUtils.js +43 -11
  764. package/dist/src/utils/planUtils.js.map +1 -1
  765. package/dist/src/utils/planUtils.test.js +10 -9
  766. package/dist/src/utils/planUtils.test.js.map +1 -1
  767. package/dist/src/utils/process-utils.d.ts +2 -1
  768. package/dist/src/utils/process-utils.js +64 -33
  769. package/dist/src/utils/process-utils.js.map +1 -1
  770. package/dist/src/utils/process-utils.test.js +9 -0
  771. package/dist/src/utils/process-utils.test.js.map +1 -1
  772. package/dist/src/utils/retry.js +18 -6
  773. package/dist/src/utils/retry.js.map +1 -1
  774. package/dist/src/utils/retry.test.js +30 -0
  775. package/dist/src/utils/retry.test.js.map +1 -1
  776. package/dist/src/utils/sessionOperations.js +3 -2
  777. package/dist/src/utils/sessionOperations.js.map +1 -1
  778. package/dist/src/utils/shell-utils.d.ts +2 -0
  779. package/dist/src/utils/shell-utils.js +237 -107
  780. package/dist/src/utils/shell-utils.js.map +1 -1
  781. package/dist/src/utils/tool-utils.d.ts +1 -29
  782. package/dist/src/utils/tool-utils.js +0 -39
  783. package/dist/src/utils/tool-utils.js.map +1 -1
  784. package/dist/src/utils/tool-utils.test.js +2 -76
  785. package/dist/src/utils/tool-utils.test.js.map +1 -1
  786. package/dist/src/utils/tool-visibility.d.ts +40 -0
  787. package/dist/src/utils/tool-visibility.js +111 -0
  788. package/dist/src/utils/tool-visibility.js.map +1 -0
  789. package/dist/src/utils/tool-visibility.test.d.ts +6 -0
  790. package/dist/src/utils/tool-visibility.test.js +96 -0
  791. package/dist/src/utils/tool-visibility.test.js.map +1 -0
  792. package/dist/src/utils/trust.d.ts +64 -0
  793. package/dist/src/utils/trust.js +276 -0
  794. package/dist/src/utils/trust.js.map +1 -0
  795. package/dist/src/utils/trust.test.d.ts +6 -0
  796. package/dist/src/utils/trust.test.js +159 -0
  797. package/dist/src/utils/trust.test.js.map +1 -0
  798. package/dist/tsconfig.tsbuildinfo +1 -1
  799. package/package.json +5 -4
  800. package/dist/docs/get-started/installation.md +0 -181
  801. package/dist/src/agents/memory-manager-agent.d.ts +0 -25
  802. package/dist/src/agents/memory-manager-agent.js +0 -138
  803. package/dist/src/agents/memory-manager-agent.js.map +0 -1
  804. package/dist/src/agents/memory-manager-agent.test.js +0 -123
  805. package/dist/src/agents/memory-manager-agent.test.js.map +0 -1
  806. package/dist/src/agents/subagent-tool-wrapper.d.ts +0 -38
  807. package/dist/src/agents/subagent-tool-wrapper.js +0 -58
  808. package/dist/src/agents/subagent-tool-wrapper.js.map +0 -1
  809. package/dist/src/agents/subagent-tool-wrapper.test.js +0 -123
  810. package/dist/src/agents/subagent-tool-wrapper.test.js.map +0 -1
  811. package/dist/src/agents/subagent-tool.d.ts +0 -18
  812. package/dist/src/agents/subagent-tool.js +0 -134
  813. package/dist/src/agents/subagent-tool.js.map +0 -1
  814. package/dist/src/agents/subagent-tool.test.js +0 -287
  815. package/dist/src/agents/subagent-tool.test.js.map +0 -1
  816. package/dist/src/policy/policies/tracker.toml +0 -34
  817. package/dist/src/prompts/snippets-memory-manager.test.js +0 -31
  818. package/dist/src/prompts/snippets-memory-manager.test.js.map +0 -1
  819. /package/dist/src/{agents/memory-manager-agent.test.d.ts → agent/tool-display-utils.test.d.ts} +0 -0
  820. /package/dist/src/agents/{subagent-tool.test.d.ts → agent-tool.test.d.ts} +0 -0
  821. /package/dist/src/{prompts/snippets-memory-manager.test.d.ts → agents/skill-extraction-agent.test.d.ts} +0 -0
  822. /package/dist/src/{agents/subagent-tool-wrapper.test.d.ts → utils/filesearch/fileWatcher.test.d.ts} +0 -0
@@ -3,56 +3,66 @@
3
3
  * Copyright 2026 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
6
+ import { describe, it, expect, beforeAll, beforeEach, afterEach, afterAll, } from 'vitest';
7
7
  import { createSandboxManager } from './sandboxManagerFactory.js';
8
8
  import { ShellExecutionService } from './shellExecutionService.js';
9
9
  import { getSecureSanitizationConfig } from './environmentSanitization.js';
10
- import { NoopSandboxManager, LocalSandboxManager, } from './sandboxManager.js';
11
- import { execFile, execSync } from 'node:child_process';
10
+ import { GOVERNANCE_FILES, } from './sandboxManager.js';
11
+ import { execFile } from 'node:child_process';
12
12
  import { promisify } from 'node:util';
13
13
  import os from 'node:os';
14
14
  import fs from 'node:fs';
15
15
  import path from 'node:path';
16
16
  import http from 'node:http';
17
17
  /**
18
- * Abstracts platform-specific shell commands for integration testing.
18
+ * Cross-platform command wrappers using Node.js inline scripts.
19
+ * Ensures consistent execution behavior and reliable exit codes across
20
+ * different host operating systems and restricted sandbox environments.
19
21
  */
20
22
  const Platform = {
21
23
  isWindows: os.platform() === 'win32',
24
+ isMac: os.platform() === 'darwin',
22
25
  /** Returns a command to create an empty file. */
23
26
  touch(filePath) {
24
- return this.isWindows
25
- ? {
26
- command: 'powershell.exe',
27
- args: [
28
- '-NoProfile',
29
- '-Command',
30
- `New-Item -Path "${filePath}" -ItemType File -Force`,
31
- ],
32
- }
33
- : { command: 'touch', args: [filePath] };
27
+ return {
28
+ command: process.execPath,
29
+ args: [
30
+ '-e',
31
+ `require("node:fs").writeFileSync(${JSON.stringify(filePath)}, "")`,
32
+ ],
33
+ };
34
34
  },
35
35
  /** Returns a command to read a file's content. */
36
36
  cat(filePath) {
37
- return this.isWindows
38
- ? { command: 'cmd.exe', args: ['/c', `type "${filePath}"`] }
39
- : { command: 'cat', args: [filePath] };
37
+ return {
38
+ command: process.execPath,
39
+ args: [
40
+ '-e',
41
+ `console.log(require("node:fs").readFileSync(${JSON.stringify(filePath)}, "utf8"))`,
42
+ ],
43
+ };
40
44
  },
41
45
  /** Returns a command to echo a string. */
42
46
  echo(text) {
43
- return this.isWindows
44
- ? { command: 'cmd.exe', args: ['/c', `echo ${text}`] }
45
- : { command: 'echo', args: [text] };
47
+ return {
48
+ command: process.execPath,
49
+ args: ['-e', `console.log(${JSON.stringify(text)})`],
50
+ };
46
51
  },
47
52
  /** Returns a command to perform a network request. */
48
53
  curl(url) {
49
- return { command: 'curl', args: ['-s', '--connect-timeout', '1', url] };
54
+ return {
55
+ command: process.execPath,
56
+ args: [
57
+ '-e',
58
+ `require("node:http").get(${JSON.stringify(url)}, (res) => { res.on("data", (d) => process.stdout.write(d)); res.on("end", () => process.exit(0)); }).on("error", () => process.exit(1));`,
59
+ ],
60
+ };
50
61
  },
51
62
  /** Returns a command that checks if the current terminal is interactive. */
52
63
  isPty() {
53
- return this.isWindows
54
- ? 'powershell.exe -NoProfile -Command "echo True"'
55
- : 'bash -c "if [ -t 1 ]; then echo True; else echo False; fi"';
64
+ // ShellExecutionService.execute expects a raw shell string
65
+ return `"${process.execPath}" -e "console.log(process.stdout.isTTY ? 'True' : 'False')"`;
56
66
  },
57
67
  /** Returns a path that is strictly outside the workspace and likely blocked. */
58
68
  getExternalBlockedPath() {
@@ -80,54 +90,66 @@ async function runCommand(command) {
80
90
  }
81
91
  }
82
92
  /**
83
- * Determines if the system has the necessary binaries to run the sandbox.
84
- * Throws an error if a supported platform is missing its required tools.
93
+ * Asserts the result of a sandboxed command execution, and provides detailed
94
+ * diagnostics on failure.
85
95
  */
86
- function ensureSandboxAvailable() {
87
- const platform = os.platform();
88
- if (platform === 'win32') {
89
- // Windows sandboxing relies on icacls, which is a core system utility and
90
- // always available.
91
- // TODO: reenable once flakiness is addressed
92
- return false;
96
+ function assertResult(result, command, expected) {
97
+ const isSuccess = result.status === 0;
98
+ const shouldBeSuccess = expected === 'success';
99
+ if (isSuccess === shouldBeSuccess) {
100
+ if (shouldBeSuccess) {
101
+ expect(result.status).toBe(0);
102
+ }
103
+ else {
104
+ expect(result.status).not.toBe(0);
105
+ }
106
+ return;
93
107
  }
94
- if (platform === 'darwin') {
95
- if (fs.existsSync('/usr/bin/sandbox-exec')) {
108
+ const commandLine = `${command.program} ${command.args.join(' ')}`;
109
+ const message = `Command ${shouldBeSuccess ? 'failed' : 'succeeded'} unexpectedly.
110
+ Command: ${commandLine}
111
+ CWD: ${command.cwd || 'N/A'}
112
+ Status: ${result.status} (expected ${expected})${result.stdout ? `\nStdout: ${result.stdout.trim()}` : ''}${result.stderr ? `\nStderr: ${result.stderr.trim()}` : ''}`;
113
+ throw new Error(message);
114
+ }
115
+ describe('SandboxManager Integration', () => {
116
+ let tempDirectories = [];
117
+ /**
118
+ * Creates a temporary directory and tracks it for automatic cleanup after each test.
119
+ * - macOS: Created in process.cwd() to avoid the seatbelt profile's global os.tmpdir() whitelist.
120
+ * - Win/Linux: Created in os.tmpdir() because enforcing sandbox restrictions inside a large directory can be very slow.
121
+ */
122
+ function createTempDir(prefix = 'gemini-sandbox-test-') {
123
+ const baseDir = Platform.isMac
124
+ ? path.join(process.cwd(), `.${prefix}`)
125
+ : path.join(os.tmpdir(), prefix);
126
+ const dir = fs.mkdtempSync(baseDir);
127
+ tempDirectories.push(dir);
128
+ return dir;
129
+ }
130
+ let workspace;
131
+ let manager;
132
+ beforeEach(() => {
133
+ tempDirectories = [];
134
+ // Create a fresh, isolated workspace for every test to prevent state
135
+ // leakage from causing intermittent or order-dependent test failures.
136
+ workspace = createTempDir('workspace-');
137
+ manager = createSandboxManager({ enabled: true }, { workspace });
138
+ });
139
+ afterEach(() => {
140
+ for (const dir of tempDirectories) {
96
141
  try {
97
- execSync('sandbox-exec -p "(version 1)(allow default)" echo test', {
98
- stdio: 'ignore',
99
- });
100
- return true;
142
+ fs.rmSync(dir, { recursive: true, force: true });
101
143
  }
102
144
  catch {
103
- // eslint-disable-next-line no-console
104
- console.warn('sandbox-exec is present but cannot be used (likely running inside a sandbox already). Skipping sandbox tests.');
105
- return false;
145
+ // Best-effort cleanup
106
146
  }
107
147
  }
108
- throw new Error('Sandboxing tests on macOS require /usr/bin/sandbox-exec to be present.');
109
- }
110
- if (platform === 'linux') {
111
- try {
112
- execSync('which bwrap', { stdio: 'ignore' });
113
- return true;
114
- }
115
- catch {
116
- throw new Error('Sandboxing tests on Linux require bubblewrap (bwrap) to be installed.');
117
- }
118
- }
119
- return false;
120
- }
121
- describe('SandboxManager Integration', () => {
122
- const workspace = process.cwd();
123
- const manager = createSandboxManager({ enabled: true }, { workspace });
124
- // Skip if we are on an unsupported platform or if it's a NoopSandboxManager
125
- const shouldSkip = manager instanceof NoopSandboxManager ||
126
- manager instanceof LocalSandboxManager ||
127
- !ensureSandboxAvailable();
128
- describe.skipIf(shouldSkip)('Cross-platform Sandbox Behavior', () => {
148
+ tempDirectories = [];
149
+ });
150
+ describe('Execution & Environment', () => {
129
151
  describe('Basic Execution', () => {
130
- it('executes commands within the workspace', async () => {
152
+ it('allows workspace execution', async () => {
131
153
  const { command, args } = Platform.echo('sandbox test');
132
154
  const sandboxed = await manager.prepareCommand({
133
155
  command,
@@ -136,12 +158,12 @@ describe('SandboxManager Integration', () => {
136
158
  env: process.env,
137
159
  });
138
160
  const result = await runCommand(sandboxed);
139
- expect(result.status).toBe(0);
161
+ assertResult(result, sandboxed, 'success');
140
162
  expect(result.stdout.trim()).toBe('sandbox test');
141
163
  });
142
164
  // The Windows sandbox wrapper (GeminiSandbox.exe) uses standard pipes
143
165
  // for I/O interception, which breaks ConPTY pseudo-terminal inheritance.
144
- it.skipIf(Platform.isWindows)('supports interactive pseudo-terminals (node-pty)', async () => {
166
+ it.skipIf(Platform.isWindows)('supports interactive terminals', async () => {
145
167
  const handle = await ShellExecutionService.execute(Platform.isPty(), workspace, () => { }, new AbortController().signal, true, {
146
168
  sanitizationConfig: getSecureSanitizationConfig(),
147
169
  sandboxManager: manager,
@@ -151,8 +173,175 @@ describe('SandboxManager Integration', () => {
151
173
  expect(result.output).toContain('True');
152
174
  });
153
175
  });
176
+ describe('Virtual Commands', () => {
177
+ it('handles virtual read commands', async () => {
178
+ const testFile = path.join(workspace, 'read-virtual.txt');
179
+ fs.writeFileSync(testFile, 'virtual read success');
180
+ const sandboxed = await manager.prepareCommand({
181
+ command: '__read',
182
+ args: [testFile],
183
+ cwd: workspace,
184
+ env: process.env,
185
+ });
186
+ const result = await runCommand(sandboxed);
187
+ assertResult(result, sandboxed, 'success');
188
+ expect(result.stdout.trim()).toBe('virtual read success');
189
+ });
190
+ it('handles virtual write commands', async () => {
191
+ const testFile = path.join(workspace, 'write-virtual.txt');
192
+ const sandboxed = await manager.prepareCommand({
193
+ command: '__write',
194
+ args: [testFile],
195
+ cwd: workspace,
196
+ env: process.env,
197
+ });
198
+ // Executing __write directly via runCommand hangs because 'cat' waits for stdin.
199
+ // Instead, we verify the command was translated correctly.
200
+ if (Platform.isWindows) {
201
+ // On Windows, the native helper handles '__write'
202
+ expect(sandboxed.args.includes('__write')).toBe(true);
203
+ }
204
+ else {
205
+ // On macOS/Linux, it is translated to a shell command with 'tee -- "$@" > /dev/null'
206
+ expect(sandboxed.args.join(' ')).toContain('tee --');
207
+ }
208
+ });
209
+ });
210
+ describe('Environment Sanitization', () => {
211
+ it('scrubs sensitive environment variables', async () => {
212
+ const checkEnvCmd = {
213
+ command: process.execPath,
214
+ args: [
215
+ '-e',
216
+ 'console.log(process.env.TEST_SECRET_TOKEN || "MISSING")',
217
+ ],
218
+ };
219
+ const sandboxed = await manager.prepareCommand({
220
+ ...checkEnvCmd,
221
+ cwd: workspace,
222
+ env: { ...process.env, TEST_SECRET_TOKEN: 'super-secret-value' },
223
+ policy: {
224
+ sanitizationConfig: {
225
+ enableEnvironmentVariableRedaction: true,
226
+ blockedEnvironmentVariables: ['TEST_SECRET_TOKEN'],
227
+ },
228
+ },
229
+ });
230
+ const result = await runCommand(sandboxed);
231
+ assertResult(result, sandboxed, 'success');
232
+ // By default, environment sanitization drops non-allowlisted vars or vars that look like secrets.
233
+ // Assuming TEST_SECRET_TOKEN is scrubbed:
234
+ expect(result.stdout.trim()).toBe('MISSING');
235
+ });
236
+ });
237
+ });
238
+ describe('Sandbox Policies & Modes', () => {
239
+ describe('Plan Mode Transitions', () => {
240
+ it('allows writing plans in plan mode', async () => {
241
+ // In Plan Mode, modeConfig sets readonly: true, allowOverrides: true
242
+ const planManager = createSandboxManager({ enabled: true }, { workspace, modeConfig: { readonly: true, allowOverrides: true } });
243
+ const plansDir = path.join(workspace, '.cell-cli/tmp/session-123/plans');
244
+ fs.mkdirSync(plansDir, { recursive: true });
245
+ const planFile = path.join(plansDir, 'feature-plan.md');
246
+ // The WriteFile tool requests explicit write access for the plan file path
247
+ const { command, args } = Platform.touch(planFile);
248
+ const sandboxed = await planManager.prepareCommand({
249
+ command,
250
+ args,
251
+ cwd: workspace,
252
+ env: process.env,
253
+ policy: { allowedPaths: [plansDir] },
254
+ });
255
+ const result = await runCommand(sandboxed);
256
+ assertResult(result, sandboxed, 'success');
257
+ expect(fs.existsSync(planFile)).toBe(true);
258
+ });
259
+ it('allows workspace writes after exiting plan mode', async () => {
260
+ // Upon exiting Plan Mode, the sandbox transitions to autoEdit/accepting_edits
261
+ // which sets readonly: false, allowOverrides: true
262
+ const editManager = createSandboxManager({ enabled: true }, { workspace, modeConfig: { readonly: false, allowOverrides: true } });
263
+ const taskFile = path.join(workspace, 'src/tasks/task.ts');
264
+ const taskDir = path.dirname(taskFile);
265
+ fs.mkdirSync(taskDir, { recursive: true });
266
+ // Simulate a generic edit anywhere in the workspace
267
+ const { command, args } = Platform.touch(taskFile);
268
+ const sandboxed = await editManager.prepareCommand({
269
+ command,
270
+ args,
271
+ cwd: workspace,
272
+ env: process.env,
273
+ policy: { allowedPaths: [taskDir] },
274
+ });
275
+ const result = await runCommand(sandboxed);
276
+ assertResult(result, sandboxed, 'success');
277
+ expect(fs.existsSync(taskFile)).toBe(true);
278
+ });
279
+ });
280
+ describe('Workspace Write Policies', () => {
281
+ it('enforces read-only mode', async () => {
282
+ const testFile = path.join(workspace, 'readonly-test.txt');
283
+ const { command, args } = Platform.touch(testFile);
284
+ const readonlyManager = createSandboxManager({ enabled: true }, {
285
+ workspace,
286
+ modeConfig: { readonly: true, allowOverrides: true },
287
+ });
288
+ const sandboxed = await readonlyManager.prepareCommand({
289
+ command,
290
+ args,
291
+ cwd: workspace,
292
+ env: process.env,
293
+ });
294
+ const result = await runCommand(sandboxed);
295
+ assertResult(result, sandboxed, 'failure');
296
+ });
297
+ it('allows writes for approved tools', async () => {
298
+ const testFile = path.join(workspace, 'approved-test.txt');
299
+ const command = Platform.isWindows ? 'cmd.exe' : 'sh';
300
+ const args = Platform.isWindows
301
+ ? ['/c', `echo test > ${testFile}`]
302
+ : ['-c', `echo test > "${testFile}"`];
303
+ // The shell wrapper is stripped by getCommandRoots, so the root command evaluated is 'echo'
304
+ const approvedTool = 'echo';
305
+ const approvedManager = createSandboxManager({ enabled: true }, {
306
+ workspace,
307
+ modeConfig: {
308
+ readonly: true,
309
+ allowOverrides: true,
310
+ approvedTools: [approvedTool],
311
+ },
312
+ });
313
+ const sandboxed = await approvedManager.prepareCommand({
314
+ command,
315
+ args,
316
+ cwd: workspace,
317
+ env: process.env,
318
+ });
319
+ const result = await runCommand(sandboxed);
320
+ assertResult(result, sandboxed, 'success');
321
+ expect(fs.existsSync(testFile)).toBe(true);
322
+ });
323
+ it('allows writes in YOLO mode', async () => {
324
+ const testFile = path.join(workspace, 'yolo-test.txt');
325
+ const { command, args } = Platform.touch(testFile);
326
+ const yoloManager = createSandboxManager({ enabled: true }, {
327
+ workspace,
328
+ modeConfig: { readonly: true, yolo: true, allowOverrides: true },
329
+ });
330
+ const sandboxed = await yoloManager.prepareCommand({
331
+ command,
332
+ args,
333
+ cwd: workspace,
334
+ env: process.env,
335
+ });
336
+ const result = await runCommand(sandboxed);
337
+ assertResult(result, sandboxed, 'success');
338
+ expect(fs.existsSync(testFile)).toBe(true);
339
+ });
340
+ });
341
+ });
342
+ describe('File System Security', () => {
154
343
  describe('File System Access', () => {
155
- it('blocks access outside the workspace', async () => {
344
+ it('prevents out-of-bounds access', async () => {
156
345
  const blockedPath = Platform.getExternalBlockedPath();
157
346
  const { command, args } = Platform.touch(blockedPath);
158
347
  const sandboxed = await manager.prepareCommand({
@@ -162,233 +351,506 @@ describe('SandboxManager Integration', () => {
162
351
  env: process.env,
163
352
  });
164
353
  const result = await runCommand(sandboxed);
165
- expect(result.status).not.toBe(0);
354
+ assertResult(result, sandboxed, 'failure');
166
355
  });
167
- it('allows dynamic expansion of permissions after a failure', async () => {
168
- const tempDir = fs.mkdtempSync(path.join(workspace, '..', 'expansion-'));
356
+ it('supports dynamic permission expansion', async () => {
357
+ const tempDir = createTempDir('expansion-');
169
358
  const testFile = path.join(tempDir, 'test.txt');
170
- try {
171
- const { command, args } = Platform.touch(testFile);
172
- // First attempt: fails due to sandbox restrictions
173
- const sandboxed1 = await manager.prepareCommand({
174
- command,
175
- args,
176
- cwd: workspace,
177
- env: process.env,
178
- });
179
- const result1 = await runCommand(sandboxed1);
180
- expect(result1.status).not.toBe(0);
181
- expect(fs.existsSync(testFile)).toBe(false);
182
- // Second attempt: succeeds with additional permissions
183
- const sandboxed2 = await manager.prepareCommand({
184
- command,
185
- args,
186
- cwd: workspace,
187
- env: process.env,
188
- policy: { allowedPaths: [tempDir] },
189
- });
190
- const result2 = await runCommand(sandboxed2);
191
- expect(result2.status).toBe(0);
192
- expect(fs.existsSync(testFile)).toBe(true);
193
- }
194
- finally {
195
- if (fs.existsSync(testFile))
196
- fs.unlinkSync(testFile);
197
- fs.rmSync(tempDir, { recursive: true, force: true });
198
- }
359
+ const { command, args } = Platform.touch(testFile);
360
+ // First attempt: fails due to sandbox restrictions
361
+ const sandboxed1 = await manager.prepareCommand({
362
+ command,
363
+ args,
364
+ cwd: workspace,
365
+ env: process.env,
366
+ });
367
+ const result1 = await runCommand(sandboxed1);
368
+ assertResult(result1, sandboxed1, 'failure');
369
+ expect(fs.existsSync(testFile)).toBe(false);
370
+ // Second attempt: succeeds with additional permissions
371
+ const sandboxed2 = await manager.prepareCommand({
372
+ command,
373
+ args,
374
+ cwd: workspace,
375
+ env: process.env,
376
+ policy: { allowedPaths: [tempDir] },
377
+ });
378
+ const result2 = await runCommand(sandboxed2);
379
+ assertResult(result2, sandboxed2, 'success');
380
+ expect(fs.existsSync(testFile)).toBe(true);
199
381
  });
200
- it('grants access to explicitly allowed paths', async () => {
201
- const allowedDir = fs.mkdtempSync(path.join(workspace, '..', 'allowed-'));
382
+ it('allows access to authorized paths', async () => {
383
+ const allowedDir = createTempDir('allowed-');
202
384
  const testFile = path.join(allowedDir, 'test.txt');
203
- try {
204
- const { command, args } = Platform.touch(testFile);
205
- const sandboxed = await manager.prepareCommand({
206
- command,
207
- args,
208
- cwd: workspace,
209
- env: process.env,
210
- policy: { allowedPaths: [allowedDir] },
211
- });
212
- const result = await runCommand(sandboxed);
213
- expect(result.status).toBe(0);
214
- expect(fs.existsSync(testFile)).toBe(true);
215
- }
216
- finally {
217
- if (fs.existsSync(testFile))
218
- fs.unlinkSync(testFile);
219
- fs.rmSync(allowedDir, { recursive: true, force: true });
220
- }
385
+ const { command, args } = Platform.touch(testFile);
386
+ const sandboxed = await manager.prepareCommand({
387
+ command,
388
+ args,
389
+ cwd: workspace,
390
+ env: process.env,
391
+ policy: { allowedPaths: [allowedDir] },
392
+ });
393
+ const result = await runCommand(sandboxed);
394
+ assertResult(result, sandboxed, 'success');
395
+ expect(fs.existsSync(testFile)).toBe(true);
221
396
  });
222
- it('blocks access to forbidden paths within the workspace', async () => {
223
- const tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'workspace-'));
397
+ it('protects forbidden paths from writes', async () => {
398
+ const tempWorkspace = createTempDir('workspace-');
224
399
  const forbiddenDir = path.join(tempWorkspace, 'forbidden');
225
400
  const testFile = path.join(forbiddenDir, 'test.txt');
226
401
  fs.mkdirSync(forbiddenDir);
227
- try {
228
- const osManager = createSandboxManager({ enabled: true }, {
229
- workspace: tempWorkspace,
230
- forbiddenPaths: async () => [forbiddenDir],
231
- });
232
- const { command, args } = Platform.touch(testFile);
233
- const sandboxed = await osManager.prepareCommand({
234
- command,
235
- args,
236
- cwd: tempWorkspace,
237
- env: process.env,
238
- });
239
- const result = await runCommand(sandboxed);
240
- expect(result.status).not.toBe(0);
241
- }
242
- finally {
243
- fs.rmSync(tempWorkspace, { recursive: true, force: true });
244
- }
402
+ const osManager = createSandboxManager({ enabled: true }, {
403
+ workspace: tempWorkspace,
404
+ forbiddenPaths: async () => [forbiddenDir],
405
+ });
406
+ const { command, args } = Platform.touch(testFile);
407
+ const sandboxed = await osManager.prepareCommand({
408
+ command,
409
+ args,
410
+ cwd: tempWorkspace,
411
+ env: process.env,
412
+ });
413
+ const result = await runCommand(sandboxed);
414
+ assertResult(result, sandboxed, 'failure');
245
415
  });
246
- it('blocks access to files inside forbidden directories recursively', async () => {
247
- const tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'workspace-'));
416
+ // Windows icacls does not reliably block read-up access for Low Integrity
417
+ // processes, so we skip read-specific assertions on Windows. The internal
418
+ // tool architecture prevents read bypasses via the C# wrapper and __read.
419
+ it.skipIf(Platform.isWindows)('protects forbidden paths from reads', async () => {
420
+ const tempWorkspace = createTempDir('workspace-');
421
+ const forbiddenDir = path.join(tempWorkspace, 'forbidden');
422
+ const testFile = path.join(forbiddenDir, 'test.txt');
423
+ fs.mkdirSync(forbiddenDir);
424
+ fs.writeFileSync(testFile, 'secret data');
425
+ const osManager = createSandboxManager({ enabled: true }, {
426
+ workspace: tempWorkspace,
427
+ forbiddenPaths: async () => [forbiddenDir],
428
+ });
429
+ const { command, args } = Platform.cat(testFile);
430
+ const sandboxed = await osManager.prepareCommand({
431
+ command,
432
+ args,
433
+ cwd: tempWorkspace,
434
+ env: process.env,
435
+ });
436
+ const result = await runCommand(sandboxed);
437
+ assertResult(result, sandboxed, 'failure');
438
+ });
439
+ it('protects forbidden directories recursively', async () => {
440
+ const tempWorkspace = createTempDir('workspace-');
248
441
  const forbiddenDir = path.join(tempWorkspace, 'forbidden');
249
442
  const nestedDir = path.join(forbiddenDir, 'nested');
250
443
  const nestedFile = path.join(nestedDir, 'test.txt');
444
+ // Create the base forbidden directory first so the manager can restrict access to it.
445
+ fs.mkdirSync(forbiddenDir);
446
+ const osManager = createSandboxManager({ enabled: true }, {
447
+ workspace: tempWorkspace,
448
+ forbiddenPaths: async () => [forbiddenDir],
449
+ });
450
+ // Execute a dummy command so the manager initializes its restrictions.
451
+ const dummyCommand = await osManager.prepareCommand({
452
+ ...Platform.echo('init'),
453
+ cwd: tempWorkspace,
454
+ env: process.env,
455
+ });
456
+ await runCommand(dummyCommand);
457
+ // Now create the nested items. They will inherit the sandbox restrictions from their parent.
251
458
  fs.mkdirSync(nestedDir, { recursive: true });
252
459
  fs.writeFileSync(nestedFile, 'secret');
253
- try {
254
- const osManager = createSandboxManager({ enabled: true }, {
255
- workspace: tempWorkspace,
256
- forbiddenPaths: async () => [forbiddenDir],
257
- });
258
- const { command, args } = Platform.cat(nestedFile);
259
- const sandboxed = await osManager.prepareCommand({
260
- command,
261
- args,
262
- cwd: tempWorkspace,
263
- env: process.env,
264
- });
265
- const result = await runCommand(sandboxed);
266
- expect(result.status).not.toBe(0);
267
- }
268
- finally {
269
- fs.rmSync(tempWorkspace, { recursive: true, force: true });
270
- }
460
+ const { command, args } = Platform.touch(nestedFile);
461
+ const sandboxed = await osManager.prepareCommand({
462
+ command,
463
+ args,
464
+ cwd: tempWorkspace,
465
+ env: process.env,
466
+ });
467
+ const result = await runCommand(sandboxed);
468
+ assertResult(result, sandboxed, 'failure');
271
469
  });
272
- it('prioritizes forbiddenPaths over allowedPaths', async () => {
273
- const tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'workspace-'));
470
+ it('prioritizes denials over allowances', async () => {
471
+ const tempWorkspace = createTempDir('workspace-');
274
472
  const conflictDir = path.join(tempWorkspace, 'conflict');
275
473
  const testFile = path.join(conflictDir, 'test.txt');
276
474
  fs.mkdirSync(conflictDir);
277
- try {
278
- const osManager = createSandboxManager({ enabled: true }, {
279
- workspace: tempWorkspace,
280
- forbiddenPaths: async () => [conflictDir],
281
- });
282
- const { command, args } = Platform.touch(testFile);
283
- const sandboxed = await osManager.prepareCommand({
284
- command,
285
- args,
286
- cwd: tempWorkspace,
287
- env: process.env,
288
- policy: {
289
- allowedPaths: [conflictDir],
290
- },
291
- });
292
- const result = await runCommand(sandboxed);
293
- expect(result.status).not.toBe(0);
294
- }
295
- finally {
296
- fs.rmSync(tempWorkspace, { recursive: true, force: true });
297
- }
475
+ const osManager = createSandboxManager({ enabled: true }, {
476
+ workspace: tempWorkspace,
477
+ forbiddenPaths: async () => [conflictDir],
478
+ });
479
+ const { command, args } = Platform.touch(testFile);
480
+ const sandboxed = await osManager.prepareCommand({
481
+ command,
482
+ args,
483
+ cwd: tempWorkspace,
484
+ env: process.env,
485
+ policy: {
486
+ allowedPaths: [conflictDir],
487
+ },
488
+ });
489
+ const result = await runCommand(sandboxed);
490
+ assertResult(result, sandboxed, 'failure');
298
491
  });
299
- it('gracefully ignores non-existent paths in allowedPaths and forbiddenPaths', async () => {
300
- const tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'workspace-'));
492
+ it('handles missing paths gracefully', async () => {
493
+ const tempWorkspace = createTempDir('workspace-');
301
494
  const nonExistentPath = path.join(tempWorkspace, 'does-not-exist');
302
- try {
303
- const osManager = createSandboxManager({ enabled: true }, {
304
- workspace: tempWorkspace,
305
- forbiddenPaths: async () => [nonExistentPath],
306
- });
307
- const { command, args } = Platform.echo('survived');
308
- const sandboxed = await osManager.prepareCommand({
309
- command,
310
- args,
311
- cwd: tempWorkspace,
312
- env: process.env,
313
- policy: {
314
- allowedPaths: [nonExistentPath],
315
- },
316
- });
317
- const result = await runCommand(sandboxed);
318
- expect(result.status).toBe(0);
319
- expect(result.stdout.trim()).toBe('survived');
320
- }
321
- finally {
322
- fs.rmSync(tempWorkspace, { recursive: true, force: true });
323
- }
495
+ const osManager = createSandboxManager({ enabled: true }, {
496
+ workspace: tempWorkspace,
497
+ forbiddenPaths: async () => [nonExistentPath],
498
+ });
499
+ const { command, args } = Platform.echo('survived');
500
+ const sandboxed = await osManager.prepareCommand({
501
+ command,
502
+ args,
503
+ cwd: tempWorkspace,
504
+ env: process.env,
505
+ policy: {
506
+ allowedPaths: [nonExistentPath],
507
+ },
508
+ });
509
+ const result = await runCommand(sandboxed);
510
+ assertResult(result, sandboxed, 'success');
511
+ expect(result.stdout.trim()).toBe('survived');
324
512
  });
325
- it('prevents creation of non-existent forbidden paths', async () => {
326
- // Windows icacls cannot explicitly protect paths that have not yet been created.
327
- if (Platform.isWindows)
328
- return;
329
- const tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'workspace-'));
513
+ it('prevents creation of forbidden files', async () => {
514
+ const tempWorkspace = createTempDir('workspace-');
330
515
  const nonExistentFile = path.join(tempWorkspace, 'never-created.txt');
331
- try {
332
- const osManager = createSandboxManager({ enabled: true }, {
333
- workspace: tempWorkspace,
334
- forbiddenPaths: async () => [nonExistentFile],
335
- });
336
- // We use touch to attempt creation of the file
337
- const { command: cmdTouch, args: argsTouch } = Platform.touch(nonExistentFile);
338
- const sandboxedCmd = await osManager.prepareCommand({
339
- command: cmdTouch,
340
- args: argsTouch,
341
- cwd: tempWorkspace,
342
- env: process.env,
343
- });
344
- // Execute the command, we expect it to fail (permission denied or read-only file system)
345
- const result = await runCommand(sandboxedCmd);
346
- expect(result.status).not.toBe(0);
347
- expect(fs.existsSync(nonExistentFile)).toBe(false);
348
- }
349
- finally {
350
- fs.rmSync(tempWorkspace, { recursive: true, force: true });
351
- }
516
+ const osManager = createSandboxManager({ enabled: true }, {
517
+ workspace: tempWorkspace,
518
+ forbiddenPaths: async () => [nonExistentFile],
519
+ });
520
+ // We use touch to attempt creation of the file
521
+ const { command: cmdTouch, args: argsTouch } = Platform.touch(nonExistentFile);
522
+ const sandboxedCmd = await osManager.prepareCommand({
523
+ command: cmdTouch,
524
+ args: argsTouch,
525
+ cwd: tempWorkspace,
526
+ env: process.env,
527
+ });
528
+ // Execute the command, we expect it to fail (permission denied or read-only file system)
529
+ const result = await runCommand(sandboxedCmd);
530
+ assertResult(result, sandboxedCmd, 'failure');
531
+ expect(fs.existsSync(nonExistentFile)).toBe(false);
352
532
  });
353
- it('blocks access to both a symlink and its target when the symlink is forbidden', async () => {
354
- if (Platform.isWindows)
355
- return;
356
- const tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'workspace-'));
533
+ it('restricts symlinks to forbidden targets', async () => {
534
+ const tempWorkspace = createTempDir('workspace-');
357
535
  const targetFile = path.join(tempWorkspace, 'target.txt');
358
536
  const symlinkFile = path.join(tempWorkspace, 'link.txt');
359
537
  fs.writeFileSync(targetFile, 'secret data');
360
538
  fs.symlinkSync(targetFile, symlinkFile);
361
- try {
362
- const osManager = createSandboxManager({ enabled: true }, {
363
- workspace: tempWorkspace,
364
- forbiddenPaths: async () => [symlinkFile],
365
- });
366
- // Attempt to read the target file directly
367
- const { command: cmdTarget, args: argsTarget } = Platform.cat(targetFile);
368
- const commandTarget = await osManager.prepareCommand({
369
- command: cmdTarget,
370
- args: argsTarget,
371
- cwd: tempWorkspace,
372
- env: process.env,
373
- });
374
- const resultTarget = await runCommand(commandTarget);
375
- expect(resultTarget.status).not.toBe(0);
376
- // Attempt to read via the symlink
377
- const { command: cmdLink, args: argsLink } = Platform.cat(symlinkFile);
378
- const commandLink = await osManager.prepareCommand({
379
- command: cmdLink,
380
- args: argsLink,
381
- cwd: tempWorkspace,
539
+ const osManager = createSandboxManager({ enabled: true }, {
540
+ workspace: tempWorkspace,
541
+ forbiddenPaths: async () => [symlinkFile],
542
+ });
543
+ // Attempt to write to the target file directly
544
+ const { command: cmdTarget, args: argsTarget } = Platform.touch(targetFile);
545
+ const commandTarget = await osManager.prepareCommand({
546
+ command: cmdTarget,
547
+ args: argsTarget,
548
+ cwd: tempWorkspace,
549
+ env: process.env,
550
+ });
551
+ const resultTarget = await runCommand(commandTarget);
552
+ assertResult(resultTarget, commandTarget, 'failure');
553
+ // Attempt to write via the symlink
554
+ const { command: cmdLink, args: argsLink } = Platform.touch(symlinkFile);
555
+ const commandLink = await osManager.prepareCommand({
556
+ command: cmdLink,
557
+ args: argsLink,
558
+ cwd: tempWorkspace,
559
+ env: process.env,
560
+ });
561
+ const resultLink = await runCommand(commandLink);
562
+ assertResult(resultLink, commandLink, 'failure');
563
+ });
564
+ });
565
+ describe('Governance Files', () => {
566
+ it('prevents modification of governance files', async () => {
567
+ // Ensure workspace is initialized and governance files are created
568
+ const { command: echoCmd, args: echoArgs } = Platform.echo('test');
569
+ await manager.prepareCommand({
570
+ command: echoCmd,
571
+ args: echoArgs,
572
+ cwd: workspace,
573
+ env: process.env,
574
+ // Even if the entire workspace is explicitly allowed, governance files must be protected
575
+ policy: { allowedPaths: [workspace] },
576
+ });
577
+ for (const file of GOVERNANCE_FILES) {
578
+ const filePath = path.join(workspace, file.path);
579
+ // Try to append to/overwrite the file or create a file inside the directory
580
+ const { command, args } = file.isDirectory
581
+ ? Platform.touch(path.join(filePath, 'evil.txt'))
582
+ : Platform.touch(filePath);
583
+ const sandboxed = await manager.prepareCommand({
584
+ command,
585
+ args,
586
+ cwd: workspace,
382
587
  env: process.env,
383
588
  });
384
- const resultLink = await runCommand(commandLink);
385
- expect(resultLink.status).not.toBe(0);
386
- }
387
- finally {
388
- fs.rmSync(tempWorkspace, { recursive: true, force: true });
589
+ const result = await runCommand(sandboxed);
590
+ assertResult(result, sandboxed, 'failure');
389
591
  }
390
592
  });
391
593
  });
594
+ describe('Git Worktree Support', () => {
595
+ it('supports git worktrees', async () => {
596
+ const mainRepo = createTempDir('main-repo-');
597
+ const worktreeDir = createTempDir('worktree-');
598
+ const mainGitDir = path.join(mainRepo, '.git');
599
+ fs.mkdirSync(mainGitDir, { recursive: true });
600
+ fs.writeFileSync(path.join(mainGitDir, 'config'), '[core]\n\trepositoryformatversion = 0\n');
601
+ const worktreeGitDir = path.join(mainGitDir, 'worktrees', 'test-worktree');
602
+ fs.mkdirSync(worktreeGitDir, { recursive: true });
603
+ // Create the .git file in the worktree directory pointing to the worktree git dir
604
+ fs.writeFileSync(path.join(worktreeDir, '.git'), `gitdir: ${worktreeGitDir}\n`);
605
+ // Create the backlink from worktree git dir to the worktree's .git file
606
+ const backlinkPath = path.join(worktreeGitDir, 'gitdir');
607
+ fs.writeFileSync(backlinkPath, path.join(worktreeDir, '.git'));
608
+ // Create a file in the worktree git dir that we want to access
609
+ const secretFile = path.join(worktreeGitDir, 'secret.txt');
610
+ fs.writeFileSync(secretFile, 'git-secret');
611
+ const osManager = createSandboxManager({ enabled: true }, { workspace: worktreeDir });
612
+ const { command, args } = Platform.cat(secretFile);
613
+ const sandboxed = await osManager.prepareCommand({
614
+ command,
615
+ args,
616
+ cwd: worktreeDir,
617
+ env: process.env,
618
+ });
619
+ const result = await runCommand(sandboxed);
620
+ assertResult(result, sandboxed, 'success');
621
+ expect(result.stdout.trim()).toBe('git-secret');
622
+ });
623
+ it('protects git worktree metadata', async () => {
624
+ const mainRepo = createTempDir('main-repo-');
625
+ const worktreeDir = createTempDir('worktree-');
626
+ const mainGitDir = path.join(mainRepo, '.git');
627
+ fs.mkdirSync(mainGitDir, { recursive: true });
628
+ const worktreeGitDir = path.join(mainGitDir, 'worktrees', 'test-worktree');
629
+ fs.mkdirSync(worktreeGitDir, { recursive: true });
630
+ fs.writeFileSync(path.join(worktreeDir, '.git'), `gitdir: ${worktreeGitDir}\n`);
631
+ fs.writeFileSync(path.join(worktreeGitDir, 'gitdir'), path.join(worktreeDir, '.git'));
632
+ const targetFile = path.join(worktreeGitDir, 'secret.txt');
633
+ const osManager = createSandboxManager({ enabled: true },
634
+ // Use YOLO mode to ensure the workspace is fully writable, but git worktrees should still be read-only
635
+ { workspace: worktreeDir, modeConfig: { yolo: true } });
636
+ const { command, args } = Platform.touch(targetFile);
637
+ const sandboxed = await osManager.prepareCommand({
638
+ command,
639
+ args,
640
+ cwd: worktreeDir,
641
+ env: process.env,
642
+ });
643
+ const result = await runCommand(sandboxed);
644
+ assertResult(result, sandboxed, 'failure');
645
+ expect(fs.existsSync(targetFile)).toBe(false);
646
+ });
647
+ });
648
+ });
649
+ describe('Governance Files', () => {
650
+ it('blocks write access to governance files in the workspace', async () => {
651
+ const tempWorkspace = createTempDir('workspace-');
652
+ const gitDir = path.join(tempWorkspace, '.git');
653
+ fs.mkdirSync(gitDir);
654
+ const testFile = path.join(gitDir, 'config');
655
+ const osManager = createSandboxManager({ enabled: true }, { workspace: tempWorkspace });
656
+ const { command, args } = Platform.touch(testFile);
657
+ const sandboxed = await osManager.prepareCommand({
658
+ command,
659
+ args,
660
+ cwd: tempWorkspace,
661
+ env: process.env,
662
+ });
663
+ const result = await runCommand(sandboxed);
664
+ assertResult(result, sandboxed, 'failure');
665
+ expect(fs.existsSync(testFile)).toBe(false);
666
+ });
667
+ it('allows write access to governance files when explicitly requested via additionalPermissions', async () => {
668
+ const tempWorkspace = createTempDir('workspace-');
669
+ const gitDir = path.join(tempWorkspace, '.git');
670
+ fs.mkdirSync(gitDir);
671
+ const testFile = path.join(gitDir, 'config');
672
+ const osManager = createSandboxManager({ enabled: true }, { workspace: tempWorkspace });
673
+ const { command, args } = Platform.touch(testFile);
674
+ const sandboxed = await osManager.prepareCommand({
675
+ command,
676
+ args,
677
+ cwd: tempWorkspace,
678
+ env: process.env,
679
+ policy: {
680
+ additionalPermissions: { fileSystem: { write: [gitDir] } },
681
+ },
682
+ });
683
+ const result = await runCommand(sandboxed);
684
+ assertResult(result, sandboxed, 'success');
685
+ expect(fs.existsSync(testFile)).toBe(true);
686
+ });
687
+ });
688
+ describe('Git Worktree Support', () => {
689
+ it('allows access to git common directory in a worktree', async () => {
690
+ const mainRepo = createTempDir('main-repo-');
691
+ const worktreeDir = createTempDir('worktree-');
692
+ const mainGitDir = path.join(mainRepo, '.git');
693
+ fs.mkdirSync(mainGitDir, { recursive: true });
694
+ fs.writeFileSync(path.join(mainGitDir, 'config'), '[core]\n\trepositoryformatversion = 0\n');
695
+ const worktreeGitDir = path.join(mainGitDir, 'worktrees', 'test-worktree');
696
+ fs.mkdirSync(worktreeGitDir, { recursive: true });
697
+ // Create the .git file in the worktree directory pointing to the worktree git dir
698
+ fs.writeFileSync(path.join(worktreeDir, '.git'), `gitdir: ${worktreeGitDir}\n`);
699
+ // Create the backlink from worktree git dir to the worktree's .git file
700
+ const backlinkPath = path.join(worktreeGitDir, 'gitdir');
701
+ fs.writeFileSync(backlinkPath, path.join(worktreeDir, '.git'));
702
+ // Create a file in the worktree git dir that we want to access
703
+ const secretFile = path.join(worktreeGitDir, 'secret.txt');
704
+ fs.writeFileSync(secretFile, 'git-secret');
705
+ const osManager = createSandboxManager({ enabled: true }, { workspace: worktreeDir });
706
+ const { command, args } = Platform.cat(secretFile);
707
+ const sandboxed = await osManager.prepareCommand({
708
+ command,
709
+ args,
710
+ cwd: worktreeDir,
711
+ env: process.env,
712
+ });
713
+ const result = await runCommand(sandboxed);
714
+ assertResult(result, sandboxed, 'success');
715
+ expect(result.stdout.trim()).toBe('git-secret');
716
+ });
717
+ it('blocks write access to git common directory in a worktree', async () => {
718
+ const mainRepo = createTempDir('main-repo-');
719
+ const worktreeDir = createTempDir('worktree-');
720
+ const mainGitDir = path.join(mainRepo, '.git');
721
+ fs.mkdirSync(mainGitDir, { recursive: true });
722
+ const worktreeGitDir = path.join(mainGitDir, 'worktrees', 'test-worktree');
723
+ fs.mkdirSync(worktreeGitDir, { recursive: true });
724
+ fs.writeFileSync(path.join(worktreeDir, '.git'), `gitdir: ${worktreeGitDir}\n`);
725
+ fs.writeFileSync(path.join(worktreeGitDir, 'gitdir'), path.join(worktreeDir, '.git'));
726
+ const targetFile = path.join(worktreeGitDir, 'secret.txt');
727
+ const osManager = createSandboxManager({ enabled: true },
728
+ // Use YOLO mode to ensure the workspace is fully writable, but git worktrees should still be read-only
729
+ { workspace: worktreeDir, modeConfig: { yolo: true } });
730
+ const { command, args } = Platform.touch(targetFile);
731
+ const sandboxed = await osManager.prepareCommand({
732
+ command,
733
+ args,
734
+ cwd: worktreeDir,
735
+ env: process.env,
736
+ });
737
+ const result = await runCommand(sandboxed);
738
+ assertResult(result, sandboxed, 'failure');
739
+ expect(fs.existsSync(targetFile)).toBe(false);
740
+ });
741
+ it('blocks write access to git common directory in a worktree when not explicitly requested via additionalPermissions', async () => {
742
+ const mainRepo = createTempDir('main-repo-');
743
+ const worktreeDir = createTempDir('worktree-');
744
+ const mainGitDir = path.join(mainRepo, '.git');
745
+ fs.mkdirSync(mainGitDir, { recursive: true });
746
+ const worktreeGitDir = path.join(mainGitDir, 'worktrees', 'test-worktree');
747
+ fs.mkdirSync(worktreeGitDir, { recursive: true });
748
+ fs.writeFileSync(path.join(worktreeDir, '.git'), `gitdir: ${worktreeGitDir}\n`);
749
+ fs.writeFileSync(path.join(worktreeGitDir, 'gitdir'), path.join(worktreeDir, '.git'));
750
+ const targetFile = path.join(worktreeGitDir, 'secret.txt');
751
+ const osManager = createSandboxManager({ enabled: true }, { workspace: worktreeDir });
752
+ const { command, args } = Platform.touch(targetFile);
753
+ const sandboxed = await osManager.prepareCommand({
754
+ command,
755
+ args,
756
+ cwd: worktreeDir,
757
+ env: process.env,
758
+ });
759
+ const result = await runCommand(sandboxed);
760
+ assertResult(result, sandboxed, 'failure');
761
+ expect(fs.existsSync(targetFile)).toBe(false);
762
+ });
763
+ it('allows write access to git common directory in a worktree when explicitly requested via additionalPermissions', async () => {
764
+ const mainRepo = createTempDir('main-repo-');
765
+ const worktreeDir = createTempDir('worktree-');
766
+ const mainGitDir = path.join(mainRepo, '.git');
767
+ fs.mkdirSync(mainGitDir, { recursive: true });
768
+ const worktreeGitDir = path.join(mainGitDir, 'worktrees', 'test-worktree');
769
+ fs.mkdirSync(worktreeGitDir, { recursive: true });
770
+ fs.writeFileSync(path.join(worktreeDir, '.git'), `gitdir: ${worktreeGitDir}\n`);
771
+ fs.writeFileSync(path.join(worktreeGitDir, 'gitdir'), path.join(worktreeDir, '.git'));
772
+ const targetFile = path.join(worktreeGitDir, 'secret.txt');
773
+ const osManager = createSandboxManager({ enabled: true }, { workspace: worktreeDir });
774
+ const { command, args } = Platform.touch(targetFile);
775
+ const sandboxed = await osManager.prepareCommand({
776
+ command,
777
+ args,
778
+ cwd: worktreeDir,
779
+ env: process.env,
780
+ policy: {
781
+ additionalPermissions: { fileSystem: { write: [worktreeGitDir] } },
782
+ },
783
+ });
784
+ const result = await runCommand(sandboxed);
785
+ assertResult(result, sandboxed, 'success');
786
+ expect(fs.existsSync(targetFile)).toBe(true);
787
+ });
788
+ it('allows write access to external git directory in a non-worktree environment when explicitly requested via additionalPermissions', async () => {
789
+ const externalGitDir = createTempDir('external-git-');
790
+ const workspaceDir = createTempDir('workspace-');
791
+ fs.mkdirSync(externalGitDir, { recursive: true });
792
+ fs.writeFileSync(path.join(workspaceDir, '.git'), `gitdir: ${externalGitDir}\n`);
793
+ const targetFile = path.join(externalGitDir, 'secret.txt');
794
+ const osManager = createSandboxManager({ enabled: true }, { workspace: workspaceDir });
795
+ const { command, args } = Platform.touch(targetFile);
796
+ const sandboxed = await osManager.prepareCommand({
797
+ command,
798
+ args,
799
+ cwd: workspaceDir,
800
+ env: process.env,
801
+ policy: {
802
+ additionalPermissions: { fileSystem: { write: [externalGitDir] } },
803
+ },
804
+ });
805
+ const result = await runCommand(sandboxed);
806
+ assertResult(result, sandboxed, 'success');
807
+ expect(fs.existsSync(targetFile)).toBe(true);
808
+ });
809
+ });
810
+ describe('Git and Governance Write Access', () => {
811
+ it('allows write access to .gitignore when workspace is writable', async () => {
812
+ const testFile = path.join(workspace, '.gitignore');
813
+ fs.writeFileSync(testFile, 'initial');
814
+ const editManager = createSandboxManager({ enabled: true }, { workspace, modeConfig: { readonly: false, allowOverrides: true } });
815
+ const { command, args } = Platform.touch(testFile);
816
+ const sandboxed = await editManager.prepareCommand({
817
+ command,
818
+ args,
819
+ cwd: workspace,
820
+ env: process.env,
821
+ });
822
+ const result = await runCommand(sandboxed);
823
+ assertResult(result, sandboxed, 'success');
824
+ expect(fs.existsSync(testFile)).toBe(true);
825
+ });
826
+ it('automatically allows write access to .git when running git command and workspace is writable', async () => {
827
+ const gitDir = path.join(workspace, '.git');
828
+ if (!fs.existsSync(gitDir))
829
+ fs.mkdirSync(gitDir);
830
+ const lockFile = path.join(gitDir, 'index.lock');
831
+ const editManager = createSandboxManager({ enabled: true }, { workspace, modeConfig: { readonly: false, allowOverrides: true } });
832
+ // We use a command that looks like git to trigger the special handling.
833
+ // LinuxSandboxManager identifies the command root from the shell wrapper.
834
+ const { command: nodePath, args: nodeArgs } = Platform.touch(lockFile);
835
+ const commandString = Platform.isWindows
836
+ ? `git --version > NUL && "${nodePath.replace(/\\/g, '/')}" ${nodeArgs
837
+ .map((a) => `'${a.replace(/\\/g, '/')}'`)
838
+ .join(' ')}`
839
+ : `git --version > /dev/null; "${nodePath}" ${nodeArgs
840
+ .map((a) => (a.includes(' ') || a.includes('(') ? `'${a}'` : a))
841
+ .join(' ')}`;
842
+ const sandboxed = await editManager.prepareCommand({
843
+ command: 'sh',
844
+ args: ['-c', commandString],
845
+ cwd: workspace,
846
+ env: process.env,
847
+ });
848
+ const result = await runCommand(sandboxed);
849
+ assertResult(result, sandboxed, 'success');
850
+ expect(fs.existsSync(lockFile)).toBe(true);
851
+ });
852
+ });
853
+ describe('Network Security', () => {
392
854
  describe('Network Access', () => {
393
855
  let server;
394
856
  let url;
@@ -413,7 +875,7 @@ describe('SandboxManager Integration', () => {
413
875
  });
414
876
  // Windows Job Object rate limits exempt loopback (127.0.0.1) traffic,
415
877
  // so this test cannot verify loopback blocking on Windows.
416
- it.skipIf(Platform.isWindows)('blocks network access by default', async () => {
878
+ it.skipIf(Platform.isWindows)('prevents unauthorized network access', async () => {
417
879
  const { command, args } = Platform.curl(url);
418
880
  const sandboxed = await manager.prepareCommand({
419
881
  command,
@@ -422,9 +884,9 @@ describe('SandboxManager Integration', () => {
422
884
  env: process.env,
423
885
  });
424
886
  const result = await runCommand(sandboxed);
425
- expect(result.status).not.toBe(0);
887
+ assertResult(result, sandboxed, 'failure');
426
888
  });
427
- it('grants network access when explicitly allowed', async () => {
889
+ it('allows authorized network access', async () => {
428
890
  const { command, args } = Platform.curl(url);
429
891
  const sandboxed = await manager.prepareCommand({
430
892
  command,
@@ -434,7 +896,7 @@ describe('SandboxManager Integration', () => {
434
896
  policy: { networkAccess: true },
435
897
  });
436
898
  const result = await runCommand(sandboxed);
437
- expect(result.status).toBe(0);
899
+ assertResult(result, sandboxed, 'success');
438
900
  if (!Platform.isWindows) {
439
901
  expect(result.stdout.trim()).toBe('ok');
440
902
  }