@within-7/minto 0.1.6 → 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.
- package/cli.js +155 -37
- package/dist/Tool.js +38 -0
- package/dist/Tool.js.map +3 -3
- package/dist/commands/agents/AgentsCommand.js +52 -26
- package/dist/commands/agents/AgentsCommand.js.map +2 -2
- package/dist/commands/agents/constants.js +1 -1
- package/dist/commands/agents/constants.js.map +1 -1
- package/dist/commands/agents/index.js +1 -1
- package/dist/commands/bug.js +74 -7
- package/dist/commands/bug.js.map +3 -3
- package/dist/commands/clear.js +3 -0
- package/dist/commands/clear.js.map +2 -2
- package/dist/commands/compact.js +37 -0
- package/dist/commands/compact.js.map +2 -2
- package/dist/commands/context.js +84 -0
- package/dist/commands/context.js.map +7 -0
- package/dist/commands/ctx_viz.js +18 -10
- package/dist/commands/ctx_viz.js.map +2 -2
- package/dist/commands/doctor.js +158 -12
- package/dist/commands/doctor.js.map +2 -2
- package/dist/commands/export.js +156 -0
- package/dist/commands/export.js.map +7 -0
- package/dist/commands/mcp-interactive.js +21 -12
- package/dist/commands/mcp-interactive.js.map +2 -2
- package/dist/commands/model.js +6 -5
- package/dist/commands/model.js.map +2 -2
- package/dist/commands/permissions.js +86 -0
- package/dist/commands/permissions.js.map +7 -0
- package/dist/commands/quit.js +3 -1
- package/dist/commands/quit.js.map +2 -2
- package/dist/commands/sandbox.js +104 -0
- package/dist/commands/sandbox.js.map +7 -0
- package/dist/commands/status.js +58 -0
- package/dist/commands/status.js.map +7 -0
- package/dist/commands/tasks.js +108 -0
- package/dist/commands/tasks.js.map +7 -0
- package/dist/commands/todos.js +123 -0
- package/dist/commands/todos.js.map +7 -0
- package/dist/commands.js +20 -2
- package/dist/commands.js.map +2 -2
- package/dist/components/AgentThinkingBlock.js +10 -18
- package/dist/components/AgentThinkingBlock.js.map +2 -2
- package/dist/components/BackgroundTasksPanel.js +78 -29
- package/dist/components/BackgroundTasksPanel.js.map +2 -2
- package/dist/components/BashStreamingProgress.js +24 -0
- package/dist/components/BashStreamingProgress.js.map +7 -0
- package/dist/components/CollapsibleHint.js +14 -0
- package/dist/components/CollapsibleHint.js.map +7 -0
- package/dist/components/FileEditToolUpdatedMessage.js +1 -1
- package/dist/components/FileEditToolUpdatedMessage.js.map +2 -2
- package/dist/components/HotkeyHelpPanel.js +137 -0
- package/dist/components/HotkeyHelpPanel.js.map +7 -0
- package/dist/components/Logo.js +5 -5
- package/dist/components/Logo.js.map +2 -2
- package/dist/components/Message.js +23 -7
- package/dist/components/Message.js.map +3 -3
- package/dist/components/ModelConfig.js +16 -3
- package/dist/components/ModelConfig.js.map +2 -2
- package/dist/components/ModelListManager.js +3 -3
- package/dist/components/ModelListManager.js.map +2 -2
- package/dist/components/ModelSelector/ModelSelector.js +1 -1
- package/dist/components/Onboarding.js +19 -14
- package/dist/components/Onboarding.js.map +2 -2
- package/dist/components/ProgressBar.js +74 -0
- package/dist/components/ProgressBar.js.map +7 -0
- package/dist/components/PromptInput.js +156 -46
- package/dist/components/PromptInput.js.map +2 -2
- package/dist/components/RequestStatusIndicator.js +194 -0
- package/dist/components/RequestStatusIndicator.js.map +7 -0
- package/dist/components/Spinner.js +92 -27
- package/dist/components/Spinner.js.map +2 -2
- package/dist/components/SpinnerSymbol.js +21 -27
- package/dist/components/SpinnerSymbol.js.map +2 -2
- package/dist/components/StreamingBashOutput.js +9 -8
- package/dist/components/StreamingBashOutput.js.map +2 -2
- package/dist/components/SubagentBlock.js +1 -1
- package/dist/components/SubagentBlock.js.map +1 -1
- package/dist/components/SubagentProgress.js +10 -11
- package/dist/components/SubagentProgress.js.map +2 -2
- package/dist/components/TaskCard.js +16 -13
- package/dist/components/TaskCard.js.map +2 -2
- package/dist/components/TodoChangeBlock.js +1 -1
- package/dist/components/TodoChangeBlock.js.map +2 -2
- package/dist/components/TodoPanel.js +120 -29
- package/dist/components/TodoPanel.js.map +3 -3
- package/dist/components/TokenCounter.js +74 -0
- package/dist/components/TokenCounter.js.map +7 -0
- package/dist/components/TokenWarning.js +2 -1
- package/dist/components/TokenWarning.js.map +2 -2
- package/dist/components/TreeConnector.js +25 -0
- package/dist/components/TreeConnector.js.map +7 -0
- package/dist/components/TurnCompletionIndicator.js +18 -0
- package/dist/components/TurnCompletionIndicator.js.map +7 -0
- package/dist/components/messages/AssistantTextMessage.js +5 -2
- package/dist/components/messages/AssistantTextMessage.js.map +2 -2
- package/dist/components/messages/AssistantThinkingMessage.js +18 -3
- package/dist/components/messages/AssistantThinkingMessage.js.map +2 -2
- package/dist/components/messages/AssistantToolUseMessage.js +11 -8
- package/dist/components/messages/AssistantToolUseMessage.js.map +2 -2
- package/dist/components/messages/GroupRenderer.js +53 -0
- package/dist/components/messages/GroupRenderer.js.map +7 -0
- package/dist/components/messages/NestedTasksPreview.js +12 -0
- package/dist/components/messages/NestedTasksPreview.js.map +7 -0
- package/dist/components/messages/ParallelTasksGroupView.js +92 -0
- package/dist/components/messages/ParallelTasksGroupView.js.map +7 -0
- package/dist/components/messages/TaskInModuleView.js +198 -0
- package/dist/components/messages/TaskInModuleView.js.map +7 -0
- package/dist/components/messages/TaskOutputContent.js +53 -0
- package/dist/components/messages/TaskOutputContent.js.map +7 -0
- package/dist/components/messages/UserPromptMessage.js +1 -1
- package/dist/components/messages/UserPromptMessage.js.map +2 -2
- package/dist/components/messages/UserToolResultMessage/UserToolSuccessMessage.js +2 -3
- package/dist/components/messages/UserToolResultMessage/UserToolSuccessMessage.js.map +2 -2
- package/dist/components/permissions/FallbackPermissionRequest.js +4 -4
- package/dist/components/permissions/FallbackPermissionRequest.js.map +2 -2
- package/dist/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.js +4 -4
- package/dist/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.js.map +2 -2
- package/dist/constants/colors.js +48 -0
- package/dist/constants/colors.js.map +2 -2
- package/dist/constants/formatRules.js +102 -0
- package/dist/constants/formatRules.js.map +7 -0
- package/dist/constants/prompts.js +12 -34
- package/dist/constants/prompts.js.map +2 -2
- package/dist/constants/symbols.js +64 -6
- package/dist/constants/symbols.js.map +2 -2
- package/dist/constants/timing.js +5 -0
- package/dist/constants/timing.js.map +2 -2
- package/dist/core/config/defaults.js +84 -0
- package/dist/core/config/defaults.js.map +7 -0
- package/dist/core/config/index.js +111 -0
- package/dist/core/config/index.js.map +7 -0
- package/dist/core/config/loader.js +221 -0
- package/dist/core/config/loader.js.map +7 -0
- package/dist/core/config/migrations.js +128 -0
- package/dist/core/config/migrations.js.map +7 -0
- package/dist/core/config/schema.js +178 -0
- package/dist/core/config/schema.js.map +7 -0
- package/dist/core/costTracker.js +138 -0
- package/dist/core/costTracker.js.map +7 -0
- package/dist/core/index.js +5 -0
- package/dist/core/index.js.map +7 -0
- package/dist/core/permissions/auditLog.js +204 -0
- package/dist/core/permissions/auditLog.js.map +7 -0
- package/dist/core/permissions/engine/index.js +3 -0
- package/dist/core/permissions/engine/index.js.map +7 -0
- package/dist/core/permissions/engine/permissionEngine.js +106 -0
- package/dist/core/permissions/engine/permissionEngine.js.map +7 -0
- package/dist/core/permissions/engine/types.js +1 -0
- package/dist/core/permissions/engine/types.js.map +7 -0
- package/dist/core/permissions/index.js +84 -0
- package/dist/core/permissions/index.js.map +7 -0
- package/dist/core/permissions/ruleEngine.js +259 -0
- package/dist/core/permissions/ruleEngine.js.map +7 -0
- package/dist/core/permissions/rules/allowedToolsRule.js +62 -0
- package/dist/core/permissions/rules/allowedToolsRule.js.map +7 -0
- package/dist/core/permissions/rules/autoEscalationRule.js +291 -0
- package/dist/core/permissions/rules/autoEscalationRule.js.map +7 -0
- package/dist/core/permissions/rules/index.js +46 -0
- package/dist/core/permissions/rules/index.js.map +7 -0
- package/dist/core/permissions/rules/planModeRule.js +55 -0
- package/dist/core/permissions/rules/planModeRule.js.map +7 -0
- package/dist/core/permissions/rules/projectBoundaryRule.js +168 -0
- package/dist/core/permissions/rules/projectBoundaryRule.js.map +7 -0
- package/dist/core/permissions/rules/safeModeRule.js +65 -0
- package/dist/core/permissions/rules/safeModeRule.js.map +7 -0
- package/dist/core/permissions/rules/sensitivePathsRule.js +340 -0
- package/dist/core/permissions/rules/sensitivePathsRule.js.map +7 -0
- package/dist/core/permissions/types.js +127 -0
- package/dist/core/permissions/types.js.map +7 -0
- package/dist/core/tools/executor.js +143 -0
- package/dist/core/tools/executor.js.map +7 -0
- package/dist/core/tools/index.js +15 -0
- package/dist/core/tools/index.js.map +7 -0
- package/dist/core/tools/registry.js +183 -0
- package/dist/core/tools/registry.js.map +7 -0
- package/dist/core/tools/types.js +1 -0
- package/dist/core/tools/types.js.map +7 -0
- package/dist/cost-tracker.js +23 -15
- package/dist/cost-tracker.js.map +2 -2
- package/dist/entrypoints/cli.js +43 -43
- package/dist/entrypoints/cli.js.map +2 -2
- package/dist/entrypoints/mcp.js +12 -4
- package/dist/entrypoints/mcp.js.map +2 -2
- package/dist/history.js +14 -3
- package/dist/history.js.map +2 -2
- package/dist/hooks/useAgentTranscripts.js +116 -0
- package/dist/hooks/useAgentTranscripts.js.map +7 -0
- package/dist/hooks/useAnimationSync.js +53 -0
- package/dist/hooks/useAnimationSync.js.map +7 -0
- package/dist/hooks/useArrowKeyHistory.js +4 -2
- package/dist/hooks/useArrowKeyHistory.js.map +2 -2
- package/dist/hooks/useCanUseTool.js +3 -1
- package/dist/hooks/useCanUseTool.js.map +2 -2
- package/dist/hooks/useCancelRequest.js +4 -1
- package/dist/hooks/useCancelRequest.js.map +2 -2
- package/dist/hooks/useExitOnCtrlCD.js +9 -5
- package/dist/hooks/useExitOnCtrlCD.js.map +2 -2
- package/dist/hooks/useHookStatus.js +40 -0
- package/dist/hooks/useHookStatus.js.map +7 -0
- package/dist/hooks/useLogMessages.js +17 -1
- package/dist/hooks/useLogMessages.js.map +2 -2
- package/dist/hooks/useMessageGroups.js +43 -0
- package/dist/hooks/useMessageGroups.js.map +7 -0
- package/dist/hooks/useTerminalSize.js +62 -6
- package/dist/hooks/useTerminalSize.js.map +2 -2
- package/dist/hooks/useUnifiedCompletion.js +69 -0
- package/dist/hooks/useUnifiedCompletion.js.map +2 -2
- package/dist/i18n/index.js +109 -0
- package/dist/i18n/index.js.map +7 -0
- package/dist/i18n/locales/en.js +347 -0
- package/dist/i18n/locales/en.js.map +7 -0
- package/dist/i18n/locales/index.js +7 -0
- package/dist/i18n/locales/index.js.map +7 -0
- package/dist/i18n/locales/zh-CN.js +347 -0
- package/dist/i18n/locales/zh-CN.js.map +7 -0
- package/dist/i18n/types.js +8 -0
- package/dist/i18n/types.js.map +7 -0
- package/dist/query.js +175 -17
- package/dist/query.js.map +3 -3
- package/dist/screens/REPL.js +501 -192
- package/dist/screens/REPL.js.map +3 -3
- package/dist/services/adapters/chatCompletions.js +3 -1
- package/dist/services/adapters/chatCompletions.js.map +2 -2
- package/dist/services/adapters/messageNormalizer.js +354 -0
- package/dist/services/adapters/messageNormalizer.js.map +7 -0
- package/dist/services/adapters/responsesAPI.js +6 -3
- package/dist/services/adapters/responsesAPI.js.map +2 -2
- package/dist/services/checkpointManager.js +386 -0
- package/dist/services/checkpointManager.js.map +7 -0
- package/dist/services/claude.js +138 -11
- package/dist/services/claude.js.map +3 -3
- package/dist/services/compressionService.js +50 -1
- package/dist/services/compressionService.js.map +2 -2
- package/dist/services/contextMonitor.js +162 -0
- package/dist/services/contextMonitor.js.map +7 -0
- package/dist/services/customCommands.js +60 -41
- package/dist/services/customCommands.js.map +2 -2
- package/dist/services/hookExecutor.js +173 -1
- package/dist/services/hookExecutor.js.map +2 -2
- package/dist/services/intelligentCompactor.js +281 -0
- package/dist/services/intelligentCompactor.js.map +7 -0
- package/dist/services/lspConfig.js +109 -0
- package/dist/services/lspConfig.js.map +7 -0
- package/dist/services/mcpClient.js +273 -34
- package/dist/services/mcpClient.js.map +2 -2
- package/dist/services/modelOrchestrator.js +310 -0
- package/dist/services/modelOrchestrator.js.map +7 -0
- package/dist/services/openai.js +8 -1
- package/dist/services/openai.js.map +2 -2
- package/dist/services/outputStyles.js +138 -0
- package/dist/services/outputStyles.js.map +7 -0
- package/dist/services/plugins/index.js +5 -0
- package/dist/services/plugins/index.js.map +7 -0
- package/dist/services/plugins/lspServers.js +188 -0
- package/dist/services/plugins/lspServers.js.map +7 -0
- package/dist/services/plugins/pluginRuntime.js +229 -0
- package/dist/services/plugins/pluginRuntime.js.map +7 -0
- package/dist/services/plugins/pluginValidation.js +219 -0
- package/dist/services/plugins/pluginValidation.js.map +7 -0
- package/dist/services/plugins/skillMarketplace.js +556 -0
- package/dist/services/plugins/skillMarketplace.js.map +7 -0
- package/dist/services/responseStateManager.js +37 -3
- package/dist/services/responseStateManager.js.map +2 -2
- package/dist/services/sandbox/filesystemBoundary.js +300 -0
- package/dist/services/sandbox/filesystemBoundary.js.map +7 -0
- package/dist/services/sandbox/index.js +14 -0
- package/dist/services/sandbox/index.js.map +7 -0
- package/dist/services/sandbox/networkProxy.js +293 -0
- package/dist/services/sandbox/networkProxy.js.map +7 -0
- package/dist/services/sandbox/sandboxController.js +574 -0
- package/dist/services/sandbox/sandboxController.js.map +7 -0
- package/dist/services/sandbox/types.js +50 -0
- package/dist/services/sandbox/types.js.map +7 -0
- package/dist/services/sessionMemory.js +266 -0
- package/dist/services/sessionMemory.js.map +7 -0
- package/dist/services/taskRouter.js +324 -0
- package/dist/services/taskRouter.js.map +7 -0
- package/dist/tools/ArchitectTool/ArchitectTool.js +10 -3
- package/dist/tools/ArchitectTool/ArchitectTool.js.map +2 -2
- package/dist/tools/AskExpertModelTool/AskExpertModelTool.js +3 -0
- package/dist/tools/AskExpertModelTool/AskExpertModelTool.js.map +2 -2
- package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js +8 -1
- package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js.map +2 -2
- package/dist/tools/BaseTool.js +72 -0
- package/dist/tools/BaseTool.js.map +7 -0
- package/dist/tools/BashOutputTool/BashOutputToolResultMessage.js +3 -0
- package/dist/tools/BashOutputTool/BashOutputToolResultMessage.js.map +2 -2
- package/dist/tools/BashTool/BashTool.js +60 -3
- package/dist/tools/BashTool/BashTool.js.map +2 -2
- package/dist/tools/BashTool/BashToolResultMessage.js +3 -0
- package/dist/tools/BashTool/BashToolResultMessage.js.map +2 -2
- package/dist/tools/BashTool/OutputLine.js +54 -0
- package/dist/tools/BashTool/OutputLine.js.map +2 -2
- package/dist/tools/BashTool/prompt.js +192 -3
- package/dist/tools/BashTool/prompt.js.map +2 -2
- package/dist/tools/FileEditTool/FileEditTool.js +29 -4
- package/dist/tools/FileEditTool/FileEditTool.js.map +2 -2
- package/dist/tools/FileReadTool/FileReadTool.js +23 -4
- package/dist/tools/FileReadTool/FileReadTool.js.map +2 -2
- package/dist/tools/FileWriteTool/FileWriteTool.js +5 -5
- package/dist/tools/FileWriteTool/FileWriteTool.js.map +2 -2
- package/dist/tools/GlobTool/GlobTool.js +14 -3
- package/dist/tools/GlobTool/GlobTool.js.map +2 -2
- package/dist/tools/GrepTool/GrepTool.js +41 -7
- package/dist/tools/GrepTool/GrepTool.js.map +2 -2
- package/dist/tools/KillShellTool/KillShellToolResultMessage.js +3 -0
- package/dist/tools/KillShellTool/KillShellToolResultMessage.js.map +2 -2
- package/dist/tools/ListMcpResourcesTool/ListMcpResourcesTool.js +109 -0
- package/dist/tools/ListMcpResourcesTool/ListMcpResourcesTool.js.map +7 -0
- package/dist/tools/ListMcpResourcesTool/prompt.js +19 -0
- package/dist/tools/ListMcpResourcesTool/prompt.js.map +7 -0
- package/dist/tools/LspTool/LspTool.js +664 -0
- package/dist/tools/LspTool/LspTool.js.map +7 -0
- package/dist/tools/LspTool/prompt.js +27 -0
- package/dist/tools/LspTool/prompt.js.map +7 -0
- package/dist/tools/MCPTool/MCPTool.js +11 -4
- package/dist/tools/MCPTool/MCPTool.js.map +2 -2
- package/dist/tools/MemoryReadTool/MemoryReadTool.js +19 -6
- package/dist/tools/MemoryReadTool/MemoryReadTool.js.map +2 -2
- package/dist/tools/MemoryWriteTool/MemoryWriteTool.js +6 -6
- package/dist/tools/MemoryWriteTool/MemoryWriteTool.js.map +2 -2
- package/dist/tools/MultiEditTool/MultiEditTool.js +19 -2
- package/dist/tools/MultiEditTool/MultiEditTool.js.map +2 -2
- package/dist/tools/NotebookEditTool/NotebookEditTool.js +5 -1
- package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
- package/dist/tools/NotebookReadTool/NotebookReadTool.js +8 -4
- package/dist/tools/NotebookReadTool/NotebookReadTool.js.map +2 -2
- package/dist/tools/PlanModeTool/EnterPlanModeTool.js +74 -0
- package/dist/tools/PlanModeTool/EnterPlanModeTool.js.map +7 -0
- package/dist/tools/PlanModeTool/ExitPlanModeTool.js +108 -0
- package/dist/tools/PlanModeTool/ExitPlanModeTool.js.map +7 -0
- package/dist/tools/PlanModeTool/prompt.js +94 -0
- package/dist/tools/PlanModeTool/prompt.js.map +7 -0
- package/dist/tools/ReadMcpResourceTool/ReadMcpResourceTool.js +130 -0
- package/dist/tools/ReadMcpResourceTool/ReadMcpResourceTool.js.map +7 -0
- package/dist/tools/ReadMcpResourceTool/prompt.js +17 -0
- package/dist/tools/ReadMcpResourceTool/prompt.js.map +7 -0
- package/dist/tools/SkillTool/SkillTool.js +14 -3
- package/dist/tools/SkillTool/SkillTool.js.map +2 -2
- package/dist/tools/SlashCommandTool/SlashCommandTool.js +260 -0
- package/dist/tools/SlashCommandTool/SlashCommandTool.js.map +7 -0
- package/dist/tools/SlashCommandTool/prompt.js +35 -0
- package/dist/tools/SlashCommandTool/prompt.js.map +7 -0
- package/dist/tools/TaskOutputTool/TaskOutputTool.js +189 -0
- package/dist/tools/TaskOutputTool/TaskOutputTool.js.map +7 -0
- package/dist/tools/TaskOutputTool/prompt.js +15 -0
- package/dist/tools/TaskOutputTool/prompt.js.map +7 -0
- package/dist/tools/TaskTool/TaskTool.js +321 -146
- package/dist/tools/TaskTool/TaskTool.js.map +2 -2
- package/dist/tools/TaskTool/prompt.js.map +2 -2
- package/dist/tools/TodoWriteTool/TodoWriteTool.js +42 -73
- package/dist/tools/TodoWriteTool/TodoWriteTool.js.map +2 -2
- package/dist/tools/URLFetcherTool/URLFetcherTool.js +7 -1
- package/dist/tools/URLFetcherTool/URLFetcherTool.js.map +2 -2
- package/dist/tools/URLFetcherTool/cache.js +55 -8
- package/dist/tools/URLFetcherTool/cache.js.map +2 -2
- package/dist/tools/WebSearchTool/WebSearchTool.js +6 -1
- package/dist/tools/WebSearchTool/WebSearchTool.js.map +2 -2
- package/dist/tools.js +31 -2
- package/dist/tools.js.map +2 -2
- package/dist/types/hooks.js +4 -0
- package/dist/types/hooks.js.map +2 -2
- package/dist/types/marketplace.js.map +2 -2
- package/dist/types/messageGroup.js +36 -0
- package/dist/types/messageGroup.js.map +7 -0
- package/dist/types/plugin.js.map +2 -2
- package/dist/types/thinking.js +1 -0
- package/dist/types/thinking.js.map +7 -0
- package/dist/utils/BackgroundShellManager.js +136 -39
- package/dist/utils/BackgroundShellManager.js.map +2 -2
- package/dist/utils/MessageBatchBuffer.js +102 -0
- package/dist/utils/MessageBatchBuffer.js.map +7 -0
- package/dist/utils/PersistentShell.js +151 -1
- package/dist/utils/PersistentShell.js.map +2 -2
- package/dist/utils/agentLoader.js +1 -23
- package/dist/utils/agentLoader.js.map +2 -2
- package/dist/utils/agentTranscripts.js +641 -0
- package/dist/utils/agentTranscripts.js.map +7 -0
- package/dist/utils/animationManager.js +213 -0
- package/dist/utils/animationManager.js.map +7 -0
- package/dist/utils/animationSync.js +110 -0
- package/dist/utils/animationSync.js.map +7 -0
- package/dist/utils/asyncFile.js +215 -0
- package/dist/utils/asyncFile.js.map +7 -0
- package/dist/utils/backgroundAgentManager.js +231 -0
- package/dist/utils/backgroundAgentManager.js.map +7 -0
- package/dist/utils/config.js +63 -7
- package/dist/utils/config.js.map +2 -2
- package/dist/utils/conversationRecovery.js +19 -0
- package/dist/utils/conversationRecovery.js.map +2 -2
- package/dist/utils/exit.js +73 -0
- package/dist/utils/exit.js.map +7 -0
- package/dist/utils/format.js +73 -5
- package/dist/utils/format.js.map +2 -2
- package/dist/utils/generators.js +76 -6
- package/dist/utils/generators.js.map +2 -2
- package/dist/utils/globalErrorHandler.js +149 -0
- package/dist/utils/globalErrorHandler.js.map +7 -0
- package/dist/utils/groupHandlers/index.js +8 -0
- package/dist/utils/groupHandlers/index.js.map +7 -0
- package/dist/utils/groupHandlers/parallelTasksHandler.js +140 -0
- package/dist/utils/groupHandlers/parallelTasksHandler.js.map +7 -0
- package/dist/utils/groupHandlers/taskHandler.js +104 -0
- package/dist/utils/groupHandlers/taskHandler.js.map +7 -0
- package/dist/utils/groupHandlers/types.js +1 -0
- package/dist/utils/groupHandlers/types.js.map +7 -0
- package/dist/utils/logRotation.js +224 -0
- package/dist/utils/logRotation.js.map +7 -0
- package/dist/utils/marketplaceManager.js +3 -5
- package/dist/utils/marketplaceManager.js.map +2 -2
- package/dist/utils/memSafety.js +264 -0
- package/dist/utils/memSafety.js.map +7 -0
- package/dist/utils/messageGroupManager.js +274 -0
- package/dist/utils/messageGroupManager.js.map +7 -0
- package/dist/utils/messages.js +13 -4
- package/dist/utils/messages.js.map +2 -2
- package/dist/utils/model.js +119 -15
- package/dist/utils/model.js.map +3 -3
- package/dist/utils/permissions/filesystem.js +157 -5
- package/dist/utils/permissions/filesystem.js.map +2 -2
- package/dist/utils/plan/planMode.js +143 -0
- package/dist/utils/plan/planMode.js.map +7 -0
- package/dist/utils/pluginLoader.js +17 -21
- package/dist/utils/pluginLoader.js.map +2 -2
- package/dist/utils/ripgrep.js +55 -2
- package/dist/utils/ripgrep.js.map +2 -2
- package/dist/utils/sanitizeInput.js +32 -0
- package/dist/utils/sanitizeInput.js.map +7 -0
- package/dist/utils/secureKeyStorage.js +312 -0
- package/dist/utils/secureKeyStorage.js.map +7 -0
- package/dist/utils/session/sessionPlugins.js +67 -0
- package/dist/utils/session/sessionPlugins.js.map +7 -0
- package/dist/utils/taskDisplayUtils.js +257 -0
- package/dist/utils/taskDisplayUtils.js.map +7 -0
- package/dist/utils/teamConfig.js +2 -1
- package/dist/utils/teamConfig.js.map +2 -2
- package/dist/utils/todoStorage.js +92 -2
- package/dist/utils/todoStorage.js.map +2 -2
- package/dist/utils/toolTimeout.js +136 -0
- package/dist/utils/toolTimeout.js.map +7 -0
- package/dist/utils/tooling/safeRender.js +115 -0
- package/dist/utils/tooling/safeRender.js.map +7 -0
- package/dist/utils/userFriendlyError.js +346 -0
- package/dist/utils/userFriendlyError.js.map +7 -0
- package/dist/utils/vendor/ripgrep/arm64-darwin/rg +0 -0
- package/dist/version.js +2 -2
- package/dist/version.js.map +1 -1
- package/package.json +14 -4
- package/scripts/postinstall.js +128 -38
- package/dist/commands/agents.js +0 -2086
- package/dist/commands/agents.js.map +0 -7
- package/dist/commands/build.js +0 -74
- package/dist/commands/build.js.map +0 -7
- package/dist/commands/compression.js +0 -57
- package/dist/commands/compression.js.map +0 -7
- package/dist/commands/listen.js +0 -37
- package/dist/commands/listen.js.map +0 -7
- package/dist/commands/login.js +0 -37
- package/dist/commands/login.js.map +0 -7
- package/dist/commands/logout.js +0 -33
- package/dist/commands/logout.js.map +0 -7
- package/dist/commands/mcp.js +0 -40
- package/dist/commands/mcp.js.map +0 -7
- package/dist/commands/mcp_refresh.js +0 -40
- package/dist/commands/mcp_refresh.js.map +0 -7
- package/dist/commands/modelstatus.js +0 -21
- package/dist/commands/modelstatus.js.map +0 -7
- package/dist/commands/onboarding.js +0 -36
- package/dist/commands/onboarding.js.map +0 -7
- package/dist/commands/plugin-interactive.js +0 -446
- package/dist/commands/plugin-interactive.js.map +0 -7
- package/dist/commands/pr_comments.js +0 -61
- package/dist/commands/pr_comments.js.map +0 -7
- package/dist/commands/release-notes.js +0 -30
- package/dist/commands/release-notes.js.map +0 -7
- package/dist/commands/review.js +0 -51
- package/dist/commands/review.js.map +0 -7
- package/dist/components/Bug.js +0 -147
- package/dist/components/Bug.js.map +0 -7
- package/dist/components/ModelSelector.js +0 -2062
- package/dist/components/ModelSelector.js.map +0 -7
- package/dist/components/ModelStatusDisplay.js +0 -87
- package/dist/components/ModelStatusDisplay.js.map +0 -7
- package/dist/entrypoints/cli-wrapper.js +0 -61
- package/dist/entrypoints/cli-wrapper.js.map +0 -7
- package/dist/screens/Doctor.js +0 -22
- 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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
childProcess.on("
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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": "AAAA,SAAS,aAAgC;AACzC,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;
|
|
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
|