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
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 muonroi
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 muonroi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,122 +1,122 @@
1
- <p align="center">
2
- <h1 align="center">muonroi-cli</h1>
3
- <p align="center">
4
- <em>An AI coding agent where models argue with each other before answering.</em>
5
- </p>
6
- <p align="center">
7
- <a href="https://github.com/muonroi/muonroi-cli/actions/workflows/ci-matrix.yml"><img alt="CI" src="https://github.com/muonroi/muonroi-cli/actions/workflows/ci-matrix.yml/badge.svg"></a>
8
- <a href="https://www.npmjs.com/package/muonroi-cli"><img alt="npm" src="https://img.shields.io/npm/v/muonroi-cli.svg"></a>
9
- <img alt="Providers" src="https://img.shields.io/badge/providers-7%20supported-blue">
10
- <img alt="License" src="https://img.shields.io/badge/license-MIT-yellow">
11
- <img alt="Runtime" src="https://img.shields.io/badge/runtime-Bun%201.3%2B-orange">
12
- </p>
13
- </p>
14
-
15
- ---
16
-
17
- > Routes each task to the optimal model, runs adversarial multi-model debates for high-stakes decisions, and persists behavioral memory across sessions. Bring your own API keys. Total cost: ~$5/month.
18
-
19
- <p align="center">
20
- <img src="https://raw.githubusercontent.com/muonroi/muonroi-cli/master/docs/demo.gif" alt="Council debate — REST vs gRPC decision" width="840" />
21
- </p>
22
-
23
- ## Quick Start
24
-
25
- ### Install
26
-
27
- **Recommended — prebuilt standalone binary (zero runtime deps, no prereqs):**
28
-
29
- Linux / macOS:
30
- ```bash
31
- curl -fsSL https://raw.githubusercontent.com/muonroi/muonroi-cli/master/install.sh | bash
32
- ```
33
-
34
- Windows PowerShell:
35
- ```powershell
36
- irm https://raw.githubusercontent.com/muonroi/muonroi-cli/master/install.ps1 | iex
37
- ```
38
-
39
- The installers download a `bun --compile` binary from GitHub Releases — single executable, all native deps bundled, no Node/Bun/build tools required.
40
-
41
- **Bun runtime (requires Bun ≥ 1.x):**
42
- ```bash
43
- bun add -g muonroi-cli
44
- muonroi-cli
45
- ```
46
-
47
- > **⚠ Why no `npm install -g`?** The TUI engine (`@opentui/core`) uses Bun-only `import ... with { type: "file" }` for Tree-sitter syntax highlight queries (`.scm` files). Node ESM cannot load these, so running under bare Node throws `ERR_UNKNOWN_FILE_EXTENSION`. Use the standalone installer (above) or `bun add -g`.
48
-
49
- ### First run
50
-
51
- On first launch the CLI shows a wizard that:
52
- 1. Lists supported providers (currently **DeepSeek** and **SiliconFlow**).
53
- 2. Offers four ways to add credentials: paste an API key, import an encrypted bundle (`keys export`/`keys import`), sync from Bitwarden, or skip and add later via `/providers` inside the TUI.
54
-
55
- After setup, role routing auto-balances across enabled providers:
56
-
57
- ```json
58
- // ~/.muonroi-cli/user-settings.json (generated by the wizard — edit if needed)
59
- {
60
- "defaultProvider": "deepseek",
61
- "providers": {
62
- "deepseek": { "enabled": true },
63
- "siliconflow": { "enabled": true }
64
- },
65
- "roleModels": {
66
- "leader": "deepseek-v4-pro",
67
- "implement": "deepseek-v4-flash",
68
- "verify": "deepseek-v4-pro",
69
- "research": "deepseek-v4-flash"
70
- }
71
- }
72
- ```
73
-
74
- ### Moving keys between devices
75
-
76
- ```bash
77
- # Source device — encrypts every stored key into one passphrase-protected file
78
- muonroi-cli keys export ~/muonroi-keys.json
79
-
80
- # Move the file via any channel (USB, Drive, AirDrop, email...)
81
-
82
- # Target device — same passphrase rehydrates the OS keychain
83
- muonroi-cli keys import ~/muonroi-keys.json
84
- ```
85
-
86
- Inside the TUI press `/providers` then `B` to sync directly from a Bitwarden vault instead.
87
-
88
- ### Updates
89
-
90
- The CLI checks npm once per day on startup and prompts when a newer version is available. Set `autoUpdate: true` in `user-settings.json` to skip the prompt and update silently. Manual: `muonroi-cli update`.
91
-
92
- ## Documentation
93
-
94
- Full documentation at **[docs.muonroi.com/docs/cli](https://docs.muonroi.com/docs/cli/overview)**
95
-
96
- | Topic | Link |
97
- |---|---|
98
- | Overview & architecture | [CLI Overview](https://docs.muonroi.com/docs/cli/overview) |
99
- | Multi-Model Council | [Council Debate Guide](https://docs.muonroi.com/docs/cli/guides/council-debate) |
100
- | Prompt Intelligence Layer | [PIL Pipeline Guide](https://docs.muonroi.com/docs/cli/guides/pil-pipeline) |
101
- | Experience Engine | [Experience Engine Guide](https://docs.muonroi.com/docs/cli/guides/experience-engine) |
102
- | Agent Harness | [Agent Harness Guide](https://docs.muonroi.com/docs/cli/guides/agent-harness) |
103
- | Settings reference | [CLI Settings Reference](https://docs.muonroi.com/docs/cli/reference/cli-settings-reference) |
104
- | Commands reference | [Commands Reference](https://docs.muonroi.com/docs/cli/reference/commands-reference) |
105
- | Providers reference | [Providers Reference](https://docs.muonroi.com/docs/cli/reference/providers-reference) |
106
-
107
- ## Development
108
-
109
- ```bash
110
- git clone https://github.com/muonroi/muonroi-cli.git
111
- cd muonroi-cli && bun install
112
-
113
- bun run dev # run from source
114
- bun run typecheck # type check
115
- bun run test # vitest
116
- bun run lint # biome check
117
- bun run build:binary # standalone binary
118
- ```
119
-
120
- ## License
121
-
122
- MIT
1
+ <p align="center">
2
+ <h1 align="center">muonroi-cli</h1>
3
+ <p align="center">
4
+ <em>An AI coding agent where models argue with each other before answering.</em>
5
+ </p>
6
+ <p align="center">
7
+ <a href="https://github.com/muonroi/muonroi-cli/actions/workflows/ci-matrix.yml"><img alt="CI" src="https://github.com/muonroi/muonroi-cli/actions/workflows/ci-matrix.yml/badge.svg"></a>
8
+ <a href="https://www.npmjs.com/package/muonroi-cli"><img alt="npm" src="https://img.shields.io/npm/v/muonroi-cli.svg"></a>
9
+ <img alt="Providers" src="https://img.shields.io/badge/providers-7%20supported-blue">
10
+ <img alt="License" src="https://img.shields.io/badge/license-MIT-yellow">
11
+ <img alt="Runtime" src="https://img.shields.io/badge/runtime-Bun%201.3%2B-orange">
12
+ </p>
13
+ </p>
14
+
15
+ ---
16
+
17
+ > Routes each task to the optimal model, runs adversarial multi-model debates for high-stakes decisions, and persists behavioral memory across sessions. Bring your own API keys. Total cost: ~$5/month.
18
+
19
+ <p align="center">
20
+ <img src="https://raw.githubusercontent.com/muonroi/muonroi-cli/master/docs/demo.gif" alt="Council debate — REST vs gRPC decision" width="840" />
21
+ </p>
22
+
23
+ ## Quick Start
24
+
25
+ ### Install
26
+
27
+ **Recommended — prebuilt standalone binary (zero runtime deps, no prereqs):**
28
+
29
+ Linux / macOS:
30
+ ```bash
31
+ curl -fsSL https://raw.githubusercontent.com/muonroi/muonroi-cli/master/install.sh | bash
32
+ ```
33
+
34
+ Windows PowerShell:
35
+ ```powershell
36
+ irm https://raw.githubusercontent.com/muonroi/muonroi-cli/master/install.ps1 | iex
37
+ ```
38
+
39
+ The installers download a `bun --compile` binary from GitHub Releases — single executable, all native deps bundled, no Node/Bun/build tools required.
40
+
41
+ **Bun runtime (requires Bun ≥ 1.x):**
42
+ ```bash
43
+ bun add -g muonroi-cli
44
+ muonroi-cli
45
+ ```
46
+
47
+ > **⚠ Why no `npm install -g`?** The TUI engine (`@opentui/core`) uses Bun-only `import ... with { type: "file" }` for Tree-sitter syntax highlight queries (`.scm` files). Node ESM cannot load these, so running under bare Node throws `ERR_UNKNOWN_FILE_EXTENSION`. Use the standalone installer (above) or `bun add -g`.
48
+
49
+ ### First run
50
+
51
+ On first launch the CLI shows a wizard that:
52
+ 1. Lists supported providers (currently **DeepSeek** and **SiliconFlow**).
53
+ 2. Offers four ways to add credentials: paste an API key, import an encrypted bundle (`keys export`/`keys import`), sync from Bitwarden, or skip and add later via `/providers` inside the TUI.
54
+
55
+ After setup, role routing auto-balances across enabled providers:
56
+
57
+ ```json
58
+ // ~/.muonroi-cli/user-settings.json (generated by the wizard — edit if needed)
59
+ {
60
+ "defaultProvider": "deepseek",
61
+ "providers": {
62
+ "deepseek": { "enabled": true },
63
+ "siliconflow": { "enabled": true }
64
+ },
65
+ "roleModels": {
66
+ "leader": "deepseek-v4-pro",
67
+ "implement": "deepseek-v4-flash",
68
+ "verify": "deepseek-v4-pro",
69
+ "research": "deepseek-v4-flash"
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### Moving keys between devices
75
+
76
+ ```bash
77
+ # Source device — encrypts every stored key into one passphrase-protected file
78
+ muonroi-cli keys export ~/muonroi-keys.json
79
+
80
+ # Move the file via any channel (USB, Drive, AirDrop, email...)
81
+
82
+ # Target device — same passphrase rehydrates the OS keychain
83
+ muonroi-cli keys import ~/muonroi-keys.json
84
+ ```
85
+
86
+ Inside the TUI press `/providers` then `B` to sync directly from a Bitwarden vault instead.
87
+
88
+ ### Updates
89
+
90
+ The CLI checks npm once per day on startup and prompts when a newer version is available. Set `autoUpdate: true` in `user-settings.json` to skip the prompt and update silently. Manual: `muonroi-cli update`.
91
+
92
+ ## Documentation
93
+
94
+ Full documentation at **[docs.muonroi.com/docs/cli](https://docs.muonroi.com/docs/cli/overview)**
95
+
96
+ | Topic | Link |
97
+ |---|---|
98
+ | Overview & architecture | [CLI Overview](https://docs.muonroi.com/docs/cli/overview) |
99
+ | Multi-Model Council | [Council Debate Guide](https://docs.muonroi.com/docs/cli/guides/council-debate) |
100
+ | Prompt Intelligence Layer | [PIL Pipeline Guide](https://docs.muonroi.com/docs/cli/guides/pil-pipeline) |
101
+ | Experience Engine | [Experience Engine Guide](https://docs.muonroi.com/docs/cli/guides/experience-engine) |
102
+ | Agent Harness | [Agent Harness Guide](https://docs.muonroi.com/docs/cli/guides/agent-harness) |
103
+ | Settings reference | [CLI Settings Reference](https://docs.muonroi.com/docs/cli/reference/cli-settings-reference) |
104
+ | Commands reference | [Commands Reference](https://docs.muonroi.com/docs/cli/reference/commands-reference) |
105
+ | Providers reference | [Providers Reference](https://docs.muonroi.com/docs/cli/reference/providers-reference) |
106
+
107
+ ## Development
108
+
109
+ ```bash
110
+ git clone https://github.com/muonroi/muonroi-cli.git
111
+ cd muonroi-cli && bun install
112
+
113
+ bun run dev # run from source
114
+ bun run typecheck # type check
115
+ bun run test # vitest
116
+ bun run lint # biome check
117
+ bun run build:binary # standalone binary
118
+ ```
119
+
120
+ ## License
121
+
122
+ MIT
@@ -22,6 +22,7 @@ export const LIFECYCLE_PRESET = new Set([
22
22
  "llm-done",
23
23
  "council-step",
24
24
  "council-speaker",
25
+ "council-turn-length",
25
26
  "askcard-open",
26
27
  "askcard-answered",
27
28
  "askcard-cancel",
@@ -54,6 +54,14 @@ const ALLOWED_FIELDS = {
54
54
  round: "pass",
55
55
  correlationId: "pass",
56
56
  },
57
+ "council-turn-length": {
58
+ role: "pass",
59
+ round: "pass",
60
+ charCount: "pass",
61
+ wordCount: "pass",
62
+ model: "pass",
63
+ correlationId: "pass",
64
+ },
57
65
  "askcard-open": {
58
66
  questionId: "pass",
59
67
  question: 300, // cap to 300 chars, then scrub
@@ -46,6 +46,22 @@ export declare function validateStartArgs(args: string[]): {
46
46
  bad: string;
47
47
  };
48
48
  export declare function sanitizeEnv(env: Record<string, string>): Record<string, string>;
49
+ /**
50
+ * Opt-in extra cwd roots for tui.start, layered ON TOP of the default
51
+ * {home, repo-root} allowlist. The posture stays deny-by-default: only roots an
52
+ * operator has explicitly listed are added. Two union sources:
53
+ *
54
+ * 1. env `MUONROI_HARNESS_EXTRA_ROOTS` — a path list separated by the OS path
55
+ * separator (";" on win32, ":" elsewhere) and/or commas.
56
+ * 2. `<REPO_ROOT>/.muonroi-harness-roots.json` — `{ "roots": string[] }`.
57
+ *
58
+ * Rationale: the drive-harness needs to dogfood sibling ecosystem repos (e.g.
59
+ * `D:\sources\Core\*`) that live outside both home and the muonroi-cli checkout.
60
+ * Without an explicit opt-in those cwds are rejected, which blocks real-task
61
+ * evaluation. Clean checkouts have neither the env nor the (gitignored) config
62
+ * file, so behaviour is identical to the original home+repo-only boundary.
63
+ */
64
+ export declare function loadExtraRoots(): string[];
49
65
  export declare function validateCwd(cwd: string): {
50
66
  ok: true;
51
67
  } | {
@@ -11,7 +11,7 @@
11
11
  * fd 3/4, named pipes, WebSocket, …). The consumer (muonroi-cli) provides the
12
12
  * spawn implementation via createMcpHarnessServer({ spawn }).
13
13
  */
14
- import { existsSync, realpathSync } from "node:fs";
14
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
15
15
  import { homedir } from "node:os";
16
16
  import { isAbsolute, resolve } from "node:path";
17
17
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -50,6 +50,51 @@ export function sanitizeEnv(env) {
50
50
  return out;
51
51
  }
52
52
  const REPO_ROOT = process.cwd();
53
+ /**
54
+ * Opt-in extra cwd roots for tui.start, layered ON TOP of the default
55
+ * {home, repo-root} allowlist. The posture stays deny-by-default: only roots an
56
+ * operator has explicitly listed are added. Two union sources:
57
+ *
58
+ * 1. env `MUONROI_HARNESS_EXTRA_ROOTS` — a path list separated by the OS path
59
+ * separator (";" on win32, ":" elsewhere) and/or commas.
60
+ * 2. `<REPO_ROOT>/.muonroi-harness-roots.json` — `{ "roots": string[] }`.
61
+ *
62
+ * Rationale: the drive-harness needs to dogfood sibling ecosystem repos (e.g.
63
+ * `D:\sources\Core\*`) that live outside both home and the muonroi-cli checkout.
64
+ * Without an explicit opt-in those cwds are rejected, which blocks real-task
65
+ * evaluation. Clean checkouts have neither the env nor the (gitignored) config
66
+ * file, so behaviour is identical to the original home+repo-only boundary.
67
+ */
68
+ export function loadExtraRoots() {
69
+ const roots = [];
70
+ const envVal = process.env.MUONROI_HARNESS_EXTRA_ROOTS;
71
+ if (envVal) {
72
+ // Split on the OS path separator or commas. On win32 the separator is ";"
73
+ // (not ":") so drive letters like "D:\..." stay intact.
74
+ const listSep = process.platform === "win32" ? ";" : ":";
75
+ for (const part of envVal.split(new RegExp(`[${listSep},]`))) {
76
+ const trimmed = part.trim();
77
+ if (trimmed)
78
+ roots.push(trimmed);
79
+ }
80
+ }
81
+ const cfgPath = resolve(REPO_ROOT, ".muonroi-harness-roots.json");
82
+ if (existsSync(cfgPath)) {
83
+ try {
84
+ const parsed = JSON.parse(readFileSync(cfgPath, "utf8"));
85
+ if (Array.isArray(parsed.roots)) {
86
+ for (const r of parsed.roots) {
87
+ if (typeof r === "string" && r.trim())
88
+ roots.push(r.trim());
89
+ }
90
+ }
91
+ }
92
+ catch (err) {
93
+ console.error(`[harness/mcp-server] failed to parse ${cfgPath}: ${err?.message}`);
94
+ }
95
+ }
96
+ return roots;
97
+ }
53
98
  export function validateCwd(cwd) {
54
99
  let real;
55
100
  try {
@@ -58,16 +103,22 @@ export function validateCwd(cwd) {
58
103
  catch {
59
104
  return { ok: false, reason: "cwd does not exist or unreadable" };
60
105
  }
61
- const home = realpathSync(homedir());
62
- const root = realpathSync(REPO_ROOT);
63
106
  const sep = process.platform === "win32" ? "\\" : "/";
64
- if (real === home || real.startsWith(home + sep)) {
65
- return { ok: true };
107
+ const allowedRoots = [realpathSync(homedir()), realpathSync(REPO_ROOT)];
108
+ for (const extra of loadExtraRoots()) {
109
+ try {
110
+ allowedRoots.push(realpathSync(extra));
111
+ }
112
+ catch (err) {
113
+ console.error(`[harness/mcp-server] extra cwd root unresolved, skipping: ${extra} (${err?.message})`);
114
+ }
66
115
  }
67
- if (real === root || real.startsWith(root + sep)) {
68
- return { ok: true };
116
+ for (const root of allowedRoots) {
117
+ if (real === root || real.startsWith(root + sep)) {
118
+ return { ok: true };
119
+ }
69
120
  }
70
- return { ok: false, reason: "cwd escapes home and repo root" };
121
+ return { ok: false, reason: "cwd escapes home, repo root, and configured extra roots" };
71
122
  }
72
123
  export function validateMockLlmPath(value) {
73
124
  const resolved = isAbsolute(value) ? value : resolve(REPO_ROOT, value);
@@ -255,7 +306,34 @@ export function registerAsyncTools(server, getDriver, deps) {
255
306
  });
256
307
  server.registerTool("tui.last_event", {
257
308
  description: "Return the most recent event of the given kind (null if none).",
258
- inputSchema: { kind: z.enum(["toast", "stream.delta"]) },
309
+ // Full protocol event set (minus the idle sentinel) so an external agent can
310
+ // observe lifecycle events — council/sprint/route/askcard, not just toasts.
311
+ // The Driver accepts any kind; this enum is the MCP-boundary validation.
312
+ inputSchema: {
313
+ kind: z.enum([
314
+ "toast",
315
+ "stream.delta",
316
+ "llm-token",
317
+ "llm-done",
318
+ "council-step",
319
+ "council-speaker",
320
+ "council-turn-length",
321
+ "askcard-open",
322
+ "askcard-answered",
323
+ "askcard-cancel",
324
+ "sprint-stage",
325
+ "sprint-halt",
326
+ "sprint-plan-committed",
327
+ "route-decision",
328
+ "steer-inject",
329
+ "usage",
330
+ "grounding-flag",
331
+ "ee-timeout",
332
+ "ee-error",
333
+ "stream-retry",
334
+ "disconnect",
335
+ ]),
336
+ },
259
337
  }, async ({ kind }) => {
260
338
  const d = getDriver();
261
339
  if (!d)
@@ -353,7 +431,7 @@ export function createMcpHarnessServer({ spawn }) {
353
431
  }
354
432
  if (!finalArgs.includes("--agent-mode"))
355
433
  finalArgs.push("--agent-mode");
356
- const entry = process.cwd() + "/src/index.ts";
434
+ const entry = `${process.cwd()}/src/index.ts`;
357
435
  // Delegate to the injected spawn implementation — the core package has no
358
436
  // knowledge of the concrete transport (fd 3/4, named pipes, WebSocket …).
359
437
  let spawnResult;
@@ -73,6 +73,21 @@ export type LiveEvent = {
73
73
  round?: number;
74
74
  /** Correlation ID linking this speaker event to the enclosing council run. */
75
75
  correlationId: string;
76
+ } | {
77
+ t: "event";
78
+ kind: "council-turn-length";
79
+ /** Council role label (e.g. "architect"); matches council-speaker.role. */
80
+ role: string;
81
+ /** Debate round: 0 = opening statements, 1+ = discussion rounds. */
82
+ round: number;
83
+ /** Full character count of the speaker's output (trimmed; never truncated). */
84
+ charCount: number;
85
+ /** Word count of the speaker's output (whitespace-split, empties dropped). */
86
+ wordCount: number;
87
+ /** The speaker's model id, for per-model thrift attribution. */
88
+ model: string;
89
+ /** Correlation ID tying turns to the enclosing council run (= sessionId). */
90
+ correlationId: string;
76
91
  } | {
77
92
  t: "event";
78
93
  kind: "askcard-open";
@@ -1,4 +1,4 @@
1
- const FLAGS = new Set(["focus", "selected", "disabled"]);
1
+ const _FLAGS = new Set(["focus", "selected", "disabled"]);
2
2
  export function parseSelector(input) {
3
3
  // First, split by child combinator >>
4
4
  const childCombinatorRegex = /\s*>>\s*/;
@@ -146,7 +146,7 @@ function readField(node, key) {
146
146
  return node.state;
147
147
  if (key.startsWith("props.")) {
148
148
  const dot = key.slice("props.".length);
149
- return (node.props ?? {})[dot];
149
+ return node.props?.[dot];
150
150
  }
151
151
  return undefined;
152
152
  }
@@ -1,6 +1,6 @@
1
1
  const MAX_BYTES = 1024 * 1024; // 1 MiB
2
2
  function serialize(msg) {
3
- const line = JSON.stringify(msg) + "\n";
3
+ const line = `${JSON.stringify(msg)}\n`;
4
4
  if (Buffer.byteLength(line, "utf8") > MAX_BYTES) {
5
5
  throw new Error(`sidechannel message exceeds 1 MiB cap`);
6
6
  }
@@ -17,8 +17,8 @@
17
17
  */
18
18
  import { createReadStream, createWriteStream } from "node:fs";
19
19
  import { createEventFilter } from "@muonroi/agent-harness-core/event-filter";
20
- import { createIdleDetector } from "@muonroi/agent-harness-core/idle";
21
20
  import { redactEvent } from "@muonroi/agent-harness-core/event-redact";
21
+ import { createIdleDetector } from "@muonroi/agent-harness-core/idle";
22
22
  import { createLineSplitter, createSidechannelWriter } from "@muonroi/agent-harness-core/transports/sidechannel";
23
23
  import { installOpenTUIHarness } from "./install.js";
24
24
  import { createSemanticRegistry } from "./reconciler-hook.js";
@@ -41,7 +41,7 @@ export function installOpenTUIHarness(opts) {
41
41
  if (frame === null)
42
42
  return; // dedup — no change
43
43
  try {
44
- opts.transport.send(JSON.stringify(frame) + "\n");
44
+ opts.transport.send(`${JSON.stringify(frame)}\n`);
45
45
  opts.onFrame?.(frame);
46
46
  }
47
47
  catch {
@@ -3,6 +3,10 @@
3
3
  * Any module that transitively imports db.ts will resolve to this stub.
4
4
  */
5
5
  import { vi } from "vitest";
6
+ // vitest 2+ removed vi.mocked(). This shim restores it for the 93+ call sites across the
7
+ // codebase without a codemod. Safe no-op: vi.mocked(item) just returns item (typed cast).
8
+ // @ts-expect-error — vi.mocked is intentionally absent from vitest 4 types.
9
+ vi.mocked ??= ((item, _options) => item);
6
10
  // @opentui/react is a pre-bundled CJS package that requires 'react-reconciler/constants'
7
11
  // without the .js extension — this fails in the vitest ESM environment. None of the unit
8
12
  // tests exercise OpenTUI hooks directly (those are covered by E2E harness specs), so mocking
@@ -5,9 +5,11 @@
5
5
  import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
6
6
  import { tmpdir } from "node:os";
7
7
  import { join } from "node:path";
8
+ import { APICallError } from "@ai-sdk/provider";
8
9
  import { generateObject, stepCountIs, streamText, tool } from "ai";
9
10
  import { afterAll, describe, expect, it } from "vitest";
10
11
  import { z } from "zod";
12
+ import { humanizeApiError } from "../../orchestrator/error-utils.js";
11
13
  import { createMockModel, loadMockModelFromDir, textOnlyStream, toolCallStream } from "../mock-model.js";
12
14
  // biome-ignore lint/suspicious/noExplicitAny: streamText result generic is provider-specific
13
15
  async function drainStream(result) {
@@ -188,6 +190,58 @@ describe("loadMockModelFromDir", () => {
188
190
  });
189
191
  expect(object).toEqual({ ok: true, label: "built" });
190
192
  });
193
+ it("constructs an APICallError from a structured error fixture (drives the humanizeApiError 5xx branch)", async () => {
194
+ // A pure-JSON fixture cannot otherwise build an APICallError (it arrives as
195
+ // a plain string). buildFixtureError() in mock-model.ts upgrades a structured
196
+ // `error` object carrying a statusCode into a real APICallError so harness/
197
+ // unit specs can exercise the orchestrator's status-aware 5xx path.
198
+ const dir = mkFixtureDir({
199
+ provider: "mock",
200
+ modelId: "mock-gpt",
201
+ stream: [
202
+ [
203
+ { type: "stream-start", warnings: [] },
204
+ {
205
+ type: "error",
206
+ error: {
207
+ apiCallError: true,
208
+ statusCode: 500,
209
+ message: "AI_APICallError: Request failed",
210
+ responseBody: JSON.stringify({ code: 60000, message: "Request failed: Unknown error." }),
211
+ url: "https://api.siliconflow.cn/v1/chat/completions",
212
+ isRetryable: true,
213
+ },
214
+ },
215
+ {
216
+ type: "finish",
217
+ finishReason: { unified: "error", raw: null },
218
+ usage: {
219
+ inputTokens: { total: 0, noCache: 0, cacheRead: null, cacheWrite: null },
220
+ outputTokens: { total: 0, text: 0, reasoning: null },
221
+ },
222
+ },
223
+ ],
224
+ ],
225
+ });
226
+ const handle = await loadMockModelFromDir(dir);
227
+ expect(handle).not.toBeNull();
228
+ const r = streamText({ model: handle.model, prompt: "hi" });
229
+ let errPart = null;
230
+ for await (const part of r.fullStream) {
231
+ if (part.type === "error")
232
+ errPart = part;
233
+ }
234
+ expect(errPart).not.toBeNull();
235
+ expect(APICallError.isInstance(errPart?.error)).toBe(true);
236
+ expect((errPart?.error).statusCode).toBe(500);
237
+ // The 5xx branch must surface the canned actionable text + (HTTP 500) and
238
+ // SUPPRESS the opaque provider body (the SiliconFlow "Unknown error" mask).
239
+ const msg = humanizeApiError(errPart?.error);
240
+ expect(msg).toContain("HTTP 500");
241
+ expect(msg.toLowerCase()).toContain("try again later");
242
+ expect(msg).not.toContain("Unknown error");
243
+ expect(msg).not.toContain("Request failed");
244
+ });
191
245
  it("supports multi-round stream arrays from the fixture file", async () => {
192
246
  const dir = mkFixtureDir({
193
247
  provider: "mock",
@@ -22,7 +22,7 @@
22
22
  * const calls = handle.calls; // LanguageModelV3CallOptions[]
23
23
  * expect(calls[0].maxOutputTokens).toBeUndefined();
24
24
  */
25
- import type { LanguageModelV3CallOptions, LanguageModelV3StreamPart } from "@ai-sdk/provider";
25
+ import { type LanguageModelV3CallOptions, type LanguageModelV3StreamPart } from "@ai-sdk/provider";
26
26
  import { MockLanguageModelV3 } from "ai/test";
27
27
  export type StreamChunks = LanguageModelV3StreamPart[];
28
28
  export interface MockModelFixture {