muonroi-cli 1.7.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +122 -122
  3. package/dist/packages/agent-harness-core/src/event-filter.js +1 -0
  4. package/dist/packages/agent-harness-core/src/event-redact.js +8 -0
  5. package/dist/packages/agent-harness-core/src/mcp-server.d.ts +16 -0
  6. package/dist/packages/agent-harness-core/src/mcp-server.js +88 -10
  7. package/dist/packages/agent-harness-core/src/protocol.d.ts +15 -0
  8. package/dist/packages/agent-harness-core/src/selector.js +2 -2
  9. package/dist/packages/agent-harness-core/src/transports/sidechannel.js +1 -1
  10. package/dist/packages/agent-harness-opentui/src/agent-mode.js +1 -1
  11. package/dist/packages/agent-harness-opentui/src/install.js +1 -1
  12. package/dist/src/__test-stubs__/vitest-setup.js +4 -0
  13. package/dist/src/agent-harness/__tests__/mock-model.spec.js +54 -0
  14. package/dist/src/agent-harness/mock-model.d.ts +1 -1
  15. package/dist/src/agent-harness/mock-model.js +32 -3
  16. package/dist/src/agent-harness/test-spawn.js +3 -1
  17. package/dist/src/chat/chat-keychain.js +9 -1
  18. package/dist/src/cli/cost-forensics.js +12 -12
  19. package/dist/src/cli/keys.d.ts +2 -2
  20. package/dist/src/cli/keys.js +107 -28
  21. package/dist/src/cli/usage-report.js +2 -2
  22. package/dist/src/council/__tests__/abort-threading.test.d.ts +1 -0
  23. package/dist/src/council/__tests__/abort-threading.test.js +193 -0
  24. package/dist/src/council/__tests__/clarification-prompt.test.js +21 -1
  25. package/dist/src/council/__tests__/council-turn-length-emit.test.d.ts +1 -0
  26. package/dist/src/council/__tests__/council-turn-length-emit.test.js +130 -0
  27. package/dist/src/council/__tests__/debate-round-budget.test.d.ts +1 -0
  28. package/dist/src/council/__tests__/debate-round-budget.test.js +45 -0
  29. package/dist/src/council/__tests__/evaluator-metrics.test.js +3 -0
  30. package/dist/src/council/__tests__/post-debate-recommendation.test.d.ts +1 -0
  31. package/dist/src/council/__tests__/post-debate-recommendation.test.js +58 -0
  32. package/dist/src/council/__tests__/research-tools.test.js +4 -0
  33. package/dist/src/council/__tests__/round-tools.test.js +6 -0
  34. package/dist/src/council/clarifier-question-cap.test.d.ts +12 -0
  35. package/dist/src/council/clarifier-question-cap.test.js +73 -0
  36. package/dist/src/council/clarifier.d.ts +29 -1
  37. package/dist/src/council/clarifier.js +64 -1
  38. package/dist/src/council/debate-planner.d.ts +2 -1
  39. package/dist/src/council/debate-planner.js +5 -3
  40. package/dist/src/council/debate.d.ts +9 -0
  41. package/dist/src/council/debate.js +88 -8
  42. package/dist/src/council/index.d.ts +46 -1
  43. package/dist/src/council/index.js +125 -19
  44. package/dist/src/council/llm.js +41 -13
  45. package/dist/src/council/prompts.js +56 -21
  46. package/dist/src/council/types.d.ts +14 -1
  47. package/dist/src/ee/__tests__/export-transcripts.test.js +1 -1
  48. package/dist/src/ee/bridge.d.ts +3 -1
  49. package/dist/src/ee/bridge.js +9 -3
  50. package/dist/src/ee/bridge.test.js +1 -2
  51. package/dist/src/ee/client.js +15 -2
  52. package/dist/src/ee/recall-ledger.d.ts +20 -0
  53. package/dist/src/ee/recall-ledger.js +40 -1
  54. package/dist/src/ee/search.d.ts +25 -0
  55. package/dist/src/ee/search.js +73 -0
  56. package/dist/src/ee/types.d.ts +15 -1
  57. package/dist/src/ee/who-am-i.d.ts +41 -0
  58. package/dist/src/ee/who-am-i.js +143 -0
  59. package/dist/src/ee/who-am-i.test.d.ts +1 -0
  60. package/dist/src/ee/who-am-i.test.js +101 -0
  61. package/dist/src/generated/version.d.ts +1 -1
  62. package/dist/src/generated/version.js +1 -1
  63. package/dist/src/headless/council-answers.js +1 -0
  64. package/dist/src/headless/output.d.ts +30 -1
  65. package/dist/src/headless/output.js +180 -0
  66. package/dist/src/headless/output.test.js +61 -1
  67. package/dist/src/hooks/index.js +48 -2
  68. package/dist/src/index.d.ts +2 -0
  69. package/dist/src/index.js +143 -104
  70. package/dist/src/lsp/client-normalize.test.d.ts +12 -0
  71. package/dist/src/lsp/client-normalize.test.js +34 -0
  72. package/dist/src/lsp/client.d.ts +1 -0
  73. package/dist/src/lsp/client.js +10 -2
  74. package/dist/src/lsp/manager.d.ts +1 -1
  75. package/dist/src/lsp/manager.js +2 -2
  76. package/dist/src/lsp/manager.test.js +2 -2
  77. package/dist/src/lsp/runtime.d.ts +1 -1
  78. package/dist/src/lsp/runtime.js +2 -2
  79. package/dist/src/maintain/__tests__/gh-create-pr.test.js +7 -3
  80. package/dist/src/maintain/__tests__/pr-builder.test.js +7 -3
  81. package/dist/src/mcp/__tests__/client-pool.spec.js +3 -3
  82. package/dist/src/mcp/__tests__/ee-tools.test.js +42 -14
  83. package/dist/src/mcp/__tests__/forensics-tools.test.js +3 -3
  84. package/dist/src/mcp/__tests__/lsp-tools.test.js +4 -4
  85. package/dist/src/mcp/client-pool.d.ts +1 -1
  86. package/dist/src/mcp/client-pool.js +25 -9
  87. package/dist/src/mcp/ee-tools.d.ts +10 -0
  88. package/dist/src/mcp/ee-tools.js +34 -0
  89. package/dist/src/mcp/oauth-callback.js +2 -2
  90. package/dist/src/mcp/parse-headers.test.js +14 -14
  91. package/dist/src/mcp/self-verify-jobs.js +1 -3
  92. package/dist/src/mcp/setup-guide-text.js +74 -74
  93. package/dist/src/mcp/smoke.test.js +43 -43
  94. package/dist/src/models/catalog-gemini.test.js +12 -10
  95. package/dist/src/models/catalog.json +106 -38
  96. package/dist/src/ops/doctor.js +7 -7
  97. package/dist/src/orchestrator/__tests__/auto-commit.test.d.ts +1 -0
  98. package/dist/src/orchestrator/__tests__/auto-commit.test.js +142 -0
  99. package/dist/src/orchestrator/__tests__/batch-turn-runner.test.js +2 -0
  100. package/dist/src/orchestrator/__tests__/delegations.test.d.ts +1 -0
  101. package/dist/src/orchestrator/__tests__/delegations.test.js +96 -0
  102. package/dist/src/orchestrator/__tests__/message-processor.test.js +1 -0
  103. package/dist/src/orchestrator/__tests__/stream-runner.test.js +29 -0
  104. package/dist/src/orchestrator/agent.test.js +5 -2
  105. package/dist/src/orchestrator/auto-commit.d.ts +110 -0
  106. package/dist/src/orchestrator/auto-commit.js +364 -0
  107. package/dist/src/orchestrator/batch-turn-runner.d.ts +3 -2
  108. package/dist/src/orchestrator/batch-turn-runner.js +2 -1
  109. package/dist/src/orchestrator/compaction-proposer-prompt.d.ts +8 -0
  110. package/dist/src/orchestrator/compaction-proposer-prompt.js +43 -0
  111. package/dist/src/orchestrator/compaction.d.ts +22 -0
  112. package/dist/src/orchestrator/compaction.js +42 -0
  113. package/dist/src/orchestrator/delegations.d.ts +1 -0
  114. package/dist/src/orchestrator/delegations.js +41 -0
  115. package/dist/src/orchestrator/error-utils.js +27 -2
  116. package/dist/src/orchestrator/message-processor.d.ts +21 -5
  117. package/dist/src/orchestrator/message-processor.js +219 -2573
  118. package/dist/src/orchestrator/orchestrator.d.ts +8 -2
  119. package/dist/src/orchestrator/orchestrator.js +187 -34
  120. package/dist/src/orchestrator/preprocessor.d.ts +13 -0
  121. package/dist/src/orchestrator/preprocessor.js +145 -0
  122. package/dist/src/orchestrator/prompts.d.ts +8 -0
  123. package/dist/src/orchestrator/prompts.js +76 -60
  124. package/dist/src/orchestrator/safety-askcard.d.ts +66 -0
  125. package/dist/src/orchestrator/safety-askcard.js +74 -0
  126. package/dist/src/orchestrator/sandbox.test.js +8 -5
  127. package/dist/src/orchestrator/scope-ceiling.js +1 -1
  128. package/dist/src/orchestrator/stall-watchdog.d.ts +47 -0
  129. package/dist/src/orchestrator/stall-watchdog.js +23 -0
  130. package/dist/src/orchestrator/stall-watchdog.test.js +36 -1
  131. package/dist/src/orchestrator/stream-runner.js +52 -30
  132. package/dist/src/orchestrator/sub-agent-model-tier.js +1 -1
  133. package/dist/src/orchestrator/subagent-compactor.d.ts +14 -0
  134. package/dist/src/orchestrator/subagent-compactor.js +83 -10
  135. package/dist/src/orchestrator/subagent-compactor.spec.js +132 -0
  136. package/dist/src/orchestrator/text-tool-call-detector.d.ts +9 -5
  137. package/dist/src/orchestrator/text-tool-call-detector.js +16 -12
  138. package/dist/src/orchestrator/text-tool-call-detector.test.js +66 -8
  139. package/dist/src/orchestrator/tool-args-repair.js +1 -1
  140. package/dist/src/orchestrator/tool-engine.d.ts +151 -0
  141. package/dist/src/orchestrator/tool-engine.js +3079 -0
  142. package/dist/src/orchestrator/turn-runner-deps.d.ts +1 -0
  143. package/dist/src/pil/__tests__/discovery-types.test.js +15 -4
  144. package/dist/src/pil/__tests__/discovery.test.js +89 -145
  145. package/dist/src/pil/__tests__/dual-run.test.js +2 -0
  146. package/dist/src/pil/__tests__/layer1-intent-trace.test.js +3 -0
  147. package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
  148. package/dist/src/pil/__tests__/layer16-clarity.test.js +35 -32
  149. package/dist/src/pil/__tests__/layer18-acceptance.test.js +1 -78
  150. package/dist/src/pil/__tests__/layer2_5-ponytail.test.d.ts +1 -0
  151. package/dist/src/pil/__tests__/layer2_5-ponytail.test.js +53 -0
  152. package/dist/src/pil/__tests__/layer3-ee-injection.test.js +67 -4
  153. package/dist/src/pil/__tests__/layer3-injected-chunk.test.js +14 -2
  154. package/dist/src/pil/__tests__/layer4-gsd.test.js +30 -0
  155. package/dist/src/pil/__tests__/layer5-context.test.js +39 -1
  156. package/dist/src/pil/__tests__/layer6-output.test.js +30 -2
  157. package/dist/src/pil/__tests__/orchestrator-integration.test.js +3 -1
  158. package/dist/src/pil/__tests__/pipeline.test.js +11 -8
  159. package/dist/src/pil/__tests__/response-tools.test.js +52 -0
  160. package/dist/src/pil/agent-operating-contract.d.ts +1 -1
  161. package/dist/src/pil/agent-operating-contract.js +5 -3
  162. package/dist/src/pil/agent-operating-contract.test.js +2 -2
  163. package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
  164. package/dist/src/pil/cheap-model-playbook.js +1 -1
  165. package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
  166. package/dist/src/pil/cheap-model-workbooks.js +1 -1
  167. package/dist/src/pil/config.d.ts +20 -0
  168. package/dist/src/pil/config.js +35 -0
  169. package/dist/src/pil/discovery-types.d.ts +32 -9
  170. package/dist/src/pil/discovery.d.ts +3 -3
  171. package/dist/src/pil/discovery.js +138 -244
  172. package/dist/src/pil/index.d.ts +1 -1
  173. package/dist/src/pil/index.js +1 -1
  174. package/dist/src/pil/layer1-intent.d.ts +9 -1
  175. package/dist/src/pil/layer1-intent.js +99 -44
  176. package/dist/src/pil/layer1-intent.test.js +112 -2
  177. package/dist/src/pil/layer15-context-scan.js +1 -1
  178. package/dist/src/pil/layer16-clarity.d.ts +21 -25
  179. package/dist/src/pil/layer16-clarity.js +52 -88
  180. package/dist/src/pil/layer18-acceptance.d.ts +13 -3
  181. package/dist/src/pil/layer18-acceptance.js +1 -75
  182. package/dist/src/pil/layer2-personality.js +7 -8
  183. package/dist/src/pil/layer2_5-ponytail.d.ts +2 -0
  184. package/dist/src/pil/layer2_5-ponytail.js +41 -0
  185. package/dist/src/pil/layer3-ee-injection.js +180 -14
  186. package/dist/src/pil/layer4-gsd.js +22 -8
  187. package/dist/src/pil/layer5-context.d.ts +1 -0
  188. package/dist/src/pil/layer5-context.js +26 -11
  189. package/dist/src/pil/layer6-output.js +23 -5
  190. package/dist/src/pil/llm-classify.js +3 -3
  191. package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
  192. package/dist/src/pil/native-capabilities-workbook.js +4 -3
  193. package/dist/src/pil/pipeline.js +15 -1
  194. package/dist/src/pil/response-tools.d.ts +16 -0
  195. package/dist/src/pil/response-tools.js +41 -0
  196. package/dist/src/pil/schema.d.ts +8 -0
  197. package/dist/src/pil/schema.js +24 -3
  198. package/dist/src/pil/task-tier-map.js +7 -7
  199. package/dist/src/pil/types.d.ts +9 -3
  200. package/dist/src/product-loop/__tests__/discovery-interview.test.js +103 -1
  201. package/dist/src/product-loop/__tests__/gather-selectable-alts.test.js +34 -0
  202. package/dist/src/product-loop/__tests__/sprint-self-verify.test.js +12 -12
  203. package/dist/src/product-loop/discovery-interview.d.ts +20 -0
  204. package/dist/src/product-loop/discovery-interview.js +121 -7
  205. package/dist/src/product-loop/done-gate.js +3 -3
  206. package/dist/src/product-loop/gather.js +19 -5
  207. package/dist/src/product-loop/loop-driver.js +20 -20
  208. package/dist/src/product-loop/progress-snapshot.js +4 -4
  209. package/dist/src/product-loop/sprint-self-verify.js +2 -2
  210. package/dist/src/providers/__tests__/reasoning-roundtrip.test.js +12 -0
  211. package/dist/src/providers/auth/__tests__/gemini-oauth.test.d.ts +1 -5
  212. package/dist/src/providers/auth/__tests__/gemini-oauth.test.js +1 -5
  213. package/dist/src/providers/auth/gcloud.d.ts +28 -0
  214. package/dist/src/providers/auth/gcloud.js +102 -0
  215. package/dist/src/providers/auth/gemini-oauth.d.ts +46 -19
  216. package/dist/src/providers/auth/gemini-oauth.js +292 -105
  217. package/dist/src/providers/auth/grok-oauth.d.ts +1 -1
  218. package/dist/src/providers/auth/grok-oauth.js +1 -1
  219. package/dist/src/providers/auth/registry.js +28 -6
  220. package/dist/src/providers/gemini.d.ts +1 -1
  221. package/dist/src/providers/gemini.js +10 -3
  222. package/dist/src/providers/keychain.d.ts +2 -2
  223. package/dist/src/providers/keychain.js +20 -2
  224. package/dist/src/providers/mcp-vision-bridge.js +48 -48
  225. package/dist/src/providers/openai.js +5 -1
  226. package/dist/src/providers/runtime.d.ts +1 -1
  227. package/dist/src/providers/runtime.js +11 -2
  228. package/dist/src/providers/siliconflow-sse-repair.js +1 -1
  229. package/dist/src/providers/strategies/google.strategy.d.ts +9 -2
  230. package/dist/src/providers/strategies/google.strategy.js +159 -9
  231. package/dist/src/providers/strategies/thinking-mode.js +19 -6
  232. package/dist/src/providers/types.d.ts +1 -1
  233. package/dist/src/reporter/index.js +1 -1
  234. package/dist/src/router/decide.test.js +40 -8
  235. package/dist/src/router/step-router.d.ts +20 -0
  236. package/dist/src/router/step-router.js +73 -0
  237. package/dist/src/scaffold/bb-ecosystem-apply.js +47 -47
  238. package/dist/src/scaffold/bb-quality-gate.js +5 -5
  239. package/dist/src/scaffold/continuation-prompt.js +60 -60
  240. package/dist/src/scaffold/init-new.js +453 -453
  241. package/dist/src/self-qa/__tests__/scenario-planner.test.js +3 -3
  242. package/dist/src/self-qa/agentic-loop.js +19 -19
  243. package/dist/src/self-qa/orchestrator.js +5 -5
  244. package/dist/src/self-qa/scenario-planner.js +1 -1
  245. package/dist/src/self-qa/spec-emitter.js +4 -4
  246. package/dist/src/storage/__tests__/migrations.test.js +2 -2
  247. package/dist/src/storage/index.d.ts +1 -1
  248. package/dist/src/storage/index.js +1 -1
  249. package/dist/src/storage/interaction-log.js +5 -5
  250. package/dist/src/storage/migrations.js +122 -122
  251. package/dist/src/storage/session-experience-store.js +4 -4
  252. package/dist/src/storage/sessions.d.ts +6 -0
  253. package/dist/src/storage/sessions.js +49 -42
  254. package/dist/src/storage/transcript-view.js +7 -1
  255. package/dist/src/storage/transcript.d.ts +1 -0
  256. package/dist/src/storage/transcript.js +55 -0
  257. package/dist/src/storage/ui-interaction-log.d.ts +17 -0
  258. package/dist/src/storage/usage.js +14 -14
  259. package/dist/src/storage/workspaces.js +12 -12
  260. package/dist/src/tools/__tests__/native-tools.test.js +6 -0
  261. package/dist/src/tools/bash.d.ts +36 -23
  262. package/dist/src/tools/bash.js +59 -301
  263. package/dist/src/tools/bash.test.js +2 -257
  264. package/dist/src/tools/file-tracker.d.ts +5 -0
  265. package/dist/src/tools/file-tracker.js +14 -0
  266. package/dist/src/tools/file.js +2 -0
  267. package/dist/src/tools/git-safety.d.ts +38 -1
  268. package/dist/src/tools/git-safety.js +94 -4
  269. package/dist/src/tools/git-safety.test.js +45 -1
  270. package/dist/src/tools/native-tools.d.ts +1 -1
  271. package/dist/src/tools/native-tools.js +56 -0
  272. package/dist/src/tools/registry-bash-empty-command.test.js +4 -3
  273. package/dist/src/tools/registry-git-safety.test.js +17 -6
  274. package/dist/src/tools/registry.d.ts +5 -0
  275. package/dist/src/tools/registry.js +185 -11
  276. package/dist/src/types/index.d.ts +7 -1
  277. package/dist/src/ui/app.d.ts +2 -1
  278. package/dist/src/ui/app.js +0 -0
  279. package/dist/src/ui/components/council-question-card.js +1 -0
  280. package/dist/src/ui/components/message-view.js +37 -3
  281. package/dist/src/ui/components/structured-response-view.js +7 -0
  282. package/dist/src/ui/components/task-list-panel.js +3 -12
  283. package/dist/src/ui/containers/chat-feed.d.ts +40 -0
  284. package/dist/src/ui/containers/chat-feed.js +66 -0
  285. package/dist/src/ui/containers/modals-layer.d.ts +87 -0
  286. package/dist/src/ui/containers/modals-layer.js +18 -0
  287. package/dist/src/ui/modals/session-picker-modal.js +14 -9
  288. package/dist/src/ui/modals/update-modal.js +2 -1
  289. package/dist/src/ui/slash/__tests__/menu-parity.test.d.ts +1 -0
  290. package/dist/src/ui/slash/__tests__/menu-parity.test.js +1 -0
  291. package/dist/src/ui/slash/__tests__/ponytail.test.d.ts +1 -0
  292. package/dist/src/ui/slash/__tests__/ponytail.test.js +37 -0
  293. package/dist/src/ui/slash/council-inspect.js +4 -4
  294. package/dist/src/ui/slash/export.js +16 -1
  295. package/dist/src/ui/slash/menu-items.js +1 -0
  296. package/dist/src/ui/slash/ponytail.d.ts +2 -0
  297. package/dist/src/ui/slash/ponytail.js +19 -0
  298. package/dist/src/ui/status-bar/store.js +0 -1
  299. package/dist/src/ui/status-bar/store.test.js +2 -2
  300. package/dist/src/ui/types.d.ts +7 -0
  301. package/dist/src/ui/use-app-logic.d.ts +26 -0
  302. package/dist/src/ui/use-app-logic.js +0 -0
  303. package/dist/src/ui/utils/relaunch.d.ts +17 -0
  304. package/dist/src/ui/utils/relaunch.js +28 -4
  305. package/dist/src/ui/utils/relaunch.test.js +50 -0
  306. package/dist/src/ui/utils/text.d.ts +11 -0
  307. package/dist/src/ui/utils/text.js +21 -1
  308. package/dist/src/ui/utils/text.test.js +25 -1
  309. package/dist/src/ui/utils/tools.js +3 -1
  310. package/dist/src/usage/cost-log.js +2 -2
  311. package/dist/src/usage/estimator.d.ts +14 -0
  312. package/dist/src/usage/estimator.js +22 -0
  313. package/dist/src/utils/__tests__/compaction-caps.test.d.ts +21 -0
  314. package/dist/src/utils/__tests__/compaction-caps.test.js +187 -0
  315. package/dist/src/utils/__tests__/llm-deadline-abort.test.d.ts +1 -0
  316. package/dist/src/utils/__tests__/llm-deadline-abort.test.js +33 -0
  317. package/dist/src/utils/clipboard-image.js +23 -23
  318. package/dist/src/utils/llm-deadline.d.ts +9 -1
  319. package/dist/src/utils/llm-deadline.js +31 -2
  320. package/dist/src/utils/permission-mode.d.ts +33 -0
  321. package/dist/src/utils/permission-mode.js +164 -3
  322. package/dist/src/utils/permission-mode.test.js +92 -1
  323. package/dist/src/utils/settings.d.ts +27 -31
  324. package/dist/src/utils/settings.js +100 -109
  325. package/dist/src/utils/side-question.js +2 -2
  326. package/dist/src/utils/skills.js +3 -3
  327. package/dist/src/verify/__tests__/coverage-parsers.test.js +30 -30
  328. package/package.json +1 -1
