muonroi-cli 1.8.4 → 1.8.5

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 (458) hide show
  1. package/LICENSE +17 -5
  2. package/dist/packages/agent-harness-core/src/driver.d.ts +27 -1
  3. package/dist/packages/agent-harness-core/src/driver.js +46 -0
  4. package/dist/packages/agent-harness-core/src/event-tee.d.ts +48 -0
  5. package/dist/packages/agent-harness-core/src/event-tee.js +77 -0
  6. package/dist/packages/agent-harness-core/src/mcp-server.d.ts +11 -0
  7. package/dist/packages/agent-harness-core/src/mcp-server.js +87 -15
  8. package/dist/packages/agent-harness-core/src/protocol.d.ts +66 -2
  9. package/dist/packages/agent-harness-core/src/protocol.js +15 -0
  10. package/dist/packages/agent-harness-core/src/visual-quality.d.ts +58 -0
  11. package/dist/packages/agent-harness-core/src/visual-quality.js +141 -0
  12. package/dist/packages/agent-harness-opentui/src/agent-mode.d.ts +6 -0
  13. package/dist/packages/agent-harness-opentui/src/agent-mode.js +14 -1
  14. package/dist/packages/agent-harness-opentui/src/input-bridge.d.ts +2 -10
  15. package/dist/packages/agent-harness-opentui/src/input-bridge.js +103 -16
  16. package/dist/packages/agent-harness-opentui/src/install.d.ts +8 -0
  17. package/dist/packages/agent-harness-opentui/src/install.js +10 -0
  18. package/dist/packages/agent-harness-opentui/src/semantic.js +12 -10
  19. package/dist/packages/agent-harness-opentui/src/visual-capture.d.ts +56 -0
  20. package/dist/packages/agent-harness-opentui/src/visual-capture.js +103 -0
  21. package/dist/src/agent-harness/mock-model.d.ts +28 -0
  22. package/dist/src/agent-harness/mock-model.js +63 -1
  23. package/dist/src/agent-harness/test-spawn.js +31 -0
  24. package/dist/src/cli/config/screen-providers.js +1 -1
  25. package/dist/src/cli/cost-forensics.d.ts +10 -0
  26. package/dist/src/cli/cost-forensics.js +18 -3
  27. package/dist/src/cli/keys-bundle.d.ts +1 -1
  28. package/dist/src/cli/keys-bundle.js +1 -1
  29. package/dist/src/cli/keys.d.ts +2 -2
  30. package/dist/src/cli/keys.js +19 -81
  31. package/dist/src/council/clarifier.d.ts +28 -2
  32. package/dist/src/council/clarifier.js +81 -15
  33. package/dist/src/council/context.js +49 -15
  34. package/dist/src/council/debate-checkpoint.d.ts +129 -0
  35. package/dist/src/council/debate-checkpoint.js +176 -0
  36. package/dist/src/council/debate-planner.js +51 -3
  37. package/dist/src/council/debate-summary.d.ts +25 -0
  38. package/dist/src/council/debate-summary.js +85 -0
  39. package/dist/src/council/debate.d.ts +169 -2
  40. package/dist/src/council/debate.js +1210 -134
  41. package/dist/src/council/index.d.ts +85 -1
  42. package/dist/src/council/index.js +634 -196
  43. package/dist/src/council/leader.d.ts +26 -0
  44. package/dist/src/council/leader.js +150 -9
  45. package/dist/src/council/llm.d.ts +32 -0
  46. package/dist/src/council/llm.js +231 -38
  47. package/dist/src/council/panel-select.d.ts +30 -0
  48. package/dist/src/council/panel-select.js +72 -0
  49. package/dist/src/council/planner.js +23 -0
  50. package/dist/src/council/preflight.d.ts +7 -0
  51. package/dist/src/council/preflight.js +14 -2
  52. package/dist/src/council/prompts.d.ts +30 -3
  53. package/dist/src/council/prompts.js +234 -64
  54. package/dist/src/council/stance-recall.d.ts +42 -0
  55. package/dist/src/council/stance-recall.js +57 -0
  56. package/dist/src/council/strip-think.d.ts +17 -0
  57. package/dist/src/council/strip-think.js +33 -0
  58. package/dist/src/council/types.d.ts +128 -0
  59. package/dist/src/ee/artifact-cache.d.ts +16 -0
  60. package/dist/src/ee/artifact-cache.js +32 -0
  61. package/dist/src/ee/auth.d.ts +1 -0
  62. package/dist/src/ee/auth.js +15 -2
  63. package/dist/src/ee/bridge.d.ts +10 -0
  64. package/dist/src/ee/bridge.js +58 -0
  65. package/dist/src/ee/client.js +81 -18
  66. package/dist/src/ee/export-transcripts.d.ts +1 -0
  67. package/dist/src/ee/export-transcripts.js +8 -10
  68. package/dist/src/ee/extract-session.js +29 -0
  69. package/dist/src/ee/extract-style.d.ts +58 -0
  70. package/dist/src/ee/extract-style.js +270 -0
  71. package/dist/src/ee/recall-ledger.d.ts +9 -0
  72. package/dist/src/ee/recall-ledger.js +3 -0
  73. package/dist/src/ee/scope.d.ts +1 -0
  74. package/dist/src/ee/scope.js +26 -1
  75. package/dist/src/ee/search.d.ts +7 -0
  76. package/dist/src/ee/search.js +24 -0
  77. package/dist/src/ee/transcript-emit.js +2 -0
  78. package/dist/src/ee/types.d.ts +22 -0
  79. package/dist/src/ee/who-am-i-brain.d.ts +35 -0
  80. package/dist/src/ee/who-am-i-brain.js +220 -0
  81. package/dist/src/ee/who-am-i.d.ts +10 -3
  82. package/dist/src/ee/who-am-i.js +12 -0
  83. package/dist/src/ee/workflow-event.d.ts +48 -0
  84. package/dist/src/ee/workflow-event.js +81 -0
  85. package/dist/src/flow/compaction/compress.d.ts +3 -3
  86. package/dist/src/flow/compaction/compress.js +45 -8
  87. package/dist/src/flow/compaction/extract.d.ts +4 -7
  88. package/dist/src/flow/compaction/extract.js +50 -10
  89. package/dist/src/flow/compaction/index.d.ts +13 -1
  90. package/dist/src/flow/compaction/index.js +70 -3
  91. package/dist/src/flow/compaction/input-guard.d.ts +24 -0
  92. package/dist/src/flow/compaction/input-guard.js +43 -0
  93. package/dist/src/flow/fold-planning.d.ts +36 -0
  94. package/dist/src/flow/fold-planning.js +83 -0
  95. package/dist/src/flow/hierarchy.d.ts +146 -0
  96. package/dist/src/flow/hierarchy.js +427 -0
  97. package/dist/src/flow/index.d.ts +1 -0
  98. package/dist/src/flow/index.js +2 -0
  99. package/dist/src/flow/run-artifacts.d.ts +102 -0
  100. package/dist/src/flow/run-artifacts.js +208 -0
  101. package/dist/src/generated/version.d.ts +1 -1
  102. package/dist/src/generated/version.js +1 -1
  103. package/dist/src/gsd/assessment-schema.d.ts +44 -0
  104. package/dist/src/gsd/assessment-schema.js +134 -0
  105. package/dist/src/gsd/capability-registry.d.ts +45 -0
  106. package/dist/src/gsd/capability-registry.js +337 -0
  107. package/dist/src/gsd/complexity-assessor.d.ts +39 -0
  108. package/dist/src/gsd/complexity-assessor.js +152 -0
  109. package/dist/src/gsd/config-bridge.d.ts +7 -0
  110. package/dist/src/gsd/config-bridge.js +114 -0
  111. package/dist/src/gsd/config-loader.d.ts +27 -0
  112. package/dist/src/gsd/config-loader.js +50 -0
  113. package/dist/src/gsd/council-context.d.ts +44 -0
  114. package/dist/src/gsd/council-context.js +114 -0
  115. package/dist/src/gsd/ee-closure.d.ts +28 -0
  116. package/dist/src/gsd/ee-closure.js +49 -0
  117. package/dist/src/gsd/flags.d.ts +55 -0
  118. package/dist/src/gsd/flags.js +83 -0
  119. package/dist/src/gsd/gsd-dispatch.d.ts +58 -0
  120. package/dist/src/gsd/gsd-dispatch.js +131 -0
  121. package/dist/src/gsd/gsd-runtime.d.ts +22 -0
  122. package/dist/src/gsd/gsd-runtime.js +37 -0
  123. package/dist/src/gsd/host-adapter.d.ts +11 -0
  124. package/dist/src/gsd/host-adapter.js +29 -0
  125. package/dist/src/gsd/index.d.ts +24 -1
  126. package/dist/src/gsd/index.js +27 -0
  127. package/dist/src/gsd/loop-host-contract.d.ts +21 -0
  128. package/dist/src/gsd/loop-host-contract.js +39 -0
  129. package/dist/src/gsd/loop-host.d.ts +69 -0
  130. package/dist/src/gsd/loop-host.js +245 -0
  131. package/dist/src/gsd/loop-resolver.d.ts +36 -0
  132. package/dist/src/gsd/loop-resolver.js +79 -0
  133. package/dist/src/gsd/model-tier.d.ts +13 -0
  134. package/dist/src/gsd/model-tier.js +45 -0
  135. package/dist/src/gsd/mutation-gate.d.ts +16 -0
  136. package/dist/src/gsd/mutation-gate.js +41 -0
  137. package/dist/src/gsd/native-roadmap.d.ts +89 -0
  138. package/dist/src/gsd/native-roadmap.js +343 -0
  139. package/dist/src/gsd/native-state.d.ts +47 -0
  140. package/dist/src/gsd/native-state.js +220 -0
  141. package/dist/src/gsd/paths.d.ts +23 -0
  142. package/dist/src/gsd/paths.js +66 -0
  143. package/dist/src/gsd/phase-dag.d.ts +12 -0
  144. package/dist/src/gsd/phase-dag.js +94 -0
  145. package/dist/src/gsd/phase-sync.d.ts +42 -0
  146. package/dist/src/gsd/phase-sync.js +321 -0
  147. package/dist/src/gsd/pil-gate-context.d.ts +13 -0
  148. package/dist/src/gsd/pil-gate-context.js +64 -0
  149. package/dist/src/gsd/pil-gate-critic.d.ts +19 -0
  150. package/dist/src/gsd/pil-gate-critic.js +74 -0
  151. package/dist/src/gsd/plan-council-prompts.d.ts +25 -0
  152. package/dist/src/gsd/plan-council-prompts.js +79 -0
  153. package/dist/src/gsd/plan-council.d.ts +44 -0
  154. package/dist/src/gsd/plan-council.js +251 -0
  155. package/dist/src/gsd/plan-gate-vocabulary.d.ts +40 -0
  156. package/dist/src/gsd/plan-gate-vocabulary.js +64 -0
  157. package/dist/src/gsd/product-workspace.d.ts +13 -0
  158. package/dist/src/gsd/product-workspace.js +124 -0
  159. package/dist/src/gsd/ship-bridge.d.ts +25 -0
  160. package/dist/src/gsd/ship-bridge.js +65 -0
  161. package/dist/src/gsd/state-document.d.ts +40 -0
  162. package/dist/src/gsd/state-document.js +163 -0
  163. package/dist/src/gsd/verdict-schema.d.ts +39 -0
  164. package/dist/src/gsd/verdict-schema.js +144 -0
  165. package/dist/src/gsd/verify-context.d.ts +22 -0
  166. package/dist/src/gsd/verify-context.js +27 -0
  167. package/dist/src/gsd/verify-council-prompts.d.ts +19 -0
  168. package/dist/src/gsd/verify-council-prompts.js +85 -0
  169. package/dist/src/gsd/verify-council.d.ts +25 -0
  170. package/dist/src/gsd/verify-council.js +119 -0
  171. package/dist/src/gsd/verify-gate-vocabulary.d.ts +25 -0
  172. package/dist/src/gsd/verify-gate-vocabulary.js +46 -0
  173. package/dist/src/gsd/workflow-engine.d.ts +60 -0
  174. package/dist/src/gsd/workflow-engine.js +207 -0
  175. package/dist/src/gsd/workflow-tools.d.ts +13 -0
  176. package/dist/src/gsd/workflow-tools.js +277 -0
  177. package/dist/src/hooks/index.js +1 -1
  178. package/dist/src/index.js +44 -11
  179. package/dist/src/maintain/pr-builder.js +23 -13
  180. package/dist/src/mcp/auto-setup.js +57 -32
  181. package/dist/src/mcp/client-pool.js +1 -1
  182. package/dist/src/mcp/ee-tools.js +1 -0
  183. package/dist/src/mcp/research-onboarding.js +8 -7
  184. package/dist/src/mcp/runtime.js +34 -2
  185. package/dist/src/models/catalog-client.d.ts +87 -0
  186. package/dist/src/models/catalog-client.js +105 -38
  187. package/dist/src/models/catalog.json +528 -265
  188. package/dist/src/models/registry.d.ts +22 -7
  189. package/dist/src/models/registry.js +73 -10
  190. package/dist/src/ops/doctor.js +1 -1
  191. package/dist/src/orchestrator/auto-commit.js +1 -1
  192. package/dist/src/orchestrator/batch-turn-runner.js +2 -2
  193. package/dist/src/orchestrator/cache-prefix.d.ts +67 -0
  194. package/dist/src/orchestrator/cache-prefix.js +83 -0
  195. package/dist/src/orchestrator/compact-request.d.ts +32 -0
  196. package/dist/src/orchestrator/compact-request.js +41 -0
  197. package/dist/src/orchestrator/compaction.d.ts +10 -0
  198. package/dist/src/orchestrator/compaction.js +27 -7
  199. package/dist/src/orchestrator/council-manager.d.ts +12 -3
  200. package/dist/src/orchestrator/council-manager.js +65 -24
  201. package/dist/src/orchestrator/cross-turn-dedup.d.ts +6 -0
  202. package/dist/src/orchestrator/cross-turn-dedup.js +22 -1
  203. package/dist/src/orchestrator/error-utils.d.ts +29 -0
  204. package/dist/src/orchestrator/error-utils.js +132 -24
  205. package/dist/src/orchestrator/grounding-check.js +39 -1
  206. package/dist/src/orchestrator/message-processor.js +242 -33
  207. package/dist/src/orchestrator/orchestrator.d.ts +39 -3
  208. package/dist/src/orchestrator/orchestrator.js +651 -102
  209. package/dist/src/orchestrator/preprocessor.js +1 -1
  210. package/dist/src/orchestrator/proactive-compact-detector.d.ts +26 -0
  211. package/dist/src/orchestrator/proactive-compact-detector.js +36 -0
  212. package/dist/src/orchestrator/prompts.js +17 -17
  213. package/dist/src/orchestrator/reactive-delegation.d.ts +39 -0
  214. package/dist/src/orchestrator/reactive-delegation.js +59 -0
  215. package/dist/src/orchestrator/retry-classifier.d.ts +2 -1
  216. package/dist/src/orchestrator/retry-classifier.js +46 -2
  217. package/dist/src/orchestrator/safety-intercept.d.ts +45 -0
  218. package/dist/src/orchestrator/safety-intercept.js +55 -0
  219. package/dist/src/orchestrator/scope-reminder.js +1 -1
  220. package/dist/src/orchestrator/session-experience.d.ts +2 -1
  221. package/dist/src/orchestrator/session-experience.js +2 -1
  222. package/dist/src/orchestrator/should-run-gate.d.ts +5 -0
  223. package/dist/src/orchestrator/should-run-gate.js +18 -0
  224. package/dist/src/orchestrator/stall-watchdog.d.ts +24 -3
  225. package/dist/src/orchestrator/stall-watchdog.js +47 -13
  226. package/dist/src/orchestrator/stream-runner.js +62 -29
  227. package/dist/src/orchestrator/sub-agent-cap.d.ts +13 -1
  228. package/dist/src/orchestrator/sub-agent-cap.js +16 -1
  229. package/dist/src/orchestrator/sub-agent-model-tier.d.ts +13 -1
  230. package/dist/src/orchestrator/sub-agent-model-tier.js +16 -2
  231. package/dist/src/orchestrator/subagent-compactor.d.ts +53 -1
  232. package/dist/src/orchestrator/subagent-compactor.js +126 -15
  233. package/dist/src/orchestrator/tool-engine.d.ts +22 -0
  234. package/dist/src/orchestrator/tool-engine.js +620 -56
  235. package/dist/src/orchestrator/tool-limit-auto-recover.d.ts +22 -0
  236. package/dist/src/orchestrator/tool-limit-auto-recover.js +30 -0
  237. package/dist/src/orchestrator/turn-runner-deps.d.ts +19 -0
  238. package/dist/src/orchestrator/turn-watchdog.d.ts +37 -0
  239. package/dist/src/orchestrator/turn-watchdog.js +55 -0
  240. package/dist/src/pil/agent-operating-contract.d.ts +1 -1
  241. package/dist/src/pil/agent-operating-contract.js +1 -1
  242. package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
  243. package/dist/src/pil/cheap-model-playbook.js +5 -1
  244. package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
  245. package/dist/src/pil/cheap-model-workbooks.js +1 -1
  246. package/dist/src/pil/discovery-types.d.ts +1 -0
  247. package/dist/src/pil/discovery.js +16 -11
  248. package/dist/src/pil/layer1-intent.d.ts +18 -6
  249. package/dist/src/pil/layer1-intent.js +66 -757
  250. package/dist/src/pil/layer15-context-scan.js +15 -1
  251. package/dist/src/pil/layer3-ee-injection.js +23 -8
  252. package/dist/src/pil/layer4-gsd.js +69 -16
  253. package/dist/src/pil/layer5-context.js +7 -3
  254. package/dist/src/pil/layer6-output.d.ts +23 -0
  255. package/dist/src/pil/layer6-output.js +5 -1
  256. package/dist/src/pil/llm-classify.d.ts +33 -2
  257. package/dist/src/pil/llm-classify.js +123 -131
  258. package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
  259. package/dist/src/pil/native-capabilities-workbook.js +1 -0
  260. package/dist/src/pil/pipeline.js +34 -2
  261. package/dist/src/pil/response-tools.js +5 -3
  262. package/dist/src/pil/schema.d.ts +1 -0
  263. package/dist/src/pil/schema.js +2 -0
  264. package/dist/src/pil/types.d.ts +18 -0
  265. package/dist/src/playbook/directives.d.ts +4 -0
  266. package/dist/src/playbook/directives.js +17 -5
  267. package/dist/src/product-loop/backlog-builder.d.ts +14 -1
  268. package/dist/src/product-loop/backlog-builder.js +30 -6
  269. package/dist/src/product-loop/discovery-context-format.js +3 -1
  270. package/dist/src/product-loop/discovery-ecosystem.js +4 -1
  271. package/dist/src/product-loop/discovery-interview.js +32 -3
  272. package/dist/src/product-loop/discovery-schema.js +5 -1
  273. package/dist/src/product-loop/ideal-trace.d.ts +7 -0
  274. package/dist/src/product-loop/ideal-trace.js +64 -0
  275. package/dist/src/product-loop/index.d.ts +13 -1
  276. package/dist/src/product-loop/index.js +333 -52
  277. package/dist/src/product-loop/loop-driver.d.ts +7 -0
  278. package/dist/src/product-loop/loop-driver.js +310 -99
  279. package/dist/src/product-loop/phase-plan.d.ts +5 -0
  280. package/dist/src/product-loop/phase-plan.js +39 -2
  281. package/dist/src/product-loop/phase-runner.js +9 -1
  282. package/dist/src/product-loop/sprint-runner.d.ts +111 -0
  283. package/dist/src/product-loop/sprint-runner.js +559 -16
  284. package/dist/src/product-loop/types.d.ts +36 -5
  285. package/dist/src/providers/adapter.d.ts +1 -1
  286. package/dist/src/providers/adapter.js +3 -4
  287. package/dist/src/providers/auth/browser-flow.d.ts +1 -1
  288. package/dist/src/providers/auth/browser-flow.js +1 -1
  289. package/dist/src/providers/auth/openai-oauth.js +1 -1
  290. package/dist/src/providers/auth/registry.js +0 -34
  291. package/dist/src/providers/auth/token-store.js +4 -1
  292. package/dist/src/providers/auth/types.d.ts +1 -1
  293. package/dist/src/providers/auth/types.js +1 -1
  294. package/dist/src/providers/capabilities.d.ts +24 -5
  295. package/dist/src/providers/capabilities.js +42 -24
  296. package/dist/src/providers/endpoints.d.ts +2 -2
  297. package/dist/src/providers/endpoints.js +11 -10
  298. package/dist/src/providers/keychain.d.ts +1 -1
  299. package/dist/src/providers/keychain.js +7 -9
  300. package/dist/src/providers/mcp-vision-bridge.js +56 -146
  301. package/dist/src/providers/openai-compatible.js +8 -1
  302. package/dist/src/providers/pricing.d.ts +2 -2
  303. package/dist/src/providers/pricing.js +3 -13
  304. package/dist/src/providers/runtime.d.ts +27 -2
  305. package/dist/src/providers/runtime.js +78 -15
  306. package/dist/src/providers/strategies/base.strategy.d.ts +16 -0
  307. package/dist/src/providers/strategies/base.strategy.js +24 -1
  308. package/dist/src/providers/strategies/{siliconflow.strategy.d.ts → opencode-go.strategy.d.ts} +3 -3
  309. package/dist/src/providers/strategies/opencode-go.strategy.js +83 -0
  310. package/dist/src/providers/strategies/registry.js +4 -4
  311. package/dist/src/providers/strategies/thinking-mode.d.ts +109 -0
  312. package/dist/src/providers/strategies/thinking-mode.js +280 -1
  313. package/dist/src/providers/strategies/zai.strategy.d.ts +14 -0
  314. package/dist/src/providers/strategies/zai.strategy.js +44 -0
  315. package/dist/src/providers/types.d.ts +5 -6
  316. package/dist/src/providers/types.js +2 -2
  317. package/dist/src/providers/vision-backend.d.ts +47 -0
  318. package/dist/src/providers/vision-backend.js +258 -0
  319. package/dist/src/providers/vision-proxy.d.ts +22 -9
  320. package/dist/src/providers/vision-proxy.js +63 -132
  321. package/dist/src/providers/wire-debug.js +95 -0
  322. package/dist/src/router/decide.d.ts +13 -0
  323. package/dist/src/router/decide.js +138 -36
  324. package/dist/src/router/peak-hour.d.ts +38 -0
  325. package/dist/src/router/peak-hour.js +107 -0
  326. package/dist/src/router/step-router.js +3 -2
  327. package/dist/src/router/warm.js +4 -5
  328. package/dist/src/scaffold/continuation-prompt.d.ts +11 -0
  329. package/dist/src/scaffold/continuation-prompt.js +26 -0
  330. package/dist/src/scaffold/point-to-existing.d.ts +21 -0
  331. package/dist/src/scaffold/point-to-existing.js +25 -0
  332. package/dist/src/self-qa/agentic-loop.js +3 -3
  333. package/dist/src/{ui/state → state}/active-run.d.ts +19 -0
  334. package/dist/src/{ui/state → state}/active-run.js +21 -0
  335. package/dist/src/{ui/status-bar/store.d.ts → state/status-bar-store.d.ts} +4 -1
  336. package/dist/src/{ui/status-bar/store.js → state/status-bar-store.js} +12 -9
  337. package/dist/src/state/turn-trace.d.ts +43 -0
  338. package/dist/src/state/turn-trace.js +32 -0
  339. package/dist/src/storage/db.js +2 -1
  340. package/dist/src/storage/index.d.ts +1 -1
  341. package/dist/src/storage/index.js +1 -1
  342. package/dist/src/storage/interaction-log.d.ts +1 -1
  343. package/dist/src/storage/migrations.js +71 -1
  344. package/dist/src/storage/sessions.d.ts +28 -10
  345. package/dist/src/storage/sessions.js +78 -21
  346. package/dist/src/storage/transcript-view.js +1 -1
  347. package/dist/src/storage/transcript.d.ts +51 -0
  348. package/dist/src/storage/transcript.js +284 -13
  349. package/dist/src/tools/file.d.ts +15 -0
  350. package/dist/src/tools/file.js +32 -0
  351. package/dist/src/tools/native-tools.js +5 -0
  352. package/dist/src/tools/registry.d.ts +3 -0
  353. package/dist/src/tools/registry.js +460 -22
  354. package/dist/src/tools/research.d.ts +29 -0
  355. package/dist/src/tools/research.js +233 -0
  356. package/dist/src/types/index.d.ts +118 -3
  357. package/dist/src/ui/app.js +0 -0
  358. package/dist/src/ui/cards/product-status-card.js +1 -1
  359. package/dist/src/ui/components/bubble-body-guard.d.ts +27 -0
  360. package/dist/src/ui/components/bubble-body-guard.js +50 -0
  361. package/dist/src/ui/components/context-rail.d.ts +26 -0
  362. package/dist/src/ui/components/context-rail.js +33 -0
  363. package/dist/src/ui/components/council-conclusion-card.d.ts +73 -0
  364. package/dist/src/ui/components/council-conclusion-card.js +420 -0
  365. package/dist/src/ui/components/council-debate-pill.d.ts +36 -0
  366. package/dist/src/ui/components/council-debate-pill.js +34 -0
  367. package/dist/src/ui/components/council-info-card.js +2 -2
  368. package/dist/src/ui/components/council-leader-bubble.d.ts +10 -3
  369. package/dist/src/ui/components/council-leader-bubble.js +21 -11
  370. package/dist/src/ui/components/council-message-bubble.d.ts +15 -2
  371. package/dist/src/ui/components/council-message-bubble.js +16 -15
  372. package/dist/src/ui/components/council-phase-timeline.d.ts +9 -1
  373. package/dist/src/ui/components/council-phase-timeline.js +49 -15
  374. package/dist/src/ui/components/council-placeholder-bubble.d.ts +16 -9
  375. package/dist/src/ui/components/council-placeholder-bubble.js +32 -29
  376. package/dist/src/ui/components/council-question-card.js +12 -12
  377. package/dist/src/ui/components/council-rail-rounds.d.ts +26 -0
  378. package/dist/src/ui/components/council-rail-rounds.js +57 -0
  379. package/dist/src/ui/components/council-round-group.d.ts +38 -0
  380. package/dist/src/ui/components/council-round-group.js +88 -0
  381. package/dist/src/ui/components/council-status-list.d.ts +3 -1
  382. package/dist/src/ui/components/council-status-list.js +36 -24
  383. package/dist/src/ui/components/council-synthesis-banner.d.ts +7 -2
  384. package/dist/src/ui/components/council-synthesis-banner.js +20 -5
  385. package/dist/src/ui/components/halt-recovery-card.js +9 -5
  386. package/dist/src/ui/components/jump-to-latest-pill.d.ts +11 -0
  387. package/dist/src/ui/components/jump-to-latest-pill.js +14 -0
  388. package/dist/src/ui/components/prompt-box.js +18 -16
  389. package/dist/src/ui/components/session-tree-card.d.ts +14 -0
  390. package/dist/src/ui/components/session-tree-card.js +46 -0
  391. package/dist/src/ui/components/slash-inline-menu.d.ts +12 -0
  392. package/dist/src/ui/components/slash-inline-menu.js +26 -5
  393. package/dist/src/ui/components/task-list-panel.d.ts +14 -1
  394. package/dist/src/ui/components/task-list-panel.js +22 -2
  395. package/dist/src/ui/containers/modals-layer.d.ts +2 -1
  396. package/dist/src/ui/hooks/use-session-picker.d.ts +3 -3
  397. package/dist/src/ui/mcp-modal.js +2 -4
  398. package/dist/src/ui/modals/api-key-modal.js +1 -1
  399. package/dist/src/ui/modals/connect-modal.js +4 -3
  400. package/dist/src/ui/modals/session-picker-modal.d.ts +2 -2
  401. package/dist/src/ui/modals/session-picker-modal.js +3 -5
  402. package/dist/src/ui/picker-providers.d.ts +1 -1
  403. package/dist/src/ui/picker-providers.js +1 -1
  404. package/dist/src/ui/primitives/index.d.ts +1 -0
  405. package/dist/src/ui/primitives/index.js +2 -0
  406. package/dist/src/ui/primitives/semantic-primitives.d.ts +76 -0
  407. package/dist/src/ui/primitives/semantic-primitives.js +81 -0
  408. package/dist/src/ui/slash/compact.js +5 -7
  409. package/dist/src/ui/slash/cost.js +1 -1
  410. package/dist/src/ui/slash/council.js +19 -1
  411. package/dist/src/ui/slash/debug.d.ts +3 -31
  412. package/dist/src/ui/slash/debug.js +9 -20
  413. package/dist/src/ui/slash/ideal.d.ts +6 -2
  414. package/dist/src/ui/slash/ideal.js +97 -7
  415. package/dist/src/ui/slash/menu-items.d.ts +7 -0
  416. package/dist/src/ui/slash/menu-items.js +12 -18
  417. package/dist/src/ui/slash/registry.d.ts +2 -0
  418. package/dist/src/ui/slash/registry.js +4 -0
  419. package/dist/src/ui/status-bar/cache-hit.d.ts +6 -0
  420. package/dist/src/ui/status-bar/cache-hit.js +9 -0
  421. package/dist/src/ui/status-bar/index.d.ts +1 -1
  422. package/dist/src/ui/status-bar/index.js +7 -3
  423. package/dist/src/ui/status-bar/usd-meter.d.ts +5 -4
  424. package/dist/src/ui/status-bar/usd-meter.js +6 -4
  425. package/dist/src/ui/theme.d.ts +1 -0
  426. package/dist/src/ui/theme.js +2 -0
  427. package/dist/src/ui/types.d.ts +7 -0
  428. package/dist/src/ui/use-app-logic.js +0 -0
  429. package/dist/src/ui/utils/format.d.ts +14 -0
  430. package/dist/src/ui/utils/format.js +23 -3
  431. package/dist/src/usage/downgrade.js +2 -2
  432. package/dist/src/usage/product-ledger.js +2 -2
  433. package/dist/src/utils/install-manager.js +2 -1
  434. package/dist/src/utils/logger.js +2 -2
  435. package/dist/src/utils/permission-mode.js +5 -3
  436. package/dist/src/utils/redactor.js +1 -1
  437. package/dist/src/utils/settings.d.ts +153 -5
  438. package/dist/src/utils/settings.js +233 -29
  439. package/dist/src/utils/visible-retry.d.ts +11 -0
  440. package/dist/src/utils/visible-retry.js +10 -1
  441. package/dist/src/verify/entrypoint.d.ts +1 -1
  442. package/dist/src/verify/entrypoint.js +1 -1
  443. package/dist/src/verify/recipes.d.ts +13 -0
  444. package/dist/src/verify/recipes.js +15 -0
  445. package/package.json +135 -132
  446. package/dist/src/providers/auth/gcloud.d.ts +0 -28
  447. package/dist/src/providers/auth/gcloud.js +0 -102
  448. package/dist/src/providers/auth/gemini-oauth.d.ts +0 -82
  449. package/dist/src/providers/auth/gemini-oauth.js +0 -472
  450. package/dist/src/providers/gemini.d.ts +0 -11
  451. package/dist/src/providers/gemini.js +0 -45
  452. package/dist/src/providers/siliconflow-sse-repair.d.ts +0 -58
  453. package/dist/src/providers/siliconflow-sse-repair.js +0 -177
  454. package/dist/src/providers/strategies/google.strategy.d.ts +0 -22
  455. package/dist/src/providers/strategies/google.strategy.js +0 -174
  456. package/dist/src/providers/strategies/siliconflow.strategy.js +0 -29
  457. package/dist/src/ui/containers/chat-feed.d.ts +0 -40
  458. package/dist/src/ui/containers/chat-feed.js +0 -66
