mu-harness 0.16.13 → 0.16.15

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 (423) hide show
  1. package/esm/harness/npm/src/common/index.d.ts +1 -1
  2. package/esm/harness/npm/src/common/index.js +1 -1
  3. package/esm/harness/npm/src/common/utils.d.ts +2 -0
  4. package/esm/harness/npm/src/common/utils.js +9 -0
  5. package/esm/harness/npm/src/harness/create.js +2 -1
  6. package/esm/harness/npm/src/harness/types.d.ts +7 -1
  7. package/esm/harness/npm/src/skills/index.d.ts +2 -1
  8. package/esm/harness/npm/src/skills/index.js +1 -0
  9. package/esm/harness/npm/src/skills/loader.d.ts +9 -1
  10. package/esm/harness/npm/src/skills/loader.js +43 -10
  11. package/esm/harness/npm/src/skills/parser.js +9 -3
  12. package/esm/harness/npm/src/skills/platform.d.ts +6 -0
  13. package/esm/harness/npm/src/skills/platform.js +14 -0
  14. package/esm/harness/npm/src/skills/types.d.ts +2 -0
  15. package/esm/harness/npm/src/skills/writer.d.ts +1 -0
  16. package/esm/harness/npm/src/skills/writer.js +49 -36
  17. package/esm/harness/npm/src/tui/chat/ChatApp.d.ts +188 -0
  18. package/esm/harness/npm/src/tui/chat/ChatApp.js +1306 -0
  19. package/esm/harness/npm/src/tui/chat/commands.d.ts +16 -0
  20. package/esm/harness/npm/src/tui/chat/commands.js +21 -0
  21. package/esm/harness/npm/src/tui/chat/editor.d.ts +31 -0
  22. package/esm/harness/npm/src/tui/chat/editor.js +147 -0
  23. package/esm/harness/npm/src/tui/chat/index.d.ts +9 -0
  24. package/esm/harness/npm/src/tui/chat/index.js +7 -0
  25. package/esm/harness/npm/src/tui/chat/markdown.d.ts +9 -0
  26. package/esm/harness/npm/src/tui/chat/markdown.js +295 -0
  27. package/esm/harness/npm/src/tui/chat/picker.d.ts +13 -0
  28. package/esm/harness/npm/src/tui/chat/picker.js +123 -0
  29. package/esm/harness/npm/src/tui/chat/status.d.ts +15 -0
  30. package/esm/harness/npm/src/tui/chat/status.js +48 -0
  31. package/esm/harness/npm/src/tui/chat/theme.d.ts +97 -0
  32. package/esm/harness/npm/src/tui/chat/theme.js +201 -0
  33. package/esm/harness/npm/src/tui/chat/transcript.d.ts +79 -0
  34. package/esm/harness/npm/src/tui/chat/transcript.js +422 -0
  35. package/esm/harness/npm/src/tui/index.d.ts +8 -7
  36. package/esm/harness/npm/src/tui/index.js +10 -8
  37. package/esm/harness/src/agents/index.d.ts +4 -0
  38. package/esm/harness/src/agents/index.js +3 -0
  39. package/esm/harness/src/agents/loader.d.ts +2 -0
  40. package/esm/harness/src/agents/loader.js +24 -0
  41. package/esm/harness/src/agents/parser.d.ts +2 -0
  42. package/esm/harness/src/agents/parser.js +46 -0
  43. package/esm/harness/src/agents/registry.d.ts +9 -0
  44. package/esm/harness/src/agents/registry.js +88 -0
  45. package/esm/harness/src/agents/types.d.ts +12 -0
  46. package/esm/harness/src/channels/channel.d.ts +7 -0
  47. package/esm/harness/src/channels/channel.js +25 -0
  48. package/esm/harness/src/channels/index.d.ts +3 -0
  49. package/esm/harness/src/channels/index.js +2 -0
  50. package/esm/harness/src/channels/manager.d.ts +6 -0
  51. package/esm/harness/src/channels/manager.js +28 -0
  52. package/esm/harness/src/channels/types.d.ts +30 -0
  53. package/esm/harness/src/channels/types.js +1 -0
  54. package/esm/harness/src/commands/defaults.d.ts +11 -0
  55. package/esm/harness/src/commands/defaults.js +53 -0
  56. package/esm/harness/src/commands/index.d.ts +3 -0
  57. package/esm/harness/src/commands/index.js +2 -0
  58. package/esm/harness/src/commands/registry.d.ts +2 -0
  59. package/esm/harness/src/commands/registry.js +59 -0
  60. package/esm/harness/src/commands/types.d.ts +23 -0
  61. package/esm/harness/src/commands/types.js +1 -0
  62. package/esm/harness/src/common/frontmatter.d.ts +6 -0
  63. package/esm/harness/src/common/frontmatter.js +18 -0
  64. package/esm/harness/src/common/index.d.ts +2 -0
  65. package/esm/harness/src/common/index.js +2 -0
  66. package/esm/harness/src/common/utils.d.ts +7 -0
  67. package/esm/harness/src/common/utils.js +22 -0
  68. package/esm/harness/src/config/index.d.ts +2 -0
  69. package/esm/harness/src/config/index.js +1 -0
  70. package/esm/harness/src/config/resolve.d.ts +2 -0
  71. package/esm/harness/src/config/resolve.js +10 -0
  72. package/esm/harness/src/config/types.d.ts +15 -0
  73. package/esm/harness/src/config/types.js +1 -0
  74. package/esm/harness/src/harness/create.d.ts +2 -0
  75. package/esm/harness/src/harness/create.js +167 -0
  76. package/esm/harness/src/harness/index.d.ts +3 -0
  77. package/esm/harness/src/harness/index.js +2 -0
  78. package/esm/harness/src/harness/models.d.ts +16 -0
  79. package/esm/harness/src/harness/models.js +30 -0
  80. package/esm/harness/src/harness/types.d.ts +50 -0
  81. package/esm/harness/src/harness/types.js +1 -0
  82. package/esm/harness/src/hooks/index.d.ts +3 -0
  83. package/esm/harness/src/hooks/index.js +2 -0
  84. package/esm/harness/src/hooks/merge-hooks.d.ts +2 -0
  85. package/esm/harness/src/hooks/merge-hooks.js +46 -0
  86. package/esm/harness/src/hooks/types.d.ts +21 -0
  87. package/esm/harness/src/hooks/types.js +1 -0
  88. package/esm/harness/src/hooks/with-hooks.d.ts +3 -0
  89. package/esm/harness/src/hooks/with-hooks.js +12 -0
  90. package/esm/harness/src/index.d.ts +14 -0
  91. package/esm/harness/src/index.js +14 -0
  92. package/esm/harness/src/permissions/allow-list.d.ts +4 -0
  93. package/esm/harness/src/permissions/allow-list.js +8 -0
  94. package/esm/harness/src/permissions/approval-manager.d.ts +31 -0
  95. package/esm/harness/src/permissions/approval-manager.js +55 -0
  96. package/esm/harness/src/permissions/approval.d.ts +17 -0
  97. package/esm/harness/src/permissions/approval.js +13 -0
  98. package/esm/harness/src/permissions/glob.d.ts +1 -0
  99. package/esm/harness/src/permissions/glob.js +2 -0
  100. package/esm/harness/src/permissions/index.d.ts +4 -0
  101. package/esm/harness/src/permissions/index.js +4 -0
  102. package/esm/harness/src/plugin/define.d.ts +2 -0
  103. package/esm/harness/src/plugin/define.js +1 -0
  104. package/esm/harness/src/plugin/import-ts.d.ts +1 -0
  105. package/esm/harness/src/plugin/import-ts.js +85 -0
  106. package/esm/harness/src/plugin/index.d.ts +5 -0
  107. package/esm/harness/src/plugin/index.js +4 -0
  108. package/esm/harness/src/plugin/resolve.d.ts +13 -0
  109. package/esm/harness/src/plugin/resolve.js +17 -0
  110. package/esm/harness/src/plugin/store.d.ts +8 -0
  111. package/esm/harness/src/plugin/store.js +26 -0
  112. package/esm/harness/src/plugin/types.d.ts +11 -0
  113. package/esm/harness/src/plugin/types.js +1 -0
  114. package/esm/harness/src/scheduler/command.d.ts +3 -0
  115. package/esm/harness/src/scheduler/command.js +19 -0
  116. package/esm/harness/src/scheduler/engine/index.d.ts +4 -0
  117. package/esm/harness/src/scheduler/engine/index.js +3 -0
  118. package/esm/harness/src/scheduler/engine/memory-store.d.ts +2 -0
  119. package/esm/harness/src/scheduler/engine/memory-store.js +23 -0
  120. package/esm/harness/src/scheduler/engine/scheduler.d.ts +13 -0
  121. package/esm/harness/src/scheduler/engine/scheduler.js +73 -0
  122. package/esm/harness/src/scheduler/engine/store.d.ts +4 -0
  123. package/esm/harness/src/scheduler/engine/store.js +55 -0
  124. package/esm/harness/src/scheduler/engine/types.d.ts +55 -0
  125. package/esm/harness/src/scheduler/engine/types.js +1 -0
  126. package/esm/harness/src/scheduler/index.d.ts +4 -0
  127. package/esm/harness/src/scheduler/index.js +3 -0
  128. package/esm/harness/src/scheduler/tool.d.ts +8 -0
  129. package/esm/harness/src/scheduler/tool.js +48 -0
  130. package/esm/harness/src/session/agent-session.d.ts +15 -0
  131. package/esm/harness/src/session/agent-session.js +83 -0
  132. package/esm/harness/src/session/catalog.d.ts +24 -0
  133. package/esm/harness/src/session/catalog.js +55 -0
  134. package/esm/harness/src/session/index.d.ts +7 -0
  135. package/esm/harness/src/session/index.js +6 -0
  136. package/esm/harness/src/session/manager.d.ts +39 -0
  137. package/esm/harness/src/session/manager.js +65 -0
  138. package/esm/harness/src/session/persist.d.ts +3 -0
  139. package/esm/harness/src/session/persist.js +24 -0
  140. package/esm/harness/src/session/store.d.ts +13 -0
  141. package/esm/harness/src/session/store.js +24 -0
  142. package/esm/harness/src/session/title.d.ts +9 -0
  143. package/esm/harness/src/session/title.js +24 -0
  144. package/esm/harness/src/session/types.d.ts +18 -0
  145. package/esm/harness/src/session/types.js +1 -0
  146. package/esm/harness/src/skills/index.d.ts +8 -0
  147. package/esm/harness/src/skills/index.js +7 -0
  148. package/esm/harness/src/skills/loader.d.ts +10 -0
  149. package/esm/harness/src/skills/loader.js +59 -0
  150. package/esm/harness/src/skills/parser.d.ts +2 -0
  151. package/esm/harness/src/skills/parser.js +16 -0
  152. package/esm/harness/src/skills/platform.d.ts +6 -0
  153. package/esm/harness/src/skills/platform.js +14 -0
  154. package/esm/harness/src/skills/registry.d.ts +8 -0
  155. package/esm/harness/src/skills/registry.js +16 -0
  156. package/esm/harness/src/skills/run.d.ts +14 -0
  157. package/esm/harness/src/skills/run.js +44 -0
  158. package/esm/harness/src/skills/tool.d.ts +3 -0
  159. package/esm/harness/src/skills/tool.js +31 -0
  160. package/esm/harness/src/skills/types.d.ts +8 -0
  161. package/esm/harness/src/skills/types.js +1 -0
  162. package/esm/harness/src/skills/writer.d.ts +8 -0
  163. package/esm/harness/src/skills/writer.js +55 -0
  164. package/esm/harness/src/subAgents/index.d.ts +4 -0
  165. package/esm/harness/src/subAgents/index.js +3 -0
  166. package/esm/harness/src/subAgents/registry.d.ts +2 -0
  167. package/esm/harness/src/subAgents/registry.js +18 -0
  168. package/esm/harness/src/subAgents/runner.d.ts +15 -0
  169. package/esm/harness/src/subAgents/runner.js +33 -0
  170. package/esm/harness/src/subAgents/tool.d.ts +6 -0
  171. package/esm/harness/src/subAgents/tool.js +64 -0
  172. package/esm/harness/src/subAgents/types.d.ts +18 -0
  173. package/esm/harness/src/subAgents/types.js +1 -0
  174. package/esm/harness/src/tui/chat/ChatApp.d.ts +188 -0
  175. package/esm/harness/src/tui/chat/ChatApp.js +1306 -0
  176. package/esm/harness/src/tui/chat/commands.d.ts +16 -0
  177. package/esm/harness/src/tui/chat/commands.js +21 -0
  178. package/esm/harness/src/tui/chat/editor.d.ts +31 -0
  179. package/esm/harness/src/tui/chat/editor.js +147 -0
  180. package/esm/harness/src/tui/chat/index.d.ts +9 -0
  181. package/esm/harness/src/tui/chat/index.js +7 -0
  182. package/esm/harness/src/tui/chat/markdown.d.ts +9 -0
  183. package/esm/harness/src/tui/chat/markdown.js +295 -0
  184. package/esm/harness/src/tui/chat/picker.d.ts +13 -0
  185. package/esm/harness/src/tui/chat/picker.js +123 -0
  186. package/esm/harness/src/tui/chat/status.d.ts +15 -0
  187. package/esm/harness/src/tui/chat/status.js +48 -0
  188. package/esm/harness/src/tui/chat/theme.d.ts +97 -0
  189. package/esm/harness/src/tui/chat/theme.js +201 -0
  190. package/esm/harness/src/tui/chat/transcript.d.ts +79 -0
  191. package/esm/harness/src/tui/chat/transcript.js +422 -0
  192. package/esm/harness/src/tui/index.d.ts +8 -0
  193. package/esm/harness/src/tui/index.js +12 -0
  194. package/package.json +5 -3
  195. package/script/harness/npm/src/common/index.d.ts +1 -1
  196. package/script/harness/npm/src/common/index.js +2 -1
  197. package/script/harness/npm/src/common/utils.d.ts +2 -0
  198. package/script/harness/npm/src/common/utils.js +11 -1
  199. package/script/harness/npm/src/harness/create.js +2 -1
  200. package/script/harness/npm/src/harness/types.d.ts +7 -1
  201. package/script/harness/npm/src/skills/index.d.ts +2 -1
  202. package/script/harness/npm/src/skills/index.js +3 -1
  203. package/script/harness/npm/src/skills/loader.d.ts +9 -1
  204. package/script/harness/npm/src/skills/loader.js +42 -9
  205. package/script/harness/npm/src/skills/parser.js +8 -2
  206. package/script/harness/npm/src/skills/platform.d.ts +6 -0
  207. package/script/harness/npm/src/skills/platform.js +18 -0
  208. package/script/harness/npm/src/skills/types.d.ts +2 -0
  209. package/script/harness/npm/src/skills/writer.d.ts +1 -0
  210. package/script/harness/npm/src/skills/writer.js +49 -36
  211. package/script/harness/npm/src/tui/chat/ChatApp.d.ts +188 -0
  212. package/script/harness/npm/src/tui/chat/ChatApp.js +1310 -0
  213. package/script/harness/npm/src/tui/chat/commands.d.ts +16 -0
  214. package/script/harness/npm/src/tui/chat/commands.js +25 -0
  215. package/script/harness/npm/src/tui/chat/editor.d.ts +31 -0
  216. package/script/harness/npm/src/tui/chat/editor.js +151 -0
  217. package/script/harness/npm/src/tui/chat/index.d.ts +9 -0
  218. package/script/harness/npm/src/tui/chat/index.js +25 -0
  219. package/script/harness/npm/src/tui/chat/markdown.d.ts +9 -0
  220. package/script/harness/npm/src/tui/chat/markdown.js +300 -0
  221. package/script/harness/npm/src/tui/chat/picker.d.ts +13 -0
  222. package/script/harness/npm/src/tui/chat/picker.js +129 -0
  223. package/script/harness/npm/src/tui/chat/status.d.ts +15 -0
  224. package/script/harness/npm/src/tui/chat/status.js +55 -0
  225. package/script/harness/npm/src/tui/chat/theme.d.ts +97 -0
  226. package/script/harness/npm/src/tui/chat/theme.js +209 -0
  227. package/script/harness/npm/src/tui/chat/transcript.d.ts +79 -0
  228. package/script/harness/npm/src/tui/chat/transcript.js +430 -0
  229. package/script/harness/npm/src/tui/index.d.ts +8 -7
  230. package/script/harness/npm/src/tui/index.js +11 -10
  231. package/script/harness/src/agents/index.d.ts +4 -0
  232. package/script/harness/src/agents/index.js +12 -0
  233. package/script/harness/src/agents/loader.d.ts +2 -0
  234. package/script/harness/src/agents/loader.js +28 -0
  235. package/script/harness/src/agents/parser.d.ts +2 -0
  236. package/script/harness/src/agents/parser.js +50 -0
  237. package/script/harness/src/agents/registry.d.ts +9 -0
  238. package/script/harness/src/agents/registry.js +95 -0
  239. package/script/harness/src/agents/types.d.ts +12 -0
  240. package/script/harness/src/channels/channel.d.ts +7 -0
  241. package/script/harness/src/channels/channel.js +29 -0
  242. package/script/harness/src/channels/index.d.ts +3 -0
  243. package/script/harness/src/channels/index.js +7 -0
  244. package/script/harness/src/channels/manager.d.ts +6 -0
  245. package/script/harness/src/channels/manager.js +32 -0
  246. package/script/harness/src/channels/types.d.ts +30 -0
  247. package/script/harness/src/channels/types.js +2 -0
  248. package/script/harness/src/commands/defaults.d.ts +11 -0
  249. package/script/harness/src/commands/defaults.js +61 -0
  250. package/script/harness/src/commands/index.d.ts +3 -0
  251. package/script/harness/src/commands/index.js +11 -0
  252. package/script/harness/src/commands/registry.d.ts +2 -0
  253. package/script/harness/src/commands/registry.js +63 -0
  254. package/script/harness/src/commands/types.d.ts +23 -0
  255. package/script/harness/src/commands/types.js +2 -0
  256. package/script/harness/src/common/frontmatter.d.ts +6 -0
  257. package/script/harness/src/common/frontmatter.js +23 -0
  258. package/script/harness/src/common/index.d.ts +2 -0
  259. package/script/harness/src/common/index.js +9 -0
  260. package/script/harness/src/common/utils.d.ts +7 -0
  261. package/script/harness/src/common/utils.js +27 -0
  262. package/script/harness/src/config/index.d.ts +2 -0
  263. package/script/harness/src/config/index.js +5 -0
  264. package/script/harness/src/config/resolve.d.ts +2 -0
  265. package/script/harness/src/config/resolve.js +14 -0
  266. package/script/harness/src/config/types.d.ts +15 -0
  267. package/script/harness/src/config/types.js +2 -0
  268. package/script/harness/src/harness/create.d.ts +2 -0
  269. package/script/harness/src/harness/create.js +174 -0
  270. package/script/harness/src/harness/index.d.ts +3 -0
  271. package/script/harness/src/harness/index.js +7 -0
  272. package/script/harness/src/harness/models.d.ts +16 -0
  273. package/script/harness/src/harness/models.js +34 -0
  274. package/script/harness/src/harness/types.d.ts +50 -0
  275. package/script/harness/src/harness/types.js +2 -0
  276. package/script/harness/src/hooks/index.d.ts +3 -0
  277. package/script/harness/src/hooks/index.js +7 -0
  278. package/script/harness/src/hooks/merge-hooks.d.ts +2 -0
  279. package/script/harness/src/hooks/merge-hooks.js +50 -0
  280. package/script/harness/src/hooks/types.d.ts +21 -0
  281. package/script/harness/src/hooks/types.js +2 -0
  282. package/script/harness/src/hooks/with-hooks.d.ts +3 -0
  283. package/script/harness/src/hooks/with-hooks.js +16 -0
  284. package/script/harness/src/index.d.ts +14 -0
  285. package/script/harness/src/index.js +30 -0
  286. package/script/harness/src/permissions/allow-list.d.ts +4 -0
  287. package/script/harness/src/permissions/allow-list.js +13 -0
  288. package/script/harness/src/permissions/approval-manager.d.ts +31 -0
  289. package/script/harness/src/permissions/approval-manager.js +59 -0
  290. package/script/harness/src/permissions/approval.d.ts +17 -0
  291. package/script/harness/src/permissions/approval.js +17 -0
  292. package/script/harness/src/permissions/glob.d.ts +1 -0
  293. package/script/harness/src/permissions/glob.js +6 -0
  294. package/script/harness/src/permissions/index.d.ts +4 -0
  295. package/script/harness/src/permissions/index.js +12 -0
  296. package/script/harness/src/plugin/define.d.ts +2 -0
  297. package/script/harness/src/plugin/define.js +5 -0
  298. package/script/harness/src/plugin/import-ts.d.ts +1 -0
  299. package/script/harness/src/plugin/import-ts.js +122 -0
  300. package/script/harness/src/plugin/index.d.ts +5 -0
  301. package/script/harness/src/plugin/index.js +11 -0
  302. package/script/harness/src/plugin/resolve.d.ts +13 -0
  303. package/script/harness/src/plugin/resolve.js +21 -0
  304. package/script/harness/src/plugin/store.d.ts +8 -0
  305. package/script/harness/src/plugin/store.js +30 -0
  306. package/script/harness/src/plugin/types.d.ts +11 -0
  307. package/script/harness/src/plugin/types.js +2 -0
  308. package/script/harness/src/scheduler/command.d.ts +3 -0
  309. package/script/harness/src/scheduler/command.js +23 -0
  310. package/script/harness/src/scheduler/engine/index.d.ts +4 -0
  311. package/script/harness/src/scheduler/engine/index.js +9 -0
  312. package/script/harness/src/scheduler/engine/memory-store.d.ts +2 -0
  313. package/script/harness/src/scheduler/engine/memory-store.js +27 -0
  314. package/script/harness/src/scheduler/engine/scheduler.d.ts +13 -0
  315. package/script/harness/src/scheduler/engine/scheduler.js +77 -0
  316. package/script/harness/src/scheduler/engine/store.d.ts +4 -0
  317. package/script/harness/src/scheduler/engine/store.js +59 -0
  318. package/script/harness/src/scheduler/engine/types.d.ts +55 -0
  319. package/script/harness/src/scheduler/engine/types.js +2 -0
  320. package/script/harness/src/scheduler/index.d.ts +4 -0
  321. package/script/harness/src/scheduler/index.js +11 -0
  322. package/script/harness/src/scheduler/tool.d.ts +8 -0
  323. package/script/harness/src/scheduler/tool.js +52 -0
  324. package/script/harness/src/session/agent-session.d.ts +15 -0
  325. package/script/harness/src/session/agent-session.js +87 -0
  326. package/script/harness/src/session/catalog.d.ts +24 -0
  327. package/script/harness/src/session/catalog.js +59 -0
  328. package/script/harness/src/session/index.d.ts +7 -0
  329. package/script/harness/src/session/index.js +17 -0
  330. package/script/harness/src/session/manager.d.ts +39 -0
  331. package/script/harness/src/session/manager.js +69 -0
  332. package/script/harness/src/session/persist.d.ts +3 -0
  333. package/script/harness/src/session/persist.js +28 -0
  334. package/script/harness/src/session/store.d.ts +13 -0
  335. package/script/harness/src/session/store.js +28 -0
  336. package/script/harness/src/session/title.d.ts +9 -0
  337. package/script/harness/src/session/title.js +30 -0
  338. package/script/harness/src/session/types.d.ts +18 -0
  339. package/script/harness/src/session/types.js +2 -0
  340. package/script/harness/src/skills/index.d.ts +8 -0
  341. package/script/harness/src/skills/index.js +18 -0
  342. package/script/harness/src/skills/loader.d.ts +10 -0
  343. package/script/harness/src/skills/loader.js +63 -0
  344. package/script/harness/src/skills/parser.d.ts +2 -0
  345. package/script/harness/src/skills/parser.js +20 -0
  346. package/script/harness/src/skills/platform.d.ts +6 -0
  347. package/script/harness/src/skills/platform.js +18 -0
  348. package/script/harness/src/skills/registry.d.ts +8 -0
  349. package/script/harness/src/skills/registry.js +20 -0
  350. package/script/harness/src/skills/run.d.ts +14 -0
  351. package/script/harness/src/skills/run.js +49 -0
  352. package/script/harness/src/skills/tool.d.ts +3 -0
  353. package/script/harness/src/skills/tool.js +35 -0
  354. package/script/harness/src/skills/types.d.ts +8 -0
  355. package/script/harness/src/skills/types.js +2 -0
  356. package/script/harness/src/skills/writer.d.ts +8 -0
  357. package/script/harness/src/skills/writer.js +59 -0
  358. package/script/harness/src/subAgents/index.d.ts +4 -0
  359. package/script/harness/src/subAgents/index.js +9 -0
  360. package/script/harness/src/subAgents/registry.d.ts +2 -0
  361. package/script/harness/src/subAgents/registry.js +22 -0
  362. package/script/harness/src/subAgents/runner.d.ts +15 -0
  363. package/script/harness/src/subAgents/runner.js +37 -0
  364. package/script/harness/src/subAgents/tool.d.ts +6 -0
  365. package/script/harness/src/subAgents/tool.js +68 -0
  366. package/script/harness/src/subAgents/types.d.ts +18 -0
  367. package/script/harness/src/subAgents/types.js +2 -0
  368. package/script/harness/src/tui/chat/ChatApp.d.ts +188 -0
  369. package/script/harness/src/tui/chat/ChatApp.js +1310 -0
  370. package/script/harness/src/tui/chat/commands.d.ts +16 -0
  371. package/script/harness/src/tui/chat/commands.js +25 -0
  372. package/script/harness/src/tui/chat/editor.d.ts +31 -0
  373. package/script/harness/src/tui/chat/editor.js +151 -0
  374. package/script/harness/src/tui/chat/index.d.ts +9 -0
  375. package/script/harness/src/tui/chat/index.js +25 -0
  376. package/script/harness/src/tui/chat/markdown.d.ts +9 -0
  377. package/script/harness/src/tui/chat/markdown.js +300 -0
  378. package/script/harness/src/tui/chat/picker.d.ts +13 -0
  379. package/script/harness/src/tui/chat/picker.js +129 -0
  380. package/script/harness/src/tui/chat/status.d.ts +15 -0
  381. package/script/harness/src/tui/chat/status.js +55 -0
  382. package/script/harness/src/tui/chat/theme.d.ts +97 -0
  383. package/script/harness/src/tui/chat/theme.js +209 -0
  384. package/script/harness/src/tui/chat/transcript.d.ts +79 -0
  385. package/script/harness/src/tui/chat/transcript.js +430 -0
  386. package/script/harness/src/tui/index.d.ts +8 -0
  387. package/script/harness/src/tui/index.js +30 -0
  388. package/esm/harness/npm/src/tui/channel.d.ts +0 -3
  389. package/esm/harness/npm/src/tui/channel.js +0 -53
  390. package/esm/harness/npm/src/tui/components/composer.d.ts +0 -3
  391. package/esm/harness/npm/src/tui/components/composer.js +0 -13
  392. package/esm/harness/npm/src/tui/components/header.d.ts +0 -3
  393. package/esm/harness/npm/src/tui/components/header.js +0 -1
  394. package/esm/harness/npm/src/tui/components/index.d.ts +0 -5
  395. package/esm/harness/npm/src/tui/components/index.js +0 -5
  396. package/esm/harness/npm/src/tui/components/message.d.ts +0 -6
  397. package/esm/harness/npm/src/tui/components/message.js +0 -26
  398. package/esm/harness/npm/src/tui/components/status.d.ts +0 -3
  399. package/esm/harness/npm/src/tui/components/status.js +0 -8
  400. package/esm/harness/npm/src/tui/components/transcript.d.ts +0 -4
  401. package/esm/harness/npm/src/tui/components/transcript.js +0 -4
  402. package/esm/harness/npm/src/tui/kit.d.ts +0 -15
  403. package/esm/harness/npm/src/tui/kit.js +0 -7
  404. package/esm/harness/npm/src/tui/types.d.ts +0 -38
  405. package/script/harness/npm/src/tui/channel.d.ts +0 -3
  406. package/script/harness/npm/src/tui/channel.js +0 -57
  407. package/script/harness/npm/src/tui/components/composer.d.ts +0 -3
  408. package/script/harness/npm/src/tui/components/composer.js +0 -17
  409. package/script/harness/npm/src/tui/components/header.d.ts +0 -3
  410. package/script/harness/npm/src/tui/components/header.js +0 -5
  411. package/script/harness/npm/src/tui/components/index.d.ts +0 -5
  412. package/script/harness/npm/src/tui/components/index.js +0 -16
  413. package/script/harness/npm/src/tui/components/message.d.ts +0 -6
  414. package/script/harness/npm/src/tui/components/message.js +0 -32
  415. package/script/harness/npm/src/tui/components/status.d.ts +0 -3
  416. package/script/harness/npm/src/tui/components/status.js +0 -12
  417. package/script/harness/npm/src/tui/components/transcript.d.ts +0 -4
  418. package/script/harness/npm/src/tui/components/transcript.js +0 -9
  419. package/script/harness/npm/src/tui/kit.d.ts +0 -15
  420. package/script/harness/npm/src/tui/kit.js +0 -10
  421. package/script/harness/npm/src/tui/types.d.ts +0 -38
  422. /package/esm/harness/{npm/src/tui → src/agents}/types.js +0 -0
  423. /package/script/harness/{npm/src/tui → src/agents}/types.js +0 -0
