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
@@ -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 {
@@ -22,6 +22,7 @@
22
22
  * const calls = handle.calls; // LanguageModelV3CallOptions[]
23
23
  * expect(calls[0].maxOutputTokens).toBeUndefined();
24
24
  */
25
+ import { APICallError } from "@ai-sdk/provider";
25
26
  import { simulateReadableStream } from "ai";
26
27
  import { MockLanguageModelV3 } from "ai/test";
27
28
  function isNestedArray(v) {
@@ -71,7 +72,7 @@ export function createMockModel(fx) {
71
72
  const text = generates.length > 0 ? generates[Math.min(genIdx, generates.length - 1)] : "{}";
72
73
  genIdx += 1;
73
74
  if (process.env.MUONROI_DEBUG_MOCK_MODEL === "1") {
74
- process.stderr.write(`[mock-model] doGenerate #${genIdx} → ${text.length} chars\n`);
75
+ process.stderr.write(`[mock-model] doGenerate #${genIdx} → ${text.length} chars\nCALLER:\n${new Error().stack}\n`);
75
76
  }
76
77
  return {
77
78
  content: [{ type: "text", text }],
@@ -198,14 +199,150 @@ export function toolCallStream(opts) {
198
199
  * string payloads here so the rest of the pipeline sees a uniform shape.
199
200
  */
200
201
  function normalizeStreamChunks(chunks) {
201
- return chunks.map((c) => {
202
- if (c && c.type === "error") {
203
- const raw = c.error;
204
- const err = raw instanceof Error ? raw : new Error(typeof raw === "string" ? raw : "mock LLM error");
205
- return { ...c, error: err };
202
+ const normalized = chunks.map((c) => {
203
+ if (!c)
204
+ return c;
205
+ const part = c;
206
+ if (part.type === "error") {
207
+ const raw = part.error;
208
+ return { ...part, error: buildFixtureError(raw) };
209
+ }
210
+ if (part.type === "text-delta") {
211
+ const delta = part.delta ?? part.textDelta ?? part.text ?? "";
212
+ const id = part.id ?? "txt-0";
213
+ return { ...part, id, delta };
214
+ }
215
+ if (part.type === "reasoning-delta") {
216
+ const delta = part.delta ?? part.textDelta ?? part.text ?? "";
217
+ const id = part.id ?? "reasoning-0";
218
+ return { ...part, id, delta };
206
219
  }
207
220
  return c;
208
221
  });
222
+ const result = [];
223
+ const textIds = new Set();
224
+ const reasoningIds = new Set();
225
+ for (const part of normalized) {
226
+ if (!part)
227
+ continue;
228
+ const p = part;
229
+ if (p.type === "text-delta") {
230
+ textIds.add(p.id ?? "txt-0");
231
+ }
232
+ else if (p.type === "reasoning-delta") {
233
+ reasoningIds.add(p.id ?? "reasoning-0");
234
+ }
235
+ }
236
+ const seenTextStart = new Set();
237
+ const seenTextEnd = new Set();
238
+ const seenReasoningStart = new Set();
239
+ const seenReasoningEnd = new Set();
240
+ for (const part of normalized) {
241
+ if (!part)
242
+ continue;
243
+ const p = part;
244
+ if (p.type === "text-start") {
245
+ seenTextStart.add(p.id);
246
+ }
247
+ else if (p.type === "text-end") {
248
+ seenTextEnd.add(p.id);
249
+ }
250
+ else if (p.type === "reasoning-start") {
251
+ seenReasoningStart.add(p.id);
252
+ }
253
+ else if (p.type === "reasoning-end") {
254
+ seenReasoningEnd.add(p.id);
255
+ }
256
+ }
257
+ const firstTextDeltaIdx = new Map();
258
+ const lastTextDeltaIdx = new Map();
259
+ const firstReasoningDeltaIdx = new Map();
260
+ const lastReasoningDeltaIdx = new Map();
261
+ for (let i = 0; i < normalized.length; i++) {
262
+ const p = normalized[i];
263
+ if (!p)
264
+ continue;
265
+ if (p.type === "text-delta") {
266
+ const id = p.id ?? "txt-0";
267
+ if (!firstTextDeltaIdx.has(id))
268
+ firstTextDeltaIdx.set(id, i);
269
+ lastTextDeltaIdx.set(id, i);
270
+ }
271
+ else if (p.type === "reasoning-delta") {
272
+ const id = p.id ?? "reasoning-0";
273
+ if (!firstReasoningDeltaIdx.has(id))
274
+ firstReasoningDeltaIdx.set(id, i);
275
+ lastReasoningDeltaIdx.set(id, i);
276
+ }
277
+ }
278
+ for (let i = 0; i < normalized.length; i++) {
279
+ const p = normalized[i];
280
+ if (!p) {
281
+ result.push(p);
282
+ continue;
283
+ }
284
+ if (p.type === "text-delta") {
285
+ const id = p.id ?? "txt-0";
286
+ if (firstTextDeltaIdx.get(id) === i && !seenTextStart.has(id)) {
287
+ result.push({ type: "text-start", id });
288
+ }
289
+ }
290
+ else if (p.type === "reasoning-delta") {
291
+ const id = p.id ?? "reasoning-0";
292
+ if (firstReasoningDeltaIdx.get(id) === i && !seenReasoningStart.has(id)) {
293
+ result.push({ type: "reasoning-start", id });
294
+ }
295
+ }
296
+ if (p.type === "finish") {
297
+ const usage = p.usage ?? buildUsage(10, 10);
298
+ result.push({ ...p, usage });
299
+ }
300
+ else {
301
+ result.push(p);
302
+ }
303
+ if (p.type === "text-delta") {
304
+ const id = p.id ?? "txt-0";
305
+ if (lastTextDeltaIdx.get(id) === i && !seenTextEnd.has(id)) {
306
+ result.push({ type: "text-end", id });
307
+ }
308
+ }
309
+ else if (p.type === "reasoning-delta") {
310
+ const id = p.id ?? "reasoning-0";
311
+ if (lastReasoningDeltaIdx.get(id) === i && !seenReasoningEnd.has(id)) {
312
+ result.push({ type: "reasoning-end", id });
313
+ }
314
+ }
315
+ }
316
+ return result;
317
+ }
318
+ /**
319
+ * Turn a fixture `error` payload into the right runtime shape:
320
+ * - an `APICallError` when the payload is an object carrying a `statusCode`
321
+ * (or `apiCallError: true`), so harness specs can exercise the orchestrator's
322
+ * status-aware paths (humanizeApiError 5xx canned text, summarizeApiErrorForLog
323
+ * forensics, retry classifier). `APICallError.isInstance` duck-types on a
324
+ * shared marker, so constructing it here satisfies the `isInstance` checks in
325
+ * error-utils.ts. A pure JSON fixture cannot otherwise build one (it would
326
+ * arrive as a plain string).
327
+ * - a plain `Error` for string payloads (the common transient-message case).
328
+ */
329
+ function buildFixtureError(raw) {
330
+ if (raw instanceof Error)
331
+ return raw;
332
+ if (raw && typeof raw === "object") {
333
+ const o = raw;
334
+ if (o.apiCallError === true || typeof o.statusCode === "number") {
335
+ return new APICallError({
336
+ message: o.message ?? `mock API error${o.statusCode ? ` (HTTP ${o.statusCode})` : ""}`,
337
+ url: o.url ?? "https://mock.invalid/v1/chat/completions",
338
+ requestBodyValues: {},
339
+ statusCode: o.statusCode,
340
+ responseBody: o.responseBody,
341
+ isRetryable: o.isRetryable,
342
+ });
343
+ }
344
+ }
345
+ return new Error(typeof raw === "string" ? raw : "mock LLM error");
209
346
  }
210
347
  export async function loadMockModelFromDir(dir) {
211
348
  // Diagnostic logging path. Without these messages, every failure mode
@@ -131,7 +131,9 @@ async function spawnWindows(args, opts) {
131
131
  await waitForHandshake(outSocket, timeoutMs);
132
132
  }
133
133
  catch (err) {
134
- proc.kill();
134
+ if (proc && typeof proc.kill === "function") {
135
+ proc.kill();
136
+ }
135
137
  inServer.close();
136
138
  outServer.close();
137
139
  throw err;
@@ -86,7 +86,15 @@ export async function deleteChatSecret(id) {
86
86
  const kt = await loadKeytar();
87
87
  if (!kt?.deletePassword)
88
88
  return false;
89
- return kt.deletePassword(KEYCHAIN_SERVICE, ACCOUNT_BY_CHAT[id]);
89
+ try {
90
+ return await kt.deletePassword(KEYCHAIN_SERVICE, ACCOUNT_BY_CHAT[id]);
91
+ }
92
+ catch (err) {
93
+ if (process.env.DEBUG || process.env.MUONROI_DEBUG_KEYCHAIN) {
94
+ console.error(`[chat-keychain] deletePassword backend error for ${id}:`, err?.message || err);
95
+ }
96
+ return false;
97
+ }
90
98
  }
91
99
  /**
92
100
  * List all stored chat secret IDs.
@@ -11,6 +11,7 @@
11
11
  import { getProviderCapabilities } from "../providers/capabilities.js";
12
12
  import { detectProviderForModel } from "../providers/runtime.js";
13
13
  import { getDatabase } from "../storage/db.js";
14
+ import { getSessionChain } from "../storage/index.js";
14
15
  import { selectSessionExperience } from "../storage/session-experience-store.js";
15
16
  function resolveSessionId(prefix) {
16
17
  const rows = getDatabase()
@@ -42,24 +43,26 @@ export function collectCostForensics(sessionId) {
42
43
  const session = db.prepare(`SELECT id, model, created_at FROM sessions WHERE id = ?`).get(sessionId);
43
44
  if (!session)
44
45
  throw new Error(`Session not found: ${sessionId}`);
46
+ const chain = getSessionChain(sessionId);
47
+ const placeholders = chain.map(() => "?").join(",");
45
48
  const rows = db
46
49
  .prepare(`
47
50
  SELECT id, source, model, message_seq, input_tokens, output_tokens,
48
51
  cache_read_tokens, cache_creation_tokens, cost_micros, created_at,
49
52
  provider_options_shape
50
53
  FROM usage_events
51
- WHERE session_id = ?
54
+ WHERE session_id IN (${placeholders})
52
55
  ORDER BY id ASC
53
56
  `)
54
- .all(sessionId);
57
+ .all(...chain);
55
58
  const counts = db
56
59
  .prepare(`
57
60
  SELECT event_type, COUNT(*) AS c
58
61
  FROM interaction_logs
59
- WHERE session_id = ?
62
+ WHERE session_id IN (${placeholders})
60
63
  GROUP BY event_type
61
64
  `)
62
- .all(sessionId);
65
+ .all(...chain);
63
66
  const countMap = new Map(counts.map((r) => [r.event_type, r.c]));
64
67
  const events = rows.map((r) => ({
65
68
  id: r.id,
@@ -15,8 +15,8 @@
15
15
  * keys delete <provider> — remove a stored key
16
16
  * keys import-bw [providers] — pull from Bitwarden vault, store in keychain
17
17
  * keys cleanup-settings — strip plaintext keys from user-settings.json
18
- * keys login <provider> — OAuth login (openai, google)
19
- * keys logout <provider> — OAuth logout (openai, google)
18
+ * keys login <provider> — OAuth login (openai, google/agy)
19
+ * keys logout <provider> — OAuth logout (openai, google/agy)
20
20
  */
21
21
  import { type ChatSecretId } from "../chat/chat-keychain.js";
22
22
  export interface KeysSetOptions {