muonroi-cli 1.7.2 → 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 (322) 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 +55 -60
  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.js +43 -43
  253. package/dist/src/storage/transcript-view.js +7 -1
  254. package/dist/src/storage/transcript.d.ts +1 -0
  255. package/dist/src/storage/transcript.js +55 -0
  256. package/dist/src/storage/ui-interaction-log.d.ts +17 -0
  257. package/dist/src/storage/usage.js +14 -14
  258. package/dist/src/storage/workspaces.js +12 -12
  259. package/dist/src/tools/__tests__/native-tools.test.js +6 -0
  260. package/dist/src/tools/bash.d.ts +36 -23
  261. package/dist/src/tools/bash.js +59 -301
  262. package/dist/src/tools/bash.test.js +2 -257
  263. package/dist/src/tools/file-tracker.d.ts +5 -0
  264. package/dist/src/tools/file-tracker.js +14 -0
  265. package/dist/src/tools/file.js +2 -0
  266. package/dist/src/tools/git-safety.d.ts +38 -1
  267. package/dist/src/tools/git-safety.js +94 -4
  268. package/dist/src/tools/git-safety.test.js +45 -1
  269. package/dist/src/tools/native-tools.d.ts +1 -1
  270. package/dist/src/tools/native-tools.js +56 -0
  271. package/dist/src/tools/registry-bash-empty-command.test.js +4 -3
  272. package/dist/src/tools/registry-git-safety.test.js +17 -6
  273. package/dist/src/tools/registry.d.ts +5 -0
  274. package/dist/src/tools/registry.js +185 -11
  275. package/dist/src/types/index.d.ts +7 -1
  276. package/dist/src/ui/app.d.ts +1 -0
  277. package/dist/src/ui/app.js +0 -0
  278. package/dist/src/ui/components/council-question-card.js +1 -0
  279. package/dist/src/ui/components/message-view.js +37 -3
  280. package/dist/src/ui/components/structured-response-view.js +7 -0
  281. package/dist/src/ui/components/task-list-panel.js +3 -12
  282. package/dist/src/ui/containers/chat-feed.d.ts +40 -0
  283. package/dist/src/ui/containers/chat-feed.js +66 -0
  284. package/dist/src/ui/containers/modals-layer.d.ts +87 -0
  285. package/dist/src/ui/containers/modals-layer.js +18 -0
  286. package/dist/src/ui/slash/__tests__/menu-parity.test.d.ts +1 -0
  287. package/dist/src/ui/slash/__tests__/menu-parity.test.js +1 -0
  288. package/dist/src/ui/slash/__tests__/ponytail.test.d.ts +1 -0
  289. package/dist/src/ui/slash/__tests__/ponytail.test.js +37 -0
  290. package/dist/src/ui/slash/council-inspect.js +4 -4
  291. package/dist/src/ui/slash/export.js +16 -1
  292. package/dist/src/ui/slash/menu-items.js +1 -0
  293. package/dist/src/ui/slash/ponytail.d.ts +2 -0
  294. package/dist/src/ui/slash/ponytail.js +19 -0
  295. package/dist/src/ui/status-bar/store.js +0 -1
  296. package/dist/src/ui/status-bar/store.test.js +2 -2
  297. package/dist/src/ui/use-app-logic.d.ts +26 -0
  298. package/dist/src/ui/use-app-logic.js +0 -0
  299. package/dist/src/ui/utils/relaunch.js +1 -1
  300. package/dist/src/ui/utils/text.d.ts +11 -0
  301. package/dist/src/ui/utils/text.js +21 -1
  302. package/dist/src/ui/utils/text.test.js +25 -1
  303. package/dist/src/ui/utils/tools.js +3 -1
  304. package/dist/src/usage/cost-log.js +2 -2
  305. package/dist/src/usage/estimator.d.ts +14 -0
  306. package/dist/src/usage/estimator.js +22 -0
  307. package/dist/src/utils/__tests__/compaction-caps.test.d.ts +21 -0
  308. package/dist/src/utils/__tests__/compaction-caps.test.js +187 -0
  309. package/dist/src/utils/__tests__/llm-deadline-abort.test.d.ts +1 -0
  310. package/dist/src/utils/__tests__/llm-deadline-abort.test.js +33 -0
  311. package/dist/src/utils/clipboard-image.js +23 -23
  312. package/dist/src/utils/llm-deadline.d.ts +9 -1
  313. package/dist/src/utils/llm-deadline.js +31 -2
  314. package/dist/src/utils/permission-mode.d.ts +33 -0
  315. package/dist/src/utils/permission-mode.js +164 -3
  316. package/dist/src/utils/permission-mode.test.js +92 -1
  317. package/dist/src/utils/settings.d.ts +27 -31
  318. package/dist/src/utils/settings.js +100 -109
  319. package/dist/src/utils/side-question.js +2 -2
  320. package/dist/src/utils/skills.js +3 -3
  321. package/dist/src/verify/__tests__/coverage-parsers.test.js +30 -30
  322. package/package.json +1 -1