@@ -0,0 +1,141 @@
1
+ /**
2
+ * visual-quality.ts — programmatic visual-quality heuristics over a VisualFrame.
3
+ *
4
+ * P1 of Harness TUI v2. Given the ground-truth rendered cell grid, compute cheap
5
+ * ground-truth signals a human would call "messy" — WITHOUT a vision model:
6
+ * - blank / near-blank row ratios (the council "↳" + "· Skip" garbage)
7
+ * - long runs of consecutive blank rows (dead space)
8
+ * - whitespace density
9
+ * - mojibake (U+FFFD replacement chars + misdecoded-UTF8 clusters like `💡`)
10
+ *
11
+ * These are the exact defects the semantic tree (LiveFrame) is blind to — the
12
+ * 2217600e1f27 export had 12 near-empty rows + mojibake that no existing
13
+ * self-verify check could see. A composite `score` + `issues[]` lets self-QA
14
+ * fail a render regression automatically.
15
+ *
16
+ * Thresholds are conservative defaults; tune per surface. This is a heuristic,
17
+ * not a proof — a low score flags "look closer", it does not assert a bug.
18
+ */
19
+ export const DEFAULT_VISUAL_QUALITY_THRESHOLDS = {
20
+ nearEmptyMaxChars: 2,
21
+ nearEmptyRowPct: 0.5,
22
+ maxConsecutiveBlankRows: 4,
23
+ mojibakeCount: 1,
24
+ whitespacePct: 0.92,
25
+ };
26
+ // Characters that legitimately never (or almost never) appear in this TUI but
27
+ // are produced when UTF-8 bytes are re-decoded as CP437/latin1. Presence in a
28
+ // 2+ cluster is a strong mojibake signal (e.g. 💡 → "≡ƒÆí", em-dash → "ΓÇô").
29
+ const MOJIBAKE_SIGNAL = new Set([
30
+ ..."≡ƒÆÇÃÂ’ÿÖÜúÑŒ¬".split(""),
31
+ "�", // replacement char
32
+ ]);
33
+ function rowText(line) {
34
+ return line.spans.map((s) => s.text).join("");
35
+ }
36
+ function countMojibake(text) {
37
+ let count = 0;
38
+ let sample;
39
+ // Definitive: U+FFFD replacement chars.
40
+ for (const ch of text) {
41
+ if (ch === "�")
42
+ count++;
43
+ }
44
+ // Clusters of 2+ consecutive signal chars — mojibake arrives in runs, which
45
+ // keeps ordinary accented text (single é, ü) from tripping the heuristic.
46
+ const chars = [...text];
47
+ let run = 0;
48
+ for (let i = 0; i <= chars.length; i++) {
49
+ const isSignal = i < chars.length && MOJIBAKE_SIGNAL.has(chars[i]) && chars[i] !== "�";
50
+ if (isSignal) {
51
+ run++;
52
+ }
53
+ else {
54
+ if (run >= 2) {
55
+ count += run;
56
+ if (!sample)
57
+ sample = chars.slice(Math.max(0, i - run - 4), i + 2).join("");
58
+ }
59
+ run = 0;
60
+ }
61
+ }
62
+ return { count, sample };
63
+ }
64
+ /**
65
+ * Compute visual-quality signals for a rendered frame. Pure + deterministic.
66
+ */
67
+ export function computeVisualQuality(frame, thresholds = DEFAULT_VISUAL_QUALITY_THRESHOLDS) {
68
+ const rows = frame.lines.length;
69
+ const cols = frame.cols;
70
+ let contentChars = 0;
71
+ let totalCells = 0;
72
+ let blankRows = 0;
73
+ let nearEmptyRows = 0;
74
+ let maxConsecutiveBlankRows = 0;
75
+ let curBlankRun = 0;
76
+ let mojibakeCount = 0;
77
+ const mojibakeSamples = [];
78
+ for (const line of frame.lines) {
79
+ const text = rowText(line);
80
+ const nonSpace = [...text].filter((c) => c.trim().length > 0).length;
81
+ contentChars += nonSpace;
82
+ totalCells += Math.max(text.length, cols);
83
+ if (nonSpace === 0) {
84
+ blankRows++;
85
+ curBlankRun++;
86
+ maxConsecutiveBlankRows = Math.max(maxConsecutiveBlankRows, curBlankRun);
87
+ }
88
+ else {
89
+ curBlankRun = 0;
90
+ }
91
+ // "Low-content" rows = blank OR only a glyph or two (skeleton noise like the
92
+ // council "↳" / "· Skip" rows, and the blank separators between them). Both
93
+ // are visual garbage, so they count together against the total row budget.
94
+ if (nonSpace <= thresholds.nearEmptyMaxChars)
95
+ nearEmptyRows++;
96
+ const mj = countMojibake(text);
97
+ if (mj.count > 0) {
98
+ mojibakeCount += mj.count;
99
+ if (mj.sample && mojibakeSamples.length < 5)
100
+ mojibakeSamples.push(mj.sample);
101
+ }
102
+ }
103
+ // Ratio of low-content (blank or near-empty) rows over the whole frame.
104
+ const nearEmptyRowPct = rows > 0 ? nearEmptyRows / rows : 0;
105
+ const whitespacePct = totalCells > 0 ? 1 - contentChars / totalCells : 1;
106
+ const issues = [];
107
+ if (nearEmptyRowPct > thresholds.nearEmptyRowPct) {
108
+ issues.push(`${nearEmptyRows}/${rows} rows are blank or near-empty (<=${thresholds.nearEmptyMaxChars} chars) — skeleton/garbage rows`);
109
+ }
110
+ if (maxConsecutiveBlankRows > thresholds.maxConsecutiveBlankRows) {
111
+ issues.push(`${maxConsecutiveBlankRows} consecutive blank rows — large dead gap`);
112
+ }
113
+ if (mojibakeCount >= thresholds.mojibakeCount) {
114
+ issues.push(`${mojibakeCount} mojibake signal(s) detected (misdecoded UTF-8 / U+FFFD)${mojibakeSamples.length ? ` e.g. ${JSON.stringify(mojibakeSamples[0])}` : ""}`);
115
+ }
116
+ if (whitespacePct > thresholds.whitespacePct && contentChars > 0) {
117
+ issues.push(`${(whitespacePct * 100).toFixed(0)}% of the grid is whitespace — very sparse render`);
118
+ }
119
+ // Composite score: start at 100, subtract weighted penalties.
120
+ let score = 100;
121
+ score -= Math.min(40, Math.round(nearEmptyRowPct * 60));
122
+ score -= Math.min(25, Math.max(0, maxConsecutiveBlankRows - thresholds.maxConsecutiveBlankRows) * 4);
123
+ score -= Math.min(30, mojibakeCount * 6);
124
+ score -= whitespacePct > thresholds.whitespacePct ? 10 : 0;
125
+ score = Math.max(0, Math.min(100, score));
126
+ return {
127
+ rows,
128
+ cols,
129
+ contentChars,
130
+ blankRows,
131
+ nearEmptyRows,
132
+ nearEmptyRowPct,
133
+ maxConsecutiveBlankRows,
134
+ whitespacePct,
135
+ mojibakeCount,
136
+ mojibakeSamples,
137
+ score,
138
+ issues,
139
+ };
140
+ }
141
+ //# sourceMappingURL=visual-quality.js.map
@@ -17,6 +17,7 @@
17
17
  */