@@ -0,0 +1,1306 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { mkdir, writeFile } from 'node:fs/promises';
3
+ import { dirname, isAbsolute, relative, resolve } from 'node:path';
4
+ import { box, column, flex, measure, ProcessTerminal, scrollView, truncateToWidth, TUI, visibleWidth, } from 'mu-tui';
5
+ import { buildCommands, filterCommands } from './commands.js';
6
+ import { MultilineEditor } from './editor.js';
7
+ import { activeMention, collectCandidates, rank } from './picker.js';
8
+ import { formatTokens, statusComponent, statusFromEvent } from './status.js';
9
+ import { asHexColor, styleToAnsi, ThemeProvider, themesByName } from './theme.js';
10
+ import { entryComponent, formatToolArgs, stickyHeader, Transcript, transcriptComponent, } from './transcript.js';
11
+ const RESET = '\x1b[0m';
12
+ const PROMPT_WIDTH = 2;
13
+ const encodeBinary = (_key, value) => value instanceof Uint8Array ? { __binary: 'base64', data: Buffer.from(value).toString('base64') } : value;
14
+ const textOf = (message) => message.content.map((part) => (part.type === 'text' ? part.text : '')).join('');
15
+ const SPINNER_INTERVAL_MS = 100;
16
+ const MAX_LIST_ROWS = 8;
17
+ const SPLASH_INPUT_WIDTH = 72;
18
+ const APPROVAL_OPTIONS = [
19
+ { label: 'Approve once', value: 'approve' },
20
+ { label: 'Approve for this session', value: 'approve_always' },
21
+ { label: 'Deny', value: 'deny' },
22
+ ];
23
+ const DIM = '\x1b[2m';
24
+ const lastAssistantText = (messages) => {
25
+ for (let i = messages.length - 1; i >= 0; i--) {
26
+ const message = messages[i];
27
+ if (message.role === 'assistant') {
28
+ return message.content.map((part) => (part.type === 'text' ? part.text : '')).join('');
29
+ }
30
+ }
31
+ return '';
32
+ };
33
+ const padTo = (value, width) => {
34
+ if (width <= 0)
35
+ return '';
36
+ const fitted = visibleWidth(value) > width ? truncateToWidth(value, width) : value;
37
+ return fitted + ' '.repeat(Math.max(0, width - visibleWidth(fitted)));
38
+ };
39
+ const listView = (rows, selected, theme) => ({
40
+ render: (s) => {
41
+ if (s.width <= 0)
42
+ return;
43
+ const normal = styleToAnsi(theme.styles.commandPaletteItem);
44
+ const sel = styleToAnsi(theme.styles.commandPaletteSelected);
45
+ const maxLeft = rows.reduce((max, r) => Math.max(max, r.left.length), 0);
46
+ for (let i = 0; i < rows.length && i < s.height; i++) {
47
+ const isSel = i === selected;
48
+ const prefix = isSel ? '› ' : ' ';
49
+ const leftPart = `${prefix}${rows[i].left}${' '.repeat(Math.max(0, maxLeft - rows[i].left.length))}`;
50
+ if (visibleWidth(leftPart) >= s.width) {
51
+ s.text(0, i, `${isSel ? sel : normal}${padTo(leftPart, s.width)}${RESET}`);
52
+ continue;
53
+ }
54
+ const rightAvail = s.width - visibleWidth(leftPart);
55
+ const rightText = rows[i].right ? ` ${rows[i].right}` : '';
56
+ const right = padTo(rightText, rightAvail);
57
+ if (isSel) {
58
+ s.text(0, i, `${sel}${leftPart}${right}${RESET}`);
59
+ }
60
+ else {
61
+ s.text(0, i, `${normal}${leftPart}${DIM}${right}${RESET}`);
62
+ }
63
+ }
64
+ },
65
+ });
66
+ export class ChatApp {
67
+ host;
68
+ tui;
69
+ terminal;
70
+ editor;
71
+ scroll;
72
+ transcript = new Transcript();
73
+ subScroll;
74
+ subTranscript = new Transcript();
75
+ themeProvider;
76
+ commands;
77
+ session;
78
+ features;
79
+ banner;
80
+ unsubscribe;
81
+ unsubscribeTheme;
82
+ unsubscribeSubAgents;
83
+ runUnsubs = new Set();
84
+ activeRuns = new Set();
85
+ mentionAc;
86
+ status = { label: 'ready', busy: false, spinnerTick: 0, context: '', model: '' };
87
+ running = false;
88
+ queue = [];
89
+ pendingShell = [];
90
+ models = [];
91
+ paletteCursor = 0;
92
+ paletteDismissedFor = '__none__';
93
+ pickerMention;
94
+ pickerRanked = [];
95
+ pickerCursor = 0;
96
+ history;
97
+ historyIndex;
98
+ historyDraft = '';
99
+ spinnerTimer;
100
+ lastEsc = 0;
101
+ modelPickerOpen = false;
102
+ modelHandle;
103
+ sessionPickerOpen = false;
104
+ sessionHandle;
105
+ approvalQueue = [];
106
+ approvalCursor = 0;
107
+ unsubscribeApproval;
108
+ errorText;
109
+ errorTimer;
110
+ stopped = false;
111
+ constructor(host) {
112
+ this.host = host;
113
+ this.session = host.session;
114
+ this.features = host.features ?? {};
115
+ this.banner = host.banner;
116
+ this.transcript.thinkingVisible = host.initialThinking;
117
+ this.history = host.history?.load() ?? [];
118
+ this.historyIndex = this.history.length;
119
+ this.themeProvider = new ThemeProvider(themesByName[host.initialTheme] ?? themesByName.dark);
120
+ this.terminal = new ProcessTerminal({
121
+ alternateScreen: true,
122
+ bracketedPaste: true,
123
+ focusEvents: true,
124
+ keyboard: true,
125
+ mouse: { drag: true, motion: true },
126
+ });
127
+ this.tui = new TUI(this.terminal);
128
+ this.editor = new MultilineEditor({
129
+ placeholder: 'type a message…',
130
+ onSubmit: (value) => this.submit(value),
131
+ onChange: (value) => this.onInputChange(value),
132
+ });
133
+ this.scroll = scrollView({ render: (s) => transcriptComponent(this.transcript, this.theme()).render(s) }, { stickyHeader: (info) => this.stickyHeaderView(info), footer: () => this.jumpToBottomHint() });
134
+ this.subScroll = scrollView({ render: (s) => transcriptComponent(this.subTranscript, this.theme()).render(s) });
135
+ this.commands = buildCommands(this.commandHost()).filter((c) => {
136
+ if ((c.name === 'sessions' || c.name === 'new') && !this.feature('sessionPicker'))
137
+ return false;
138
+ if (c.name === 'model' && !this.feature('modelPicker'))
139
+ return false;
140
+ return true;
141
+ });
142
+ this.tui.setRoot({ render: (s) => this.root().render(s) });
143
+ this.tui.setBackgroundColor(this.theme().colors.background);
144
+ this.tui.setFocus(this.editor);
145
+ this.tui.addInputInterceptor((event) => this.intercept(event));
146
+ this.tui.addGlobalKeybinding({ chord: { key: 'c', ctrl: true }, handler: () => this.onCtrlC() });
147
+ this.tui.addGlobalKeybinding({ chord: { key: 't', ctrl: true }, handler: () => this.toggleTheme() });
148
+ this.tui.addGlobalKeybinding({ chord: { key: 'o', ctrl: true }, handler: () => this.toggleExpand() });
149
+ this.tui.addGlobalKeybinding({ chord: { key: 'end', ctrl: true }, handler: () => this.jumpToBottom() });
150
+ this.unsubscribeTheme = this.themeProvider.subscribe(() => {
151
+ this.tui.setBackgroundColor(this.theme().colors.background);
152
+ this.tui.requestRender(true);
153
+ });
154
+ this.bindSession();
155
+ if (this.feature('subAgents')) {
156
+ this.unsubscribeSubAgents = this.host.subAgents.subscribe((run) => this.onSubAgentRun(run));
157
+ }
158
+ if (this.feature('approvals')) {
159
+ this.unsubscribeApproval = this.host.approvals.subscribe((req) => {
160
+ this.approvalQueue.push(req);
161
+ this.tui.requestRender();
162
+ });
163
+ }
164
+ this.updateSpeaker();
165
+ this.transcript.seed(this.session?.messages ?? []);
166
+ }
167
+ async start() {
168
+ this.tui.start();
169
+ await this.loadModels();
170
+ }
171
+ async stop() {
172
+ if (this.stopped)
173
+ return;
174
+ this.stopped = true;
175
+ this.unsubscribe?.();
176
+ this.unsubscribeTheme?.();
177
+ this.unsubscribeSubAgents?.();
178
+ this.unsubscribeApproval?.();
179
+ this.clearRuns();
180
+ this.stopSpinner();
181
+ this.clearError();
182
+ this.session?.abort();
183
+ this.tui.stop();
184
+ }
185
+ theme() {
186
+ return this.themeProvider.current();
187
+ }
188
+ commandHost() {
189
+ return {
190
+ newSession: () => this.newSession(),
191
+ openModelPicker: () => this.openModelPicker(),
192
+ toggleExpand: () => this.toggleExpand(),
193
+ toggleThinking: () => this.toggleThinking(),
194
+ exportContext: (args) => void this.exportContext(args),
195
+ listSessions: () => void this.openSessionPicker(),
196
+ quit: () => void this.stop().then(() => this.host.onExit(0)),
197
+ };
198
+ }
199
+ async openSessionPicker() {
200
+ if (this.running) {
201
+ this.showError('Cannot switch sessions while a response is running.');
202
+ return;
203
+ }
204
+ const sessions = await this.host.listSessions();
205
+ if (sessions.length === 0) {
206
+ this.transcript.note('No sessions yet.');
207
+ this.tui.requestRender();
208
+ return;
209
+ }
210
+ const content = this.buildSessionPicker(sessions, (id) => {
211
+ this.sessionHandle?.close();
212
+ if (id !== this.session?.id)
213
+ void this.host.openSession(id).then((next) => this.swapSession(next));
214
+ });
215
+ this.sessionPickerOpen = true;
216
+ this.sessionHandle = this.tui.showModal(content, {
217
+ width: 72,
218
+ border: false,
219
+ background: this.theme().colors.surface,
220
+ onClose: () => {
221
+ this.sessionPickerOpen = false;
222
+ this.tui.setFocus(this.editor);
223
+ },
224
+ });
225
+ }
226
+ buildSessionPicker(sessions, onPick) {
227
+ const currentId = this.session?.id;
228
+ let cursor = Math.max(0, sessions.findIndex((s) => s.id === currentId));
229
+ return {
230
+ handleInput: (event) => {
231
+ if (event.type !== 'key' || event.kind === 'release' || sessions.length === 0)
232
+ return;
233
+ if (event.key === 'up')
234
+ cursor = (cursor - 1 + sessions.length) % sessions.length;
235
+ else if (event.key === 'down')
236
+ cursor = (cursor + 1) % sessions.length;
237
+ else if (event.key === 'enter')
238
+ onPick(sessions[cursor].id);
239
+ },
240
+ render: (s) => {
241
+ if (s.width <= 0)
242
+ return;
243
+ const theme = this.theme();
244
+ const itemSgr = styleToAnsi(theme.styles.commandPaletteItem);
245
+ const selSgr = styleToAnsi(theme.styles.commandPaletteSelected);
246
+ const muted = styleToAnsi(theme.styles.muted);
247
+ const ITEM_INDENT = 2;
248
+ const textX = ITEM_INDENT;
249
+ const innerW = Math.max(1, s.width);
250
+ s.text(textX, 1, `${styleToAnsi(theme.styles.title)}Sessions${RESET}`);
251
+ const maxRows = Math.min(sessions.length, 10, Math.max(1, s.height - 6));
252
+ const top = cursor >= maxRows ? cursor - maxRows + 1 : 0;
253
+ for (let r = 0; r < maxRows; r++) {
254
+ const session = sessions[top + r];
255
+ if (!session)
256
+ break;
257
+ const isSel = top + r === cursor;
258
+ const marker = session.id === currentId ? '● ' : ' ';
259
+ const label = session.title || session.id;
260
+ const body = `${marker}${label}`;
261
+ s.text(0, 3 + r, `${isSel ? selSgr : itemSgr}${padTo(body, innerW)}${RESET}`);
262
+ }
263
+ const footerRow = 3 + maxRows + 1;
264
+ s.text(textX, footerRow, `${muted}↑/↓ move · Enter open · Esc close${RESET}`);
265
+ s.text(0, footerRow + 1, '');
266
+ },
267
+ };
268
+ }
269
+ bindSession() {
270
+ this.unsubscribe = this.session?.subscribe((event) => this.handleEvent(event));
271
+ }
272
+ feature(name) {
273
+ return this.features[name] !== false;
274
+ }
275
+ /** Returns the active session, creating it lazily on first use. */
276
+ ensureSession() {
277
+ if (!this.session) {
278
+ this.session = this.host.createSession();
279
+ this.bindSession();
280
+ }
281
+ return this.session;
282
+ }
283
+ clearRuns() {
284
+ this.mentionAc?.abort();
285
+ this.mentionAc = undefined;
286
+ for (const unsub of this.runUnsubs)
287
+ unsub();
288
+ this.runUnsubs.clear();
289
+ this.abortRuns();
290
+ }
291
+ abortRuns() {
292
+ for (const run of this.activeRuns) {
293
+ run.cancelled = true;
294
+ run.handle.cancel();
295
+ run.session.abort();
296
+ }
297
+ this.activeRuns.clear();
298
+ }
299
+ onSubAgentRun(run) {
300
+ if (run.parentId !== this.session?.id)
301
+ return;
302
+ const handle = this.transcript.appendSubAgent(run.agent, run.session.messages);
303
+ const record = { session: run.session, handle, cancelled: false };
304
+ this.activeRuns.add(record);
305
+ const toolNames = new Map();
306
+ const unsub = run.session.subscribe((event) => {
307
+ switch (event.type) {
308
+ case 'tool_call': {
309
+ toolNames.set(event.id, event.name);
310
+ const args = formatToolArgs(event.name, event.input);
311
+ handle.addTool(args ? `${event.name} ${args}` : event.name);
312
+ break;
313
+ }
314
+ case 'turn_end':
315
+ if (!record.cancelled)
316
+ handle.finish(lastAssistantText(run.session.messages));
317
+ this.activeRuns.delete(record);
318
+ unsub();
319
+ this.runUnsubs.delete(unsub);
320
+ break;
321
+ case 'error':
322
+ if (!record.cancelled)
323
+ handle.fail(event.error instanceof Error ? event.error.message : String(event.error));
324
+ this.activeRuns.delete(record);
325
+ unsub();
326
+ this.runUnsubs.delete(unsub);
327
+ break;
328
+ }
329
+ this.tui.requestRender();
330
+ });
331
+ this.runUnsubs.add(unsub);
332
+ this.tui.requestRender();
333
+ }
334
+ tryDispatch(text) {
335
+ const match = /^@([^\s]+)\s+([\s\S]+)$/.exec(text);
336
+ if (!match)
337
+ return false;
338
+ const [, agent, task] = match;
339
+ if (!this.host.agentNames().includes(agent))
340
+ return false;
341
+ if (this.running) {
342
+ this.queue.push(text);
343
+ this.tui.requestRender();
344
+ return true;
345
+ }
346
+ this.dispatchMention(agent, task, text);
347
+ return true;
348
+ }
349
+ dispatchMention(agent, task, displayText) {
350
+ const session = this.ensureSession();
351
+ this.transcript.appendUser(displayText);
352
+ const ac = new AbortController();
353
+ this.mentionAc = ac;
354
+ this.running = true;
355
+ this.status.busy = true;
356
+ this.setStatus('thinking…');
357
+ this.startSpinner();
358
+ this.tui.requestRender();
359
+ this.host.dispatchSubAgent(agent, task, session.id)
360
+ .then((result) => {
361
+ if (ac.signal.aborted)
362
+ return;
363
+ this.mentionAc = undefined;
364
+ const content = `The "${agent}" sub-agent was asked:\n${task}\n\nIts result:\n${result.text}\n\nUse this to respond to the user.`;
365
+ return session.send(content);
366
+ })
367
+ .catch((err) => {
368
+ if (ac.signal.aborted)
369
+ return;
370
+ this.running = false;
371
+ this.status.busy = false;
372
+ this.stopSpinner();
373
+ this.showError(err instanceof Error ? err.message : String(err));
374
+ });
375
+ }
376
+ swapSession(next) {
377
+ this.unsubscribe?.();
378
+ this.clearRuns();
379
+ this.session = next;
380
+ this.bindSession();
381
+ this.updateSpeaker();
382
+ this.transcript.seed(next.messages);
383
+ this.queue.length = 0;
384
+ this.pendingShell.length = 0;
385
+ this.running = false;
386
+ this.status.busy = false;
387
+ this.status.context = '';
388
+ this.stopSpinner();
389
+ this.setStatus('ready');
390
+ this.tui.requestRender(true);
391
+ }
392
+ handleEvent(event) {
393
+ this.updateSpeaker();
394
+ this.transcript.applyEvent(event);
395
+ const label = statusFromEvent(event);
396
+ if (label !== undefined)
397
+ this.status.label = label;
398
+ switch (event.type) {
399
+ case 'turn_start':
400
+ this.running = true;
401
+ this.startSpinner();
402
+ break;
403
+ case 'turn_end':
404
+ this.onTurnComplete();
405
+ break;
406
+ case 'usage':
407
+ this.applyUsage(event.usage);
408
+ break;
409
+ case 'error': {
410
+ const message = event.error instanceof Error ? event.error.message : String(event.error);
411
+ this.showError(message);
412
+ this.onTurnComplete();
413
+ break;
414
+ }
415
+ }
416
+ this.tui.requestRender();
417
+ }
418
+ applyUsage(usage) {
419
+ const used = usage.total ?? ((usage.input ?? 0) + (usage.output ?? 0) || undefined);
420
+ const total = usage.contextWindow;
421
+ if (used !== undefined && total) {
422
+ this.status.context = `${formatTokens(used)}/${formatTokens(total)} (${Math.round((used / total) * 100)}%)`;
423
+ }
424
+ else if (used !== undefined) {
425
+ this.status.context = `${formatTokens(used)} tokens`;
426
+ }
427
+ else if (total) {
428
+ this.status.context = `${formatTokens(total)} ctx`;
429
+ }
430
+ }
431
+ onTurnComplete() {
432
+ this.running = false;
433
+ if (this.queue.length > 0) {
434
+ const next = this.queue.shift();
435
+ if (!this.tryDispatch(next))
436
+ this.send(next);
437
+ return;
438
+ }
439
+ this.status.busy = false;
440
+ this.stopSpinner();
441
+ this.setStatus('ready');
442
+ }
443
+ stripFileMentions(text) {
444
+ const agents = new Set(this.host.agentNames());
445
+ return text.replace(/(^|\s)@(\S+)/g, (match, pre, token) => (agents.has(token) ? match : `${pre}${token}`));
446
+ }
447
+ send(value) {
448
+ const session = this.ensureSession();
449
+ this.transcript.appendUser(value);
450
+ const content = this.flushShellContext(this.stripFileMentions(value));
451
+ this.running = true;
452
+ this.status.busy = true;
453
+ this.setStatus('thinking…');
454
+ this.startSpinner();
455
+ this.tui.requestRender();
456
+ session.send(content).catch((err) => {
457
+ this.running = false;
458
+ this.status.busy = false;
459
+ this.stopSpinner();
460
+ this.showError(err instanceof Error ? err.message : String(err));
461
+ });
462
+ }
463
+ flushShellContext(userText) {
464
+ if (this.pendingShell.length === 0)
465
+ return userText;
466
+ const blocks = this.pendingShell
467
+ .map((entry) => `$ ${entry.cmd}\n${entry.output}`)
468
+ .join('\n\n');
469
+ this.pendingShell.length = 0;
470
+ return `<shell-output>\nShell commands the user ran locally since the last message:\n\n${blocks}\n</shell-output>\n\n${userText}`;
471
+ }
472
+ submit(value) {
473
+ const trimmed = value.trim();
474
+ if (!trimmed)
475
+ return;
476
+ if (this.modelPickerOpen || this.sessionPickerOpen)
477
+ return;
478
+ this.clearError();
479
+ this.pushHistory(trimmed);
480
+ this.editor.setValue('');
481
+ if (trimmed.startsWith('!') || trimmed.startsWith('$')) {
482
+ this.runShell(trimmed.slice(1).trim());
483
+ return;
484
+ }
485
+ if (trimmed.startsWith('/')) {
486
+ this.runCommand(trimmed);
487
+ return;
488
+ }
489
+ if (this.tryDispatch(trimmed))
490
+ return;
491
+ if (this.running) {
492
+ this.queue.push(trimmed);
493
+ this.tui.requestRender();
494
+ return;
495
+ }
496
+ this.send(trimmed);
497
+ }
498
+ enqueueFromInput() {
499
+ const value = this.editor.getValue().trim();
500
+ if (!value)
501
+ return;
502
+ this.pushHistory(value);
503
+ this.editor.setValue('');
504
+ this.queue.push(value);
505
+ this.tui.requestRender();
506
+ }
507
+ onInputChange(value) {
508
+ if (value !== this.paletteDismissedFor)
509
+ this.paletteDismissedFor = '__none__';
510
+ const items = this.paletteItems();
511
+ this.paletteCursor = Math.max(0, Math.min(this.paletteCursor, Math.max(0, items.length - 1)));
512
+ if (items.length === 0) {
513
+ const mention = activeMention(value, this.editor.cursorPos);
514
+ if (mention) {
515
+ this.pickerMention = mention;
516
+ this.pickerRanked = rank(mention.query, collectCandidates(this.host.cwd, this.host.agentNames()), MAX_LIST_ROWS);
517
+ this.pickerCursor = Math.max(0, Math.min(this.pickerCursor, Math.max(0, this.pickerRanked.length - 1)));
518
+ }
519
+ else {
520
+ this.pickerMention = undefined;
521
+ this.pickerRanked = [];
522
+ }
523
+ }
524
+ else {
525
+ this.pickerMention = undefined;
526
+ this.pickerRanked = [];
527
+ }
528
+ this.editor.hiddenPrefix = value.startsWith('/') || value.startsWith('!') || value.startsWith('$')
529
+ ? value[0]
530
+ : this.pickerVisible() && value.startsWith('@')
531
+ ? '@'
532
+ : '';
533
+ this.tui.requestRender();
534
+ }
535
+ intercept(event) {
536
+ if (this.modelPickerOpen || this.sessionPickerOpen)
537
+ return false;
538
+ if (event.type !== 'key' || event.kind === 'release')
539
+ return false;
540
+ const key = event.key;
541
+ if (this.approvalQueue.length > 0) {
542
+ const count = APPROVAL_OPTIONS.length;
543
+ if (key === 'left' || (key === 'tab' && event.shift))
544
+ return this.moveApproval(-1);
545
+ if (key === 'right' || (key === 'tab' && !event.shift))
546
+ return this.moveApproval(1);
547
+ if (key === 'enter')
548
+ return this.resolveApproval(APPROVAL_OPTIONS[this.approvalCursor % count].value);
549
+ if (key === 'escape' || key === 'esc')
550
+ return this.resolveApproval('deny');
551
+ return true;
552
+ }
553
+ if (key === 'escape' || key === 'esc')
554
+ return this.onEscape();
555
+ if (key === 'backspace') {
556
+ if (this.deleteMention())
557
+ return true;
558
+ return false;
559
+ }
560
+ if (key === 'enter' && event.alt) {
561
+ if (this.running) {
562
+ this.enqueueFromInput();
563
+ return true;
564
+ }
565
+ return false;
566
+ }
567
+ if (this.paletteItems().length > 0) {
568
+ if (key === 'up')
569
+ return this.paletteMove(-1);
570
+ if (key === 'down')
571
+ return this.paletteMove(1);
572
+ if (key === 'tab')
573
+ return this.paletteComplete();
574
+ if (key === 'enter')
575
+ return this.paletteRun();
576
+ return false;
577
+ }
578
+ if (this.pickerVisible()) {
579
+ if (key === 'up')
580
+ return this.pickerMove(-1);
581
+ if (key === 'down')
582
+ return this.pickerMove(1);
583
+ if (key === 'tab' || key === 'enter')
584
+ return this.pickerAccept();
585
+ return false;
586
+ }
587
+ if (key === 'tab')
588
+ return this.cycleAgent();
589
+ if (key === 'up')
590
+ return this.navigateHistory('up');
591
+ if (key === 'down')
592
+ return this.navigateHistory('down');
593
+ return false;
594
+ }
595
+ cycleAgent() {
596
+ this.host.cycleAgent();
597
+ this.tui.requestRender();
598
+ return true;
599
+ }
600
+ onEscape() {
601
+ const focused = this.focusedSub();
602
+ if (focused) {
603
+ focused.open = false;
604
+ this.tui.requestRender();
605
+ return true;
606
+ }
607
+ if (this.paletteItems().length > 0) {
608
+ this.paletteDismissedFor = this.editor.getValue();
609
+ this.tui.requestRender();
610
+ return true;
611
+ }
612
+ if (this.pickerVisible()) {
613
+ this.pickerMention = undefined;
614
+ this.pickerRanked = [];
615
+ this.tui.requestRender();
616
+ return true;
617
+ }
618
+ const value = this.editor.getValue();
619
+ if (value.startsWith('!') || value.startsWith('$') || value.startsWith('/')) {
620
+ this.editor.setValue('');
621
+ this.tui.requestRender();
622
+ return true;
623
+ }
624
+ if (this.running) {
625
+ const now = Date.now();
626
+ if (this.lastEsc > 0 && now - this.lastEsc < 1500) {
627
+ this.lastEsc = 0;
628
+ this.cancelGeneration();
629
+ }
630
+ else {
631
+ this.lastEsc = now;
632
+ this.setStatus('press Esc again to cancel');
633
+ this.tui.requestRender();
634
+ }
635
+ return true;
636
+ }
637
+ return false;
638
+ }
639
+ cancelGeneration() {
640
+ this.queue.length = 0;
641
+ this.mentionAc?.abort();
642
+ this.mentionAc = undefined;
643
+ this.abortRuns();
644
+ this.session?.abort();
645
+ this.onTurnComplete();
646
+ this.tui.requestRender();
647
+ }
648
+ paletteItems() {
649
+ return filterCommands(this.commands, this.editor.getValue(), this.paletteDismissedFor).slice(0, MAX_LIST_ROWS);
650
+ }
651
+ paletteMove(delta) {
652
+ const items = this.paletteItems();
653
+ if (items.length === 0)
654
+ return true;
655
+ this.paletteCursor = (this.paletteCursor + delta + items.length) % items.length;
656
+ this.tui.requestRender();
657
+ return true;
658
+ }
659
+ paletteComplete() {
660
+ const items = this.paletteItems();
661
+ const command = items[this.paletteCursor];
662
+ if (!command)
663
+ return true;
664
+ const next = `/${command.name} `;
665
+ this.editor.setValue(next);
666
+ this.editor.setCursor(next.length);
667
+ return true;
668
+ }
669
+ paletteRun() {
670
+ const items = this.paletteItems();
671
+ const command = items[this.paletteCursor];
672
+ if (!command)
673
+ return true;
674
+ this.editor.setValue('');
675
+ void command.run('');
676
+ return true;
677
+ }
678
+ runCommand(value) {
679
+ const body = value.trim().slice(1);
680
+ const space = body.indexOf(' ');
681
+ const name = (space === -1 ? body : body.slice(0, space)).toLowerCase();
682
+ const args = space === -1 ? '' : body.slice(space + 1).trim();
683
+ const command = this.commands.find((c) => c.name === name);
684
+ if (!command) {
685
+ this.transcript.note(`Unknown command: /${name}`, true);
686
+ this.tui.requestRender();
687
+ return;
688
+ }
689
+ void command.run(args);
690
+ }
691
+ pickerVisible() {
692
+ return this.pickerMention !== undefined && this.pickerRanked.length > 0 && this.paletteItems().length === 0;
693
+ }
694
+ pickerMove(delta) {
695
+ if (this.pickerRanked.length === 0)
696
+ return true;
697
+ this.pickerCursor = (this.pickerCursor + delta + this.pickerRanked.length) % this.pickerRanked.length;
698
+ this.tui.requestRender();
699
+ return true;
700
+ }
701
+ pickerAccept() {
702
+ const mention = this.pickerMention;
703
+ const candidate = this.pickerRanked[this.pickerCursor];
704
+ if (!mention || !candidate)
705
+ return true;
706
+ const value = this.editor.getValue();
707
+ const cursor = this.editor.cursorPos;
708
+ const insertion = `@${candidate.insert} `;
709
+ const next = value.slice(0, mention.start) + insertion + value.slice(cursor);
710
+ this.editor.setValue(next);
711
+ this.editor.setCursor(mention.start + insertion.length);
712
+ this.pickerMention = undefined;
713
+ this.pickerRanked = [];
714
+ this.tui.requestRender();
715
+ return true;
716
+ }
717
+ deleteMention() {
718
+ const value = this.editor.getValue();
719
+ const cursor = this.editor.cursorPos;
720
+ const re = /@[^\s]+/g;
721
+ let match;
722
+ while ((match = re.exec(value)) !== null) {
723
+ const start = match.index;
724
+ const end = start + match[0].length;
725
+ if (cursor > start && cursor <= end) {
726
+ this.editor.setValue(value.slice(0, start) + value.slice(end));
727
+ this.editor.setCursor(start);
728
+ this.tui.requestRender();
729
+ return true;
730
+ }
731
+ }
732
+ return false;
733
+ }
734
+ pushHistory(text) {
735
+ this.host.history?.append(text);
736
+ if (this.history[this.history.length - 1] !== text)
737
+ this.history.push(text);
738
+ this.historyIndex = this.history.length;
739
+ this.historyDraft = '';
740
+ }
741
+ navigateHistory(direction) {
742
+ if (this.history.length === 0)
743
+ return false;
744
+ if (direction === 'up') {
745
+ if (this.historyIndex === 0)
746
+ return true;
747
+ if (this.historyIndex === this.history.length)
748
+ this.historyDraft = this.editor.getValue();
749
+ this.historyIndex -= 1;
750
+ }
751
+ else {
752
+ if (this.historyIndex >= this.history.length)
753
+ return true;
754
+ this.historyIndex += 1;
755
+ }
756
+ const value = this.historyIndex === this.history.length ? this.historyDraft : this.history[this.historyIndex];
757
+ this.editor.setValue(value);
758
+ this.editor.setCursor(value.length);
759
+ this.tui.requestRender();
760
+ return true;
761
+ }
762
+ newSession() {
763
+ if (this.running) {
764
+ this.showError('Cannot start a new session while a response is running.');
765
+ return;
766
+ }
767
+ this.swapSession(this.host.createSession());
768
+ }
769
+ async openModelPicker() {
770
+ if (this.running) {
771
+ this.showError('Cannot switch models while a response is running.');
772
+ return;
773
+ }
774
+ if (this.models.length === 0)
775
+ await this.loadModels();
776
+ if (this.models.length === 0) {
777
+ this.showError('No models available from the backend.');
778
+ return;
779
+ }
780
+ const ref = this.host.modelRef();
781
+ const currentId = ref.includes('/') ? ref.slice(ref.indexOf('/') + 1) : ref;
782
+ const content = this.buildModelPicker(currentId, (id) => {
783
+ this.modelHandle?.close();
784
+ void this.switchModel(`local/${id}`);
785
+ });
786
+ this.modelPickerOpen = true;
787
+ this.modelHandle = this.tui.showModal(content, {
788
+ width: 60,
789
+ border: false,
790
+ background: this.theme().colors.surface,
791
+ onClose: () => {
792
+ this.modelPickerOpen = false;
793
+ this.tui.setFocus(this.editor);
794
+ },
795
+ });
796
+ }
797
+ buildModelPicker(currentId, onPick) {
798
+ const models = this.models;
799
+ const maxId = models.reduce((max, m) => Math.max(max, m.id.length), 0);
800
+ const PADX = 0;
801
+ let cursor = Math.max(0, models.findIndex((m) => m.id === currentId));
802
+ return {
803
+ handleInput: (event) => {
804
+ if (event.type !== 'key' || event.kind === 'release' || models.length === 0)
805
+ return;
806
+ if (event.key === 'up')
807
+ cursor = (cursor - 1 + models.length) % models.length;
808
+ else if (event.key === 'down')
809
+ cursor = (cursor + 1) % models.length;
810
+ else if (event.key === 'enter')
811
+ onPick(models[cursor].id);
812
+ },
813
+ render: (s) => {
814
+ if (s.width <= 0)
815
+ return;
816
+ const theme = this.theme();
817
+ const itemSgr = styleToAnsi(theme.styles.commandPaletteItem);
818
+ const selSgr = styleToAnsi(theme.styles.commandPaletteSelected);
819
+ const muted = styleToAnsi(theme.styles.muted);
820
+ const ITEM_INDENT = 2;
821
+ const textX = PADX + ITEM_INDENT;
822
+ const innerW = Math.max(1, s.width - PADX * 2);
823
+ s.text(textX, 1, `${styleToAnsi(theme.styles.title)}Model Picker${RESET}`);
824
+ const maxRows = Math.min(models.length, 10, Math.max(1, s.height - 6));
825
+ const top = cursor >= maxRows ? cursor - maxRows + 1 : 0;
826
+ for (let r = 0; r < maxRows; r++) {
827
+ const m = models[top + r];
828
+ if (!m)
829
+ break;
830
+ const isSel = top + r === cursor;
831
+ const pad = ' '.repeat(Math.max(0, maxId - m.id.length));
832
+ const provider = m.ownedBy ? ` ${m.ownedBy}` : '';
833
+ const indent = ' '.repeat(ITEM_INDENT);
834
+ const body = isSel ? `${indent}${m.id}${pad}${provider}` : `${indent}${m.id}${pad}${DIM}${provider}`;
835
+ s.text(PADX, 3 + r, `${isSel ? selSgr : itemSgr}${padTo(body, innerW)}${RESET}`);
836
+ }
837
+ const footerRow = 3 + maxRows + 1;
838
+ s.text(textX, footerRow, `${muted}↑/↓ move · Enter select · Esc close${RESET}`);
839
+ s.text(0, footerRow + 1, '');
840
+ },
841
+ };
842
+ }
843
+ moveApproval(delta) {
844
+ const count = APPROVAL_OPTIONS.length;
845
+ this.approvalCursor = (this.approvalCursor + delta + count) % count;
846
+ this.tui.requestRender();
847
+ return true;
848
+ }
849
+ resolveApproval(action) {
850
+ const req = this.approvalQueue.shift();
851
+ if (!req)
852
+ return true;
853
+ this.approvalCursor = 0;
854
+ this.host.approvals.resolve(req.id, action);
855
+ this.tui.requestRender();
856
+ return true;
857
+ }
858
+ approvalView() {
859
+ const req = this.approvalQueue[0];
860
+ if (!req)
861
+ return undefined;
862
+ const cursor = this.approvalCursor;
863
+ const args = formatToolArgs(req.name, req.input);
864
+ return {
865
+ render: (s) => {
866
+ if (s.width <= 0)
867
+ return;
868
+ const theme = this.theme();
869
+ const itemSgr = styleToAnsi(theme.styles.commandPaletteItem);
870
+ const selSgr = styleToAnsi({ ...theme.styles.commandPaletteSelected, fg: '#000000' });
871
+ const muted = styleToAnsi(theme.styles.muted);
872
+ const innerW = Math.max(1, s.width);
873
+ const queued = this.approvalQueue.length > 1 ? ` (+${this.approvalQueue.length - 1} more)` : '';
874
+ const title = (req.agent ? `Tool approval · ${req.agent}` : 'Tool approval') + queued;
875
+ s.text(0, 0, `${styleToAnsi(theme.styles.title)}${title}${RESET}`);
876
+ const head = args ? `${req.name} ${args}` : req.name;
877
+ s.text(0, 1, `${muted}${truncateToWidth(head, innerW)}${RESET}`);
878
+ const choices = APPROVAL_OPTIONS
879
+ .map((opt, r) => `${r === cursor ? selSgr : itemSgr} ${opt.label} ${RESET}`)
880
+ .join(`${muted} ${RESET}`);
881
+ s.text(0, 3, choices);
882
+ s.text(0, 4, `${muted}←/→ or tab move · Enter select · Esc deny${RESET}`);
883
+ },
884
+ };
885
+ }
886
+ async switchModel(ref) {
887
+ this.host.selectModel(ref);
888
+ const current = this.session;
889
+ const carry = current ? current.messages.some((m) => m.role !== 'system') : false;
890
+ let next;
891
+ try {
892
+ next = carry && current
893
+ ? await this.host.forkSession(current.id, current.messages.length - 1)
894
+ : this.host.createSession();
895
+ }
896
+ catch {
897
+ next = this.host.createSession();
898
+ }
899
+ this.swapSession(next);
900
+ this.transcript.note(`switched model to ${ref}`);
901
+ this.tui.requestRender();
902
+ }
903
+ async exportContext(args) {
904
+ const all = this.session?.messages ?? [];
905
+ if (all.length === 0) {
906
+ this.showError('Nothing to export yet.');
907
+ return;
908
+ }
909
+ const outputPath = args.trim() || '.mu/context.json';
910
+ const resolved = resolve(this.host.cwd, outputPath);
911
+ const rel = relative(this.host.cwd, resolved);
912
+ if (rel.startsWith('..') || isAbsolute(rel)) {
913
+ this.showError('Export path must stay inside the project directory.');
914
+ return;
915
+ }
916
+ const system = all.filter((message) => message.role === 'system').map(textOf).join('\n\n');
917
+ const payload = {
918
+ exportedAt: new Date().toISOString(),
919
+ session: {
920
+ id: this.session?.id ?? '',
921
+ cwd: this.host.cwd,
922
+ agent: this.host.agentRef(),
923
+ model: this.host.modelRef(),
924
+ },
925
+ request: {
926
+ system,
927
+ tools: (this.session?.tools ?? []).map((tool) => ({
928
+ name: tool.name,
929
+ description: tool.description,
930
+ parameters: tool.parameters,
931
+ ...(tool.prompt ? { prompt: tool.prompt } : {}),
932
+ })),
933
+ messages: all.filter((message) => message.role !== 'system'),
934
+ },
935
+ };
936
+ try {
937
+ await mkdir(dirname(resolved), { recursive: true });
938
+ await writeFile(resolved, `${JSON.stringify(payload, encodeBinary, 2)}\n`, 'utf-8');
939
+ this.transcript.note(`saved full context to ${outputPath}`);
940
+ }
941
+ catch (error) {
942
+ this.showError(`Failed to export: ${error instanceof Error ? error.message : String(error)}`);
943
+ }
944
+ this.tui.requestRender();
945
+ }
946
+ toggleExpand() {
947
+ if (this.transcript.toggleExpanded())
948
+ this.tui.requestRender();
949
+ }
950
+ toggleThinking() {
951
+ this.host.saveThinking(this.transcript.toggleReasoning());
952
+ this.tui.requestRender();
953
+ }
954
+ toggleTheme() {
955
+ const next = this.theme().name === 'dark' ? themesByName.light : themesByName.dark;
956
+ this.themeProvider.setTheme(next);
957
+ this.host.saveTheme(next.name);
958
+ }
959
+ onCtrlC() {
960
+ if (this.editor.getValue().length > 0) {
961
+ this.editor.setValue('');
962
+ this.tui.requestRender();
963
+ return;
964
+ }
965
+ void this.stop().then(() => this.host.onExit(130));
966
+ }
967
+ recordShell(cmd, output) {
968
+ const MAX = 10_000;
969
+ const capped = output.length > MAX ? `${output.slice(0, MAX)}\n…[truncated]` : output;
970
+ this.pendingShell.push({ cmd, output: capped });
971
+ }
972
+ runShell(cmd) {
973
+ if (!cmd)
974
+ return;
975
+ const handle = this.transcript.appendShell(cmd);
976
+ this.tui.requestRender();
977
+ let stdout = '';
978
+ let stderr = '';
979
+ let proc;
980
+ try {
981
+ proc = spawn('bash', ['-c', cmd], { cwd: this.host.cwd, stdio: ['pipe', 'pipe', 'pipe'] });
982
+ }
983
+ catch (err) {
984
+ const message = err instanceof Error ? err.message : String(err);
985
+ handle.setOutput(message, true);
986
+ this.recordShell(cmd, message);
987
+ this.tui.requestRender();
988
+ return;
989
+ }
990
+ proc.stdout?.on('data', (data) => {
991
+ stdout += data.toString('utf-8');
992
+ });
993
+ proc.stderr?.on('data', (data) => {
994
+ stderr += data.toString('utf-8');
995
+ });
996
+ proc.on('close', (code) => {
997
+ const output = code !== 0 || stderr ? [stdout, stderr].filter(Boolean).join('\n') : stdout;
998
+ const trimmed = output.trim() || '(no output)';
999
+ handle.setOutput(trimmed, code !== 0);
1000
+ this.recordShell(cmd, code !== 0 ? `(exit ${code})\n${trimmed}` : trimmed);
1001
+ this.tui.requestRender();
1002
+ });
1003
+ proc.on('error', (err) => {
1004
+ handle.setOutput(err.message, true);
1005
+ this.recordShell(cmd, err.message);
1006
+ this.tui.requestRender();
1007
+ });
1008
+ }
1009
+ setStatus(label) {
1010
+ this.status.label = label;
1011
+ }
1012
+ showError(message) {
1013
+ this.errorText = message;
1014
+ if (this.errorTimer)
1015
+ clearTimeout(this.errorTimer);
1016
+ this.errorTimer = setTimeout(() => {
1017
+ this.errorText = undefined;
1018
+ this.errorTimer = undefined;
1019
+ this.tui.requestRender();
1020
+ }, 6000);
1021
+ this.tui.requestRender();
1022
+ }
1023
+ clearError() {
1024
+ if (this.errorTimer)
1025
+ clearTimeout(this.errorTimer);
1026
+ this.errorTimer = undefined;
1027
+ this.errorText = undefined;
1028
+ }
1029
+ startSpinner() {
1030
+ this.status.busy = true;
1031
+ if (this.spinnerTimer)
1032
+ return;
1033
+ this.spinnerTimer = setInterval(() => {
1034
+ this.status.spinnerTick += 1;
1035
+ this.tui.requestRender();
1036
+ }, SPINNER_INTERVAL_MS);
1037
+ }
1038
+ stopSpinner() {
1039
+ if (this.spinnerTimer) {
1040
+ clearInterval(this.spinnerTimer);
1041
+ this.spinnerTimer = undefined;
1042
+ }
1043
+ this.status.busy = false;
1044
+ }
1045
+ async loadModels() {
1046
+ try {
1047
+ this.models = await this.host.listModels();
1048
+ this.tui.requestRender();
1049
+ }
1050
+ catch {
1051
+ // backend may be unreachable; surfaced on first send
1052
+ }
1053
+ }
1054
+ /** Plain model id (+ provider) shown in the status bar. */
1055
+ modelText() {
1056
+ const ref = this.host.modelRef();
1057
+ const slash = ref.indexOf('/');
1058
+ const id = slash >= 0 ? ref.slice(slash + 1) : ref;
1059
+ const providerName = slash >= 0 ? ref.slice(0, slash) : '';
1060
+ const model = this.models.find((m) => m.id === id);
1061
+ const provider = model?.ownedBy ?? providerName;
1062
+ return provider ? `${id} ${provider}` : id;
1063
+ }
1064
+ updateSpeaker() {
1065
+ this.transcript.speaker = { name: this.host.agentRef(), color: asHexColor(this.host.agentColor()) };
1066
+ }
1067
+ promptGlyph() {
1068
+ const theme = this.theme();
1069
+ const muted = styleToAnsi(theme.styles.muted);
1070
+ const value = this.editor.getValue();
1071
+ if (value.startsWith('!') || value.startsWith('$'))
1072
+ return `${styleToAnsi(theme.styles.bashPrompt)}$ ${RESET}`;
1073
+ if (value.startsWith('/'))
1074
+ return `${muted}/ ${RESET}`;
1075
+ if (this.pickerVisible())
1076
+ return `${muted}@ ${RESET}`;
1077
+ return `${muted}❯ ${RESET}`;
1078
+ }
1079
+ inputPanel() {
1080
+ const inner = this.approvalView() ?? this.editorInner();
1081
+ return box(inner, { background: this.theme().colors.surface, padding: 1 });
1082
+ }
1083
+ editorInner() {
1084
+ const prompt = this.promptGlyph();
1085
+ const editor = this.editor;
1086
+ const editorRows = editor.rows();
1087
+ return {
1088
+ render: (s) => {
1089
+ if (s.width <= 0 || s.height <= 0)
1090
+ return;
1091
+ s.text(0, 0, prompt);
1092
+ const rows = Math.min(editorRows, Math.max(1, s.height - 1));
1093
+ s.child(editor, { x: PROMPT_WIDTH, y: 0, width: Math.max(1, s.width - PROMPT_WIDTH), height: rows });
1094
+ },
1095
+ };
1096
+ }
1097
+ errorView() {
1098
+ const message = this.errorText;
1099
+ if (!message)
1100
+ return undefined;
1101
+ const theme = this.theme();
1102
+ const head = styleToAnsi(theme.styles.errorPrefix);
1103
+ const body = styleToAnsi(theme.styles.errorLine);
1104
+ return {
1105
+ render: (s) => {
1106
+ if (s.width <= 0)
1107
+ return;
1108
+ const text = visibleWidth(message) > s.width - 2 ? truncateToWidth(message, Math.max(1, s.width - 2)) : message;
1109
+ s.text(0, 0, `${head}!${RESET} ${body}${text}${RESET}`);
1110
+ s.text(0, 1, '');
1111
+ },
1112
+ };
1113
+ }
1114
+ waitingView() {
1115
+ if (this.queue.length === 0)
1116
+ return undefined;
1117
+ const theme = this.theme();
1118
+ const muted = styleToAnsi(theme.styles.muted);
1119
+ const body = styleToAnsi(theme.styles.body);
1120
+ const rows = this.queue.slice(0, 6).map((entry) => entry.replace(/\s+/g, ' '));
1121
+ return {
1122
+ render: (s) => {
1123
+ for (let i = 0; i < rows.length; i++) {
1124
+ const tag = '[follow-up] ';
1125
+ const text = padTo(rows[i], Math.max(0, s.width - tag.length));
1126
+ s.text(0, i, `${muted}${tag}${RESET}${body}${text}${RESET}`);
1127
+ }
1128
+ },
1129
+ };
1130
+ }
1131
+ /** The input area: error/palette/picker/waiting affordances + the input panel (no status bar). */
1132
+ inputGroup() {
1133
+ const children = [];
1134
+ const error = this.errorView();
1135
+ if (error)
1136
+ children.push(error);
1137
+ const palette = this.paletteItems();
1138
+ if (palette.length > 0) {
1139
+ const rows = palette.map((c) => ({ left: `/${c.name}`, right: c.description }));
1140
+ children.push(listView(rows, this.paletteCursor, this.theme()));
1141
+ }
1142
+ else if (this.pickerVisible()) {
1143
+ const rows = this.pickerRanked.map((c) => ({ left: c.label, right: c.kind === 'agent' ? 'agent' : '' }));
1144
+ children.push(listView(rows, this.pickerCursor, this.theme()));
1145
+ }
1146
+ const waiting = this.waitingView();
1147
+ if (waiting)
1148
+ children.push(waiting);
1149
+ children.push(this.inputPanel());
1150
+ return children;
1151
+ }
1152
+ statusBar() {
1153
+ this.status.model = this.modelText();
1154
+ return statusComponent(this.status, this.theme());
1155
+ }
1156
+ dock() {
1157
+ return column([...this.inputGroup(), this.statusBar()]);
1158
+ }
1159
+ jumpToBottom() {
1160
+ this.scroll.scrollToBottom();
1161
+ this.tui.requestRender();
1162
+ }
1163
+ jumpToBottomHint() {
1164
+ const theme = this.theme();
1165
+ const label = ' ↓ Jump to bottom (ctrl+End) ';
1166
+ const pill = styleToAnsi({ fg: theme.colors.text, bg: theme.colors.surfaceMuted });
1167
+ let pillStart = 0;
1168
+ let pillEnd = 0;
1169
+ return {
1170
+ handleInput: (event) => {
1171
+ if (event.type !== 'mouse' || event.kind !== 'press' || event.button !== 'left')
1172
+ return false;
1173
+ if (event.localY !== 0)
1174
+ return false;
1175
+ if (event.localX === undefined || event.localX < pillStart || event.localX >= pillEnd)
1176
+ return false;
1177
+ this.jumpToBottom();
1178
+ return true;
1179
+ },
1180
+ render: (s) => {
1181
+ if (s.width <= 0)
1182
+ return;
1183
+ const text = visibleWidth(label) > s.width ? truncateToWidth(label, s.width) : label;
1184
+ const width = visibleWidth(text);
1185
+ pillStart = Math.max(0, Math.floor((s.width - width) / 2));
1186
+ pillEnd = pillStart + width;
1187
+ s.text(pillStart, 0, `${pill}${text}${RESET}`);
1188
+ s.text(0, 1, '');
1189
+ },
1190
+ };
1191
+ }
1192
+ stickyHeaderView(info) {
1193
+ const gov = this.governingUser(info.scrollY, info.width);
1194
+ if (!gov || info.scrollY < gov.endRow)
1195
+ return undefined;
1196
+ return stickyHeader(gov.text, this.theme());
1197
+ }
1198
+ governingUser(scrollY, width) {
1199
+ if (width <= 0)
1200
+ return undefined;
1201
+ const theme = this.theme();
1202
+ const expanded = this.transcript.expanded;
1203
+ let y = 0;
1204
+ let current;
1205
+ for (const entry of this.transcript.entries) {
1206
+ if (y > scrollY)
1207
+ break;
1208
+ const margin = entry.kind === 'tool_call' ? 0 : 1;
1209
+ const height = measure(entryComponent(entry, theme, expanded), width) + margin;
1210
+ if (entry.kind === 'user')
1211
+ current = { text: entry.text, endRow: y + height };
1212
+ y += height;
1213
+ }
1214
+ return current;
1215
+ }
1216
+ focusedSub() {
1217
+ for (const entry of this.transcript.entries) {
1218
+ if (entry.kind === 'subagent' && entry.open)
1219
+ return entry;
1220
+ }
1221
+ return undefined;
1222
+ }
1223
+ subAgentHeader(entry) {
1224
+ const theme = this.theme();
1225
+ const color = entry.status === 'done'
1226
+ ? theme.colors.success
1227
+ : entry.status === 'error'
1228
+ ? theme.styles.errorPrefix.fg ?? theme.colors.danger
1229
+ : entry.status === 'canceled'
1230
+ ? theme.colors.textMuted
1231
+ : theme.colors.accent;
1232
+ const accent = styleToAnsi({ fg: color, bold: true });
1233
+ const muted = styleToAnsi(theme.styles.muted);
1234
+ const line = `${accent}▌ ${entry.agent}${RESET}${muted} sub-agent · ${entry.status} · esc to close${RESET}`;
1235
+ return {
1236
+ render: (s) => {
1237
+ if (s.width <= 0)
1238
+ return;
1239
+ s.text(0, 0, visibleWidth(line) > s.width ? truncateToWidth(line, s.width) : line);
1240
+ s.text(0, 1, '');
1241
+ },
1242
+ };
1243
+ }
1244
+ subAgentView(entry) {
1245
+ this.subTranscript.seed(entry.messages ?? []);
1246
+ return column([this.subAgentHeader(entry), flex(this.subScroll)]);
1247
+ }
1248
+ /** Wraps a child to a max width, horizontally centered (keeps its natural height). */
1249
+ centered(child, maxW) {
1250
+ return {
1251
+ render: (s) => {
1252
+ const w = Math.max(0, Math.min(maxW, s.width));
1253
+ if (w === 0)
1254
+ return;
1255
+ const h = s.measure(child, w);
1256
+ const x = Math.max(0, Math.floor((s.width - w) / 2));
1257
+ s.child(child, { x, y: 0, width: w, height: h });
1258
+ },
1259
+ };
1260
+ }
1261
+ /** The ASCII banner rendered at its natural height, centered as a block. */
1262
+ bannerBlock() {
1263
+ const theme = this.theme();
1264
+ const sgr = styleToAnsi({ fg: theme.colors.accent, bold: true });
1265
+ const lines = (this.banner ?? '').split('\n');
1266
+ const blockW = lines.reduce((max, line) => Math.max(max, visibleWidth(line)), 0);
1267
+ return {
1268
+ render: (s) => {
1269
+ if (s.width <= 0)
1270
+ return;
1271
+ const x = Math.max(0, Math.floor((s.width - blockW) / 2));
1272
+ for (let i = 0; i < lines.length; i++)
1273
+ s.text(x, i, `${sgr}${lines[i]}${RESET}`);
1274
+ s.text(0, lines.length, '');
1275
+ },
1276
+ };
1277
+ }
1278
+ root() {
1279
+ const focused = this.focusedSub();
1280
+ const showBanner = this.banner !== undefined && this.transcript.entries.length === 0 && !focused;
1281
+ const spacer = { render: () => { } };
1282
+ const inner = focused
1283
+ ? this.subAgentView(focused)
1284
+ : showBanner
1285
+ // Splash: banner + a centered, width-limited minimal input; status pinned at the bottom.
1286
+ ? column([
1287
+ flex(spacer),
1288
+ this.bannerBlock(),
1289
+ this.centered(column(this.inputGroup()), SPLASH_INPUT_WIDTH),
1290
+ flex(spacer),
1291
+ this.statusBar(),
1292
+ ])
1293
+ // Conversation: transcript fills, input docked at the bottom.
1294
+ : column([flex(this.scroll), this.dock()]);
1295
+ return {
1296
+ render: (s) => {
1297
+ s.fill({ x: 0, y: 0, width: s.width, height: s.height }, this.theme().colors.background);
1298
+ if (s.width <= 2) {
1299
+ inner.render(s);
1300
+ return;
1301
+ }
1302
+ s.child(inner, { x: 1, y: 0, width: s.width - 2, height: s.height });
1303
+ },
1304
+ };
1305
+ }
1306
+ }