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
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { stripStrayModelMacros } from "./text.js";
2
+ import { stripInvisibleChars, stripStrayModelMacros } from "./text.js";
3
3
  describe("stripStrayModelMacros", () => {
4
4
  it("strips a trailing \\confidence{NN} macro and the blank line before it", () => {
5
5
  const input = "Root cause: parseInt radix.\n\nFix: use radix 10.\n\n\\confidence{85}";
@@ -20,4 +20,28 @@ describe("stripStrayModelMacros", () => {
20
20
  expect(stripStrayModelMacros("")).toBe("");
21
21
  });
22
22
  });
23
+ describe("stripInvisibleChars", () => {
24
+ it("strips zero-width spaces (U+200B)", () => {
25
+ expect(stripInvisibleChars("hello\u200Bworld")).toBe("helloworld");
26
+ });
27
+ it("strips soft hyphen (U+00AD)", () => {
28
+ expect(stripInvisibleChars("hy\u00ADphen")).toBe("hyphen");
29
+ });
30
+ it("strips BOM (U+FEFF)", () => {
31
+ expect(stripInvisibleChars("\uFEFFHello")).toBe("Hello");
32
+ });
33
+ it("strips BiDi overrides (U+202E)", () => {
34
+ expect(stripInvisibleChars("abc\u202Edef")).toBe("abcdef");
35
+ });
36
+ it("strips C0 controls except \\t \\n \\r", () => {
37
+ expect(stripInvisibleChars("a\u0001b\rc\td")).toBe("ab\rc\td");
38
+ });
39
+ it("keeps normal text unchanged (fast path)", () => {
40
+ const normal = "Hello world, how are you?";
41
+ expect(stripInvisibleChars(normal)).toBe(normal);
42
+ });
43
+ it("handles empty string", () => {
44
+ expect(stripInvisibleChars("")).toBe("");
45
+ });
46
+ });
23
47
  //# sourceMappingURL=text.test.js.map
@@ -27,7 +27,9 @@ export function toolArgs(tc) {
27
27
  try {
28
28
  const a = JSON.parse(tc.function.arguments);
29
29
  if (tc.function.name === "bash")
30
- return String(a.command || "").replace(/\n/g, " ").trim();
30
+ return String(a.command || "")
31
+ .replace(/\n/g, " ")
32
+ .trim();
31
33
  if (tc.function.name === "read_file" || tc.function.name === "write_file" || tc.function.name === "edit_file")
32
34
  return String(a.file_path || a.path || "");
33
35
  if (describeMcpFsTool(tc.function.name)) {
@@ -14,7 +14,7 @@
14
14
  import { promises as fs } from "node:fs";
15
15
  import * as os from "node:os";
16
16
  import * as path from "node:path";
17
- import { projectCostUSD } from "./estimator.js";
17
+ import { projectCostUSD, sanitizeInputTokens } from "./estimator.js";
18
18
  function muonroiHome(homeOverride) {
19
19
  return homeOverride ?? process.env.MUONROI_CLI_HOME ?? path.join(os.homedir(), ".muonroi-cli");
20
20
  }
@@ -57,7 +57,7 @@ export async function meteredGenerate(args, fn) {
57
57
  const startedAt = Date.now();
58
58
  try {
59
59
  const { result, actualInputTokens, actualOutputTokens, cachedInputTokens, stepCount } = await fn();
60
- const inTok = actualInputTokens ?? estIn;
60
+ const inTok = sanitizeInputTokens(actualInputTokens, estIn);
61
61
  const outTok = actualOutputTokens ?? Math.max(1, Math.ceil(estOut / 4));
62
62
  const usd = projectCostUSD(args.provider, args.model, inTok, outTok);
63
63
  await appendCostLog({
@@ -10,6 +10,20 @@
10
10
  * Acceptable for cap projection only -- not for billing reconciliation.
11
11
  */
12
12
  export declare function estimateTokensFromChars(chars: number): number;
13
+ /**
14
+ * Sanitize an actual input-token count by comparing it to the estimated
15
+ * (chars/4) count. Some providers (SiliconFlow, possibly others) return
16
+ * implausibly low `prompt_tokens` (e.g. 10) regardless of actual prompt size,
17
+ * which under-reports cost and inflates apparent cache-hit ratios.
18
+ *
19
+ * Rules:
20
+ * - When `actual === undefined`: return `estimated` (no data = use estimate).
21
+ * - When `actual === 0`: return 0 (preserve mock / failed-call semantics).
22
+ * - When `estimated > 0` and `actual < estimated * 0.1`: the value is likely
23
+ * bogus — return `estimated` so cost projections stay accurate.
24
+ * - Otherwise: return `actual` as-is.
25
+ */
26
+ export declare function sanitizeInputTokens(actual: number | undefined, estimated: number): number;
13
27
  /**
14
28
  * Project the USD cost of a request given token estimates and the static pricing table.
15
29
  * Returns 0 for unknown provider/model -- caller decides how to handle (ledger treats as 0 risk).
@@ -13,6 +13,28 @@ import { lookupPricing } from "../providers/pricing.js";
13
13
  export function estimateTokensFromChars(chars) {
14
14
  return Math.ceil(chars / 4);
15
15
  }
16
+ /**
17
+ * Sanitize an actual input-token count by comparing it to the estimated
18
+ * (chars/4) count. Some providers (SiliconFlow, possibly others) return
19
+ * implausibly low `prompt_tokens` (e.g. 10) regardless of actual prompt size,
20
+ * which under-reports cost and inflates apparent cache-hit ratios.
21
+ *
22
+ * Rules:
23
+ * - When `actual === undefined`: return `estimated` (no data = use estimate).
24
+ * - When `actual === 0`: return 0 (preserve mock / failed-call semantics).
25
+ * - When `estimated > 0` and `actual < estimated * 0.1`: the value is likely
26
+ * bogus — return `estimated` so cost projections stay accurate.
27
+ * - Otherwise: return `actual` as-is.
28
+ */
29
+ export function sanitizeInputTokens(actual, estimated) {
30
+ if (actual === undefined)
31
+ return estimated;
32
+ if (actual === 0)
33
+ return 0;
34
+ if (estimated > 0 && actual < estimated * 0.1)
35
+ return estimated;
36
+ return actual;
37
+ }
16
38
  /**
17
39
  * Project the USD cost of a request given token estimates and the static pricing table.
18
40
  * Returns 0 for unknown provider/model -- caller decides how to handle (ledger treats as 0 risk).
@@ -0,0 +1,21 @@
1
+ /**
2
+ * src/utils/__tests__/compaction-caps.test.ts
3
+ *
4
+ * G4 regression guard for the token-thrift compaction caps.
5
+ *
6
+ * The compaction caps themselves are wired correctly (cost-leak-b3/b4 specs
7
+ * cover the algorithm; the *-tui specs cover the live wiring). The hole this
8
+ * file closes is DRIFT: nothing pinned the PRODUCTION DEFAULT knob values, and
9
+ * the fast cost-leak specs drive `compactSubAgentMessages` with HARDCODED
10
+ * options (thresholdChars: 60_000, keepLastTurns: 1) that are decoupled from
11
+ * the real getters. So a regression that widens a default (e.g. reverting the
12
+ * Phase C5 40_000 back to 200_000, or fat-fingering a clamp range) would sail
13
+ * past the entire fast suite and only be caught by the slow ~30-120s TUI specs.
14
+ *
15
+ * Part 1 pins each getter's documented contract (default / clamp / honor).
16
+ * Part 2 feeds the REAL getter outputs into the compactor over a synthetic
17
+ * load sized BETWEEN the default threshold and a plausible widened drift, so a
18
+ * default-value regression fails behaviourally too — not just at the value
19
+ * assertion.
20
+ */
21
+ export {};
@@ -0,0 +1,187 @@
1
+ /**
2
+ * src/utils/__tests__/compaction-caps.test.ts
3
+ *
4
+ * G4 regression guard for the token-thrift compaction caps.
5
+ *
6
+ * The compaction caps themselves are wired correctly (cost-leak-b3/b4 specs
7
+ * cover the algorithm; the *-tui specs cover the live wiring). The hole this
8
+ * file closes is DRIFT: nothing pinned the PRODUCTION DEFAULT knob values, and
9
+ * the fast cost-leak specs drive `compactSubAgentMessages` with HARDCODED
10
+ * options (thresholdChars: 60_000, keepLastTurns: 1) that are decoupled from
11
+ * the real getters. So a regression that widens a default (e.g. reverting the
12
+ * Phase C5 40_000 back to 200_000, or fat-fingering a clamp range) would sail
13
+ * past the entire fast suite and only be caught by the slow ~30-120s TUI specs.
14
+ *
15
+ * Part 1 pins each getter's documented contract (default / clamp / honor).
16
+ * Part 2 feeds the REAL getter outputs into the compactor over a synthetic
17
+ * load sized BETWEEN the default threshold and a plausible widened drift, so a
18
+ * default-value regression fails behaviourally too — not just at the value
19
+ * assertion.
20
+ */
21
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
22
+ import { compactSubAgentMessages, cumulativeMessageChars } from "../../orchestrator/subagent-compactor.js";
23
+ import { getSubAgentBudgetChars, getSubAgentCompactKeepLast, getSubAgentCompactThresholdChars, getTopLevelCompactKeepLast, getTopLevelCompactThresholdChars, getTopLevelToolBudgetChars, loadUserSettings, } from "../settings.js";
24
+ const ENV_KEYS = [
25
+ "MUONROI_SUB_AGENT_BUDGET_CHARS",
26
+ "MUONROI_SUBAGENT_COMPACT_THRESHOLD_CHARS",
27
+ "MUONROI_SUBAGENT_COMPACT_KEEP_LAST",
28
+ "MUONROI_TOP_LEVEL_COMPACT_THRESHOLD_CHARS",
29
+ "MUONROI_TOP_LEVEL_COMPACT_KEEP_LAST",
30
+ "MUONROI_TOP_LEVEL_TOOL_BUDGET_CHARS",
31
+ ];
32
+ const saved = {};
33
+ beforeEach(() => {
34
+ for (const k of ENV_KEYS) {
35
+ saved[k] = process.env[k];
36
+ delete process.env[k];
37
+ }
38
+ });
39
+ afterEach(() => {
40
+ for (const k of ENV_KEYS) {
41
+ if (saved[k] === undefined)
42
+ delete process.env[k];
43
+ else
44
+ process.env[k] = saved[k];
45
+ }
46
+ });
47
+ describe("compaction cap getters — production contract (G4 drift guard)", () => {
48
+ describe("compact thresholds & keepLast (Phase C5 defaults — no user-settings layer)", () => {
49
+ it("return the documented defaults when env is unset", () => {
50
+ expect(getSubAgentCompactThresholdChars()).toBe(40_000);
51
+ expect(getSubAgentCompactKeepLast()).toBe(3);
52
+ expect(getTopLevelCompactThresholdChars()).toBe(100_000);
53
+ expect(getTopLevelCompactKeepLast()).toBe(5);
54
+ });
55
+ it("clamp out-of-range env back to the default", () => {
56
+ process.env.MUONROI_SUBAGENT_COMPACT_THRESHOLD_CHARS = "999999999"; // > 500_000
57
+ expect(getSubAgentCompactThresholdChars()).toBe(40_000);
58
+ process.env.MUONROI_SUBAGENT_COMPACT_THRESHOLD_CHARS = "1000"; // < 20_000
59
+ expect(getSubAgentCompactThresholdChars()).toBe(40_000);
60
+ process.env.MUONROI_SUBAGENT_COMPACT_KEEP_LAST = "0"; // < 1
61
+ expect(getSubAgentCompactKeepLast()).toBe(3);
62
+ process.env.MUONROI_SUBAGENT_COMPACT_KEEP_LAST = "99"; // > 20
63
+ expect(getSubAgentCompactKeepLast()).toBe(3);
64
+ process.env.MUONROI_TOP_LEVEL_COMPACT_THRESHOLD_CHARS = "10000"; // < 50_000
65
+ expect(getTopLevelCompactThresholdChars()).toBe(100_000);
66
+ process.env.MUONROI_TOP_LEVEL_COMPACT_KEEP_LAST = "99"; // > 30
67
+ expect(getTopLevelCompactKeepLast()).toBe(5);
68
+ });
69
+ it("honor valid in-range env overrides", () => {
70
+ process.env.MUONROI_SUBAGENT_COMPACT_THRESHOLD_CHARS = "60000";
71
+ expect(getSubAgentCompactThresholdChars()).toBe(60_000);
72
+ process.env.MUONROI_SUBAGENT_COMPACT_KEEP_LAST = "5";
73
+ expect(getSubAgentCompactKeepLast()).toBe(5);
74
+ process.env.MUONROI_TOP_LEVEL_COMPACT_THRESHOLD_CHARS = "250000";
75
+ expect(getTopLevelCompactThresholdChars()).toBe(250_000);
76
+ process.env.MUONROI_TOP_LEVEL_COMPACT_KEEP_LAST = "8";
77
+ expect(getTopLevelCompactKeepLast()).toBe(8);
78
+ });
79
+ });
80
+ describe("budget getters (env override → user-settings → default)", () => {
81
+ it("getSubAgentBudgetChars: default 240_000 when env unset and no user override", () => {
82
+ // Deterministic on a clean checkout / CI (no user-settings.json). When a
83
+ // dev machine HAS an explicit override the getter must honor it instead —
84
+ // the default-drift case is what CI guards.
85
+ const override = loadUserSettings().subAgentBudgetChars;
86
+ if (typeof override === "number" && override >= 20_000 && override <= 5_000_000) {
87
+ expect(getSubAgentBudgetChars()).toBe(Math.floor(override));
88
+ }
89
+ else {
90
+ expect(getSubAgentBudgetChars()).toBe(240_000);
91
+ }
92
+ });
93
+ it("getTopLevelToolBudgetChars: default 400_000 when env unset and no user override", () => {
94
+ const override = loadUserSettings().topLevelToolBudgetChars;
95
+ if (typeof override === "number" && override >= 50_000 && override <= 10_000_000) {
96
+ expect(getTopLevelToolBudgetChars()).toBe(Math.floor(override));
97
+ }
98
+ else {
99
+ expect(getTopLevelToolBudgetChars()).toBe(400_000);
100
+ }
101
+ });
102
+ it("honor valid in-range env overrides (env short-circuits user-settings)", () => {
103
+ process.env.MUONROI_SUB_AGENT_BUDGET_CHARS = "300000";
104
+ expect(getSubAgentBudgetChars()).toBe(300_000);
105
+ process.env.MUONROI_TOP_LEVEL_TOOL_BUDGET_CHARS = "800000";
106
+ expect(getTopLevelToolBudgetChars()).toBe(800_000);
107
+ });
108
+ it("reject out-of-range env (result stays within the documented range)", () => {
109
+ process.env.MUONROI_SUB_AGENT_BUDGET_CHARS = "999999999"; // > 5_000_000
110
+ const sub = getSubAgentBudgetChars();
111
+ expect(sub).not.toBe(999_999_999);
112
+ expect(sub).toBeGreaterThanOrEqual(20_000);
113
+ expect(sub).toBeLessThanOrEqual(5_000_000);
114
+ process.env.MUONROI_TOP_LEVEL_TOOL_BUDGET_CHARS = "1000"; // < 50_000
115
+ const top = getTopLevelToolBudgetChars();
116
+ expect(top).not.toBe(1000);
117
+ expect(top).toBeGreaterThanOrEqual(50_000);
118
+ expect(top).toBeLessThanOrEqual(10_000_000);
119
+ });
120
+ });
121
+ });
122
+ /**
123
+ * Build N tool turns (assistant tool-call + tool result), each result
124
+ * `resultChars` long, using a tool name that is NOT high-value (so the
125
+ * compactor is allowed to elide it). Mirrors the cost-leak-b3 message shape.
126
+ */
127
+ function buildToolTurns(n, resultChars) {
128
+ const msgs = [
129
+ { role: "system", content: "You are the Explore sub-agent." },
130
+ { role: "user", content: "trace auth wiring" },
131
+ ];
132
+ for (let i = 0; i < n; i++) {
133
+ const id = `c${i}`;
134
+ msgs.push({
135
+ role: "assistant",
136
+ content: [{ type: "tool-call", toolCallId: id, toolName: "fake_read", input: { path: `/tmp/${id}.txt` } }],
137
+ });
138
+ msgs.push({
139
+ role: "tool",
140
+ content: [
141
+ {
142
+ type: "tool-result",
143
+ toolCallId: id,
144
+ toolName: "fake_read",
145
+ output: { type: "text", value: "x".repeat(resultChars) },
146
+ },
147
+ ],
148
+ });
149
+ }
150
+ return msgs;
151
+ }
152
+ describe("compaction fires with the REAL default getter values (G4 behavioural guard)", () => {
153
+ // Load sized BETWEEN the default thresholds (40K sub / 100K top) and a
154
+ // plausible widened drift (200K). So: with the real defaults it compacts;
155
+ // if a default were reverted to 200_000 the load would fall below threshold
156
+ // and these assertions would fail — catching drift behaviourally.
157
+ it("sub-agent: default threshold (40K) elides older tool results on a ~120K load", () => {
158
+ const messages = buildToolTurns(6, 20_000); // ~120K cumulative, 6 tool turns
159
+ const cumulative = cumulativeMessageChars(messages);
160
+ expect(cumulative).toBeGreaterThan(40_000);
161
+ expect(cumulative).toBeLessThan(200_000);
162
+ const compacted = compactSubAgentMessages(messages, {
163
+ thresholdChars: getSubAgentCompactThresholdChars(),
164
+ keepLastTurns: getSubAgentCompactKeepLast(),
165
+ });
166
+ expect(compacted).not.toBe(messages); // identity differs → compaction happened
167
+ const before = JSON.stringify(messages).length;
168
+ const after = JSON.stringify(compacted).length;
169
+ expect(after).toBeLessThan(before);
170
+ expect(JSON.stringify(compacted)).toContain("elided by sub-agent compactor");
171
+ });
172
+ it("top-level: default threshold (100K) elides older tool results on a ~160K load", () => {
173
+ const messages = buildToolTurns(8, 20_000); // ~160K cumulative, 8 tool turns
174
+ const cumulative = cumulativeMessageChars(messages);
175
+ expect(cumulative).toBeGreaterThan(100_000);
176
+ expect(cumulative).toBeLessThan(200_000);
177
+ const compacted = compactSubAgentMessages(messages, {
178
+ thresholdChars: getTopLevelCompactThresholdChars(),
179
+ keepLastTurns: getTopLevelCompactKeepLast(),
180
+ label: "top-level",
181
+ });
182
+ expect(compacted).not.toBe(messages);
183
+ expect(JSON.stringify(compacted).length).toBeLessThan(JSON.stringify(messages).length);
184
+ expect(JSON.stringify(compacted)).toContain("elided by top-level compactor");
185
+ });
186
+ });
187
+ //# sourceMappingURL=compaction-caps.test.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * withDeadlineRace abort-grace: a provider that ignores its abortSignal mid-call
3
+ * must not keep the caller blocked until the (minutes-long) wall-clock deadline.
4
+ * When the user-abort signal fires, the race rejects within a short grace window.
5
+ */
6
+ import { describe, expect, it } from "vitest";
7
+ import { withDeadlineRace } from "../llm-deadline.js";
8
+ describe("withDeadlineRace — abort grace", () => {
9
+ it("rejects shortly after abort even when fn() never settles", async () => {
10
+ const ac = new AbortController();
11
+ const neverSettles = () => new Promise(() => { }); // simulates a stalled provider
12
+ const start = Date.now();
13
+ setTimeout(() => ac.abort(new Error("user pressed Esc")), 40);
14
+ await expect(withDeadlineRace(neverSettles, 60_000, "test", ac.signal, 80)).rejects.toThrow(/aborted by user/);
15
+ // Must unblock far before the 60s deadline.
16
+ expect(Date.now() - start).toBeLessThan(2_000);
17
+ });
18
+ it("rejects immediately when the signal is already aborted", async () => {
19
+ const ac = new AbortController();
20
+ ac.abort(new Error("already cancelled"));
21
+ const neverSettles = () => new Promise(() => { });
22
+ await expect(withDeadlineRace(neverSettles, 60_000, "test", ac.signal, 50)).rejects.toThrow(/aborted by user/);
23
+ });
24
+ it("returns the fn() result when it settles before any abort", async () => {
25
+ const ac = new AbortController();
26
+ await expect(withDeadlineRace(() => Promise.resolve("ok"), 60_000, "test", ac.signal, 80)).resolves.toBe("ok");
27
+ });
28
+ it("still enforces the wall-clock deadline when no signal is passed (back-compat)", async () => {
29
+ const neverSettles = () => new Promise(() => { });
30
+ await expect(withDeadlineRace(neverSettles, 60, "test")).rejects.toThrow(/deadline/);
31
+ });
32
+ });
33
+ //# sourceMappingURL=llm-deadline-abort.test.js.map
@@ -1,11 +1,23 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
2
  import { withDeadlineRace, withTimeoutSignal } from "../llm-deadline.js";
3
+ async function advanceTimersAsync(ms) {
4
+ if (typeof vi.advanceTimersByTimeAsync === "function") {
5
+ await vi.advanceTimersByTimeAsync(ms);
6
+ }
7
+ else {
8
+ vi.advanceTimersByTime(ms);
9
+ // Flush microtasks in Bun/older Node
10
+ for (let i = 0; i < 20; i++) {
11
+ await Promise.resolve();
12
+ }
13
+ }
14
+ }
3
15
  describe("withDeadlineRace", () => {
4
16
  beforeEach(() => vi.useFakeTimers());
5
17
  afterEach(() => vi.useRealTimers());
6
18
  it("resolves with the value when fn settles before the deadline", async () => {
7
19
  const p = withDeadlineRace(async () => "ok", 1000, "test-call");
8
- await vi.advanceTimersByTimeAsync(10);
20
+ await advanceTimersAsync(10);
9
21
  await expect(p).resolves.toBe("ok");
10
22
  });
11
23
  it("rejects with a labelled timeout error when fn exceeds the deadline", async () => {
@@ -14,7 +26,7 @@ describe("withDeadlineRace", () => {
14
26
  // of whether the underlying SDK honours its abort signal.
15
27
  const p = withDeadlineRace(() => new Promise(() => { }), 500, "plan_debate");
16
28
  const assertion = expect(p).rejects.toThrow(/plan_debate exceeded 500ms deadline/);
17
- await vi.advanceTimersByTimeAsync(600);
29
+ await advanceTimersAsync(600);
18
30
  await assertion;
19
31
  });
20
32
  });
@@ -29,7 +41,7 @@ describe("withTimeoutSignal", () => {
29
41
  it("aborts the signal once the timeout elapses", async () => {
30
42
  const { signal } = withTimeoutSignal(undefined, 200);
31
43
  expect(signal.aborted).toBe(false);
32
- await vi.advanceTimersByTimeAsync(250);
44
+ await advanceTimersAsync(250);
33
45
  expect(signal.aborted).toBe(true);
34
46
  });
35
47
  it("aborts immediately when the parent signal is already aborted", () => {
@@ -49,7 +61,7 @@ describe("withTimeoutSignal", () => {
49
61
  it("cleanup clears the timer so the signal never aborts afterwards", async () => {
50
62
  const { signal, cleanup } = withTimeoutSignal(undefined, 200);
51
63
  cleanup();
52
- await vi.advanceTimersByTimeAsync(500);
64
+ await advanceTimersAsync(500);
53
65
  expect(signal.aborted).toBe(false);
54
66
  });
55
67
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,115 @@
1
+ import * as fs from "fs";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { logger, redactObject, redactSecrets } from "../logger.js";
4
+ vi.mock("fs", () => ({
5
+ appendFileSync: vi.fn(),
6
+ existsSync: vi.fn(() => true),
7
+ mkdirSync: vi.fn(),
8
+ }));
9
+ function setTuiActive(active) {
10
+ globalThis.__muonroiTuiActive = active;
11
+ }
12
+ function clearTuiActive() {
13
+ delete globalThis.__muonroiTuiActive;
14
+ }
15
+ describe("logger utility", () => {
16
+ let logSpy;
17
+ let warnSpy;
18
+ let errorSpy;
19
+ beforeEach(() => {
20
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => undefined);
21
+ warnSpy = vi.spyOn(console, "warn").mockImplementation(() => undefined);
22
+ errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
23
+ vi.mocked(fs.appendFileSync).mockClear();
24
+ vi.mocked(fs.existsSync).mockClear();
25
+ vi.mocked(fs.existsSync).mockReturnValue(true);
26
+ vi.mocked(fs.mkdirSync).mockClear();
27
+ clearTuiActive();
28
+ });
29
+ afterEach(() => {
30
+ logSpy.mockRestore();
31
+ warnSpy.mockRestore();
32
+ errorSpy.mockRestore();
33
+ clearTuiActive();
34
+ });
35
+ describe("redactSecrets", () => {
36
+ it("redacts openai keys", () => {
37
+ const msg = "sending request with key sk-proj12345678901234567890123456";
38
+ expect(redactSecrets(msg)).toBe("sending request with key [REDACTED_API_KEY]");
39
+ });
40
+ it("redacts google keys", () => {
41
+ const msg = "sending request with key AIzaSyA1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q";
42
+ expect(redactSecrets(msg)).toBe("sending request with key [REDACTED_API_KEY]");
43
+ });
44
+ it("redacts xai keys", () => {
45
+ const msg = "sending request with key xai-proj12345678901234567890123456";
46
+ expect(redactSecrets(msg)).toBe("sending request with key [REDACTED_API_KEY]");
47
+ });
48
+ });
49
+ describe("redactObject", () => {
50
+ it("redacts sensitive fields in an object recursively", () => {
51
+ const raw = {
52
+ name: "test",
53
+ apiKey: "sk-proj1234567890",
54
+ nested: {
55
+ secretToken: "some-secret",
56
+ plainVal: "hello",
57
+ },
58
+ arr: ["item1", "sk-proj12345678901234567890123456"],
59
+ };
60
+ const expected = {
61
+ name: "test",
62
+ apiKey: "[REDACTED]",
63
+ nested: {
64
+ secretToken: "[REDACTED]",
65
+ plainVal: "hello",
66
+ },
67
+ arr: ["item1", "[REDACTED_API_KEY]"],
68
+ };
69
+ expect(redactObject(raw)).toEqual(expected);
70
+ });
71
+ });
72
+ describe("logger functionality in CLI mode", () => {
73
+ it("logs info messages to console.log", () => {
74
+ logger.info("cli", "test message", { val: 42 });
75
+ expect(logSpy).toHaveBeenCalledTimes(1);
76
+ const callArg = logSpy.mock.calls[0][0];
77
+ expect(callArg).toContain("[INFO]");
78
+ expect(callArg).toContain("[CLI]");
79
+ expect(callArg).toContain("test message");
80
+ expect(callArg).toContain('{"val":42}');
81
+ });
82
+ it("logs warn messages to console.warn", () => {
83
+ logger.warn("orchestrator", "warning message");
84
+ expect(warnSpy).toHaveBeenCalledTimes(1);
85
+ const callArg = warnSpy.mock.calls[0][0];
86
+ expect(callArg).toContain("[WARN]");
87
+ expect(callArg).toContain("[ORCHESTRATOR]");
88
+ expect(callArg).toContain("warning message");
89
+ });
90
+ it("logs error messages to console.error", () => {
91
+ logger.error("storage", "error message");
92
+ expect(errorSpy).toHaveBeenCalledTimes(1);
93
+ const callArg = errorSpy.mock.calls[0][0];
94
+ expect(callArg).toContain("[ERROR]");
95
+ expect(callArg).toContain("[STORAGE]");
96
+ expect(callArg).toContain("error message");
97
+ });
98
+ });
99
+ describe("logger functionality in TUI mode", () => {
100
+ it("does not log to console, but appends to debug.log", () => {
101
+ setTuiActive(true);
102
+ logger.info("ui", "render component", { id: "main" });
103
+ expect(logSpy).not.toHaveBeenCalled();
104
+ expect(fs.appendFileSync).toHaveBeenCalledTimes(1);
105
+ const filePath = vi.mocked(fs.appendFileSync).mock.calls[0][0];
106
+ const logContent = vi.mocked(fs.appendFileSync).mock.calls[0][1];
107
+ expect(filePath).toContain("debug.log");
108
+ expect(logContent).toContain("[INFO]");
109
+ expect(logContent).toContain("[UI]");
110
+ expect(logContent).toContain("render component");
111
+ expect(logContent).toContain('{"id":"main"}');
112
+ });
113
+ });
114
+ });
115
+ //# sourceMappingURL=logger.test.js.map
@@ -0,0 +1,48 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+ describe("UserSettings.agentFirst", () => {
6
+ const tmpHome = path.join(os.tmpdir(), `muonroi-cli-agentfirst-${process.pid}-${Date.now()}`);
7
+ const origHome = process.env.HOME;
8
+ const origUserProfile = process.env.USERPROFILE;
9
+ let saveUserSettings;
10
+ let loadUserSettings;
11
+ beforeEach(async () => {
12
+ fs.mkdirSync(tmpHome, { recursive: true });
13
+ process.env.HOME = tmpHome;
14
+ process.env.USERPROFILE = tmpHome;
15
+ vi.resetModules();
16
+ const mod = await import("../settings.js");
17
+ saveUserSettings = mod.saveUserSettings;
18
+ loadUserSettings = mod.loadUserSettings;
19
+ const settingsPath = path.join(tmpHome, ".muonroi-cli", "user-settings.json");
20
+ if (fs.existsSync(settingsPath))
21
+ fs.rmSync(settingsPath, { force: true });
22
+ });
23
+ afterEach(() => {
24
+ fs.rmSync(tmpHome, { recursive: true, force: true, maxRetries: 10, retryDelay: 50 });
25
+ if (origHome === undefined)
26
+ delete process.env.HOME;
27
+ else
28
+ process.env.HOME = origHome;
29
+ if (origUserProfile === undefined)
30
+ delete process.env.USERPROFILE;
31
+ else
32
+ process.env.USERPROFILE = origUserProfile;
33
+ });
34
+ it("persists agentFirst, maxToolRounds, hardMaxToolRounds, maxLlmCallsPerTurn", () => {
35
+ saveUserSettings({
36
+ agentFirst: true,
37
+ maxToolRounds: 150,
38
+ hardMaxToolRounds: 250,
39
+ maxLlmCallsPerTurn: 80,
40
+ });
41
+ const loaded = loadUserSettings();
42
+ expect(loaded.agentFirst).toBe(true);
43
+ expect(loaded.maxToolRounds).toBe(150);
44
+ expect(loaded.hardMaxToolRounds).toBe(250);
45
+ expect(loaded.maxLlmCallsPerTurn).toBe(80);
46
+ });
47
+ });
48
+ //# sourceMappingURL=settings-agent-first.test.js.map
@@ -49,6 +49,7 @@ export declare function getReleaseTargetForPlatform(platform?: NodeJS.Platform,
49
49
  export declare function loadScriptInstallMetadata(homeDir?: string): ScriptInstallMetadata | null;
50
50
  export declare function saveScriptInstallMetadata(metadata: ScriptInstallMetadata, homeDir?: string): void;
51
51
  export declare function getScriptInstallContext(homeDir?: string): ScriptInstallContext | null;
52
+ export declare function fetchLatestGitTag(gitDir: string): Promise<string | null>;
52
53
  export declare function fetchLatestReleaseVersion(): Promise<string | null>;
53
54
  export declare function parseChecksumsFile(contents: string): Map<string, string>;
54
55
  /**