muonroi-cli 1.7.2 → 1.8.1

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 (326) hide show
  1. package/dist/packages/agent-harness-core/src/event-filter.js +1 -0
  2. package/dist/packages/agent-harness-core/src/event-redact.js +8 -0
  3. package/dist/packages/agent-harness-core/src/mcp-server.d.ts +16 -0
  4. package/dist/packages/agent-harness-core/src/mcp-server.js +88 -10
  5. package/dist/packages/agent-harness-core/src/protocol.d.ts +15 -0
  6. package/dist/packages/agent-harness-core/src/selector.js +2 -2
  7. package/dist/packages/agent-harness-core/src/transports/sidechannel.js +1 -1
  8. package/dist/packages/agent-harness-opentui/src/agent-mode.js +1 -1
  9. package/dist/packages/agent-harness-opentui/src/install.js +1 -1
  10. package/dist/src/__test-stubs__/vitest-setup.js +4 -0
  11. package/dist/src/agent-harness/__tests__/mock-model.spec.js +54 -0
  12. package/dist/src/agent-harness/mock-model.d.ts +1 -1
  13. package/dist/src/agent-harness/mock-model.js +143 -6
  14. package/dist/src/agent-harness/test-spawn.js +3 -1
  15. package/dist/src/chat/chat-keychain.js +9 -1
  16. package/dist/src/cli/cost-forensics.js +7 -4
  17. package/dist/src/cli/keys.d.ts +2 -2
  18. package/dist/src/cli/keys.js +107 -28
  19. package/dist/src/cli/usage-report.js +2 -2
  20. package/dist/src/council/__tests__/abort-threading.test.d.ts +1 -0
  21. package/dist/src/council/__tests__/abort-threading.test.js +193 -0
  22. package/dist/src/council/__tests__/clarification-prompt.test.js +21 -1
  23. package/dist/src/council/__tests__/council-turn-length-emit.test.d.ts +1 -0
  24. package/dist/src/council/__tests__/council-turn-length-emit.test.js +130 -0
  25. package/dist/src/council/__tests__/debate-round-budget.test.d.ts +1 -0
  26. package/dist/src/council/__tests__/debate-round-budget.test.js +45 -0
  27. package/dist/src/council/__tests__/evaluator-metrics.test.js +3 -0
  28. package/dist/src/council/__tests__/post-debate-recommendation.test.d.ts +1 -0
  29. package/dist/src/council/__tests__/post-debate-recommendation.test.js +58 -0
  30. package/dist/src/council/__tests__/research-tools.test.js +4 -0
  31. package/dist/src/council/__tests__/round-tools.test.js +6 -0
  32. package/dist/src/council/clarifier-question-cap.test.d.ts +12 -0
  33. package/dist/src/council/clarifier-question-cap.test.js +73 -0
  34. package/dist/src/council/clarifier.d.ts +29 -1
  35. package/dist/src/council/clarifier.js +64 -1
  36. package/dist/src/council/debate-planner.d.ts +2 -1
  37. package/dist/src/council/debate-planner.js +5 -3
  38. package/dist/src/council/debate.d.ts +9 -0
  39. package/dist/src/council/debate.js +88 -8
  40. package/dist/src/council/index.d.ts +46 -1
  41. package/dist/src/council/index.js +125 -19
  42. package/dist/src/council/llm.js +41 -13
  43. package/dist/src/council/prompts.js +36 -1
  44. package/dist/src/council/types.d.ts +14 -1
  45. package/dist/src/ee/__tests__/export-transcripts.test.js +1 -1
  46. package/dist/src/ee/bridge.d.ts +3 -1
  47. package/dist/src/ee/bridge.js +9 -3
  48. package/dist/src/ee/bridge.test.js +1 -2
  49. package/dist/src/ee/client.js +15 -2
  50. package/dist/src/ee/recall-ledger.d.ts +20 -0
  51. package/dist/src/ee/recall-ledger.js +40 -1
  52. package/dist/src/ee/search.d.ts +25 -0
  53. package/dist/src/ee/search.js +73 -0
  54. package/dist/src/ee/types.d.ts +15 -1
  55. package/dist/src/ee/who-am-i.d.ts +41 -0
  56. package/dist/src/ee/who-am-i.js +143 -0
  57. package/dist/src/ee/who-am-i.test.d.ts +1 -0
  58. package/dist/src/ee/who-am-i.test.js +101 -0
  59. package/dist/src/flow/compaction/index.d.ts +1 -0
  60. package/dist/src/flow/compaction/index.js +4 -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 +59 -62
  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 +13 -5
  76. package/dist/src/lsp/manager.test.js +41 -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/auto-setup.js +0 -8
  86. package/dist/src/mcp/client-pool.d.ts +1 -1
  87. package/dist/src/mcp/client-pool.js +25 -9
  88. package/dist/src/mcp/ee-tools.d.ts +10 -0
  89. package/dist/src/mcp/ee-tools.js +34 -0
  90. package/dist/src/mcp/self-verify-jobs.d.ts +1 -0
  91. package/dist/src/mcp/self-verify-jobs.js +1 -3
  92. package/dist/src/mcp/self-verify-runner.js +1 -1
  93. package/dist/src/models/catalog-gemini.test.js +12 -10
  94. package/dist/src/models/catalog.json +106 -38
  95. package/dist/src/orchestrator/__tests__/auto-commit.test.d.ts +1 -0
  96. package/dist/src/orchestrator/__tests__/auto-commit.test.js +142 -0
  97. package/dist/src/orchestrator/__tests__/batch-turn-runner.test.js +3 -0
  98. package/dist/src/orchestrator/__tests__/delegations.test.d.ts +1 -0
  99. package/dist/src/orchestrator/__tests__/delegations.test.js +96 -0
  100. package/dist/src/orchestrator/__tests__/message-processor.test.js +2 -0
  101. package/dist/src/orchestrator/__tests__/prompts.test.d.ts +1 -0
  102. package/dist/src/orchestrator/__tests__/prompts.test.js +88 -0
  103. package/dist/src/orchestrator/__tests__/stream-runner.test.js +29 -0
  104. package/dist/src/orchestrator/__tests__/sub-session-delegation.test.d.ts +1 -0
  105. package/dist/src/orchestrator/__tests__/sub-session-delegation.test.js +507 -0
  106. package/dist/src/orchestrator/agent.test.js +5 -2
  107. package/dist/src/orchestrator/auto-commit.d.ts +110 -0
  108. package/dist/src/orchestrator/auto-commit.js +366 -0
  109. package/dist/src/orchestrator/batch-turn-runner.d.ts +3 -2
  110. package/dist/src/orchestrator/batch-turn-runner.js +2 -1
  111. package/dist/src/orchestrator/compaction-proposer-prompt.d.ts +8 -0
  112. package/dist/src/orchestrator/compaction-proposer-prompt.js +43 -0
  113. package/dist/src/orchestrator/compaction.d.ts +22 -0
  114. package/dist/src/orchestrator/compaction.js +42 -0
  115. package/dist/src/orchestrator/delegations.d.ts +1 -0
  116. package/dist/src/orchestrator/delegations.js +41 -0
  117. package/dist/src/orchestrator/error-utils.js +27 -2
  118. package/dist/src/orchestrator/message-processor.d.ts +23 -5
  119. package/dist/src/orchestrator/message-processor.js +226 -2573
  120. package/dist/src/orchestrator/orchestrator.d.ts +11 -2
  121. package/dist/src/orchestrator/orchestrator.js +530 -43
  122. package/dist/src/orchestrator/pending-calls.js +2 -1
  123. package/dist/src/orchestrator/preprocessor.d.ts +13 -0
  124. package/dist/src/orchestrator/preprocessor.js +146 -0
  125. package/dist/src/orchestrator/prompts.d.ts +16 -0
  126. package/dist/src/orchestrator/prompts.js +130 -67
  127. package/dist/src/orchestrator/safety-askcard.d.ts +66 -0
  128. package/dist/src/orchestrator/safety-askcard.js +74 -0
  129. package/dist/src/orchestrator/sandbox.test.js +8 -5
  130. package/dist/src/orchestrator/scope-ceiling.js +1 -1
  131. package/dist/src/orchestrator/stall-watchdog.d.ts +47 -0
  132. package/dist/src/orchestrator/stall-watchdog.js +23 -0
  133. package/dist/src/orchestrator/stall-watchdog.test.js +36 -1
  134. package/dist/src/orchestrator/stream-runner.js +52 -30
  135. package/dist/src/orchestrator/sub-agent-cap.d.ts +2 -0
  136. package/dist/src/orchestrator/sub-agent-cap.js +12 -4
  137. package/dist/src/orchestrator/sub-agent-model-tier.js +1 -1
  138. package/dist/src/orchestrator/subagent-compactor.d.ts +21 -0
  139. package/dist/src/orchestrator/subagent-compactor.js +148 -24
  140. package/dist/src/orchestrator/subagent-compactor.spec.js +249 -1
  141. package/dist/src/orchestrator/text-tool-call-detector.d.ts +9 -5
  142. package/dist/src/orchestrator/text-tool-call-detector.js +16 -12
  143. package/dist/src/orchestrator/text-tool-call-detector.test.js +66 -8
  144. package/dist/src/orchestrator/tool-args-repair.js +1 -1
  145. package/dist/src/orchestrator/tool-engine.d.ts +153 -0
  146. package/dist/src/orchestrator/tool-engine.js +3138 -0
  147. package/dist/src/orchestrator/turn-runner-deps.d.ts +3 -0
  148. package/dist/src/pil/__tests__/discovery-types.test.js +15 -4
  149. package/dist/src/pil/__tests__/discovery.test.js +89 -145
  150. package/dist/src/pil/__tests__/dual-run.test.js +2 -0
  151. package/dist/src/pil/__tests__/layer1-intent-trace.test.js +3 -0
  152. package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
  153. package/dist/src/pil/__tests__/layer16-clarity.test.js +35 -32
  154. package/dist/src/pil/__tests__/layer18-acceptance.test.js +1 -78
  155. package/dist/src/pil/__tests__/layer2_5-ponytail.test.d.ts +1 -0
  156. package/dist/src/pil/__tests__/layer2_5-ponytail.test.js +53 -0
  157. package/dist/src/pil/__tests__/layer3-ee-injection.test.js +67 -4
  158. package/dist/src/pil/__tests__/layer3-injected-chunk.test.js +14 -2
  159. package/dist/src/pil/__tests__/layer4-gsd.test.js +30 -0
  160. package/dist/src/pil/__tests__/layer5-context.test.js +39 -1
  161. package/dist/src/pil/__tests__/layer6-output.test.js +30 -2
  162. package/dist/src/pil/__tests__/llm-classify.test.js +79 -1
  163. package/dist/src/pil/__tests__/orchestrator-integration.test.js +3 -1
  164. package/dist/src/pil/__tests__/pipeline.test.js +11 -8
  165. package/dist/src/pil/__tests__/response-tools.test.js +52 -0
  166. package/dist/src/pil/agent-operating-contract.d.ts +1 -1
  167. package/dist/src/pil/agent-operating-contract.js +5 -3
  168. package/dist/src/pil/agent-operating-contract.test.js +2 -2
  169. package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
  170. package/dist/src/pil/cheap-model-playbook.js +1 -1
  171. package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
  172. package/dist/src/pil/cheap-model-workbooks.js +1 -1
  173. package/dist/src/pil/config.d.ts +20 -0
  174. package/dist/src/pil/config.js +35 -0
  175. package/dist/src/pil/discovery-types.d.ts +32 -9
  176. package/dist/src/pil/discovery.d.ts +3 -3
  177. package/dist/src/pil/discovery.js +138 -244
  178. package/dist/src/pil/index.d.ts +1 -1
  179. package/dist/src/pil/index.js +1 -1
  180. package/dist/src/pil/layer1-intent.d.ts +9 -1
  181. package/dist/src/pil/layer1-intent.js +62 -7
  182. package/dist/src/pil/layer1-intent.test.js +112 -2
  183. package/dist/src/pil/layer15-context-scan.js +1 -1
  184. package/dist/src/pil/layer16-clarity.d.ts +21 -25
  185. package/dist/src/pil/layer16-clarity.js +52 -88
  186. package/dist/src/pil/layer18-acceptance.d.ts +13 -3
  187. package/dist/src/pil/layer18-acceptance.js +1 -75
  188. package/dist/src/pil/layer2-personality.js +7 -8
  189. package/dist/src/pil/layer2_5-ponytail.d.ts +2 -0
  190. package/dist/src/pil/layer2_5-ponytail.js +41 -0
  191. package/dist/src/pil/layer3-ee-injection.js +180 -14
  192. package/dist/src/pil/layer4-gsd.js +22 -8
  193. package/dist/src/pil/layer5-context.d.ts +1 -0
  194. package/dist/src/pil/layer5-context.js +26 -11
  195. package/dist/src/pil/layer6-output.js +23 -5
  196. package/dist/src/pil/llm-classify.d.ts +11 -0
  197. package/dist/src/pil/llm-classify.js +196 -3
  198. package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
  199. package/dist/src/pil/native-capabilities-workbook.js +4 -3
  200. package/dist/src/pil/pipeline.js +15 -1
  201. package/dist/src/pil/response-tools.d.ts +16 -0
  202. package/dist/src/pil/response-tools.js +41 -0
  203. package/dist/src/pil/schema.d.ts +8 -0
  204. package/dist/src/pil/schema.js +24 -3
  205. package/dist/src/pil/task-tier-map.js +7 -7
  206. package/dist/src/pil/types.d.ts +9 -3
  207. package/dist/src/product-loop/__tests__/discovery-interview.test.js +103 -1
  208. package/dist/src/product-loop/__tests__/gather-selectable-alts.test.js +34 -0
  209. package/dist/src/product-loop/__tests__/sprint-self-verify.test.js +12 -12
  210. package/dist/src/product-loop/discovery-interview.d.ts +20 -0
  211. package/dist/src/product-loop/discovery-interview.js +121 -7
  212. package/dist/src/product-loop/gather.js +19 -5
  213. package/dist/src/product-loop/loop-driver.js +2 -2
  214. package/dist/src/product-loop/sprint-self-verify.js +2 -2
  215. package/dist/src/providers/__tests__/reasoning-roundtrip.test.js +12 -0
  216. package/dist/src/providers/auth/__tests__/gemini-oauth.test.d.ts +1 -5
  217. package/dist/src/providers/auth/__tests__/gemini-oauth.test.js +1 -5
  218. package/dist/src/providers/auth/gcloud.d.ts +28 -0
  219. package/dist/src/providers/auth/gcloud.js +102 -0
  220. package/dist/src/providers/auth/gemini-oauth.d.ts +46 -19
  221. package/dist/src/providers/auth/gemini-oauth.js +292 -105
  222. package/dist/src/providers/auth/grok-oauth.d.ts +1 -1
  223. package/dist/src/providers/auth/grok-oauth.js +1 -1
  224. package/dist/src/providers/auth/registry.js +28 -6
  225. package/dist/src/providers/gemini.d.ts +1 -1
  226. package/dist/src/providers/gemini.js +10 -3
  227. package/dist/src/providers/keychain.d.ts +2 -2
  228. package/dist/src/providers/keychain.js +20 -2
  229. package/dist/src/providers/openai.js +5 -1
  230. package/dist/src/providers/runtime.d.ts +1 -1
  231. package/dist/src/providers/runtime.js +11 -2
  232. package/dist/src/providers/siliconflow-sse-repair.js +1 -1
  233. package/dist/src/providers/strategies/google.strategy.d.ts +9 -2
  234. package/dist/src/providers/strategies/google.strategy.js +159 -9
  235. package/dist/src/providers/strategies/thinking-mode.js +19 -6
  236. package/dist/src/providers/types.d.ts +1 -1
  237. package/dist/src/router/decide.test.js +40 -8
  238. package/dist/src/router/step-router.d.ts +20 -0
  239. package/dist/src/router/step-router.js +73 -0
  240. package/dist/src/self-qa/agentic-loop.js +30 -21
  241. package/dist/src/self-qa/orchestrator.js +5 -5
  242. package/dist/src/self-qa/scenario-planner.js +1 -1
  243. package/dist/src/self-qa/spec-emitter.js +4 -4
  244. package/dist/src/storage/__tests__/hierarchical-session.test.d.ts +1 -0
  245. package/dist/src/storage/__tests__/hierarchical-session.test.js +161 -0
  246. package/dist/src/storage/__tests__/sweep-stale-pending.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/migrations.js +13 -1
  250. package/dist/src/storage/transcript-response-entry.test.js +18 -5
  251. package/dist/src/storage/transcript-view.js +7 -1
  252. package/dist/src/storage/transcript.d.ts +3 -0
  253. package/dist/src/storage/transcript.js +254 -73
  254. package/dist/src/storage/ui-interaction-log.d.ts +17 -0
  255. package/dist/src/storage/usage.js +22 -5
  256. package/dist/src/tools/__tests__/native-tools.test.js +6 -0
  257. package/dist/src/tools/bash.d.ts +36 -23
  258. package/dist/src/tools/bash.js +59 -301
  259. package/dist/src/tools/bash.test.js +2 -257
  260. package/dist/src/tools/file-tracker.d.ts +5 -0
  261. package/dist/src/tools/file-tracker.js +14 -0
  262. package/dist/src/tools/file.js +2 -0
  263. package/dist/src/tools/git-safety.d.ts +38 -1
  264. package/dist/src/tools/git-safety.js +94 -4
  265. package/dist/src/tools/git-safety.test.js +45 -1
  266. package/dist/src/tools/native-tools.d.ts +1 -1
  267. package/dist/src/tools/native-tools.js +66 -1
  268. package/dist/src/tools/registry-bash-empty-command.test.js +4 -3
  269. package/dist/src/tools/registry-git-safety.test.js +17 -6
  270. package/dist/src/tools/registry.d.ts +6 -0
  271. package/dist/src/tools/registry.js +215 -13
  272. package/dist/src/types/index.d.ts +8 -2
  273. package/dist/src/ui/app.d.ts +1 -0
  274. package/dist/src/ui/app.js +0 -0
  275. package/dist/src/ui/components/council-question-card.js +1 -0
  276. package/dist/src/ui/components/message-view.js +37 -3
  277. package/dist/src/ui/components/structured-response-view.js +7 -0
  278. package/dist/src/ui/components/task-list-panel.js +3 -12
  279. package/dist/src/ui/containers/chat-feed.d.ts +40 -0
  280. package/dist/src/ui/containers/chat-feed.js +66 -0
  281. package/dist/src/ui/containers/modals-layer.d.ts +87 -0
  282. package/dist/src/ui/containers/modals-layer.js +18 -0
  283. package/dist/src/ui/slash/__tests__/menu-parity.test.d.ts +1 -0
  284. package/dist/src/ui/slash/__tests__/menu-parity.test.js +1 -0
  285. package/dist/src/ui/slash/__tests__/ponytail.test.d.ts +1 -0
  286. package/dist/src/ui/slash/__tests__/ponytail.test.js +37 -0
  287. package/dist/src/ui/slash/expand.js +14 -1
  288. package/dist/src/ui/slash/export.js +16 -1
  289. package/dist/src/ui/slash/menu-items.js +1 -0
  290. package/dist/src/ui/slash/ponytail.d.ts +2 -0
  291. package/dist/src/ui/slash/ponytail.js +19 -0
  292. package/dist/src/ui/status-bar/store.js +0 -1
  293. package/dist/src/ui/status-bar/store.test.js +2 -2
  294. package/dist/src/ui/use-app-logic.d.ts +26 -0
  295. package/dist/src/ui/use-app-logic.js +0 -0
  296. package/dist/src/ui/utils/relaunch.js +1 -1
  297. package/dist/src/ui/utils/text.d.ts +11 -0
  298. package/dist/src/ui/utils/text.js +21 -1
  299. package/dist/src/ui/utils/text.test.js +25 -1
  300. package/dist/src/ui/utils/tools.js +3 -1
  301. package/dist/src/usage/cost-log.js +2 -2
  302. package/dist/src/usage/estimator.d.ts +14 -0
  303. package/dist/src/usage/estimator.js +22 -0
  304. package/dist/src/utils/__tests__/compaction-caps.test.d.ts +21 -0
  305. package/dist/src/utils/__tests__/compaction-caps.test.js +187 -0
  306. package/dist/src/utils/__tests__/llm-deadline-abort.test.d.ts +1 -0
  307. package/dist/src/utils/__tests__/llm-deadline-abort.test.js +33 -0
  308. package/dist/src/utils/__tests__/llm-deadline.test.js +16 -4
  309. package/dist/src/utils/__tests__/logger.test.d.ts +1 -0
  310. package/dist/src/utils/__tests__/logger.test.js +115 -0
  311. package/dist/src/utils/__tests__/settings-agent-first.test.d.ts +1 -0
  312. package/dist/src/utils/__tests__/settings-agent-first.test.js +48 -0
  313. package/dist/src/utils/install-manager.d.ts +1 -0
  314. package/dist/src/utils/install-manager.js +76 -7
  315. package/dist/src/utils/install-manager.test.js +43 -1
  316. package/dist/src/utils/llm-deadline.d.ts +9 -1
  317. package/dist/src/utils/llm-deadline.js +31 -2
  318. package/dist/src/utils/logger.d.ts +28 -0
  319. package/dist/src/utils/logger.js +171 -0
  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 +36 -32
  324. package/dist/src/utils/settings.js +151 -123
  325. package/dist/src/utils/update-checker.test.js +5 -1
  326. package/package.json +1 -1