@@ -4,7 +4,7 @@ import { mkdtemp, rm, stat, unlink } from "fs/promises";
4
4
  import os from "os";
5
5
  import path from "path";
6
6
  import { executeEventHooks } from "../hooks/index.js";
7
- import { appendAudit } from "../utils/permission-mode.js";
7
+ import { checkCatastrophicCommand } from "../utils/permission-mode.js";
8
8
  import { posixToNative, resolveShell } from "../utils/shell.js";
9
9
  import { nextBashRunId, recordBashRun, stripAnsi } from "./bash-output-cache.js";
10
10
  const MAX_TAIL_BYTES = 8_192;
@@ -14,17 +14,27 @@ export class BashTool {
14
14
  cwd;
15
15
  bgProcesses = new Map();
16
16
  tmpDir = null;
17
- sandboxMode;
18
- sandboxSettings;
19
17
  shellSettings;
20
18
  resolvedShell;
21
- sandboxUnsupportedWarned = false;
19
+ sandboxSettings = {};
22
20
  constructor(initialCwd = process.cwd(), options = {}) {
23
21
  this.cwd = initialCwd;
24
- this.sandboxMode = options.sandboxMode ?? "off";
25
- this.sandboxSettings = options.sandboxSettings ?? {};
26
22
  this.shellSettings = options.shellSettings ?? {};
27
23
  this.resolvedShell = resolveShell(this.shellSettings);
24
+ this.sandboxSettings = options.sandboxSettings ?? {};
25
+ }
26
+ /**
27
+ * Sandbox has been removed. Always returns "off".
28
+ * Kept for backward compatibility with orchestrator / stream-runner callers.
29
+ */
30
+ getSandboxMode() {
31
+ return "off";
32
+ }
33
+ getSandboxSettings() {
34
+ return this.sandboxSettings;
35
+ }
36
+ setSandboxSettings(settings) {
37
+ this.sandboxSettings = settings;
28
38
  }
29
39
  async ensureTmpDir() {
30
40
  if (!this.tmpDir) {
@@ -35,19 +45,6 @@ export class BashTool {
35
45
  async execute(command, timeout = 30_000, abortSignal) {
36
46
  try {
37
47
  if (command.startsWith("cd ")) {
38
- // Parse "cd <dir>" with optional "&&|||;" chained remainder.
39
- // Without this split, `cd D:\foo && gh run list` was passed wholesale
40
- // to stat() → ENOENT → agents retried with different shell syntaxes
41
- // and burned hundreds of tool calls (session 127140a47b56: 248 bash
42
- // calls all hit this bug).
43
- // Path token = quoted string or bare word stopping at first unquoted
44
- // shell separator. The regex deliberately excludes `&|;<>` from the
45
- // bare-word class so chained commands split correctly.
46
- // Strip cmd.exe-style DOS flags (e.g. `cd /d D:\path` — `/d` enables
47
- // drive-change, irrelevant in our cwd tracker). Without this, agents
48
- // using `cd /d <path>` (session 7dcf8fd7d6a4 caller still does this
49
- // habitually) parse as dir="/d" → resolve fails. Drop the flag and
50
- // continue parsing.
51
48
  const afterCd = command
52
49
  .substring(3)
53
50
  .trim()
@@ -57,18 +54,10 @@ export class BashTool {
57
54
  const rawDir = parsed?.[1] ?? afterCd;
58
55
  const chainOp = parsed?.[2];
59
56
  const remainder = parsed?.[3]?.trim() || null;
60
- const dir = rawDir
61
- .replace(/^["']|["']$/g, "")
62
- // Strip a trailing backslash that was an escaped quote in the shell
63
- // parse (e.g. cd "C:\foo\" && cmd → C:\foo). Only strip on Windows
64
- // because on POSIX a trailing backslash is a valid path continuation.
65
- .replace(process.platform === "win32" ? /\\$/ : /(?!x)x/, "");
57
+ const dir = rawDir.replace(/^["']|["']$/g, "").replace(process.platform === "win32" ? /\\$/ : /(?!x)x/, "");
66
58
  let cdSucceeded = false;
67
59
  let cdError = null;
68
60
  try {
69
- // Translate POSIX-style absolute paths (e.g. /d/sources/x, ~/foo)
70
- // when the active shell understands them, so cwd tracking matches
71
- // what the shell would actually resolve.
72
61
  const translated = this.resolvedShell.isPosix ? posixToNative(dir) : dir;
73
62
  const nextCwd = path.resolve(this.cwd, translated);
74
63
  const info = await stat(nextCwd);
@@ -92,14 +81,9 @@ export class BashTool {
92
81
  const msg = err instanceof Error ? err.message : String(err);
93
82
  cdError = { success: false, error: `Cannot change directory: ${msg}` };
94
83
  }
95
- // No chained command — return the cd outcome as before.
96
84
  if (!remainder) {
97
85
  return cdSucceeded ? { success: true, output: `Changed directory to: ${this.cwd}` } : cdError;
98
86
  }
99
- // Chained command — respect shell semantics:
100
- // `&&` runs remainder only if cd succeeded
101
- // `||` runs remainder only if cd failed
102
- // `;` runs remainder unconditionally
103
87
  const shouldRunRemainder = chainOp === ";" || (chainOp === "&&" && cdSucceeded) || (chainOp === "||" && !cdSucceeded);
104
88
  if (!shouldRunRemainder) {
105
89
  return cdSucceeded ? { success: true, output: `Changed directory to: ${this.cwd}` } : cdError;
@@ -133,18 +117,12 @@ export class BashTool {
133
117
  timeout,
134
118
  maxBuffer: 10 * 1024 * 1024,
135
119
  env: { ...process.env, FORCE_COLOR: "0" },
136
- // Route through the resolved shell so POSIX syntax
137
- // (`2>&1 &`, `$()`, single-quotes, `&&` as logical AND, …)
138
- // works on Windows instead of falling back to cmd.exe.
139
120
  ...(this.resolvedShell.binary ? { shell: this.resolvedShell.binary } : {}),
140
121
  }, (err, stdoutRaw, stderrRaw) => {
141
122
  if (aborted || abortSignal?.aborted) {
142
123
  finish({ success: false, error: "[Cancelled]" });
143
124
  return;
144
125
  }
145
- // ANSI strip — FORCE_COLOR=0 covers cooperating children, but
146
- // vitest / bun emit cursor-control sequences regardless, so we
147
- // strip post-process before the output ever leaves this module.
148
126
  const stdout = stripAnsi(stdoutRaw);
149
127
  const stderr = stripAnsi(stderrRaw);
150
128
  const exitCode = err && typeof err.code === "number" ? err.code : err ? 1 : 0;
@@ -159,11 +137,6 @@ export class BashTool {
159
137
  const totalChars = stdout.length + stderr.length;
160
138
  const output = stdout + (stderr ? `\nSTDERR: ${stderr}` : "");
161
139
  if (err) {
162
- const sandboxError = this.formatSandboxRuntimeError(output, err.message);
163
- if (sandboxError) {
164
- finish({ success: false, error: sandboxError, bashRunId: runId, bashTotalChars: totalChars });
165
- return;
166
- }
167
140
  if (output.trim()) {
168
141
  finish({ success: false, error: output.trim(), bashRunId: runId, bashTotalChars: totalChars });
169
142
  return;
@@ -309,10 +282,10 @@ export class BashTool {
309
282
  ].join("\n"),
310
283
  };
311
284
  }
312
- catch {
285
+ catch (err) {
313
286
  return {
314
- success: true,
315
- output: `[Process ${id} ${entry.alive ? "running" : "exited"}] (no output yet)`,
287
+ success: false,
288
+ output: `Failed to read logs for process ${id}: ${err instanceof Error ? err.message : String(err)}`,
316
289
  };
317
290
  }
318
291
  }
@@ -322,36 +295,18 @@ export class BashTool {
322
295
  return { success: false, output: `No background process with id ${id}.` };
323
296
  }
324
297
  if (!entry.alive) {
325
- return {
326
- success: true,
327
- output: `Process ${id} already exited (code ${entry.exitCode ?? "unknown"}).`,
328
- };
298
+ return { success: false, output: `Process ${id} is already stopped.` };
329
299
  }
330
300
  try {
331
301
  entry.child.kill("SIGTERM");
332
- await new Promise((resolve) => {
333
- const timeout = setTimeout(() => {
334
- try {
335
- entry.child.kill("SIGKILL");
336
- }
337
- catch {
338
- /* already dead */
339
- }
340
- resolve();
341
- }, 3_000);
342
- entry.child.on("exit", () => {
343
- clearTimeout(timeout);
344
- resolve();
345
- });
346
- });
347
- return {
348
- success: true,
349
- output: `Process ${id} (pid ${entry.pid}) stopped.`,
350
- };
302
+ entry.alive = false;
303
+ return { success: true, output: `Process ${id} terminated.` };
351
304
  }
352
305
  catch (err) {
353
- const msg = err instanceof Error ? err.message : String(err);
354
- return { success: false, output: `Failed to stop process ${id}: ${msg}` };
306
+ return {
307
+ success: false,
308
+ output: `Failed to stop process ${id}: ${err instanceof Error ? err.message : String(err)}`,
309
+ };
355
310
  }
356
311
  }
357
312
  listProcesses() {
@@ -408,52 +363,7 @@ export class BashTool {
408
363
  }
409
364
  this.cwd = next;
410
365
  }
411
- getSandboxMode() {
412
- return this.sandboxMode;
413
- }
414
- setSandboxMode(mode) {
415
- this.sandboxMode = mode;
416
- }
417
- /**
418
- * The sandbox mode that actually takes effect. When "shuru" is configured but
419
- * the current platform cannot run Shuru (non-macOS/arm64), the sandbox is
420
- * inert: every wrapped command would fail with the macOS-only error, and the
421
- * tool description would still advertise a `/workspace` mount that does not
422
- * exist — so the model `cd`s into a phantom dir and abandons verification.
423
- * Degrade to "off" (host execution) with a one-time warning so bash keeps
424
- * working and the description reflects reality. A stale checked-in project
425
- * `sandboxMode: "shuru"` therefore no longer bricks the shell off-platform.
426
- */
427
- effectiveSandboxMode() {
428
- if (this.sandboxMode === "shuru" && getSandboxUnsupportedReason() !== null) {
429
- if (!this.sandboxUnsupportedWarned) {
430
- this.sandboxUnsupportedWarned = true;
431
- console.error(`[bash] sandboxMode "shuru" is configured but ${getSandboxUnsupportedReason()} ` +
432
- 'Falling back to host execution for this platform. Set sandboxMode to "off" to silence this.');
433
- }
434
- return "off";
435
- }
436
- return this.sandboxMode;
437
- }
438
- getSandboxSettings() {
439
- return this.sandboxSettings;
440
- }
441
- setSandboxSettings(settings) {
442
- this.sandboxSettings = settings;
443
- }
444
366
  getToolDescription() {
445
- if (this.effectiveSandboxMode() === "shuru") {
446
- const s = this.sandboxSettings;
447
- const netStatus = s.allowNet
448
- ? s.allowedHosts?.length
449
- ? `network is restricted to: ${s.allowedHosts.join(", ")}`
450
- : "network access is enabled"
451
- : "network is disabled";
452
- const hostBrowserNote = s.hostBrowserCommandsOnHost
453
- ? " Commands that invoke agent-browser run on the host instead of inside Shuru so they can interact with forwarded localhost services."
454
- : "";
455
- return `Execute a bash command inside a Shuru sandbox. Use for find, ls, git inspection, build tools, test runners, and other shell commands that should stay isolated. For content search, prefer the dedicated grep tool. The current workspace is mounted inside the sandbox at /workspace, ${netStatus}, and shell-side workspace file changes do not persist back to the host in this version, so prefer the dedicated file tools for durable edits.${hostBrowserNote} Set background=true for long-running processes like dev servers or watchers.`;
456
- }
457
367
  return "Execute a bash command. Use for find, ls, git, build tools, package managers, running tests, and any other shell command. For content search, prefer the dedicated grep tool. Set background=true for long-running processes like dev servers, watchers, or anything that should keep running while you continue working. For file read/write/edit, prefer the dedicated file tools instead.";
458
368
  }
459
369
  getResolvedShell() {
@@ -474,48 +384,44 @@ export class BashTool {
474
384
  // cmd.exe or unknown
475
385
  return { binary: shell.binary, args: ["/d", "/s", "/c", command] };
476
386
  }
477
- // No explicit binary: rely on platform default the same way exec() does.
478
387
  if (process.platform === "win32") {
479
388
  return { binary: process.env.ComSpec || "cmd.exe", args: ["/d", "/s", "/c", command] };
480
389
  }
481
390
  return { binary: "/bin/sh", args: ["-c", command] };
482
391
  }
392
+ /**
393
+ * Pre-execution command preparation.
394
+ *
395
+ * Layer 1 — Catastrophic hard-block: checks the command against
396
+ * CATASTROPHIC_PATTERNS in permission-mode.ts. These are irreversible or
397
+ * severely dangerous operations that are blocked regardless of permission
398
+ * mode (safe / auto-edit / yolo) and regardless of sandbox state.
399
+ *
400
+ * Layer 2 — Future sandbox: when a new sandbox is wired in, wrap the
401
+ * command here before returning.
402
+ */
403
+ /**
404
+ * Pre-execution command preparation with two-layer safety.
405
+ *
406
+ * Layer 1 — Catastrophic hard-block: checks the command against
407
+ * CATASTROPHIC_PATTERNS in permission-mode.ts. Returns structured block
408
+ * result so callers can route to askcard approval flow.
409
+ *
410
+ * Layer 2 — Future sandbox: when a new sandbox is wired in, wrap the
411
+ * command here before returning.
412
+ */
483
413
  prepareCommand(command) {
484
- if (this.effectiveSandboxMode() !== "shuru") {
485
- return { ok: true, command };
486
- }
487
- if (shouldRunOnHostInSandboxMode(command, this.sandboxSettings)) {
488
- return { ok: true, command: wrapHostBrowserCommand(command) };
489
- }
490
- const unsupportedReason = getSandboxUnsupportedReason();
491
- if (unsupportedReason) {
492
- return { ok: false, error: unsupportedReason };
493
- }
494
- const blockedReason = getSandboxMutationBlockReason(command, this.sandboxSettings);
495
- if (blockedReason) {
496
- return { ok: false, error: blockedReason };
497
- }
498
- const wrapped = wrapCommandForShuru(this.cwd, command, this.sandboxSettings);
499
- appendAudit({
500
- kind: "permission-override",
501
- tool: "bash",
502
- mode: "safe",
503
- context: { command, shuru: true, settings: this.sandboxSettings },
504
- ts: Date.now(),
505
- });
506
- return { ok: true, command: wrapped };
507
- }
508
- formatSandboxRuntimeError(output, fallbackMessage) {
509
- if (this.effectiveSandboxMode() !== "shuru") {
510
- return null;
511
- }
512
- if (output.includes("shuru: command not found") || output.includes("sh: shuru: not found")) {
513
- return "Shuru sandbox mode is enabled, but the `shuru` CLI is not installed or not on PATH. Install Shuru or disable sandbox mode.";
514
- }
515
- if (output.includes("Apple Silicon") || fallbackMessage.includes("Apple Silicon")) {
516
- return "Shuru sandbox mode requires macOS on Apple Silicon.";
414
+ const catBlock = checkCatastrophicCommand(command);
415
+ if (catBlock) {
416
+ return {
417
+ ok: false,
418
+ error: `BLOCKED (${catBlock.kind}): ${catBlock.reason}\n` +
419
+ "This command is blocked by the safety filter. " +
420
+ "Use the safety_override tool or ask the user for approval.",
421
+ block: catBlock,
422
+ };
517
423
  }
518
- return null;
424
+ return { ok: true, command };
519
425
  }
520
426
  }
521
427
  function truncCmd(cmd, max) {
@@ -532,155 +438,7 @@ function formatAge(start) {
532
438
  const hr = Math.floor(min / 60);
533
439
  return `${hr}h${min % 60}m`;
534
440
  }
535
- export function wrapCommandForShuru(cwd, command, settings = {}) {
536
- const parts = ["shuru", "run"];
537
- if (settings.cpus)
538
- parts.push("--cpus", String(settings.cpus));
539
- if (settings.memory)
540
- parts.push("--memory", String(settings.memory));
541
- if (settings.diskSize)
542
- parts.push("--disk-size", String(settings.diskSize));
543
- if (settings.allowNet)
544
- parts.push("--allow-net");
545
- if (settings.allowedHosts) {
546
- for (const host of settings.allowedHosts)
547
- parts.push("--allow-host", host);
548
- }
549
- if (settings.ports) {
550
- for (const port of settings.ports)
551
- parts.push("-p", port);
552
- }
553
- if (settings.secrets) {
554
- for (const s of settings.secrets) {
555
- parts.push("--secret", `${s.name}=${s.fromEnv}@${s.hosts.join(",")}`);
556
- }
557
- }
558
- if (settings.from)
559
- parts.push("--from", settings.from);
560
- const mountArg = `${cwd}:/workspace`;
561
- parts.push("--mount", shellQuote(mountArg));
562
- const shellInit = buildShellInitScript(settings);
563
- const guestPrelude = buildGuestWorkspacePrelude(settings);
564
- const guestSteps = [
565
- shellInit,
566
- guestPrelude,
567
- `cd ${shellPathForScript(settings.guestWorkdir || "/workspace")}`,
568
- command,
569
- ].filter(Boolean);
570
- const guestCommand = guestSteps.join(" && ");
571
- parts.push("--", "sh", "-lc", shellQuote(guestCommand));
572
- return parts.join(" ");
573
- }
574
- const HOST_SAFE_SEGMENT_RE = /^\s*(?:(?:npx(?:\s+-y)?|bunx)\s+)?agent-browser\b|^\s*mkdir\s|^\s*sleep\s|^\s*echo\s|^\s*true\s*$|^\s*$/;
575
- export function shouldRunOnHostInSandboxMode(command, settings = {}) {
576
- if (!settings.hostBrowserCommandsOnHost) {
577
- return false;
578
- }
579
- if (!/\bagent-browser\b/.test(command)) {
580
- return false;
581
- }
582
- if (/\$\(|`/.test(command)) {
583
- return false;
584
- }
585
- const segments = command.split(/\s*(?:&&|\|\||;|\|[^|]|>>?)\s*/);
586
- return segments.every((segment) => HOST_SAFE_SEGMENT_RE.test(segment));
587
- }
588
441
  export function wrapHostBrowserCommand(command) {
589
- const normalized = command
590
- .replace(/\bbunx\s+agent-browser\b/g, "__muonroi_ab")
591
- .replace(/\bnpx(?:\s+-y)?\s+agent-browser\b/g, "__muonroi_ab")
592
- .replace(/\bagent-browser\b/g, "__muonroi_ab");
593
- return [
594
- "__muonroi_ab() {",
595
- " if command -v agent-browser >/dev/null 2>&1; then",
596
- ' command agent-browser "$@"',
597
- " elif command -v bunx >/dev/null 2>&1; then",
598
- ' bunx agent-browser "$@"',
599
- " elif command -v npx >/dev/null 2>&1; then",
600
- ' npx -y agent-browser "$@"',
601
- " else",
602
- ' echo "agent-browser: not found (no bunx/npx fallback)" >&2',
603
- " return 127",
604
- " fi",
605
- "}",
606
- normalized,
607
- ].join("\n");
608
- }
609
- function shellQuote(value) {
610
- return `'${value.replace(/'/g, `'\\''`)}'`;
611
- }
612
- function shellPathForScript(value) {
613
- return /^[A-Za-z0-9_./-]+$/.test(value) ? value : shellQuote(value);
614
- }
615
- function buildGuestWorkspacePrelude(settings) {
616
- if (!settings.guestWorkdir || !settings.syncHostWorkspace) {
617
- return "";
618
- }
619
- const guest = shellQuote(settings.guestWorkdir);
620
- const exclusions = [
621
- ".git",
622
- "node_modules",
623
- ".venv",
624
- ".next",
625
- "dist",
626
- "build",
627
- "target",
628
- ".pytest_cache",
629
- ".mypy_cache",
630
- ];
631
- const tarExcludes = exclusions.map((entry) => `--exclude=${shellQuote(entry)}`).join(" ");
632
- return [
633
- `mkdir -p ${guest}`,
634
- `find ${guest} -mindepth 1 -maxdepth 1 ${exclusions
635
- .map((entry) => `! -name ${shellQuote(entry)}`)
636
- .join(" ")} -exec rm -rf {} + 2>/dev/null || true`,
637
- `tar -C /workspace ${tarExcludes} -cf - . | tar -C ${guest} -xf -`,
638
- ].join(" && ");
639
- }
640
- function buildShellInitScript(settings) {
641
- return (settings.shellInit ?? []).filter(Boolean).join(" && ");
642
- }
643
- function getSandboxUnsupportedReason() {
644
- if (process.platform !== "darwin" || process.arch !== "arm64") {
645
- return "Shuru sandbox mode currently requires macOS on Apple Silicon.";
646
- }
647
- return null;
648
- }
649
- export function getSandboxMutationBlockReason(command, settings = {}) {
650
- const trimmed = command.trim();
651
- if (!trimmed)
652
- return null;
653
- if (/\bgit\s+/.test(trimmed) && !/\bgit\s+(status|diff|log|show|rev-parse|grep|ls-files)\b/.test(trimmed)) {
654
- return [
655
- "Sandbox mode blocks git commands that mutate repository state because Shuru guest-side workspace changes do not persist back to the host.",
656
- "Disable sandbox mode to run persistent git mutations on the real workspace.",
657
- ].join(" ");
658
- }
659
- const blockedPatterns = [
660
- {
661
- pattern: /\b(?:prettier\s+--write|eslint\b.*--fix|biome\s+check\b.*--write|ruff\s+check\b.*--fix|gofmt\s+-w|rustfmt\b|clang-format\b.*-i)\b/,
662
- reason: "Shell-driven formatters that rewrite files are blocked in sandbox mode because those file changes would not persist back to the host workspace.",
663
- },
664
- ];
665
- if (!settings.allowEphemeralInstall) {
666
- const installPatterns = [
667
- {
668
- pattern: /\b(?:npm|pnpm|yarn|bun)\s+(?:add|install|remove|unlink|update|upgrade)\b/,
669
- reason: "Package-manager installs are blocked in sandbox mode because workspace changes like lockfile updates would stay inside the Shuru guest overlay.",
670
- },
671
- {
672
- pattern: /\b(?:pip|pip3)\s+install\b|\bpoetry\s+add\b|\buv\s+add\b|\bcargo\s+add\b/,
673
- reason: "Dependency install commands are blocked in sandbox mode because resulting workspace changes would not persist back to the host.",
674
- },
675
- ];
676
- const installMatch = installPatterns.find(({ pattern }) => pattern.test(trimmed));
677
- if (installMatch) {
678
- return `${installMatch.reason} Use read_file/edit_file/write_file for durable edits, or disable sandbox mode for host-persistent shell changes.`;
679
- }
680
- }
681
- const matched = blockedPatterns.find(({ pattern }) => pattern.test(trimmed));
682
- if (!matched)
683
- return null;
684
- return `${matched.reason} Use read_file/edit_file/write_file for durable edits, or disable sandbox mode for host-persistent shell changes.`;
442
+ return command;
685
443
  }
686
444
  //# sourceMappingURL=bash.js.map