nastech-tui 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (424) hide show
  1. package/.prettierrc +11 -0
  2. package/README.md +346 -0
  3. package/eslint.config.mjs +111 -0
  4. package/package.json +51 -0
  5. package/packages/nastech-ink/ambient.d.ts +83 -0
  6. package/packages/nastech-ink/index.d.ts +40 -0
  7. package/packages/nastech-ink/index.js +1 -0
  8. package/packages/nastech-ink/nastech-ink/ambient.d.ts +83 -0
  9. package/packages/nastech-ink/nastech-ink/index.d.ts +40 -0
  10. package/packages/nastech-ink/nastech-ink/index.js +1 -0
  11. package/packages/nastech-ink/nastech-ink/package.json +54 -0
  12. package/packages/nastech-ink/nastech-ink/src/bootstrap/state.ts +9 -0
  13. package/packages/nastech-ink/nastech-ink/src/entry-exports.ts +32 -0
  14. package/packages/nastech-ink/nastech-ink/src/hooks/use-stderr.ts +15 -0
  15. package/packages/nastech-ink/nastech-ink/src/hooks/use-stdout.ts +15 -0
  16. package/packages/nastech-ink/nastech-ink/src/ink/Ansi.tsx +435 -0
  17. package/packages/nastech-ink/nastech-ink/src/ink/app-mouse.test.ts +123 -0
  18. package/packages/nastech-ink/nastech-ink/src/ink/bidi.ts +145 -0
  19. package/packages/nastech-ink/nastech-ink/src/ink/cache-eviction.ts +45 -0
  20. package/packages/nastech-ink/nastech-ink/src/ink/clearTerminal.ts +68 -0
  21. package/packages/nastech-ink/nastech-ink/src/ink/colorize.test.ts +60 -0
  22. package/packages/nastech-ink/nastech-ink/src/ink/colorize.ts +277 -0
  23. package/packages/nastech-ink/nastech-ink/src/ink/components/AlternateScreen.tsx +133 -0
  24. package/packages/nastech-ink/nastech-ink/src/ink/components/App.tsx +830 -0
  25. package/packages/nastech-ink/nastech-ink/src/ink/components/AppContext.ts +20 -0
  26. package/packages/nastech-ink/nastech-ink/src/ink/components/Box.tsx +294 -0
  27. package/packages/nastech-ink/nastech-ink/src/ink/components/Button.tsx +236 -0
  28. package/packages/nastech-ink/nastech-ink/src/ink/components/ClockContext.tsx +133 -0
  29. package/packages/nastech-ink/nastech-ink/src/ink/components/CursorAdvanceContext.ts +35 -0
  30. package/packages/nastech-ink/nastech-ink/src/ink/components/CursorDeclarationContext.ts +28 -0
  31. package/packages/nastech-ink/nastech-ink/src/ink/components/ErrorOverview.tsx +130 -0
  32. package/packages/nastech-ink/nastech-ink/src/ink/components/Link.tsx +38 -0
  33. package/packages/nastech-ink/nastech-ink/src/ink/components/Newline.tsx +43 -0
  34. package/packages/nastech-ink/nastech-ink/src/ink/components/NoSelect.tsx +73 -0
  35. package/packages/nastech-ink/nastech-ink/src/ink/components/RawAnsi.tsx +61 -0
  36. package/packages/nastech-ink/nastech-ink/src/ink/components/ScrollBox.tsx +290 -0
  37. package/packages/nastech-ink/nastech-ink/src/ink/components/Spacer.tsx +23 -0
  38. package/packages/nastech-ink/nastech-ink/src/ink/components/StdinContext.ts +25 -0
  39. package/packages/nastech-ink/nastech-ink/src/ink/components/TerminalFocusContext.tsx +63 -0
  40. package/packages/nastech-ink/nastech-ink/src/ink/components/TerminalSizeContext.tsx +7 -0
  41. package/packages/nastech-ink/nastech-ink/src/ink/components/Text.test.ts +38 -0
  42. package/packages/nastech-ink/nastech-ink/src/ink/components/Text.tsx +336 -0
  43. package/packages/nastech-ink/nastech-ink/src/ink/constants.ts +6 -0
  44. package/packages/nastech-ink/nastech-ink/src/ink/cursor.ts +5 -0
  45. package/packages/nastech-ink/nastech-ink/src/ink/devtools.ts +2 -0
  46. package/packages/nastech-ink/nastech-ink/src/ink/dom.ts +495 -0
  47. package/packages/nastech-ink/nastech-ink/src/ink/events/click-event.ts +38 -0
  48. package/packages/nastech-ink/nastech-ink/src/ink/events/cmd-shortcuts.test.ts +65 -0
  49. package/packages/nastech-ink/nastech-ink/src/ink/events/dispatcher.ts +242 -0
  50. package/packages/nastech-ink/nastech-ink/src/ink/events/emitter.ts +40 -0
  51. package/packages/nastech-ink/nastech-ink/src/ink/events/event-handlers.ts +84 -0
  52. package/packages/nastech-ink/nastech-ink/src/ink/events/event.ts +11 -0
  53. package/packages/nastech-ink/nastech-ink/src/ink/events/focus-event.ts +18 -0
  54. package/packages/nastech-ink/nastech-ink/src/ink/events/input-event.ts +176 -0
  55. package/packages/nastech-ink/nastech-ink/src/ink/events/keyboard-event.ts +57 -0
  56. package/packages/nastech-ink/nastech-ink/src/ink/events/mouse-event.ts +18 -0
  57. package/packages/nastech-ink/nastech-ink/src/ink/events/paste-event.ts +10 -0
  58. package/packages/nastech-ink/nastech-ink/src/ink/events/resize-event.ts +12 -0
  59. package/packages/nastech-ink/nastech-ink/src/ink/events/terminal-event.ts +107 -0
  60. package/packages/nastech-ink/nastech-ink/src/ink/events/terminal-focus-event.ts +19 -0
  61. package/packages/nastech-ink/nastech-ink/src/ink/focus.ts +219 -0
  62. package/packages/nastech-ink/nastech-ink/src/ink/frame.ts +124 -0
  63. package/packages/nastech-ink/nastech-ink/src/ink/get-max-width.ts +27 -0
  64. package/packages/nastech-ink/nastech-ink/src/ink/global.d.ts +1 -0
  65. package/packages/nastech-ink/nastech-ink/src/ink/hit-test.test.ts +38 -0
  66. package/packages/nastech-ink/nastech-ink/src/ink/hit-test.ts +224 -0
  67. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-animation-frame.ts +62 -0
  68. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-app.ts +9 -0
  69. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-cursor-advance.ts +33 -0
  70. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-declared-cursor.ts +75 -0
  71. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-external-process.ts +27 -0
  72. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-input.ts +95 -0
  73. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-interval.ts +71 -0
  74. package/packages/nastech-ink/package.json +57 -0
  75. package/packages/nastech-ink/src/bootstrap/state.ts +9 -0
  76. package/packages/nastech-ink/src/entry-exports.ts +32 -0
  77. package/packages/nastech-ink/src/hooks/use-stderr.ts +15 -0
  78. package/packages/nastech-ink/src/hooks/use-stdout.ts +15 -0
  79. package/packages/nastech-ink/src/ink/Ansi.tsx +435 -0
  80. package/packages/nastech-ink/src/ink/app-mouse.test.ts +123 -0
  81. package/packages/nastech-ink/src/ink/app-rawmode-mouse.test.ts +91 -0
  82. package/packages/nastech-ink/src/ink/bidi.ts +145 -0
  83. package/packages/nastech-ink/src/ink/cache-eviction.ts +45 -0
  84. package/packages/nastech-ink/src/ink/clearTerminal.ts +68 -0
  85. package/packages/nastech-ink/src/ink/colorize.test.ts +60 -0
  86. package/packages/nastech-ink/src/ink/colorize.ts +277 -0
  87. package/packages/nastech-ink/src/ink/components/AlternateScreen.tsx +133 -0
  88. package/packages/nastech-ink/src/ink/components/App.tsx +855 -0
  89. package/packages/nastech-ink/src/ink/components/AppContext.ts +20 -0
  90. package/packages/nastech-ink/src/ink/components/Box.tsx +294 -0
  91. package/packages/nastech-ink/src/ink/components/Button.tsx +236 -0
  92. package/packages/nastech-ink/src/ink/components/ClockContext.tsx +133 -0
  93. package/packages/nastech-ink/src/ink/components/CursorAdvanceContext.ts +35 -0
  94. package/packages/nastech-ink/src/ink/components/CursorDeclarationContext.ts +28 -0
  95. package/packages/nastech-ink/src/ink/components/ErrorOverview.tsx +130 -0
  96. package/packages/nastech-ink/src/ink/components/Link.tsx +38 -0
  97. package/packages/nastech-ink/src/ink/components/Newline.tsx +43 -0
  98. package/packages/nastech-ink/src/ink/components/NoSelect.tsx +73 -0
  99. package/packages/nastech-ink/src/ink/components/RawAnsi.tsx +61 -0
  100. package/packages/nastech-ink/src/ink/components/ScrollBox.tsx +290 -0
  101. package/packages/nastech-ink/src/ink/components/Spacer.tsx +23 -0
  102. package/packages/nastech-ink/src/ink/components/StdinContext.ts +25 -0
  103. package/packages/nastech-ink/src/ink/components/TerminalFocusContext.tsx +63 -0
  104. package/packages/nastech-ink/src/ink/components/TerminalSizeContext.tsx +7 -0
  105. package/packages/nastech-ink/src/ink/components/Text.test.ts +38 -0
  106. package/packages/nastech-ink/src/ink/components/Text.tsx +336 -0
  107. package/packages/nastech-ink/src/ink/constants.ts +6 -0
  108. package/packages/nastech-ink/src/ink/cursor.ts +5 -0
  109. package/packages/nastech-ink/src/ink/devtools.ts +2 -0
  110. package/packages/nastech-ink/src/ink/dom.ts +495 -0
  111. package/packages/nastech-ink/src/ink/events/click-event.ts +38 -0
  112. package/packages/nastech-ink/src/ink/events/cmd-shortcuts.test.ts +65 -0
  113. package/packages/nastech-ink/src/ink/events/dispatcher.ts +242 -0
  114. package/packages/nastech-ink/src/ink/events/emitter.ts +40 -0
  115. package/packages/nastech-ink/src/ink/events/event-handlers.ts +84 -0
  116. package/packages/nastech-ink/src/ink/events/event.ts +11 -0
  117. package/packages/nastech-ink/src/ink/events/focus-event.ts +18 -0
  118. package/packages/nastech-ink/src/ink/events/input-event.ts +176 -0
  119. package/packages/nastech-ink/src/ink/events/keyboard-event.ts +57 -0
  120. package/packages/nastech-ink/src/ink/events/mouse-event.ts +18 -0
  121. package/packages/nastech-ink/src/ink/events/paste-event.ts +10 -0
  122. package/packages/nastech-ink/src/ink/events/resize-event.ts +12 -0
  123. package/packages/nastech-ink/src/ink/events/terminal-event.ts +107 -0
  124. package/packages/nastech-ink/src/ink/events/terminal-focus-event.ts +19 -0
  125. package/packages/nastech-ink/src/ink/focus.ts +219 -0
  126. package/packages/nastech-ink/src/ink/frame.ts +124 -0
  127. package/packages/nastech-ink/src/ink/get-max-width.ts +27 -0
  128. package/packages/nastech-ink/src/ink/global.d.ts +1 -0
  129. package/packages/nastech-ink/src/ink/hit-test.test.ts +38 -0
  130. package/packages/nastech-ink/src/ink/hit-test.ts +224 -0
  131. package/packages/nastech-ink/src/ink/hooks/use-animation-frame.ts +62 -0
  132. package/packages/nastech-ink/src/ink/hooks/use-app.ts +9 -0
  133. package/packages/nastech-ink/src/ink/hooks/use-cursor-advance.ts +33 -0
  134. package/packages/nastech-ink/src/ink/hooks/use-declared-cursor.ts +75 -0
  135. package/packages/nastech-ink/src/ink/hooks/use-external-process.ts +27 -0
  136. package/packages/nastech-ink/src/ink/hooks/use-input.ts +95 -0
  137. package/packages/nastech-ink/src/ink/hooks/use-interval.ts +71 -0
  138. package/packages/nastech-ink/src/ink/hooks/use-search-highlight.ts +56 -0
  139. package/packages/nastech-ink/src/ink/hooks/use-selection.ts +101 -0
  140. package/packages/nastech-ink/src/ink/hooks/use-stdin.ts +9 -0
  141. package/packages/nastech-ink/src/ink/hooks/use-tab-status.ts +71 -0
  142. package/packages/nastech-ink/src/ink/hooks/use-terminal-focus.ts +18 -0
  143. package/packages/nastech-ink/src/ink/hooks/use-terminal-title.ts +34 -0
  144. package/packages/nastech-ink/src/ink/hooks/use-terminal-viewport.ts +100 -0
  145. package/packages/nastech-ink/src/ink/hyperlinkHover.ts +52 -0
  146. package/packages/nastech-ink/src/ink/ink-cursor-advance.test.ts +234 -0
  147. package/packages/nastech-ink/src/ink/ink-resize.test.ts +50 -0
  148. package/packages/nastech-ink/src/ink/ink.tsx +2705 -0
  149. package/packages/nastech-ink/src/ink/instances.ts +10 -0
  150. package/packages/nastech-ink/src/ink/layout/engine.ts +6 -0
  151. package/packages/nastech-ink/src/ink/layout/geometry.ts +98 -0
  152. package/packages/nastech-ink/src/ink/layout/node.ts +145 -0
  153. package/packages/nastech-ink/src/ink/layout/yoga.ts +313 -0
  154. package/packages/nastech-ink/src/ink/line-width-cache.ts +38 -0
  155. package/packages/nastech-ink/src/ink/log-update.test.ts +223 -0
  156. package/packages/nastech-ink/src/ink/log-update.ts +752 -0
  157. package/packages/nastech-ink/src/ink/lru.ts +14 -0
  158. package/packages/nastech-ink/src/ink/measure-element.ts +23 -0
  159. package/packages/nastech-ink/src/ink/measure-text.ts +50 -0
  160. package/packages/nastech-ink/src/ink/node-cache.ts +53 -0
  161. package/packages/nastech-ink/src/ink/optimizer.ts +99 -0
  162. package/packages/nastech-ink/src/ink/output.ts +845 -0
  163. package/packages/nastech-ink/src/ink/parse-keypress.test.ts +133 -0
  164. package/packages/nastech-ink/src/ink/parse-keypress.ts +848 -0
  165. package/packages/nastech-ink/src/ink/reconciler.ts +382 -0
  166. package/packages/nastech-ink/src/ink/render-border.ts +206 -0
  167. package/packages/nastech-ink/src/ink/render-node-to-output.ts +1582 -0
  168. package/packages/nastech-ink/src/ink/render-to-screen.ts +236 -0
  169. package/packages/nastech-ink/src/ink/renderer.ts +169 -0
  170. package/packages/nastech-ink/src/ink/root.ts +204 -0
  171. package/packages/nastech-ink/src/ink/screen.ts +1590 -0
  172. package/packages/nastech-ink/src/ink/searchHighlight.ts +91 -0
  173. package/packages/nastech-ink/src/ink/selection.test.ts +82 -0
  174. package/packages/nastech-ink/src/ink/selection.ts +1143 -0
  175. package/packages/nastech-ink/src/ink/squash-text-nodes.ts +74 -0
  176. package/packages/nastech-ink/src/ink/stringWidth.ts +341 -0
  177. package/packages/nastech-ink/src/ink/styles.ts +750 -0
  178. package/packages/nastech-ink/src/ink/supports-hyperlinks.ts +51 -0
  179. package/packages/nastech-ink/src/ink/tabstops.ts +44 -0
  180. package/packages/nastech-ink/src/ink/terminal-focus-state.ts +52 -0
  181. package/packages/nastech-ink/src/ink/terminal-querier.ts +222 -0
  182. package/packages/nastech-ink/src/ink/terminal.test.ts +15 -0
  183. package/packages/nastech-ink/src/ink/terminal.ts +299 -0
  184. package/packages/nastech-ink/src/ink/termio/ansi.ts +75 -0
  185. package/packages/nastech-ink/src/ink/termio/csi.ts +334 -0
  186. package/packages/nastech-ink/src/ink/termio/dec.ts +99 -0
  187. package/packages/nastech-ink/src/ink/termio/esc.ts +69 -0
  188. package/packages/nastech-ink/src/ink/termio/osc.test.ts +191 -0
  189. package/packages/nastech-ink/src/ink/termio/osc.ts +724 -0
  190. package/packages/nastech-ink/src/ink/termio/parser.ts +467 -0
  191. package/packages/nastech-ink/src/ink/termio/sgr.ts +362 -0
  192. package/packages/nastech-ink/src/ink/termio/tokenize.test.ts +185 -0
  193. package/packages/nastech-ink/src/ink/termio/tokenize.ts +350 -0
  194. package/packages/nastech-ink/src/ink/termio/types.ts +230 -0
  195. package/packages/nastech-ink/src/ink/termio.ts +42 -0
  196. package/packages/nastech-ink/src/ink/useTerminalNotification.ts +110 -0
  197. package/packages/nastech-ink/src/ink/warn.ts +15 -0
  198. package/packages/nastech-ink/src/ink/widest-line.ts +22 -0
  199. package/packages/nastech-ink/src/ink/wrap-text.test.ts +17 -0
  200. package/packages/nastech-ink/src/ink/wrap-text.ts +144 -0
  201. package/packages/nastech-ink/src/ink/wrapAnsi.ts +13 -0
  202. package/packages/nastech-ink/src/native-ts/yoga-layout/enums.ts +112 -0
  203. package/packages/nastech-ink/src/native-ts/yoga-layout/index.ts +2326 -0
  204. package/packages/nastech-ink/src/utils/debug.ts +6 -0
  205. package/packages/nastech-ink/src/utils/earlyInput.ts +131 -0
  206. package/packages/nastech-ink/src/utils/env.ts +66 -0
  207. package/packages/nastech-ink/src/utils/envUtils.ts +13 -0
  208. package/packages/nastech-ink/src/utils/execFileNoThrow.test.ts +146 -0
  209. package/packages/nastech-ink/src/utils/execFileNoThrow.ts +115 -0
  210. package/packages/nastech-ink/src/utils/fullscreen.ts +3 -0
  211. package/packages/nastech-ink/src/utils/intl.ts +87 -0
  212. package/packages/nastech-ink/src/utils/log.ts +7 -0
  213. package/packages/nastech-ink/src/utils/semver.ts +57 -0
  214. package/packages/nastech-ink/src/utils/sliceAnsi.ts +106 -0
  215. package/packages/nastech-ink/text-input.d.ts +2 -0
  216. package/packages/nastech-ink/text-input.js +1 -0
  217. package/scripts/build.mjs +61 -0
  218. package/scripts/profile-tui.mjs +121 -0
  219. package/src/__tests__/activeSessionSwitcher.test.ts +157 -0
  220. package/src/__tests__/appChromeStatusRule.test.tsx +84 -0
  221. package/src/__tests__/appChromeStatusRuleDevCredits.test.tsx +73 -0
  222. package/src/__tests__/approvalAction.test.ts +50 -0
  223. package/src/__tests__/asCommandDispatch.test.ts +27 -0
  224. package/src/__tests__/blockLayout.test.ts +122 -0
  225. package/src/__tests__/clipboard.test.ts +369 -0
  226. package/src/__tests__/constants.test.ts +53 -0
  227. package/src/__tests__/createGatewayEventHandler.test.ts +1091 -0
  228. package/src/__tests__/createSlashHandler.test.ts +822 -0
  229. package/src/__tests__/creditsCommand.test.ts +144 -0
  230. package/src/__tests__/cursorDriftRegression.test.ts +114 -0
  231. package/src/__tests__/details.test.ts +115 -0
  232. package/src/__tests__/emoji.test.ts +64 -0
  233. package/src/__tests__/externalLink.test.ts +144 -0
  234. package/src/__tests__/forceTruecolor.test.ts +191 -0
  235. package/src/__tests__/gatewayClient.test.ts +394 -0
  236. package/src/__tests__/gatewayRecovery.test.ts +47 -0
  237. package/src/__tests__/markdown.test.ts +331 -0
  238. package/src/__tests__/mathUnicode.test.ts +293 -0
  239. package/src/__tests__/memoryMonitor.test.ts +102 -0
  240. package/src/__tests__/messageLine.test.ts +19 -0
  241. package/src/__tests__/messages.test.ts +92 -0
  242. package/src/__tests__/orchestratorPromptSession.test.ts +64 -0
  243. package/src/__tests__/osc52.test.ts +67 -0
  244. package/src/__tests__/parentLog.test.ts +75 -0
  245. package/src/__tests__/paths.test.ts +70 -0
  246. package/src/__tests__/platform.test.ts +556 -0
  247. package/src/__tests__/precisionWheel.test.ts +44 -0
  248. package/src/__tests__/prompt.test.ts +31 -0
  249. package/src/__tests__/providers.test.ts +65 -0
  250. package/src/__tests__/reasoning.test.ts +76 -0
  251. package/src/__tests__/rpc.test.ts +27 -0
  252. package/src/__tests__/scroll.test.ts +99 -0
  253. package/src/__tests__/slashParity.test.ts +123 -0
  254. package/src/__tests__/spawnHistoryStore.test.ts +46 -0
  255. package/src/__tests__/stateIsolation.test.ts +46 -0
  256. package/src/__tests__/statusBarTicker.test.ts +18 -0
  257. package/src/__tests__/statusRule.test.ts +32 -0
  258. package/src/__tests__/streamingMarkdown.test.ts +121 -0
  259. package/src/__tests__/subagentTree.test.ts +407 -0
  260. package/src/__tests__/syntax.test.ts +45 -0
  261. package/src/__tests__/terminalModes.test.ts +39 -0
  262. package/src/__tests__/terminalParity.test.ts +77 -0
  263. package/src/__tests__/terminalSetup.test.ts +386 -0
  264. package/src/__tests__/termux.test.ts +35 -0
  265. package/src/__tests__/termuxComposerLayout.test.ts +40 -0
  266. package/src/__tests__/text.test.ts +233 -0
  267. package/src/__tests__/textInputBurstInput.test.ts +40 -0
  268. package/src/__tests__/textInputCursorSourceOfTruth.test.ts +50 -0
  269. package/src/__tests__/textInputFastEcho.test.ts +200 -0
  270. package/src/__tests__/textInputLineNav.test.ts +55 -0
  271. package/src/__tests__/textInputPassThrough.test.ts +59 -0
  272. package/src/__tests__/textInputRightClick.test.ts +48 -0
  273. package/src/__tests__/textInputWrap.test.ts +151 -0
  274. package/src/__tests__/theme.test.ts +311 -0
  275. package/src/__tests__/turnControllerNotice.test.ts +43 -0
  276. package/src/__tests__/turnStore.test.ts +66 -0
  277. package/src/__tests__/useCompletion.test.ts +35 -0
  278. package/src/__tests__/useComposerState.test.ts +59 -0
  279. package/src/__tests__/useConfigSync.test.ts +460 -0
  280. package/src/__tests__/useInputHandlers.test.ts +77 -0
  281. package/src/__tests__/useQueue.test.ts +28 -0
  282. package/src/__tests__/useSessionLifecycle.test.ts +60 -0
  283. package/src/__tests__/useVirtualHistoryHeights.test.ts +39 -0
  284. package/src/__tests__/viewport.test.ts +58 -0
  285. package/src/__tests__/viewportStore.test.ts +85 -0
  286. package/src/__tests__/virtualHeights.test.ts +96 -0
  287. package/src/__tests__/virtualHistoryClamp.test.ts +19 -0
  288. package/src/__tests__/virtualHistoryOffsetCache.test.ts +282 -0
  289. package/src/__tests__/wheelAccel.test.ts +138 -0
  290. package/src/app/createGatewayEventHandler.ts +833 -0
  291. package/src/app/createSlashHandler.ts +130 -0
  292. package/src/app/delegationStore.ts +77 -0
  293. package/src/app/gatewayContext.tsx +19 -0
  294. package/src/app/gatewayRecovery.ts +35 -0
  295. package/src/app/inputSelectionStore.ts +15 -0
  296. package/src/app/interfaces.ts +394 -0
  297. package/src/app/overlayStore.ts +53 -0
  298. package/src/app/scroll.ts +71 -0
  299. package/src/app/setupHandoff.ts +54 -0
  300. package/src/app/slash/commands/core.ts +648 -0
  301. package/src/app/slash/commands/credits.ts +57 -0
  302. package/src/app/slash/commands/debug.ts +48 -0
  303. package/src/app/slash/commands/ops.ts +717 -0
  304. package/src/app/slash/commands/session.ts +554 -0
  305. package/src/app/slash/commands/setup.ts +20 -0
  306. package/src/app/slash/registry.ts +20 -0
  307. package/src/app/slash/types.ts +21 -0
  308. package/src/app/spawnHistoryStore.ts +159 -0
  309. package/src/app/turnController.ts +866 -0
  310. package/src/app/turnStore.ts +85 -0
  311. package/src/app/uiStore.ts +44 -0
  312. package/src/app/useComposerState.ts +367 -0
  313. package/src/app/useConfigSync.ts +288 -0
  314. package/src/app/useInputHandlers.ts +576 -0
  315. package/src/app/useLongRunToolCharms.ts +69 -0
  316. package/src/app/useMainApp.ts +1039 -0
  317. package/src/app/useSessionLifecycle.ts +366 -0
  318. package/src/app/useSubmission.ts +429 -0
  319. package/src/app.tsx +25 -0
  320. package/src/banner.ts +93 -0
  321. package/src/components/activeSessionSwitcher.tsx +635 -0
  322. package/src/components/agentsOverlay.tsx +1073 -0
  323. package/src/components/appChrome.tsx +554 -0
  324. package/src/components/appLayout.tsx +444 -0
  325. package/src/components/appOverlays.tsx +254 -0
  326. package/src/components/branding.tsx +466 -0
  327. package/src/components/fpsOverlay.tsx +30 -0
  328. package/src/components/helpHint.tsx +73 -0
  329. package/src/components/markdown.tsx +1119 -0
  330. package/src/components/maskedPrompt.tsx +34 -0
  331. package/src/components/messageLine.tsx +237 -0
  332. package/src/components/modelPicker.tsx +527 -0
  333. package/src/components/overlayControls.tsx +50 -0
  334. package/src/components/pluginsHub.tsx +238 -0
  335. package/src/components/prompts.tsx +276 -0
  336. package/src/components/queuedMessages.tsx +64 -0
  337. package/src/components/sessionPicker.tsx +227 -0
  338. package/src/components/skillsHub.tsx +308 -0
  339. package/src/components/streamingAssistant.tsx +110 -0
  340. package/src/components/streamingMarkdown.tsx +174 -0
  341. package/src/components/textInput.tsx +1340 -0
  342. package/src/components/themed.tsx +30 -0
  343. package/src/components/thinking.tsx +1224 -0
  344. package/src/components/todoPanel.tsx +93 -0
  345. package/src/config/env.ts +64 -0
  346. package/src/config/limits.ts +13 -0
  347. package/src/config/timing.ts +6 -0
  348. package/src/content/charms.ts +1 -0
  349. package/src/content/faces.ts +17 -0
  350. package/src/content/fortunes.ts +30 -0
  351. package/src/content/hotkeys.ts +37 -0
  352. package/src/content/placeholders.ts +13 -0
  353. package/src/content/setup.ts +17 -0
  354. package/src/content/verbs.ts +38 -0
  355. package/src/domain/blockLayout.ts +146 -0
  356. package/src/domain/details.ts +76 -0
  357. package/src/domain/messages.ts +91 -0
  358. package/src/domain/paths.ts +16 -0
  359. package/src/domain/providers.ts +11 -0
  360. package/src/domain/roles.ts +9 -0
  361. package/src/domain/slash.ts +10 -0
  362. package/src/domain/usage.ts +3 -0
  363. package/src/domain/viewport.ts +51 -0
  364. package/src/entry.tsx +104 -0
  365. package/src/gatewayClient.ts +730 -0
  366. package/src/gatewayTypes.ts +568 -0
  367. package/src/hooks/useCompletion.ts +112 -0
  368. package/src/hooks/useGitBranch.ts +72 -0
  369. package/src/hooks/useInputHistory.ts +11 -0
  370. package/src/hooks/useQueue.ts +76 -0
  371. package/src/hooks/useVirtualHistory.ts +554 -0
  372. package/src/lib/circularBuffer.ts +48 -0
  373. package/src/lib/clipboard.ts +182 -0
  374. package/src/lib/editor.test.ts +74 -0
  375. package/src/lib/editor.ts +47 -0
  376. package/src/lib/emoji.ts +55 -0
  377. package/src/lib/externalCli.ts +16 -0
  378. package/src/lib/externalLink.ts +435 -0
  379. package/src/lib/forceTruecolor.ts +60 -0
  380. package/src/lib/fpsStore.ts +51 -0
  381. package/src/lib/fuzzy.test.ts +109 -0
  382. package/src/lib/fuzzy.ts +177 -0
  383. package/src/lib/gracefulExit.ts +47 -0
  384. package/src/lib/history.ts +82 -0
  385. package/src/lib/inputMetrics.ts +203 -0
  386. package/src/lib/liveProgress.test.ts +116 -0
  387. package/src/lib/liveProgress.ts +79 -0
  388. package/src/lib/mathUnicode.ts +770 -0
  389. package/src/lib/memory.test.ts +155 -0
  390. package/src/lib/memory.ts +188 -0
  391. package/src/lib/memoryMonitor.ts +109 -0
  392. package/src/lib/messages.test.ts +29 -0
  393. package/src/lib/messages.ts +8 -0
  394. package/src/lib/openExternalUrl.test.ts +217 -0
  395. package/src/lib/openExternalUrl.ts +158 -0
  396. package/src/lib/osc52.ts +73 -0
  397. package/src/lib/parentLog.ts +57 -0
  398. package/src/lib/perfPane.tsx +107 -0
  399. package/src/lib/platform.ts +409 -0
  400. package/src/lib/precisionWheel.ts +48 -0
  401. package/src/lib/prompt.ts +35 -0
  402. package/src/lib/reasoning.ts +55 -0
  403. package/src/lib/rpc.ts +41 -0
  404. package/src/lib/subagentTree.ts +355 -0
  405. package/src/lib/syntax.ts +117 -0
  406. package/src/lib/terminalModes.ts +51 -0
  407. package/src/lib/terminalParity.ts +78 -0
  408. package/src/lib/terminalSetup.ts +444 -0
  409. package/src/lib/termux.ts +29 -0
  410. package/src/lib/text.test.ts +18 -0
  411. package/src/lib/text.ts +339 -0
  412. package/src/lib/todo.test.ts +21 -0
  413. package/src/lib/todo.ts +9 -0
  414. package/src/lib/viewportStore.ts +124 -0
  415. package/src/lib/virtualHeights.ts +145 -0
  416. package/src/lib/wheelAccel.ts +190 -0
  417. package/src/protocol/interpolation.ts +3 -0
  418. package/src/protocol/paste.ts +1 -0
  419. package/src/theme.ts +589 -0
  420. package/src/types/nastech-ink.d.ts +176 -0
  421. package/src/types.ts +212 -0
  422. package/tsconfig.build.json +9 -0
  423. package/tsconfig.json +19 -0
  424. package/vitest.config.ts +7 -0
