@within-7/minto 0.1.7 → 0.2.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 (481) hide show
  1. package/cli.js +155 -37
  2. package/dist/Tool.js +38 -0
  3. package/dist/Tool.js.map +3 -3
  4. package/dist/commands/agents/AgentsCommand.js +52 -26
  5. package/dist/commands/agents/AgentsCommand.js.map +2 -2
  6. package/dist/commands/agents/constants.js +1 -1
  7. package/dist/commands/agents/constants.js.map +1 -1
  8. package/dist/commands/agents/index.js +1 -1
  9. package/dist/commands/bug.js +74 -7
  10. package/dist/commands/bug.js.map +3 -3
  11. package/dist/commands/clear.js +3 -0
  12. package/dist/commands/clear.js.map +2 -2
  13. package/dist/commands/compact.js +37 -0
  14. package/dist/commands/compact.js.map +2 -2
  15. package/dist/commands/context.js +84 -0
  16. package/dist/commands/context.js.map +7 -0
  17. package/dist/commands/ctx_viz.js +18 -10
  18. package/dist/commands/ctx_viz.js.map +2 -2
  19. package/dist/commands/doctor.js +158 -12
  20. package/dist/commands/doctor.js.map +2 -2
  21. package/dist/commands/export.js +156 -0
  22. package/dist/commands/export.js.map +7 -0
  23. package/dist/commands/mcp-interactive.js +21 -12
  24. package/dist/commands/mcp-interactive.js.map +2 -2
  25. package/dist/commands/model.js +6 -5
  26. package/dist/commands/model.js.map +2 -2
  27. package/dist/commands/permissions.js +86 -0
  28. package/dist/commands/permissions.js.map +7 -0
  29. package/dist/commands/quit.js +3 -1
  30. package/dist/commands/quit.js.map +2 -2
  31. package/dist/commands/sandbox.js +104 -0
  32. package/dist/commands/sandbox.js.map +7 -0
  33. package/dist/commands/status.js +58 -0
  34. package/dist/commands/status.js.map +7 -0
  35. package/dist/commands/tasks.js +108 -0
  36. package/dist/commands/tasks.js.map +7 -0
  37. package/dist/commands/todos.js +123 -0
  38. package/dist/commands/todos.js.map +7 -0
  39. package/dist/commands.js +20 -2
  40. package/dist/commands.js.map +2 -2
  41. package/dist/components/AgentThinkingBlock.js +10 -18
  42. package/dist/components/AgentThinkingBlock.js.map +2 -2
  43. package/dist/components/BackgroundTasksPanel.js +78 -29
  44. package/dist/components/BackgroundTasksPanel.js.map +2 -2
  45. package/dist/components/BashStreamingProgress.js +24 -0
  46. package/dist/components/BashStreamingProgress.js.map +7 -0
  47. package/dist/components/CollapsibleHint.js +14 -0
  48. package/dist/components/CollapsibleHint.js.map +7 -0
  49. package/dist/components/FileEditToolUpdatedMessage.js +1 -1
  50. package/dist/components/FileEditToolUpdatedMessage.js.map +2 -2
  51. package/dist/components/HotkeyHelpPanel.js +137 -0
  52. package/dist/components/HotkeyHelpPanel.js.map +7 -0
  53. package/dist/components/Logo.js +5 -5
  54. package/dist/components/Logo.js.map +2 -2
  55. package/dist/components/Message.js +23 -7
  56. package/dist/components/Message.js.map +3 -3
  57. package/dist/components/ModelConfig.js +16 -3
  58. package/dist/components/ModelConfig.js.map +2 -2
  59. package/dist/components/ModelListManager.js +3 -3
  60. package/dist/components/ModelListManager.js.map +2 -2
  61. package/dist/components/ModelSelector/ModelSelector.js +1 -1
  62. package/dist/components/Onboarding.js +19 -14
  63. package/dist/components/Onboarding.js.map +2 -2
  64. package/dist/components/ProgressBar.js +74 -0
  65. package/dist/components/ProgressBar.js.map +7 -0
  66. package/dist/components/PromptInput.js +156 -46
  67. package/dist/components/PromptInput.js.map +2 -2
  68. package/dist/components/RequestStatusIndicator.js +194 -0
  69. package/dist/components/RequestStatusIndicator.js.map +7 -0
  70. package/dist/components/Spinner.js +92 -27
  71. package/dist/components/Spinner.js.map +2 -2
  72. package/dist/components/SpinnerSymbol.js +21 -27
  73. package/dist/components/SpinnerSymbol.js.map +2 -2
  74. package/dist/components/StreamingBashOutput.js +9 -8
  75. package/dist/components/StreamingBashOutput.js.map +2 -2
  76. package/dist/components/SubagentBlock.js +1 -1
  77. package/dist/components/SubagentBlock.js.map +1 -1
  78. package/dist/components/SubagentProgress.js +10 -11
  79. package/dist/components/SubagentProgress.js.map +2 -2
  80. package/dist/components/TaskCard.js +16 -13
  81. package/dist/components/TaskCard.js.map +2 -2
  82. package/dist/components/TodoChangeBlock.js +1 -1
  83. package/dist/components/TodoChangeBlock.js.map +2 -2
  84. package/dist/components/TodoPanel.js +120 -29
  85. package/dist/components/TodoPanel.js.map +3 -3
  86. package/dist/components/TokenCounter.js +74 -0
  87. package/dist/components/TokenCounter.js.map +7 -0
  88. package/dist/components/TokenWarning.js +2 -1
  89. package/dist/components/TokenWarning.js.map +2 -2
  90. package/dist/components/TreeConnector.js +25 -0
  91. package/dist/components/TreeConnector.js.map +7 -0
  92. package/dist/components/TurnCompletionIndicator.js +18 -0
  93. package/dist/components/TurnCompletionIndicator.js.map +7 -0
  94. package/dist/components/messages/AssistantTextMessage.js +5 -2
  95. package/dist/components/messages/AssistantTextMessage.js.map +2 -2
  96. package/dist/components/messages/AssistantThinkingMessage.js +18 -3
  97. package/dist/components/messages/AssistantThinkingMessage.js.map +2 -2
  98. package/dist/components/messages/AssistantToolUseMessage.js +11 -8
  99. package/dist/components/messages/AssistantToolUseMessage.js.map +2 -2
  100. package/dist/components/messages/GroupRenderer.js +53 -0
  101. package/dist/components/messages/GroupRenderer.js.map +7 -0
  102. package/dist/components/messages/NestedTasksPreview.js +12 -0
  103. package/dist/components/messages/NestedTasksPreview.js.map +7 -0
  104. package/dist/components/messages/ParallelTasksGroupView.js +92 -0
  105. package/dist/components/messages/ParallelTasksGroupView.js.map +7 -0
  106. package/dist/components/messages/TaskInModuleView.js +198 -0
  107. package/dist/components/messages/TaskInModuleView.js.map +7 -0
  108. package/dist/components/messages/TaskOutputContent.js +53 -0
  109. package/dist/components/messages/TaskOutputContent.js.map +7 -0
  110. package/dist/components/messages/UserPromptMessage.js +1 -1
  111. package/dist/components/messages/UserPromptMessage.js.map +2 -2
  112. package/dist/components/messages/UserToolResultMessage/UserToolSuccessMessage.js +2 -3
  113. package/dist/components/messages/UserToolResultMessage/UserToolSuccessMessage.js.map +2 -2
  114. package/dist/components/permissions/FallbackPermissionRequest.js +4 -4
  115. package/dist/components/permissions/FallbackPermissionRequest.js.map +2 -2
  116. package/dist/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.js +4 -4
  117. package/dist/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.js.map +2 -2
  118. package/dist/constants/colors.js +48 -0
  119. package/dist/constants/colors.js.map +2 -2
  120. package/dist/constants/formatRules.js +102 -0
  121. package/dist/constants/formatRules.js.map +7 -0
  122. package/dist/constants/prompts.js +12 -34
  123. package/dist/constants/prompts.js.map +2 -2
  124. package/dist/constants/symbols.js +64 -6
  125. package/dist/constants/symbols.js.map +2 -2
  126. package/dist/constants/timing.js +5 -0
  127. package/dist/constants/timing.js.map +2 -2
  128. package/dist/core/config/defaults.js +84 -0
  129. package/dist/core/config/defaults.js.map +7 -0
  130. package/dist/core/config/index.js +111 -0
  131. package/dist/core/config/index.js.map +7 -0
  132. package/dist/core/config/loader.js +221 -0
  133. package/dist/core/config/loader.js.map +7 -0
  134. package/dist/core/config/migrations.js +128 -0
  135. package/dist/core/config/migrations.js.map +7 -0
  136. package/dist/core/config/schema.js +178 -0
  137. package/dist/core/config/schema.js.map +7 -0
  138. package/dist/core/costTracker.js +138 -0
  139. package/dist/core/costTracker.js.map +7 -0
  140. package/dist/core/index.js +5 -0
  141. package/dist/core/index.js.map +7 -0
  142. package/dist/core/permissions/auditLog.js +204 -0
  143. package/dist/core/permissions/auditLog.js.map +7 -0
  144. package/dist/core/permissions/engine/index.js +3 -0
  145. package/dist/core/permissions/engine/index.js.map +7 -0
  146. package/dist/core/permissions/engine/permissionEngine.js +106 -0
  147. package/dist/core/permissions/engine/permissionEngine.js.map +7 -0
  148. package/dist/core/permissions/engine/types.js +1 -0
  149. package/dist/core/permissions/engine/types.js.map +7 -0
  150. package/dist/core/permissions/index.js +84 -0
  151. package/dist/core/permissions/index.js.map +7 -0
  152. package/dist/core/permissions/ruleEngine.js +259 -0
  153. package/dist/core/permissions/ruleEngine.js.map +7 -0
  154. package/dist/core/permissions/rules/allowedToolsRule.js +62 -0
  155. package/dist/core/permissions/rules/allowedToolsRule.js.map +7 -0
  156. package/dist/core/permissions/rules/autoEscalationRule.js +291 -0
  157. package/dist/core/permissions/rules/autoEscalationRule.js.map +7 -0
  158. package/dist/core/permissions/rules/index.js +46 -0
  159. package/dist/core/permissions/rules/index.js.map +7 -0
  160. package/dist/core/permissions/rules/planModeRule.js +55 -0
  161. package/dist/core/permissions/rules/planModeRule.js.map +7 -0
  162. package/dist/core/permissions/rules/projectBoundaryRule.js +168 -0
  163. package/dist/core/permissions/rules/projectBoundaryRule.js.map +7 -0
  164. package/dist/core/permissions/rules/safeModeRule.js +65 -0
  165. package/dist/core/permissions/rules/safeModeRule.js.map +7 -0
  166. package/dist/core/permissions/rules/sensitivePathsRule.js +340 -0
  167. package/dist/core/permissions/rules/sensitivePathsRule.js.map +7 -0
  168. package/dist/core/permissions/types.js +127 -0
  169. package/dist/core/permissions/types.js.map +7 -0
  170. package/dist/core/tools/executor.js +143 -0
  171. package/dist/core/tools/executor.js.map +7 -0
  172. package/dist/core/tools/index.js +15 -0
  173. package/dist/core/tools/index.js.map +7 -0
  174. package/dist/core/tools/registry.js +183 -0
  175. package/dist/core/tools/registry.js.map +7 -0
  176. package/dist/core/tools/types.js +1 -0
  177. package/dist/core/tools/types.js.map +7 -0
  178. package/dist/cost-tracker.js +23 -15
  179. package/dist/cost-tracker.js.map +2 -2
  180. package/dist/entrypoints/cli.js +43 -43
  181. package/dist/entrypoints/cli.js.map +2 -2
  182. package/dist/entrypoints/mcp.js +12 -4
  183. package/dist/entrypoints/mcp.js.map +2 -2
  184. package/dist/history.js +14 -3
  185. package/dist/history.js.map +2 -2
  186. package/dist/hooks/useAgentTranscripts.js +116 -0
  187. package/dist/hooks/useAgentTranscripts.js.map +7 -0
  188. package/dist/hooks/useAnimationSync.js +53 -0
  189. package/dist/hooks/useAnimationSync.js.map +7 -0
  190. package/dist/hooks/useArrowKeyHistory.js +4 -2
  191. package/dist/hooks/useArrowKeyHistory.js.map +2 -2
  192. package/dist/hooks/useCanUseTool.js +3 -1
  193. package/dist/hooks/useCanUseTool.js.map +2 -2
  194. package/dist/hooks/useCancelRequest.js +4 -1
  195. package/dist/hooks/useCancelRequest.js.map +2 -2
  196. package/dist/hooks/useExitOnCtrlCD.js +9 -5
  197. package/dist/hooks/useExitOnCtrlCD.js.map +2 -2
  198. package/dist/hooks/useHookStatus.js +40 -0
  199. package/dist/hooks/useHookStatus.js.map +7 -0
  200. package/dist/hooks/useLogMessages.js +17 -1
  201. package/dist/hooks/useLogMessages.js.map +2 -2
  202. package/dist/hooks/useMessageGroups.js +43 -0
  203. package/dist/hooks/useMessageGroups.js.map +7 -0
  204. package/dist/hooks/useTerminalSize.js +62 -6
  205. package/dist/hooks/useTerminalSize.js.map +2 -2
  206. package/dist/hooks/useUnifiedCompletion.js +69 -0
  207. package/dist/hooks/useUnifiedCompletion.js.map +2 -2
  208. package/dist/i18n/index.js +109 -0
  209. package/dist/i18n/index.js.map +7 -0
  210. package/dist/i18n/locales/en.js +347 -0
  211. package/dist/i18n/locales/en.js.map +7 -0
  212. package/dist/i18n/locales/index.js +7 -0
  213. package/dist/i18n/locales/index.js.map +7 -0
  214. package/dist/i18n/locales/zh-CN.js +347 -0
  215. package/dist/i18n/locales/zh-CN.js.map +7 -0
  216. package/dist/i18n/types.js +8 -0
  217. package/dist/i18n/types.js.map +7 -0
  218. package/dist/query.js +175 -17
  219. package/dist/query.js.map +3 -3
  220. package/dist/screens/REPL.js +501 -192
  221. package/dist/screens/REPL.js.map +3 -3
  222. package/dist/services/adapters/chatCompletions.js +3 -1
  223. package/dist/services/adapters/chatCompletions.js.map +2 -2
  224. package/dist/services/adapters/messageNormalizer.js +354 -0
  225. package/dist/services/adapters/messageNormalizer.js.map +7 -0
  226. package/dist/services/adapters/responsesAPI.js +6 -3
  227. package/dist/services/adapters/responsesAPI.js.map +2 -2
  228. package/dist/services/checkpointManager.js +386 -0
  229. package/dist/services/checkpointManager.js.map +7 -0
  230. package/dist/services/claude.js +138 -11
  231. package/dist/services/claude.js.map +3 -3
  232. package/dist/services/compressionService.js +50 -1
  233. package/dist/services/compressionService.js.map +2 -2
  234. package/dist/services/contextMonitor.js +162 -0
  235. package/dist/services/contextMonitor.js.map +7 -0
  236. package/dist/services/customCommands.js +60 -41
  237. package/dist/services/customCommands.js.map +2 -2
  238. package/dist/services/hookExecutor.js +173 -1
  239. package/dist/services/hookExecutor.js.map +2 -2
  240. package/dist/services/intelligentCompactor.js +281 -0
  241. package/dist/services/intelligentCompactor.js.map +7 -0
  242. package/dist/services/lspConfig.js +109 -0
  243. package/dist/services/lspConfig.js.map +7 -0
  244. package/dist/services/mcpClient.js +273 -34
  245. package/dist/services/mcpClient.js.map +2 -2
  246. package/dist/services/modelOrchestrator.js +310 -0
  247. package/dist/services/modelOrchestrator.js.map +7 -0
  248. package/dist/services/openai.js +8 -1
  249. package/dist/services/openai.js.map +2 -2
  250. package/dist/services/outputStyles.js +138 -0
  251. package/dist/services/outputStyles.js.map +7 -0
  252. package/dist/services/plugins/index.js +5 -0
  253. package/dist/services/plugins/index.js.map +7 -0
  254. package/dist/services/plugins/lspServers.js +188 -0
  255. package/dist/services/plugins/lspServers.js.map +7 -0
  256. package/dist/services/plugins/pluginRuntime.js +229 -0
  257. package/dist/services/plugins/pluginRuntime.js.map +7 -0
  258. package/dist/services/plugins/pluginValidation.js +219 -0
  259. package/dist/services/plugins/pluginValidation.js.map +7 -0
  260. package/dist/services/plugins/skillMarketplace.js +556 -0
  261. package/dist/services/plugins/skillMarketplace.js.map +7 -0
  262. package/dist/services/responseStateManager.js +37 -3
  263. package/dist/services/responseStateManager.js.map +2 -2
  264. package/dist/services/sandbox/filesystemBoundary.js +300 -0
  265. package/dist/services/sandbox/filesystemBoundary.js.map +7 -0
  266. package/dist/services/sandbox/index.js +14 -0
  267. package/dist/services/sandbox/index.js.map +7 -0
  268. package/dist/services/sandbox/networkProxy.js +293 -0
  269. package/dist/services/sandbox/networkProxy.js.map +7 -0
  270. package/dist/services/sandbox/sandboxController.js +574 -0
  271. package/dist/services/sandbox/sandboxController.js.map +7 -0
  272. package/dist/services/sandbox/types.js +50 -0
  273. package/dist/services/sandbox/types.js.map +7 -0
  274. package/dist/services/sessionMemory.js +266 -0
  275. package/dist/services/sessionMemory.js.map +7 -0
  276. package/dist/services/taskRouter.js +324 -0
  277. package/dist/services/taskRouter.js.map +7 -0
  278. package/dist/tools/ArchitectTool/ArchitectTool.js +7 -1
  279. package/dist/tools/ArchitectTool/ArchitectTool.js.map +2 -2
  280. package/dist/tools/AskExpertModelTool/AskExpertModelTool.js +3 -0
  281. package/dist/tools/AskExpertModelTool/AskExpertModelTool.js.map +2 -2
  282. package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js.map +2 -2
  283. package/dist/tools/BaseTool.js +72 -0
  284. package/dist/tools/BaseTool.js.map +7 -0
  285. package/dist/tools/BashOutputTool/BashOutputToolResultMessage.js +3 -0
  286. package/dist/tools/BashOutputTool/BashOutputToolResultMessage.js.map +2 -2
  287. package/dist/tools/BashTool/BashTool.js +60 -3
  288. package/dist/tools/BashTool/BashTool.js.map +2 -2
  289. package/dist/tools/BashTool/BashToolResultMessage.js +3 -0
  290. package/dist/tools/BashTool/BashToolResultMessage.js.map +2 -2
  291. package/dist/tools/BashTool/OutputLine.js +54 -0
  292. package/dist/tools/BashTool/OutputLine.js.map +2 -2
  293. package/dist/tools/BashTool/prompt.js +192 -3
  294. package/dist/tools/BashTool/prompt.js.map +2 -2
  295. package/dist/tools/FileEditTool/FileEditTool.js +29 -4
  296. package/dist/tools/FileEditTool/FileEditTool.js.map +2 -2
  297. package/dist/tools/FileWriteTool/FileWriteTool.js +5 -5
  298. package/dist/tools/FileWriteTool/FileWriteTool.js.map +2 -2
  299. package/dist/tools/GlobTool/GlobTool.js +4 -2
  300. package/dist/tools/GlobTool/GlobTool.js.map +2 -2
  301. package/dist/tools/GrepTool/GrepTool.js +36 -7
  302. package/dist/tools/GrepTool/GrepTool.js.map +2 -2
  303. package/dist/tools/KillShellTool/KillShellToolResultMessage.js +3 -0
  304. package/dist/tools/KillShellTool/KillShellToolResultMessage.js.map +2 -2
  305. package/dist/tools/ListMcpResourcesTool/ListMcpResourcesTool.js +109 -0
  306. package/dist/tools/ListMcpResourcesTool/ListMcpResourcesTool.js.map +7 -0
  307. package/dist/tools/ListMcpResourcesTool/prompt.js +19 -0
  308. package/dist/tools/ListMcpResourcesTool/prompt.js.map +7 -0
  309. package/dist/tools/LspTool/LspTool.js +664 -0
  310. package/dist/tools/LspTool/LspTool.js.map +7 -0
  311. package/dist/tools/LspTool/prompt.js +27 -0
  312. package/dist/tools/LspTool/prompt.js.map +7 -0
  313. package/dist/tools/MCPTool/MCPTool.js +9 -1
  314. package/dist/tools/MCPTool/MCPTool.js.map +2 -2
  315. package/dist/tools/MemoryReadTool/MemoryReadTool.js +19 -6
  316. package/dist/tools/MemoryReadTool/MemoryReadTool.js.map +2 -2
  317. package/dist/tools/MemoryWriteTool/MemoryWriteTool.js +6 -6
  318. package/dist/tools/MemoryWriteTool/MemoryWriteTool.js.map +2 -2
  319. package/dist/tools/MultiEditTool/MultiEditTool.js +19 -2
  320. package/dist/tools/MultiEditTool/MultiEditTool.js.map +2 -2
  321. package/dist/tools/NotebookEditTool/NotebookEditTool.js +5 -1
  322. package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
  323. package/dist/tools/NotebookReadTool/NotebookReadTool.js.map +2 -2
  324. package/dist/tools/PlanModeTool/EnterPlanModeTool.js +74 -0
  325. package/dist/tools/PlanModeTool/EnterPlanModeTool.js.map +7 -0
  326. package/dist/tools/PlanModeTool/ExitPlanModeTool.js +108 -0
  327. package/dist/tools/PlanModeTool/ExitPlanModeTool.js.map +7 -0
  328. package/dist/tools/PlanModeTool/prompt.js +94 -0
  329. package/dist/tools/PlanModeTool/prompt.js.map +7 -0
  330. package/dist/tools/ReadMcpResourceTool/ReadMcpResourceTool.js +130 -0
  331. package/dist/tools/ReadMcpResourceTool/ReadMcpResourceTool.js.map +7 -0
  332. package/dist/tools/ReadMcpResourceTool/prompt.js +17 -0
  333. package/dist/tools/ReadMcpResourceTool/prompt.js.map +7 -0
  334. package/dist/tools/SkillTool/SkillTool.js +6 -1
  335. package/dist/tools/SkillTool/SkillTool.js.map +2 -2
  336. package/dist/tools/SlashCommandTool/SlashCommandTool.js +260 -0
  337. package/dist/tools/SlashCommandTool/SlashCommandTool.js.map +7 -0
  338. package/dist/tools/SlashCommandTool/prompt.js +35 -0
  339. package/dist/tools/SlashCommandTool/prompt.js.map +7 -0
  340. package/dist/tools/TaskOutputTool/TaskOutputTool.js +189 -0
  341. package/dist/tools/TaskOutputTool/TaskOutputTool.js.map +7 -0
  342. package/dist/tools/TaskOutputTool/prompt.js +15 -0
  343. package/dist/tools/TaskOutputTool/prompt.js.map +7 -0
  344. package/dist/tools/TaskTool/TaskTool.js +302 -104
  345. package/dist/tools/TaskTool/TaskTool.js.map +2 -2
  346. package/dist/tools/TaskTool/prompt.js.map +2 -2
  347. package/dist/tools/TodoWriteTool/TodoWriteTool.js +42 -77
  348. package/dist/tools/TodoWriteTool/TodoWriteTool.js.map +2 -2
  349. package/dist/tools/URLFetcherTool/URLFetcherTool.js +4 -1
  350. package/dist/tools/URLFetcherTool/URLFetcherTool.js.map +2 -2
  351. package/dist/tools/URLFetcherTool/cache.js +55 -8
  352. package/dist/tools/URLFetcherTool/cache.js.map +2 -2
  353. package/dist/tools.js +31 -2
  354. package/dist/tools.js.map +2 -2
  355. package/dist/types/hooks.js +4 -0
  356. package/dist/types/hooks.js.map +2 -2
  357. package/dist/types/marketplace.js.map +2 -2
  358. package/dist/types/messageGroup.js +36 -0
  359. package/dist/types/messageGroup.js.map +7 -0
  360. package/dist/types/plugin.js.map +2 -2
  361. package/dist/types/thinking.js +1 -0
  362. package/dist/types/thinking.js.map +7 -0
  363. package/dist/utils/BackgroundShellManager.js +136 -39
  364. package/dist/utils/BackgroundShellManager.js.map +2 -2
  365. package/dist/utils/MessageBatchBuffer.js +102 -0
  366. package/dist/utils/MessageBatchBuffer.js.map +7 -0
  367. package/dist/utils/PersistentShell.js +151 -1
  368. package/dist/utils/PersistentShell.js.map +2 -2
  369. package/dist/utils/agentLoader.js +1 -23
  370. package/dist/utils/agentLoader.js.map +2 -2
  371. package/dist/utils/agentTranscripts.js +641 -0
  372. package/dist/utils/agentTranscripts.js.map +7 -0
  373. package/dist/utils/animationManager.js +213 -0
  374. package/dist/utils/animationManager.js.map +7 -0
  375. package/dist/utils/animationSync.js +110 -0
  376. package/dist/utils/animationSync.js.map +7 -0
  377. package/dist/utils/asyncFile.js +215 -0
  378. package/dist/utils/asyncFile.js.map +7 -0
  379. package/dist/utils/backgroundAgentManager.js +231 -0
  380. package/dist/utils/backgroundAgentManager.js.map +7 -0
  381. package/dist/utils/config.js +63 -7
  382. package/dist/utils/config.js.map +2 -2
  383. package/dist/utils/conversationRecovery.js +19 -0
  384. package/dist/utils/conversationRecovery.js.map +2 -2
  385. package/dist/utils/exit.js +73 -0
  386. package/dist/utils/exit.js.map +7 -0
  387. package/dist/utils/format.js +73 -5
  388. package/dist/utils/format.js.map +2 -2
  389. package/dist/utils/generators.js +76 -6
  390. package/dist/utils/generators.js.map +2 -2
  391. package/dist/utils/globalErrorHandler.js +149 -0
  392. package/dist/utils/globalErrorHandler.js.map +7 -0
  393. package/dist/utils/groupHandlers/index.js +8 -0
  394. package/dist/utils/groupHandlers/index.js.map +7 -0
  395. package/dist/utils/groupHandlers/parallelTasksHandler.js +140 -0
  396. package/dist/utils/groupHandlers/parallelTasksHandler.js.map +7 -0
  397. package/dist/utils/groupHandlers/taskHandler.js +104 -0
  398. package/dist/utils/groupHandlers/taskHandler.js.map +7 -0
  399. package/dist/utils/groupHandlers/types.js +1 -0
  400. package/dist/utils/groupHandlers/types.js.map +7 -0
  401. package/dist/utils/logRotation.js +224 -0
  402. package/dist/utils/logRotation.js.map +7 -0
  403. package/dist/utils/marketplaceManager.js +3 -5
  404. package/dist/utils/marketplaceManager.js.map +2 -2
  405. package/dist/utils/memSafety.js +264 -0
  406. package/dist/utils/memSafety.js.map +7 -0
  407. package/dist/utils/messageGroupManager.js +274 -0
  408. package/dist/utils/messageGroupManager.js.map +7 -0
  409. package/dist/utils/messages.js +13 -4
  410. package/dist/utils/messages.js.map +2 -2
  411. package/dist/utils/model.js +119 -15
  412. package/dist/utils/model.js.map +3 -3
  413. package/dist/utils/permissions/filesystem.js +157 -5
  414. package/dist/utils/permissions/filesystem.js.map +2 -2
  415. package/dist/utils/plan/planMode.js +143 -0
  416. package/dist/utils/plan/planMode.js.map +7 -0
  417. package/dist/utils/pluginLoader.js +17 -21
  418. package/dist/utils/pluginLoader.js.map +2 -2
  419. package/dist/utils/ripgrep.js +55 -2
  420. package/dist/utils/ripgrep.js.map +2 -2
  421. package/dist/utils/sanitizeInput.js +32 -0
  422. package/dist/utils/sanitizeInput.js.map +7 -0
  423. package/dist/utils/secureKeyStorage.js +312 -0
  424. package/dist/utils/secureKeyStorage.js.map +7 -0
  425. package/dist/utils/session/sessionPlugins.js +67 -0
  426. package/dist/utils/session/sessionPlugins.js.map +7 -0
  427. package/dist/utils/taskDisplayUtils.js +257 -0
  428. package/dist/utils/taskDisplayUtils.js.map +7 -0
  429. package/dist/utils/teamConfig.js +2 -1
  430. package/dist/utils/teamConfig.js.map +2 -2
  431. package/dist/utils/todoStorage.js +92 -2
  432. package/dist/utils/todoStorage.js.map +2 -2
  433. package/dist/utils/toolTimeout.js +136 -0
  434. package/dist/utils/toolTimeout.js.map +7 -0
  435. package/dist/utils/tooling/safeRender.js +115 -0
  436. package/dist/utils/tooling/safeRender.js.map +7 -0
  437. package/dist/utils/userFriendlyError.js +346 -0
  438. package/dist/utils/userFriendlyError.js.map +7 -0
  439. package/dist/utils/vendor/ripgrep/arm64-darwin/rg +0 -0
  440. package/dist/version.js +2 -2
  441. package/dist/version.js.map +1 -1
  442. package/package.json +14 -4
  443. package/scripts/postinstall.js +128 -38
  444. package/dist/commands/agents.js +0 -2086
  445. package/dist/commands/agents.js.map +0 -7
  446. package/dist/commands/build.js +0 -74
  447. package/dist/commands/build.js.map +0 -7
  448. package/dist/commands/compression.js +0 -57
  449. package/dist/commands/compression.js.map +0 -7
  450. package/dist/commands/listen.js +0 -37
  451. package/dist/commands/listen.js.map +0 -7
  452. package/dist/commands/login.js +0 -37
  453. package/dist/commands/login.js.map +0 -7
  454. package/dist/commands/logout.js +0 -33
  455. package/dist/commands/logout.js.map +0 -7
  456. package/dist/commands/mcp.js +0 -40
  457. package/dist/commands/mcp.js.map +0 -7
  458. package/dist/commands/mcp_refresh.js +0 -40
  459. package/dist/commands/mcp_refresh.js.map +0 -7
  460. package/dist/commands/modelstatus.js +0 -21
  461. package/dist/commands/modelstatus.js.map +0 -7
  462. package/dist/commands/onboarding.js +0 -36
  463. package/dist/commands/onboarding.js.map +0 -7
  464. package/dist/commands/plugin-interactive.js +0 -446
  465. package/dist/commands/plugin-interactive.js.map +0 -7
  466. package/dist/commands/pr_comments.js +0 -61
  467. package/dist/commands/pr_comments.js.map +0 -7
  468. package/dist/commands/release-notes.js +0 -30
  469. package/dist/commands/release-notes.js.map +0 -7
  470. package/dist/commands/review.js +0 -51
  471. package/dist/commands/review.js.map +0 -7
  472. package/dist/components/Bug.js +0 -147
  473. package/dist/components/Bug.js.map +0 -7
  474. package/dist/components/ModelSelector.js +0 -2062
  475. package/dist/components/ModelSelector.js.map +0 -7
  476. package/dist/components/ModelStatusDisplay.js +0 -87
  477. package/dist/components/ModelStatusDisplay.js.map +0 -7
  478. package/dist/entrypoints/cli-wrapper.js +0 -61
  479. package/dist/entrypoints/cli-wrapper.js.map +0 -7
  480. package/dist/screens/Doctor.js +0 -22
  481. package/dist/screens/Doctor.js.map +0 -7