@@ -169,5 +169,34 @@ describe("StreamRunner — DI surface", () => {
169
169
  const b = new StreamRunner(makeDeps());
170
170
  expect(a).not.toBe(b);
171
171
  });
172
+ describe("StreamRunner — maxSteps custom overrides", () => {
173
+ it("respects default maxSteps logic when request.maxToolRounds is undefined", async () => {
174
+ const deps = makeDeps({ getMaxToolRounds: () => 50 });
175
+ const runner = new StreamRunner(deps);
176
+ const outcome = await runner.setup({
177
+ agent: "explore",
178
+ description: "test",
179
+ prompt: "test",
180
+ });
181
+ expect(outcome.kind).toBe("prepared");
182
+ if (outcome.kind === "prepared") {
183
+ expect(outcome.prepared.maxSteps).toBe(100); // deps.getMaxToolRounds() * 2
184
+ }
185
+ });
186
+ it("uses request.maxToolRounds when it is explicitly defined", async () => {
187
+ const deps = makeDeps({ getMaxToolRounds: () => 50 });
188
+ const runner = new StreamRunner(deps);
189
+ const outcome = await runner.setup({
190
+ agent: "explore",
191
+ description: "test",
192
+ prompt: "test",
193
+ maxToolRounds: 75,
194
+ });
195
+ expect(outcome.kind).toBe("prepared");
196
+ if (outcome.kind === "prepared") {
197
+ expect(outcome.prepared.maxSteps).toBe(75);
198
+ }
199
+ });
200
+ });
172
201
  });