@@ -36,32 +36,23 @@ function ItemRow({ item, t }) {
36
36
  let glyph;
37
37
  let glyphColor;
38
38
  let textColor;
39
- let label;
39
+ const label = item.status === "in_progress" ? (item.activeForm ?? item.subject) : item.subject;
40
40
  if (item.status === "completed") {
41
41
  glyph = "✓";
42
42
  glyphColor = t.diffAddedFg;
43
43
  textColor = t.textMuted;
44
- // Strikethrough via U+0336 combining mark. OpenTUI doesn't expose CSS
45
- // text-decoration so we synthesise it character-by-character — matches the
46
- // visual goal in the spec.
47
- label = item.subject
48
- .split("")
49
- .map((c) => `${c}̶`)
50
- .join("");
51
44
  }
52
45
  else if (item.status === "in_progress") {
53
46
  glyph = "◉";
54
47
  glyphColor = t.accent;
55
48
  textColor = t.text;
56
- label = item.activeForm ?? item.subject;
57
49
  }
58
50
  else {
59
51
  glyph = "○";
60
52
  glyphColor = t.textMuted;
61
53
  textColor = t.text;
62
- label = item.subject;
63
54
  }
64
- return (_jsx(Semantic, { id: `task-${item.id}`, role: "listitem", name: label, state: item.status, children: _jsxs("text", { children: [_jsx("span", { style: { fg: glyphColor }, children: `${glyph} ` }), _jsx("span", { style: { fg: textColor }, children: trunc(label, 80) })] }) }));
55
+ return (_jsx(Semantic, { id: `task-${item.id}`, role: "listitem", name: label, state: item.status, children: _jsx("box", { width: "100%", children: _jsxs("text", { fg: textColor, children: [_jsx("span", { style: { fg: glyphColor }, children: `${glyph} ` }), trunc(label, 70)] }) }) }));
65
56
  }