@@ -34,12 +34,17 @@ class RegExpCache {
34
34
  return regex;
35
35
  }
36
36
  }
37
+ const AUTO_CLEANUP_INTERVAL = 5 * 60 * 1e3;
38
+ const MAX_COMPLETED_SHELL_AGE = 30 * 60 * 1e3;
39
+ const MAX_COMPLETED_SHELLS = 50;
37
40
  class BackgroundShellManager extends EventEmitter {
38
41
  static instance = null;
39
42
  shells = /* @__PURE__ */ new Map();
40
43
  regexCache = new RegExpCache();
44
+ autoCleanupTimer = null;
41
45
  constructor() {
42
46
  super();
47
+ this.startAutoCleanup();
43
48
  }
44
49
  static getInstance() {
45
50
  if (!BackgroundShellManager.instance) {
@@ -47,6 +52,62 @@ class BackgroundShellManager extends EventEmitter {
47
52
  }
48
53
  return BackgroundShellManager.instance;
49
54
  }
55
+ /**
56
+ * Start automatic cleanup timer
57
+ * Cleans up old completed shells periodically to prevent memory bloat
58
+ */
59
+ startAutoCleanup() {
60
+ if (this.autoCleanupTimer) return;
61
+ this.autoCleanupTimer = setInterval(() => {
62
+ this.autoCleanup();
63
+ }, AUTO_CLEANUP_INTERVAL);
64
+ this.autoCleanupTimer.unref();
65
+ }
66
+ /**
67
+ * Stop automatic cleanup timer
68
+ * Should be called when shutting down
69
+ */
70
+ stopAutoCleanup() {
71
+ if (this.autoCleanupTimer) {
72
+ clearInterval(this.autoCleanupTimer);
73
+ this.autoCleanupTimer = null;
74
+ }
75
+ }
76
+ /**
77
+ * Automatic cleanup of old and excessive completed shells
78
+ * Called periodically by the auto-cleanup timer
79
+ */
80
+ autoCleanup() {
81
+ const completedShells = [];
82
+ for (const [shellId, shell] of this.shells.entries()) {
83
+ if (shell.status !== "running") {
84
+ completedShells.push([shellId, shell]);
85
+ }
86
+ }
87
+ let removed = 0;
88
+ const now = Date.now();
89
+ for (const [shellId, shell] of completedShells) {
90
+ if (shell.endTime && now - shell.endTime > MAX_COMPLETED_SHELL_AGE) {
91
+ this.cleanupListeners(shell);
92
+ this.shells.delete(shellId);
93
+ removed++;
94
+ }
95
+ }
96
+ const remainingCompleted = completedShells.filter(([shellId]) => this.shells.has(shellId)).sort((a, b) => (a[1].endTime || 0) - (b[1].endTime || 0));
97
+ while (remainingCompleted.length > MAX_COMPLETED_SHELLS) {
98
+ const oldest = remainingCompleted.shift();
99
+ if (oldest) {
100
+ const [shellId, shell] = oldest;
101
+ this.cleanupListeners(shell);
102
+ this.shells.delete(shellId);
103
+ removed++;
104
+ }
105
+ }
106
+ if (removed > 0) {
107
+ this.emit("autoCleaned", removed);
108
+ this.emitListChange();
109
+ }
110
+ }
50
111
  /**
51
112
  * Create a new background shell and start executing the command
52
113
  */
@@ -74,46 +135,55 @@ class BackgroundShellManager extends EventEmitter {
74
135
  lastReadStdoutIndex: 0,
75
136
  lastReadStderrIndex: 0
76
137
  };
77
- childProcess.stdout?.on("data", (data) => {
78
- const lines = data.toString().split("\n");
79
- shell.stdout.push(...lines);
80
- if (shell.stdout.length > MAX_OUTPUT_LINES) {
81
- const removed = shell.stdout.length - MAX_OUTPUT_LINES;
82
- shell.stdout = shell.stdout.slice(removed);
83
- shell.lastReadStdoutIndex = Math.max(
84
- 0,
85
- shell.lastReadStdoutIndex - removed
86
- );
87
- }
88
- this.emit("output", shellId, "stdout", lines);
89
- });
90
- childProcess.stderr?.on("data", (data) => {
91
- const lines = data.toString().split("\n");
92
- shell.stderr.push(...lines);
93
- if (shell.stderr.length > MAX_OUTPUT_LINES) {
94
- const removed = shell.stderr.length - MAX_OUTPUT_LINES;
95
- shell.stderr = shell.stderr.slice(removed);
96
- shell.lastReadStderrIndex = Math.max(
97
- 0,
98
- shell.lastReadStderrIndex - removed
99
- );
138
+ const listeners = {
139
+ stdout: (data) => {
140
+ const lines = data.toString().split("\n");
141
+ shell.stdout.push(...lines);
142
+ if (shell.stdout.length > MAX_OUTPUT_LINES) {
143
+ const removed = shell.stdout.length - MAX_OUTPUT_LINES;
144
+ shell.stdout = shell.stdout.slice(removed);
145
+ shell.lastReadStdoutIndex = Math.max(
146
+ 0,
147
+ shell.lastReadStdoutIndex - removed
148
+ );
149
+ }
150
+ this.emit("output", shellId, "stdout", lines);
151
+ },
152
+ stderr: (data) => {
153
+ const lines = data.toString().split("\n");
154
+ shell.stderr.push(...lines);
155
+ if (shell.stderr.length > MAX_OUTPUT_LINES) {
156
+ const removed = shell.stderr.length - MAX_OUTPUT_LINES;
157
+ shell.stderr = shell.stderr.slice(removed);
158
+ shell.lastReadStderrIndex = Math.max(
159
+ 0,
160
+ shell.lastReadStderrIndex - removed
161
+ );
162
+ }
163
+ this.emit("output", shellId, "stderr", lines);
164
+ },
165
+ exit: (code, signal) => {
166
+ shell.status = signal ? "killed" : "done";
167
+ shell.exitCode = code ?? void 0;
168
+ shell.endTime = Date.now();
169
+ this.emit("statusChange", shellId, shell.status);
170
+ this.emitListChange();
171
+ this.cleanupListeners(shell);
172
+ },
173
+ error: (error) => {
174
+ logError(`Background shell ${shellId} error: ${error.message}`);
175
+ shell.status = "killed";
176
+ shell.endTime = Date.now();
177
+ this.emit("statusChange", shellId, shell.status);
178
+ this.emitListChange();
179
+ this.cleanupListeners(shell);
100
180
  }
101
- this.emit("output", shellId, "stderr", lines);
102
- });
103
- childProcess.on("exit", (code, signal) => {
104
- shell.status = signal ? "killed" : "done";
105
- shell.exitCode = code ?? void 0;
106
- shell.endTime = Date.now();
107
- this.emit("statusChange", shellId, shell.status);
108
- this.emitListChange();
109
- });
110
- childProcess.on("error", (error) => {
111
- logError(`Background shell ${shellId} error: ${error.message}`);
112
- shell.status = "killed";
113
- shell.endTime = Date.now();
114
- this.emit("statusChange", shellId, shell.status);
115
- this.emitListChange();
116
- });
181
+ };
182
+ shell._listeners = listeners;
183
+ if (listeners.stdout) childProcess.stdout?.on("data", listeners.stdout);
184
+ if (listeners.stderr) childProcess.stderr?.on("data", listeners.stderr);
185
+ if (listeners.exit) childProcess.on("exit", listeners.exit);
186
+ if (listeners.error) childProcess.on("error", listeners.error);
117
187
  this.shells.set(shellId, shell);
118
188
  this.emit("shellCreated", shellId);
119
189
  this.emitListChange();
@@ -126,6 +196,29 @@ class BackgroundShellManager extends EventEmitter {
126
196
  emitListChange() {
127
197
  this.emit("listChange", this.list());
128
198
  }
199
+ /**
200
+ * 🔧 CRITICAL FIX: Clean up event listeners to prevent memory leaks
201
+ * This removes all event listeners attached to the child process
202
+ */
203
+ cleanupListeners(shell) {
204
+ if (!shell._listeners || !shell.process) {
205
+ return;
206
+ }
207
+ const { stdout, stderr, exit, error } = shell._listeners;
208
+ if (stdout) {
209
+ shell.process.stdout?.off("data", stdout);
210
+ }
211
+ if (stderr) {
212
+ shell.process.stderr?.off("data", stderr);
213
+ }
214
+ if (exit) {
215
+ shell.process.off("exit", exit);
216
+ }
217
+ if (error) {
218
+ shell.process.off("error", error);
219
+ }
220
+ delete shell._listeners;
221
+ }
129
222
  /**
130
223
  * Get a background shell by ID
131
224
  */
@@ -204,6 +297,7 @@ class BackgroundShellManager extends EventEmitter {
204
297
  }
205
298
  shell.status = "killed";
206
299
  shell.endTime = Date.now();
300
+ this.cleanupListeners(shell);
207
301
  this.emit("statusChange", shellId, "killed");
208
302
  this.emitListChange();
209
303
  return true;
@@ -213,6 +307,7 @@ class BackgroundShellManager extends EventEmitter {
213
307
  error
214
308
  );
215
309
  logError(`Failed to kill shell ${shellId}: ${error}`);
310
+ this.cleanupListeners(shell);
216
311
  return false;
217
312
  }
218
313
  }
@@ -240,6 +335,7 @@ class BackgroundShellManager extends EventEmitter {
240
335
  remove(shellId) {
241
336
  const shell = this.shells.get(shellId);
242
337
  if (!shell || shell.status === "running") return false;
338
+ this.cleanupListeners(shell);
243
339
  this.shells.delete(shellId);
244
340
  this.emit("shellRemoved", shellId);
245
341
  this.emitListChange();
@@ -253,6 +349,7 @@ class BackgroundShellManager extends EventEmitter {
253
349
  let removed = 0;
254
350
  for (const [shellId, shell] of this.shells.entries()) {
255
351
  if (shell.status !== "running" && shell.endTime && now - shell.endTime > maxAge) {
352
+ this.cleanupListeners(shell);
256
353
  this.shells.delete(shellId);
257
354
  removed++;
258
355
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/BackgroundShellManager.ts"],
4
- "sourcesContent": ["import { spawn, type ChildProcess } from 'child_process'\nimport { EventEmitter } from 'events'\nimport { randomUUID } from 'crypto'\nimport { logError } from './log'\n\nexport type BackgroundShellStatus = 'running' | 'done' | 'killed'\n\nexport interface BackgroundShell {\n id: string\n command: string\n status: BackgroundShellStatus\n startTime: number\n endTime?: number\n process: ChildProcess\n stdout: string[]\n stderr: string[]\n exitCode?: number\n lastReadStdoutIndex: number\n lastReadStderrIndex: number\n}\n\nconst MAX_OUTPUT_LINES = 1000 // Keep last N lines in memory\nconst REGEX_CACHE_SIZE = 50 // LRU cache size for compiled regexes\n\n// Simple LRU cache for compiled RegExp objects\nclass RegExpCache {\n private cache = new Map<string, RegExp>()\n private maxSize: number\n\n constructor(maxSize: number = REGEX_CACHE_SIZE) {\n this.maxSize = maxSize\n }\n\n get(pattern: string): RegExp | undefined {\n const regex = this.cache.get(pattern)\n if (regex) {\n // Move to end (most recently used)\n this.cache.delete(pattern)\n this.cache.set(pattern, regex)\n }\n return regex\n }\n\n set(pattern: string, regex: RegExp): void {\n // Evict oldest if at capacity\n if (this.cache.size >= this.maxSize) {\n const oldest = this.cache.keys().next().value\n if (oldest) this.cache.delete(oldest)\n }\n this.cache.set(pattern, regex)\n }\n\n getOrCreate(pattern: string): RegExp {\n let regex = this.get(pattern)\n if (!regex) {\n regex = new RegExp(pattern)\n this.set(pattern, regex)\n }\n return regex\n }\n}\n\nexport class BackgroundShellManager extends EventEmitter {\n private static instance: BackgroundShellManager | null = null\n private shells: Map<string, BackgroundShell> = new Map()\n private regexCache = new RegExpCache()\n\n private constructor() {\n super()\n }\n\n static getInstance(): BackgroundShellManager {\n if (!BackgroundShellManager.instance) {\n BackgroundShellManager.instance = new BackgroundShellManager()\n }\n return BackgroundShellManager.instance\n }\n\n /**\n * Create a new background shell and start executing the command\n */\n create(command: string, cwd?: string): string {\n const shellId = randomUUID().slice(0, 8)\n\n // Spawn the command in a shell\n const childProcess = spawn(command, [], {\n shell: true,\n cwd: cwd || process.cwd(),\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: true, // Create a new process group for easier cleanup\n env: {\n ...process.env,\n GIT_EDITOR: 'true',\n },\n })\n\n const shell: BackgroundShell = {\n id: shellId,\n command,\n status: 'running',\n startTime: Date.now(),\n process: childProcess,\n stdout: [],\n stderr: [],\n lastReadStdoutIndex: 0,\n lastReadStderrIndex: 0,\n }\n\n // Capture stdout\n childProcess.stdout?.on('data', (data: Buffer) => {\n const lines = data.toString().split('\\n')\n shell.stdout.push(...lines)\n\n // Keep only last MAX_OUTPUT_LINES\n if (shell.stdout.length > MAX_OUTPUT_LINES) {\n const removed = shell.stdout.length - MAX_OUTPUT_LINES\n shell.stdout = shell.stdout.slice(removed)\n shell.lastReadStdoutIndex = Math.max(\n 0,\n shell.lastReadStdoutIndex - removed,\n )\n }\n\n this.emit('output', shellId, 'stdout', lines)\n })\n\n // Capture stderr\n childProcess.stderr?.on('data', (data: Buffer) => {\n const lines = data.toString().split('\\n')\n shell.stderr.push(...lines)\n\n // Keep only last MAX_OUTPUT_LINES\n if (shell.stderr.length > MAX_OUTPUT_LINES) {\n const removed = shell.stderr.length - MAX_OUTPUT_LINES\n shell.stderr = shell.stderr.slice(removed)\n shell.lastReadStderrIndex = Math.max(\n 0,\n shell.lastReadStderrIndex - removed,\n )\n }\n\n this.emit('output', shellId, 'stderr', lines)\n })\n\n // Handle process exit\n childProcess.on('exit', (code, signal) => {\n shell.status = signal ? 'killed' : 'done'\n shell.exitCode = code ?? undefined\n shell.endTime = Date.now()\n this.emit('statusChange', shellId, shell.status)\n this.emitListChange()\n })\n\n childProcess.on('error', error => {\n logError(`Background shell ${shellId} error: ${error.message}`)\n shell.status = 'killed'\n shell.endTime = Date.now()\n this.emit('statusChange', shellId, shell.status)\n this.emitListChange()\n })\n\n this.shells.set(shellId, shell)\n this.emit('shellCreated', shellId)\n this.emitListChange()\n\n return shellId\n }\n\n /**\n * Emit a list change event with the current shell list\n * Used for event-driven updates instead of polling\n */\n private emitListChange(): void {\n this.emit('listChange', this.list())\n }\n\n /**\n * Get a background shell by ID\n */\n get(shellId: string): BackgroundShell | undefined {\n return this.shells.get(shellId)\n }\n\n /**\n * Get new output since last read\n */\n getNewOutput(\n shellId: string,\n filter?: string,\n ): {\n stdout: string[]\n stderr: string[]\n } | null {\n const shell = this.shells.get(shellId)\n if (!shell) return null\n\n let newStdout = shell.stdout.slice(shell.lastReadStdoutIndex)\n let newStderr = shell.stderr.slice(shell.lastReadStderrIndex)\n\n // Apply regex filter if provided (using cached regex)\n if (filter) {\n try {\n const regex = this.regexCache.getOrCreate(filter)\n newStdout = newStdout.filter(line => regex.test(line))\n newStderr = newStderr.filter(line => regex.test(line))\n } catch (e) {\n logError(`Invalid regex filter: ${filter}`)\n }\n }\n\n // Update read indices\n shell.lastReadStdoutIndex = shell.stdout.length\n shell.lastReadStderrIndex = shell.stderr.length\n\n return { stdout: newStdout, stderr: newStderr }\n }\n\n /**\n * Get all output (entire buffer)\n */\n getAllOutput(shellId: string): {\n stdout: string[]\n stderr: string[]\n } | null {\n const shell = this.shells.get(shellId)\n if (!shell) return null\n\n return {\n stdout: [...shell.stdout],\n stderr: [...shell.stderr],\n }\n }\n\n /**\n * Kill a background shell\n */\n kill(shellId: string): boolean {\n const shell = this.shells.get(shellId)\n if (!shell) {\n console.log(\n `[BackgroundShellManager] Cannot kill: shell ${shellId} not found`,\n )\n return false\n }\n\n if (shell.status === 'running') {\n try {\n const pid = shell.process.pid\n if (!pid) {\n console.log(\n `[BackgroundShellManager] Cannot kill: shell ${shellId} has no PID`,\n )\n return false\n }\n\n console.log(\n `[BackgroundShellManager] Killing shell ${shellId} (PID: ${pid}, command: ${shell.command})`,\n )\n\n // Kill the process and all its children\n if (process.platform === 'win32') {\n spawn('taskkill', ['/pid', pid.toString(), '/f', '/t'])\n } else {\n // Send SIGKILL to process group (more forceful than SIGTERM)\n // The negative PID sends signal to the entire process group\n try {\n process.kill(-pid, 'SIGKILL')\n console.log(\n `[BackgroundShellManager] Sent SIGKILL to process group -${pid}`,\n )\n } catch (e) {\n // If process group kill fails, try killing just the main process\n console.log(\n `[BackgroundShellManager] Process group kill failed, trying main process only`,\n )\n process.kill(pid, 'SIGKILL')\n }\n }\n\n shell.status = 'killed'\n shell.endTime = Date.now()\n this.emit('statusChange', shellId, 'killed')\n this.emitListChange()\n return true\n } catch (error) {\n console.error(\n `[BackgroundShellManager] Failed to kill shell ${shellId}:`,\n error,\n )\n logError(`Failed to kill shell ${shellId}: ${error}`)\n return false\n }\n }\n\n console.log(\n `[BackgroundShellManager] Shell ${shellId} is not running (status: ${shell.status})`,\n )\n return false\n }\n\n /**\n * List all background shells\n */\n list(): BackgroundShell[] {\n return Array.from(this.shells.values())\n }\n\n /**\n * Get shell status\n */\n getStatus(shellId: string): BackgroundShellStatus | null {\n const shell = this.shells.get(shellId)\n return shell ? shell.status : null\n }\n\n /**\n * Remove a shell from the manager (only if done or killed)\n */\n remove(shellId: string): boolean {\n const shell = this.shells.get(shellId)\n if (!shell || shell.status === 'running') return false\n\n this.shells.delete(shellId)\n this.emit('shellRemoved', shellId)\n this.emitListChange()\n return true\n }\n\n /**\n * Clean up old completed/killed shells\n */\n cleanup(maxAge: number = 3600000): number {\n const now = Date.now()\n let removed = 0\n\n for (const [shellId, shell] of this.shells.entries()) {\n if (\n shell.status !== 'running' &&\n shell.endTime &&\n now - shell.endTime > maxAge\n ) {\n this.shells.delete(shellId)\n removed++\n }\n }\n\n if (removed > 0) {\n this.emit('cleaned', removed)\n this.emitListChange()\n }\n\n return removed\n }\n\n /**\n * Get summary of shell for display\n */\n getSummary(shellId: string): string | null {\n const shell = this.shells.get(shellId)\n if (!shell) return null\n\n const duration = shell.endTime\n ? Math.floor((shell.endTime - shell.startTime) / 1000)\n : Math.floor((Date.now() - shell.startTime) / 1000)\n\n // Get last few lines of output for preview\n const lastLines = shell.stdout.slice(-2).join(' ').slice(0, 50)\n const preview = lastLines ? ` ${lastLines}\u2026` : ''\n\n return `${shell.command}${preview}`\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { spawn, type ChildProcess } from 'child_process'\nimport { EventEmitter } from 'events'\nimport { randomUUID } from 'crypto'\nimport { logError } from './log'\n\nexport type BackgroundShellStatus = 'running' | 'done' | 'killed'\n\n// \uD83D\uDD27 Internal event listener references for cleanup\ninterface ShellListeners {\n stdout?: (data: Buffer) => void\n stderr?: (data: Buffer) => void\n exit?: (code: number | null, signal: NodeJS.Signals | null) => void\n error?: (error: Error) => void\n}\n\nexport interface BackgroundShell {\n id: string\n command: string\n status: BackgroundShellStatus\n startTime: number\n endTime?: number\n process: ChildProcess\n stdout: string[]\n stderr: string[]\n exitCode?: number\n lastReadStdoutIndex: number\n lastReadStderrIndex: number\n /** @internal Event listeners for cleanup */\n _listeners?: ShellListeners\n}\n\nconst MAX_OUTPUT_LINES = 1000 // Keep last N lines in memory\nconst REGEX_CACHE_SIZE = 50 // LRU cache size for compiled regexes\n\n// Simple LRU cache for compiled RegExp objects\nclass RegExpCache {\n private cache = new Map<string, RegExp>()\n private maxSize: number\n\n constructor(maxSize: number = REGEX_CACHE_SIZE) {\n this.maxSize = maxSize\n }\n\n get(pattern: string): RegExp | undefined {\n const regex = this.cache.get(pattern)\n if (regex) {\n // Move to end (most recently used)\n this.cache.delete(pattern)\n this.cache.set(pattern, regex)\n }\n return regex\n }\n\n set(pattern: string, regex: RegExp): void {\n // Evict oldest if at capacity\n if (this.cache.size >= this.maxSize) {\n const oldest = this.cache.keys().next().value\n if (oldest) this.cache.delete(oldest)\n }\n this.cache.set(pattern, regex)\n }\n\n getOrCreate(pattern: string): RegExp {\n let regex = this.get(pattern)\n if (!regex) {\n regex = new RegExp(pattern)\n this.set(pattern, regex)\n }\n return regex\n }\n}\n\n// Auto-cleanup configuration\nconst AUTO_CLEANUP_INTERVAL = 5 * 60 * 1000 // 5 minutes\nconst MAX_COMPLETED_SHELL_AGE = 30 * 60 * 1000 // 30 minutes\nconst MAX_COMPLETED_SHELLS = 50 // Maximum completed shells to keep\n\nexport class BackgroundShellManager extends EventEmitter {\n private static instance: BackgroundShellManager | null = null\n private shells: Map<string, BackgroundShell> = new Map()\n private regexCache = new RegExpCache()\n private autoCleanupTimer: ReturnType<typeof setInterval> | null = null\n\n private constructor() {\n super()\n // Start auto-cleanup timer\n this.startAutoCleanup()\n }\n\n static getInstance(): BackgroundShellManager {\n if (!BackgroundShellManager.instance) {\n BackgroundShellManager.instance = new BackgroundShellManager()\n }\n return BackgroundShellManager.instance\n }\n\n /**\n * Start automatic cleanup timer\n * Cleans up old completed shells periodically to prevent memory bloat\n */\n private startAutoCleanup(): void {\n if (this.autoCleanupTimer) return\n\n this.autoCleanupTimer = setInterval(() => {\n this.autoCleanup()\n }, AUTO_CLEANUP_INTERVAL)\n\n // Don't keep the process alive just for cleanup\n this.autoCleanupTimer.unref()\n }\n\n /**\n * Stop automatic cleanup timer\n * Should be called when shutting down\n */\n stopAutoCleanup(): void {\n if (this.autoCleanupTimer) {\n clearInterval(this.autoCleanupTimer)\n this.autoCleanupTimer = null\n }\n }\n\n /**\n * Automatic cleanup of old and excessive completed shells\n * Called periodically by the auto-cleanup timer\n */\n private autoCleanup(): void {\n const completedShells: Array<[string, BackgroundShell]> = []\n\n // Collect all non-running shells\n for (const [shellId, shell] of this.shells.entries()) {\n if (shell.status !== 'running') {\n completedShells.push([shellId, shell])\n }\n }\n\n let removed = 0\n\n // First, remove shells older than MAX_COMPLETED_SHELL_AGE\n const now = Date.now()\n for (const [shellId, shell] of completedShells) {\n if (shell.endTime && now - shell.endTime > MAX_COMPLETED_SHELL_AGE) {\n this.cleanupListeners(shell)\n this.shells.delete(shellId)\n removed++\n }\n }\n\n // Then, if still too many, remove oldest until under limit\n const remainingCompleted = completedShells\n .filter(([shellId]) => this.shells.has(shellId))\n .sort((a, b) => (a[1].endTime || 0) - (b[1].endTime || 0))\n\n while (remainingCompleted.length > MAX_COMPLETED_SHELLS) {\n const oldest = remainingCompleted.shift()\n if (oldest) {\n const [shellId, shell] = oldest\n this.cleanupListeners(shell)\n this.shells.delete(shellId)\n removed++\n }\n }\n\n if (removed > 0) {\n this.emit('autoCleaned', removed)\n this.emitListChange()\n }\n }\n\n /**\n * Create a new background shell and start executing the command\n */\n create(command: string, cwd?: string): string {\n const shellId = randomUUID().slice(0, 8)\n\n // Spawn the command in a shell\n const childProcess = spawn(command, [], {\n shell: true,\n cwd: cwd || process.cwd(),\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: true, // Create a new process group for easier cleanup\n env: {\n ...process.env,\n GIT_EDITOR: 'true',\n },\n })\n\n const shell: BackgroundShell = {\n id: shellId,\n command,\n status: 'running',\n startTime: Date.now(),\n process: childProcess,\n stdout: [],\n stderr: [],\n lastReadStdoutIndex: 0,\n lastReadStderrIndex: 0,\n }\n\n // \uD83D\uDD27 CRITICAL FIX: Create named listener functions for cleanup\n const listeners: ShellListeners = {\n stdout: (data: Buffer) => {\n const lines = data.toString().split('\\n')\n shell.stdout.push(...lines)\n\n // Keep only last MAX_OUTPUT_LINES\n if (shell.stdout.length > MAX_OUTPUT_LINES) {\n const removed = shell.stdout.length - MAX_OUTPUT_LINES\n shell.stdout = shell.stdout.slice(removed)\n shell.lastReadStdoutIndex = Math.max(\n 0,\n shell.lastReadStdoutIndex - removed,\n )\n }\n\n this.emit('output', shellId, 'stdout', lines)\n },\n stderr: (data: Buffer) => {\n const lines = data.toString().split('\\n')\n shell.stderr.push(...lines)\n\n // Keep only last MAX_OUTPUT_LINES\n if (shell.stderr.length > MAX_OUTPUT_LINES) {\n const removed = shell.stderr.length - MAX_OUTPUT_LINES\n shell.stderr = shell.stderr.slice(removed)\n shell.lastReadStderrIndex = Math.max(\n 0,\n shell.lastReadStderrIndex - removed,\n )\n }\n\n this.emit('output', shellId, 'stderr', lines)\n },\n exit: (code, signal) => {\n shell.status = signal ? 'killed' : 'done'\n shell.exitCode = code ?? undefined\n shell.endTime = Date.now()\n this.emit('statusChange', shellId, shell.status)\n this.emitListChange()\n // \uD83D\uDD27 Auto-cleanup listeners when process exits\n this.cleanupListeners(shell)\n },\n error: error => {\n logError(`Background shell ${shellId} error: ${error.message}`)\n shell.status = 'killed'\n shell.endTime = Date.now()\n this.emit('statusChange', shellId, shell.status)\n this.emitListChange()\n // \uD83D\uDD27 Auto-cleanup listeners when process errors\n this.cleanupListeners(shell)\n },\n }\n\n // Store listeners for later cleanup\n shell._listeners = listeners\n\n // Attach listeners\n if (listeners.stdout) childProcess.stdout?.on('data', listeners.stdout)\n if (listeners.stderr) childProcess.stderr?.on('data', listeners.stderr)\n if (listeners.exit) childProcess.on('exit', listeners.exit)\n if (listeners.error) childProcess.on('error', listeners.error)\n\n this.shells.set(shellId, shell)\n this.emit('shellCreated', shellId)\n this.emitListChange()\n\n return shellId\n }\n\n /**\n * Emit a list change event with the current shell list\n * Used for event-driven updates instead of polling\n */\n private emitListChange(): void {\n this.emit('listChange', this.list())\n }\n\n /**\n * \uD83D\uDD27 CRITICAL FIX: Clean up event listeners to prevent memory leaks\n * This removes all event listeners attached to the child process\n */\n private cleanupListeners(shell: BackgroundShell): void {\n if (!shell._listeners || !shell.process) {\n return\n }\n\n const { stdout, stderr, exit, error } = shell._listeners\n\n // Remove all listeners\n if (stdout) {\n shell.process.stdout?.off('data', stdout)\n }\n if (stderr) {\n shell.process.stderr?.off('data', stderr)\n }\n if (exit) {\n shell.process.off('exit', exit)\n }\n if (error) {\n shell.process.off('error', error)\n }\n\n // Clear the reference\n delete shell._listeners\n }\n\n /**\n * Get a background shell by ID\n */\n get(shellId: string): BackgroundShell | undefined {\n return this.shells.get(shellId)\n }\n\n /**\n * Get new output since last read\n */\n getNewOutput(\n shellId: string,\n filter?: string,\n ): {\n stdout: string[]\n stderr: string[]\n } | null {\n const shell = this.shells.get(shellId)\n if (!shell) return null\n\n let newStdout = shell.stdout.slice(shell.lastReadStdoutIndex)\n let newStderr = shell.stderr.slice(shell.lastReadStderrIndex)\n\n // Apply regex filter if provided (using cached regex)\n if (filter) {\n try {\n const regex = this.regexCache.getOrCreate(filter)\n newStdout = newStdout.filter(line => regex.test(line))\n newStderr = newStderr.filter(line => regex.test(line))\n } catch (e) {\n logError(`Invalid regex filter: ${filter}`)\n }\n }\n\n // Update read indices\n shell.lastReadStdoutIndex = shell.stdout.length\n shell.lastReadStderrIndex = shell.stderr.length\n\n return { stdout: newStdout, stderr: newStderr }\n }\n\n /**\n * Get all output (entire buffer)\n */\n getAllOutput(shellId: string): {\n stdout: string[]\n stderr: string[]\n } | null {\n const shell = this.shells.get(shellId)\n if (!shell) return null\n\n return {\n stdout: [...shell.stdout],\n stderr: [...shell.stderr],\n }\n }\n\n /**\n * Kill a background shell\n */\n kill(shellId: string): boolean {\n const shell = this.shells.get(shellId)\n if (!shell) {\n console.log(\n `[BackgroundShellManager] Cannot kill: shell ${shellId} not found`,\n )\n return false\n }\n\n if (shell.status === 'running') {\n try {\n const pid = shell.process.pid\n if (!pid) {\n console.log(\n `[BackgroundShellManager] Cannot kill: shell ${shellId} has no PID`,\n )\n return false\n }\n\n console.log(\n `[BackgroundShellManager] Killing shell ${shellId} (PID: ${pid}, command: ${shell.command})`,\n )\n\n // Kill the process and all its children\n if (process.platform === 'win32') {\n spawn('taskkill', ['/pid', pid.toString(), '/f', '/t'])\n } else {\n // Send SIGKILL to process group (more forceful than SIGTERM)\n // The negative PID sends signal to the entire process group\n try {\n process.kill(-pid, 'SIGKILL')\n console.log(\n `[BackgroundShellManager] Sent SIGKILL to process group -${pid}`,\n )\n } catch (e) {\n // If process group kill fails, try killing just the main process\n console.log(\n `[BackgroundShellManager] Process group kill failed, trying main process only`,\n )\n process.kill(pid, 'SIGKILL')\n }\n }\n\n shell.status = 'killed'\n shell.endTime = Date.now()\n // \uD83D\uDD27 CRITICAL FIX: Clean up listeners when killing shell\n this.cleanupListeners(shell)\n this.emit('statusChange', shellId, 'killed')\n this.emitListChange()\n return true\n } catch (error) {\n console.error(\n `[BackgroundShellManager] Failed to kill shell ${shellId}:`,\n error,\n )\n logError(`Failed to kill shell ${shellId}: ${error}`)\n // \uD83D\uDD27 Still try to clean up listeners even on error\n this.cleanupListeners(shell)\n return false\n }\n }\n\n console.log(\n `[BackgroundShellManager] Shell ${shellId} is not running (status: ${shell.status})`,\n )\n return false\n }\n\n /**\n * List all background shells\n */\n list(): BackgroundShell[] {\n return Array.from(this.shells.values())\n }\n\n /**\n * Get shell status\n */\n getStatus(shellId: string): BackgroundShellStatus | null {\n const shell = this.shells.get(shellId)\n return shell ? shell.status : null\n }\n\n /**\n * Remove a shell from the manager (only if done or killed)\n */\n remove(shellId: string): boolean {\n const shell = this.shells.get(shellId)\n if (!shell || shell.status === 'running') return false\n\n // \uD83D\uDD27 CRITICAL FIX: Clean up listeners before removing\n this.cleanupListeners(shell)\n this.shells.delete(shellId)\n this.emit('shellRemoved', shellId)\n this.emitListChange()\n return true\n }\n\n /**\n * Clean up old completed/killed shells\n */\n cleanup(maxAge: number = 3600000): number {\n const now = Date.now()\n let removed = 0\n\n for (const [shellId, shell] of this.shells.entries()) {\n if (\n shell.status !== 'running' &&\n shell.endTime &&\n now - shell.endTime > maxAge\n ) {\n // \uD83D\uDD27 CRITICAL FIX: Clean up listeners before deleting\n this.cleanupListeners(shell)\n this.shells.delete(shellId)\n removed++\n }\n }\n\n if (removed > 0) {\n this.emit('cleaned', removed)\n this.emitListChange()\n }\n\n return removed\n }\n\n /**\n * Get summary of shell for display\n */\n getSummary(shellId: string): string | null {\n const shell = this.shells.get(shellId)\n if (!shell) return null\n\n const duration = shell.endTime\n ? Math.floor((shell.endTime - shell.startTime) / 1000)\n : Math.floor((Date.now() - shell.startTime) / 1000)\n\n // Get last few lines of output for preview\n const lastLines = shell.stdout.slice(-2).join(' ').slice(0, 50)\n const preview = lastLines ? ` ${lastLines}\u2026` : ''\n\n return `${shell.command}${preview}`\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,102 @@
1
+ class MessageBatchBuffer {
2
+ buffer = [];
3
+ flushTimer = null;
4
+ flushCallback;
5
+ options;
6
+ messageCount = 0;
7
+ flushCount = 0;
8
+ constructor(flushCallback, options = {}) {
9
+ this.flushCallback = flushCallback;
10
+ this.options = {
11
+ flushInterval: options.flushInterval ?? 100,
12
+ maxBatchSize: options.maxBatchSize ?? 50,
13
+ debug: options.debug ?? false
14
+ };
15
+ }
16
+ /**
17
+ * Add a message to the buffer
18
+ * Will trigger immediate flush if buffer size exceeds maxBatchSize
19
+ */
20
+ add(message) {
21
+ this.buffer.push(message);
22
+ this.messageCount++;
23
+ if (this.options.debug) {
24
+ console.log(
25
+ `[MessageBatchBuffer] Added message (buffer: ${this.buffer.length}/${this.options.maxBatchSize})`
26
+ );
27
+ }
28
+ if (this.buffer.length >= this.options.maxBatchSize) {
29
+ if (this.options.debug) {
30
+ console.log(`[MessageBatchBuffer] Buffer full, forcing immediate flush`);
31
+ }
32
+ this.flush();
33
+ return;
34
+ }
35
+ this.scheduleFlush();
36
+ }
37
+ /**
38
+ * Schedule a flush after flushInterval
39
+ * Cancels any pending flush and reschedules
40
+ */
41
+ scheduleFlush() {
42
+ if (this.flushTimer !== null) {
43
+ clearTimeout(this.flushTimer);
44
+ }
45
+ this.flushTimer = setTimeout(() => {
46
+ this.flush();
47
+ }, this.options.flushInterval);
48
+ }
49
+ /**
50
+ * Immediately flush all buffered messages
51
+ */
52
+ flush() {
53
+ if (this.buffer.length === 0) {
54
+ return;
55
+ }
56
+ const messagesToFlush = [...this.buffer];
57
+ this.buffer = [];
58
+ this.flushCount++;
59
+ if (this.flushTimer !== null) {
60
+ clearTimeout(this.flushTimer);
61
+ this.flushTimer = null;
62
+ }
63
+ if (this.options.debug) {
64
+ console.log(
65
+ `[MessageBatchBuffer] Flush #${this.flushCount}: ${messagesToFlush.length} messages`
66
+ );
67
+ }
68
+ this.flushCallback(messagesToFlush);
69
+ }
70
+ /**
71
+ * Clean up timers and discard remaining messages
72
+ *
73
+ * CRITICAL: Does NOT flush to avoid race condition with exit flow
74
+ * When component unmounts, we're likely exiting and don't want to
75
+ * trigger additional renders that would clear the cost summary.
76
+ *
77
+ * Any pending messages will be discarded on unmount, which is acceptable
78
+ * since we're exiting anyway.
79
+ */
80
+ dispose() {
81
+ if (this.flushTimer !== null) {
82
+ clearTimeout(this.flushTimer);
83
+ this.flushTimer = null;
84
+ }
85
+ this.buffer = [];
86
+ }
87
+ /**
88
+ * Get statistics about buffer performance
89
+ */
90
+ getStats() {
91
+ return {
92
+ totalMessages: this.messageCount,
93
+ totalFlushes: this.flushCount,
94
+ averageBatchSize: this.flushCount > 0 ? this.messageCount / this.flushCount : 0,
95
+ currentBufferSize: this.buffer.length
96
+ };
97
+ }
98
+ }
99
+ export {
100
+ MessageBatchBuffer
101
+ };
102
+ //# sourceMappingURL=MessageBatchBuffer.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/utils/MessageBatchBuffer.ts"],
4
+ "sourcesContent": ["/**\n * MessageBatchBuffer - Unified message batching system to eliminate screen flicker\n *\n * Problem: Each streaming message triggers immediate React state update \u2192 render \u2192 flicker\n * Solution: Buffer messages and flush in batches at controlled intervals\n *\n * Benefits:\n * - Reduces render frequency by batching multiple messages into single update\n * - Eliminates visual flicker from rapid successive renders\n * - Balances responsiveness with performance\n *\n * @example\n * const buffer = new MessageBatchBuffer((messages) => {\n * setMessages(prev => [...prev, ...messages])\n * }, { flushInterval: 100 })\n *\n * // Instead of: setMessages(prev => [...prev, message])\n * buffer.add(message)\n */\n\nimport type { Message } from '@minto-types/conversation'\n\nexport interface MessageBatchBufferOptions {\n /**\n * How often to flush buffered messages (ms)\n * @default 100 - balances responsiveness with batching efficiency\n */\n flushInterval?: number\n\n /**\n * Maximum messages to buffer before forcing a flush\n * Prevents unbounded memory growth\n * @default 50\n */\n maxBatchSize?: number\n\n /**\n * Enable debug logging\n * @default false\n */\n debug?: boolean\n}\n\nexport class MessageBatchBuffer {\n private buffer: Message[] = []\n private flushTimer: NodeJS.Timeout | null = null\n private readonly flushCallback: (messages: Message[]) => void\n private readonly options: Required<MessageBatchBufferOptions>\n private messageCount = 0\n private flushCount = 0\n\n constructor(\n flushCallback: (messages: Message[]) => void,\n options: MessageBatchBufferOptions = {},\n ) {\n this.flushCallback = flushCallback\n this.options = {\n flushInterval: options.flushInterval ?? 100,\n maxBatchSize: options.maxBatchSize ?? 50,\n debug: options.debug ?? false,\n }\n }\n\n /**\n * Add a message to the buffer\n * Will trigger immediate flush if buffer size exceeds maxBatchSize\n */\n add(message: Message): void {\n this.buffer.push(message)\n this.messageCount++\n\n if (this.options.debug) {\n console.log(\n `[MessageBatchBuffer] Added message (buffer: ${this.buffer.length}/${this.options.maxBatchSize})`,\n )\n }\n\n // Force flush if buffer is full\n if (this.buffer.length >= this.options.maxBatchSize) {\n if (this.options.debug) {\n console.log(`[MessageBatchBuffer] Buffer full, forcing immediate flush`)\n }\n this.flush()\n return\n }\n\n // Schedule deferred flush\n this.scheduleFlush()\n }\n\n /**\n * Schedule a flush after flushInterval\n * Cancels any pending flush and reschedules\n */\n private scheduleFlush(): void {\n // Clear any existing timer\n if (this.flushTimer !== null) {\n clearTimeout(this.flushTimer)\n }\n\n // Schedule new flush\n this.flushTimer = setTimeout(() => {\n this.flush()\n }, this.options.flushInterval)\n }\n\n /**\n * Immediately flush all buffered messages\n */\n flush(): void {\n if (this.buffer.length === 0) {\n return\n }\n\n const messagesToFlush = [...this.buffer]\n this.buffer = []\n this.flushCount++\n\n if (this.flushTimer !== null) {\n clearTimeout(this.flushTimer)\n this.flushTimer = null\n }\n\n if (this.options.debug) {\n console.log(\n `[MessageBatchBuffer] Flush #${this.flushCount}: ${messagesToFlush.length} messages`,\n )\n }\n\n this.flushCallback(messagesToFlush)\n }\n\n /**\n * Clean up timers and discard remaining messages\n *\n * CRITICAL: Does NOT flush to avoid race condition with exit flow\n * When component unmounts, we're likely exiting and don't want to\n * trigger additional renders that would clear the cost summary.\n *\n * Any pending messages will be discarded on unmount, which is acceptable\n * since we're exiting anyway.\n */\n dispose(): void {\n // Clear timer but DON'T flush\n if (this.flushTimer !== null) {\n clearTimeout(this.flushTimer)\n this.flushTimer = null\n }\n // Discard remaining messages\n this.buffer = []\n }\n\n /**\n * Get statistics about buffer performance\n */\n getStats(): {\n totalMessages: number\n totalFlushes: number\n averageBatchSize: number\n currentBufferSize: number\n } {\n return {\n totalMessages: this.messageCount,\n totalFlushes: this.flushCount,\n averageBatchSize:\n this.flushCount > 0 ? this.messageCount / this.flushCount : 0,\n currentBufferSize: this.buffer.length,\n }\n }\n}\n"],
5
+ "mappings": "AA2CO,MAAM,mBAAmB;AAAA,EACtB,SAAoB,CAAC;AAAA,EACrB,aAAoC;AAAA,EAC3B;AAAA,EACA;AAAA,EACT,eAAe;AAAA,EACf,aAAa;AAAA,EAErB,YACE,eACA,UAAqC,CAAC,GACtC;AACA,SAAK,gBAAgB;AACrB,SAAK,UAAU;AAAA,MACb,eAAe,QAAQ,iBAAiB;AAAA,MACxC,cAAc,QAAQ,gBAAgB;AAAA,MACtC,OAAO,QAAQ,SAAS;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,SAAwB;AAC1B,SAAK,OAAO,KAAK,OAAO;AACxB,SAAK;AAEL,QAAI,KAAK,QAAQ,OAAO;AACtB,cAAQ;AAAA,QACN,+CAA+C,KAAK,OAAO,MAAM,IAAI,KAAK,QAAQ,YAAY;AAAA,MAChG;AAAA,IACF;AAGA,QAAI,KAAK,OAAO,UAAU,KAAK,QAAQ,cAAc;AACnD,UAAI,KAAK,QAAQ,OAAO;AACtB,gBAAQ,IAAI,2DAA2D;AAAA,MACzE;AACA,WAAK,MAAM;AACX;AAAA,IACF;AAGA,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,gBAAsB;AAE5B,QAAI,KAAK,eAAe,MAAM;AAC5B,mBAAa,KAAK,UAAU;AAAA,IAC9B;AAGA,SAAK,aAAa,WAAW,MAAM;AACjC,WAAK,MAAM;AAAA,IACb,GAAG,KAAK,QAAQ,aAAa;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,QAAI,KAAK,OAAO,WAAW,GAAG;AAC5B;AAAA,IACF;AAEA,UAAM,kBAAkB,CAAC,GAAG,KAAK,MAAM;AACvC,SAAK,SAAS,CAAC;AACf,SAAK;AAEL,QAAI,KAAK,eAAe,MAAM;AAC5B,mBAAa,KAAK,UAAU;AAC5B,WAAK,aAAa;AAAA,IACpB;AAEA,QAAI,KAAK,QAAQ,OAAO;AACtB,cAAQ;AAAA,QACN,+BAA+B,KAAK,UAAU,KAAK,gBAAgB,MAAM;AAAA,MAC3E;AAAA,IACF;AAEA,SAAK,cAAc,eAAe;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,UAAgB;AAEd,QAAI,KAAK,eAAe,MAAM;AAC5B,mBAAa,KAAK,UAAU;AAC5B,WAAK,aAAa;AAAA,IACpB;AAEA,SAAK,SAAS,CAAC;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,WAKE;AACA,WAAO;AAAA,MACL,eAAe,KAAK;AAAA,MACpB,cAAc,KAAK;AAAA,MACnB,kBACE,KAAK,aAAa,IAAI,KAAK,eAAe,KAAK,aAAa;AAAA,MAC9D,mBAAmB,KAAK,OAAO;AAAA,IACjC;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -132,6 +132,11 @@ function detectShell() {
132
132
  ].join("\n");
133
133
  throw new Error(hint);
134
134
  }
135
+ const shellCrashListeners = /* @__PURE__ */ new Set();
136
+ function onShellCrash(callback) {
137
+ shellCrashListeners.add(callback);
138
+ return () => shellCrashListeners.delete(callback);
139
+ }
135
140
  class PersistentShell {
136
141
  commandQueue = [];
137
142
  isExecuting = false;
@@ -167,6 +172,13 @@ class PersistentShell {
167
172
  this.shell.on("exit", (code, signal) => {
168
173
  if (code) {
169
174
  logError(`Shell exited with code ${code} and signal ${signal}`);
175
+ for (const listener of shellCrashListeners) {
176
+ try {
177
+ listener(code, signal);
178
+ } catch (e) {
179
+ logError(`Shell crash listener error: ${e}`);
180
+ }
181
+ }
170
182
  }
171
183
  for (const file of [
172
184
  this.statusFile,
@@ -256,6 +268,143 @@ class PersistentShell {
256
268
  this.processQueue();
257
269
  });
258
270
  }
271
+ /**
272
+ * Execute a command with streaming output.
273
+ * Yields partial stdout/stderr as the command runs.
274
+ *
275
+ * @param command - The command to execute
276
+ * @param abortSignal - Optional signal to abort the command
277
+ * @param timeout - Optional timeout in milliseconds
278
+ * @param streamInterval - Interval in ms to check for new output (default: 50ms)
279
+ */
280
+ async *execStreaming(command, abortSignal, timeout, streamInterval = 50) {
281
+ while (this.isExecuting) {
282
+ await new Promise((resolve2) => setTimeout(resolve2, 10));
283
+ }
284
+ yield* this.execStreaming_(command, abortSignal, timeout, streamInterval);
285
+ }
286
+ async *execStreaming_(command, abortSignal, timeout, streamInterval = 50) {
287
+ const quotedCommand = quoteForBash(command);
288
+ try {
289
+ if (this.shellType === "wsl") {
290
+ execFileSync("wsl.exe", ["-e", "bash", "-n", "-c", command], {
291
+ stdio: "ignore",
292
+ timeout: 1e3
293
+ });
294
+ } else if (this.shellType === "msys") {
295
+ execFileSync(this.binShell, ["-n", "-c", command], {
296
+ stdio: "ignore",
297
+ timeout: 1e3
298
+ });
299
+ } else {
300
+ execFileSync(this.binShell, ["-n", "-c", command], {
301
+ stdio: "ignore",
302
+ timeout: 1e3
303
+ });
304
+ }
305
+ } catch (error) {
306
+ const execError = error;
307
+ const actualExitCode = execError?.status ?? execError?.code ?? 2;
308
+ const errorStr = execError?.stderr?.toString() || execError?.message || String(error || "");
309
+ yield {
310
+ type: "result",
311
+ stdout: "",
312
+ stderr: errorStr,
313
+ code: actualExitCode,
314
+ interrupted: false
315
+ };
316
+ return;
317
+ }
318
+ const commandTimeout = timeout || DEFAULT_TIMEOUT;
319
+ this.commandInterrupted = false;
320
+ this.isExecuting = true;
321
+ const killChildren = () => this.killChildren();
322
+ if (abortSignal) {
323
+ abortSignal.addEventListener("abort", killChildren);
324
+ }
325
+ try {
326
+ fs.writeFileSync(this.stdoutFile, "");
327
+ fs.writeFileSync(this.stderrFile, "");
328
+ fs.writeFileSync(this.statusFile, "");
329
+ const commandParts = [];
330
+ commandParts.push(
331
+ `eval ${quotedCommand} < /dev/null > ${quoteForBash(this.stdoutFileBashPath)} 2> ${quoteForBash(this.stderrFileBashPath)}`
332
+ );
333
+ commandParts.push(`EXEC_EXIT_CODE=$?`);
334
+ if (this.shellType === "msys") {
335
+ commandParts.push(`pwd -W > ${quoteForBash(this.cwdFileBashPath)}`);
336
+ } else {
337
+ commandParts.push(`pwd > ${quoteForBash(this.cwdFileBashPath)}`);
338
+ }
339
+ commandParts.push(
340
+ `echo $EXEC_EXIT_CODE > ${quoteForBash(this.statusFileBashPath)}`
341
+ );
342
+ this.sendToShell(commandParts.join("\n"));
343
+ const start = Date.now();
344
+ let lastStdout = "";
345
+ let lastStderr = "";
346
+ while (true) {
347
+ let statusFileSize = 0;
348
+ if (fs.existsSync(this.statusFile)) {
349
+ statusFileSize = fs.statSync(this.statusFile).size;
350
+ }
351
+ const isTimeout = Date.now() - start > commandTimeout;
352
+ const isComplete = statusFileSize > 0 || isTimeout || this.commandInterrupted;
353
+ let currentStdout = "";
354
+ let currentStderr = "";
355
+ if (fs.existsSync(this.stdoutFile)) {
356
+ try {
357
+ currentStdout = fs.readFileSync(this.stdoutFile, "utf8");
358
+ } catch {
359
+ }
360
+ }
361
+ if (fs.existsSync(this.stderrFile)) {
362
+ try {
363
+ currentStderr = fs.readFileSync(this.stderrFile, "utf8");
364
+ } catch {
365
+ }
366
+ }
367
+ const newStdout = currentStdout.slice(lastStdout.length);
368
+ const newStderr = currentStderr.slice(lastStderr.length);
369
+ if (newStdout || newStderr) {
370
+ yield {
371
+ type: "chunk",
372
+ stdout: newStdout,
373
+ stderr: newStderr
374
+ };
375
+ lastStdout = currentStdout;
376
+ lastStderr = currentStderr;
377
+ }
378
+ if (isComplete) {
379
+ if (isTimeout && !this.commandInterrupted) {
380
+ this.killChildren();
381
+ currentStderr += (currentStderr ? "\n" : "") + "Command execution timed out";
382
+ }
383
+ let code;
384
+ if (statusFileSize > 0) {
385
+ code = Number(fs.readFileSync(this.statusFile, "utf8"));
386
+ } else {
387
+ code = SIGTERM_CODE;
388
+ }
389
+ yield {
390
+ type: "result",
391
+ stdout: currentStdout,
392
+ stderr: currentStderr,
393
+ code,
394
+ interrupted: this.commandInterrupted
395
+ };
396
+ return;
397
+ }
398
+ await new Promise((resolve2) => setTimeout(resolve2, streamInterval));
399
+ }
400
+ } finally {
401
+ this.isExecuting = false;
402
+ if (abortSignal) {
403
+ abortSignal.removeEventListener("abort", killChildren);
404
+ }
405
+ this.processQueue();
406
+ }
407
+ }
259
408
  async exec_(command, timeout) {
260
409
  const quotedCommand = quoteForBash(command);
261
410
  try {
@@ -371,6 +520,7 @@ class PersistentShell {
371
520
  }
372
521
  }
373
522
  export {
374
- PersistentShell
523
+ PersistentShell,
524
+ onShellCrash
375
525
  };
376
526
  //# sourceMappingURL=PersistentShell.js.map