173
202
  //# sourceMappingURL=stream-runner.test.js.map
@@ -0,0 +1,507 @@
1
+ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { installMockModel, textOnlyStream } from "../../agent-harness/mock-model.js";
3
+ import { loadCatalog } from "../../models/registry.js";
4
+ import { Agent } from "../orchestrator.js";
5
+ // Mock generateText from 'ai'
6
+ const mockGenerateText = vi.fn().mockResolvedValue({ text: "Mocked parent advice response" });
7
+ vi.mock("ai", () => {
8
+ return {
9
+ generateText: (...args) => mockGenerateText(...args),
10
+ };
11
+ });
12
+ // 1. Mock classifySubSessionAction
13
+ const mockClassifySubSessionAction = vi.fn();
14
+ vi.mock("../../pil/llm-classify.js", () => {
15
+ return {
16
+ classifySubSessionAction: (...args) => mockClassifySubSessionAction.apply(null, args),
17
+ };
18
+ });
19
+ // Mock deliberateCompact
20
+ const mockDeliberateCompact = vi.fn().mockResolvedValue({
21
+ summary: "Compact summary",
22
+ tokensBeforeCompress: 100,
23
+ });
24
+ vi.mock("../../flow/compaction/index.js", () => {
25
+ return {
26
+ deliberateCompact: (...args) => mockDeliberateCompact.apply(null, args),
27
+ };
28
+ });
29
+ // 2. Mock DB
30
+ const mockDb = {
31
+ prepare: vi.fn().mockReturnValue({
32
+ run: vi.fn().mockReturnValue({ changes: 1 }),
33
+ get: vi.fn(),
34
+ all: vi.fn().mockReturnValue([]),
35
+ }),
36
+ };
37
+ vi.mock("../../storage/db.js", () => {
38
+ return {
39
+ getDatabase: () => mockDb,
40
+ };
41
+ });
42
+ // 3. Mock Storage/Transcript module functions
43
+ const mockLoadLatestCompaction = vi.fn();
44
+ const mockGetNextMessageSequence = vi.fn(() => 1);
45
+ const mockAppendCompaction = vi.fn();
46
+ const mockLoadTranscriptState = vi.fn();
47
+ const mockAppendMessages = vi.fn((sessId, msgs) => msgs.map((_, idx) => idx + 100));
48
+ const mockMarkMessageCompleted = vi.fn();
49
+ vi.mock("../../storage/transcript.js", () => {
50
+ return {
51
+ loadLatestCompaction: (...args) => mockLoadLatestCompaction.apply(null, args),
52
+ getNextMessageSequence: (...args) => mockGetNextMessageSequence.apply(null, args),
53
+ appendCompaction: (...args) => mockAppendCompaction.apply(null, args),
54
+ loadTranscriptState: (...args) => mockLoadTranscriptState.apply(null, args),
55
+ appendMessages: (...args) => mockAppendMessages.apply(null, args),
56
+ markMessageCompleted: (...args) => mockMarkMessageCompleted.apply(null, args),
57
+ buildChatEntries: vi.fn(() => []),
58
+ getLastTodoWriteArgs: vi.fn(() => null),
59
+ loadTranscript: vi.fn(() => []),
60
+ };
61
+ });
62
+ // 4. Mock the main storage index barrel file
63
+ const parentSessionState = {
64
+ messages: [{ role: "user", content: "Hello parent" }],
65
+ seqs: [1],
66
+ };
67
+ vi.mock("../../storage/index.js", () => {
68
+ return {
69
+ appendCompaction: (...args) => mockAppendCompaction.apply(null, args),
70
+ appendMessages: (...args) => mockAppendMessages.apply(null, args),
71
+ getNextMessageSequence: (...args) => mockGetNextMessageSequence.apply(null, args),
72
+ loadTranscriptState: (...args) => mockLoadTranscriptState.apply(null, args),
73
+ loadTranscript: vi.fn(() => []),
74
+ buildChatEntries: vi.fn(() => []),
75
+ getLastTodoWriteArgs: vi.fn(() => null),
76
+ markMessageCompleted: (...args) => mockMarkMessageCompleted.apply(null, args),
77
+ appendSystemMessage: vi.fn(() => 0),
78
+ getSessionTotalTokens: vi.fn(() => ({ inputTokens: 0, outputTokens: 0 })),
79
+ logInteraction: vi.fn(),
80
+ recordUsageEvent: vi.fn(),
81
+ SessionStore: class {
82
+ getWorkspace() {
83
+ return { id: "workspace-1", rootPath: "/dummy" };
84
+ }
85
+ openSession(selector, model, mode, cwd) {
86
+ return {
87
+ id: "session-parent",
88
+ workspaceId: "workspace-1",
89
+ model,
90
+ mode,
91
+ cwdAtStart: cwd,
92
+ cwdLast: cwd,
93
+ status: "active",
94
+ createdAt: new Date(),
95
+ updatedAt: new Date(),
96
+ };
97
+ }
98
+ createSession(model, mode, cwd) {
99
+ return {
100
+ id: "session-child",
101
+ workspaceId: "workspace-1",
102
+ model,
103
+ mode,
104
+ cwdAtStart: cwd,
105
+ cwdLast: cwd,
106
+ status: "active",
107
+ createdAt: new Date(),
108
+ updatedAt: new Date(),
109
+ };
110
+ }
111
+ getRequiredSession(id) {
112
+ return {
113
+ id,
114
+ workspaceId: "workspace-1",
115
+ model: "dummy-model",
116
+ mode: "agent",
117
+ cwdAtStart: "/dummy",
118
+ cwdLast: "/dummy",
119
+ status: "active",
120
+ createdAt: new Date(),
121
+ updatedAt: new Date(),
122
+ };
123
+ }
124
+ touchSession() { }
125
+ setMode() { }
126
+ setStatus() { }
127
+ setModel() { }
128
+ setTitle() { }
129
+ getLatestSession() {
130
+ return null;
131
+ }
132
+ getSessionById() {
133
+ return null;
134
+ }
135
+ },
136
+ };
137
+ });
138
+ // 5. Mock MessageProcessor so we can simulate the messages it writes
139
+ vi.mock("../message-processor.js", () => {
140
+ return {
141
+ MessageProcessor: class {
142
+ deps;
143
+ constructor(deps) {
144
+ this.deps = deps;
145
+ }
146
+ async *run(userMessage) {
147
+ if (userMessage === "trigger error") {
148
+ throw new Error("Simulated MessageProcessor crash");
149
+ }
150
+ if (userMessage === "trigger transient error") {
151
+ globalThis.__transientAttempts = (globalThis.__transientAttempts || 0) + 1;
152
+ if (globalThis.__transientAttempts < 3) {
153
+ throw new Error("fetch failed");
154
+ }
155
+ this.deps.messages.push({ role: "assistant", content: "Sub-session final structured response after retry" }, { role: "tool", content: "Final tool outcome (should be copied)" });
156
+ yield { type: "content", content: "processing after retry..." };
157
+ return;
158
+ }
159
+ if (userMessage === "test consult") {
160
+ if (this.deps.consultParentSession) {
161
+ const advice = await this.deps.consultParentSession("How to handle this error?");
162
+ this.deps.messages.push({ role: "assistant", content: `Advice: ${advice}` }, { role: "tool", content: "Final tool outcome (should be copied)" });
163
+ }
164
+ yield { type: "content", content: "consulting..." };
165
+ return;
166
+ }
167
+ // Simulating writing messages during turn execution
168
+ // Under a sub-session, we want to write intermediate clutter,
169
+ // and check that only final assistant response gets absorbed.
170
+ this.deps.messages.push({ role: "assistant", content: "Intermediate assistant prompt analysis" }, { role: "tool", content: "Intermediate tool result that should be ignored" }, { role: "assistant", content: "Sub-session final structured response" }, { role: "tool", content: "Final tool outcome (should be copied)" });
171
+ yield { type: "content", content: "processing..." };
172
+ }
173
+ },
174
+ };
175
+ });
176
+ describe("Agent - Sub-Session Delegation & Absorption", () => {
177
+ let cleanup = null;
178
+ beforeAll(async () => {
179
+ await loadCatalog();
180
+ });
181
+ beforeEach(() => {
182
+ process.env.MUONROI_FORCE_ROUTING_CLASSIFY = "1";
183
+ vi.clearAllMocks();
184
+ globalThis.__transientAttempts = 0;
185
+ parentSessionState.messages = [{ role: "user", content: "Hello parent" }];
186
+ parentSessionState.seqs = [1];
187
+ mockLoadTranscriptState.mockReturnValue(parentSessionState);
188
+ const mockModelHandle = installMockModel({ fixture: { stream: textOnlyStream("ignored mock stream") } });
189
+ cleanup = () => {
190
+ delete process.env.MUONROI_FORCE_ROUTING_CLASSIFY;
191
+ mockModelHandle.uninstall();
192
+ };
193
+ });
194
+ afterEach(() => {
195
+ cleanup?.();
196
+ });
197
+ it("routes DIRECT_ANSWER directly without spawning a sub-session", async () => {
198
+ mockClassifySubSessionAction.mockResolvedValue({
199
+ action: "DIRECT_ANSWER",
200
+ confidence: 0.95,
201
+ reason: "simple question",
202
+ });
203
+ const agent = new Agent("sk-dummy", undefined, "deepseek-v4-flash", undefined, {
204
+ persistSession: true,
205
+ session: "session-parent",
206
+ });
207
+ // Clear initial messages
208
+ agent.messages = [];
209
+ const generator = agent.processMessage("What is 2+2?");
210
+ const chunks = [];
211
+ for await (const chunk of generator) {
212
+ chunks.push(chunk);
213
+ }
214
+ // Since it's DIRECT_ANSWER, it should NOT try to insert child sub-sessions
215
+ expect(mockDb.prepare).not.toHaveBeenCalledWith(expect.stringContaining("parent_session_id"));
216
+ expect(agent.getSessionId()).toBe("session-parent");
217
+ });
218
+ it("routes SPAWN_SUB_SESSION: creates child session, injects overlay prompt, runs in isolation, and absorbs final outcome", async () => {
219
+ mockClassifySubSessionAction.mockResolvedValue({
220
+ action: "SPAWN_SUB_SESSION",
221
+ confidence: 0.98,
222
+ reason: "requires multi-step tool execution",
223
+ });
224
+ const agent = new Agent("sk-dummy", undefined, "deepseek-v4-flash", undefined, {
225
+ persistSession: true,
226
+ session: "session-parent",
227
+ });
228
+ // Start with empty messages in parent
229
+ agent.messages = [];
230
+ const generator = agent.processMessage("Implement auth and write tests");
231
+ const chunks = [];
232
+ for await (const chunk of generator) {
233
+ chunks.push(chunk);
234
+ }
235
+ // 1. Verify DB parent session link update is triggered
236
+ expect(mockDb.prepare).toHaveBeenCalledWith("UPDATE sessions SET parent_session_id = ? WHERE id = ?");
237
+ // 2. Verify parent session was restored at the end
238
+ expect(agent.getSessionId()).toBe("session-parent");
239
+ // 3. Verify final messages in parent contain the absorbed outcome
240
+ // We expect the final messages list to have absorbed:
241
+ // - last assistant message: "Sub-session final structured response"
242
+ // - subsequent tool message(s): "Final tool outcome (should be copied)"
243
+ // It should NOT contain:
244
+ // - intermediate assistant: "Intermediate assistant prompt analysis"
245
+ // - intermediate tool: "Intermediate tool result that should be ignored"
246
+ expect(agent.messages).toHaveLength(3); // Hello parent + absorbed assistant + absorbed tool
247
+ expect(agent.messages[1]).toEqual({
248
+ role: "assistant",
249
+ content: "Sub-session final structured response",
250
+ });
251
+ expect(agent.messages[2]).toEqual({
252
+ role: "tool",
253
+ content: "Final tool outcome (should be copied)",
254
+ });
255
+ // 4. Verify appendMessages was called to persist the absorbed turn in the parent
256
+ expect(mockAppendMessages).toHaveBeenCalledWith("session-parent", expect.arrayContaining([
257
+ expect.objectContaining({ role: "user", content: "Implement auth and write tests" }),
258
+ expect.objectContaining({ role: "assistant", content: "Sub-session final structured response" }),
259
+ expect.objectContaining({ role: "tool", content: "Final tool outcome (should be copied)" }),
260
+ ]));
261
+ });
262
+ it("restores parent session if sub-session execution crashes", async () => {
263
+ mockClassifySubSessionAction.mockResolvedValue({
264
+ action: "SPAWN_SUB_SESSION",
265
+ confidence: 0.98,
266
+ reason: "requires sub-session",
267
+ });
268
+ const agent = new Agent("sk-dummy", undefined, "deepseek-v4-flash", undefined, {
269
+ persistSession: true,
270
+ session: "session-parent",
271
+ });
272
+ // Start with empty messages in parent
273
+ agent.messages = [];
274
+ // Run a turn that will crash
275
+ let threw = false;
276
+ try {
277
+ const generator = agent.processMessage("trigger error");
278
+ for await (const chunk of generator) {
279
+ // consume stream
280
+ }
281
+ }
282
+ catch (err) {
283
+ expect(err.message).toBe("Simulated MessageProcessor crash");
284
+ threw = true;
285
+ }
286
+ expect(threw).toBe(true);
287
+ // Verify parent session is restored even after crash
288
+ expect(agent.getSessionId()).toBe("session-parent");
289
+ });
290
+ it("resumes an existing active sub-session if found in database", async () => {
291
+ mockClassifySubSessionAction.mockResolvedValue({
292
+ action: "SPAWN_SUB_SESSION",
293
+ confidence: 0.98,
294
+ reason: "requires sub-session",
295
+ });
296
+ // Mock active sub-session row lookup
297
+ mockDb.prepare.mockImplementation((sql) => {
298
+ if (sql.includes("status = 'active'")) {
299
+ return {
300
+ get: () => ({ id: "session-active-child" }),
301
+ run: () => ({ changes: 1 }),
302
+ all: () => [],
303
+ };
304
+ }
305
+ return {
306
+ run: () => ({ changes: 1 }),
307
+ get: () => undefined,
308
+ all: () => [],
309
+ };
310
+ });
311
+ // Mock loadTranscriptState to return a state for the child session or parent session depending on input
312
+ mockLoadTranscriptState.mockImplementation((sessId) => {
313
+ if (sessId === "session-parent") {
314
+ return parentSessionState;
315
+ }
316
+ return {
317
+ messages: [{ role: "user", content: "Previous attempt" }],
318
+ seqs: [1],
319
+ };
320
+ });
321
+ const agent = new Agent("sk-dummy", undefined, "deepseek-v4-flash", undefined, {
322
+ persistSession: true,
323
+ session: "session-parent",
324
+ });
325
+ // Start with empty messages in parent
326
+ agent.messages = [];
327
+ const generator = agent.processMessage("Implement auth and write tests");
328
+ const chunks = [];
329
+ for await (const chunk of generator) {
330
+ chunks.push(chunk);
331
+ }
332
+ // 1. Verify that we yielded the resumption status message
333
+ const hasResumeMessage = chunks.some((c) => c.type === "content" && c.content?.includes("Phát hiện sub-session trước đó bị gián đoạn"));
334
+ expect(hasResumeMessage).toBe(true);
335
+ // 2. Verify that the agent restored parent session at the end
336
+ expect(agent.getSessionId()).toBe("session-parent");
337
+ // 3. Verify that loadTranscriptState was called with the resumed sub-session ID
338
+ expect(mockLoadTranscriptState).toHaveBeenCalledWith("session-active-child");
339
+ });
340
+ it("triggers session rotation if the model decides ROTATE_SESSION", async () => {
341
+ mockClassifySubSessionAction.mockResolvedValue({
342
+ action: "ROTATE_SESSION",
343
+ confidence: 0.9,
344
+ reason: "New topic",
345
+ });
346
+ const agent = new Agent("sk-dummy", undefined, "deepseek-v4-flash", undefined, {
347
+ persistSession: true,
348
+ session: "session-parent",
349
+ });
350
+ agent.messages = [{ role: "user", content: "Switch topics" }];
351
+ const generator = agent.processMessage("switch topic");
352
+ const chunks = [];
353
+ for await (const chunk of generator) {
354
+ chunks.push(chunk);
355
+ }
356
+ // Verify deliberateCompact was called
357
+ expect(mockDeliberateCompact).toHaveBeenCalled();
358
+ // Verify a new session was created (the active session ID changes)
359
+ expect(agent.getSessionId()).toBe("session-child");
360
+ });
361
+ it("triggers session rotation if currentChars exceeds the hard safety threshold (threshold * 2)", async () => {
362
+ process.env.MUONROI_SILENT_ROTATION_THRESHOLD = "100";
363
+ mockClassifySubSessionAction.mockResolvedValue({
364
+ action: "DIRECT_ANSWER",
365
+ confidence: 0.95,
366
+ reason: "simple answer",
367
+ });
368
+ const agent = new Agent("sk-dummy", undefined, "deepseek-v4-flash", undefined, {
369
+ persistSession: true,
370
+ session: "session-parent",
371
+ });
372
+ agent.messages = [{ role: "user", content: "A".repeat(250) }];
373
+ try {
374
+ const generator = agent.processMessage("short message");
375
+ const chunks = [];
376
+ for await (const chunk of generator) {
377
+ chunks.push(chunk);
378
+ }
379
+ // Verify deliberateCompact was called
380
+ expect(mockDeliberateCompact).toHaveBeenCalled();
381
+ // Verify it rotated to session-child
382
+ expect(agent.getSessionId()).toBe("session-child");
383
+ }
384
+ finally {
385
+ delete process.env.MUONROI_SILENT_ROTATION_THRESHOLD;
386
+ }
387
+ });
388
+ it("does not resume a stale active sub-session (timeout), marks it as abandoned, and forks a new child session", async () => {
389
+ mockClassifySubSessionAction.mockResolvedValue({
390
+ action: "SPAWN_SUB_SESSION",
391
+ confidence: 0.98,
392
+ reason: "requires sub-session",
393
+ });
394
+ const twentyMinsAgo = new Date(Date.now() - 20 * 60 * 1000).toISOString();
395
+ // Mock active sub-session row lookup returning a stale row
396
+ mockDb.prepare.mockImplementation((sql) => {
397
+ if (sql.includes("status = 'active'")) {
398
+ return {
399
+ get: () => ({ id: "session-stale-child", updated_at: twentyMinsAgo }),
400
+ run: () => ({ changes: 1 }),
401
+ all: () => [],
402
+ };
403
+ }
404
+ return {
405
+ run: () => ({ changes: 1 }),
406
+ get: () => undefined,
407
+ all: () => [],
408
+ };
409
+ });
410
+ const agent = new Agent("sk-dummy", undefined, "deepseek-v4-flash", undefined, {
411
+ persistSession: true,
412
+ session: "session-parent",
413
+ });
414
+ // Start with empty messages in parent
415
+ agent.messages = [];
416
+ const generator = agent.processMessage("Implement auth and write tests");
417
+ const chunks = [];
418
+ for await (const chunk of generator) {
419
+ chunks.push(chunk);
420
+ }
421
+ // 1. Verify that we did NOT yield the resumption message
422
+ const hasResumeMessage = chunks.some((c) => c.type === "content" && c.content?.includes("Phát hiện sub-session trước đó bị gián đoạn"));
423
+ expect(hasResumeMessage).toBe(false);
424
+ // 2. Verify that we marked the stale sub-session as abandoned
425
+ expect(mockDb.prepare).toHaveBeenCalledWith("UPDATE sessions SET status = 'abandoned', updated_at = ? WHERE id = ?");
426
+ // 3. Verify that we created a new session
427
+ expect(agent.getSessionId()).toBe("session-parent");
428
+ });
429
+ it("registers and successfully executes consultParentSession tool in sub-session", async () => {
430
+ mockClassifySubSessionAction.mockResolvedValue({
431
+ action: "SPAWN_SUB_SESSION",
432
+ confidence: 0.98,
433
+ reason: "requires sub-session for consultation test",
434
+ });
435
+ mockDb.prepare.mockImplementation((sql) => {
436
+ if (sql.includes("SELECT parent_session_id")) {
437
+ return {
438
+ get: () => ({ parent_session_id: "session-parent" }),
439
+ run: () => ({ changes: 1 }),
440
+ all: () => [],
441
+ };
442
+ }
443
+ if (sql.includes("SELECT model_id")) {
444
+ return {
445
+ get: () => ({ model_id: "deepseek-v4-flash" }),
446
+ run: () => ({ changes: 1 }),
447
+ all: () => [],
448
+ };
449
+ }
450
+ return {
451
+ run: () => ({ changes: 1 }),
452
+ get: () => undefined,
453
+ all: () => [],
454
+ };
455
+ });
456
+ mockLoadTranscriptState.mockReturnValue({
457
+ messages: [{ role: "user", content: "Hello parent" }],
458
+ seqs: [1],
459
+ });
460
+ const agent = new Agent("sk-dummy", undefined, "deepseek-v4-flash", undefined, {
461
+ persistSession: true,
462
+ session: "session-parent",
463
+ });
464
+ // Start with empty messages in parent
465
+ agent.messages = [];
466
+ const generator = agent.processMessage("test consult");
467
+ const chunks = [];
468
+ for await (const chunk of generator) {
469
+ chunks.push(chunk);
470
+ }
471
+ // Verify that generateText was called to advice the sub-session
472
+ expect(mockGenerateText).toHaveBeenCalled();
473
+ // Verify parent session is restored
474
+ expect(agent.getSessionId()).toBe("session-parent");
475
+ // The parent's final messages should have absorbed the assistant message which contains Advice
476
+ expect(agent.messages).toHaveLength(3); // Hello parent + absorbed assistant (advice) + absorbed tool
477
+ expect(agent.messages[1].content).toContain("Advice: Mocked parent advice response");
478
+ });
479
+ it("automatically retries transient errors in sub-session before succeeding", async () => {
480
+ mockClassifySubSessionAction.mockResolvedValue({
481
+ action: "SPAWN_SUB_SESSION",
482
+ confidence: 0.98,
483
+ reason: "requires sub-session for transient error test",
484
+ });
485
+ const agent = new Agent("sk-dummy", undefined, "deepseek-v4-flash", undefined, {
486
+ persistSession: true,
487
+ session: "session-parent",
488
+ });
489
+ // Start with empty messages in parent
490
+ agent.messages = [];
491
+ const generator = agent.processMessage("trigger transient error");
492
+ const chunks = [];
493
+ for await (const chunk of generator) {
494
+ chunks.push(chunk);
495
+ }
496
+ // Verify parent session is restored
497
+ expect(agent.getSessionId()).toBe("session-parent");
498
+ console.log("TEST MESSAGES: ", JSON.stringify(agent.messages, null, 2));
499
+ // The sub-session should have succeeded on the 3rd attempt and its output should be absorbed
500
+ expect(agent.messages).toHaveLength(3); // Hello parent + absorbed assistant (after retry) + absorbed tool
501
+ expect(agent.messages[1].content).toContain("Sub-session final structured response after retry");
502
+ // Verify warning chunks were emitted
503
+ const warningChunks = chunks.filter((c) => c.type === "content" && c.content?.includes("Có lỗi mạng hoặc rate limit xảy ra trong sub-session"));
504
+ expect(warningChunks.length).toBe(2);
505
+ });
506
+ });
507
+ //# sourceMappingURL=sub-session-delegation.test.js.map
@@ -76,13 +76,16 @@ describe("Agent class", { timeout: 30_000 }, () => {
76
76
  agent.setModel("claude-sonnet-4-6-20250514");
77
77
  expect(agent.getModel()).toBe("claude-sonnet-4-6-20250514");
78
78
  });