66
57
  export function TaskListPanel({ snapshot, t, expanded }) {
67
58
  if (snapshot.items.length === 0)
@@ -78,7 +69,7 @@ export function TaskListPanel({ snapshot, t, expanded }) {
78
69
  if (pending > 0)
79
70
  footerParts.push(`${pending} queued`);
80
71
  const footer = footerParts.length > 0 ? footerParts.join(" · ") : "0 todos";
81
- return (_jsx(Semantic, { id: "task-list", role: "listbox", name: "Todos", children: _jsxs("box", { border: true, borderColor: t.border, paddingLeft: 2, paddingRight: 2, paddingTop: 0, paddingBottom: 0, marginBottom: 1, flexDirection: "column", children: [_jsx("text", { children: _jsx("span", { style: { fg: t.textMuted }, children: "Todos" }) }), visible.map((it) => (_jsx(ItemRow, { item: it, t: t }, it.id))), overflow > 0 && _jsx("text", { fg: t.textMuted, children: `… +${overflow} more (ctrl+e to expand)` }), _jsx("text", { fg: t.textDim, children: footer })] }) }));
72
+ return (_jsx(Semantic, { id: "task-list", role: "listbox", name: "Todos", children: _jsxs("box", { border: true, borderColor: t.border, paddingLeft: 2, paddingRight: 2, paddingTop: 0, paddingBottom: 0, marginBottom: 1, flexDirection: "column", children: [_jsx("box", { paddingBottom: 1, children: _jsx("text", { fg: t.textMuted, children: _jsx("b", { children: "Todos" }) }) }), _jsx("box", { flexDirection: "column", children: visible.map((it) => (_jsx(ItemRow, { item: it, t: t }, it.id))) }), overflow > 0 && (_jsx("box", { marginTop: 1, children: _jsx("text", { fg: t.textMuted, children: `… +${overflow} more (ctrl+e to expand)` }) })), _jsx("box", { marginTop: 1, paddingTop: 1, border: ["top"], borderColor: t.border, children: _jsx("text", { fg: t.textDim, children: footer }) })] }) }));
82
73
  }
83
74
  export const __TEST_ONLY__ = { sortItems, MAX_VISIBLE };
84
75
  //# sourceMappingURL=task-list-panel.js.map
@@ -0,0 +1,40 @@
1
+ import React from "react";
2
+ export interface ChatFeedProps {
3
+ getSide: any;
4
+ getPartnerLast: any;
5
+ resolveStyle: any;
6
+ scrollRef: any;
7
+ messages: any;
8
+ expandedMessages: any;
9
+ liveTurnSourceLabel: any;
10
+ activeToolCalls: any;
11
+ streamContent: any;
12
+ isProcessing: any;
13
+ activeSubagent: any;
14
+ councilPhases: any;
15
+ productStatus: any;
16
+ councilStatuses: any;
17
+ councilInfoCards: any;
18
+ councilMessages: any;
19
+ councilPlaceholders: any;
20
+ reasoningActive: any;
21
+ lastReasoningElapsedMs: any;
22
+ streamReasoning: any;
23
+ showPlanPanel: any;
24
+ planQuestions: any;
25
+ pqs: any;
26
+ pendingPaymentApproval: any;
27
+ activeHaltCard: any;
28
+ haltSelectedIndex: any;
29
+ initNewForm: any;
30
+ pointToExistingForm: any;
31
+ councilProgress: any;
32
+ pendingCouncilQuestion: any;
33
+ councilCardState: any;
34
+ pendingCouncilPreflight: any;
35
+ preflightCardState: any;
36
+ t: any;
37
+ width: any;
38
+ modeInfo: any;
39
+ }
40
+ export declare function ChatFeed(props: ChatFeedProps): React.ReactNode;
@@ -0,0 +1,66 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
+ import { Semantic } from "@muonroi/agent-harness-opentui";
3
+ import { ProductStatusCard } from "../cards/product-status-card.js";
4
+ import { makePairKey } from "../components/bubble-layout.js";
5
+ import { CouncilInfoCardView } from "../components/council-info-card.js";
6
+ import { CouncilLeaderBubble } from "../components/council-leader-bubble.js";
7
+ import { CouncilMessageBubble } from "../components/council-message-bubble.js";
8
+ import { CouncilPhaseTimeline } from "../components/council-phase-timeline.js";
9
+ import { CouncilPlaceholderBubble } from "../components/council-placeholder-bubble.js";
10
+ import { CouncilQuestionCard } from "../components/council-question-card.js";
11
+ import { CouncilStatusList } from "../components/council-status-list.js";
12
+ import { CouncilSynthesisBanner } from "../components/council-synthesis-banner.js";
13
+ import { HaltRecoveryCard } from "../components/halt-recovery-card.js";
14
+ import { InitNewFormCard } from "../components/init-new-form-card.js";
15
+ import { computeMcpRunInfo, MessageView } from "../components/message-view.js";
16
+ import { PointToExistingFormCard } from "../components/point-to-existing-form-card.js";
17
+ import { DelegationTaskLine, InlineTool, ShimmerText, SubagentActivity, SubagentTaskLine, } from "../components/tool-result-views.js";
18
+ import { Markdown } from "../markdown.js";
19
+ import { PaymentApprovalPanel } from "../modals/wallet-picker-modal.js";
20
+ import { PlanQuestionsPanel } from "../plan.js";
21
+ import { buildPreflightQuestion } from "../utils/format.js";
22
+ import { toolArgs, toolLabel, tryParseArg } from "../utils/tools.js";
23
+ export function ChatFeed(props) {
24
+ const { modeInfo, getSide, getPartnerLast, resolveStyle, scrollRef, messages, expandedMessages, liveTurnSourceLabel, activeToolCalls, streamContent, isProcessing, activeSubagent, councilPhases, productStatus, councilStatuses, councilInfoCards, councilMessages, councilPlaceholders, reasoningActive, lastReasoningElapsedMs, streamReasoning, showPlanPanel, planQuestions, pqs, pendingPaymentApproval, activeHaltCard, haltSelectedIndex, initNewForm, pointToExistingForm, councilProgress, pendingCouncilQuestion, councilCardState, pendingCouncilPreflight, preflightCardState, t, width, } = props;
25
+ return (_jsx(Semantic, { id: "log", role: "log", props: { scrollTop: scrollRef.current?.scrollTop ?? 0 }, children: _jsxs("scrollbox", { ref: scrollRef, flexGrow: 1, stickyScroll: true, stickyStart: "bottom", children: [(() => {
26
+ const mcpRuns = computeMcpRunInfo(messages);
27
+ // Phase 5 F7 — index of the last assistant message so
28
+ // MessageView can skip auto-collapse on it (final answer
29
+ // should always be fully visible, not hidden behind
30
+ // "ctrl+e expand").
31
+ let _lastAssistantIdx = -1;
32
+ for (let _i = messages.length - 1; _i >= 0; _i--) {
33
+ if (messages[_i]?.type === "assistant") {
34
+ _lastAssistantIdx = _i;
35
+ break;
36
+ }
37
+ }
38
+ return messages.map((msg, i) => (
39
+ // biome-ignore lint/suspicious/noArrayIndexKey: append-only message log; index is part of the stable semantic id
40
+ _jsx(Semantic, { id: `msg-${i}`, role: "listitem", name: `${msg.type ?? "msg"}:${String(msg.content ?? "").slice(0, 40)}`, children: _jsx(MessageView, { entry: msg, index: i, t: t, modeColor: modeInfo.color, expandedMessages: expandedMessages, mcpRun: mcpRuns[i], isFinalAssistant: i === _lastAssistantIdx }, `${msg.timestamp?.getTime?.() ?? i}-${msg.type}-${msg.remoteKey ?? ""}-${String(msg.content ?? "").slice(0, 24)}`) }, `sem-${msg.timestamp?.getTime?.() ?? i}-${i}`)));
41
+ })(), liveTurnSourceLabel && (activeToolCalls.length > 0 || streamContent || isProcessing) && (_jsx("box", { paddingLeft: 3, marginTop: 1, flexShrink: 0, children: _jsx("text", { fg: t.textMuted, children: liveTurnSourceLabel }) })), activeToolCalls.map((tc) => tc.function.name === "task" ? (_jsx(SubagentTaskLine, { t: t, agent: tryParseArg(tc, "agent") || "sub-agent", label: toolArgs(tc) || "Working", pending: true }, tc.id)) : tc.function.name === "delegate" ? (_jsx(DelegationTaskLine, { t: t, label: toolArgs(tc) || "Background research", pending: true, id: undefined }, tc.id)) : (_jsx(InlineTool, { t: t, pending: true, children: toolLabel(tc) }, tc.id))), activeSubagent && _jsx(SubagentActivity, { t: t, status: activeSubagent }), councilPhases.length > 0 && (_jsx(Semantic, { id: "council-phases", role: "listbox", name: "Council Phases", children: _jsx(CouncilPhaseTimeline, { phases: councilPhases, theme: t }) })), productStatus && _jsx(ProductStatusCard, { data: productStatus, theme: t }), councilStatuses.length > 0 && (_jsx(Semantic, { id: "council-status", role: "listbox", name: "Council Status", children: _jsx(CouncilStatusList, { statuses: councilStatuses, theme: t }) })), councilInfoCards.map((card, idx) => (_jsx(Semantic, { id: `council-card-${idx}`, role: "listitem", name: card.title || `Council card ${idx}`, children: _jsx(CouncilInfoCardView, { card: card, terminalCols: width, theme: t }, `info-card-${idx}-${card.title}`) }, `sem-info-${idx}-${card.title}`))), councilMessages.map((cm, idx) => {
42
+ const side = cm.kind === "debate" && cm.partner
43
+ ? getSide(makePairKey(cm.speaker.role, cm.partner.role), cm.speaker.role)
44
+ : "left";
45
+ const semName = `${cm.kind}:${cm.speaker?.role ?? "?"}`;
46
+ if (cm.kind === "leader") {
47
+ return (_jsx(Semantic, { id: `council-msg-${idx}`, role: "listitem", name: semName, value: cm.text, children: _jsx(CouncilLeaderBubble, { msg: cm, terminalCols: width }, idx) }, `sem-cm-${idx}`));
48
+ }
49
+ if (cm.kind === "synthesis") {
50
+ return (_jsx(Semantic, { id: `council-msg-${idx}`, role: "listitem", name: semName, value: cm.text, children: _jsx(CouncilSynthesisBanner, { msg: cm }, idx) }, `sem-cm-${idx}`));
51
+ }
52
+ const pairKey = cm.partner ? makePairKey(cm.speaker.role, cm.partner.role) : `solo::${cm.speaker.role}`;
53
+ const partnerLastText = cm.partner ? getPartnerLast(pairKey, cm.partner.role) : undefined;
54
+ return (_jsx(Semantic, { id: `council-msg-${idx}`, role: "listitem", name: semName, value: cm.text, children: _jsx(CouncilMessageBubble, { msg: cm, terminalCols: width, side: side, resolveStyle: resolveStyle, partnerLastText: partnerLastText, partnerRole: cm.partner?.role, theme: t }, idx) }, `sem-cm-${idx}`));
55
+ }), Array.from(councilPlaceholders.entries()).map(([id, p]) => (_jsx(CouncilPlaceholderBubble, { role: p.role, side: p.side, terminalCols: width, color: p.color, theme: t, variant: p.variant }, id))), (reasoningActive || lastReasoningElapsedMs > 0) && (_jsxs("box", { paddingLeft: 3, marginTop: 1, flexShrink: 0, flexDirection: "column", children: [_jsx("text", { fg: t.textMuted, children: reasoningActive ? "[Thought] Thinking..." : `[Thought] Thought for ${(lastReasoningElapsedMs / 1000).toFixed(1)}s` }), streamReasoning ? (_jsx("box", { border: ["left"], borderColor: t.textMuted, paddingLeft: 2, marginTop: 1, flexDirection: "column", children: reasoningActive ? (
56
+ // While actively streaming, render only the last 3
57
+ // non-empty lines as plain text to avoid Markdown
58
+ // re-parse overhead every 150ms.
59
+ _jsx("text", { fg: t.textMuted, children: streamReasoning
60
+ .split("\n")
61
+ .filter((l) => l.trim().length > 0)
62
+ .slice(-3)
63
+ .join(" · ") })) : (_jsx(Markdown, { content: streamReasoning, t: t })) })) : null] })), streamContent && (_jsx("box", { paddingLeft: 3, marginTop: 1, flexShrink: 0, children: _jsx(Markdown, { content: streamContent, t: t }) })), isProcessing && !streamContent && activeToolCalls.length === 0 && (_jsx(ShimmerText, { t: t, text: "Planning next moves" })), showPlanPanel && _jsx(PlanQuestionsPanel, { t: t, questions: planQuestions, state: pqs }), pendingPaymentApproval && _jsx(PaymentApprovalPanel, { t: t, payment: pendingPaymentApproval }), activeHaltCard && (_jsx(HaltRecoveryCard, { halt: activeHaltCard, selectedIndex: haltSelectedIndex, terminalCols: width, theme: t })), initNewForm && _jsx(InitNewFormCard, { state: initNewForm, terminalCols: width, theme: t }), pointToExistingForm && _jsx(PointToExistingFormCard, { state: pointToExistingForm, terminalCols: width, theme: t }), councilProgress && (_jsx(Semantic, { id: "continue-as-council-progress", role: "log", name: "Council brainstorm", children: _jsx("box", { flexDirection: "column", borderStyle: "single", borderColor: councilProgress.status === "error" ? t.initFormError : t.text, padding: 1, marginTop: 1, children: _jsxs("text", { fg: t.text, children: [councilProgress.status === "running" && "Council brainstorming — writing spec.md...", councilProgress.status === "done" &&
64
+ `Council brainstorm complete: ${councilProgress.specPath}${councilProgress.hasContent ? "" : " (no content — production council wiring deferred)"}`, councilProgress.status === "error" && `Council brainstorm failed: ${councilProgress.error}`] }) }) })), pendingCouncilQuestion && councilCardState && (_jsx(CouncilQuestionCard, { question: pendingCouncilQuestion, theme: t, state: councilCardState })), pendingCouncilPreflight && preflightCardState && (_jsx(CouncilQuestionCard, { question: buildPreflightQuestion(pendingCouncilPreflight), theme: t, state: preflightCardState }))] }) }));
65
+ }
66
+ //# sourceMappingURL=chat-feed.js.map
@@ -0,0 +1,87 @@
1
+ export interface ModalsLayerProps {
2
+ agentRows: any;
3
+ agentsEditorDraft: any;
4
+ agentsEditorError: any;
5
+ agentsEditorField: any;
6
+ agentsEditorModelIndex: any;
7
+ agentsEditorSyncKey: any;
8
+ agentsModalIndex: any;
9
+ agentsSearchQuery: any;
10
+ apiKeyError: any;
11
+ apiKeyInputRef: any;
12
+ apiKeyPrompt: any;
13
+ bwSync: any;
14
+ configuredProviders: any;
15
+ connectModalIndex: any;
16
+ defaultProvider: any;
17
+ disabledModels: any;
18
+ disabledProviders: any;
19
+ editingMcpId: any;
20
+ editingSubagent: any;
21
+ filteredModels: any;
22
+ height: any;
23
+ mcpArgsRef: any;
24
+ mcpCommandRef: any;
25
+ mcpCwdRef: any;
26
+ mcpEditorDraft: any;
27
+ mcpEditorError: any;
28
+ mcpEditorField: any;
29
+ mcpEditorSyncKey: any;
30
+ mcpEnvRef: any;
31
+ mcpHeadersRef: any;
32
+ mcpLabelRef: any;
33
+ mcpModalIndex: any;
34
+ mcpRows: any;
35
+ mcpSearchQuery: any;
36
+ mcpUrlRef: any;
37
+ model: any;
38
+ modelPickerFocus: any;
39
+ modelPickerIndex: any;
40
+ modelSearchQuery: any;
41
+ providerChipIndex: any;
42
+ providersWithKey: any;
43
+ reasoningEffortByModel: any;
44
+ sandboxMode: any;
45
+ sandboxSettings: any;
46
+ sandboxSettingsEditBuffer: any;
47
+ sandboxSettingsEditing: any;
48
+ sandboxSettingsFocusIndex: any;
49
+ scheduleModalIndex: any;
50
+ scheduleRows: any;
51
+ scheduleSearchQuery: any;
52
+ sessionPickerIndex: any;
53
+ sessionPickerList: any;
54
+ showAgentsEditor: any;
55
+ showAgentsModal: any;
56
+ showApiKeyModal: any;
57
+ showConnectModal: any;
58
+ showMcpEditor: any;
59
+ showMcpModal: any;
60
+ showModelPicker: any;
61
+ showSandboxPicker: any;
62
+ showScheduleModal: any;
63
+ showSessionPicker: any;
64
+ showTelegramPairModal: any;
65
+ showTelegramTokenModal: any;
66
+ showUpdateModal: any;
67
+ showWalletPicker: any;
68
+ startupConfig: any;
69
+ subagentInstructionRef: any;
70
+ subagentNameRef: any;
71
+ submitApiKey: any;
72
+ submitMcpEditor: any;
73
+ submitSubagentEditor: any;
74
+ submitTelegramPair: any;
75
+ submitTelegramToken: any;
76
+ t: any;
77
+ telegramPairError: any;
78
+ telegramPairInputRef: any;
79
+ telegramTokenError: any;
80
+ telegramTokenInputRef: any;
81
+ updateInfo: any;
82
+ walletDisplayInfo: any;
83
+ walletFocusIndex: any;
84
+ walletSettings: any;
85
+ width: any;
86
+ }
87
+ export declare function ModalsLayer(props: ModalsLayerProps): import("react").ReactNode;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
+ import { formatSubagentName } from "../../utils/subagent-display.js";
3
+ import { SubagentEditorModal, SubagentsBrowserModal } from "../agents-modal.js";
4
+ import { CONNECT_CHANNELS } from "../constants.js";
5
+ import { McpBrowserModal, McpEditorModal } from "../mcp-modal.js";
6
+ import { ApiKeyModal } from "../modals/api-key-modal.js";
7
+ import { ConnectModal, TelegramPairModal, TelegramTokenModal } from "../modals/connect-modal.js";
8
+ import { ModelPickerModal } from "../modals/model-picker-modal.js";
9
+ import { SandboxPickerModal } from "../modals/sandbox-picker-modal.js";
10
+ import { SessionPickerModal } from "../modals/session-picker-modal.js";
11
+ import { UpdateModal } from "../modals/update-modal.js";
12
+ import { WalletPickerModal } from "../modals/wallet-picker-modal.js";
13
+ import { ScheduleBrowserModal } from "../schedule-modal.js";
14
+ export function ModalsLayer(props) {
15
+ const { agentRows, agentsEditorDraft, agentsEditorError, agentsEditorField, agentsEditorModelIndex, agentsEditorSyncKey, agentsModalIndex, agentsSearchQuery, apiKeyError, apiKeyInputRef, apiKeyPrompt, bwSync, configuredProviders, connectModalIndex, defaultProvider, disabledModels, disabledProviders, editingMcpId, editingSubagent, filteredModels, height, mcpArgsRef, mcpCommandRef, mcpCwdRef, mcpEditorDraft, mcpEditorError, mcpEditorField, mcpEditorSyncKey, mcpEnvRef, mcpHeadersRef, mcpLabelRef, mcpModalIndex, mcpRows, mcpSearchQuery, mcpUrlRef, model, modelPickerFocus, modelPickerIndex, modelSearchQuery, providerChipIndex, providersWithKey, reasoningEffortByModel, sandboxMode, sandboxSettings, sandboxSettingsEditBuffer, sandboxSettingsEditing, sandboxSettingsFocusIndex, scheduleModalIndex, scheduleRows, scheduleSearchQuery, sessionPickerIndex, sessionPickerList, showAgentsEditor, showAgentsModal, showApiKeyModal, showConnectModal, showMcpEditor, showMcpModal, showModelPicker, showSandboxPicker, showScheduleModal, showSessionPicker, showTelegramPairModal, showTelegramTokenModal, showUpdateModal, showWalletPicker, startupConfig, subagentInstructionRef, subagentNameRef, submitApiKey, submitMcpEditor, submitSubagentEditor, submitTelegramPair, submitTelegramToken, t, telegramPairError, telegramPairInputRef, telegramTokenError, telegramTokenInputRef, updateInfo, walletDisplayInfo, walletFocusIndex, walletSettings, width, } = props;
16
+ return (_jsxs(_Fragment, { children: [showApiKeyModal && (_jsx(ApiKeyModal, { t: t, width: width, height: height, inputRef: apiKeyInputRef, error: apiKeyError, onSubmit: submitApiKey })), showUpdateModal && updateInfo && (_jsx(UpdateModal, { t: t, width: width, height: height, currentVersion: startupConfig.version, latestVersion: updateInfo.latestVersion })), showMcpModal && !showMcpEditor && (_jsx(McpBrowserModal, { t: t, width: width, height: height, selectedIndex: mcpModalIndex, searchQuery: mcpSearchQuery, rows: mcpRows })), showMcpEditor && (_jsx(McpEditorModal, { t: t, width: width, height: height, draft: mcpEditorDraft, focusedField: mcpEditorField, syncKey: mcpEditorSyncKey, error: mcpEditorError, title: editingMcpId ? "Edit MCP Server" : "Add MCP Server", labelRef: mcpLabelRef, urlRef: mcpUrlRef, headersRef: mcpHeadersRef, commandRef: mcpCommandRef, argsRef: mcpArgsRef, cwdRef: mcpCwdRef, envRef: mcpEnvRef, onSubmit: submitMcpEditor })), showScheduleModal && (_jsx(ScheduleBrowserModal, { t: t, width: width, height: height, selectedIndex: scheduleModalIndex, searchQuery: scheduleSearchQuery, rows: scheduleRows })), showAgentsModal && !showAgentsEditor && (_jsx(SubagentsBrowserModal, { t: t, width: width, height: height, selectedIndex: agentsModalIndex, searchQuery: agentsSearchQuery, rows: agentRows })), showAgentsEditor && (_jsx(SubagentEditorModal, { t: t, width: width, height: height, draft: agentsEditorDraft, focusedField: agentsEditorField, modelIndex: agentsEditorModelIndex, error: agentsEditorError, title: editingSubagent ? `Edit sub-agent: ${formatSubagentName(editingSubagent.name)}` : "Add sub-agent", nameRef: subagentNameRef, instructionRef: subagentInstructionRef, onSubmit: submitSubagentEditor, showRemoveHint: !!editingSubagent }, `subagent-editor-${agentsEditorSyncKey}`)), showModelPicker && (_jsx(ModelPickerModal, { t: t, currentModel: model, selectedIndex: modelPickerIndex, width: width, height: height, searchQuery: modelSearchQuery, filteredModels: filteredModels, reasoningEffortByModel: reasoningEffortByModel, configuredProviders: configuredProviders, disabledProviders: disabledProviders, disabledModels: disabledModels, defaultProvider: defaultProvider, focus: modelPickerFocus, providerChipIndex: providerChipIndex, providersWithKey: providersWithKey, apiKeyPrompt: apiKeyPrompt, bwSync: bwSync })), showSessionPicker && (_jsx(SessionPickerModal, { t: t, sessions: sessionPickerList, focusIndex: sessionPickerIndex, width: width, height: height })), showWalletPicker && (_jsx(WalletPickerModal, { t: t, settings: walletSettings, walletInfo: walletDisplayInfo, focusIndex: walletFocusIndex, width: width, height: height })), showSandboxPicker && (_jsx(SandboxPickerModal, { t: t, currentMode: sandboxMode, settings: sandboxSettings, focusIndex: sandboxSettingsFocusIndex, editing: sandboxSettingsEditing, editBuffer: sandboxSettingsEditBuffer, width: width, height: height })), showConnectModal && (_jsx(ConnectModal, { t: t, width: width, height: height, selectedIndex: connectModalIndex, channels: CONNECT_CHANNELS })), showTelegramTokenModal && (_jsx(TelegramTokenModal, { t: t, width: width, height: height, inputRef: telegramTokenInputRef, error: telegramTokenError, onSubmit: submitTelegramToken })), showTelegramPairModal && (_jsx(TelegramPairModal, { t: t, width: width, height: height, inputRef: telegramPairInputRef, error: telegramPairError, onSubmit: () => void submitTelegramPair() }))] }));
17
+ }
18
+ //# sourceMappingURL=modals-layer.js.map
@@ -14,3 +14,4 @@ import "../council-inspect.js";
14
14
  import "../ideal.js";
15
15
  import "../route.js";
16
16
  import "../status.js";
17
+ import "../ponytail.js";
@@ -26,6 +26,7 @@ import "../council-inspect.js";
26
26
  import "../ideal.js";
27
27
  import "../route.js";
28
28
  import "../status.js";
29
+ import "../ponytail.js";
29
30
  describe("slash menu / registry parity", () => {
30
31
  it("every registered slash command has a menu entry", () => {
31
32
  const menuIds = new Set(SLASH_MENU_ITEMS.map((m) => m.id));
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { handlePonytailSlash } from "../ponytail.js";
3
+ describe("handlePonytailSlash", () => {
4
+ const originalEnv = process.env.MUONROI_PONYTAIL_DISABLE;
5
+ const dummyCtx = {
6
+ cwd: "/",
7
+ tenantId: "test",
8
+ defaultProvider: "openai",
9
+ defaultModel: "gpt-4",
10
+ };
11
+ afterEach(() => {
12
+ if (originalEnv === undefined) {
13
+ delete process.env.MUONROI_PONYTAIL_DISABLE;
14
+ }
15
+ else {
16
+ process.env.MUONROI_PONYTAIL_DISABLE = originalEnv;
17
+ }
18
+ });
19
+ it("should enable ponytail mode and modify process.env", async () => {
20
+ process.env.MUONROI_PONYTAIL_DISABLE = "1";
21
+ const response = await handlePonytailSlash(["on"], dummyCtx);
22
+ expect(process.env.MUONROI_PONYTAIL_DISABLE).toBe("0");
23
+ expect(response).toContain("Ponytail Mode enabled");
24
+ });
25
+ it("should disable ponytail mode and modify process.env", async () => {
26
+ process.env.MUONROI_PONYTAIL_DISABLE = "0";
27
+ const response = await handlePonytailSlash(["off"], dummyCtx);
28
+ expect(process.env.MUONROI_PONYTAIL_DISABLE).toBe("1");
29
+ expect(response).toContain("Ponytail Mode disabled");
30
+ });
31
+ it("should return status when no args are provided", async () => {
32
+ process.env.MUONROI_PONYTAIL_DISABLE = "0";
33
+ const response = await handlePonytailSlash([], dummyCtx);
34
+ expect(response).toContain("ON (enabled)");
35
+ });
36
+ });
37
+ //# sourceMappingURL=ponytail.test.js.map
@@ -37,10 +37,10 @@ export const handleCouncilInspectSlash = async (args) => {
37
37
  }
38
38
  // Load all system messages for this session — parameterized query prevents SQL injection (T-17-04)
39
39
  const rows = db
40
- .prepare(`SELECT role, message_json, seq, created_at
41
- FROM messages
42
- WHERE session_id = ?
43
- AND role = 'system'
40
+ .prepare(`SELECT role, message_json, seq, created_at
41
+ FROM messages
42
+ WHERE session_id = ?
43
+ AND role = 'system'
44
44
  ORDER BY seq ASC`)
45
45
  .all(sessionId);
46
46
  if (rows.length === 0) {
@@ -18,6 +18,7 @@
18
18
  */
19
19
  import * as fs from "node:fs";
20
20
  import * as path from "node:path";
21
+ import { appendCrashLog } from "../../index.js";
21
22
  import { getDatabase } from "../../storage/db.js";
22
23
  import { buildChatEntries } from "../../storage/transcript.js";
23
24
  import { registerSlash } from "./registry.js";
@@ -251,7 +252,21 @@ export const handleExportSlash = async (_args, ctx) => {
251
252
  if (!sessionId) {
252
253
  return "No active session. Start a conversation first.";
253
254
  }
254
- const dbEntries = buildChatEntries(sessionId);
255
+ let dbEntries = [];
256
+ try {
257
+ dbEntries = buildChatEntries(sessionId);
258
+ }
259
+ catch (err) {
260
+ // A DB read failure here (SQLite busy/locked right after a council turn,
261
+ // schema drift, a corrupt page) used to throw out of this async handler.
262
+ // With no .catch on the dispatchSlash call site that became an
263
+ // unhandledRejection → process.exit(1) → the user kicked out of the TUI.
264
+ // Degrade instead: export the live TUI scrollback + interaction timeline.
265
+ // Logged to crash.log, NOT console (console.error corrupts the OpenTUI
266
+ // framebuffer while the renderer owns the terminal).
267
+ appendCrashLog("EXPORT", `buildChatEntries failed for session ${sessionId}: ${err instanceof Error ? err.stack || err.message : String(err)}`);
268
+ dbEntries = [];
269
+ }
255
270
  const liveEntries = ctx.getLiveEntries?.() ?? [];
256
271
  const timeline = selectInteractionTimeline(sessionId);
257
272
  if (dbEntries.length === 0 && liveEntries.length === 0 && timeline.length === 0) {
@@ -87,6 +87,7 @@ export const SLASH_MENU_ITEMS = [
87
87
  { id: "ee-context-status", label: "ee-context status", description: "Show current BB-context setting", hidden: true },
88
88
  { id: "export", label: "export", description: "Export entire conversation to a .txt file", hidden: true },
89
89
  { id: "status", label: "status", description: "Show Agile sprint progress dashboard", hidden: true },
90
+ { id: "ponytail", label: "ponytail", description: "Toggle Lazy Senior AI Mode (on/off/status)", hidden: false },
90
91
  ];
91
92
  /** Items shown in the splash autocomplete + /help listing. */
92
93
  export const VISIBLE_SLASH_MENU_ITEMS = SLASH_MENU_ITEMS.filter((m) => !m.hidden);
@@ -0,0 +1,2 @@
1
+ import { type SlashHandler } from "./registry.js";
2
+ export declare const handlePonytailSlash: SlashHandler;
@@ -0,0 +1,19 @@
1
+ import { isPonytailModeEnabled } from "../../pil/config.js";
2
+ import { registerSlash } from "./registry.js";
3
+ export const handlePonytailSlash = async (args, _ctx) => {
4
+ const arg = args[0]?.toLowerCase();
5
+ if (arg === "on") {
6
+ process.env.MUONROI_PONYTAIL_DISABLE = "0";
7
+ return "✅ Ponytail Mode enabled (Lazy Senior mode). The agent will enforce YAGNI and prioritize standard libraries.";
8
+ }
9
+ if (arg === "off") {
10
+ process.env.MUONROI_PONYTAIL_DISABLE = "1";
11
+ return "❌ Ponytail Mode disabled. The agent is free to propose complex architectures and dependencies.";
12
+ }
13
+ // Status check
14
+ const status = isPonytailModeEnabled() ? "ON (enabled)" : "OFF (disabled)";
15
+ return `Ponytail Mode is currently: **${status}**\nUsage: \`/ponytail on\` or \`/ponytail off\``;
16
+ };
17
+ // Self-register on module import
18
+ registerSlash("ponytail", handlePonytailSlash);
19
+ //# sourceMappingURL=ponytail.js.map
@@ -65,7 +65,6 @@ export function wireStatusBar() {
65
65
  });
66
66
  const offThresholds = subscribeThresholds((ev) => {
67
67
  statusBarStore.setState({
68
- session_usd: ev.current_usd,
69
68
  month_usd: ev.current_usd,
70
69
  cap_usd: ev.cap_usd,
71
70
  current_pct: ev.current_pct,
@@ -87,7 +87,7 @@ describe("wireStatusBar", () => {
87
87
  expect(s.model).toBe("claude-3-5-sonnet-latest");
88
88
  off();
89
89
  });
90
- it("threshold event updates session_usd and month_usd", () => {
90
+ it("threshold event updates month_usd (not session_usd)", () => {
91
91
  const off = wireStatusBar();
92
92
  thresholdCallback({
93
93
  level: 80,
@@ -97,8 +97,8 @@ describe("wireStatusBar", () => {
97
97
  atMs: 1,
98
98
  });
99
99
  const s = statusBarStore.getState();
100
- expect(s.session_usd).toBe(12.5);
101
100
  expect(s.month_usd).toBe(12.5);
101
+ expect(s.session_usd).toBe(0); // orchestrator owns session_usd, not thresholds
102
102
  off();
103
103
  });
104
104
  it("downgrade event updates model", () => {
@@ -0,0 +1,26 @@
1
+ import "./slash/route.js";
2
+ import "./slash/optimize.js";
3
+ import "./slash/discuss.js";
4
+ import "./slash/plan.js";
5
+ import "./slash/execute.js";
6
+ import "./slash/compact.js";
7
+ import "./slash/expand.js";
8
+ import "./slash/clear.js";
9
+ import "./slash/pin.js";
10
+ import "./slash/cost.js";
11
+ import "./slash/ee.js";
12
+ import "./slash/debug.js";
13
+ import "./slash/council.js";
14
+ import "./slash/ideal.js";
15
+ import "./slash/export.js";
16
+ import "./slash/status.js";
17
+ import "./slash/ponytail.js";
18
+ export type { AppStartupConfig } from "./types.js";
19
+ export interface AppLogicProps {
20
+ agent: any;
21
+ startupConfig: any;
22
+ initialMessage?: any;
23
+ onExit: any;
24
+ onRelaunch: any;
25
+ }
26
+ export declare function useAppLogic(props: AppLogicProps): any;
Binary file
@@ -20,7 +20,7 @@ import { spawn } from "node:child_process";
20
20
  * `process.argv[0]` itself. We sanitize the WHOLE args portion in one pass.
21
21
  */
22
22
  export function sanitizeArgvForResume(args, sessionId) {
23
- if (!sessionId || !sessionId.trim()) {
23
+ if (!sessionId?.trim()) {
24
24
  throw new Error("sanitizeArgvForResume: sessionId is required");
25
25
  }
26
26
  const out = [];
@@ -1,4 +1,15 @@
1
1
  export declare function formatTokenCount(tokens: number): string;
2
+ /**
3
+ * Strip invisible/control Unicode characters that leak into terminal output.
4
+ * Keeps \t \n \r (needed for rendering); removes everything else that most
5
+ * terminals cannot display safely: zero-width spaces, soft hyphens, BiDi
6
+ * overrides, C0/C1 controls, BOM, and other default-ignorable code points.
7
+ * Fast-pathed: returns the input unchanged when no match is found.
8
+ *
9
+ * Uses `new RegExp()` to avoid Biome's `noControlCharactersInRegex` lint
10
+ * (control-char escapes in a regex literal are blocked).
11
+ */
12
+ export declare function stripInvisibleChars(text: string): string;
2
13
  export declare function trunc(s: string, n: number): string;
3
14
  export declare function truncateLine(s: string, n: number): string;
4
15
  export declare function truncateBlock(text: string, maxLines: number): string;
@@ -5,6 +5,26 @@ export function formatTokenCount(tokens) {
5
5
  return `${Math.round(tokens / 1_000)}K`;
6
6
  return String(tokens);
7
7
  }
8
+ /**
9
+ * Strip invisible/control Unicode characters that leak into terminal output.
10
+ * Keeps \t \n \r (needed for rendering); removes everything else that most
11
+ * terminals cannot display safely: zero-width spaces, soft hyphens, BiDi
12
+ * overrides, C0/C1 controls, BOM, and other default-ignorable code points.
13
+ * Fast-pathed: returns the input unchanged when no match is found.
14
+ *
15
+ * Uses `new RegExp()` to avoid Biome's `noControlCharactersInRegex` lint
16
+ * (control-char escapes in a regex literal are blocked).
17
+ */
18
+ export function stripInvisibleChars(text) {
19
+ if (!text)
20
+ return text;
21
+ // Constructed via new RegExp to bypass Biome lint for control-char patterns.
22
+ // U+034F (Combining Grapheme Joiner) is separated via alternation per
23
+ // Biome's noMisleadingCharacterClass rule.
24
+ const re = new RegExp("[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F\u00AD\u061C\u070F\u200B-\u200F\u2028-\u202F\u2060-\u2069\uFEFF\uFFF9-\uFFFB]|" +
25
+ "\u034F|\u{1BCA0}-\u{1BCA3}|\u{E0001}|\u{E0020}-\u{E007F}", "gu");
26
+ return re.test(text) ? text.replace(re, "") : text;
27
+ }
8
28
  export function trunc(s, n) {
9
29
  const str = String(s ?? "");
10
30
  return str.length <= n ? str : `${str.slice(0, n)}…`;
@@ -37,7 +57,7 @@ export function sanitizeContent(raw) {
37
57
  * answer (e.g. `\frac{a}{b}`) is untouched. Fast-pathed: no work when absent.
38
58
  */
39
59
  export function stripStrayModelMacros(text) {
40
- if (!text || !text.includes("\\confidence"))
60
+ if (!text?.includes("\\confidence"))
41
61
  return text;
42
62
  // Trailing form (most common) — also swallow the whitespace/newline before it.
43
63
  let out = text.replace(/\s*\\confidence\s*\{[^}]*\}\s*$/i, "");
@@ -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).