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,155 @@
1
+ import { mkdtempSync, readdirSync, rmSync, statSync, utimesSync, writeFileSync } from 'node:fs'
2
+ import { tmpdir } from 'node:os'
3
+ import { join } from 'node:path'
4
+
5
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
6
+
7
+ import { performHeapDump } from './memory.js'
8
+
9
+ const ENV_KEYS = ['NASTECH_AUTO_HEAPDUMP', 'NASTECH_HEAPDUMP_DIR', 'NASTECH_HEAPDUMP_MAX_BYTES'] as const
10
+
11
+ describe('performHeapDump auto opt-in gate (#21767)', () => {
12
+ let saved: Record<string, string | undefined>
13
+ let dir: string
14
+
15
+ beforeEach(() => {
16
+ saved = {}
17
+
18
+ for (const k of ENV_KEYS) {
19
+ saved[k] = process.env[k]
20
+ delete process.env[k]
21
+ }
22
+
23
+ dir = mkdtempSync(join(tmpdir(), 'nastech-heapdump-test-'))
24
+ process.env.NASTECH_HEAPDUMP_DIR = dir
25
+ })
26
+
27
+ afterEach(() => {
28
+ for (const k of ENV_KEYS) {
29
+ if (saved[k] === undefined) {
30
+ delete process.env[k]
31
+ } else {
32
+ process.env[k] = saved[k]
33
+ }
34
+ }
35
+
36
+ rmSync(dir, { force: true, recursive: true })
37
+ })
38
+
39
+ it('writes diagnostics only for auto-high without NASTECH_AUTO_HEAPDUMP', async () => {
40
+ const result = await performHeapDump('auto-high')
41
+
42
+ expect(result.success).toBe(true)
43
+ expect(result.suppressed).toBe(true)
44
+ expect(result.diagPath).toBeDefined()
45
+ expect(result.heapPath).toBeUndefined()
46
+
47
+ const files = readdirSync(dir)
48
+ expect(files.some(f => f.endsWith('.diagnostics.json'))).toBe(true)
49
+ expect(files.some(f => f.endsWith('.heapsnapshot'))).toBe(false)
50
+ })
51
+
52
+ it('writes diagnostics only for auto-critical without NASTECH_AUTO_HEAPDUMP', async () => {
53
+ const result = await performHeapDump('auto-critical')
54
+
55
+ expect(result.success).toBe(true)
56
+ expect(result.suppressed).toBe(true)
57
+ expect(result.heapPath).toBeUndefined()
58
+
59
+ const files = readdirSync(dir)
60
+ expect(files.some(f => f.endsWith('.heapsnapshot'))).toBe(false)
61
+ })
62
+
63
+ it('writes both diagnostics and snapshot for auto-high when NASTECH_AUTO_HEAPDUMP=1', async () => {
64
+ process.env.NASTECH_AUTO_HEAPDUMP = '1'
65
+
66
+ const result = await performHeapDump('auto-high')
67
+
68
+ expect(result.success).toBe(true)
69
+ expect(result.suppressed).toBeUndefined()
70
+ expect(result.diagPath).toBeDefined()
71
+ expect(result.heapPath).toBeDefined()
72
+
73
+ const files = readdirSync(dir)
74
+ expect(files.some(f => f.endsWith('.heapsnapshot'))).toBe(true)
75
+ })
76
+
77
+ it('accepts truthy spellings (true|yes|on, case-insensitive) as opt-in', async () => {
78
+ for (const value of ['true', 'YES', 'On']) {
79
+ process.env.NASTECH_AUTO_HEAPDUMP = value
80
+ const result = await performHeapDump('auto-high')
81
+
82
+ expect(result.success).toBe(true)
83
+ expect(result.heapPath).toBeDefined()
84
+ }
85
+ })
86
+
87
+ it('treats other values (0, off, garbage) as opt-out for auto triggers', async () => {
88
+ for (const value of ['0', 'off', 'nope']) {
89
+ process.env.NASTECH_AUTO_HEAPDUMP = value
90
+ const result = await performHeapDump('auto-high')
91
+
92
+ expect(result.success).toBe(true)
93
+ expect(result.suppressed).toBe(true)
94
+ expect(result.heapPath).toBeUndefined()
95
+ }
96
+ })
97
+
98
+ it('writes both for manual triggers regardless of NASTECH_AUTO_HEAPDUMP', async () => {
99
+ const result = await performHeapDump('manual')
100
+
101
+ expect(result.success).toBe(true)
102
+ expect(result.suppressed).toBeUndefined()
103
+ expect(result.heapPath).toBeDefined()
104
+
105
+ const files = readdirSync(dir)
106
+ expect(files.some(f => f.endsWith('.heapsnapshot'))).toBe(true)
107
+ })
108
+ })
109
+
110
+ describe('heapdump retention guard (#21767)', () => {
111
+ let savedDir: string | undefined
112
+ let savedMax: string | undefined
113
+ let dir: string
114
+
115
+ beforeEach(() => {
116
+ savedDir = process.env.NASTECH_HEAPDUMP_DIR
117
+ savedMax = process.env.NASTECH_HEAPDUMP_MAX_BYTES
118
+ delete process.env.NASTECH_AUTO_HEAPDUMP
119
+ dir = mkdtempSync(join(tmpdir(), 'nastech-heapdump-prune-'))
120
+ process.env.NASTECH_HEAPDUMP_DIR = dir
121
+ })
122
+
123
+ afterEach(() => {
124
+ if (savedDir === undefined) {delete process.env.NASTECH_HEAPDUMP_DIR}
125
+ else {process.env.NASTECH_HEAPDUMP_DIR = savedDir}
126
+
127
+ if (savedMax === undefined) {delete process.env.NASTECH_HEAPDUMP_MAX_BYTES}
128
+ else {process.env.NASTECH_HEAPDUMP_MAX_BYTES = savedMax}
129
+
130
+ rmSync(dir, { force: true, recursive: true })
131
+ })
132
+
133
+ it('evicts oldest files when total bytes exceed the cap, retaining the newest', async () => {
134
+ const blob = 'x'.repeat(1024)
135
+ const now = Date.now()
136
+
137
+ for (let i = 0; i < 4; i++) {
138
+ const p = join(dir, `old-${i}.heapsnapshot`)
139
+ writeFileSync(p, blob)
140
+ const t = (now - (4 - i) * 60_000) / 1000
141
+ utimesSync(p, t, t)
142
+ }
143
+
144
+ process.env.NASTECH_HEAPDUMP_MAX_BYTES = String(2 * 1024)
145
+
146
+ const result = await performHeapDump('auto-high')
147
+ expect(result.success).toBe(true)
148
+
149
+ const remaining = readdirSync(dir)
150
+ const totalBytes = remaining.reduce((acc, f) => acc + statSync(join(dir, f)).size, 0)
151
+ expect(totalBytes <= 2 * 1024 || remaining.length === 1).toBe(true)
152
+ expect(remaining.length).toBeLessThan(5)
153
+ expect(remaining.some(f => f.endsWith('.diagnostics.json'))).toBe(true)
154
+ })
155
+ })
@@ -0,0 +1,188 @@
1
+ import { createWriteStream } from 'node:fs'
2
+ import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises'
3
+ import { homedir, tmpdir } from 'node:os'
4
+ import { join } from 'node:path'
5
+ import { pipeline } from 'node:stream/promises'
6
+ import { getHeapSnapshot, getHeapSpaceStatistics, getHeapStatistics } from 'node:v8'
7
+
8
+ export type MemoryTrigger = 'auto-critical' | 'auto-high' | 'manual'
9
+
10
+ export interface MemoryDiagnostics {
11
+ activeHandles: number
12
+ activeRequests: number
13
+ analysis: {
14
+ potentialLeaks: string[]
15
+ recommendation: string
16
+ }
17
+ memoryGrowthRate: {
18
+ bytesPerSecond: number
19
+ mbPerHour: number
20
+ }
21
+ memoryUsage: {
22
+ arrayBuffers: number
23
+ external: number
24
+ heapTotal: number
25
+ heapUsed: number
26
+ rss: number
27
+ }
28
+ nodeVersion: string
29
+ openFileDescriptors?: number
30
+ platform: string
31
+ resourceUsage: {
32
+ maxRSS: number
33
+ systemCPUTime: number
34
+ userCPUTime: number
35
+ }
36
+ smapsRollup?: string
37
+ timestamp: string
38
+ trigger: MemoryTrigger
39
+ uptimeSeconds: number
40
+ v8HeapSpaces?: { available: number; name: string; size: number; used: number }[]
41
+ v8HeapStats: {
42
+ detachedContexts: number
43
+ heapSizeLimit: number
44
+ mallocedMemory: number
45
+ nativeContexts: number
46
+ peakMallocedMemory: number
47
+ }
48
+ }
49
+
50
+ export interface HeapDumpResult {
51
+ diagPath?: string
52
+ error?: string
53
+ heapPath?: string
54
+ success: boolean
55
+ suppressed?: boolean
56
+ }
57
+
58
+ export async function captureMemoryDiagnostics(trigger: MemoryTrigger): Promise<MemoryDiagnostics> {
59
+ const usage = process.memoryUsage()
60
+ const heapStats = getHeapStatistics()
61
+ const resourceUsage = process.resourceUsage()
62
+ const uptimeSeconds = process.uptime()
63
+
64
+ // Not available on Bun / older Node.
65
+ let heapSpaces: ReturnType<typeof getHeapSpaceStatistics> | undefined
66
+
67
+ try {
68
+ heapSpaces = getHeapSpaceStatistics()
69
+ } catch {
70
+ /* noop */
71
+ }
72
+
73
+ const internals = process as unknown as {
74
+ _getActiveHandles: () => unknown[]
75
+ _getActiveRequests: () => unknown[]
76
+ }
77
+
78
+ const activeHandles = internals._getActiveHandles().length
79
+ const activeRequests = internals._getActiveRequests().length
80
+ const openFileDescriptors = await swallow(async () => (await readdir('/proc/self/fd')).length)
81
+ const smapsRollup = await swallow(() => readFile('/proc/self/smaps_rollup', 'utf8'))
82
+
83
+ const nativeMemory = usage.rss - usage.heapUsed
84
+ // Real growth rate since STARTED_AT (captured at module load) — NOT a lifetime
85
+ // average of rss/uptime, which would report phantom "growth" for a stable process.
86
+ const elapsed = Math.max(0, uptimeSeconds - STARTED_AT.uptime)
87
+ const bytesPerSecond = elapsed > 0 ? (usage.rss - STARTED_AT.rss) / elapsed : 0
88
+ const mbPerHour = (bytesPerSecond * 3600) / (1024 * 1024)
89
+
90
+ const potentialLeaks = [
91
+ heapStats.number_of_detached_contexts > 0 &&
92
+ `${heapStats.number_of_detached_contexts} detached context(s) — possible component/closure leak`,
93
+ activeHandles > 100 && `${activeHandles} active handles — possible timer/socket leak`,
94
+ nativeMemory > usage.heapUsed && 'Native memory > heap — leak may be in native addons',
95
+ mbPerHour > 100 && `High memory growth rate: ${mbPerHour.toFixed(1)} MB/hour`,
96
+ openFileDescriptors && openFileDescriptors > 500 && `${openFileDescriptors} open FDs — possible file/socket leak`
97
+ ].filter((s): s is string => typeof s === 'string')
98
+
99
+ return {
100
+ activeHandles,
101
+ activeRequests,
102
+ analysis: {
103
+ potentialLeaks,
104
+ recommendation: potentialLeaks.length
105
+ ? `WARNING: ${potentialLeaks.length} potential leak indicator(s). See potentialLeaks.`
106
+ : 'No obvious leak indicators. Inspect heap snapshot for retained objects.'
107
+ },
108
+ memoryGrowthRate: { bytesPerSecond, mbPerHour },
109
+ memoryUsage: {
110
+ arrayBuffers: usage.arrayBuffers,
111
+ external: usage.external,
112
+ heapTotal: usage.heapTotal,
113
+ heapUsed: usage.heapUsed,
114
+ rss: usage.rss
115
+ },
116
+ nodeVersion: process.version,
117
+ openFileDescriptors,
118
+ platform: process.platform,
119
+ resourceUsage: {
120
+ maxRSS: resourceUsage.maxRSS * 1024,
121
+ systemCPUTime: resourceUsage.systemCPUTime,
122
+ userCPUTime: resourceUsage.userCPUTime
123
+ },
124
+ smapsRollup,
125
+ timestamp: new Date().toISOString(),
126
+ trigger,
127
+ uptimeSeconds,
128
+ v8HeapSpaces: heapSpaces?.map(s => ({
129
+ available: s.space_available_size,
130
+ name: s.space_name,
131
+ size: s.space_size,
132
+ used: s.space_used_size
133
+ })),
134
+ v8HeapStats: {
135
+ detachedContexts: heapStats.number_of_detached_contexts,
136
+ heapSizeLimit: heapStats.heap_size_limit,
137
+ mallocedMemory: heapStats.malloced_memory,
138
+ nativeContexts: heapStats.number_of_native_contexts,
139
+ peakMallocedMemory: heapStats.peak_malloced_memory
140
+ }
141
+ }
142
+ }
143
+
144
+ export async function performHeapDump(trigger: MemoryTrigger = 'manual'): Promise<HeapDumpResult> {
145
+ try {
146
+ // Diagnostics first — heap-snapshot serialization can crash on very large
147
+ // heaps, and the JSON sidecar is the most actionable artifact if so.
148
+ const diagnostics = await captureMemoryDiagnostics(trigger)
149
+ const dir = process.env.NASTECH_HEAPDUMP_DIR?.trim() || join(homedir() || tmpdir(), '.nastech', 'heapdumps')
150
+
151
+ await mkdir(dir, { recursive: true })
152
+
153
+ const base = `nastech-${new Date().toISOString().replace(/[:.]/g, '-')}-${process.pid}-${trigger}`
154
+ const heapPath = join(dir, `${base}.heapsnapshot`)
155
+ const diagPath = join(dir, `${base}.diagnostics.json`)
156
+
157
+ await writeFile(diagPath, JSON.stringify(diagnostics, null, 2), { mode: 0o600 })
158
+ await pipeline(getHeapSnapshot(), createWriteStream(heapPath, { mode: 0o600 }))
159
+
160
+ return { diagPath, heapPath, success: true }
161
+ } catch (e) {
162
+ return { error: e instanceof Error ? e.message : String(e), success: false }
163
+ }
164
+ }
165
+
166
+ export function formatBytes(bytes: number): string {
167
+ if (!Number.isFinite(bytes) || bytes <= 0) {
168
+ return '0B'
169
+ }
170
+
171
+ const exp = Math.min(UNITS.length - 1, Math.floor(Math.log10(bytes) / 3))
172
+ const value = bytes / 1024 ** exp
173
+
174
+ return `${value >= 100 ? value.toFixed(0) : value.toFixed(1)}${UNITS[exp]}`
175
+ }
176
+
177
+ const UNITS = ['B', 'KB', 'MB', 'GB', 'TB']
178
+
179
+ const STARTED_AT = { rss: process.memoryUsage().rss, uptime: process.uptime() }
180
+
181
+ // Returns undefined when the probe isn't available (non-Linux paths, sandboxed FS).
182
+ const swallow = async <T>(fn: () => Promise<T>): Promise<T | undefined> => {
183
+ try {
184
+ return await fn()
185
+ } catch {
186
+ return undefined
187
+ }
188
+ }
@@ -0,0 +1,109 @@
1
+ import { type HeapDumpResult, performHeapDump } from './memory.js'
2
+
3
+ export type MemoryLevel = 'critical' | 'high' | 'normal'
4
+
5
+ export interface MemorySnapshot {
6
+ heapUsed: number
7
+ level: MemoryLevel
8
+ rss: number
9
+ }
10
+
11
+ export interface MemoryMonitorOptions {
12
+ criticalBytes?: number
13
+ highBytes?: number
14
+ intervalMs?: number
15
+ onCritical?: (snap: MemorySnapshot, dump: HeapDumpResult | null) => void
16
+ onHigh?: (snap: MemorySnapshot, dump: HeapDumpResult | null) => void
17
+ }
18
+
19
+ const GB = 1024 ** 3
20
+
21
+ // Deferred @nastech/ink import: loading `@nastechai/ink` at module top-level
22
+ // pulls the full ~414KB Ink bundle (React, renderer, components, hooks) onto
23
+ // the critical path before the Python gateway can even be spawned. That
24
+ // serialised roughly 150ms of Node work in front of gw.start() on every
25
+ // cold `nastech --tui` launch.
26
+ //
27
+ // evictInkCaches only runs inside `tick()`, which fires on a 10s timer and
28
+ // only when heap pressure crosses the high-water mark — by then Ink has
29
+ // long since been loaded by the app entry. This dynamic import is a no-op
30
+ // on the hot path (module is already in the ESM cache); when a startup
31
+ // spike somehow trips the threshold before the app registers its own Ink
32
+ // import, we pay the load cost exactly once, inside the tick that needs it.
33
+ let _evictInkCaches: ((level: 'all' | 'half') => unknown) | null = null
34
+ let _evictInkCachesPromise: Promise<(level: 'all' | 'half') => unknown> | null = null
35
+
36
+ async function _ensureEvictInkCaches(): Promise<(level: 'all' | 'half') => unknown> {
37
+ if (_evictInkCaches) {
38
+ return _evictInkCaches
39
+ }
40
+
41
+ _evictInkCachesPromise ??= import('@nastechai/ink')
42
+ .then(mod => {
43
+ _evictInkCaches = mod.evictInkCaches as (level: 'all' | 'half') => unknown
44
+
45
+ return _evictInkCaches
46
+ })
47
+ .catch(err => {
48
+ _evictInkCachesPromise = null
49
+ throw err
50
+ })
51
+
52
+ return _evictInkCachesPromise
53
+ }
54
+
55
+ export function startMemoryMonitor({
56
+ criticalBytes = 2.5 * GB,
57
+ highBytes = 1.5 * GB,
58
+ intervalMs = 10_000,
59
+ onCritical,
60
+ onHigh
61
+ }: MemoryMonitorOptions = {}): () => void {
62
+ const dumped = new Set<Exclude<MemoryLevel, 'normal'>>()
63
+ const inFlight = new Set<Exclude<MemoryLevel, 'normal'>>()
64
+
65
+ const tick = async () => {
66
+ const { heapUsed, rss } = process.memoryUsage()
67
+ const level: MemoryLevel = heapUsed >= criticalBytes ? 'critical' : heapUsed >= highBytes ? 'high' : 'normal'
68
+
69
+ if (level === 'normal') {
70
+ dumped.clear()
71
+ return
72
+ }
73
+
74
+ if (dumped.has(level) || inFlight.has(level)) {
75
+ return
76
+ }
77
+
78
+ inFlight.add(level)
79
+
80
+ // Prune Ink content caches before dump/exit — half on 'high' (recoverable),
81
+ // full on 'critical' (post-dump RSS reduction, keeps user running).
82
+ // Deferred import keeps `@nastechai/ink` off the cold-start critical path;
83
+ // by the time a tick fires 10s after launch the app has already loaded
84
+ // the same module, so this resolves instantly from the ESM cache.
85
+ try {
86
+ try {
87
+ const evictInkCaches = await _ensureEvictInkCaches()
88
+ evictInkCaches(level === 'critical' ? 'all' : 'half')
89
+ } catch {
90
+ // Best-effort: if the dynamic import fails for any reason we still
91
+ // continue to the heap dump below so the user gets diagnostics.
92
+ }
93
+
94
+ dumped.add(level)
95
+ const dump = await performHeapDump(level === 'critical' ? 'auto-critical' : 'auto-high').catch(() => null)
96
+ const snap: MemorySnapshot = { heapUsed, level, rss }
97
+
98
+ ;(level === 'critical' ? onCritical : onHigh)?.(snap, dump)
99
+ } finally {
100
+ inFlight.delete(level)
101
+ }
102
+ }
103
+
104
+ const handle = setInterval(() => void tick(), intervalMs)
105
+
106
+ handle.unref?.()
107
+
108
+ return () => clearInterval(handle)
109
+ }
@@ -0,0 +1,29 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { appendTranscriptMessage } from './messages.js'
4
+
5
+ describe('appendTranscriptMessage', () => {
6
+ it('merges adjacent tool-only shelves into one transcript row', () => {
7
+ const out = appendTranscriptMessage([{ kind: 'trail', role: 'system', text: '', tools: ['Terminal("one") ✓'] }], {
8
+ kind: 'trail',
9
+ role: 'system',
10
+ text: '',
11
+ tools: ['Terminal("two") ✓']
12
+ })
13
+
14
+ expect(out).toEqual([
15
+ { kind: 'trail', role: 'system', text: '', tools: ['Terminal("one") ✓', 'Terminal("two") ✓'] }
16
+ ])
17
+ })
18
+
19
+ it('merges tool shelves into the nearest thinking shelf', () => {
20
+ const out = appendTranscriptMessage(
21
+ [{ kind: 'trail', role: 'system', text: '', thinking: 'plan', tools: ['Terminal("one") ✓'] }],
22
+ { kind: 'trail', role: 'system', text: '', tools: ['Terminal("two") ✓'] }
23
+ )
24
+
25
+ expect(out).toEqual([
26
+ { kind: 'trail', role: 'system', text: '', thinking: 'plan', tools: ['Terminal("one") ✓', 'Terminal("two") ✓'] }
27
+ ])
28
+ })
29
+ })
@@ -0,0 +1,8 @@
1
+ import type { Msg, Role } from '../types.js'
2
+
3
+ import { appendToolShelfMessage } from './liveProgress.js'
4
+
5
+ export const appendTranscriptMessage = (prev: Msg[], msg: Msg): Msg[] => appendToolShelfMessage(prev, msg)
6
+
7
+ export const upsert = (prev: Msg[], role: Role, text: string): Msg[] =>
8
+ prev.at(-1)?.role === role ? [...prev.slice(0, -1), { role, text }] : [...prev, { role, text }]