@nexus-cortex/tui 4.47.0

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 (409) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +2 -0
  3. package/README.md +13 -0
  4. package/bin/cli-full.js +23 -0
  5. package/bin/cortex-ui.js +26 -0
  6. package/bin/ink-ui.js +108 -0
  7. package/bin/launcher-dev.js +266 -0
  8. package/bin/launcher.js +516 -0
  9. package/dist/commands/agent/InteractiveAgentMenu.d.ts +100 -0
  10. package/dist/commands/agent/InteractiveAgentMenu.d.ts.map +1 -0
  11. package/dist/commands/agent/InteractiveAgentMenu.js +909 -0
  12. package/dist/commands/agent/InteractiveAgentMenu.js.map +1 -0
  13. package/dist/commands/agent/index.d.ts +55 -0
  14. package/dist/commands/agent/index.d.ts.map +1 -0
  15. package/dist/commands/agent/index.js +714 -0
  16. package/dist/commands/agent/index.js.map +1 -0
  17. package/dist/commands/chat/interactive.d.ts +13 -0
  18. package/dist/commands/chat/interactive.d.ts.map +1 -0
  19. package/dist/commands/chat/interactive.js +2055 -0
  20. package/dist/commands/chat/interactive.js.map +1 -0
  21. package/dist/commands/system-message/EditorLauncher.d.ts +65 -0
  22. package/dist/commands/system-message/EditorLauncher.d.ts.map +1 -0
  23. package/dist/commands/system-message/EditorLauncher.js +143 -0
  24. package/dist/commands/system-message/EditorLauncher.js.map +1 -0
  25. package/dist/commands/system-message/InlineEditor.d.ts +23 -0
  26. package/dist/commands/system-message/InlineEditor.d.ts.map +1 -0
  27. package/dist/commands/system-message/InlineEditor.js +225 -0
  28. package/dist/commands/system-message/InlineEditor.js.map +1 -0
  29. package/dist/commands/system-message/InteractiveMenu.d.ts +76 -0
  30. package/dist/commands/system-message/InteractiveMenu.d.ts.map +1 -0
  31. package/dist/commands/system-message/InteractiveMenu.js +979 -0
  32. package/dist/commands/system-message/InteractiveMenu.js.map +1 -0
  33. package/dist/commands/system-message/index.d.ts +56 -0
  34. package/dist/commands/system-message/index.d.ts.map +1 -0
  35. package/dist/commands/system-message/index.js +694 -0
  36. package/dist/commands/system-message/index.js.map +1 -0
  37. package/dist/commands/ui/artifacts.d.ts +14 -0
  38. package/dist/commands/ui/artifacts.d.ts.map +1 -0
  39. package/dist/commands/ui/artifacts.js +22 -0
  40. package/dist/commands/ui/artifacts.js.map +1 -0
  41. package/dist/commands/ui/config.d.ts +12 -0
  42. package/dist/commands/ui/config.d.ts.map +1 -0
  43. package/dist/commands/ui/config.js +26 -0
  44. package/dist/commands/ui/config.js.map +1 -0
  45. package/dist/commands/ui/context.d.ts +16 -0
  46. package/dist/commands/ui/context.d.ts.map +1 -0
  47. package/dist/commands/ui/context.js +28 -0
  48. package/dist/commands/ui/context.js.map +1 -0
  49. package/dist/commands/ui/middleware.d.ts +14 -0
  50. package/dist/commands/ui/middleware.d.ts.map +1 -0
  51. package/dist/commands/ui/middleware.js +22 -0
  52. package/dist/commands/ui/middleware.js.map +1 -0
  53. package/dist/commands/ui/models.d.ts +14 -0
  54. package/dist/commands/ui/models.d.ts.map +1 -0
  55. package/dist/commands/ui/models.js +50 -0
  56. package/dist/commands/ui/models.js.map +1 -0
  57. package/dist/commands/ui/permissions.d.ts +14 -0
  58. package/dist/commands/ui/permissions.d.ts.map +1 -0
  59. package/dist/commands/ui/permissions.js +22 -0
  60. package/dist/commands/ui/permissions.js.map +1 -0
  61. package/dist/commands/ui/sessions.d.ts +13 -0
  62. package/dist/commands/ui/sessions.d.ts.map +1 -0
  63. package/dist/commands/ui/sessions.js +45 -0
  64. package/dist/commands/ui/sessions.js.map +1 -0
  65. package/dist/commands/ui/system-messages.d.ts +5 -0
  66. package/dist/commands/ui/system-messages.d.ts.map +1 -0
  67. package/dist/commands/ui/system-messages.js +19 -0
  68. package/dist/commands/ui/system-messages.js.map +1 -0
  69. package/dist/commands/ui/themes.d.ts +12 -0
  70. package/dist/commands/ui/themes.d.ts.map +1 -0
  71. package/dist/commands/ui/themes.js +40 -0
  72. package/dist/commands/ui/themes.js.map +1 -0
  73. package/dist/commands/ui/tmux.d.ts +5 -0
  74. package/dist/commands/ui/tmux.d.ts.map +1 -0
  75. package/dist/commands/ui/tmux.js +19 -0
  76. package/dist/commands/ui/tmux.js.map +1 -0
  77. package/dist/cortex.d.ts +12 -0
  78. package/dist/cortex.d.ts.map +1 -0
  79. package/dist/cortex.js +320 -0
  80. package/dist/cortex.js.map +1 -0
  81. package/dist/index.d.ts +3 -0
  82. package/dist/index.d.ts.map +1 -0
  83. package/dist/index.js +112 -0
  84. package/dist/index.js.map +1 -0
  85. package/dist/ink-ui/CortexApp.d.ts +28 -0
  86. package/dist/ink-ui/CortexApp.d.ts.map +1 -0
  87. package/dist/ink-ui/CortexApp.js +2291 -0
  88. package/dist/ink-ui/CortexApp.js.map +1 -0
  89. package/dist/ink-ui/SimpleInputBuffer.d.ts +63 -0
  90. package/dist/ink-ui/SimpleInputBuffer.d.ts.map +1 -0
  91. package/dist/ink-ui/SimpleInputBuffer.js +369 -0
  92. package/dist/ink-ui/SimpleInputBuffer.js.map +1 -0
  93. package/dist/ink-ui/commands/slashCommands.d.ts +40 -0
  94. package/dist/ink-ui/commands/slashCommands.d.ts.map +1 -0
  95. package/dist/ink-ui/commands/slashCommands.js +111 -0
  96. package/dist/ink-ui/commands/slashCommands.js.map +1 -0
  97. package/dist/ink-ui/commands/types.d.ts +161 -0
  98. package/dist/ink-ui/commands/types.d.ts.map +1 -0
  99. package/dist/ink-ui/commands/types.js +12 -0
  100. package/dist/ink-ui/commands/types.js.map +1 -0
  101. package/dist/ink-ui/components/ApprovalDialog.d.ts +21 -0
  102. package/dist/ink-ui/components/ApprovalDialog.d.ts.map +1 -0
  103. package/dist/ink-ui/components/ApprovalDialog.js +81 -0
  104. package/dist/ink-ui/components/ApprovalDialog.js.map +1 -0
  105. package/dist/ink-ui/components/CommandSuggestions.d.ts +19 -0
  106. package/dist/ink-ui/components/CommandSuggestions.d.ts.map +1 -0
  107. package/dist/ink-ui/components/CommandSuggestions.js +36 -0
  108. package/dist/ink-ui/components/CommandSuggestions.js.map +1 -0
  109. package/dist/ink-ui/components/DiffPreview.d.ts +60 -0
  110. package/dist/ink-ui/components/DiffPreview.d.ts.map +1 -0
  111. package/dist/ink-ui/components/DiffPreview.js +195 -0
  112. package/dist/ink-ui/components/DiffPreview.js.map +1 -0
  113. package/dist/ink-ui/components/DocumentPreview.d.ts +50 -0
  114. package/dist/ink-ui/components/DocumentPreview.d.ts.map +1 -0
  115. package/dist/ink-ui/components/DocumentPreview.js +92 -0
  116. package/dist/ink-ui/components/DocumentPreview.js.map +1 -0
  117. package/dist/ink-ui/components/MarkdownText.d.ts +27 -0
  118. package/dist/ink-ui/components/MarkdownText.d.ts.map +1 -0
  119. package/dist/ink-ui/components/MarkdownText.js +342 -0
  120. package/dist/ink-ui/components/MarkdownText.js.map +1 -0
  121. package/dist/ink-ui/components/MenuRenderer.d.ts +26 -0
  122. package/dist/ink-ui/components/MenuRenderer.d.ts.map +1 -0
  123. package/dist/ink-ui/components/MenuRenderer.js +264 -0
  124. package/dist/ink-ui/components/MenuRenderer.js.map +1 -0
  125. package/dist/ink-ui/components/ModelPickerDialog.d.ts +33 -0
  126. package/dist/ink-ui/components/ModelPickerDialog.d.ts.map +1 -0
  127. package/dist/ink-ui/components/ModelPickerDialog.js +98 -0
  128. package/dist/ink-ui/components/ModelPickerDialog.js.map +1 -0
  129. package/dist/ink-ui/components/StreamDisplay.d.ts +31 -0
  130. package/dist/ink-ui/components/StreamDisplay.d.ts.map +1 -0
  131. package/dist/ink-ui/components/StreamDisplay.js +573 -0
  132. package/dist/ink-ui/components/StreamDisplay.js.map +1 -0
  133. package/dist/ink-ui/components/SubAgentPanel.d.ts +98 -0
  134. package/dist/ink-ui/components/SubAgentPanel.d.ts.map +1 -0
  135. package/dist/ink-ui/components/SubAgentPanel.js +155 -0
  136. package/dist/ink-ui/components/SubAgentPanel.js.map +1 -0
  137. package/dist/ink-ui/components/ThinkingDisplay.d.ts +46 -0
  138. package/dist/ink-ui/components/ThinkingDisplay.d.ts.map +1 -0
  139. package/dist/ink-ui/components/ThinkingDisplay.js +176 -0
  140. package/dist/ink-ui/components/ThinkingDisplay.js.map +1 -0
  141. package/dist/ink-ui/components/ToolCallDisplay.d.ts +51 -0
  142. package/dist/ink-ui/components/ToolCallDisplay.d.ts.map +1 -0
  143. package/dist/ink-ui/components/ToolCallDisplay.js +378 -0
  144. package/dist/ink-ui/components/ToolCallDisplay.js.map +1 -0
  145. package/dist/ink-ui/components/WritePreview.d.ts +42 -0
  146. package/dist/ink-ui/components/WritePreview.d.ts.map +1 -0
  147. package/dist/ink-ui/components/WritePreview.js +100 -0
  148. package/dist/ink-ui/components/WritePreview.js.map +1 -0
  149. package/dist/ink-ui/contexts/KeypressContext.d.ts +32 -0
  150. package/dist/ink-ui/contexts/KeypressContext.d.ts.map +1 -0
  151. package/dist/ink-ui/contexts/KeypressContext.js +529 -0
  152. package/dist/ink-ui/contexts/KeypressContext.js.map +1 -0
  153. package/dist/ink-ui/contexts/SessionContext.d.ts +46 -0
  154. package/dist/ink-ui/contexts/SessionContext.d.ts.map +1 -0
  155. package/dist/ink-ui/contexts/SessionContext.js +162 -0
  156. package/dist/ink-ui/contexts/SessionContext.js.map +1 -0
  157. package/dist/ink-ui/core-stubs.d.ts +270 -0
  158. package/dist/ink-ui/core-stubs.d.ts.map +1 -0
  159. package/dist/ink-ui/core-stubs.js +276 -0
  160. package/dist/ink-ui/core-stubs.js.map +1 -0
  161. package/dist/ink-ui/cortex-types.d.ts +214 -0
  162. package/dist/ink-ui/cortex-types.d.ts.map +1 -0
  163. package/dist/ink-ui/cortex-types.js +52 -0
  164. package/dist/ink-ui/cortex-types.js.map +1 -0
  165. package/dist/ink-ui/hooks/useCortexStream.d.ts +75 -0
  166. package/dist/ink-ui/hooks/useCortexStream.d.ts.map +1 -0
  167. package/dist/ink-ui/hooks/useCortexStream.js +518 -0
  168. package/dist/ink-ui/hooks/useCortexStream.js.map +1 -0
  169. package/dist/ink-ui/hooks/useFocus.d.ts +11 -0
  170. package/dist/ink-ui/hooks/useFocus.d.ts.map +1 -0
  171. package/dist/ink-ui/hooks/useFocus.js +51 -0
  172. package/dist/ink-ui/hooks/useFocus.js.map +1 -0
  173. package/dist/ink-ui/hooks/useHistoryManager.d.ts +23 -0
  174. package/dist/ink-ui/hooks/useHistoryManager.d.ts.map +1 -0
  175. package/dist/ink-ui/hooks/useHistoryManager.js +63 -0
  176. package/dist/ink-ui/hooks/useHistoryManager.js.map +1 -0
  177. package/dist/ink-ui/hooks/useKeypress.d.ts +18 -0
  178. package/dist/ink-ui/hooks/useKeypress.d.ts.map +1 -0
  179. package/dist/ink-ui/hooks/useKeypress.js +27 -0
  180. package/dist/ink-ui/hooks/useKeypress.js.map +1 -0
  181. package/dist/ink-ui/hooks/useReactApprovalHandler.d.ts +74 -0
  182. package/dist/ink-ui/hooks/useReactApprovalHandler.d.ts.map +1 -0
  183. package/dist/ink-ui/hooks/useReactApprovalHandler.js +83 -0
  184. package/dist/ink-ui/hooks/useReactApprovalHandler.js.map +1 -0
  185. package/dist/ink-ui/hooks/useTerminalSize.d.ts +10 -0
  186. package/dist/ink-ui/hooks/useTerminalSize.d.ts.map +1 -0
  187. package/dist/ink-ui/hooks/useTerminalSize.js +28 -0
  188. package/dist/ink-ui/hooks/useTerminalSize.js.map +1 -0
  189. package/dist/ink-ui/semantic-colors.d.ts +8 -0
  190. package/dist/ink-ui/semantic-colors.d.ts.map +1 -0
  191. package/dist/ink-ui/semantic-colors.js +24 -0
  192. package/dist/ink-ui/semantic-colors.js.map +1 -0
  193. package/dist/ink-ui/state/extensions.d.ts +68 -0
  194. package/dist/ink-ui/state/extensions.d.ts.map +1 -0
  195. package/dist/ink-ui/state/extensions.js +97 -0
  196. package/dist/ink-ui/state/extensions.js.map +1 -0
  197. package/dist/ink-ui/themes/ansi-light.d.ts +8 -0
  198. package/dist/ink-ui/themes/ansi-light.d.ts.map +1 -0
  199. package/dist/ink-ui/themes/ansi-light.js +143 -0
  200. package/dist/ink-ui/themes/ansi-light.js.map +1 -0
  201. package/dist/ink-ui/themes/ansi.d.ts +8 -0
  202. package/dist/ink-ui/themes/ansi.d.ts.map +1 -0
  203. package/dist/ink-ui/themes/ansi.js +153 -0
  204. package/dist/ink-ui/themes/ansi.js.map +1 -0
  205. package/dist/ink-ui/themes/atom-one-dark.d.ts +8 -0
  206. package/dist/ink-ui/themes/atom-one-dark.d.ts.map +1 -0
  207. package/dist/ink-ui/themes/atom-one-dark.js +140 -0
  208. package/dist/ink-ui/themes/atom-one-dark.js.map +1 -0
  209. package/dist/ink-ui/themes/ayu-light.d.ts +8 -0
  210. package/dist/ink-ui/themes/ayu-light.d.ts.map +1 -0
  211. package/dist/ink-ui/themes/ayu-light.js +132 -0
  212. package/dist/ink-ui/themes/ayu-light.js.map +1 -0
  213. package/dist/ink-ui/themes/ayu.d.ts +8 -0
  214. package/dist/ink-ui/themes/ayu.d.ts.map +1 -0
  215. package/dist/ink-ui/themes/ayu.js +106 -0
  216. package/dist/ink-ui/themes/ayu.js.map +1 -0
  217. package/dist/ink-ui/themes/color-utils.d.ts +23 -0
  218. package/dist/ink-ui/themes/color-utils.d.ts.map +1 -0
  219. package/dist/ink-ui/themes/color-utils.js +237 -0
  220. package/dist/ink-ui/themes/color-utils.js.map +1 -0
  221. package/dist/ink-ui/themes/default-light.d.ts +8 -0
  222. package/dist/ink-ui/themes/default-light.d.ts.map +1 -0
  223. package/dist/ink-ui/themes/default-light.js +100 -0
  224. package/dist/ink-ui/themes/default-light.js.map +1 -0
  225. package/dist/ink-ui/themes/default.d.ts +8 -0
  226. package/dist/ink-ui/themes/default.d.ts.map +1 -0
  227. package/dist/ink-ui/themes/default.js +143 -0
  228. package/dist/ink-ui/themes/default.js.map +1 -0
  229. package/dist/ink-ui/themes/dracula.d.ts +8 -0
  230. package/dist/ink-ui/themes/dracula.d.ts.map +1 -0
  231. package/dist/ink-ui/themes/dracula.js +117 -0
  232. package/dist/ink-ui/themes/dracula.js.map +1 -0
  233. package/dist/ink-ui/themes/github-dark.d.ts +8 -0
  234. package/dist/ink-ui/themes/github-dark.d.ts.map +1 -0
  235. package/dist/ink-ui/themes/github-dark.js +140 -0
  236. package/dist/ink-ui/themes/github-dark.js.map +1 -0
  237. package/dist/ink-ui/themes/github-light.d.ts +8 -0
  238. package/dist/ink-ui/themes/github-light.d.ts.map +1 -0
  239. package/dist/ink-ui/themes/github-light.js +142 -0
  240. package/dist/ink-ui/themes/github-light.js.map +1 -0
  241. package/dist/ink-ui/themes/googlecode.d.ts +8 -0
  242. package/dist/ink-ui/themes/googlecode.d.ts.map +1 -0
  243. package/dist/ink-ui/themes/googlecode.js +139 -0
  244. package/dist/ink-ui/themes/googlecode.js.map +1 -0
  245. package/dist/ink-ui/themes/no-color.d.ts +8 -0
  246. package/dist/ink-ui/themes/no-color.d.ts.map +1 -0
  247. package/dist/ink-ui/themes/no-color.js +118 -0
  248. package/dist/ink-ui/themes/no-color.js.map +1 -0
  249. package/dist/ink-ui/themes/semantic-tokens.d.ts +40 -0
  250. package/dist/ink-ui/themes/semantic-tokens.d.ts.map +1 -0
  251. package/dist/ink-ui/themes/semantic-tokens.js +100 -0
  252. package/dist/ink-ui/themes/semantic-tokens.js.map +1 -0
  253. package/dist/ink-ui/themes/shades-of-purple.d.ts +12 -0
  254. package/dist/ink-ui/themes/shades-of-purple.d.ts.map +1 -0
  255. package/dist/ink-ui/themes/shades-of-purple.js +306 -0
  256. package/dist/ink-ui/themes/shades-of-purple.js.map +1 -0
  257. package/dist/ink-ui/themes/theme-manager.d.ts +67 -0
  258. package/dist/ink-ui/themes/theme-manager.d.ts.map +1 -0
  259. package/dist/ink-ui/themes/theme-manager.js +263 -0
  260. package/dist/ink-ui/themes/theme-manager.js.map +1 -0
  261. package/dist/ink-ui/themes/theme.d.ts +134 -0
  262. package/dist/ink-ui/themes/theme.d.ts.map +1 -0
  263. package/dist/ink-ui/themes/theme.js +390 -0
  264. package/dist/ink-ui/themes/theme.js.map +1 -0
  265. package/dist/ink-ui/themes/xcode.d.ts +8 -0
  266. package/dist/ink-ui/themes/xcode.d.ts.map +1 -0
  267. package/dist/ink-ui/themes/xcode.js +147 -0
  268. package/dist/ink-ui/themes/xcode.js.map +1 -0
  269. package/dist/ink-ui/types.d.ts +306 -0
  270. package/dist/ink-ui/types.d.ts.map +1 -0
  271. package/dist/ink-ui/types.js +65 -0
  272. package/dist/ink-ui/types.js.map +1 -0
  273. package/dist/ink-ui/utils/input.d.ts +18 -0
  274. package/dist/ink-ui/utils/input.d.ts.map +1 -0
  275. package/dist/ink-ui/utils/input.js +51 -0
  276. package/dist/ink-ui/utils/input.js.map +1 -0
  277. package/dist/ink-ui/utils/mouse.d.ts +33 -0
  278. package/dist/ink-ui/utils/mouse.d.ts.map +1 -0
  279. package/dist/ink-ui/utils/mouse.js +182 -0
  280. package/dist/ink-ui/utils/mouse.js.map +1 -0
  281. package/dist/services/BuiltinCommandLoader.d.ts +21 -0
  282. package/dist/services/BuiltinCommandLoader.d.ts.map +1 -0
  283. package/dist/services/BuiltinCommandLoader.js +182 -0
  284. package/dist/services/BuiltinCommandLoader.js.map +1 -0
  285. package/dist/services/CommandService.d.ts +39 -0
  286. package/dist/services/CommandService.d.ts.map +1 -0
  287. package/dist/services/CommandService.js +49 -0
  288. package/dist/services/CommandService.js.map +1 -0
  289. package/dist/services/FileCommandLoader.d.ts +16 -0
  290. package/dist/services/FileCommandLoader.d.ts.map +1 -0
  291. package/dist/services/FileCommandLoader.js +19 -0
  292. package/dist/services/FileCommandLoader.js.map +1 -0
  293. package/dist/services/McpPromptLoader.d.ts +16 -0
  294. package/dist/services/McpPromptLoader.d.ts.map +1 -0
  295. package/dist/services/McpPromptLoader.js +19 -0
  296. package/dist/services/McpPromptLoader.js.map +1 -0
  297. package/dist/services/index.d.ts +8 -0
  298. package/dist/services/index.d.ts.map +1 -0
  299. package/dist/services/index.js +8 -0
  300. package/dist/services/index.js.map +1 -0
  301. package/dist/ui/ChalkMenuRenderer.d.ts +22 -0
  302. package/dist/ui/ChalkMenuRenderer.d.ts.map +1 -0
  303. package/dist/ui/ChalkMenuRenderer.js +478 -0
  304. package/dist/ui/ChalkMenuRenderer.js.map +1 -0
  305. package/dist/ui/ChalkModelPicker.d.ts +20 -0
  306. package/dist/ui/ChalkModelPicker.d.ts.map +1 -0
  307. package/dist/ui/ChalkModelPicker.js +363 -0
  308. package/dist/ui/ChalkModelPicker.js.map +1 -0
  309. package/dist/ui/ChalkThemePicker.d.ts +20 -0
  310. package/dist/ui/ChalkThemePicker.d.ts.map +1 -0
  311. package/dist/ui/ChalkThemePicker.js +370 -0
  312. package/dist/ui/ChalkThemePicker.js.map +1 -0
  313. package/dist/ui/InkApprovalDialog.d.ts +8 -0
  314. package/dist/ui/InkApprovalDialog.d.ts.map +1 -0
  315. package/dist/ui/InkApprovalDialog.js +25 -0
  316. package/dist/ui/InkApprovalDialog.js.map +1 -0
  317. package/dist/ui/InkCommandPalette.d.ts +43 -0
  318. package/dist/ui/InkCommandPalette.d.ts.map +1 -0
  319. package/dist/ui/InkCommandPalette.js +105 -0
  320. package/dist/ui/InkCommandPalette.js.map +1 -0
  321. package/dist/ui/InkConfigMenu.d.ts +24 -0
  322. package/dist/ui/InkConfigMenu.d.ts.map +1 -0
  323. package/dist/ui/InkConfigMenu.js +259 -0
  324. package/dist/ui/InkConfigMenu.js.map +1 -0
  325. package/dist/ui/InkHelp.d.ts +25 -0
  326. package/dist/ui/InkHelp.d.ts.map +1 -0
  327. package/dist/ui/InkHelp.js +55 -0
  328. package/dist/ui/InkHelp.js.map +1 -0
  329. package/dist/ui/InkMenuPicker.d.ts +19 -0
  330. package/dist/ui/InkMenuPicker.d.ts.map +1 -0
  331. package/dist/ui/InkMenuPicker.js +33 -0
  332. package/dist/ui/InkMenuPicker.js.map +1 -0
  333. package/dist/ui/InkModelPicker.d.ts +23 -0
  334. package/dist/ui/InkModelPicker.d.ts.map +1 -0
  335. package/dist/ui/InkModelPicker.js +53 -0
  336. package/dist/ui/InkModelPicker.js.map +1 -0
  337. package/dist/ui/PersistentInput.d.ts +91 -0
  338. package/dist/ui/PersistentInput.d.ts.map +1 -0
  339. package/dist/ui/PersistentInput.js +283 -0
  340. package/dist/ui/PersistentInput.js.map +1 -0
  341. package/dist/ui/RawInput.d.ts +27 -0
  342. package/dist/ui/RawInput.d.ts.map +1 -0
  343. package/dist/ui/RawInput.js +731 -0
  344. package/dist/ui/RawInput.js.map +1 -0
  345. package/dist/ui/SlashCommandAutocomplete.d.ts +94 -0
  346. package/dist/ui/SlashCommandAutocomplete.d.ts.map +1 -0
  347. package/dist/ui/SlashCommandAutocomplete.js +274 -0
  348. package/dist/ui/SlashCommandAutocomplete.js.map +1 -0
  349. package/dist/ui/SplashScreen.d.ts +76 -0
  350. package/dist/ui/SplashScreen.d.ts.map +1 -0
  351. package/dist/ui/SplashScreen.js +316 -0
  352. package/dist/ui/SplashScreen.js.map +1 -0
  353. package/dist/ui/components/ArtifactDashboard.d.ts +16 -0
  354. package/dist/ui/components/ArtifactDashboard.d.ts.map +1 -0
  355. package/dist/ui/components/ArtifactDashboard.js +102 -0
  356. package/dist/ui/components/ArtifactDashboard.js.map +1 -0
  357. package/dist/ui/components/ChatInput.d.ts +36 -0
  358. package/dist/ui/components/ChatInput.d.ts.map +1 -0
  359. package/dist/ui/components/ChatInput.js +11 -0
  360. package/dist/ui/components/ChatInput.js.map +1 -0
  361. package/dist/ui/components/ConfigWizard.d.ts +17 -0
  362. package/dist/ui/components/ConfigWizard.d.ts.map +1 -0
  363. package/dist/ui/components/ConfigWizard.js +134 -0
  364. package/dist/ui/components/ConfigWizard.js.map +1 -0
  365. package/dist/ui/components/ContextViewer.d.ts +18 -0
  366. package/dist/ui/components/ContextViewer.d.ts.map +1 -0
  367. package/dist/ui/components/ContextViewer.js +85 -0
  368. package/dist/ui/components/ContextViewer.js.map +1 -0
  369. package/dist/ui/components/MiddlewareDashboard.d.ts +16 -0
  370. package/dist/ui/components/MiddlewareDashboard.d.ts.map +1 -0
  371. package/dist/ui/components/MiddlewareDashboard.js +98 -0
  372. package/dist/ui/components/MiddlewareDashboard.js.map +1 -0
  373. package/dist/ui/components/ModelPicker.d.ts +24 -0
  374. package/dist/ui/components/ModelPicker.d.ts.map +1 -0
  375. package/dist/ui/components/ModelPicker.js +96 -0
  376. package/dist/ui/components/ModelPicker.js.map +1 -0
  377. package/dist/ui/components/MultiLineInput.d.ts +27 -0
  378. package/dist/ui/components/MultiLineInput.d.ts.map +1 -0
  379. package/dist/ui/components/MultiLineInput.js +182 -0
  380. package/dist/ui/components/MultiLineInput.js.map +1 -0
  381. package/dist/ui/components/PermissionsBrowser.d.ts +16 -0
  382. package/dist/ui/components/PermissionsBrowser.d.ts.map +1 -0
  383. package/dist/ui/components/PermissionsBrowser.js +110 -0
  384. package/dist/ui/components/PermissionsBrowser.js.map +1 -0
  385. package/dist/ui/components/SessionBrowser.d.ts +19 -0
  386. package/dist/ui/components/SessionBrowser.d.ts.map +1 -0
  387. package/dist/ui/components/SessionBrowser.js +66 -0
  388. package/dist/ui/components/SessionBrowser.js.map +1 -0
  389. package/dist/ui/components/StatusBar.d.ts +32 -0
  390. package/dist/ui/components/StatusBar.d.ts.map +1 -0
  391. package/dist/ui/components/StatusBar.js +27 -0
  392. package/dist/ui/components/StatusBar.js.map +1 -0
  393. package/dist/ui/components/SystemMessageBrowser.d.ts +11 -0
  394. package/dist/ui/components/SystemMessageBrowser.d.ts.map +1 -0
  395. package/dist/ui/components/SystemMessageBrowser.js +106 -0
  396. package/dist/ui/components/SystemMessageBrowser.js.map +1 -0
  397. package/dist/ui/components/ThemePicker.d.ts +11 -0
  398. package/dist/ui/components/ThemePicker.d.ts.map +1 -0
  399. package/dist/ui/components/ThemePicker.js +62 -0
  400. package/dist/ui/components/ThemePicker.js.map +1 -0
  401. package/dist/ui/components/TmuxBrowser.d.ts +11 -0
  402. package/dist/ui/components/TmuxBrowser.d.ts.map +1 -0
  403. package/dist/ui/components/TmuxBrowser.js +85 -0
  404. package/dist/ui/components/TmuxBrowser.js.map +1 -0
  405. package/dist/ui/components/index.d.ts +22 -0
  406. package/dist/ui/components/index.d.ts.map +1 -0
  407. package/dist/ui/components/index.js +21 -0
  408. package/dist/ui/components/index.js.map +1 -0
  409. package/package.json +70 -0