18
18
  import type { LiveEvent } from "@muonroi/agent-harness-core/protocol";
19
19
  import type { SemanticRegistry } from "./reconciler-hook.js";
20
+ import { type RendererLike } from "./visual-capture.js";
20
21
  export type AgentModeOptions = {
21
22
  cols: number;
22
23
  rows: number;
@@ -40,6 +41,11 @@ export type AgentModeRuntime = {
40
41
  onCommand: (h: (cmd: unknown) => void) => void;
41
42
  /** Get the current timestamp (real or fake clock). */
42
43
  now: () => number;
44
+ /**
45
+ * Attach the OpenTUI renderer once it exists (constructed AFTER this runtime
46
+ * — see src/index.ts). Enables VisualFrame capture of the real cell grid.
47
+ */
48
+ attachRenderer: (renderer: RendererLike) => void;
43
49
  /** Tear down: close streams, dispose idle detector. */
44
50
  dispose: () => void;
45
51
  };
@@ -22,6 +22,7 @@ import { createIdleDetector } from "@muonroi/agent-harness-core/idle";
22
22
  import { createLineSplitter, createSidechannelWriter } from "@muonroi/agent-harness-core/transports/sidechannel";
23
23
  import { installOpenTUIHarness } from "./install.js";
24
24
  import { createSemanticRegistry } from "./reconciler-hook.js";
25
+ import { createVisualCaptureHook } from "./visual-capture.js";
25
26
  // ---------------------------------------------------------------------------
26
27
  // Factory
27
28
  // ---------------------------------------------------------------------------
@@ -90,6 +91,11 @@ export async function startAgentMode(opts) {
90
91
  const now = () => (opts.fakeClock ? 0 : Date.now());
91
92
  // --- Semantic registry ---------------------------------------------------
92
93
  const registry = createSemanticRegistry();
94
+ // --- Visual capture (real rendered cell grid) ----------------------------
95
+ // The renderer does not exist yet — it is constructed after this runtime and
96
+ // handed back via attachRenderer(). The hook reads it late-bound.
97
+ let rendererRef;
98
+ const visualHook = createVisualCaptureHook(() => rendererRef);
93
99
  // --- Idle detector -------------------------------------------------------
94
100
  const idle = createIdleDetector({
95
101
  quiescenceMs: opts.idleMs,
@@ -116,6 +122,9 @@ export async function startAgentMode(opts) {
116
122
  // becomes deterministic (seq*16). Without this, ts = Date.now() and the
117
123
  // determinism spec sees timestamps differ between runs.
118
124
  fakeClock: opts.fakeClock,
125
+ // Emit a VisualFrame (real rendered cell grid) alongside each semantic
126
+ // change. No-op until attachRenderer() supplies the renderer.
127
+ captureVisual: (seq, ts) => visualHook.capture(seq, ts),
119
128
  });
120
129
  // --- Command channel (in stream → handlers) ------------------------------
121
130
  const commandHandlers = [];
@@ -165,6 +174,10 @@ export async function startAgentMode(opts) {
165
174
  const onCommand = (h) => {
166
175
  commandHandlers.push(h);
167
176
  };
177
+ const attachRenderer = (renderer) => {
178
+ rendererRef = renderer;
179
+ visualHook.resetDedup(); // force the first visual frame after attach
180
+ };
168
181
  const dispose = () => {
169
182
  harnessHandle.uninstall(); // stops poll interval; transport.close() ends outStream
170
183
  idle.dispose();
@@ -173,6 +186,6 @@ export async function startAgentMode(opts) {
173
186
  inStream.destroy();
174
187
  }
175
188
  };
176
- return { registry, capture, emitEvent, onCommand, now, dispose };
189
+ return { registry, capture, emitEvent, onCommand, now, attachRenderer, dispose };
177
190
  }
178
191
  //# sourceMappingURL=agent-mode.js.map
@@ -13,15 +13,7 @@
13
13
  * raw byte. We construct ParsedKey directly so we don't depend on terminal
14
14
  * escape sequence conventions.
15
15
  */
16
+ import type { KeyEvent } from "@opentui/core";
16
17
  import type { AgentModeRuntime } from "./agent-mode.js";
17
- /**
18
- * Subscribe to agentRuntime.onCommand and inject synthetic key events into
19
- * OpenTUI's keyHandler. Safe no-op when agentRuntime is undefined (normal user
20
- * mode).
21
- *
22
- * Special key `__focus__:<id>` is intentionally NOT translated to OpenTUI input
23
- * here — it is a driver-level signal currently ignored at this layer. (Focus
24
- * routing in OpenTUI is owned by individual components; a global focus dispatch
25
- * is out of scope for this bridge.)
26
- */
18
+ export declare function keyForNamed(key: string): KeyEvent | null;
27
19
  export declare function useAgentInputBridge(agentRuntime: AgentModeRuntime | undefined): void;
@@ -35,7 +35,54 @@ const NAMED = {
35
35
  PageUp: { name: "pageup", sequence: "\x1b[5~", raw: "\x1b[5~" },
36
36
  PageDown: { name: "pagedown", sequence: "\x1b[6~", raw: "\x1b[6~" },
37
37
  };
38
- function makeKey(partial) {
38
+ // Case-insensitive view of NAMED. The map above is capitalized, but drivers
39
+ // naturally send lowercase ("enter", "escape", "tab", "space") — those missed
40
+ // the exact-case lookup, so keyForNamed returned null and the keystroke was a
41
+ // SILENT no-op (Enter never submitted, Escape never dismissed a modal). Resolve
42
+ // on the lowercased base so every casing works. Includes a couple of common
43
+ // short aliases drivers reach for.
44
+ const NAMED_LC = {
45
+ ...Object.fromEntries(Object.entries(NAMED).map(([k, v]) => [k.toLowerCase(), v])),
46
+ ret: NAMED.Return,
47
+ del: NAMED.Delete,
48
+ spacebar: NAMED.Space,
49
+ pgup: NAMED.PageUp,
50
+ pgdn: NAMED.PageDown,
51
+ };
52
+ /**
53
+ * Strip modifier prefixes (`C-` ctrl, `M-` meta/alt, `S-` shift) off a harness
54
+ * key string so combos like `C-b` or `C-o` can be driven. Prefixes may stack in
55
+ * any order (`C-S-tab`). The remainder is the base key (named or single char).
56
+ */
57
+ function parseModifiers(key) {
58
+ const mods = {};
59
+ let base = key;
60
+ // Only consume a prefix when a base remains after it, so a literal "C" or a
61
+ // hyphen key is not mis-parsed.
62
+ for (;;) {
63
+ if (base.length > 2 && base[1] === "-") {
64
+ const p = base[0];
65
+ if (p === "C" || p === "c") {
66
+ mods.ctrl = true;
67
+ base = base.slice(2);
68
+ continue;
69
+ }
70
+ if (p === "M" || p === "m") {
71
+ mods.meta = true;
72
+ base = base.slice(2);
73
+ continue;
74
+ }
75
+ if (p === "S") {
76
+ mods.shift = true;
77
+ base = base.slice(2);
78
+ continue;
79
+ }
80
+ }
81
+ break;
82
+ }
83
+ return { mods, base };
84
+ }
85
+ function makeKey(partial, mods = {}) {
39
86
  // Some app key-handlers conditionally call key.preventDefault()/stopPropagation()
40
87
  // (typed `?.()` so they tolerate undefined). When the host's keypress arrives
41
88
  // through a terminal, OpenTUI attaches these methods; in agent-mode we
@@ -48,9 +95,9 @@ function makeKey(partial) {
48
95
  let _propagationStopped = false;
49
96
  const ev = {
50
97
  name: partial.name,
51
- ctrl: false,
52
- meta: false,
53
- shift: false,
98
+ ctrl: mods.ctrl ?? false,
99
+ meta: mods.meta ?? false,
100
+ shift: mods.shift ?? false,
54
101
  option: false,
55
102
  sequence: partial.sequence,
56
103
  number: false,
@@ -72,18 +119,22 @@ function makeKey(partial) {
72
119
  };
73
120
  return ev;
74
121
  }
75
- function keyForChar(ch) {
76
- return makeKey({ name: ch, sequence: ch, raw: ch });
122
+ function keyForChar(ch, mods = {}) {
123
+ return makeKey({ name: ch, sequence: ch, raw: ch }, mods);
77
124
  }
78
- function keyForNamed(key) {
79
- const m = NAMED[key];
125
+ export function keyForNamed(key) {
126
+ const { mods, base } = parseModifiers(key);
127
+ // Exact case first (fast path), then case-insensitive so "enter"/"Enter"/
128
+ // "ENTER"/"return" all resolve. Single-letter bases never collide with a
129
+ // NAMED word, so they still fall through to the literal-char path below.
130
+ const m = NAMED[base] ?? NAMED_LC[base.toLowerCase()];
80
131
  if (!m) {
81
132
  // Unknown named key — fall back to treating it as a literal character if it's 1 char.
82
- if (key.length === 1)
83
- return keyForChar(key);
133
+ if (base.length === 1)
134
+ return keyForChar(base, mods);
84
135
  return null;
85
136
  }
86
- return makeKey(m);
137
+ return makeKey(m, mods);
87
138
  }
88
139
  /**
89
140
  * Subscribe to agentRuntime.onCommand and inject synthetic key events into
@@ -95,15 +146,51 @@ function keyForNamed(key) {
95
146
  * routing in OpenTUI is owned by individual components; a global focus dispatch
96
147
  * is out of scope for this bridge.)
97
148
  */
149
+ // Runtimes that already have the input bridge wired — module-level so the guard
150
+ // survives App remounts (a per-component ref would reset and re-register).
151
+ const mountedRuntimes = new WeakSet();
98
152
  export function useAgentInputBridge(agentRuntime) {
99
- const { keyHandler } = useAppContext();
153
+ const ctx = useAppContext();
154
+ const keyHandler = ctx.keyHandler;
155
+ // Focused renderables (the composer textarea) subscribe to keypresses via
156
+ // renderer._internalKeyInput.onInternal("keypress"), NOT via the public
157
+ // keyHandler (useAppContext().keyHandler === renderer.keyInput). On the real
158
+ // TTY renderer both are the SAME InternalKeyHandler, but under the agent-mode
159
+ // headless renderer they are DISTINCT objects — so emitting only on keyHandler
160
+ // reached app-level useKeyboard shortcuts yet never the textarea, and typed
161
+ // text silently vanished (the tier-2 harness-drive bug). Prefer
162
+ // _internalKeyInput: its emitWithPriority dispatches to BOTH the focused
163
+ // renderable AND global listeners registered on it.
164
+ const internal = ctx.renderer?._internalKeyInput;
100
165
  useEffect(() => {
101
- if (!agentRuntime || !keyHandler)
166
+ if (!agentRuntime)
167
+ return;
168
+ // onCommand has no unsubscribe (it only pushes to a handler array), so
169
+ // registering twice doubles every keystroke (observed: a typed prompt
170
+ // arrived duplicated). A per-component ref is not enough — the App can
171
+ // remount (e.g. hero → chat transition) giving a fresh ref while the prior
172
+ // handler stays registered on the same long-lived runtime. Track mounted
173
+ // runtimes in a module-level WeakSet so registration is once-per-runtime
174
+ // across remounts.
175
+ if (mountedRuntimes.has(agentRuntime))
176
+ return;
177
+ const target = internal ?? keyHandler;
178
+ if (!target)
102
179
  return;
103
180
  // Toast-only stub in normal interactive mode provides emitEvent but not
104
181
  // onCommand. Guard so the bridge no-ops cleanly instead of crashing the TUI.
105
182
  if (typeof agentRuntime.onCommand !== "function")
106
183
  return;
184
+ mountedRuntimes.add(agentRuntime);
185
+ // Emit to the renderable channel (the focused textarea), and also to the
186
+ // public keyHandler when it is a distinct object so global shortcuts still
187
+ // fire. The `!== target` guard avoids double-dispatch on the real renderer
188
+ // where the two are identical.
189
+ const emitKey = (k) => {
190
+ target.emit("keypress", k);
191
+ if (keyHandler && keyHandler !== target)
192
+ keyHandler.emit("keypress", k);
193
+ };
107
194
  agentRuntime.onCommand((cmd) => {
108
195
  if (!cmd || typeof cmd !== "object")
109
196
  return;
@@ -113,14 +200,14 @@ export function useAgentInputBridge(agentRuntime) {
113
200
  return;
114
201
  const k = keyForNamed(c.key);
115
202
  if (k)
116
- keyHandler.emit("keypress", k);
203
+ emitKey(k);
117
204
  }
118
205
  else if (c.op === "type" && typeof c.text === "string") {
119
206
  for (const ch of c.text) {
120
- keyHandler.emit("keypress", keyForChar(ch));
207
+ emitKey(keyForChar(ch));
121
208
  }
122
209
  }
123
210
  });
124
- }, [agentRuntime, keyHandler]);
211
+ }, [agentRuntime, internal, keyHandler]);
125
212
  }
126
213
  //# sourceMappingURL=input-bridge.js.map
@@ -41,6 +41,14 @@ export interface InstallOpenTUIHarnessOptions {
41
41
  * the `--agent-fake-clock` CLI flag.
42
42
  */
43
43
  fakeClock?: boolean;
44
+ /**
45
+ * Optional visual-frame capture. When provided, a VisualFrame (the actual
46
+ * rendered cell grid — colors + attributes) is captured and sent right after
47
+ * each SEMANTIC frame change. Piggybacking on semantic changes bounds the
48
+ * per-frame `getSpanLines()` allocation to real UI updates instead of every
49
+ * poll tick. Returns null on dedup / unavailable renderer.
50
+ */
51
+ captureVisual?: (seq: number, ts: number) => object | null;
44
52
  }
45
53
  export interface OpenTUIHarnessHandle {
46
54
  /**
@@ -28,6 +28,7 @@ export function installOpenTUIHarness(opts) {
28
28
  const fps = opts.fps ?? 60;
29
29
  const intervalMs = Math.max(1, Math.round(1000 / fps));
30
30
  let seq = 0;
31
+ let vseq = 0;
31
32
  const fakeClock = opts.fakeClock ?? false;
32
33
  const hook = createReconcilerHook({
33
34
  registry: opts.registry,
@@ -43,6 +44,15 @@ export function installOpenTUIHarness(opts) {
43
44
  try {
44
45
  opts.transport.send(`${JSON.stringify(frame)}\n`);
45
46
  opts.onFrame?.(frame);
47
+ // Piggyback the visual (rendered cell-grid) frame on semantic changes so
48
+ // getSpanLines() only runs when the UI actually changed, not every tick.
49
+ if (opts.captureVisual) {
50
+ const vframe = opts.captureVisual(vseq, fakeClock ? vseq * 16 : Date.now());
51
+ if (vframe !== null) {
52
+ vseq++;
53
+ opts.transport.send(`${JSON.stringify(vframe)}\n`);
54
+ }
55
+ }
46
56
  }
47
57
  catch {
48
58
  // Drop frame on transport error — survivor of next tick will retry.
@@ -31,22 +31,24 @@ export function Semantic(props) {
31
31
  const unregister = registry.register({ ...nodeRef.current, parentId });
32
32
  return unregister;
33
33
  }, [registry, parentId, node.id]);
34
- // Updates: re-register on prop change. This calls register() with the new
35
- // node fields; Map.set on an existing key preserves insertion order.
36
- // biome-ignore lint/correctness/useExhaustiveDependencies: nodeKey is the intentional cache-bust for node fields
34
+ // Updates: patch fields on prop change via registry.update(). nodeKey is the
35
+ // intentional cache-bust dep it serializes the node fields so this effect
36
+ // re-runs whenever any field changes; Map.set on an existing key preserves
37
+ // insertion order.
37
38
  React.useEffect(() => {
38
39
  if (!registry)
39
40
  return;
40
41
  if (!nodeKey)
41
42
  return;
42
43
  // Skip the very first run — the mount effect above already registered.
43
- // We detect "subsequent run" by checking if the entry exists in the
44
- // registry. On mount, the order is: this effect runs after the mount
45
- // effect, so the entry IS present. We then call register() again with
46
- // the same node that's a no-op write that updates fields without
47
- // changing position. On subsequent renders with different nodeKey,
48
- // register() updates the fields in place.
49
- registry.register({ ...nodeRef.current, parentId });
44
+ // On mount this effect runs after the mount effect, so the entry IS
45
+ // present; update() patches fields in place (Map.set on an existing key
46
+ // preserves insertion order). On subsequent renders with a different
47
+ // nodeKey it patches the changed fields. Using update() (not register())
48
+ // is the cheaper intended path it merges instead of rebuilding the
49
+ // entry, and it is a no-op if the entry was already unmounted.
50
+ const { id: _id, ...patch } = nodeRef.current;
51
+ registry.update(nodeRef.current.id, patch);
50
52
  // No cleanup — the mount effect's cleanup is the single source of
51
53
  // truth for unmounting. If we returned a cleanup here, it would race
52
54
  // with re-renders and re-delete the entry.
@@ -0,0 +1,56 @@
1
+ /**
2
+ * visual-capture.ts — read OpenTUI's ACTUAL rendered cell grid.
3
+ *
4
+ * The semantic harness (reconciler-hook) reports STRUCTURE (id/role/name/props)
5
+ * but is blind to what a human SEES — colors, bold/italic, alignment, blank
6
+ * rows, mojibake. This module snapshots the ground-truth render buffer instead:
7
+ * OpenTUI's `CliRenderer.currentRenderBuffer` is a cell grid it already paints,
8
+ * and `OptimizedBuffer.getSpanLines()` returns per-cell char + RGBA fg/bg +
9
+ * attribute bits (verified against @opentui/core@0.1.107 buffer.d.ts:43 +
10
+ * lib/RGBA.d.ts). No OCR — the buffer is authoritative and in-process.
11
+ *
12
+ * Consumers get a `VisualFrame` (protocol) they can query per-cell, render to
13
+ * annotated text, or (P2) rasterize to PNG for a vision model.
14
+ */
15
+ import { type VisualFrame } from "@muonroi/agent-harness-core/protocol";
16
+ interface RgbaLike {
17
+ toInts?: () => [number, number, number, number];
18
+ r?: number;
19
+ g?: number;
20
+ b?: number;
21
+ a?: number;
22
+ }
23
+ interface CapturedSpanLike {
24
+ text: string;
25
+ fg: RgbaLike;
26
+ bg: RgbaLike;
27
+ attributes: number;
28
+ width: number;
29
+ }
30
+ interface CapturedLineLike {
31
+ spans: CapturedSpanLike[];
32
+ }
33
+ interface OptimizedBufferLike {
34
+ width: number;
35
+ height: number;
36
+ getSpanLines: () => CapturedLineLike[];
37
+ }
38
+ export interface RendererLike {
39
+ currentRenderBuffer?: OptimizedBufferLike;
40
+ nextRenderBuffer?: OptimizedBufferLike;
41
+ }
42
+ export interface VisualCaptureHook {
43
+ /** Build a VisualFrame from the renderer's current buffer. Returns null when
44
+ * no renderer is attached, the buffer is unreadable, or content is unchanged
45
+ * (content-hash dedup — same contract as the semantic reconciler hook). */
46
+ capture(seq: number, ts: number): VisualFrame | null;
47
+ /** Reset dedup state so the next capture always emits. */
48
+ resetDedup(): void;
49
+ }
50
+ /**
51
+ * Create a visual-capture hook. `getRenderer` is a late-bound accessor because
52
+ * the OpenTUI renderer is constructed AFTER the agent-mode runtime (which owns
53
+ * this hook) — see src/index.ts attachRenderer wiring.
54
+ */
55
+ export declare function createVisualCaptureHook(getRenderer: () => RendererLike | undefined): VisualCaptureHook;
56
+ export {};
@@ -0,0 +1,103 @@
1
+ /**
2
+ * visual-capture.ts — read OpenTUI's ACTUAL rendered cell grid.
3
+ *
4
+ * The semantic harness (reconciler-hook) reports STRUCTURE (id/role/name/props)
5
+ * but is blind to what a human SEES — colors, bold/italic, alignment, blank
6
+ * rows, mojibake. This module snapshots the ground-truth render buffer instead:
7
+ * OpenTUI's `CliRenderer.currentRenderBuffer` is a cell grid it already paints,
8
+ * and `OptimizedBuffer.getSpanLines()` returns per-cell char + RGBA fg/bg +
9
+ * attribute bits (verified against @opentui/core@0.1.107 buffer.d.ts:43 +
10
+ * lib/RGBA.d.ts). No OCR — the buffer is authoritative and in-process.
11
+ *
12
+ * Consumers get a `VisualFrame` (protocol) they can query per-cell, render to
13
+ * annotated text, or (P2) rasterize to PNG for a vision model.
14
+ */
15
+ import { createHash } from "node:crypto";
16
+ import { PROTOCOL_VERSION, } from "@muonroi/agent-harness-core/protocol";
17
+ /** Low byte of OpenTUI's TextAttributes (ATTRIBUTE_BASE_MASK = 255). */
18
+ const ATTR_MASK = 255;
19
+ function clampByte(n) {
20
+ return Math.max(0, Math.min(255, Math.round(n)));
21
+ }
22
+ /**
23
+ * Convert an OpenTUI RGBA to a "#rrggbb" (or "#rrggbbaa" when translucent) hex
24
+ * string. Prefers `toInts()` (0-255 ints); falls back to component getters,
25
+ * scaling 0-1 floats up to 0-255 when the renderer exposes normalized channels.
26
+ */
27
+ function toHex(c) {
28
+ let r = 0;
29
+ let g = 0;
30
+ let b = 0;
31
+ let a = 255;
32
+ if (c && typeof c.toInts === "function") {
33
+ const ints = c.toInts();
34
+ r = ints[0];
35
+ g = ints[1];
36
+ b = ints[2];
37
+ a = ints[3];
38
+ }
39
+ else if (c) {
40
+ const scale = (v) => (v === undefined ? 0 : v <= 1 ? v * 255 : v);
41
+ r = scale(c.r);
42
+ g = scale(c.g);
43
+ b = scale(c.b);
44
+ a = c.a === undefined ? 255 : c.a <= 1 ? c.a * 255 : c.a;
45
+ }
46
+ const h = (n) => clampByte(n).toString(16).padStart(2, "0");
47
+ return a < 255 ? `#${h(r)}${h(g)}${h(b)}${h(a)}` : `#${h(r)}${h(g)}${h(b)}`;
48
+ }
49
+ /**
50
+ * Create a visual-capture hook. `getRenderer` is a late-bound accessor because
51
+ * the OpenTUI renderer is constructed AFTER the agent-mode runtime (which owns
52
+ * this hook) — see src/index.ts attachRenderer wiring.
53
+ */
54
+ export function createVisualCaptureHook(getRenderer) {
55
+ let lastHash;
56
+ function capture(seq, ts) {
57
+ const renderer = getRenderer();
58
+ const buffer = renderer?.currentRenderBuffer ?? renderer?.nextRenderBuffer;
59
+ if (!buffer || typeof buffer.getSpanLines !== "function")
60
+ return null;
61
+ let captured;
62
+ try {
63
+ captured = buffer.getSpanLines();
64
+ }
65
+ catch (err) {
66
+ // getSpanLines allocates + decodes native Zig memory; a mid-resize/destroy
67
+ // race can throw. Skip this frame (the next tick retries). Debug-gated log
68
+ // only — unconditional logging here would spam the render loop.
69
+ if (process.env.MUONROI_DEBUG_VISUAL) {
70
+ console.error(`[visual-capture] getSpanLines failed: ${err?.message}`);
71
+ }
72
+ return null;
73
+ }
74
+ const lines = captured.map((ln) => ({
75
+ spans: (ln.spans ?? []).map((s) => ({
76
+ text: s.text,
77
+ fg: toHex(s.fg),
78
+ bg: toHex(s.bg),
79
+ attrs: (s.attributes ?? 0) & ATTR_MASK,
80
+ width: s.width,
81
+ })),
82
+ }));
83
+ const hash = createHash("sha1").update(JSON.stringify(lines)).digest("hex");
84
+ if (lastHash === hash)
85
+ return null;
86
+ lastHash = hash;
87
+ return {
88
+ mode: "visual",
89
+ version: PROTOCOL_VERSION,
90
+ seq,
91
+ ts,
92
+ cols: buffer.width,
93
+ rows: buffer.height,
94
+ cursor: null,
95
+ lines,
96
+ };
97
+ }
98
+ function resetDedup() {
99
+ lastHash = undefined;
100
+ }
101
+ return { capture, resetDedup };
102
+ }
103
+ //# sourceMappingURL=visual-capture.js.map