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
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@opentui/reac
2
2
  import { memo } from "react";
3
3
  import { Markdown } from "../markdown.js";
4
4
  import { PlanView } from "../plan.js";
5
- import { stripStrayModelMacros, trunc } from "../utils/text.js";
5
+ import { stripInvisibleChars, stripStrayModelMacros, trunc } from "../utils/text.js";
6
6
  import { describeMcpFsTool, toolArgs, toolLabel, tryParseArg } from "../utils/tools.js";
7
7
  import { DiffView, ReadFilePreviewView } from "./diff-view.js";
8
8
  import { LspDiagnosticsView, LspResultView } from "./lsp-views.js";
@@ -32,6 +32,8 @@ export function AssistantMessageContent({ content, t, expanded, isFinal, }) {
32
32
  // Strip stray model self-annotation macros (e.g. grok's trailing
33
33
  // `\confidence{85}`) that leak into the answer — not instructed in the prompt.
34
34
  content = stripStrayModelMacros(content);
35
+ // Strip invisible/control Unicode characters that mess up terminal rendering.
36
+ content = stripInvisibleChars(content);
35
37
  const lines = content.split("\n");
36
38
  const isLong = lines.length > ASSISTANT_MSG_COLLAPSED_LINES;
37
39
  // Phase 5 F7 — the FINAL assistant message in a turn IS the answer the
@@ -114,8 +116,34 @@ function MessageViewImpl({ entry, index, t, modeColor, expandedMessages, mcpRun,
114
116
  switch (entry.type) {
115
117
  case "user":
116
118
  return (_jsx("box", { border: ["left"], customBorderChars: SPLIT, borderColor: entry.modeColor || modeColor, marginTop: index === 0 ? 0 : 1, marginBottom: 1, children: _jsxs("box", { paddingTop: 1, paddingBottom: 1, paddingLeft: 2, backgroundColor: t.backgroundPanel, flexShrink: 0, flexDirection: "column", children: [entry.sourceLabel ? _jsx("text", { fg: t.textMuted, children: entry.sourceLabel }) : null, _jsx(UserMessageContent, { content: entry.content, t: t, expanded: expandedMessages?.has(index) ?? false })] }) }));
117
- case "assistant":
118
- return (_jsxs("box", { paddingLeft: 3, marginTop: 1, flexShrink: 0, flexDirection: "column", children: [entry.sourceLabel ? _jsx("text", { fg: t.textMuted, children: entry.sourceLabel }) : null, _jsx(AssistantMessageContent, { content: entry.content, t: t, expanded: expandedMessages?.has(index) ?? false, isFinal: isFinalAssistant === true })] }));
119
+ case "assistant": {
120
+ let fallbackSr = null;
121
+ const rawContent = entry.content.trim();
122
+ if (rawContent.startsWith("{") && rawContent.endsWith("}")) {
123
+ try {
124
+ const parsed = JSON.parse(rawContent);
125
+ if (Array.isArray(parsed.steps) && parsed.steps.length > 0 && parsed.steps[0].action) {
126
+ fallbackSr = { taskType: "plan", data: parsed };
127
+ }
128
+ else if (Array.isArray(parsed.changes) && parsed.changes.length > 0) {
129
+ fallbackSr = { taskType: "refactor", data: parsed };
130
+ }
131
+ else if (parsed.hypothesis && parsed.root_cause) {
132
+ fallbackSr = { taskType: "debug", data: parsed };
133
+ }
134
+ else if (Array.isArray(parsed.findings)) {
135
+ fallbackSr = { taskType: "analyze", data: parsed };
136
+ }
137
+ else if (Array.isArray(parsed.files) && parsed.files.length > 0 && parsed.files[0].path) {
138
+ fallbackSr = { taskType: "generate", data: parsed };
139
+ }
140
+ }
141
+ catch {
142
+ // ignore
143
+ }
144
+ }
145
+ return (_jsxs("box", { paddingLeft: 3, marginTop: 1, flexShrink: 0, flexDirection: "column", children: [entry.sourceLabel ? _jsx("text", { fg: t.textMuted, children: entry.sourceLabel }) : null, entry.reasoning ? (_jsxs("box", { paddingLeft: 0, marginBottom: 1, flexShrink: 0, flexDirection: "column", children: [_jsx("text", { fg: t.textMuted, children: "[Thought] Thought" }), _jsx("box", { border: ["left"], borderColor: t.textMuted, paddingLeft: 2, marginTop: 1, flexDirection: "column", children: _jsx(Markdown, { content: entry.reasoning, t: t }) })] })) : null, fallbackSr ? (_jsx(StructuredResponseView, { t: t, sr: fallbackSr, modeColor: entry.modeColor || modeColor })) : (_jsx(AssistantMessageContent, { content: entry.content, t: t, expanded: expandedMessages?.has(index) ?? false, isFinal: isFinalAssistant === true }))] }));
146
+ }
119
147
  case "tool_call":
120
148
  return (_jsx("box", { paddingLeft: 3, marginTop: 1, children: _jsxs("text", { children: [_jsx("span", { style: { fg: entry.modeColor || modeColor }, children: "▣ " }), _jsx("span", { style: { fg: t.textMuted }, children: entry.content.replace("▣ ", "") })] }) }));
121
149
  case "tool_group":
@@ -234,6 +262,12 @@ export const MessageView = memo(MessageViewImpl, (prev, next) => {
234
262
  return false;
235
263
  if ((prevMcp?.count ?? 0) !== (nextMcp?.count ?? 0))
236
264
  return false;
265
+ // Phase 5 F7 — isFinalAssistant changes when a new assistant message is
266
+ // appended (the previously-final message is no longer final). Without this
267
+ // check, the memo skips the re-render and the old final assistant stays
268
+ // incorrectly expanded for the rest of the session.
269
+ if ((prev.isFinalAssistant ?? false) !== (next.isFinalAssistant ?? false))
270
+ return false;
237
271
  return true;
238
272
  });
239
273
  //# sourceMappingURL=message-view.js.map
@@ -23,6 +23,13 @@ export function StructuredResponseView({ t, sr, modeColor }) {
23
23
  }
24
24
  case "analyze": {
25
25
  const r = d;
26
+ // Graceful fallback: model may have called respond_analyze but sent a
27
+ // free-form { response: "..." } payload (schema mismatch due to tool being
28
+ // unavailable in the current turn). Render as plain markdown rather than
29
+ // an empty findings list (session 48d22fe436f6 swallowed-answer bug).
30
+ if ((!r.findings || r.findings.length === 0) && typeof r.response === "string" && r.response.trim()) {
31
+ return (_jsx("box", { flexDirection: "column", paddingLeft: 2, marginTop: 1, children: renderMarkdown(r.response, t) }));
32
+ }
26
33
  const sevColor = (s) => (s === "high" ? t.diffRemovedFg : s === "medium" ? t.planStepNum : t.textMuted);
27
34
  return (_jsx("box", { flexDirection: "column", paddingLeft: 2, marginTop: 1, children: (r.findings ?? []).map((f, i) => (_jsxs("box", { flexDirection: "column", children: [_jsxs("text", { children: [_jsx("span", { style: { fg: sevColor(f.severity) }, children: `[${f.severity.toUpperCase()}] ` }), _jsx("span", { children: f.text })] }), _jsx("text", { fg: t.textMuted, children: ` evidence: ${f.evidence}` })] }, `af${i}`))) }));
28
35
  }
@@ -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
@@ -30,11 +30,24 @@ export const handleExpandSlash = async (_args, ctx) => {
30
30
  const latestFile = snapshots[snapshots.length - 1];
31
31
  const latestPath = path.join(historyDir, latestFile);
32
32
  const content = await fs.readFile(latestPath, "utf8");
33
+ // Attempt to read the corresponding .json file for structured messages restore
34
+ let jsonContent = "";
35
+ const jsonPath = latestPath.replace(/\.md$/, ".json");
36
+ try {
37
+ jsonContent = await fs.readFile(jsonPath, "utf8");
38
+ await fs.unlink(jsonPath).catch(() => { });
39
+ }
40
+ catch {
41
+ // no JSON snapshot found (legacy md-only snapshot)
42
+ }
33
43
  // Delete the snapshot file (prevent double-expand per Pitfall 5)
34
44
  await fs.unlink(latestPath);
35
45
  // Count lines for summary
36
46
  const lines = content.split("\n").length;
37
- // Return signal for orchestrator to restore messages
47
+ if (jsonContent) {
48
+ return `__EXPAND_JSON__\n${jsonContent}\nRestored from ${latestFile} (${lines} lines). Previous compaction reversed.\n${content}`;
49
+ }
50
+ // Return signal for orchestrator to restore messages (legacy text fallback)
38
51
  return `__EXPAND__\nRestored from ${latestFile} (${lines} lines). Previous compaction reversed.\n${content}`;
39
52
  };
40
53
  // Self-register on module import
@@ -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, "");