@@ -0,0 +1,2291 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * Nexus Cortex - Main Application Component
4
+ *
5
+ * This is the entry point for the Ink-based UI, adapted from Gemini CLI
6
+ * but wired to our @nexus-cortex/core orchestrator.
7
+ *
8
+ * Architecture:
9
+ * - CortexApp (this file) - Application shell and state management
10
+ * - useCortexStream - Bridge hook to orchestrator
11
+ * - Gemini UI components - Rendering (DiffRenderer, InputPrompt, etc.)
12
+ */
13
+ import React, { useState, useCallback, useEffect, useMemo, useRef, } from 'react';
14
+ import fs, { realpathSync } from 'fs';
15
+ import path from 'path';
16
+ import { fileURLToPath } from 'url';
17
+ import { Box, Text, Static, useApp, useInput, useStdout, useStdin, } from 'ink';
18
+ import { createInputBuffer } from './SimpleInputBuffer.js';
19
+ import { OrchestratorClient } from '@nexus-cortex/cli/dist/orchestrator/OrchestratorClient.js';
20
+ import { useCortexStream } from './hooks/useCortexStream.js';
21
+ import { useTerminalSize } from './hooks/useTerminalSize.js';
22
+ // useHistory hook removed - using orchestrator history directly
23
+ // import { useHistory } from './hooks/useHistoryManager.js';
24
+ import { useReactApprovalHandler } from './hooks/useReactApprovalHandler.js';
25
+ import { StreamingState, MessageType } from './cortex-types.js';
26
+ import { Colors, setTheme, getThemeNames, getThemeDefinition, initializeTheme, persistModel, loadPersistedModel } from '@nexus-cortex/cli/dist/themes/colors.js';
27
+ import { ApprovalDialog } from './components/ApprovalDialog.js';
28
+ import { InlineThinkingDisplay } from './components/ThinkingDisplay.js';
29
+ import { StreamDisplay } from './components/StreamDisplay.js';
30
+ import { UnifiedDiffDisplay } from './components/DiffPreview.js';
31
+ import { WritePreview } from './components/WritePreview.js';
32
+ import { DocumentPreview } from './components/DocumentPreview.js';
33
+ import { MarkdownText } from './components/MarkdownText.js';
34
+ import { CommandSuggestions } from './components/CommandSuggestions.js';
35
+ import { getSuggestions } from './commands/slashCommands.js';
36
+ import { InteractiveMenu } from '../commands/system-message/InteractiveMenu.js';
37
+ import { SystemMessageStore } from '@nexus-cortex/core/system-messages/SystemMessageStore.js';
38
+ import { MentorshipConfigService, slashCommandRegistry } from '@nexus-cortex/core';
39
+ import { ModelPickerDialog } from './components/ModelPickerDialog.js';
40
+ import { MenuRenderer } from './components/MenuRenderer.js';
41
+ import { SubAgentPanel, createSubAgentStateManager } from './components/SubAgentPanel.js';
42
+ import { ConfigMenu } from '../ui/InkConfigMenu.js';
43
+ // Get installation root from this file's location
44
+ // Use realpathSync to resolve symlinks (important for npm link)
45
+ // Compiled file is at: packages/cli/dist/ink-ui/CortexApp.js
46
+ // Installation root is 4 levels up: ink-ui -> dist -> cli -> packages -> root
47
+ const __filename = realpathSync(fileURLToPath(import.meta.url));
48
+ const __dirname = path.dirname(__filename);
49
+ const CLI_INSTALLATION_ROOT = path.join(__dirname, '..', '..', '..', '..');
50
+ /**
51
+ * Compact header - CC-style 3-line display: name + model + cwd
52
+ */
53
+ const CompactHeader = ({ model, cwd }) => {
54
+ const home = process.env.HOME || '';
55
+ const displayCwd = home && cwd.startsWith(home) ? '~' + cwd.slice(home.length) : cwd;
56
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: Colors.primary, bold: true, children: "CORTEX" }), _jsx(Text, { dimColor: true, children: " \u00B7 " }), _jsx(Text, { color: Colors.AccentGreen, children: model }), _jsx(Text, { dimColor: true, children: " \u00B7 " }), _jsx(Text, { color: Colors.AccentBlue, children: displayCwd })] }), _jsx(Text, { dimColor: true, children: "Tab: thinking | Shift+Tab: auto-approve | /help: commands | ESC: abort" })] }));
57
+ };
58
+ /**
59
+ * Status Line component - displays at bottom below input
60
+ * Shows model, status indicators, and keyboard shortcuts
61
+ */
62
+ const StatusLine = ({ model, autoApprove, showThinking, reasoningEffort, supportsReasoning }) => {
63
+ return (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: Colors.AccentGreen, children: model }), showThinking && _jsx(Text, { color: Colors.AccentBlue, children: " [Think]" }), supportsReasoning && reasoningEffort && reasoningEffort !== 'none' && (_jsxs(Text, { color: Colors.AccentPurple, children: [" [R:", reasoningEffort, "]"] })), autoApprove && _jsx(Text, { color: Colors.AccentYellow, children: " [Auto-Approve]" })] }));
64
+ };
65
+ /**
66
+ * Format tool input for display (compact summary)
67
+ */
68
+ function formatToolInputForDisplay(name, input) {
69
+ if (!input)
70
+ return '';
71
+ switch (name) {
72
+ case 'Read':
73
+ case 'Write':
74
+ case 'Edit':
75
+ return input.file_path || '';
76
+ case 'Glob':
77
+ case 'Grep':
78
+ return input.pattern || '';
79
+ case 'Bash':
80
+ const cmd = input.command || '';
81
+ return cmd.length > 50 ? cmd.slice(0, 50) + '...' : cmd;
82
+ default:
83
+ const first = Object.values(input).find(v => typeof v === 'string');
84
+ if (first) {
85
+ const s = String(first);
86
+ return s.length > 50 ? s.slice(0, 50) + '...' : s;
87
+ }
88
+ return '';
89
+ }
90
+ }
91
+ /**
92
+ * Message display component - memoized to prevent re-renders on input changes
93
+ */
94
+ const MessageDisplay = React.memo(({ item, terminalWidth, showThinking = false, expandedDocuments = new Set(), documentContents = new Map() }) => {
95
+ // Calculate content width accounting for prefix characters
96
+ const contentWidth = Math.max(terminalWidth - 2, 40); // "◆ " or "❯ " = 2 chars
97
+ switch (item.type) {
98
+ case MessageType.User:
99
+ return (_jsxs(Box, { marginY: 1, children: [_jsx(Text, { color: Colors.AccentBlue, children: "\u276F " }), _jsx(Text, { children: typeof item.userContent === 'string' ? item.userContent : item.userContent?.text })] }));
100
+ case MessageType.Model:
101
+ const modelItem = item;
102
+ return (_jsxs(Box, { marginY: 1, flexDirection: "column", children: [showThinking && modelItem.thought && (_jsx(InlineThinkingDisplay, { text: modelItem.thought, isComplete: true, width: terminalWidth })), _jsxs(Box, { children: [_jsx(Text, { color: Colors.AccentGreen, children: "\u25C6 " }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, children: _jsx(MarkdownText, { width: contentWidth, children: modelItem.modelContent || '' }) })] })] }));
103
+ case MessageType.Error:
104
+ return (_jsxs(Box, { marginY: 1, children: [_jsx(Text, { color: Colors.AccentRed, children: "\u2717 Error: " }), _jsx(Text, { color: Colors.AccentRed, children: item.errorContent })] }));
105
+ case MessageType.Info:
106
+ return (_jsx(Box, { marginY: 1, children: _jsxs(Text, { dimColor: true, children: ["\u2139 ", item.infoContent] }) }));
107
+ case 'orchestrator': {
108
+ // Render orchestrator message from core library
109
+ const orchestratorItem = item;
110
+ const msg = orchestratorItem.message;
111
+ if (!msg)
112
+ return null;
113
+ const role = msg.message?.role || msg.type;
114
+ const content = msg.message?.content;
115
+ // User message - check for tool_result with Edit diff or Write preview first
116
+ if (role === 'user') {
117
+ // Check if this is a tool_result message with Edit diffs or Write previews
118
+ if (Array.isArray(content)) {
119
+ const editResults = content.filter((b) => b.type === 'tool_result' &&
120
+ b.tool_name?.toLowerCase() === 'edit' &&
121
+ b.metadata?.diff &&
122
+ !b.is_error);
123
+ const writeResults = content.filter((b) => b.type === 'tool_result' &&
124
+ b.tool_name?.toLowerCase() === 'write' &&
125
+ b.metadata?.writePreview &&
126
+ !b.is_error);
127
+ const documentResults = content.filter((b) => b.type === 'tool_result' &&
128
+ b.tool_name?.toLowerCase() === 'write' &&
129
+ b.metadata?.documentPreview &&
130
+ !b.is_error);
131
+ if (editResults.length > 0 || writeResults.length > 0 || documentResults.length > 0) {
132
+ // Render Edit tool results with diffs, Write tool results with previews, and Document previews
133
+ return (_jsxs(Box, { marginY: 1, flexDirection: "column", children: [editResults.map((block, i) => (_jsx(Box, { marginLeft: 2, children: _jsx(UnifiedDiffDisplay, { diffString: block.metadata.diff, filePath: block.metadata?.fileStats?.path || '', compact: false, fileStats: block.metadata?.fileStats }) }, `${msg.uuid}-edit-result-${i}`))), writeResults.map((block, i) => (_jsx(Box, { marginLeft: 2, children: _jsx(WritePreview, { filePath: block.metadata.writePreview.filePath, content: block.metadata.writePreview.content, lineCount: block.metadata.writePreview.lineCount, byteSize: block.metadata.writePreview.byteSize, language: block.metadata.writePreview.language, terminalWidth: terminalWidth - 4 }) }, `${msg.uuid}-write-result-${i}`))), documentResults.map((block, i) => {
134
+ const docId = `${msg.uuid}-doc-${i}`;
135
+ const isExpanded = expandedDocuments.has(docId);
136
+ const docContent = documentContents.get(docId);
137
+ return (_jsx(Box, { marginLeft: 2, children: _jsx(DocumentPreview, { docId: docId, filePath: block.metadata.documentPreview.filePath, lineCount: block.metadata.documentPreview.lineCount, wordCount: block.metadata.documentPreview.wordCount, isMarkdown: block.metadata.documentPreview.isMarkdown, isExpanded: isExpanded, content: docContent ?? undefined, isLoading: isExpanded && !documentContents.has(docId), terminalWidth: terminalWidth - 2 }) }, docId));
138
+ })] }));
139
+ }
140
+ }
141
+ // Regular user text message
142
+ const text = typeof content === 'string'
143
+ ? content
144
+ : Array.isArray(content)
145
+ ? content.find((b) => b.type === 'text')?.text || ''
146
+ : '';
147
+ if (!text)
148
+ return null; // Skip empty tool_result messages
149
+ return (_jsxs(Box, { marginY: 1, children: [_jsx(Text, { color: Colors.AccentBlue, children: "\u276F " }), _jsx(Text, { children: text })] }));
150
+ }
151
+ // Assistant message - render content blocks in order
152
+ if (role === 'assistant' && Array.isArray(content)) {
153
+ return (_jsx(Box, { marginY: 1, flexDirection: "column", children: content.map((block, i) => {
154
+ if (block.type === 'text' && block.text) {
155
+ return (_jsxs(Box, { children: [_jsx(Text, { color: Colors.AccentGreen, children: "\u25C6 " }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, children: _jsx(MarkdownText, { width: contentWidth, children: block.text }) })] }, `${msg.uuid}-text-${i}`));
156
+ }
157
+ // Interleaved thinking - ALWAYS show (it's natural model output)
158
+ // This is NOT controlled by Tab toggle - that's for extended thinking only
159
+ if (block.type === 'thinking' && block.thinking) {
160
+ // Determine thinking type for visual distinction
161
+ const thinkingBlock = block;
162
+ let thinkingType = 'native';
163
+ // Check metadata first (explicitly set during conversion)
164
+ if (thinkingBlock.thinkingMetadata?.source) {
165
+ thinkingType = thinkingBlock.thinkingMetadata.source;
166
+ }
167
+ else if (thinkingBlock.signature) {
168
+ // Has signature = extended thinking (Opus + Tab)
169
+ thinkingType = 'extended';
170
+ }
171
+ return (_jsx(InlineThinkingDisplay, { text: block.thinking, isComplete: true, width: terminalWidth, thinkingType: thinkingType }, `${msg.uuid}-thinking-${i}`));
172
+ }
173
+ if (block.type === 'tool_use' && block.toolUse) {
174
+ const toolName = block.toolUse.name;
175
+ const input = block.toolUse.input || {};
176
+ const inputStr = formatToolInputForDisplay(toolName, input);
177
+ return (_jsxs(Box, { children: [_jsx(Text, { color: Colors.AccentCyan, children: "\u25CF " }), _jsx(Text, { bold: true, children: toolName }), inputStr && _jsxs(Text, { dimColor: true, children: [" (", inputStr, ")"] })] }, `${msg.uuid}-tool-${i}`));
178
+ }
179
+ if (block.type === 'tool_result') {
180
+ const isError = block.is_error;
181
+ const isEdit = block.tool_name?.toLowerCase() === 'edit';
182
+ const isWrite = block.tool_name?.toLowerCase() === 'write';
183
+ const hasDiff = block.metadata?.diff;
184
+ const hasWritePreview = block.metadata?.writePreview;
185
+ const filePath = block.metadata?.fileStats?.path || '';
186
+ // For Edit tools with diff, show the diff inline
187
+ if (isEdit && hasDiff && !isError) {
188
+ return (_jsx(Box, { flexDirection: "column", marginLeft: 2, children: _jsx(UnifiedDiffDisplay, { diffString: block.metadata.diff, filePath: filePath, compact: false, fileStats: block.metadata.fileStats }) }, `${msg.uuid}-result-${i}`));
189
+ }
190
+ // For Write tools with preview, show the file content
191
+ if (isWrite && hasWritePreview && !isError) {
192
+ return (_jsx(Box, { flexDirection: "column", marginLeft: 2, children: _jsx(WritePreview, { filePath: block.metadata.writePreview.filePath, content: block.metadata.writePreview.content, lineCount: block.metadata.writePreview.lineCount, byteSize: block.metadata.writePreview.byteSize, language: block.metadata.writePreview.language, terminalWidth: terminalWidth - 4 }) }, `${msg.uuid}-result-${i}`));
193
+ }
194
+ // For Write tools with document preview (markdown/txt), show collapsible preview
195
+ const hasDocumentPreview = block.metadata?.documentPreview;
196
+ if (isWrite && hasDocumentPreview && !isError) {
197
+ const docId = `${msg.uuid}-doc-${i}`;
198
+ const isExpanded = expandedDocuments.has(docId);
199
+ const docContent = documentContents.get(docId);
200
+ return (_jsx(Box, { flexDirection: "column", marginLeft: 2, children: _jsx(DocumentPreview, { docId: docId, filePath: block.metadata.documentPreview.filePath, lineCount: block.metadata.documentPreview.lineCount, wordCount: block.metadata.documentPreview.wordCount, isMarkdown: block.metadata.documentPreview.isMarkdown, isExpanded: isExpanded, content: docContent ?? undefined, isLoading: isExpanded && !documentContents.has(docId), terminalWidth: terminalWidth - 2 }) }, `${msg.uuid}-result-${i}`));
201
+ }
202
+ // For other tools or Edit without diff, show simple status
203
+ return (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { dimColor: true, children: "\u23BF " }), _jsx(Text, { color: isError ? Colors.AccentRed : Colors.AccentGreen, children: isError ? 'Error' : 'Done' })] }, `${msg.uuid}-result-${i}`));
204
+ }
205
+ return null;
206
+ }) }));
207
+ }
208
+ return null;
209
+ }
210
+ case MessageType.ToolGroup: {
211
+ // Render tool group with diffs for Edit tools and previews for Write tools
212
+ const tools = item.tools || [];
213
+ return (_jsx(Box, { marginY: 1, flexDirection: "column", children: tools.map((tool, i) => {
214
+ const isEdit = tool.name?.toLowerCase() === 'edit';
215
+ const isWrite = tool.name?.toLowerCase() === 'write';
216
+ const hasDiff = tool.resultDisplay?.metadata?.diff;
217
+ const hasWritePreview = tool.resultDisplay?.metadata?.writePreview;
218
+ const isSuccess = tool.status === 'Success';
219
+ const filePath = tool.resultDisplay?.metadata?.fileStats?.path || '';
220
+ if (isEdit && isSuccess && hasDiff) {
221
+ // Show full diff for Edit tools
222
+ return (_jsx(Box, { flexDirection: "column", children: _jsx(UnifiedDiffDisplay, { diffString: tool.resultDisplay.metadata.diff, filePath: filePath, compact: false, fileStats: tool.resultDisplay.metadata.fileStats }) }, `tool-${i}`));
223
+ }
224
+ if (isWrite && isSuccess && hasWritePreview) {
225
+ // Show file content preview for Write tools
226
+ return (_jsx(Box, { flexDirection: "column", children: _jsx(WritePreview, { filePath: tool.resultDisplay.metadata.writePreview.filePath, content: tool.resultDisplay.metadata.writePreview.content, lineCount: tool.resultDisplay.metadata.writePreview.lineCount, byteSize: tool.resultDisplay.metadata.writePreview.byteSize, language: tool.resultDisplay.metadata.writePreview.language, terminalWidth: terminalWidth }) }, `tool-${i}`));
227
+ }
228
+ // Show document preview for markdown/text files
229
+ const hasDocumentPreview = tool.resultDisplay?.metadata?.documentPreview;
230
+ if (isWrite && isSuccess && hasDocumentPreview) {
231
+ const docId = `${item.id}-tool-doc-${i}`;
232
+ const isExpanded = expandedDocuments.has(docId);
233
+ const docContent = documentContents.get(docId);
234
+ return (_jsx(Box, { flexDirection: "column", children: _jsx(DocumentPreview, { docId: docId, filePath: tool.resultDisplay.metadata.documentPreview.filePath, lineCount: tool.resultDisplay.metadata.documentPreview.lineCount, wordCount: tool.resultDisplay.metadata.documentPreview.wordCount, isMarkdown: tool.resultDisplay.metadata.documentPreview.isMarkdown, isExpanded: isExpanded, content: docContent ?? undefined, isLoading: isExpanded && !documentContents.has(docId), terminalWidth: terminalWidth }) }, `tool-${i}`));
235
+ }
236
+ // Show simple tool result for non-Edit/Write tools
237
+ return (_jsxs(Box, { children: [_jsxs(Text, { color: isSuccess ? Colors.AccentGreen : Colors.AccentRed, children: [isSuccess ? '✓' : '✗', ' '] }), _jsx(Text, { bold: true, children: tool.name }), filePath && _jsxs(Text, { dimColor: true, children: [" (", filePath, ")"] })] }, `tool-${i}`));
238
+ }) }));
239
+ }
240
+ default:
241
+ return null;
242
+ }
243
+ });
244
+ // Display name for debugging
245
+ MessageDisplay.displayName = 'MessageDisplay';
246
+ /**
247
+ * Enhanced Input component with proper cursor control and paste support
248
+ *
249
+ * Uses our SimpleInputBuffer for standalone input handling:
250
+ * - Full cursor control: Left/Right for chars, Up/Down for lines
251
+ * - Multi-line input: Ctrl+J adds new line, Enter submits
252
+ * - Input history: Up/Down arrows navigate previous messages (when single-line)
253
+ * - Slash command autocomplete: Shows suggestions when typing /
254
+ * - Paste support via bracketed paste mode (raw stdin parsing)
255
+ * - Visual cursor with inverse highlight
256
+ *
257
+ * Wrapped in React.memo to prevent re-renders from parent state changes
258
+ */
259
+ const EnhancedInput = React.memo(({ onSubmit, disabled, placeholder = 'Type your message...', userMessages, prefillValue }) => {
260
+ const { width: terminalWidth } = useTerminalSize();
261
+ const { stdin } = useStdin();
262
+ // Use our simple input buffer (state is managed internally)
263
+ const bufferRef = useRef(null);
264
+ if (!bufferRef.current) {
265
+ bufferRef.current = createInputBuffer();
266
+ }
267
+ const buffer = bufferRef.current;
268
+ // Force re-render when buffer changes
269
+ const [, forceUpdate] = useState({});
270
+ const triggerUpdate = useCallback(() => forceUpdate({}), []);
271
+ // Ghost text (prediction autocomplete shown as dim text after cursor)
272
+ const [ghostText, setGhostText] = useState(null);
273
+ const lastPrefillRef = useRef(null);
274
+ // §5.1 prefill provenance for the CURRENT composition (reset on submit):
275
+ // ghostSeenRef — a ghost was rendered at any point since the last submit
276
+ // (even if later dismissed by typing/Escape): submission = 'shown'.
277
+ // ghostInsertedRef — the user Tab/→-accepted the ghost (edits after
278
+ // insertion don't clear it — anchoring already happened): 'inserted'.
279
+ const ghostSeenRef = useRef(false);
280
+ const ghostInsertedRef = useRef(false);
281
+ useEffect(() => {
282
+ if (prefillValue && prefillValue !== lastPrefillRef.current) {
283
+ setGhostText(prefillValue);
284
+ ghostSeenRef.current = true;
285
+ lastPrefillRef.current = prefillValue;
286
+ }
287
+ else if (!prefillValue) {
288
+ setGhostText(null);
289
+ lastPrefillRef.current = null;
290
+ }
291
+ }, [prefillValue]);
292
+ // Input history state
293
+ const [historyIndex, setHistoryIndex] = useState(-1);
294
+ const [originalValue, setOriginalValue] = useState('');
295
+ // Command suggestions state
296
+ const [suggestions, setSuggestions] = useState([]);
297
+ const [suggestionIndex, setSuggestionIndex] = useState(0);
298
+ // Derived state
299
+ const text = buffer.text;
300
+ const lines = buffer.lines;
301
+ const lineCount = lines.length;
302
+ const isMultiLine = lineCount > 1;
303
+ const [cursorRow, cursorCol] = buffer.cursor;
304
+ const showSuggestions = suggestions.length > 0 && text.startsWith('/');
305
+ // Update suggestions when text changes
306
+ useEffect(() => {
307
+ if (text.startsWith('/') && !isMultiLine) {
308
+ const newSuggestions = getSuggestions(text); // No limit - allow scrolling through all commands
309
+ setSuggestions(newSuggestions);
310
+ setSuggestionIndex(prev => prev >= newSuggestions.length ? Math.max(0, newSuggestions.length - 1) : prev);
311
+ }
312
+ else {
313
+ setSuggestions([]);
314
+ setSuggestionIndex(0);
315
+ }
316
+ }, [text, isMultiLine]);
317
+ // Refs for current state in event handler
318
+ const stateRef = useRef({ historyIndex, originalValue, suggestions, suggestionIndex, text, cursorRow, lineCount });
319
+ stateRef.current = { historyIndex, originalValue, suggestions, suggestionIndex, text, cursorRow, lineCount };
320
+ // Flag to skip useInput when we've handled a paste
321
+ const skipNextInputRef = useRef(false);
322
+ // Enable bracketed paste mode when component mounts
323
+ useEffect(() => {
324
+ if (disabled)
325
+ return;
326
+ // Enable bracketed paste mode
327
+ process.stdout.write('\x1b[?2004h');
328
+ return () => {
329
+ // Disable bracketed paste mode on cleanup
330
+ process.stdout.write('\x1b[?2004l');
331
+ };
332
+ }, [disabled]);
333
+ // Raw stdin handling ONLY for bracketed paste detection
334
+ // Regular keys are handled by useInput below
335
+ useEffect(() => {
336
+ if (!stdin || disabled)
337
+ return;
338
+ const handleData = (data) => {
339
+ // Check for bracketed paste (escape sequence wrapped)
340
+ // Format: \x1b[200~ ... content ... \x1b[201~
341
+ if (data.includes('\x1b[200~')) {
342
+ const startIdx = data.indexOf('\x1b[200~');
343
+ const endIdx = data.indexOf('\x1b[201~');
344
+ if (endIdx !== -1) {
345
+ const pasteContent = data.slice(startIdx + 6, endIdx);
346
+ // Normalize line endings
347
+ const cleanedPaste = pasteContent
348
+ .replace(/\r\n/g, '\n')
349
+ .replace(/\r/g, '\n')
350
+ .replace(/\n+$/, '');
351
+ buffer.insert(cleanedPaste, { isPaste: true });
352
+ if (stateRef.current.historyIndex !== -1) {
353
+ setHistoryIndex(-1);
354
+ }
355
+ // Set flag to skip useInput processing for a short time
356
+ // This prevents the double-processing issue
357
+ skipNextInputRef.current = true;
358
+ setTimeout(() => { skipNextInputRef.current = false; }, 50);
359
+ triggerUpdate();
360
+ return;
361
+ }
362
+ }
363
+ // Heuristic: If data contains multiple characters including newlines,
364
+ // and it doesn't start with escape sequences, treat it as a paste.
365
+ const looksLikePaste = data.length > 1 &&
366
+ !data.startsWith('\x1b') &&
367
+ (data.includes('\n') || data.includes('\r')) &&
368
+ data.replace(/[\r\n]/g, '').length > 0;
369
+ if (looksLikePaste) {
370
+ const cleanedPaste = data
371
+ .replace(/\r\n/g, '\n')
372
+ .replace(/\r/g, '\n')
373
+ .replace(/\n+$/, '');
374
+ buffer.insert(cleanedPaste, { isPaste: true });
375
+ if (stateRef.current.historyIndex !== -1) {
376
+ setHistoryIndex(-1);
377
+ }
378
+ skipNextInputRef.current = true;
379
+ setTimeout(() => { skipNextInputRef.current = false; }, 50);
380
+ triggerUpdate();
381
+ }
382
+ // All other input is handled by useInput below
383
+ };
384
+ stdin.on('data', handleData);
385
+ return () => {
386
+ stdin.removeListener('data', handleData);
387
+ };
388
+ }, [stdin, disabled, buffer, triggerUpdate]);
389
+ // Use Ink's useInput for all keyboard handling (it properly parses escape sequences)
390
+ useInput((input, key) => {
391
+ if (disabled)
392
+ return;
393
+ // Skip if we just handled a paste via raw stdin
394
+ if (skipNextInputRef.current)
395
+ return;
396
+ const state = stateRef.current;
397
+ const showingSuggestions = state.suggestions.length > 0 && buffer.text.startsWith('/');
398
+ // Submit on Enter
399
+ if (key.return) {
400
+ if (showingSuggestions) {
401
+ const selected = state.suggestions[state.suggestionIndex];
402
+ if (selected) {
403
+ // Slash-command pick: control action, not a conversational turn —
404
+ // provenance 'none'; ghost refs deliberately NOT reset (a seen ghost
405
+ // can still anchor the next real message after e.g. /model).
406
+ onSubmit(selected.fullPath, 'none');
407
+ buffer.setText('');
408
+ setSuggestions([]);
409
+ setSuggestionIndex(0);
410
+ setHistoryIndex(-1);
411
+ setOriginalValue('');
412
+ }
413
+ }
414
+ else if (buffer.text.trim()) {
415
+ // §5.1 provenance of this submission relative to the ghost prefill
416
+ const provenance = ghostInsertedRef.current
417
+ ? 'inserted'
418
+ : ghostSeenRef.current ? 'shown' : 'none';
419
+ ghostSeenRef.current = false;
420
+ ghostInsertedRef.current = false;
421
+ onSubmit(buffer.text, provenance);
422
+ buffer.setText('');
423
+ setHistoryIndex(-1);
424
+ setOriginalValue('');
425
+ setSuggestions([]);
426
+ setSuggestionIndex(0);
427
+ if (ghostText)
428
+ setGhostText(null);
429
+ }
430
+ triggerUpdate();
431
+ return;
432
+ }
433
+ // Tab handling
434
+ if (key.tab) {
435
+ if (key.shift) {
436
+ // Shift+Tab handled at app level
437
+ return;
438
+ }
439
+ if (showingSuggestions) {
440
+ const selected = state.suggestions[state.suggestionIndex];
441
+ if (selected) {
442
+ buffer.setText(selected.fullPath + ' ');
443
+ setSuggestions([]);
444
+ setSuggestionIndex(0);
445
+ }
446
+ triggerUpdate();
447
+ }
448
+ else if (ghostText) {
449
+ buffer.setText(buffer.text + ghostText);
450
+ ghostInsertedRef.current = true;
451
+ setGhostText(null);
452
+ triggerUpdate();
453
+ }
454
+ return;
455
+ }
456
+ // Up arrow
457
+ if (key.upArrow) {
458
+ if (showingSuggestions) {
459
+ setSuggestionIndex(i => Math.max(0, i - 1));
460
+ return;
461
+ }
462
+ // At top line (or single line)
463
+ if (state.lineCount === 1 || state.cursorRow === 0) {
464
+ // If cursor is not at beginning, first move to beginning
465
+ if (buffer.cursor[1] > 0) {
466
+ buffer.moveHome();
467
+ triggerUpdate();
468
+ return;
469
+ }
470
+ // Cursor already at beginning - scroll through history
471
+ if (userMessages.length === 0)
472
+ return;
473
+ if (state.historyIndex === -1) {
474
+ setOriginalValue(buffer.text);
475
+ setHistoryIndex(0);
476
+ const newText = userMessages[userMessages.length - 1] || '';
477
+ buffer.setText(newText);
478
+ }
479
+ else if (state.historyIndex < userMessages.length - 1) {
480
+ const newIndex = state.historyIndex + 1;
481
+ setHistoryIndex(newIndex);
482
+ const newText = userMessages[userMessages.length - 1 - newIndex] || '';
483
+ buffer.setText(newText);
484
+ }
485
+ triggerUpdate();
486
+ return;
487
+ }
488
+ // Move cursor up in multiline
489
+ buffer.moveUp();
490
+ triggerUpdate();
491
+ return;
492
+ }
493
+ // Down arrow
494
+ if (key.downArrow) {
495
+ if (showingSuggestions) {
496
+ setSuggestionIndex(i => Math.min(state.suggestions.length - 1, i + 1));
497
+ return;
498
+ }
499
+ // At bottom line (or single line)
500
+ if (state.lineCount === 1 || state.cursorRow === state.lineCount - 1) {
501
+ const currentLineLength = buffer.lines[buffer.cursor[0]]?.length || 0;
502
+ // If cursor is not at end, first move to end
503
+ if (buffer.cursor[1] < currentLineLength) {
504
+ buffer.moveEnd();
505
+ triggerUpdate();
506
+ return;
507
+ }
508
+ // Cursor already at end - scroll through history (if in history mode)
509
+ if (state.historyIndex === -1)
510
+ return;
511
+ if (state.historyIndex === 0) {
512
+ setHistoryIndex(-1);
513
+ buffer.setText(state.originalValue);
514
+ }
515
+ else {
516
+ const newIndex = state.historyIndex - 1;
517
+ setHistoryIndex(newIndex);
518
+ const newText = userMessages[userMessages.length - 1 - newIndex] || '';
519
+ buffer.setText(newText);
520
+ }
521
+ triggerUpdate();
522
+ return;
523
+ }
524
+ // Move cursor down in multiline
525
+ buffer.moveDown();
526
+ triggerUpdate();
527
+ return;
528
+ }
529
+ // Left arrow
530
+ if (key.leftArrow) {
531
+ if (key.ctrl) {
532
+ buffer.moveWordLeft();
533
+ }
534
+ else {
535
+ buffer.moveLeft();
536
+ }
537
+ triggerUpdate();
538
+ return;
539
+ }
540
+ // Right arrow
541
+ if (key.rightArrow) {
542
+ const atEnd = buffer.cursor[0] === buffer.lines.length - 1
543
+ && buffer.cursor[1] >= (buffer.lines[buffer.cursor[0]] || '').length;
544
+ if (atEnd && ghostText) {
545
+ buffer.setText(buffer.text + ghostText);
546
+ ghostInsertedRef.current = true;
547
+ setGhostText(null);
548
+ }
549
+ else if (key.ctrl) {
550
+ buffer.moveWordRight();
551
+ }
552
+ else {
553
+ buffer.moveRight();
554
+ }
555
+ triggerUpdate();
556
+ return;
557
+ }
558
+ // Option+Delete (Alt+Backspace) - delete word to the left
559
+ // On Mac, Option+Delete sends \x17 (ETB) or the meta flag is set
560
+ if ((key.meta && (key.delete || key.backspace || input === '\x7f')) || input === '\x17') {
561
+ buffer.deleteWordLeft();
562
+ if (state.historyIndex !== -1)
563
+ setHistoryIndex(-1);
564
+ triggerUpdate();
565
+ return;
566
+ }
567
+ // Mac Delete key (⌫) - deletes character to the LEFT of cursor
568
+ // Ink sets key.delete=true for Mac Delete key, so treat ALL delete as backspace
569
+ if (key.delete || key.backspace || input === '\x7f' || input === '\b') {
570
+ buffer.backspace();
571
+ if (state.historyIndex !== -1)
572
+ setHistoryIndex(-1);
573
+ triggerUpdate();
574
+ return;
575
+ }
576
+ // Escape
577
+ if (key.escape) {
578
+ if (ghostText) {
579
+ setGhostText(null);
580
+ }
581
+ else if (showingSuggestions) {
582
+ setSuggestions([]);
583
+ setSuggestionIndex(0);
584
+ }
585
+ else if (state.historyIndex !== -1) {
586
+ setHistoryIndex(-1);
587
+ buffer.setText(state.originalValue);
588
+ }
589
+ else if (buffer.text) {
590
+ buffer.setText('');
591
+ }
592
+ triggerUpdate();
593
+ return;
594
+ }
595
+ // Ctrl+J for newline
596
+ if (key.ctrl && input === 'j') {
597
+ buffer.newline();
598
+ if (state.historyIndex !== -1)
599
+ setHistoryIndex(-1);
600
+ triggerUpdate();
601
+ return;
602
+ }
603
+ // Ctrl+A - move to start
604
+ if (key.ctrl && input === 'a') {
605
+ buffer.moveHome();
606
+ triggerUpdate();
607
+ return;
608
+ }
609
+ // Ctrl+E - move to end
610
+ if (key.ctrl && input === 'e') {
611
+ buffer.moveEnd();
612
+ triggerUpdate();
613
+ return;
614
+ }
615
+ // Ctrl+U - kill to start of line
616
+ if (key.ctrl && input === 'u') {
617
+ while (buffer.cursor[1] > 0) {
618
+ buffer.backspace();
619
+ }
620
+ if (state.historyIndex !== -1)
621
+ setHistoryIndex(-1);
622
+ triggerUpdate();
623
+ return;
624
+ }
625
+ // Ctrl+K - kill to end of line
626
+ if (key.ctrl && input === 'k') {
627
+ const currentLine = buffer.lines[buffer.cursor[0]] || '';
628
+ while (buffer.cursor[1] < currentLine.length) {
629
+ buffer.delete();
630
+ }
631
+ if (state.historyIndex !== -1)
632
+ setHistoryIndex(-1);
633
+ triggerUpdate();
634
+ return;
635
+ }
636
+ // Ctrl+Z - undo
637
+ if (key.ctrl && input === 'z') {
638
+ buffer.undo();
639
+ triggerUpdate();
640
+ return;
641
+ }
642
+ // Regular character input (not control keys)
643
+ if (input && !key.ctrl && !key.meta) {
644
+ if (ghostText)
645
+ setGhostText(null);
646
+ buffer.insert(input);
647
+ if (state.historyIndex !== -1)
648
+ setHistoryIndex(-1);
649
+ triggerUpdate();
650
+ }
651
+ }, { isActive: !disabled });
652
+ // Render text with cursor highlighting
653
+ const renderContent = () => {
654
+ if (!text) {
655
+ if (ghostText && !disabled) {
656
+ // Empty buffer with ghost text: show cursor then dim prediction
657
+ return (_jsxs(Text, { children: [_jsx(Text, { inverse: true, children: ghostText.charAt(0) }), _jsx(Text, { dimColor: true, children: ghostText.slice(1) })] }));
658
+ }
659
+ // When showing placeholder: show cursor position with first char highlighted
660
+ const displayPlaceholder = placeholder || 'Type your message...';
661
+ return (_jsxs(Text, { children: [!disabled && _jsx(Text, { inverse: true, children: displayPlaceholder.charAt(0) || ' ' }), _jsx(Text, { dimColor: true, children: disabled ? displayPlaceholder : displayPlaceholder.slice(1) })] }));
662
+ }
663
+ const isLastLine = (idx) => idx === lines.length - 1;
664
+ const showGhostOnLine = (idx) => ghostText && !disabled && isLastLine(idx) && idx === cursorRow
665
+ && cursorCol >= (lines[idx] || '').length;
666
+ return (_jsx(Box, { flexDirection: "column", children: lines.map((line, lineIdx) => {
667
+ const isOnCursorLine = lineIdx === cursorRow;
668
+ if (!isOnCursorLine || disabled) {
669
+ return (_jsx(Text, { color: disabled ? Colors.Gray : undefined, children: line || ' ' }, lineIdx));
670
+ }
671
+ // Render line with cursor
672
+ const lineLen = line.length;
673
+ const before = line.slice(0, cursorCol);
674
+ const cursorChar = cursorCol < lineLen ? line.slice(cursorCol, cursorCol + 1) : ' ';
675
+ const after = cursorCol < lineLen ? line.slice(cursorCol + 1) : '';
676
+ return (_jsxs(Text, { children: [before, _jsx(Text, { inverse: true, children: cursorChar }), after, showGhostOnLine(lineIdx) && _jsx(Text, { dimColor: true, children: ghostText })] }, lineIdx));
677
+ }) }));
678
+ };
679
+ // Determine border color based on mode
680
+ const getBorderColor = () => {
681
+ if (disabled)
682
+ return Colors.Gray;
683
+ if (showSuggestions)
684
+ return Colors.AccentGreen;
685
+ if (historyIndex !== -1)
686
+ return Colors.AccentYellow;
687
+ return Colors.AccentCyan;
688
+ };
689
+ return (_jsxs(Box, { flexDirection: "column", children: [showSuggestions && (_jsx(CommandSuggestions, { suggestions: suggestions, selectedIndex: suggestionIndex, maxVisible: 8 })), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: getBorderColor(), children: '─'.repeat(Math.max(terminalWidth - 2, 40)) }), _jsx(Box, { paddingX: 1, minHeight: 1, children: renderContent() }), _jsx(Text, { color: getBorderColor(), children: '─'.repeat(Math.max(terminalWidth - 2, 40)) })] }), _jsxs(Box, { paddingX: 1, children: [showSuggestions && (_jsx(Text, { dimColor: true, color: Colors.AccentGreen, children: "\u2191\u2193 select \u2022 Tab complete \u2022 Enter execute \u2022 Esc close" })), !showSuggestions && historyIndex !== -1 && (_jsxs(Text, { dimColor: true, color: Colors.AccentYellow, children: ["History [", historyIndex + 1, "/", userMessages.length, "] \u2022 \u2193 newer \u2022 Esc cancel"] })), !showSuggestions && isMultiLine && historyIndex === -1 && (_jsxs(Text, { dimColor: true, children: [lineCount, " lines \u2022 \u2190\u2192\u2191\u2193 cursor \u2022 Ctrl+A/E home/end \u2022 Enter submit"] })), !showSuggestions && !isMultiLine && historyIndex === -1 && !ghostText && (_jsx(Text, { dimColor: true, children: "/ commands \u2022 \u2191\u2193 history \u2022 Ctrl+J new line \u2022 \u2190\u2192 cursor" })), ghostText && !showSuggestions && historyIndex === -1 && (_jsx(Text, { dimColor: true, children: "Tab or \u2192 accept prediction \u2022 Esc dismiss \u2022 type to replace" }))] })] }));
690
+ });
691
+ // Display name for debugging
692
+ EnhancedInput.displayName = 'EnhancedInput';
693
+ /**
694
+ * Loading spinner component
695
+ */
696
+ const LoadingSpinner = ({ text = 'Thinking...' }) => {
697
+ const [frame, setFrame] = useState(0);
698
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
699
+ useEffect(() => {
700
+ const timer = setInterval(() => {
701
+ setFrame(f => (f + 1) % frames.length);
702
+ }, 80);
703
+ return () => clearInterval(timer);
704
+ }, []);
705
+ return (_jsxs(Text, { color: Colors.AccentCyan, children: [frames[frame], " ", text] }));
706
+ };
707
+ /**
708
+ * Theme Picker Dialog Component
709
+ *
710
+ * Interactive theme selection with live preview colors
711
+ */
712
+ const ThemePickerDialog = ({ themes, currentTheme, onSelect, onCancel }) => {
713
+ const [selectedIndex, setSelectedIndex] = useState(() => Math.max(0, themes.indexOf(currentTheme)));
714
+ const selectedTheme = themes[selectedIndex] || themes[0];
715
+ const themeDef = selectedTheme ? getThemeDefinition(selectedTheme) : null;
716
+ useInput((input, key) => {
717
+ // Up/Down to navigate
718
+ if (key.upArrow) {
719
+ setSelectedIndex(i => Math.max(0, i - 1));
720
+ return;
721
+ }
722
+ if (key.downArrow) {
723
+ setSelectedIndex(i => Math.min(themes.length - 1, i + 1));
724
+ return;
725
+ }
726
+ // Number keys for quick selection (1-9)
727
+ const num = parseInt(input, 10);
728
+ if (num >= 1 && num <= 9 && num <= themes.length) {
729
+ setSelectedIndex(num - 1);
730
+ return;
731
+ }
732
+ // Enter to select
733
+ if (key.return && selectedTheme) {
734
+ onSelect(selectedTheme);
735
+ return;
736
+ }
737
+ // Escape to cancel
738
+ if (key.escape) {
739
+ onCancel();
740
+ return;
741
+ }
742
+ });
743
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: Colors.AccentPurple, paddingX: 2, paddingY: 1, marginY: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: Colors.AccentPurple, bold: true, children: "Select Theme" }) }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexDirection: "column", width: "50%", children: themes.map((theme, index) => {
744
+ const def = getThemeDefinition(theme);
745
+ const isSelected = index === selectedIndex;
746
+ const isCurrent = theme === currentTheme;
747
+ return (_jsx(Box, { children: _jsxs(Text, { color: isSelected ? Colors.AccentCyan : undefined, bold: isSelected, children: [isSelected ? '❯' : ' ', " ", index < 9 ? `${index + 1}.` : ' ', ' ', _jsx(Text, { color: isSelected ? Colors.AccentCyan : (isCurrent ? Colors.AccentGreen : undefined), children: def?.name || theme }), isCurrent && _jsx(Text, { dimColor: true, children: " (current)" })] }) }, theme));
748
+ }) }), themeDef && (_jsxs(Box, { flexDirection: "column", width: "50%", paddingLeft: 2, children: [_jsx(Text, { bold: true, color: Colors.AccentPurple, children: "Preview" }), _jsxs(Box, { borderStyle: "single", borderColor: themeDef.dimmed, flexDirection: "column", paddingX: 1, marginTop: 1, children: [_jsx(Text, { color: themeDef.primary, children: "\u25CF Primary" }), _jsx(Text, { color: themeDef.secondary, children: "\u25CF Secondary" }), _jsx(Text, { color: themeDef.success, children: "\u25CF Success" }), _jsx(Text, { color: themeDef.warning, children: "\u25CF Warning" }), _jsx(Text, { color: themeDef.error, children: "\u25CF Error" }), _jsx(Text, { color: themeDef.info, children: "\u25CF Info" }), _jsx(Text, { color: themeDef.text, children: "\u25CF Text" }), _jsx(Text, { color: themeDef.dimmed, children: "\u25CF Dimmed" })] })] }))] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191\u2193 navigate \u2022 1-9 quick select \u2022 Enter apply \u2022 Esc cancel" }) })] }));
749
+ };
750
+ /**
751
+ * Format relative time for session display
752
+ */
753
+ function formatSessionAge(date) {
754
+ const now = new Date();
755
+ const diffMs = now.getTime() - date.getTime();
756
+ const diffMins = Math.floor(diffMs / (1000 * 60));
757
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
758
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
759
+ if (diffMins < 1)
760
+ return 'Just now';
761
+ if (diffMins < 60)
762
+ return `${diffMins}m ago`;
763
+ if (diffHours < 24)
764
+ return `${diffHours}h ago`;
765
+ if (diffDays === 1)
766
+ return 'Yesterday';
767
+ if (diffDays < 7)
768
+ return `${diffDays}d ago`;
769
+ if (diffDays < 30)
770
+ return `${Math.floor(diffDays / 7)}w ago`;
771
+ return date.toLocaleDateString();
772
+ }
773
+ /**
774
+ * Session Picker Dialog Component
775
+ *
776
+ * Interactive session selection with details panel, similar to ModelPickerDialog.
777
+ */
778
+ const SessionPickerDialog = ({ sessions, onSelect, onCancel, loading = false }) => {
779
+ const [selectedIndex, setSelectedIndex] = useState(0);
780
+ // Memoize to prevent recalculation
781
+ const selectedSession = useMemo(() => sessions[selectedIndex] || sessions[0], [sessions, selectedIndex]);
782
+ useInput((input, key) => {
783
+ if (loading)
784
+ return;
785
+ if (key.upArrow) {
786
+ setSelectedIndex(i => Math.max(0, i - 1));
787
+ return;
788
+ }
789
+ if (key.downArrow) {
790
+ setSelectedIndex(i => Math.min(sessions.length - 1, i + 1));
791
+ return;
792
+ }
793
+ const num = parseInt(input, 10);
794
+ if (num >= 1 && num <= 9 && num <= sessions.length) {
795
+ setSelectedIndex(num - 1);
796
+ return;
797
+ }
798
+ if (key.return && selectedSession) {
799
+ onSelect(selectedSession.sessionId);
800
+ return;
801
+ }
802
+ if (key.escape) {
803
+ onCancel();
804
+ return;
805
+ }
806
+ });
807
+ // Fixed window size - always show exactly this many rows
808
+ const VISIBLE_ROWS = 10;
809
+ // Calculate scroll window with stable bounds
810
+ const scrollWindow = useMemo(() => {
811
+ const total = sessions.length;
812
+ if (total <= VISIBLE_ROWS) {
813
+ return { start: 0, end: total };
814
+ }
815
+ // Keep selection centered when possible
816
+ const halfWindow = Math.floor(VISIBLE_ROWS / 2);
817
+ let start = selectedIndex - halfWindow;
818
+ let end = selectedIndex + halfWindow + (VISIBLE_ROWS % 2);
819
+ // Clamp to bounds
820
+ if (start < 0) {
821
+ start = 0;
822
+ end = VISIBLE_ROWS;
823
+ }
824
+ else if (end > total) {
825
+ end = total;
826
+ start = total - VISIBLE_ROWS;
827
+ }
828
+ return { start, end };
829
+ }, [sessions.length, selectedIndex]);
830
+ // Build display rows - memoized
831
+ const displayRows = useMemo(() => {
832
+ const rows = [];
833
+ for (let i = scrollWindow.start; i < scrollWindow.end; i++) {
834
+ const session = sessions[i];
835
+ if (session) {
836
+ rows.push({
837
+ key: `row-${i}-${session.sessionId}`,
838
+ session,
839
+ index: i,
840
+ isSelected: i === selectedIndex,
841
+ });
842
+ }
843
+ }
844
+ return rows;
845
+ }, [sessions, scrollWindow, selectedIndex]);
846
+ const aboveCount = scrollWindow.start;
847
+ const belowCount = sessions.length - scrollWindow.end;
848
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: Colors.AccentPurple, paddingX: 2, paddingY: 1, marginY: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: Colors.AccentPurple, bold: true, children: ["Resume Session (", sessions.length, " available)"] }), loading && _jsx(Text, { dimColor: true, children: " loading..." })] }), loading ? (_jsx(Box, { height: VISIBLE_ROWS + 2, children: _jsx(Text, { dimColor: true, children: "Loading sessions..." }) })) : sessions.length === 0 ? (_jsx(Box, { height: 3, children: _jsx(Text, { dimColor: true, children: "No previous sessions found." }) })) : (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Box, { flexDirection: "column", width: 44, children: [aboveCount > 0 && (_jsxs(Text, { dimColor: true, children: [" \u2191 ", aboveCount, " more"] })), aboveCount === 0 && _jsx(Text, { children: " " }), displayRows.map((row) => (_jsxs(Text, { children: [_jsx(Text, { color: row.isSelected ? Colors.AccentCyan : undefined, bold: row.isSelected, children: row.isSelected ? '❯ ' : ' ' }), _jsxs(Text, { dimColor: true, children: [row.index + 1, ". "] }), _jsx(Text, { color: row.isSelected ? Colors.AccentCyan : Colors.AccentBlue, children: row.session.title
849
+ ? row.session.title.slice(0, 28).padEnd(28)
850
+ : row.session.shortId.padEnd(28) }), _jsx(Text, { dimColor: true, children: " \u2502 " }), _jsx(Text, { color: row.isSelected ? Colors.AccentCyan : undefined, children: row.session.age.padEnd(9) })] }, row.key))), belowCount > 0 && (_jsxs(Text, { dimColor: true, children: [" \u2193 ", belowCount, " more"] })), belowCount === 0 && _jsx(Text, { children: " " })] }), _jsxs(Box, { flexDirection: "column", width: 38, paddingLeft: 1, children: [_jsx(Text, { bold: true, color: Colors.AccentPurple, children: "Session Details" }), _jsx(Box, { borderStyle: "single", borderColor: Colors.Gray, flexDirection: "column", paddingX: 1, height: 13, children: selectedSession && (_jsxs(_Fragment, { children: [selectedSession.title && (_jsx(Text, { bold: true, color: Colors.AccentCyan, children: selectedSession.title.slice(0, 34) })), _jsxs(Text, { children: [_jsx(Text, { color: Colors.AccentCyan, children: "ID:" }), " ", selectedSession.shortId] }), _jsxs(Text, { children: [_jsx(Text, { color: Colors.AccentCyan, children: "Messages:" }), " ", selectedSession.messageCount] }), _jsxs(Text, { children: [_jsx(Text, { color: Colors.AccentCyan, children: "Last Active:" }), " ", selectedSession.age] }), _jsxs(Text, { children: [_jsx(Text, { color: Colors.AccentCyan, children: "Model:" }), " ", (selectedSession.model || 'Unknown').slice(0, 24)] }), _jsxs(Text, { children: [_jsx(Text, { color: Colors.AccentCyan, children: "Date:" }), " ", selectedSession.lastModified.toLocaleDateString()] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: Colors.AccentCyan, children: "First Prompt:" }), _jsx(Text, { dimColor: true, wrap: "wrap", children: selectedSession.firstUserMessage
851
+ ? selectedSession.firstUserMessage.slice(0, 250)
852
+ : '(No preview available)' })] })] })) })] })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191\u2193 navigate \u2022 1-9 quick select \u2022 Enter resume \u2022 Esc cancel" }) })] }));
853
+ };
854
+ /**
855
+ * Main Cortex Application
856
+ */
857
+ export const CortexApp = ({ modelId, debug = false, projectPath, autoApprove: initialAutoApprove = false, initialPrompt, }) => {
858
+ const { exit } = useApp();
859
+ useStdout(); // Keep for potential future use
860
+ const { width: terminalWidth, height: terminalHeight } = useTerminalSize();
861
+ // Application state
862
+ const [client, setClient] = useState(null);
863
+ const [initialized, setInitialized] = useState(false);
864
+ const [initError, setInitError] = useState(null);
865
+ const [showHelp, setShowHelp] = useState(false);
866
+ const [showConfigMenu, setShowConfigMenu] = useState(false);
867
+ const [showThinking, setShowThinking] = useState(false);
868
+ const [autoApprove, setAutoApprove] = useState(initialAutoApprove);
869
+ const [currentModel, setCurrentModel] = useState(modelId || 'loading...');
870
+ const [reasoningEffort, setReasoningEffort] = useState('none');
871
+ const [supportsReasoning, setSupportsReasoning] = useState(false);
872
+ const [reasoningToggleable, setReasoningToggleable] = useState(false);
873
+ // Initialize theme from .cortex/config.json or use default
874
+ const [currentTheme, setCurrentTheme] = useState(() => initializeTheme());
875
+ const [showThemePicker, setShowThemePicker] = useState(false);
876
+ // Model picker state
877
+ const [showModelPicker, setShowModelPicker] = useState(false);
878
+ const [availableModels, setAvailableModels] = useState([]);
879
+ const [modelsLoading, setModelsLoading] = useState(false);
880
+ // Session picker state
881
+ const [showSessionPicker, setShowSessionPicker] = useState(false);
882
+ const [availableSessions, setAvailableSessions] = useState([]);
883
+ const [sessionsLoading, setSessionsLoading] = useState(false);
884
+ // System message manager state
885
+ const [showSystemMessageManager, setShowSystemMessageManager] = useState(false);
886
+ const [systemMessageStore, setSystemMessageStore] = useState(null);
887
+ // Mentorship config menu state
888
+ const [showMentorshipConfig, setShowMentorshipConfig] = useState(false);
889
+ const [mentorshipMenuDef, setMentorshipMenuDef] = useState(null);
890
+ const [mentorshipInitialValues, setMentorshipInitialValues] = useState({});
891
+ const [mentorshipService, setMentorshipService] = useState(null);
892
+ // Sub-agent tracking state for parallel agent display
893
+ const subAgentStateManagerRef = useRef(createSubAgentStateManager());
894
+ const [subAgents, setSubAgents] = useState(new Map());
895
+ // Sub-agent event handler callback - updates state for UI
896
+ const handleSubAgentEvent = useCallback((event) => {
897
+ const manager = subAgentStateManagerRef.current;
898
+ switch (event.type) {
899
+ case 'started':
900
+ manager.handleStarted({
901
+ agentId: event.agentId,
902
+ agentName: event.agentName,
903
+ model: event.data.model || 'unknown',
904
+ });
905
+ break;
906
+ case 'progress':
907
+ manager.handleProgress({
908
+ agentId: event.agentId,
909
+ turnNumber: event.data.turnNumber || 0,
910
+ totalTokens: event.data.totalTokens || 0,
911
+ elapsedMs: event.data.elapsedMs || 0,
912
+ });
913
+ break;
914
+ case 'tool_call':
915
+ manager.handleToolCall({
916
+ agentId: event.agentId,
917
+ toolName: event.data.toolName || 'unknown',
918
+ toolInput: {},
919
+ });
920
+ break;
921
+ case 'text':
922
+ manager.handleText({
923
+ agentId: event.agentId,
924
+ text: event.data.text || '',
925
+ isFinal: event.data.isFinal || false,
926
+ });
927
+ break;
928
+ case 'completed':
929
+ manager.handleCompleted({
930
+ agentId: event.agentId,
931
+ result: {
932
+ status: event.data.status || 'completed',
933
+ durationMs: event.data.elapsedMs || 0,
934
+ turnCount: event.data.turnNumber || 0,
935
+ },
936
+ });
937
+ // Clear completed agent from display after brief delay
938
+ // This allows the completion status to show briefly, then it scrolls away
939
+ setTimeout(() => {
940
+ manager.agents.delete(event.agentId);
941
+ setSubAgents(new Map(manager.agents));
942
+ }, 2000);
943
+ break;
944
+ case 'error':
945
+ manager.handleError({
946
+ agentId: event.agentId,
947
+ agentName: event.agentName,
948
+ error: new Error(event.data.error || 'Unknown error'),
949
+ });
950
+ // Clear errored agent from display after delay
951
+ setTimeout(() => {
952
+ manager.agents.delete(event.agentId);
953
+ setSubAgents(new Map(manager.agents));
954
+ }, 3000);
955
+ break;
956
+ }
957
+ // Trigger re-render with updated agents
958
+ setSubAgents(new Map(manager.agents));
959
+ }, []);
960
+ // Queued/staged message - submitted while streaming, will auto-send when ready
961
+ const [queuedMessage, setQueuedMessage] = useState(null);
962
+ // History management - read directly from orchestrator (single source of truth)
963
+ const [orchestratorHistory, setOrchestratorHistory] = useState([]);
964
+ // Sync history from orchestrator (used for real-time updates during streaming)
965
+ // TODO: Wire this into streaming to show real-time tool execution
966
+ const refreshOrchestratorHistory = useCallback(() => {
967
+ if (client) {
968
+ const messages = client.getMessageHistory();
969
+ setOrchestratorHistory([...messages]);
970
+ }
971
+ }, [client]);
972
+ // Initialize orchestrator history on client ready
973
+ useEffect(() => {
974
+ if (client) {
975
+ refreshOrchestratorHistory();
976
+ }
977
+ }, [client, refreshOrchestratorHistory]);
978
+ // Legacy addItem for slash commands that add info/error messages
979
+ // These are UI-only messages, not part of the conversation
980
+ const [uiMessages, setUiMessages] = useState([]);
981
+ const addItem = useCallback((item, timestamp, _isResuming) => {
982
+ const id = `ui-${timestamp}-${Math.random().toString(36).substr(2, 5)}`;
983
+ setUiMessages(prev => [...prev, { ...item, id, timestamp }]);
984
+ return id;
985
+ }, []);
986
+ // Document preview expansion state (for collapsible document previews)
987
+ const [expandedDocuments, setExpandedDocuments] = useState(new Set());
988
+ const [documentContents, setDocumentContents] = useState(new Map());
989
+ const clearHistory = useCallback(() => {
990
+ setUiMessages([]);
991
+ // Note: This doesn't clear orchestrator history - that's persistent
992
+ }, []);
993
+ // Combined history: orchestrator messages + UI messages, sorted by timestamp
994
+ const history = useMemo(() => {
995
+ const orchestratorItems = orchestratorHistory.map(msg => ({
996
+ id: msg.uuid,
997
+ timestamp: new Date(msg.timestamp).getTime(),
998
+ type: 'orchestrator',
999
+ message: msg,
1000
+ }));
1001
+ return [...orchestratorItems, ...uiMessages].sort((a, b) => a.timestamp - b.timestamp);
1002
+ }, [orchestratorHistory, uiMessages]);
1003
+ // Extract user messages from history for input history navigation
1004
+ const userMessages = useMemo(() => {
1005
+ return history
1006
+ .filter((item) => item.type === MessageType.User)
1007
+ .map(item => {
1008
+ if ('userContent' in item && item.userContent) {
1009
+ return typeof item.userContent === 'string'
1010
+ ? item.userContent
1011
+ : item.userContent.text || '';
1012
+ }
1013
+ return '';
1014
+ })
1015
+ .filter(text => text.length > 0);
1016
+ }, [history]);
1017
+ // YOLO mode toggle callback for approval handler
1018
+ const handleYoloToggle = useCallback(() => {
1019
+ setAutoApprove(prev => {
1020
+ const newValue = !prev;
1021
+ // Also update the orchestrator
1022
+ if (client) {
1023
+ if (newValue) {
1024
+ client.enableYoloMode();
1025
+ }
1026
+ else {
1027
+ client.disableYoloMode();
1028
+ }
1029
+ }
1030
+ return newValue;
1031
+ });
1032
+ }, [client]);
1033
+ // React-compatible approval handler
1034
+ const { handler: approvalHandler, pendingApproval, approve, deny, approveAndEnableYolo, } = useReactApprovalHandler(handleYoloToggle);
1035
+ // Initialize orchestrator client
1036
+ useEffect(() => {
1037
+ const initClient = async () => {
1038
+ try {
1039
+ // Priority: CLI arg > persisted model > env var > fallback
1040
+ const persistedModel = loadPersistedModel();
1041
+ const effectiveModelId = modelId || persistedModel || process.env.DEFAULT_MODEL_ID || 'grok-code-fast-1';
1042
+ const orchestratorClient = new OrchestratorClient({
1043
+ mode: 'direct',
1044
+ defaultModelId: effectiveModelId,
1045
+ projectPath: projectPath || process.cwd(),
1046
+ debug,
1047
+ });
1048
+ await orchestratorClient.initialize();
1049
+ // Set our React-compatible approval handler
1050
+ orchestratorClient.setApprovalHandler(approvalHandler);
1051
+ // Set sub-agent event callback for parallel agent display
1052
+ orchestratorClient.setSubAgentEventCallback(handleSubAgentEvent);
1053
+ setClient(orchestratorClient);
1054
+ setInitialized(true);
1055
+ // Get actual model name and capabilities
1056
+ const model = orchestratorClient.getCurrentModel();
1057
+ if (model) {
1058
+ setCurrentModel(model.name || model.id);
1059
+ // Check if model supports reasoning
1060
+ setSupportsReasoning(model.reasoning?.supported === true);
1061
+ setReasoningToggleable(model.reasoning?.toggleable === true);
1062
+ }
1063
+ // Set initial YOLO mode if requested
1064
+ if (initialAutoApprove) {
1065
+ orchestratorClient.enableYoloMode();
1066
+ }
1067
+ }
1068
+ catch (err) {
1069
+ setInitError(err instanceof Error ? err.message : 'Failed to initialize');
1070
+ }
1071
+ };
1072
+ initClient();
1073
+ }, [modelId, projectPath, debug, approvalHandler, initialAutoApprove, handleSubAgentEvent]);
1074
+ // Debug message handler
1075
+ const handleDebugMessage = useCallback((message) => {
1076
+ if (debug) {
1077
+ console.log(`[Debug] ${message}`);
1078
+ }
1079
+ }, [debug]);
1080
+ // Parse slash command into parts
1081
+ const parseSlashCommand = useCallback((cmd) => {
1082
+ const withoutSlash = cmd.slice(1).trim();
1083
+ const parts = withoutSlash.split(/\s+/);
1084
+ const command = parts[0]?.toLowerCase() || '';
1085
+ const subcommand = parts[1]?.toLowerCase();
1086
+ const args = parts.slice(2);
1087
+ return { command, subcommand, args, raw: withoutSlash };
1088
+ }, []);
1089
+ // Slash command handler - expanded with all chalk CLI commands
1090
+ const handleSlashCommand = useCallback(async (cmd) => {
1091
+ const { command, subcommand, args, raw } = parseSlashCommand(cmd);
1092
+ switch (command) {
1093
+ case 'help':
1094
+ case '?':
1095
+ setShowHelp(true);
1096
+ return { handled: true };
1097
+ case 'clear':
1098
+ clearHistory();
1099
+ addItem({ type: MessageType.Info, infoContent: 'History cleared' }, Date.now());
1100
+ return { handled: true };
1101
+ case 'exit':
1102
+ case 'quit':
1103
+ case 'q':
1104
+ exit();
1105
+ return { handled: true };
1106
+ case 'yolo': {
1107
+ const isDisabling = args[0] === 'off';
1108
+ const isYoloActive = client?.isYoloModeActive() || false;
1109
+ if (isDisabling || isYoloActive) {
1110
+ client?.disableYoloMode();
1111
+ setAutoApprove(false);
1112
+ addItem({ type: MessageType.Info, infoContent: 'YOLO mode disabled. Interactive approval restored.' }, Date.now());
1113
+ }
1114
+ else {
1115
+ client?.enableYoloMode();
1116
+ setAutoApprove(true);
1117
+ addItem({ type: MessageType.Info, infoContent: ' YOLO MODE ACTIVATED - All tools auto-approved!' }, Date.now());
1118
+ }
1119
+ return { handled: true };
1120
+ }
1121
+ case 'thinking':
1122
+ setShowThinking(prev => !prev);
1123
+ addItem({
1124
+ type: MessageType.Info,
1125
+ infoContent: `Show thinking: ${!showThinking ? 'ON' : 'OFF'}`,
1126
+ }, Date.now());
1127
+ return { handled: true };
1128
+ case 'theme': {
1129
+ // /theme - show current theme
1130
+ // /theme list - list all themes
1131
+ // /theme set <name> - set theme
1132
+ // /theme picker - open interactive theme picker
1133
+ if (!subcommand) {
1134
+ const themeDef = getThemeDefinition(currentTheme);
1135
+ addItem({
1136
+ type: MessageType.Info,
1137
+ infoContent: `Current theme: ${themeDef?.name || currentTheme}\nTip: Use /theme picker for interactive selection`,
1138
+ }, Date.now());
1139
+ return { handled: true };
1140
+ }
1141
+ if (subcommand === 'picker' || subcommand === 'pick') {
1142
+ setShowThemePicker(true);
1143
+ return { handled: true };
1144
+ }
1145
+ if (subcommand === 'list') {
1146
+ const themes = getThemeNames();
1147
+ const themeList = themes.map(name => {
1148
+ const def = getThemeDefinition(name);
1149
+ const marker = name === currentTheme ? '●' : '○';
1150
+ return ` ${marker} ${name} - ${def?.name || name}`;
1151
+ }).join('\n');
1152
+ addItem({
1153
+ type: MessageType.Info,
1154
+ infoContent: `Available themes:\n${themeList}`,
1155
+ }, Date.now());
1156
+ return { handled: true };
1157
+ }
1158
+ if (subcommand === 'set') {
1159
+ const themeName = args[0];
1160
+ if (!themeName) {
1161
+ addItem({
1162
+ type: MessageType.Error,
1163
+ errorContent: 'Usage: /theme set <theme-name>',
1164
+ }, Date.now());
1165
+ return { handled: true };
1166
+ }
1167
+ if (setTheme(themeName)) {
1168
+ setCurrentTheme(themeName);
1169
+ const themeDef = getThemeDefinition(themeName);
1170
+ addItem({
1171
+ type: MessageType.Info,
1172
+ infoContent: `✓ Theme set to: ${themeDef?.name || themeName}`,
1173
+ }, Date.now());
1174
+ }
1175
+ else {
1176
+ const available = getThemeNames().join(', ');
1177
+ addItem({
1178
+ type: MessageType.Error,
1179
+ errorContent: `Unknown theme: ${themeName}\nAvailable: ${available}`,
1180
+ }, Date.now());
1181
+ }
1182
+ return { handled: true };
1183
+ }
1184
+ addItem({
1185
+ type: MessageType.Error,
1186
+ errorContent: 'Usage: /theme [list | set <name> | picker]',
1187
+ }, Date.now());
1188
+ return { handled: true };
1189
+ }
1190
+ case 'm':
1191
+ case 'model': {
1192
+ // /model picker - open interactive model picker
1193
+ if (subcommand === 'picker' || subcommand === 'pick') {
1194
+ if (!client) {
1195
+ addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
1196
+ return { handled: true };
1197
+ }
1198
+ // Load models and open picker
1199
+ setModelsLoading(true);
1200
+ setShowModelPicker(true);
1201
+ try {
1202
+ const models = await client.listModels();
1203
+ const modelDisplayInfos = models.map((m) => ({
1204
+ id: m.id,
1205
+ displayName: m.displayName || m.id,
1206
+ provider: m.owned_by || m.provider || 'Unknown',
1207
+ contextWindow: m.contextWindow,
1208
+ supportsReasoning: m.reasoning?.supported === true,
1209
+ reasoningToggleable: m.reasoning?.toggleable === true,
1210
+ inputCost: m.inputCostPer1M,
1211
+ outputCost: m.outputCostPer1M,
1212
+ }));
1213
+ setAvailableModels(modelDisplayInfos);
1214
+ }
1215
+ catch (error) {
1216
+ addItem({ type: MessageType.Error, errorContent: `Error loading models: ${error.message}` }, Date.now());
1217
+ setShowModelPicker(false);
1218
+ }
1219
+ finally {
1220
+ setModelsLoading(false);
1221
+ }
1222
+ return { handled: true };
1223
+ }
1224
+ // /model alone shows current model
1225
+ addItem({
1226
+ type: MessageType.Info,
1227
+ infoContent: `Current model: ${currentModel}\nTip: Use /model picker for interactive selection`,
1228
+ }, Date.now());
1229
+ return { handled: true };
1230
+ }
1231
+ case 'session':
1232
+ if (subcommand === 'list') {
1233
+ // /session list
1234
+ if (!client) {
1235
+ addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
1236
+ return { handled: true };
1237
+ }
1238
+ try {
1239
+ const result = await client.listSessions();
1240
+ const sessions = result.sessions;
1241
+ if (sessions && sessions.length > 0) {
1242
+ const sessionList = sessions.slice(0, 10).map((s) => ` ${s.sessionId?.slice(0, 8) || s.id?.slice(0, 8)} - ${s.messageCount || 0} messages`).join('\n');
1243
+ addItem({
1244
+ type: MessageType.Info,
1245
+ infoContent: `Sessions:\n${sessionList}${sessions.length > 10 ? `\n ... and ${sessions.length - 10} more` : ''}`,
1246
+ }, Date.now());
1247
+ }
1248
+ else {
1249
+ addItem({ type: MessageType.Info, infoContent: 'No sessions found' }, Date.now());
1250
+ }
1251
+ }
1252
+ catch (error) {
1253
+ addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
1254
+ }
1255
+ return { handled: true };
1256
+ }
1257
+ if (subcommand === 'checkpoint') {
1258
+ // /session checkpoint [name]
1259
+ const checkpointName = args.join(' ') || `Checkpoint ${new Date().toLocaleString()}`;
1260
+ if (!client) {
1261
+ addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
1262
+ return { handled: true };
1263
+ }
1264
+ try {
1265
+ const checkpoint = await client.createCheckpoint(checkpointName);
1266
+ addItem({
1267
+ type: MessageType.Info,
1268
+ infoContent: `✓ Checkpoint created: ${checkpoint.id?.slice(0, 8) || 'unknown'} - "${checkpointName}"`,
1269
+ }, Date.now());
1270
+ }
1271
+ catch (error) {
1272
+ addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
1273
+ }
1274
+ return { handled: true };
1275
+ }
1276
+ addItem({ type: MessageType.Info, infoContent: 'Available: /session list, /session checkpoint [name]' }, Date.now());
1277
+ return { handled: true };
1278
+ case 'cache':
1279
+ if (subcommand === 'metrics') {
1280
+ // /cache metrics
1281
+ if (!client) {
1282
+ addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
1283
+ return { handled: true };
1284
+ }
1285
+ try {
1286
+ const result = await client.getCacheMetrics();
1287
+ if (result?.metrics) {
1288
+ const m = result.metrics;
1289
+ const hitRate = m.overallCacheHitRate ? `${(m.overallCacheHitRate * 100).toFixed(1)}%` : '0%';
1290
+ const costSavings = m.overallCostSavingsRatio ? `${(m.overallCostSavingsRatio * 100).toFixed(1)}%` : '0%';
1291
+ const lines = [
1292
+ `Cache Metrics:`,
1293
+ ` Total Requests: ${m.requestCount || 0}`,
1294
+ ` Requests with Cache Hits: ${m.requestsWithCacheHits || 0}`,
1295
+ '',
1296
+ ` Total Input Tokens: ${(m.totalInputTokens || 0).toLocaleString()}`,
1297
+ ` Cache Creation: ${(m.totalCacheCreationTokens || 0).toLocaleString()}`,
1298
+ ` Cache Reads: ${(m.totalCacheReadTokens || 0).toLocaleString()}`,
1299
+ ` Uncached: ${(m.totalUncachedInputTokens || 0).toLocaleString()}`,
1300
+ '',
1301
+ ` Cache Hit Rate: ${hitRate}`,
1302
+ ` Est. Cost Savings: ${costSavings}`
1303
+ ];
1304
+ // Show provider breakdown if available
1305
+ if (Object.keys(m.byProvider || {}).length > 0) {
1306
+ lines.push('');
1307
+ lines.push(' By Provider:');
1308
+ for (const [provider, pm] of Object.entries(m.byProvider)) {
1309
+ const providerHitRate = pm.cacheHitRate ? `${(pm.cacheHitRate * 100).toFixed(1)}%` : '0%';
1310
+ lines.push(` ${provider}: ${pm.cacheReadTokens.toLocaleString()} cached (${providerHitRate})`);
1311
+ }
1312
+ }
1313
+ addItem({ type: MessageType.Info, infoContent: lines.join('\n') }, Date.now());
1314
+ }
1315
+ else {
1316
+ addItem({
1317
+ type: MessageType.Info,
1318
+ infoContent: 'No cache metrics available\n[i] Make some API requests with caching enabled'
1319
+ }, Date.now());
1320
+ }
1321
+ }
1322
+ catch (error) {
1323
+ addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
1324
+ }
1325
+ return { handled: true };
1326
+ }
1327
+ if (subcommand === 'report') {
1328
+ // /cache report
1329
+ if (!client) {
1330
+ addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
1331
+ return { handled: true };
1332
+ }
1333
+ try {
1334
+ const report = await client.getCacheReport();
1335
+ addItem({ type: MessageType.Info, infoContent: report || 'No cache report available' }, Date.now());
1336
+ }
1337
+ catch (error) {
1338
+ addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
1339
+ }
1340
+ return { handled: true };
1341
+ }
1342
+ addItem({ type: MessageType.Info, infoContent: 'Available: /cache metrics, /cache report' }, Date.now());
1343
+ return { handled: true };
1344
+ case 'mcp':
1345
+ if (subcommand === 'list') {
1346
+ // /mcp list
1347
+ if (!client) {
1348
+ addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
1349
+ return { handled: true };
1350
+ }
1351
+ try {
1352
+ const result = await client.listMcpServers();
1353
+ if (!result.enabled) {
1354
+ addItem({ type: MessageType.Info, infoContent: 'MCP is not enabled' }, Date.now());
1355
+ return { handled: true };
1356
+ }
1357
+ const servers = result.servers;
1358
+ if (servers && servers.length > 0) {
1359
+ const serverList = servers.map((s) => ` ${s.name} [${s.enabled ? 'enabled' : 'disabled'}]${s.description ? ` - ${s.description}` : ''}`).join('\n');
1360
+ addItem({ type: MessageType.Info, infoContent: `MCP Servers:\n${serverList}` }, Date.now());
1361
+ }
1362
+ else {
1363
+ addItem({ type: MessageType.Info, infoContent: 'No MCP servers configured' }, Date.now());
1364
+ }
1365
+ }
1366
+ catch (error) {
1367
+ addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
1368
+ }
1369
+ return { handled: true };
1370
+ }
1371
+ if (subcommand === 'enable') {
1372
+ const serverName = args[0];
1373
+ if (!serverName) {
1374
+ addItem({ type: MessageType.Error, errorContent: 'Usage: /mcp enable <server-name>' }, Date.now());
1375
+ return { handled: true };
1376
+ }
1377
+ if (!client) {
1378
+ addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
1379
+ return { handled: true };
1380
+ }
1381
+ try {
1382
+ await client.enableMCPServer(serverName);
1383
+ addItem({ type: MessageType.Info, infoContent: `✓ Enabled MCP server: ${serverName}` }, Date.now());
1384
+ }
1385
+ catch (error) {
1386
+ addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
1387
+ }
1388
+ return { handled: true };
1389
+ }
1390
+ if (subcommand === 'disable') {
1391
+ const serverName = args[0];
1392
+ if (!serverName) {
1393
+ addItem({ type: MessageType.Error, errorContent: 'Usage: /mcp disable <server-name>' }, Date.now());
1394
+ return { handled: true };
1395
+ }
1396
+ if (!client) {
1397
+ addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
1398
+ return { handled: true };
1399
+ }
1400
+ try {
1401
+ await client.disableMCPServer(serverName);
1402
+ addItem({ type: MessageType.Info, infoContent: `✓ Disabled MCP server: ${serverName}` }, Date.now());
1403
+ }
1404
+ catch (error) {
1405
+ addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
1406
+ }
1407
+ return { handled: true };
1408
+ }
1409
+ addItem({ type: MessageType.Info, infoContent: 'Available: /mcp list, /mcp enable <name>, /mcp disable <name>' }, Date.now());
1410
+ return { handled: true };
1411
+ case 'tools':
1412
+ if (subcommand === 'list') {
1413
+ // /tools list [--grouped]
1414
+ const grouped = args.includes('--grouped');
1415
+ if (!client) {
1416
+ addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
1417
+ return { handled: true };
1418
+ }
1419
+ try {
1420
+ const result = await client.listTools(grouped);
1421
+ if (grouped && result.grouped) {
1422
+ let output = 'Available Tools:\n';
1423
+ for (const [category, tools] of Object.entries(result.grouped)) {
1424
+ output += `\n ${category}:\n`;
1425
+ for (const tool of tools) {
1426
+ output += ` ${tool.name}${tool.description ? ` - ${tool.description.slice(0, 50)}...` : ''}\n`;
1427
+ }
1428
+ }
1429
+ addItem({ type: MessageType.Info, infoContent: output }, Date.now());
1430
+ }
1431
+ else if (result.tools) {
1432
+ const toolList = result.tools.slice(0, 20).map((t) => ` ${t.name}${t.description ? ` - ${t.description.slice(0, 40)}...` : ''}`).join('\n');
1433
+ addItem({
1434
+ type: MessageType.Info,
1435
+ infoContent: `Available Tools (${result.totalCount}):\n${toolList}${result.tools.length > 20 ? '\n ...' : ''}`,
1436
+ }, Date.now());
1437
+ }
1438
+ }
1439
+ catch (error) {
1440
+ addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
1441
+ }
1442
+ return { handled: true };
1443
+ }
1444
+ if (subcommand === 'info') {
1445
+ const toolName = args[0];
1446
+ if (!toolName) {
1447
+ addItem({ type: MessageType.Error, errorContent: 'Usage: /tools info <tool-name>' }, Date.now());
1448
+ return { handled: true };
1449
+ }
1450
+ if (!client) {
1451
+ addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
1452
+ return { handled: true };
1453
+ }
1454
+ try {
1455
+ const toolInfo = await client.getToolInfo(toolName);
1456
+ if (toolInfo) {
1457
+ const info = [
1458
+ `Tool: ${toolInfo.name}`,
1459
+ toolInfo.description ? `Description: ${toolInfo.description}` : '',
1460
+ toolInfo.category ? `Category: ${toolInfo.category}` : '',
1461
+ ].filter(Boolean).join('\n ');
1462
+ addItem({ type: MessageType.Info, infoContent: info }, Date.now());
1463
+ }
1464
+ }
1465
+ catch (error) {
1466
+ addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
1467
+ }
1468
+ return { handled: true };
1469
+ }
1470
+ addItem({ type: MessageType.Info, infoContent: 'Available: /tools list [--grouped], /tools info <name>' }, Date.now());
1471
+ return { handled: true };
1472
+ case 'init':
1473
+ // /init - Scan project, then let the model write CORTEX.md
1474
+ if (!client) {
1475
+ addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
1476
+ return { handled: true };
1477
+ }
1478
+ try {
1479
+ addItem({ type: MessageType.Info, infoContent: '[INIT] Scanning project...' }, Date.now());
1480
+ const { InitCortexContext } = await import('@nexus-cortex/core');
1481
+ const isGlobal = args.includes('--global');
1482
+ const depthIndex = args.indexOf('--depth');
1483
+ const depthArg = depthIndex >= 0 ? args[depthIndex + 1] : undefined;
1484
+ const scan = await InitCortexContext.scan(projectPath || process.cwd(), {
1485
+ scope: isGlobal ? 'global' : 'auto',
1486
+ max_depth: depthArg ? parseInt(depthArg, 10) : undefined,
1487
+ });
1488
+ const prompt = InitCortexContext.formatScanAsPrompt(scan);
1489
+ addItem({ type: MessageType.Info, infoContent: `[INIT] Scan complete. Generating CORTEX.md...` }, Date.now());
1490
+ return { type: 'submit_prompt', content: prompt };
1491
+ }
1492
+ catch (error) {
1493
+ addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
1494
+ }
1495
+ return { handled: true };
1496
+ case 'continue':
1497
+ // /continue - Interactive session picker
1498
+ if (!client) {
1499
+ addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
1500
+ return { handled: true };
1501
+ }
1502
+ try {
1503
+ setSessionsLoading(true);
1504
+ setShowSessionPicker(true);
1505
+ const { resolveContext, JSONLHistoryStore } = await import('@nexus-cortex/core');
1506
+ const context = resolveContext({ cwd: projectPath || process.cwd() });
1507
+ const historyStore = new JSONLHistoryStore({ baseDir: context.sessionsDir });
1508
+ const sessions = await historyStore.listSessions();
1509
+ // Sort by most recent
1510
+ sessions.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
1511
+ // Fetch first user message for each session (for preview)
1512
+ const sessionDisplayInfos = await Promise.all(sessions.slice(0, 50).map(async (s) => {
1513
+ let firstUserMessage;
1514
+ try {
1515
+ const messages = await historyStore.loadSession(s.sessionId);
1516
+ // Find first user message - messages have type: 'user'|'assistant'|etc and message object
1517
+ const firstUser = messages.find((m) => m.type === 'user');
1518
+ if (firstUser && firstUser.message) {
1519
+ const msg = firstUser.message;
1520
+ if (typeof msg.content === 'string') {
1521
+ firstUserMessage = msg.content.replace(/\n/g, ' ').trim();
1522
+ }
1523
+ else if (Array.isArray(msg.content)) {
1524
+ const textPart = msg.content.find((p) => p.type === 'text');
1525
+ if (textPart && textPart.text) {
1526
+ firstUserMessage = textPart.text.replace(/\n/g, ' ').trim();
1527
+ }
1528
+ }
1529
+ }
1530
+ }
1531
+ catch {
1532
+ // Ignore errors loading individual sessions
1533
+ }
1534
+ return {
1535
+ sessionId: s.sessionId,
1536
+ shortId: s.sessionId.slice(0, 8),
1537
+ lastModified: s.lastModified,
1538
+ messageCount: s.messageCount,
1539
+ model: s.metadata?.currentModel,
1540
+ title: s.metadata?.title,
1541
+ firstUserMessage,
1542
+ age: formatSessionAge(s.lastModified),
1543
+ };
1544
+ }));
1545
+ setAvailableSessions(sessionDisplayInfos);
1546
+ setSessionsLoading(false);
1547
+ // Store sessions for /resume command (backwards compatibility)
1548
+ client._cachedSessions = sessions.slice(0, 50);
1549
+ }
1550
+ catch (error) {
1551
+ setShowSessionPicker(false);
1552
+ setSessionsLoading(false);
1553
+ addItem({ type: MessageType.Error, errorContent: `Error loading sessions: ${error.message}` }, Date.now());
1554
+ }
1555
+ return { handled: true };
1556
+ case 'resume': {
1557
+ // /resume <number|session-id>
1558
+ const selector = raw.split(/\s+/)[1];
1559
+ if (!selector) {
1560
+ addItem({ type: MessageType.Error, errorContent: 'Usage: /resume <number> or /resume <session-id>' }, Date.now());
1561
+ return { handled: true };
1562
+ }
1563
+ if (!client) {
1564
+ addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
1565
+ return { handled: true };
1566
+ }
1567
+ try {
1568
+ let sessionId;
1569
+ const cachedSessions = client._cachedSessions;
1570
+ // Check if selector is a number (index) or session ID
1571
+ const num = parseInt(selector, 10);
1572
+ if (!isNaN(num) && cachedSessions && num >= 1 && num <= cachedSessions.length) {
1573
+ sessionId = cachedSessions[num - 1].sessionId;
1574
+ }
1575
+ else {
1576
+ // Treat as session ID (or prefix)
1577
+ sessionId = selector;
1578
+ }
1579
+ addItem({ type: MessageType.Info, infoContent: `Resuming session ${sessionId.slice(0, 8)}...` }, Date.now());
1580
+ await client.resumeSession(sessionId);
1581
+ // Refresh history to display the loaded session's messages
1582
+ refreshOrchestratorHistory();
1583
+ addItem({ type: MessageType.Info, infoContent: `✓ Session resumed. Continue your conversation.` }, Date.now());
1584
+ }
1585
+ catch (error) {
1586
+ addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
1587
+ }
1588
+ return { handled: true };
1589
+ }
1590
+ case 'debug': {
1591
+ const newDebugState = !(client?.isDebugActive() ?? process.env.DEBUG === 'true');
1592
+ if (client) {
1593
+ client.setDebug(newDebugState);
1594
+ }
1595
+ else {
1596
+ process.env.DEBUG = newDebugState ? 'true' : 'false';
1597
+ }
1598
+ addItem({
1599
+ type: MessageType.Info,
1600
+ infoContent: `Debug mode: ${newDebugState ? 'ON' : 'OFF'}`,
1601
+ }, Date.now());
1602
+ return { handled: true };
1603
+ }
1604
+ case 'config': {
1605
+ // /config - show summary
1606
+ // /config list - list all config keys
1607
+ // /config get <key> - get specific value
1608
+ // /config set <key> <value> - set value
1609
+ if (!client) {
1610
+ addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
1611
+ return { handled: true };
1612
+ }
1613
+ if (!subcommand) {
1614
+ setShowConfigMenu(true);
1615
+ return { handled: true };
1616
+ }
1617
+ if (subcommand === 'list') {
1618
+ try {
1619
+ const keys = await client.listConfigKeys();
1620
+ const keyList = keys.map(k => ` ${k}`).join('\n');
1621
+ addItem({
1622
+ type: MessageType.Info,
1623
+ infoContent: `Configuration Keys:\n${keyList}`,
1624
+ }, Date.now());
1625
+ }
1626
+ catch (error) {
1627
+ addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
1628
+ }
1629
+ return { handled: true };
1630
+ }
1631
+ if (subcommand === 'get') {
1632
+ const key = args[0];
1633
+ if (!key) {
1634
+ addItem({ type: MessageType.Error, errorContent: 'Usage: /config get <key>' }, Date.now());
1635
+ return { handled: true };
1636
+ }
1637
+ try {
1638
+ const value = await client.getConfig(key);
1639
+ // Mask API keys
1640
+ const displayValue = key.includes('API_KEY') && value ? '***configured***' : value;
1641
+ addItem({
1642
+ type: MessageType.Info,
1643
+ infoContent: `${key} = ${displayValue}`,
1644
+ }, Date.now());
1645
+ }
1646
+ catch (error) {
1647
+ addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
1648
+ }
1649
+ return { handled: true };
1650
+ }
1651
+ if (subcommand === 'set') {
1652
+ const key = args[0];
1653
+ const value = args.slice(1).join(' ');
1654
+ if (!key || !value) {
1655
+ setShowConfigMenu(true);
1656
+ return { handled: true };
1657
+ }
1658
+ try {
1659
+ await client.setConfig(key, value);
1660
+ const { getRuntimeConfigEntry, isLiveToggleable } = await import('@nexus-cortex/core');
1661
+ const entry = getRuntimeConfigEntry(key);
1662
+ if (entry?.tier === 'config' && entry.mapper) {
1663
+ client.updateRuntimeConfig(entry.mapper(value));
1664
+ }
1665
+ addItem({
1666
+ type: MessageType.Info,
1667
+ infoContent: isLiveToggleable(key)
1668
+ ? `[OK] ${key} updated (live)`
1669
+ : `[OK] ${key} updated (restart required)`,
1670
+ }, Date.now());
1671
+ }
1672
+ catch (error) {
1673
+ addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
1674
+ }
1675
+ return { handled: true };
1676
+ }
1677
+ if (subcommand === 'reset') {
1678
+ try {
1679
+ const { configReset } = await import('@nexus-cortex/cli/dist/commands/config/reset.js');
1680
+ await configReset();
1681
+ addItem({
1682
+ type: MessageType.Info,
1683
+ infoContent: '[OK] Configuration reset to benchmark-proven optimal defaults (API keys preserved). Restart recommended.',
1684
+ }, Date.now());
1685
+ }
1686
+ catch (error) {
1687
+ addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
1688
+ }
1689
+ return { handled: true };
1690
+ }
1691
+ addItem({
1692
+ type: MessageType.Info,
1693
+ infoContent: 'Available: /config, /config list, /config get <key>, /config set <key> <value>, /config reset',
1694
+ }, Date.now());
1695
+ return { handled: true };
1696
+ }
1697
+ case 'system-message':
1698
+ case 'sysmsg': {
1699
+ // /system-message - open interactive manager (default)
1700
+ // /system-message list - quick list of system messages
1701
+ // /system-message view <filename> - view content
1702
+ if (!client) {
1703
+ addItem({ type: MessageType.Error, errorContent: 'Client not initialized' }, Date.now());
1704
+ return { handled: true };
1705
+ }
1706
+ // Interactive manager mode (default or 'manage' subcommand)
1707
+ if (!subcommand || subcommand === 'manage') {
1708
+ try {
1709
+ // Get context-aware paths using ContextResolver
1710
+ const { resolveContext } = await import('@nexus-cortex/core/utils/ContextResolver.js');
1711
+ const context = resolveContext({ cwd: projectPath, debug });
1712
+ // Builtin directory: relative to package location
1713
+ const { fileURLToPath } = await import('url');
1714
+ const { dirname, join } = await import('path');
1715
+ // packages/cli/dist/ink-ui/CortexApp.js -> packages/core/dist/system-messages
1716
+ const currentDir = dirname(fileURLToPath(import.meta.url));
1717
+ const cliDistDir = dirname(currentDir); // packages/cli/dist
1718
+ const packagesDir = dirname(dirname(cliDistDir)); // packages/
1719
+ const builtinDir = join(packagesDir, 'core', 'dist', 'system-messages');
1720
+ // Initialize store
1721
+ const store = new SystemMessageStore({
1722
+ builtinDir,
1723
+ runtimeDir: context.systemMessagesDir,
1724
+ enableWatching: false, // Don't watch in dialog mode
1725
+ debug: debug || false,
1726
+ });
1727
+ await store.initialize();
1728
+ setSystemMessageStore(store);
1729
+ setShowSystemMessageManager(true);
1730
+ addItem({
1731
+ type: MessageType.Info,
1732
+ infoContent: ' Opening System Message Manager...',
1733
+ }, Date.now());
1734
+ }
1735
+ catch (error) {
1736
+ addItem({ type: MessageType.Error, errorContent: `Error opening manager: ${error.message}` }, Date.now());
1737
+ }
1738
+ return { handled: true };
1739
+ }
1740
+ if (subcommand === 'list') {
1741
+ try {
1742
+ const messages = await client.listSystemMessages();
1743
+ if (messages.length === 0) {
1744
+ addItem({
1745
+ type: MessageType.Info,
1746
+ infoContent: 'No system messages found.\nCreate files in .cortex/system-messages/ (e.g., 01-base.md)\n\nUse /system-message to open interactive manager',
1747
+ }, Date.now());
1748
+ }
1749
+ else {
1750
+ const msgList = messages.map(m => ` [${String(m.priority).padStart(2, '0')}] ${m.filename}`).join('\n');
1751
+ addItem({
1752
+ type: MessageType.Info,
1753
+ infoContent: `System Messages:\n${msgList}\n\nUse /system-message to manage interactively`,
1754
+ }, Date.now());
1755
+ }
1756
+ }
1757
+ catch (error) {
1758
+ addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
1759
+ }
1760
+ return { handled: true };
1761
+ }
1762
+ if (subcommand === 'view') {
1763
+ const filename = args[0];
1764
+ if (!filename) {
1765
+ addItem({ type: MessageType.Error, errorContent: 'Usage: /system-message view <filename>' }, Date.now());
1766
+ return { handled: true };
1767
+ }
1768
+ try {
1769
+ const content = await client.getSystemMessageContent(filename);
1770
+ // Truncate if too long
1771
+ const maxLength = 2000;
1772
+ const displayContent = content.length > maxLength
1773
+ ? content.slice(0, maxLength) + '\n... (truncated)'
1774
+ : content;
1775
+ addItem({
1776
+ type: MessageType.Info,
1777
+ infoContent: `─── ${filename} ───\n${displayContent}`,
1778
+ }, Date.now());
1779
+ }
1780
+ catch (error) {
1781
+ addItem({ type: MessageType.Error, errorContent: `Error: ${error.message}` }, Date.now());
1782
+ }
1783
+ return { handled: true };
1784
+ }
1785
+ addItem({
1786
+ type: MessageType.Info,
1787
+ infoContent: 'Available:\n /system-message - Interactive manager\n /system-message list - Quick list\n /system-message view <filename>',
1788
+ }, Date.now());
1789
+ return { handled: true };
1790
+ }
1791
+ case 'mentorship': {
1792
+ // Direct mentorship commands
1793
+ // Find installation root - prefer env var, then use module location
1794
+ const getInstallationRoot = () => {
1795
+ const root = process.env.CORTEX_ROOT;
1796
+ if (root)
1797
+ return root;
1798
+ return CLI_INSTALLATION_ROOT;
1799
+ };
1800
+ const mentorshipSvc = new MentorshipConfigService(getInstallationRoot());
1801
+ // /mentorship or /mentorship status - show status immediately
1802
+ if (!subcommand || subcommand === 'status') {
1803
+ const summary = mentorshipSvc.getSummary();
1804
+ const statusIcon = summary.status === 'enabled' ? '[ON]' : '[OFF]';
1805
+ addItem({
1806
+ type: MessageType.Info,
1807
+ infoContent: [
1808
+ `Mentorship: ${statusIcon} ${summary.status.toUpperCase()}`,
1809
+ `Helper: ${summary.helperModel}`,
1810
+ `Triggers: ${summary.triggers.join(', ')}`,
1811
+ '',
1812
+ '/mentorship enable|disable|config',
1813
+ ].join('\n'),
1814
+ }, Date.now());
1815
+ return { handled: true };
1816
+ }
1817
+ if (subcommand === 'enable') {
1818
+ await mentorshipSvc.quickEnable();
1819
+ client?.updateRuntimeConfig({
1820
+ reactiveMentorship: {
1821
+ enabled: true,
1822
+ triggerOnError: true,
1823
+ errorSeverityThreshold: 'medium',
1824
+ enableKeywords: false,
1825
+ patternDetection: true,
1826
+ },
1827
+ });
1828
+ addItem({
1829
+ type: MessageType.Info,
1830
+ infoContent: '[OK] Mentorship enabled',
1831
+ }, Date.now());
1832
+ return { handled: true };
1833
+ }
1834
+ if (subcommand === 'disable') {
1835
+ await mentorshipSvc.quickDisable();
1836
+ client?.updateRuntimeConfig({
1837
+ reactiveMentorship: { enabled: false, triggerOnError: false, errorSeverityThreshold: 'medium', enableKeywords: false },
1838
+ });
1839
+ addItem({
1840
+ type: MessageType.Info,
1841
+ infoContent: 'Mentorship disabled',
1842
+ }, Date.now());
1843
+ return { handled: true };
1844
+ }
1845
+ // /mentorship config - show interactive config menu
1846
+ if (subcommand === 'config') {
1847
+ const menuDef = mentorshipSvc.getMenuDefinition();
1848
+ const config = mentorshipSvc.getConfig();
1849
+ // Extract initial values from config matching menu item keys
1850
+ const initialValues = {};
1851
+ for (const section of menuDef.sections) {
1852
+ for (const item of section.items) {
1853
+ // Map menu item keys (ENV var names) to config values
1854
+ const configKeyMap = {
1855
+ 'MENTORSHIP_ENABLED': 'enabled',
1856
+ 'MENTORSHIP_TRIGGER_ON_ERROR': 'triggerOnError',
1857
+ 'MENTORSHIP_ERROR_THRESHOLD': 'errorThreshold',
1858
+ 'MENTORSHIP_KEYWORDS_ENABLED': 'keywordsEnabled',
1859
+ 'MENTORSHIP_CUSTOM_KEYWORDS': 'customKeywords',
1860
+ 'MENTORSHIP_HELPER_MODEL': 'helperModel',
1861
+ 'MENTORSHIP_TURN_BASED_ENABLED': 'turnBasedEnabled',
1862
+ 'MENTORSHIP_TURN_INTERVAL': 'turnInterval',
1863
+ 'MENTORSHIP_INTERLEAVED_THINKING': 'interleavedThinking',
1864
+ 'MENTORSHIP_PATTERN_DETECTION': 'patternDetection',
1865
+ 'MENTORSHIP_PATTERN_THRESHOLD': 'patternThreshold',
1866
+ };
1867
+ const configKey = configKeyMap[item.key];
1868
+ if (configKey) {
1869
+ initialValues[item.key] = config[configKey];
1870
+ }
1871
+ }
1872
+ }
1873
+ // Store service and show menu
1874
+ setMentorshipService(mentorshipSvc);
1875
+ setMentorshipMenuDef(menuDef);
1876
+ setMentorshipInitialValues(initialValues);
1877
+ setShowMentorshipConfig(true);
1878
+ return { handled: true };
1879
+ }
1880
+ addItem({
1881
+ type: MessageType.Error,
1882
+ errorContent: 'Usage: /mentorship [status|enable|disable|config]',
1883
+ }, Date.now());
1884
+ return { handled: true };
1885
+ }
1886
+ case 'about': {
1887
+ const { renderSplashScreen: renderFullSplash } = await import('../ui/SplashScreen.js');
1888
+ addItem({ type: MessageType.Info, infoContent: renderFullSplash(undefined, 'neon') }, Date.now());
1889
+ return { handled: true };
1890
+ }
1891
+ case 'agent': {
1892
+ const { AgentStore } = await import('@nexus-cortex/core');
1893
+ const agentDir = path.join(projectPath || process.cwd(), '.cortex', 'agents');
1894
+ const agentStore = new AgentStore({ projectDir: agentDir, enableWatching: false });
1895
+ await agentStore.initialize();
1896
+ try {
1897
+ const agents = agentStore.getAll();
1898
+ if (subcommand === 'list' || !subcommand) {
1899
+ if (agents.length === 0) {
1900
+ addItem({ type: MessageType.Info, infoContent: 'No agents found. Create one in .cortex/agents/' }, Date.now());
1901
+ }
1902
+ else {
1903
+ const lines = agents.map((a) => {
1904
+ const loc = 'location' in a ? a.location === 'project' ? '[P]' : '[G]' : '';
1905
+ const toolsInfo = a.tools === 'all' ? 'all' : Array.isArray(a.tools) ? `${a.tools.length} tools` : '';
1906
+ return ` ${loc} ${a.name} — ${a.description || '(no description)'} (${a.model || 'default'}, ${toolsInfo})`;
1907
+ });
1908
+ addItem({ type: MessageType.Info, infoContent: `Agents (${agents.length}):\n${lines.join('\n')}` }, Date.now());
1909
+ }
1910
+ }
1911
+ else if (subcommand === 'info' && args.length > 0) {
1912
+ const agent = agentStore.getAgent(args[0]);
1913
+ if (agent) {
1914
+ const toolsList = agent.tools === 'all' ? 'all' : Array.isArray(agent.tools) ? agent.tools.join(', ') : '';
1915
+ const details = [
1916
+ `Name: ${agent.name}`,
1917
+ `Description: ${agent.description || '(none)'}`,
1918
+ `Model: ${agent.model || '(default)'}`,
1919
+ `Tools: ${toolsList}`,
1920
+ 'location' in agent ? `Location: ${agent.location}` : null,
1921
+ 'filePath' in agent ? `File: ${agent.filePath}` : null,
1922
+ ].filter(Boolean).join('\n');
1923
+ addItem({ type: MessageType.Info, infoContent: details }, Date.now());
1924
+ }
1925
+ else {
1926
+ addItem({ type: MessageType.Error, errorContent: `Agent "${args[0]}" not found` }, Date.now());
1927
+ }
1928
+ }
1929
+ else {
1930
+ addItem({ type: MessageType.Info, infoContent: 'Usage: /agent [list|info <name>]' }, Date.now());
1931
+ }
1932
+ }
1933
+ finally {
1934
+ await agentStore.destroy();
1935
+ }
1936
+ return { handled: true };
1937
+ }
1938
+ default:
1939
+ addItem({
1940
+ type: MessageType.Error,
1941
+ errorContent: `Unknown command: /${command}. Type /help for available commands.`,
1942
+ }, Date.now());
1943
+ return { handled: true };
1944
+ }
1945
+ }, [parseSlashCommand, clearHistory, addItem, exit, autoApprove, showThinking, currentModel, debug, client, projectPath]);
1946
+ // Cancel handler
1947
+ const handleCancelSubmit = useCallback((_shouldRestorePrompt) => {
1948
+ handleDebugMessage('Request cancelled');
1949
+ }, [handleDebugMessage]);
1950
+ // Streaming hook
1951
+ const { streamingState, submitQuery, initError: _streamInitError, pendingHistoryItems: _pendingHistoryItems, thought: _thought, cancelOngoingRequest, pendingToolCalls: _pendingToolCalls, pendingToolInputArgs: _pendingToolInputArgs, handleApprovalModeChange, streamEvents, turnSummary, turnUsage: _turnUsage, nextActionPrediction, streamStartTime, } = useCortexStream(client, history, addItem, { debug, projectPath, autoApprove }, { showThinking }, handleDebugMessage, handleSlashCommand, false, // shellModeActive
1952
+ handleCancelSubmit, terminalWidth, terminalHeight, refreshOrchestratorHistory);
1953
+ // Keyboard shortcuts
1954
+ useInput((input, key) => {
1955
+ // Tab - toggle extended reasoning (only for toggleable models)
1956
+ if (key.tab && !key.shift) {
1957
+ if (reasoningToggleable) {
1958
+ // Toggle extended reasoning on/off
1959
+ setReasoningEffort(prev => {
1960
+ const newEffort = prev === 'none' ? 'high' : 'none';
1961
+ addItem({
1962
+ type: MessageType.Info,
1963
+ infoContent: `Extended reasoning: ${newEffort === 'high' ? 'ON' : 'OFF'}`,
1964
+ }, Date.now());
1965
+ return newEffort;
1966
+ });
1967
+ }
1968
+ else {
1969
+ // Model has native interleaved thinking or no reasoning support
1970
+ addItem({
1971
+ type: MessageType.Info,
1972
+ infoContent: supportsReasoning
1973
+ ? `${currentModel} has native interleaved thinking (always visible)`
1974
+ : `${currentModel} does not support extended reasoning`,
1975
+ }, Date.now());
1976
+ }
1977
+ return;
1978
+ }
1979
+ // Shift+Tab - toggle auto-approve
1980
+ if (key.tab && key.shift) {
1981
+ setAutoApprove(prev => {
1982
+ const newValue = !prev;
1983
+ handleApprovalModeChange(newValue);
1984
+ return newValue;
1985
+ });
1986
+ return;
1987
+ }
1988
+ // ESC - cancel streaming or close dialogs
1989
+ if (key.escape) {
1990
+ if (showHelp) {
1991
+ setShowHelp(false);
1992
+ return;
1993
+ }
1994
+ if (showConfigMenu) {
1995
+ setShowConfigMenu(false);
1996
+ return;
1997
+ }
1998
+ if (streamingState === StreamingState.Streaming) {
1999
+ cancelOngoingRequest();
2000
+ return;
2001
+ }
2002
+ }
2003
+ // Ctrl+C - exit
2004
+ if (key.ctrl && input === 'c') {
2005
+ exit();
2006
+ return;
2007
+ }
2008
+ // Ctrl+E - toggle document previews (expand/collapse)
2009
+ if (key.ctrl && input === 'e' && streamingState === StreamingState.Idle) {
2010
+ // Collect all document IDs from history
2011
+ const allDocIds = [];
2012
+ orchestratorHistory.forEach((msg) => {
2013
+ const content = msg.message?.content;
2014
+ if (Array.isArray(content)) {
2015
+ content.forEach((block, i) => {
2016
+ if (block.type === 'tool_result' && block.metadata?.documentPreview) {
2017
+ allDocIds.push({
2018
+ id: `${msg.uuid}-doc-${i}`,
2019
+ filePath: block.metadata.documentPreview.filePath,
2020
+ });
2021
+ }
2022
+ });
2023
+ }
2024
+ });
2025
+ if (allDocIds.length === 0) {
2026
+ // No documents to toggle
2027
+ return;
2028
+ }
2029
+ // Toggle all documents: if any expanded, collapse all; otherwise expand all
2030
+ setExpandedDocuments(prev => {
2031
+ if (prev.size > 0) {
2032
+ // Collapse all
2033
+ return new Set();
2034
+ }
2035
+ else {
2036
+ // Expand all and lazy-load content
2037
+ const newSet = new Set(allDocIds.map(d => d.id));
2038
+ // Trigger lazy loading for all documents
2039
+ allDocIds.forEach(({ id, filePath }) => {
2040
+ if (!documentContents.has(id)) {
2041
+ // Resolve the file path (it's relative, need to make absolute)
2042
+ const absolutePath = path.isAbsolute(filePath)
2043
+ ? filePath
2044
+ : path.resolve(process.cwd(), filePath);
2045
+ // Read file content asynchronously
2046
+ fs.promises.readFile(absolutePath, 'utf-8').then((content) => {
2047
+ setDocumentContents(prev => new Map(prev).set(id, content));
2048
+ }).catch(() => {
2049
+ setDocumentContents(prev => new Map(prev).set(id, null));
2050
+ });
2051
+ }
2052
+ });
2053
+ return newSet;
2054
+ }
2055
+ });
2056
+ addItem({
2057
+ type: MessageType.Info,
2058
+ infoContent: `Document previews: ${expandedDocuments.size > 0 ? 'collapsed' : 'expanded'}`,
2059
+ }, Date.now());
2060
+ return;
2061
+ }
2062
+ });
2063
+ // Use refs for values that change during streaming to avoid invalidating memo
2064
+ const streamingStateRef = useRef(streamingState);
2065
+ const reasoningEffortRef = useRef(reasoningEffort);
2066
+ const supportsReasoningRef = useRef(supportsReasoning);
2067
+ useEffect(() => {
2068
+ streamingStateRef.current = streamingState;
2069
+ reasoningEffortRef.current = reasoningEffort;
2070
+ supportsReasoningRef.current = supportsReasoning;
2071
+ }, [streamingState, reasoningEffort, supportsReasoning]);
2072
+ // Handle message submission - stable callback for EnhancedInput memo
2073
+ const handleSubmit = useCallback(async (text, prefillProvenance = 'none') => {
2074
+ // If currently streaming, queue the message for later (provenance rides
2075
+ // the queue — though ghosts only render at Idle, so it's near-always 'none')
2076
+ if (streamingStateRef.current === StreamingState.Streaming) {
2077
+ setQueuedMessage({ text, prefillProvenance });
2078
+ addItem({
2079
+ type: MessageType.Info,
2080
+ infoContent: ` Message queued: "${text.slice(0, 50)}${text.length > 50 ? '...' : ''}"`,
2081
+ }, Date.now());
2082
+ return;
2083
+ }
2084
+ // Pass reasoning effort for models that support it
2085
+ await submitQuery(text, {
2086
+ reasoningEffort: supportsReasoningRef.current ? reasoningEffortRef.current : undefined,
2087
+ prefillProvenance,
2088
+ });
2089
+ }, [submitQuery, addItem]); // Minimal dependencies for stable reference
2090
+ // Auto-send initial prompt (from --prompt flag) once initialized
2091
+ const hasSentInitialPrompt = useRef(false);
2092
+ useEffect(() => {
2093
+ if (initialPrompt && initialized && !hasSentInitialPrompt.current) {
2094
+ hasSentInitialPrompt.current = true;
2095
+ handleSubmit(initialPrompt);
2096
+ }
2097
+ }, [initialPrompt, initialized, handleSubmit]);
2098
+ // Process queued message when streaming completes
2099
+ useEffect(() => {
2100
+ if (streamingState === StreamingState.Idle && queuedMessage) {
2101
+ const message = queuedMessage;
2102
+ setQueuedMessage(null);
2103
+ addItem({
2104
+ type: MessageType.Info,
2105
+ infoContent: `▶ Sending queued message...`,
2106
+ }, Date.now());
2107
+ // Submit directly - the state is already Idle
2108
+ submitQuery(message.text, {
2109
+ reasoningEffort: supportsReasoning ? reasoningEffort : undefined,
2110
+ prefillProvenance: message.prefillProvenance,
2111
+ });
2112
+ }
2113
+ }, [streamingState, queuedMessage, submitQuery, supportsReasoning, reasoningEffort, addItem]);
2114
+ // Loading state - show simple spinner while initializing
2115
+ if (!initialized) {
2116
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { color: Colors.AccentCyan, children: "CORTEX" }), _jsx(LoadingSpinner, { text: "Initializing..." }), initError && _jsxs(Text, { color: Colors.AccentRed, children: ["Error: ", initError] })] }));
2117
+ }
2118
+ // Help screen - shows all commands from unified registry
2119
+ if (showHelp) {
2120
+ const allCommands = slashCommandRegistry.getAllCommands();
2121
+ const categories = slashCommandRegistry.getCategories();
2122
+ // Group commands by category
2123
+ const grouped = {};
2124
+ for (const cmd of allCommands) {
2125
+ if (!grouped[cmd.category])
2126
+ grouped[cmd.category] = [];
2127
+ grouped[cmd.category].push(cmd);
2128
+ }
2129
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Text, { bold: true, color: Colors.AccentCyan, children: ["\u2501\u2501\u2501 CORTEX Help (", allCommands.length, " commands) \u2501\u2501\u2501"] }), _jsx(Text, {}), categories.map((category) => {
2130
+ const cmds = grouped[category.name];
2131
+ if (!cmds || cmds.length === 0)
2132
+ return null;
2133
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, color: Colors.AccentGreen, children: [category.icon, " ", category.label, ":"] }), cmds.map((cmd) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsxs(Text, { color: Colors.AccentCyan, children: [" /", cmd.name] }), cmd.altName && _jsxs(Text, { dimColor: true, children: [" (/", cmd.altName, ")"] }), _jsxs(Text, { dimColor: true, children: [" ", cmd.description] })] }), cmd.subcommands && cmd.subcommands.map((sub) => (_jsxs(Text, { children: [_jsxs(Text, { color: Colors.Gray, children: [" ", sub.name] }), sub.altName && _jsxs(Text, { dimColor: true, children: [" (", sub.altName, ")"] }), _jsxs(Text, { dimColor: true, children: [" ", sub.description] })] }, sub.name)))] }, cmd.name))), _jsx(Text, {})] }, category.name));
2134
+ }), _jsx(Text, { bold: true, color: Colors.AccentYellow, children: "Keyboard Shortcuts:" }), _jsx(Text, { children: " Tab Toggle thinking display" }), _jsx(Text, { children: " Shift+Tab Toggle auto-approve (YOLO)" }), _jsx(Text, { children: " Ctrl+E Expand/collapse document previews" }), _jsx(Text, { children: " ESC Cancel streaming / Close dialogs" }), _jsx(Text, { children: " Ctrl+C Exit" }), _jsx(Text, {}), _jsx(Text, { bold: true, color: Colors.AccentYellow, children: "Input Features:" }), _jsx(Text, { children: " \u2191/\u2193 Navigate message history" }), _jsx(Text, { children: " Shift+Enter Multi-line input (new line)" }), _jsx(Text, { children: " Enter Submit message" }), _jsx(Text, { children: " ESC Clear input / Cancel history" }), _jsx(Text, {}), _jsx(Text, { dimColor: true, children: "Press ESC to close" })] }));
2135
+ }
2136
+ if (showConfigMenu) {
2137
+ return (_jsx(ConfigMenu, { projectPath: projectPath || process.cwd(), onUpdateRuntimeConfig: client ? (updates) => client.updateRuntimeConfig(updates) : undefined, onClose: () => setShowConfigMenu(false) }));
2138
+ }
2139
+ // Main UI - Using Static for history to prevent re-renders on input
2140
+ return (_jsxs(Box, { flexDirection: "column", width: terminalWidth, children: [history.length === 0 && streamingState === StreamingState.Idle && (_jsx(CompactHeader, { model: currentModel, cwd: projectPath || process.cwd() })), debug && (_jsxs(Text, { dimColor: true, children: ["[Debug: ", history.length, " messages in history]"] })), _jsx(Static, { items: history, children: (item) => (_jsx(MessageDisplay, { item: item, terminalWidth: terminalWidth, showThinking: showThinking, expandedDocuments: expandedDocuments, documentContents: documentContents }, item.id)) }, `history-exp-${expandedDocuments.size}-content-${documentContents.size}`), _jsxs(Box, { flexDirection: "column", children: [streamingState === StreamingState.Streaming && (_jsx(StreamDisplay, { events: streamEvents, isStreaming: true, showThinking: showThinking, streamStartTime: streamStartTime })), subAgents.size > 0 && (_jsx(SubAgentPanel, { agents: subAgents, terminalWidth: terminalWidth })), pendingApproval && (_jsx(ApprovalDialog, { pendingApproval: pendingApproval, onApprove: approve, onDeny: deny, onApproveAndYolo: approveAndEnableYolo })), showThemePicker && (_jsx(ThemePickerDialog, { themes: getThemeNames(), currentTheme: currentTheme, onSelect: (theme) => {
2141
+ if (setTheme(theme)) {
2142
+ setCurrentTheme(theme);
2143
+ const themeDef = getThemeDefinition(theme);
2144
+ addItem({
2145
+ type: MessageType.Info,
2146
+ infoContent: `✓ Theme set to: ${themeDef?.name || theme}`,
2147
+ }, Date.now());
2148
+ }
2149
+ setShowThemePicker(false);
2150
+ }, onCancel: () => setShowThemePicker(false) })), showModelPicker && (_jsx(ModelPickerDialog, { models: availableModels, currentModelId: client?.getCurrentModel()?.id || currentModel, loading: modelsLoading, onSelect: async (selectedModelId) => {
2151
+ if (client) {
2152
+ try {
2153
+ await client.switchModel(selectedModelId);
2154
+ const newModel = client.getCurrentModel();
2155
+ const modelSupportsReasoning = newModel?.reasoning?.supported === true;
2156
+ const modelReasoningToggleable = newModel?.reasoning?.toggleable === true;
2157
+ setCurrentModel(newModel?.name || newModel?.id || selectedModelId);
2158
+ setSupportsReasoning(modelSupportsReasoning);
2159
+ setReasoningToggleable(modelReasoningToggleable);
2160
+ // Reset reasoning effort when switching models
2161
+ if (modelSupportsReasoning && modelReasoningToggleable) {
2162
+ setReasoningEffort(showThinking ? 'high' : 'none');
2163
+ }
2164
+ else {
2165
+ setReasoningEffort('none');
2166
+ }
2167
+ // Persist model choice to .cortex/config.json
2168
+ persistModel(selectedModelId);
2169
+ addItem({
2170
+ type: MessageType.Info,
2171
+ infoContent: `✓ Switched to model: ${newModel?.name || selectedModelId}${modelSupportsReasoning ? ' (supports reasoning)' : ''}\n (saved as default)`,
2172
+ }, Date.now());
2173
+ }
2174
+ catch (error) {
2175
+ addItem({
2176
+ type: MessageType.Error,
2177
+ errorContent: `Error switching model: ${error.message}`,
2178
+ }, Date.now());
2179
+ }
2180
+ }
2181
+ setShowModelPicker(false);
2182
+ }, onCancel: () => setShowModelPicker(false) })), showSessionPicker && (_jsx(SessionPickerDialog, { sessions: availableSessions, loading: sessionsLoading, onSelect: async (sessionId) => {
2183
+ if (client) {
2184
+ try {
2185
+ addItem({ type: MessageType.Info, infoContent: `Resuming session ${sessionId.slice(0, 8)}...` }, Date.now());
2186
+ await client.resumeSession(sessionId);
2187
+ // Refresh history to display the loaded session's messages
2188
+ refreshOrchestratorHistory();
2189
+ addItem({ type: MessageType.Info, infoContent: `✓ Session resumed. Continue your conversation.` }, Date.now());
2190
+ }
2191
+ catch (error) {
2192
+ addItem({
2193
+ type: MessageType.Error,
2194
+ errorContent: `Error resuming session: ${error.message}`,
2195
+ }, Date.now());
2196
+ }
2197
+ }
2198
+ setShowSessionPicker(false);
2199
+ }, onCancel: () => setShowSessionPicker(false) })), showSystemMessageManager && systemMessageStore && (_jsx(InteractiveMenu, { store: systemMessageStore, runtimeDir: systemMessageStore.getRuntimeDir(), builtinDir: systemMessageStore.getBuiltinDir(), onExit: async () => {
2200
+ setShowSystemMessageManager(false);
2201
+ // Cleanup store
2202
+ if (systemMessageStore) {
2203
+ await systemMessageStore.destroy();
2204
+ setSystemMessageStore(null);
2205
+ }
2206
+ addItem({
2207
+ type: MessageType.Info,
2208
+ infoContent: '✓ System Message Manager closed',
2209
+ }, Date.now());
2210
+ } })), showMentorshipConfig && mentorshipMenuDef && mentorshipService && (_jsx(MenuRenderer, { definition: mentorshipMenuDef, initialValues: mentorshipInitialValues, isActive: showMentorshipConfig, onComplete: async (result) => {
2211
+ setShowMentorshipConfig(false);
2212
+ if (result.action === 'save' && result.hasChanges) {
2213
+ // Map menu keys back to config keys and save
2214
+ const configKeyMap = {
2215
+ 'MENTORSHIP_ENABLED': 'enabled',
2216
+ 'MENTORSHIP_TRIGGER_ON_ERROR': 'triggerOnError',
2217
+ 'MENTORSHIP_ERROR_THRESHOLD': 'errorThreshold',
2218
+ 'MENTORSHIP_KEYWORDS_ENABLED': 'keywordsEnabled',
2219
+ 'MENTORSHIP_CUSTOM_KEYWORDS': 'customKeywords',
2220
+ 'MENTORSHIP_HELPER_MODEL': 'helperModel',
2221
+ 'MENTORSHIP_TURN_BASED_ENABLED': 'turnBasedEnabled',
2222
+ 'MENTORSHIP_TURN_INTERVAL': 'turnInterval',
2223
+ 'MENTORSHIP_INTERLEAVED_THINKING': 'interleavedThinking',
2224
+ 'MENTORSHIP_PATTERN_DETECTION': 'patternDetection',
2225
+ 'MENTORSHIP_PATTERN_THRESHOLD': 'patternThreshold',
2226
+ };
2227
+ const configChanges = {};
2228
+ for (const [menuKey, value] of Object.entries(result.changes)) {
2229
+ const configKey = configKeyMap[menuKey];
2230
+ if (configKey) {
2231
+ configChanges[configKey] = value;
2232
+ }
2233
+ }
2234
+ try {
2235
+ await mentorshipService.setConfig(configChanges);
2236
+ addItem({
2237
+ type: MessageType.Info,
2238
+ infoContent: `✓ Mentorship config saved (${Object.keys(result.changes).length} changes)`,
2239
+ }, Date.now());
2240
+ }
2241
+ catch (error) {
2242
+ addItem({
2243
+ type: MessageType.Error,
2244
+ errorContent: `Error saving config: ${error.message}`,
2245
+ }, Date.now());
2246
+ }
2247
+ }
2248
+ else if (result.action === 'reset') {
2249
+ try {
2250
+ await mentorshipService.resetToDefaults();
2251
+ addItem({
2252
+ type: MessageType.Info,
2253
+ infoContent: '✓ Mentorship config reset to defaults',
2254
+ }, Date.now());
2255
+ }
2256
+ catch (error) {
2257
+ addItem({
2258
+ type: MessageType.Error,
2259
+ errorContent: `Error resetting config: ${error.message}`,
2260
+ }, Date.now());
2261
+ }
2262
+ }
2263
+ else if (result.action === 'cancel') {
2264
+ addItem({
2265
+ type: MessageType.Info,
2266
+ infoContent: 'Mentorship config cancelled',
2267
+ }, Date.now());
2268
+ }
2269
+ // Cleanup
2270
+ setMentorshipService(null);
2271
+ setMentorshipMenuDef(null);
2272
+ setMentorshipInitialValues({});
2273
+ }, onLiveUpdate: async (key, value) => {
2274
+ // Handle live updates for toggles
2275
+ const configKeyMap = {
2276
+ 'MENTORSHIP_ENABLED': 'enabled',
2277
+ 'MENTORSHIP_TRIGGER_ON_ERROR': 'triggerOnError',
2278
+ 'MENTORSHIP_ERROR_THRESHOLD': 'errorThreshold',
2279
+ 'MENTORSHIP_KEYWORDS_ENABLED': 'keywordsEnabled',
2280
+ 'MENTORSHIP_TURN_BASED_ENABLED': 'turnBasedEnabled',
2281
+ 'MENTORSHIP_PATTERN_DETECTION': 'patternDetection',
2282
+ 'MENTORSHIP_INTERLEAVED_THINKING': 'interleavedThinking',
2283
+ };
2284
+ const configKey = configKeyMap[key];
2285
+ if (configKey && mentorshipService) {
2286
+ await mentorshipService.setConfig({ [configKey]: value });
2287
+ }
2288
+ } }))] }), queuedMessage && (_jsxs(Box, { paddingX: 1, marginBottom: 1, children: [_jsxs(Text, { color: Colors.AccentYellow, children: ["Queued: \"", queuedMessage.text.slice(0, 60), queuedMessage.text.length > 60 ? '...' : '', "\""] }), _jsx(Text, { dimColor: true, children: " (will send when ready)" })] })), turnSummary && streamingState === StreamingState.Idle && (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { dimColor: true, children: ["[Summary] ", turnSummary] }) })), _jsx(EnhancedInput, { onSubmit: handleSubmit, disabled: !!pendingApproval || showThemePicker || showModelPicker || showSessionPicker || showSystemMessageManager, placeholder: streamingState === StreamingState.Streaming ? "Type next message (will queue)..." : undefined, userMessages: userMessages, prefillValue: streamingState === StreamingState.Idle ? nextActionPrediction : null }), _jsx(StatusLine, { model: currentModel, autoApprove: autoApprove, showThinking: showThinking, reasoningEffort: reasoningEffort, supportsReasoning: supportsReasoning })] }));
2289
+ };
2290
+ export default CortexApp;
2291
+ //# sourceMappingURL=CortexApp.js.map