79
- it("constructs with sandbox mode", async () => {
79
+ it("constructs with sandbox mode — always returns 'off' (sandbox removed)", async () => {
80
80
  const { Agent } = await importAgentModule();
81
+ // Sandbox has been removed; passing sandboxMode: "shuru" is accepted for
82
+ // back-compat but getSandboxMode() always returns "off" until a new
83
+ // sandbox implementation is wired in.
81
84
  const agent = new Agent(undefined, undefined, undefined, undefined, {
82
85
  persistSession: false,
83
86
  sandboxMode: "shuru",
84
87
  });
85
- expect(agent.getSandboxMode()).toBe("shuru");
88
+ expect(agent.getSandboxMode()).toBe("off");
86
89
  });
87
90
  it("defaults sandbox mode to off", async () => {
88
91
  const { Agent } = await importAgentModule();
@@ -0,0 +1,110 @@
1
+ import type { LspDiagnostic, LspDiagnosticFile } from "../lsp/types.js";
2
+ /** Attribution line every auto-commit message ends with (mirrors the prompt rule). */
3
+ export declare const AUTO_COMMIT_ATTRIBUTION = "Coding by - Muonroi-CLI";
4
+ export interface AutoCommitResult {
5
+ committed: boolean;
6
+ sha?: string;
7
+ fileCount?: number;
8
+ reason?: string;
9
+ /** G1: when reason === "lsp-errors", the per-file diagnostic summary the agent should fix. */
10
+ detail?: string;
11
+ }
12
+ /**
13
+ * G1 commit quality gate. Default ON; disable with `MUONROI_COMMIT_GATE=0`
14
+ * (mirrors the `MUONROI_AUTO_COMMIT=0` convention). Off automatically under the
15
+ * unit-test suite so specs that commit fixtures aren't gated.
16
+ */
17
+ export declare function isCommitGateEnabled(): boolean;
18
+ /**
19
+ * G1: per-file LSP-errors gate. Runs the SAME LSP diagnostics produced at
20
+ * write-time on each staged path; blocks the commit if any staged file has an
21
+ * ERROR (severity 1). Scoped to each file's OWN diagnostics so unrelated repo
22
+ * breakage never blocks. Files with no registered LSP server return no
23
+ * diagnostics and pass (so docs/config/non-source commits are unaffected).
24
+ *
25
+ * Fails OPEN on timeout or any error — the gate must never hang a turn or block
26
+ * a commit on its own failure. Only a clean, in-budget run with a real
27
+ * severity-1 diagnostic blocks.
28
+ */
29
+ /**
30
+ * G1 (pure): the gate's BLOCK decision for one staged file — its own
31
+ * severity-1 (error) diagnostics. Scoped by absolute path so a diagnostic LSP
32
+ * reports against a DIFFERENT file (cross-file type breakage) never blocks this
33
+ * commit, and warnings/infos (severity >= 2) are ignored. Exported for testing.
34
+ */
35
+ export declare function blockingErrorsForFile(diagFiles: LspDiagnosticFile[], absPath: string): LspDiagnostic[];
36
+ export declare function gateStagedPaths(cwd: string, paths: string[], budgetMs?: number): Promise<{
37
+ ok: boolean;
38
+ summary?: string;
39
+ }>;
40
+ /**
41
+ * G1 follow-up: the set of paths a bash-tool `git commit` would include, so the
42
+ * LSP commit gate can run on them. A raw `git commit` (unlike the `git_commit`
43
+ * tool) doesn't tell us its paths, so derive them from git state:
44
+ * - always: the already-staged set (`git diff --cached --name-only`)
45
+ * - `git commit -a`: + tracked modifications it auto-stages at commit time
46
+ * (`git diff --name-only`)
47
+ * - `git add -A`/`.`/`--all` chained in the SAME command: + the whole
48
+ * working-tree change set (`git status --porcelain`), since the add hasn't
49
+ * run yet at pre-exec time so it isn't reflected in `--cached`.
50
+ * Deleted/binary/unreadable paths are skipped later by gateStagedPaths when it
51
+ * reads them. KNOWN GAP: `git add <specific-path> && git commit` in one command
52
+ * where <specific-path> was not pre-staged is NOT covered (we don't parse
53
+ * pathspecs); the `git_commit` tool + auto-commit backstop remain the primary
54
+ * gates. Returns repo-relative paths (gateStagedPaths resolves them against cwd).
55
+ */
56
+ export declare function pathsForCommitGate(cwd: string, opts: {
57
+ broadAdd: boolean;
58
+ commitAll: boolean;
59
+ }): Promise<string[]>;
60
+ export declare function isAutoCommitEnabled(): boolean;
61
+ /**
62
+ * Parse `git status --porcelain` output into the set of changed paths. Handles
63
+ * renames (`orig -> new`, keeps the new path) and quoted paths with spaces.
64
+ */
65
+ export declare function parsePorcelainPaths(out: string): Set<string>;
66
+ export declare function isSensitivePath(path: string): boolean;
67
+ /** CLI artifact / build junk that must never be folded into an agent commit. */
68
+ export declare function isCliArtifactPath(path: string): boolean;
69
+ /** Any path the auto-commit must skip: a secret or a CLI/build artifact. */
70
+ export declare function isExcludedPath(path: string): boolean;
71
+ /**
72
+ * Backstop subject naming the changed FILES — used only by the deterministic
73
+ * end-of-turn safety net (when the agent did not commit its own work via the
74
+ * git_commit tool). Deliberately NOT derived from the raw user prompt (a
75
+ * truncated prompt is meaningless, especially for a multi-step plan); a file list
76
+ * at least says what changed. The meaningful, model-authored message is the
77
+ * git_commit tool's job (the agent writes it per chunk/plan-step).
78
+ */
79
+ export declare function buildFileListSubject(paths: string[]): string;
80
+ /**
81
+ * Split an AGENT-authored message into a bounded subject (first line, <=72 so it
82
+ * passes conventional-commit hooks) and a bounded body. Strips CRs and drops any
83
+ * attribution line the agent already added (we append exactly one ourselves).
84
+ * Callers pass subject/body/attribution as SEPARATE `-m` flags so git inserts the
85
+ * blank-line separators itself — embedding "\n\n" in a single `-m` arg gets
86
+ * mangled by Windows execFile (the attribution then glues onto the subject).
87
+ */
88
+ export declare function splitCommitMessage(message: string): {
89
+ subject: string;
90
+ body: string;
91
+ };
92
+ /** Snapshot the dirty/untracked path set before a turn (empty when not a repo). */
93
+ export declare function snapshotDirtyPaths(cwd: string): Promise<Set<string>>;
94
+ /**
95
+ * Commit the files the agent changed this turn. `dirtyBefore` is the snapshot
96
+ * from before the turn. Returns a result describing what happened; never throws.
97
+ */
98
+ export declare function maybeAutoCommitTurn(opts: {
99
+ cwd: string;
100
+ dirtyBefore: Set<string>;
101
+ userMessage: string;
102
+ }): Promise<AutoCommitResult>;
103
+ /**
104
+ * Commit a SPECIFIC set of paths (absolute or repo-relative) with an
105
+ * AGENT-AUTHORED message — backs the git_commit tool. Stages only `paths` (minus
106
+ * secrets/artifacts), commits with the model's message + the attribution line,
107
+ * scoped by pathspec. No-op if nothing among `paths` is actually staged
108
+ * (e.g. already committed). Never throws.
109
+ */
110
+ export declare function commitSpecificPaths(cwd: string, paths: string[], message: string): Promise<AutoCommitResult>;