@@ -0,0 +1,635 @@
1
+ import { Box, Text, useInput, useStdout } from '@nastechai/ink'
2
+ import { useCallback, useEffect, useRef, useState } from 'react'
3
+
4
+ import { TUI_SESSION_MODEL_FLAG } from '../domain/slash.js'
5
+ import type { GatewayClient } from '../gatewayClient.js'
6
+ import type { SessionActiveItem, SessionActiveListResponse, SessionCloseResponse } from '../gatewayTypes.js'
7
+ import { asRpcResult, rpcErrorMessage } from '../lib/rpc.js'
8
+ import type { Theme } from '../theme.js'
9
+
10
+ import { ModelPicker } from './modelPicker.js'
11
+ import { windowOffset } from './overlayControls.js'
12
+ import { TextInput } from './textInput.js'
13
+
14
+ const VISIBLE = 12
15
+ const MIN_WIDTH = 64
16
+ const MAX_WIDTH = 128
17
+ const TITLE_MAX = 64
18
+
19
+ const STATUS_GLYPH: Record<string, string> = {
20
+ idle: '✓',
21
+ starting: '…',
22
+ waiting: '?',
23
+ working: '▶'
24
+ }
25
+
26
+ const STATUS_LABEL: Record<string, string> = {
27
+ idle: 'idle',
28
+ starting: 'starting',
29
+ waiting: 'waiting',
30
+ working: 'working'
31
+ }
32
+
33
+ const CTRL_OFFSET = 96
34
+
35
+ const shortModel = (model = '') => model.replace(/^.*\//, '') || 'model?'
36
+ const ctrlChar = (letter: string) => String.fromCharCode(letter.charCodeAt(0) - CTRL_OFFSET)
37
+
38
+ export const fixedSessionColumnStyle = () => ({ flexShrink: 0 })
39
+
40
+ export const activeSessionCountLabel = (count: number) =>
41
+ `${count} live ${count === 1 ? 'session' : 'sessions'}`
42
+
43
+ export type OrchestratorHintRole = 'hotkey' | 'label' | 'text'
44
+
45
+ export interface OrchestratorHintSegment {
46
+ role: OrchestratorHintRole
47
+ text: string
48
+ }
49
+
50
+ export const orchestratorContextHintSegments = (newSelected: boolean): OrchestratorHintSegment[] =>
51
+ newSelected
52
+ ? [
53
+ { role: 'label', text: 'New row:' },
54
+ { role: 'text', text: ' type prompt · ' },
55
+ { role: 'hotkey', text: 'Enter' },
56
+ { role: 'text', text: ' start · ' },
57
+ { role: 'hotkey', text: 'Tab' },
58
+ { role: 'text', text: ' model' }
59
+ ]
60
+ : [
61
+ { role: 'label', text: 'Session row:' },
62
+ { role: 'text', text: ' ' },
63
+ { role: 'hotkey', text: 'Enter' },
64
+ { role: 'text', text: ' switch · ' },
65
+ { role: 'hotkey', text: 'Ctrl+D' },
66
+ { role: 'text', text: ' close' }
67
+ ]
68
+
69
+ export const orchestratorGlobalHotkeyHintSegments: OrchestratorHintSegment[] = [
70
+ { role: 'hotkey', text: '↑↓' },
71
+ { role: 'text', text: ' move · ' },
72
+ { role: 'hotkey', text: 'Ctrl+N' },
73
+ { role: 'text', text: ' new · ' },
74
+ { role: 'hotkey', text: 'Ctrl+R' },
75
+ { role: 'text', text: ' refresh · ' },
76
+ { role: 'hotkey', text: 'Esc' },
77
+ { role: 'text', text: ' close' }
78
+ ]
79
+
80
+ const hintText = (segments: readonly OrchestratorHintSegment[]) => segments.map(segment => segment.text).join('')
81
+
82
+ export const orchestratorContextHint = (newSelected: boolean) => hintText(orchestratorContextHintSegments(newSelected))
83
+
84
+ export const orchestratorGlobalHotkeyHint = hintText(orchestratorGlobalHotkeyHintSegments)
85
+
86
+ export const orchestratorHintSegmentColor = (t: Theme, role: OrchestratorHintRole) => {
87
+ if (role === 'hotkey') {
88
+ return t.color.accent
89
+ }
90
+
91
+ if (role === 'label') {
92
+ return t.color.label
93
+ }
94
+
95
+ return t.color.muted
96
+ }
97
+
98
+ export const selectedSessionRowStyle = (t: Theme) => ({
99
+ backgroundColor: t.color.selectionBg,
100
+ color: t.color.text
101
+ })
102
+
103
+ export const newSessionMarkerColor = (t: Theme, selected: boolean) =>
104
+ selected ? selectedSessionRowStyle(t).color : t.color.label
105
+
106
+ export const newSessionRowIndex = (sessionCount: number) => Math.max(0, sessionCount)
107
+
108
+ export const isNewSessionRow = (index: number, sessionCount: number) => index >= newSessionRowIndex(sessionCount)
109
+
110
+ export const canTypeOrchestratorPrompt = (index: number, sessionCount: number) => isNewSessionRow(index, sessionCount)
111
+
112
+ export const clampOrchestratorSelection = (index: number, sessionCount: number) =>
113
+ Math.max(0, Math.min(index, newSessionRowIndex(sessionCount)))
114
+
115
+ export const currentSessionSelectionIndex = (
116
+ sessions: readonly SessionActiveItem[],
117
+ currentSessionId: null | string
118
+ ) => {
119
+ const index = sessions.findIndex(s => Boolean(s.current) || (!!currentSessionId && s.id === currentSessionId))
120
+
121
+ return index >= 0 ? index : 0
122
+ }
123
+
124
+ export const orchestratorVisibleRowIndexes = (sessionCount: number, selected: number, visible = VISIBLE) => {
125
+ const total = Math.max(0, sessionCount) + 1
126
+ const clamped = clampOrchestratorSelection(selected, sessionCount)
127
+ const offset = windowOffset(total, clamped, visible)
128
+ const count = Math.min(visible, total - offset)
129
+
130
+ return Array.from({ length: count }, (_, i) => offset + i)
131
+ }
132
+
133
+ export type CloseFallback = { action: 'activate'; sessionId: string } | { action: 'new' } | { action: 'stay' }
134
+
135
+ export const closeFallbackAfterClose = (
136
+ closedId: string,
137
+ currentSessionId: null | string,
138
+ remaining: readonly SessionActiveItem[]
139
+ ): CloseFallback => {
140
+ if (!currentSessionId || closedId !== currentSessionId) {
141
+ return { action: 'stay' }
142
+ }
143
+
144
+ const next = remaining.find(s => s.id !== closedId)
145
+
146
+ return next ? { action: 'activate', sessionId: next.id } : { action: 'new' }
147
+ }
148
+
149
+ export const draftModelArgFromPickerValue = (value: string) => {
150
+ const parts = value.trim().split(/\s+/).filter(Boolean)
151
+ const kept: string[] = []
152
+
153
+ for (const part of parts) {
154
+ if (part === TUI_SESSION_MODEL_FLAG || part === '--global') {
155
+ continue
156
+ }
157
+
158
+ kept.push(part)
159
+ }
160
+
161
+ return kept.join(' ')
162
+ }
163
+
164
+ export const draftModelNameFromArg = (value: string) => {
165
+ const parts = draftModelArgFromPickerValue(value).split(/\s+/).filter(Boolean)
166
+ const modelParts: string[] = []
167
+
168
+ for (let i = 0; i < parts.length; i++) {
169
+ const part = parts[i]!
170
+
171
+ if (part === '--provider') {
172
+ i++
173
+ continue
174
+ }
175
+
176
+ if (part.startsWith('--')) {
177
+ continue
178
+ }
179
+
180
+ modelParts.push(part)
181
+ }
182
+
183
+ return modelParts.join(' ').trim()
184
+ }
185
+
186
+ export const draftModelDisplayLabel = (value: string) => {
187
+ const modelName = draftModelNameFromArg(value)
188
+
189
+ return modelName ? shortModel(modelName) : 'current/default'
190
+ }
191
+
192
+ export type OrchestratorRowClickAction = { action: 'activate'; sessionId: string } | { action: 'select-new' }
193
+
194
+ export const orchestratorRowClickAction = (
195
+ index: number,
196
+ sessions: readonly SessionActiveItem[]
197
+ ): OrchestratorRowClickAction => {
198
+ const target = sessions[index]
199
+
200
+ return target && !isNewSessionRow(index, sessions.length)
201
+ ? { action: 'activate', sessionId: target.id }
202
+ : { action: 'select-new' }
203
+ }
204
+
205
+ export const draftTitleFromPrompt = (prompt: string, max = TITLE_MAX) => {
206
+ const compact = prompt.replace(/\s+/g, ' ').trim()
207
+
208
+ if (compact.length <= max) {
209
+ return compact
210
+ }
211
+
212
+ return `${compact.slice(0, Math.max(0, max - 1)).trimEnd()}…`
213
+ }
214
+
215
+ function OrchestratorHintSegments({ segments, t }: OrchestratorHintTextProps) {
216
+ return (
217
+ <>
218
+ {segments.map((segment, index) => (
219
+ <Text color={orchestratorHintSegmentColor(t, segment.role)} key={`${segment.role}-${index}`}>
220
+ {segment.text}
221
+ </Text>
222
+ ))}
223
+ </>
224
+ )
225
+ }
226
+
227
+ function OrchestratorHintText({ segments, t }: OrchestratorHintTextProps) {
228
+ return (
229
+ <Text color={orchestratorHintSegmentColor(t, 'text')} wrap="truncate-end">
230
+ <OrchestratorHintSegments segments={segments} t={t} />
231
+ </Text>
232
+ )
233
+ }
234
+
235
+ export function ActiveSessionSwitcher({
236
+ currentSessionId,
237
+ gw,
238
+ onCancel,
239
+ onClose,
240
+ onNew,
241
+ onNewPrompt,
242
+ onSelect,
243
+ t
244
+ }: ActiveSessionSwitcherProps) {
245
+ const [items, setItems] = useState<SessionActiveItem[]>([])
246
+ const [err, setErr] = useState('')
247
+ const [sel, setSel] = useState(0)
248
+ const [loading, setLoading] = useState(true)
249
+ const [draft, setDraft] = useState('')
250
+ const [draftModel, setDraftModel] = useState('')
251
+ const [pickingModel, setPickingModel] = useState(false)
252
+ const [closingId, setClosingId] = useState('')
253
+ const initialSelectionAppliedRef = useRef(false)
254
+ const { stdout } = useStdout()
255
+ const width = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, (stdout?.columns ?? 80) - 6))
256
+ const promptColumns = Math.max(20, width - 11)
257
+
258
+ const load = useCallback(
259
+ async (quiet = false) => {
260
+ if (!quiet) {
261
+ setLoading(true)
262
+ }
263
+
264
+ try {
265
+ const raw = await gw.request<SessionActiveListResponse>('session.active_list', {
266
+ current_session_id: currentSessionId
267
+ })
268
+ const r = asRpcResult<SessionActiveListResponse>(raw)
269
+
270
+ if (!r) {
271
+ setErr('invalid response: session.active_list')
272
+ setLoading(false)
273
+
274
+ return []
275
+ }
276
+
277
+ const next = r.sessions ?? []
278
+ const initializeSelection = !initialSelectionAppliedRef.current
279
+ initialSelectionAppliedRef.current = true
280
+ setItems(next)
281
+ setSel(s =>
282
+ initializeSelection
283
+ ? clampOrchestratorSelection(currentSessionSelectionIndex(next, currentSessionId), next.length)
284
+ : clampOrchestratorSelection(s, next.length)
285
+ )
286
+ setErr('')
287
+ setLoading(false)
288
+
289
+ return next
290
+ } catch (e: unknown) {
291
+ setErr(rpcErrorMessage(e))
292
+ setLoading(false)
293
+
294
+ return []
295
+ }
296
+ },
297
+ [currentSessionId, gw]
298
+ )
299
+
300
+ useEffect(() => {
301
+ void load()
302
+ const timer = setInterval(() => void load(true), 1500)
303
+
304
+ return () => clearInterval(timer)
305
+ }, [load])
306
+
307
+ const submitDraft = useCallback(
308
+ (value: string) => {
309
+ const prompt = value.trim()
310
+
311
+ if (!prompt) {
312
+ return
313
+ }
314
+
315
+ setDraft('')
316
+ onNewPrompt(prompt, draftModel || undefined)
317
+ },
318
+ [draftModel, onNewPrompt]
319
+ )
320
+
321
+ const closeSelected = useCallback(async () => {
322
+ const target = items[sel]
323
+
324
+ if (!target || isNewSessionRow(sel, items.length) || closingId) {
325
+ return
326
+ }
327
+
328
+ setErr('')
329
+ setClosingId(target.id)
330
+
331
+ try {
332
+ const result = await onClose(target.id)
333
+ const closed = Boolean(result?.closed ?? result?.ok)
334
+
335
+ if (!closed) {
336
+ setErr('session was already closed')
337
+
338
+ return
339
+ }
340
+
341
+ const remaining = await load(true)
342
+ const fallback = closeFallbackAfterClose(target.id, currentSessionId, remaining)
343
+
344
+ if (fallback.action === 'activate') {
345
+ onSelect(fallback.sessionId)
346
+ } else if (fallback.action === 'new') {
347
+ onNew()
348
+ } else {
349
+ setSel(s => clampOrchestratorSelection(s, remaining.length))
350
+ }
351
+ } catch (e: unknown) {
352
+ setErr(rpcErrorMessage(e))
353
+ } finally {
354
+ setClosingId('')
355
+ }
356
+ }, [closingId, currentSessionId, items, load, onClose, onNew, onSelect, sel])
357
+
358
+ const handleRowClick = useCallback(
359
+ (index: number) => (event: { stopImmediatePropagation?: () => void }) => {
360
+ event.stopImmediatePropagation?.()
361
+ const action = orchestratorRowClickAction(index, items)
362
+
363
+ if (action.action === 'activate') {
364
+ setSel(clampOrchestratorSelection(index, items.length))
365
+ onSelect(action.sessionId)
366
+
367
+ return
368
+ }
369
+
370
+ setSel(newSessionRowIndex(items.length))
371
+ },
372
+ [items, onSelect]
373
+ )
374
+
375
+ const newSelected = isNewSessionRow(sel, items.length)
376
+ const draftHasText = Boolean(draft.trim())
377
+
378
+ useInput((ch, key) => {
379
+ if (pickingModel) {
380
+ return
381
+ }
382
+
383
+ const lower = ch?.toLowerCase() ?? ''
384
+ const isCtrl = (letter: string) => key.ctrl && (lower === letter || ch === ctrlChar(letter))
385
+
386
+ if (key.escape) {
387
+ return onCancel()
388
+ }
389
+
390
+ if (isCtrl('n')) {
391
+ return onNew()
392
+ }
393
+
394
+ if (isCtrl('r')) {
395
+ void load()
396
+
397
+ return
398
+ }
399
+
400
+ if (key.tab) {
401
+ if (newSelected) {
402
+ setPickingModel(true)
403
+ }
404
+
405
+ return
406
+ }
407
+
408
+ if (isCtrl('d')) {
409
+ if (!newSelected) {
410
+ void closeSelected()
411
+ }
412
+
413
+ return
414
+ }
415
+
416
+ if (newSelected && draftHasText) {
417
+ return
418
+ }
419
+
420
+ if (key.upArrow && sel > 0) {
421
+ return setSel(s => clampOrchestratorSelection(s - 1, items.length))
422
+ }
423
+
424
+ if (key.downArrow && sel < newSessionRowIndex(items.length)) {
425
+ return setSel(s => clampOrchestratorSelection(s + 1, items.length))
426
+ }
427
+
428
+ if (key.return) {
429
+ if (newSelected) {
430
+ if (!draftHasText) {
431
+ return onNew()
432
+ }
433
+
434
+ return
435
+ }
436
+
437
+ if (items[sel]) {
438
+ return onSelect(items[sel]!.id)
439
+ }
440
+ }
441
+ })
442
+
443
+ if (pickingModel) {
444
+ return (
445
+ <ModelPicker
446
+ allowPersistGlobal={false}
447
+ gw={gw}
448
+ onCancel={() => setPickingModel(false)}
449
+ onSelect={value => {
450
+ setDraftModel(draftModelArgFromPickerValue(value))
451
+ setPickingModel(false)
452
+ }}
453
+ sessionId={currentSessionId}
454
+ t={t}
455
+ />
456
+ )
457
+ }
458
+
459
+ if (loading) {
460
+ return <Text color={t.color.muted}>loading session orchestrator…</Text>
461
+ }
462
+
463
+ const totalRows = items.length + 1
464
+ const offset = windowOffset(totalRows, sel, VISIBLE)
465
+ const visibleRows = orchestratorVisibleRowIndexes(items.length, sel, VISIBLE)
466
+
467
+ return (
468
+ <Box flexDirection="column" width={width}>
469
+ <Text bold color={t.color.accent}>
470
+ Session Orchestrator
471
+ </Text>
472
+ <Text color={t.color.muted}>{activeSessionCountLabel(items.length)}</Text>
473
+
474
+ {err && <Text color={t.color.label}>error: {err}</Text>}
475
+ {!items.length && (
476
+ <Text color={t.color.muted}>no live sessions — closed TUIs only leave resumable transcripts</Text>
477
+ )}
478
+ {offset > 0 && <Text color={t.color.muted}> ↑ {offset} more</Text>}
479
+
480
+ {visibleRows.map(i => {
481
+ const selected = sel === i
482
+ const selectedStyle = selected ? selectedSessionRowStyle(t) : null
483
+ const rowTextColor = selectedStyle?.color
484
+
485
+ if (isNewSessionRow(i, items.length)) {
486
+ const promptTitle = draftTitleFromPrompt(draft) || 'Start a new live session'
487
+ const markerColor = newSessionMarkerColor(t, selected)
488
+
489
+ return (
490
+ <Box
491
+ backgroundColor={selectedStyle?.backgroundColor}
492
+ flexDirection="row"
493
+ key="new-session"
494
+ onClick={handleRowClick(i)}
495
+ width="100%"
496
+ >
497
+ <Text bold={selected} color={rowTextColor ?? t.color.muted}>
498
+ {selected ? '▸ ' : ' '}
499
+ </Text>
500
+
501
+ <Box {...fixedSessionColumnStyle()} width={5}>
502
+ <Text bold={selected} color={markerColor}>
503
+ +
504
+ </Text>
505
+ </Box>
506
+
507
+ <Box {...fixedSessionColumnStyle()} width={11}>
508
+ <Text bold={selected} color={markerColor} wrap="truncate-end">
509
+ new
510
+ </Text>
511
+ </Box>
512
+
513
+ <Box {...fixedSessionColumnStyle()} width={11}>
514
+ <Text color={rowTextColor ?? t.color.muted} wrap="truncate-end">
515
+ ✎ draft
516
+ </Text>
517
+ </Box>
518
+
519
+ <Box {...fixedSessionColumnStyle()} width={18}>
520
+ <Text color={rowTextColor ?? t.color.muted} wrap="truncate-end">
521
+ {draftModelDisplayLabel(draftModel)}
522
+ </Text>
523
+ </Box>
524
+
525
+ <Box flexGrow={1} flexShrink={1} minWidth={0}>
526
+ <Text bold={selected} color={rowTextColor ?? t.color.muted} wrap="truncate-end">
527
+ {promptTitle}
528
+ </Text>
529
+ </Box>
530
+ </Box>
531
+ )
532
+ }
533
+
534
+ const s = items[i]!
535
+ const status = s.status ?? 'idle'
536
+ const current = s.current || s.id === currentSessionId
537
+ const title = closingId === s.id ? 'closing…' : s.title || s.preview || '(untitled)'
538
+
539
+ return (
540
+ <Box
541
+ backgroundColor={selectedStyle?.backgroundColor}
542
+ flexDirection="row"
543
+ key={s.id}
544
+ onClick={handleRowClick(i)}
545
+ width="100%"
546
+ >
547
+ <Text bold={selected} color={rowTextColor ?? t.color.muted}>
548
+ {selected ? '▸ ' : ' '}
549
+ </Text>
550
+
551
+ <Box {...fixedSessionColumnStyle()} width={5}>
552
+ <Text bold={selected} color={rowTextColor ?? t.color.muted}>
553
+ {String(i + 1).padStart(2)}.
554
+ </Text>
555
+ </Box>
556
+
557
+ <Box {...fixedSessionColumnStyle()} width={11}>
558
+ <Text
559
+ bold={selected}
560
+ color={rowTextColor ?? (current ? t.color.label : t.color.muted)}
561
+ wrap="truncate-end"
562
+ >
563
+ {current ? 'current' : s.id}
564
+ </Text>
565
+ </Box>
566
+
567
+ <Box {...fixedSessionColumnStyle()} width={11}>
568
+ <Text
569
+ color={
570
+ rowTextColor ??
571
+ (status === 'working' ? t.color.ok : status === 'waiting' ? t.color.label : t.color.muted)
572
+ }
573
+ wrap="truncate-end"
574
+ >
575
+ {STATUS_GLYPH[status] ?? '·'} {STATUS_LABEL[status] ?? status}
576
+ </Text>
577
+ </Box>
578
+
579
+ <Box {...fixedSessionColumnStyle()} width={18}>
580
+ <Text color={rowTextColor ?? t.color.muted} wrap="truncate-end">
581
+ {shortModel(s.model)}
582
+ </Text>
583
+ </Box>
584
+
585
+ <Box flexGrow={1} flexShrink={1} minWidth={0}>
586
+ <Text bold={selected} color={rowTextColor ?? t.color.muted} wrap="truncate-end">
587
+ {title}
588
+ </Text>
589
+ </Box>
590
+ </Box>
591
+ )
592
+ })}
593
+
594
+ {offset + VISIBLE < totalRows && <Text color={t.color.muted}> ↓ {totalRows - offset - VISIBLE} more</Text>}
595
+
596
+ {newSelected ? (
597
+ <>
598
+ <Box marginTop={1}>
599
+ <Text color={t.color.label}>prompt › </Text>
600
+ <TextInput columns={promptColumns} onChange={setDraft} onSubmit={submitDraft} value={draft} />
601
+ </Box>
602
+ <OrchestratorHintText segments={orchestratorContextHintSegments(true)} t={t} />
603
+ <Text color={t.color.muted} wrap="truncate-end">
604
+ model: {draftModelDisplayLabel(draftModel)}
605
+ </Text>
606
+ </>
607
+ ) : (
608
+ <Box marginTop={1} flexDirection="column">
609
+ <OrchestratorHintText segments={orchestratorContextHintSegments(false)} t={t} />
610
+ <Text color={t.color.muted} wrap="truncate-end">
611
+ Select <Text color={newSessionMarkerColor(t, false)}>+new</Text> to type a prompt
612
+ </Text>
613
+ </Box>
614
+ )}
615
+
616
+ <OrchestratorHintText segments={orchestratorGlobalHotkeyHintSegments} t={t} />
617
+ </Box>
618
+ )
619
+ }
620
+
621
+ interface OrchestratorHintTextProps {
622
+ segments: readonly OrchestratorHintSegment[]
623
+ t: Theme
624
+ }
625
+
626
+ interface ActiveSessionSwitcherProps {
627
+ currentSessionId: null | string
628
+ gw: GatewayClient
629
+ onCancel: () => void
630
+ onClose: (id: string) => Promise<null | SessionCloseResponse>
631
+ onNew: () => void
632
+ onNewPrompt: (prompt: string, modelArg?: string) => void
633
+ onSelect: (id: string) => void
634
+ t: Theme
635
+ }