centaurus-cli 3.1.2 → 3.1.4

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 (142) hide show
  1. package/dist/cli-adapter.js +689 -155
  2. package/dist/cli-adapter.js.map +1 -1
  3. package/dist/config/defaultConfig.js +1 -4
  4. package/dist/config/defaultConfig.js.map +1 -1
  5. package/dist/config/models.js +6 -0
  6. package/dist/config/models.js.map +1 -1
  7. package/dist/config/slash-commands.js +66 -2
  8. package/dist/config/slash-commands.js.map +1 -1
  9. package/dist/config/types.js +4 -4
  10. package/dist/config/types.js.map +1 -1
  11. package/dist/index.js +36 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/services/ai-context-injector.js +109 -0
  14. package/dist/services/ai-context-injector.js.map +1 -1
  15. package/dist/services/ai-service-client.js +3 -2
  16. package/dist/services/ai-service-client.js.map +1 -1
  17. package/dist/services/api-client.js.map +1 -1
  18. package/dist/services/background-task-manager.js +59 -0
  19. package/dist/services/background-task-manager.js.map +1 -1
  20. package/dist/services/local-chat-storage.js +2 -0
  21. package/dist/services/local-chat-storage.js.map +1 -1
  22. package/dist/services/skill-storage.js +141 -0
  23. package/dist/services/skill-storage.js.map +1 -0
  24. package/dist/services/sub-agent-manager.js +49 -8
  25. package/dist/services/sub-agent-manager.js.map +1 -1
  26. package/dist/services/warpify-detector.js +17 -5
  27. package/dist/services/warpify-detector.js.map +1 -1
  28. package/dist/tools/background-command.js +5 -2
  29. package/dist/tools/background-command.js.map +1 -1
  30. package/dist/tools/command.js +367 -109
  31. package/dist/tools/command.js.map +1 -1
  32. package/dist/tools/file-ops.js +23 -6
  33. package/dist/tools/file-ops.js.map +1 -1
  34. package/dist/tools/plan-mode.js +184 -336
  35. package/dist/tools/plan-mode.js.map +1 -1
  36. package/dist/tools/sub-agent.js +24 -5
  37. package/dist/tools/sub-agent.js.map +1 -1
  38. package/dist/tools/todo-list.js +157 -0
  39. package/dist/tools/todo-list.js.map +1 -0
  40. package/dist/types/skill.js +30 -0
  41. package/dist/types/skill.js.map +1 -0
  42. package/dist/ui/components/App.js +956 -162
  43. package/dist/ui/components/App.js.map +1 -1
  44. package/dist/ui/components/AuthScreen.js +3 -1
  45. package/dist/ui/components/AuthScreen.js.map +1 -1
  46. package/dist/ui/components/AuthWelcomeScreen.js +3 -1
  47. package/dist/ui/components/AuthWelcomeScreen.js.map +1 -1
  48. package/dist/ui/components/CodeBlock.js +3 -1
  49. package/dist/ui/components/CodeBlock.js.map +1 -1
  50. package/dist/ui/components/CompactShellPreview.js +44 -0
  51. package/dist/ui/components/CompactShellPreview.js.map +1 -0
  52. package/dist/ui/components/ConfigViewer.js +3 -1
  53. package/dist/ui/components/ConfigViewer.js.map +1 -1
  54. package/dist/ui/components/ConfirmPrompt.js +3 -1
  55. package/dist/ui/components/ConfirmPrompt.js.map +1 -1
  56. package/dist/ui/components/ConnectionStatusMessage.js +3 -1
  57. package/dist/ui/components/ConnectionStatusMessage.js.map +1 -1
  58. package/dist/ui/components/DetailedPlanReviewScreen.js +84 -74
  59. package/dist/ui/components/DetailedPlanReviewScreen.js.map +1 -1
  60. package/dist/ui/components/DiffViewer.js +6 -3
  61. package/dist/ui/components/DiffViewer.js.map +1 -1
  62. package/dist/ui/components/FileCreationPreview.js.map +1 -1
  63. package/dist/ui/components/FileTagAutocomplete.js +4 -2
  64. package/dist/ui/components/FileTagAutocomplete.js.map +1 -1
  65. package/dist/ui/components/InputBox.js +243 -40
  66. package/dist/ui/components/InputBox.js.map +1 -1
  67. package/dist/ui/components/InteractiveShell.js +5 -3
  68. package/dist/ui/components/InteractiveShell.js.map +1 -1
  69. package/dist/ui/components/KeyboardHelp.js +4 -1
  70. package/dist/ui/components/KeyboardHelp.js.map +1 -1
  71. package/dist/ui/components/LoadingIndicator.js +3 -1
  72. package/dist/ui/components/LoadingIndicator.js.map +1 -1
  73. package/dist/ui/components/MCPAddScreen.js +63 -13
  74. package/dist/ui/components/MCPAddScreen.js.map +1 -1
  75. package/dist/ui/components/MarkdownRenderer.js +3 -1
  76. package/dist/ui/components/MarkdownRenderer.js.map +1 -1
  77. package/dist/ui/components/MessageDisplay.js +9 -7
  78. package/dist/ui/components/MessageDisplay.js.map +1 -1
  79. package/dist/ui/components/ModelPicker.js +170 -0
  80. package/dist/ui/components/ModelPicker.js.map +1 -0
  81. package/dist/ui/components/MonitorModeAIPanel.js +3 -1
  82. package/dist/ui/components/MonitorModeAIPanel.js.map +1 -1
  83. package/dist/ui/components/PlanAcceptedMessage.js +12 -6
  84. package/dist/ui/components/PlanAcceptedMessage.js.map +1 -1
  85. package/dist/ui/components/PlanQuestionMessage.js +37 -0
  86. package/dist/ui/components/PlanQuestionMessage.js.map +1 -0
  87. package/dist/ui/components/PlanQuestionScreen.js +138 -0
  88. package/dist/ui/components/PlanQuestionScreen.js.map +1 -0
  89. package/dist/ui/components/PlanReviewScreen.js +7 -9
  90. package/dist/ui/components/PlanReviewScreen.js.map +1 -1
  91. package/dist/ui/components/RulesEditorScreen.js +65 -28
  92. package/dist/ui/components/RulesEditorScreen.js.map +1 -1
  93. package/dist/ui/components/SelectPrompt.js +3 -1
  94. package/dist/ui/components/SelectPrompt.js.map +1 -1
  95. package/dist/ui/components/SkillCreatorScreen.js +217 -0
  96. package/dist/ui/components/SkillCreatorScreen.js.map +1 -0
  97. package/dist/ui/components/SlashCommandAutocomplete.js +4 -2
  98. package/dist/ui/components/SlashCommandAutocomplete.js.map +1 -1
  99. package/dist/ui/components/StatusBar.js +4 -2
  100. package/dist/ui/components/StatusBar.js.map +1 -1
  101. package/dist/ui/components/StreamingMessageDisplay.js +5 -3
  102. package/dist/ui/components/StreamingMessageDisplay.js.map +1 -1
  103. package/dist/ui/components/SubAgentListScreen.js +65 -0
  104. package/dist/ui/components/SubAgentListScreen.js.map +1 -0
  105. package/dist/ui/components/SubAgentViewScreen.js +123 -0
  106. package/dist/ui/components/SubAgentViewScreen.js.map +1 -0
  107. package/dist/ui/components/TaskCompletedMessage.js +40 -8
  108. package/dist/ui/components/TaskCompletedMessage.js.map +1 -1
  109. package/dist/ui/components/TaskProgressIndicator.js +6 -4
  110. package/dist/ui/components/TaskProgressIndicator.js.map +1 -1
  111. package/dist/ui/components/TextEditor.js +297 -0
  112. package/dist/ui/components/TextEditor.js.map +1 -0
  113. package/dist/ui/components/TodoListMessage.js +59 -0
  114. package/dist/ui/components/TodoListMessage.js.map +1 -0
  115. package/dist/ui/components/ToolExecutionMessage.js +134 -84
  116. package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
  117. package/dist/ui/components/ToolExecutionStatus.js +3 -1
  118. package/dist/ui/components/ToolExecutionStatus.js.map +1 -1
  119. package/dist/ui/components/WelcomeBanner.js +33 -33
  120. package/dist/ui/components/WelcomeBanner.js.map +1 -1
  121. package/dist/ui/components/WorkflowCreatorScreen.js +5 -3
  122. package/dist/ui/components/WorkflowCreatorScreen.js.map +1 -1
  123. package/dist/ui/theme.js +97 -0
  124. package/dist/ui/theme.js.map +1 -0
  125. package/dist/ui/utils/chat-history-limit.js +247 -0
  126. package/dist/ui/utils/chat-history-limit.js.map +1 -0
  127. package/dist/utils/chat-formatter.js +22 -9
  128. package/dist/utils/chat-formatter.js.map +1 -1
  129. package/dist/utils/git-stats.js +7 -5
  130. package/dist/utils/git-stats.js.map +1 -1
  131. package/dist/utils/input-classifier.js +11 -1
  132. package/dist/utils/input-classifier.js.map +1 -1
  133. package/dist/utils/output-truncation.js +175 -0
  134. package/dist/utils/output-truncation.js.map +1 -0
  135. package/dist/utils/rule-reference-resolver.js +3 -3
  136. package/dist/utils/rule-reference-resolver.js.map +1 -1
  137. package/dist/utils/tunnel-commands-manager.js +134 -0
  138. package/dist/utils/tunnel-commands-manager.js.map +1 -0
  139. package/package.json +91 -90
  140. package/postinstall.js +4 -11
  141. package/dist/ui/components/MultiLineInput.js +0 -255
  142. package/dist/ui/components/MultiLineInput.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/sub-agent-manager.ts"],"sourcesContent":["/**\r\n * Sub-Agent Manager Service\r\n * \r\n * Manages the lifecycle of background sub-agents that can be spawned\r\n * by the main agent to handle delegated tasks.\r\n */\r\n\r\nimport { randomUUID } from 'crypto';\r\nimport { aiServiceClient, Message as AIMessage, StreamChunk } from './ai-service-client.js';\r\nimport { apiClient } from './api-client.js';\r\nimport { ToolRegistry } from '../tools/registry.js';\r\nimport { ToolExecutionContext } from '../tools/types.js';\r\nimport { sessionQuotaManager } from './session-quota-manager.js';\r\nimport { logError, logWarning } from '../utils/logger.js';\r\nimport { quickLog } from '../utils/conversation-logger.js';\r\nimport { ContextManager } from '../context/context-manager.js';\r\n\r\n// ==================== Types ====================\r\n\r\nexport interface SubAgentConfig {\r\n prompt: string; // Task description\r\n context: string; // Context information (file paths, structure, etc.)\r\n workingDirectory: string; // CWD for the sub-agent\r\n complexity: number; // 1-10, decides model selection\r\n parentContextManager?: any; // Parent's ContextManager for remote session support (SSH/Docker/WSL)\r\n}\r\n\r\nexport interface FileOperation {\r\n type: 'create' | 'edit' | 'delete';\r\n path: string;\r\n originalContent?: string; // For edits: content before change\r\n newContent?: string; // For create/edit: content after change\r\n diff?: string; // Unified diff format\r\n reasonText?: string; // reason_text from the tool call\r\n timestamp: Date;\r\n}\r\n\r\nexport interface ToolExecution {\r\n toolName: string;\r\n arguments: Record<string, any>;\r\n reasonText?: string;\r\n result: string;\r\n success: boolean;\r\n timestamp: Date;\r\n}\r\n\r\nexport type SubAgentStatus = 'pending' | 'running' | 'completed' | 'failed' | 'terminated';\r\n\r\nexport interface SubAgent {\r\n id: string;\r\n status: SubAgentStatus;\r\n prompt: string;\r\n context: string;\r\n workingDirectory: string;\r\n complexity: number;\r\n model: string;\r\n startTime: Date;\r\n endTime?: Date;\r\n\r\n // Agent loop state\r\n conversationHistory: AIMessage[];\r\n turnCount: number;\r\n\r\n // Tracking changes\r\n fileOperations: FileOperation[];\r\n toolHistory: ToolExecution[];\r\n\r\n // Results\r\n result?: string;\r\n error?: string;\r\n\r\n // Internal flags\r\n isRead: boolean; // Whether main agent has read the results\r\n abortController?: AbortController; // For cancellation\r\n}\r\n\r\n// ==================== Constants ====================\r\n\r\nconst MAX_CONCURRENT_SUBAGENTS = 5;\r\nconst SUBAGENT_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes\r\nconst MAX_TURNS_PER_SUBAGENT = 50;\r\n\r\n// Model selection based on complexity\r\nconst SIMPLE_MODEL = 'gemini-3-flash-preview';\r\nconst COMPLEX_MODEL = 'gemini-3.1-pro-preview';\r\nconst COMPLEXITY_THRESHOLD = 5;\r\n\r\n// ==================== Sub-Agent System Prompt ====================\r\n\r\nconst SUBAGENT_SYSTEM_PROMPT = `You are a sub-agent spawned by a main AI agent to complete a specific task.\r\n\r\nIMPORTANT RULES:\r\n1. You have full access to file system tools (read, write, edit, list, grep, find)\r\n2. You have access to command execution tools\r\n3. Focus ONLY on the task assigned to you\r\n4. Always provide reason_text for every tool call to explain what you're doing\r\n5. Call task_complete when you've finished the assigned task\r\n6. Be efficient - don't explore unnecessarily, stick to the task\r\n7. If you encounter errors, try to resolve them or report clearly what went wrong\r\n\r\nCONTEXT FROM MAIN AGENT:\r\nWorking Directory: {WORKING_DIRECTORY}\r\n\r\n{CONTEXT}\r\n\r\nYOUR TASK:\r\n{PROMPT}\r\n\r\nComplete this task efficiently and call task_complete when done.`;\r\n\r\n// ==================== SubAgentManager Class ====================\r\n\r\nclass SubAgentManagerClass {\r\n private subAgents: Map<string, SubAgent> = new Map();\r\n private toolRegistry?: ToolRegistry;\r\n private onSubAgentCountChange?: (count: number) => void;\r\n private onToolExecutionUpdate?: (update: any) => void;\r\n\r\n /**\r\n * Initialize the manager with a tool registry\r\n */\r\n initialize(toolRegistry: ToolRegistry): void {\r\n this.toolRegistry = toolRegistry;\r\n quickLog(`[${new Date().toISOString()}] [SubAgentManager] Initialized with tool registry\\n`);\r\n }\r\n\r\n /**\r\n * Set callback for sub-agent count changes\r\n */\r\n setOnSubAgentCountChange(callback: (count: number) => void): void {\r\n this.onSubAgentCountChange = callback;\r\n quickLog(`[${new Date().toISOString()}] [SubAgentManager] setOnSubAgentCountChange callback registered\\n`);\r\n }\r\n\r\n /**\r\n * Set callback for tool execution updates (for logging)\r\n */\r\n setOnToolExecutionUpdate(callback: (update: any) => void): void {\r\n this.onToolExecutionUpdate = callback;\r\n }\r\n\r\n /**\r\n * Notify listeners about sub-agent count change\r\n */\r\n private notifyCountChange(): void {\r\n const runningAgents = this.getRunningSubAgents();\r\n const runningCount = runningAgents.length;\r\n const details = runningAgents.map(a => `${a.id}:${a.status}`).join(', ');\r\n\r\n quickLog(`[${new Date().toISOString()}] [SubAgentManager] notifyCountChange called, runningCount=${runningCount}, agents=[${details}]\\n`);\r\n\r\n if (this.onSubAgentCountChange) {\r\n this.onSubAgentCountChange(runningCount);\r\n // quickLog(`Callback invoked`) // Removed redundant log or kept if preferred\r\n }\r\n }\r\n\r\n /**\r\n * Select model based on complexity\r\n */\r\n private selectModel(complexity: number): string {\r\n return complexity <= COMPLEXITY_THRESHOLD ? SIMPLE_MODEL : COMPLEX_MODEL;\r\n }\r\n\r\n /**\r\n * Generate system prompt for sub-agent\r\n */\r\n private generateSystemPrompt(config: SubAgentConfig): string {\r\n return SUBAGENT_SYSTEM_PROMPT\r\n .replace('{WORKING_DIRECTORY}', config.workingDirectory)\r\n .replace('{CONTEXT}', config.context || 'No additional context provided.')\r\n .replace('{PROMPT}', config.prompt);\r\n }\r\n\r\n /**\r\n * Spawn a new sub-agent\r\n */\r\n async spawnSubAgent(config: SubAgentConfig): Promise<{ success: boolean; agentId?: string; error?: string }> {\r\n // Check concurrent limit\r\n if (this.getRunningSubAgents().length >= MAX_CONCURRENT_SUBAGENTS) {\r\n return {\r\n success: false,\r\n error: `Maximum ${MAX_CONCURRENT_SUBAGENTS} concurrent sub-agents allowed. Wait for existing sub-agents to complete.`\r\n };\r\n }\r\n\r\n // Check if tool registry is initialized\r\n if (!this.toolRegistry) {\r\n return {\r\n success: false,\r\n error: 'SubAgentManager not initialized. Tool registry is missing.'\r\n };\r\n }\r\n\r\n // Check authentication\r\n if (!apiClient.isAuthenticated()) {\r\n return {\r\n success: false,\r\n error: 'Authentication required for sub-agents.'\r\n };\r\n }\r\n\r\n // Check session quota\r\n if (!sessionQuotaManager.canSendMessage()) {\r\n return {\r\n success: false,\r\n error: 'Session quota exhausted. Cannot spawn sub-agent.'\r\n };\r\n }\r\n\r\n // Create sub-agent\r\n const agentId = `subagent-${randomUUID().substring(0, 8)}`;\r\n const model = this.selectModel(config.complexity);\r\n\r\n const subAgent: SubAgent = {\r\n id: agentId,\r\n status: 'pending',\r\n prompt: config.prompt,\r\n context: config.context,\r\n workingDirectory: config.workingDirectory,\r\n complexity: config.complexity,\r\n model,\r\n startTime: new Date(),\r\n conversationHistory: [],\r\n turnCount: 0,\r\n fileOperations: [],\r\n toolHistory: [],\r\n isRead: false,\r\n abortController: new AbortController(),\r\n };\r\n\r\n this.subAgents.set(agentId, subAgent);\r\n quickLog(`[${new Date().toISOString()}] [SubAgentManager] Sub-agent ${agentId} added to map, calling notifyCountChange\\\\n`);\r\n this.notifyCountChange();\r\n\r\n // Start the sub-agent loop in background (don't await)\r\n this.runSubAgentLoop(agentId, config).catch(error => {\r\n logError(`Sub-agent ${agentId} failed:`, error);\r\n const agent = this.subAgents.get(agentId);\r\n if (agent && agent.status === 'running') {\r\n agent.status = 'failed';\r\n agent.error = error.message || String(error);\r\n agent.endTime = new Date();\r\n this.notifyCountChange();\r\n }\r\n });\r\n\r\n return { success: true, agentId };\r\n }\r\n\r\n /**\r\n * Run the sub-agent's agent loop\r\n */\r\n private async runSubAgentLoop(agentId: string, config: SubAgentConfig): Promise<void> {\r\n const subAgent = this.subAgents.get(agentId);\r\n if (!subAgent) {\r\n throw new Error(`Sub-agent ${agentId} not found`);\r\n }\r\n\r\n subAgent.status = 'running';\r\n this.notifyCountChange();\r\n\r\n // Set up timeout\r\n const timeoutId = setTimeout(() => {\r\n const agent = this.subAgents.get(agentId);\r\n if (agent && agent.status === 'running') {\r\n agent.status = 'failed';\r\n agent.error = 'Sub-agent timed out after 10 minutes';\r\n agent.endTime = new Date();\r\n agent.abortController?.abort();\r\n this.notifyCountChange();\r\n }\r\n }, SUBAGENT_TIMEOUT_MS);\r\n\r\n try {\r\n // Initialize conversation with system prompt (as user message since backend injects system)\r\n const systemPrompt = this.generateSystemPrompt(config);\r\n subAgent.conversationHistory.push({\r\n role: 'user',\r\n content: systemPrompt,\r\n });\r\n\r\n const tools = this.toolRegistry!.getSchemas();\r\n\r\n // Build execution context\r\n // Use parent's ContextManager if available (for remote sessions like SSH/Docker/WSL),\r\n // otherwise create a new local one\r\n const effectiveContextManager = config.parentContextManager || new ContextManager(config.workingDirectory, process.platform);\r\n\r\n const context: ToolExecutionContext = {\r\n cwd: config.workingDirectory,\r\n contextManager: effectiveContextManager,\r\n requireApproval: async () => true, // Auto-approve for sub-agents (autonomous)\r\n onStreamingOutput: () => { }, // No streaming output for sub-agents (yet)\r\n };\r\n\r\n // Detect environment from the effective context manager's current state\r\n // This ensures the AI model knows the correct OS/shell when in a remote session\r\n const currentCtx = effectiveContextManager.getCurrentContext();\r\n const isRemote = currentCtx && currentCtx.type !== 'local';\r\n let envOs: string;\r\n let envShell: string;\r\n let envHomeDir: string;\r\n let envPlatform: string;\r\n\r\n if (isRemote && currentCtx.metadata) {\r\n // Use remote session's OS/shell info\r\n const remoteOs = currentCtx.metadata.os;\r\n envOs = remoteOs || 'linux'; // Most remote sessions are Linux\r\n envPlatform = envOs === 'windows' ? 'win32' : 'linux';\r\n envShell = currentCtx.metadata.shell || 'bash';\r\n envHomeDir = currentCtx.metadata.homeDirectory || `/home/${currentCtx.metadata.username || 'user'}`;\r\n } else {\r\n // Local environment\r\n envOs = process.platform === 'win32' ? 'windows' : process.platform === 'darwin' ? 'macos' : 'linux';\r\n envPlatform = process.platform;\r\n envShell = process.env.SHELL || 'cmd.exe';\r\n envHomeDir = process.env.USERPROFILE || process.env.HOME || '';\r\n }\r\n\r\n // Agent loop\r\n while (subAgent.turnCount < MAX_TURNS_PER_SUBAGENT && subAgent.status === 'running') {\r\n subAgent.turnCount++;\r\n\r\n // Check quota\r\n sessionQuotaManager.incrementMessageCount();\r\n if (!sessionQuotaManager.canSendMessage()) {\r\n subAgent.status = 'failed';\r\n subAgent.error = 'Session quota exhausted during sub-agent execution';\r\n break;\r\n }\r\n\r\n let assistantMessage = '';\r\n let toolCalls: any[] = [];\r\n\r\n // Stream AI response\r\n try {\r\n for await (const chunk of aiServiceClient.streamChat(\r\n subAgent.model,\r\n subAgent.conversationHistory,\r\n tools,\r\n { cwd: config.workingDirectory, platform: envPlatform, shell: envShell, os: envOs as 'windows' | 'macos' | 'linux', homeDir: envHomeDir },\r\n undefined,\r\n undefined,\r\n subAgent.abortController?.signal\r\n )) {\r\n if (chunk.type === 'error') {\r\n throw new Error(chunk.message);\r\n }\r\n\r\n if (chunk.type === 'text') {\r\n assistantMessage += chunk.content;\r\n }\r\n\r\n if (chunk.type === 'tool_call') {\r\n const toolCall = chunk.toolCall;\r\n // Parse string arguments if needed\r\n if (toolCall.arguments && typeof toolCall.arguments === 'string') {\r\n try {\r\n toolCall.arguments = JSON.parse(toolCall.arguments);\r\n } catch (e) {\r\n // Ignore parsing error\r\n }\r\n }\r\n toolCalls.push(toolCall);\r\n }\r\n\r\n if (chunk.type === 'done') {\r\n break;\r\n }\r\n }\r\n } catch (error: any) {\r\n if (error.name === 'AbortError') {\r\n // Sub-agent was terminated\r\n subAgent.status = 'terminated';\r\n subAgent.endTime = new Date();\r\n break;\r\n }\r\n throw error;\r\n }\r\n\r\n // Add assistant message to history\r\n if (toolCalls.length > 0 || assistantMessage) {\r\n const assistantMsg: AIMessage = {\r\n role: 'assistant',\r\n content: assistantMessage,\r\n };\r\n if (toolCalls.length > 0) {\r\n assistantMsg.tool_calls = toolCalls;\r\n }\r\n subAgent.conversationHistory.push(assistantMsg);\r\n }\r\n\r\n // Execute tool calls\r\n if (toolCalls.length === 0) {\r\n // No tool calls and no task_complete - something went wrong\r\n subAgent.status = 'completed';\r\n subAgent.result = assistantMessage || 'Sub-agent completed without explicit task_complete';\r\n subAgent.endTime = new Date();\r\n break;\r\n }\r\n\r\n for (const toolCall of toolCalls) {\r\n // Check for task_complete\r\n if (toolCall.name === 'task_complete') {\r\n subAgent.status = 'completed';\r\n subAgent.result = assistantMessage || 'Task completed successfully';\r\n subAgent.endTime = new Date();\r\n break;\r\n }\r\n\r\n // Track the tool execution\r\n const reasonText = toolCall.arguments?.reason_text;\r\n const toolExecution: ToolExecution = {\r\n toolName: toolCall.name,\r\n arguments: toolCall.arguments,\r\n reasonText,\r\n result: '',\r\n success: false,\r\n timestamp: new Date(),\r\n };\r\n\r\n try {\r\n quickLog(`[SubAgent ${subAgent.id}] Executing tool: ${toolCall.name}`);\r\n\r\n // Execute the tool\r\n const result = await this.toolRegistry!.execute(toolCall.name, toolCall.arguments, context);\r\n\r\n quickLog(`[SubAgent ${subAgent.id}] Tool ${toolCall.name} execution result: ${JSON.stringify(result)}`);\r\n\r\n toolExecution.success = result.success;\r\n toolExecution.result = result.result;\r\n\r\n // Track file operations\r\n this.trackFileOperation(subAgent, toolCall, result);\r\n\r\n // Add tool result to history\r\n subAgent.conversationHistory.push({\r\n role: 'tool',\r\n tool_call_id: toolCall.id,\r\n content: result.success ? result.result : `Error: ${result.error}`,\r\n });\r\n\r\n } catch (error: any) {\r\n toolExecution.success = false;\r\n toolExecution.result = `Error: ${error.message}`;\r\n\r\n subAgent.conversationHistory.push({\r\n role: 'tool',\r\n tool_call_id: toolCall.id,\r\n content: `Error: ${error.message}`,\r\n });\r\n }\r\n\r\n subAgent.toolHistory.push(toolExecution);\r\n }\r\n\r\n // Check if task was completed\r\n if (subAgent.status === 'completed') {\r\n break;\r\n }\r\n }\r\n\r\n // Check if we hit max turns\r\n if (subAgent.turnCount >= MAX_TURNS_PER_SUBAGENT && subAgent.status === 'running') {\r\n subAgent.status = 'failed';\r\n subAgent.error = `Sub-agent exceeded maximum ${MAX_TURNS_PER_SUBAGENT} turns`;\r\n subAgent.endTime = new Date();\r\n }\r\n\r\n } finally {\r\n clearTimeout(timeoutId);\r\n this.notifyCountChange();\r\n }\r\n\r\n quickLog(`[${new Date().toISOString()}] [SubAgent] ${agentId} finished with status: ${subAgent.status}\\n`);\r\n }\r\n\r\n /**\r\n * Track file operations made by sub-agent\r\n */\r\n private trackFileOperation(subAgent: SubAgent, toolCall: any, result: any): void {\r\n const { name, arguments: args } = toolCall;\r\n\r\n if (name === 'write_to_file') {\r\n subAgent.fileOperations.push({\r\n type: 'create',\r\n path: args.TargetFile || args.target_path || args.file_path || 'unknown_file',\r\n newContent: args.content,\r\n reasonText: args.reason_text,\r\n timestamp: new Date(),\r\n });\r\n } else if (name === 'edit_file' || name === 'multi_edit_file') {\r\n subAgent.fileOperations.push({\r\n type: 'edit',\r\n path: args.file_path || args.target_path || 'unknown_file',\r\n diff: result.success ? result.result : undefined,\r\n reasonText: args.reason_text,\r\n timestamp: new Date(),\r\n });\r\n }\r\n // Note: We could also track delete operations if there was a delete_file tool\r\n }\r\n\r\n /**\r\n * Get a sub-agent by ID\r\n */\r\n getSubAgent(agentId: string): SubAgent | undefined {\r\n return this.subAgents.get(agentId);\r\n }\r\n\r\n /**\r\n * Get all sub-agents\r\n */\r\n getAllSubAgents(): SubAgent[] {\r\n return Array.from(this.subAgents.values());\r\n }\r\n\r\n /**\r\n * Get running sub-agents\r\n */\r\n getRunningSubAgents(): SubAgent[] {\r\n return this.getAllSubAgents().filter(a => a.status === 'running' || a.status === 'pending');\r\n }\r\n\r\n /**\r\n * Get completed but unread sub-agents\r\n */\r\n getCompletedUnreadSubAgents(): SubAgent[] {\r\n return this.getAllSubAgents().filter(a =>\r\n (a.status === 'completed' || a.status === 'failed' || a.status === 'terminated') &&\r\n !a.isRead\r\n );\r\n }\r\n\r\n /**\r\n * Terminate a sub-agent\r\n */\r\n terminateSubAgent(agentId: string): { success: boolean; error?: string } {\r\n const subAgent = this.subAgents.get(agentId);\r\n if (!subAgent) {\r\n return { success: false, error: `Sub-agent ${agentId} not found` };\r\n }\r\n\r\n if (subAgent.status !== 'running' && subAgent.status !== 'pending') {\r\n return { success: false, error: `Sub-agent ${agentId} is not running (status: ${subAgent.status})` };\r\n }\r\n\r\n subAgent.abortController?.abort();\r\n subAgent.status = 'terminated';\r\n subAgent.endTime = new Date();\r\n this.notifyCountChange();\r\n\r\n return { success: true };\r\n }\r\n\r\n /**\r\n * Get formatted diff output for a sub-agent\r\n */\r\n getSubAgentDiff(agentId: string): string {\r\n const subAgent = this.subAgents.get(agentId);\r\n if (!subAgent) {\r\n return `Sub-agent ${agentId} not found`;\r\n }\r\n\r\n const lines: string[] = [];\r\n lines.push(`## Sub-Agent Results: ${agentId}`);\r\n lines.push(`**Status:** ${subAgent.status}`);\r\n lines.push(`**Model:** ${subAgent.model}`);\r\n lines.push(`**Duration:** ${this.formatDuration(subAgent.startTime, subAgent.endTime)}`);\r\n lines.push(`**Turns:** ${subAgent.turnCount}`);\r\n lines.push('');\r\n\r\n if (subAgent.result) {\r\n lines.push(`**Result:** ${subAgent.result}`);\r\n lines.push('');\r\n }\r\n\r\n if (subAgent.error) {\r\n lines.push(`**Error:** ${subAgent.error}`);\r\n lines.push('');\r\n }\r\n\r\n // File operations\r\n if (subAgent.fileOperations.length > 0) {\r\n lines.push('### File Operations');\r\n lines.push('');\r\n\r\n for (const op of subAgent.fileOperations) {\r\n const typeIcon = op.type === 'create' ? 'šŸ“' : op.type === 'edit' ? 'āœļø' : 'šŸ—‘ļø';\r\n lines.push(`${typeIcon} **${op.type.toUpperCase()}**: \\`${op.path}\\``);\r\n\r\n if (op.reasonText) {\r\n lines.push(` *Reason:* ${op.reasonText}`);\r\n }\r\n\r\n if (op.diff) {\r\n lines.push('```diff');\r\n lines.push(op.diff);\r\n lines.push('```');\r\n } else if (op.newContent && op.type === 'create') {\r\n // For new files, show a summary\r\n const lineCount = op.newContent.split('\\n').length;\r\n lines.push(` *Created file with ${lineCount} lines*`);\r\n }\r\n lines.push('');\r\n }\r\n }\r\n\r\n // Tool execution history (summarized)\r\n if (subAgent.toolHistory.length > 0) {\r\n lines.push('### Tool Execution Summary');\r\n lines.push('');\r\n\r\n const toolCounts = new Map<string, number>();\r\n for (const exec of subAgent.toolHistory) {\r\n toolCounts.set(exec.toolName, (toolCounts.get(exec.toolName) || 0) + 1);\r\n }\r\n\r\n for (const [tool, count] of toolCounts) {\r\n lines.push(`- ${tool}: ${count} call(s)`);\r\n }\r\n lines.push('');\r\n }\r\n\r\n const markdownReport = lines.join('\\n');\r\n\r\n // Create structured data for UI\r\n const structuredData = {\r\n agentId: subAgent.id,\r\n status: subAgent.status,\r\n model: subAgent.model,\r\n duration: this.formatDuration(subAgent.startTime, subAgent.endTime),\r\n turnCount: subAgent.turnCount,\r\n fileOperations: subAgent.fileOperations.map(op => ({\r\n type: op.type,\r\n path: op.path\r\n })),\r\n result: subAgent.result,\r\n error: subAgent.error\r\n };\r\n\r\n // Return JSON string containing both checkable text (for AI) and structured data (for UI)\r\n // Wrapped in a format that looks like text but can be parsed as JSON if needed by UI\r\n // Actually, let's just return a JSON object string. The AI can read JSON.\r\n return JSON.stringify({\r\n report: markdownReport,\r\n data: structuredData\r\n }, null, 2);\r\n }\r\n\r\n /**\r\n * Mark sub-agent results as read\r\n */\r\n markSubAgentRead(agentId: string): void {\r\n const subAgent = this.subAgents.get(agentId);\r\n if (subAgent) {\r\n subAgent.isRead = true;\r\n }\r\n }\r\n\r\n /**\r\n * Format duration between two dates\r\n */\r\n private formatDuration(start: Date, end?: Date): string {\r\n const endTime = end || new Date();\r\n const durationMs = endTime.getTime() - start.getTime();\r\n const seconds = Math.floor(durationMs / 1000);\r\n const minutes = Math.floor(seconds / 60);\r\n\r\n if (minutes > 0) {\r\n return `${minutes}m ${seconds % 60}s`;\r\n }\r\n return `${seconds}s`;\r\n }\r\n\r\n /**\r\n * Clean up old completed sub-agents (call periodically)\r\n */\r\n cleanup(): void {\r\n const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);\r\n\r\n for (const [id, agent] of this.subAgents) {\r\n if (agent.endTime && agent.endTime < oneHourAgo && agent.isRead) {\r\n this.subAgents.delete(id);\r\n }\r\n }\r\n }\r\n}\r\n\r\n// Export singleton\r\nexport const SubAgentManager = new SubAgentManagerClass();\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/services/sub-agent-manager.ts"],"sourcesContent":["/**\r\n * Sub-Agent Manager Service\r\n * \r\n * Manages the lifecycle of background sub-agents that can be spawned\r\n * by the main agent to handle delegated tasks.\r\n */\r\n\r\nimport { randomUUID } from 'crypto';\r\nimport { aiServiceClient, Message as AIMessage, StreamChunk } from './ai-service-client.js';\r\nimport { apiClient } from './api-client.js';\r\nimport { ToolRegistry } from '../tools/registry.js';\r\nimport { ToolExecutionContext } from '../tools/types.js';\r\nimport { sessionQuotaManager } from './session-quota-manager.js';\r\nimport { logError, logWarning } from '../utils/logger.js';\r\nimport { quickLog } from '../utils/conversation-logger.js';\r\nimport { ContextManager } from '../context/context-manager.js';\r\n\r\n// ==================== Types ====================\r\n\r\nexport interface SubAgentConfig {\r\n prompt: string; // Task description\r\n context: string; // Context information (file paths, structure, etc.)\r\n workingDirectory: string; // CWD for the sub-agent\r\n complexity: number; // 1-10, decides model selection\r\n parentContextManager?: any; // Parent's ContextManager for remote session support (SSH/Docker/WSL)\r\n allowedTools?: string[]; // Optional whitelist of tool names the sub-agent may use\r\n modelOverride?: string; // Optional model ID override (bypasses complexity-based selection)\r\n}\r\n\r\nexport interface FileOperation {\r\n type: 'create' | 'edit' | 'delete';\r\n path: string;\r\n originalContent?: string; // For edits: content before change\r\n newContent?: string; // For create/edit: content after change\r\n diff?: string; // Unified diff format\r\n reasonText?: string; // reason_text from the tool call\r\n timestamp: Date;\r\n}\r\n\r\nexport interface ToolExecution {\r\n toolName: string;\r\n arguments: Record<string, any>;\r\n reasonText?: string;\r\n result: string;\r\n success: boolean;\r\n timestamp: Date;\r\n}\r\n\r\nexport type SubAgentStatus = 'pending' | 'running' | 'completed' | 'failed' | 'terminated';\r\n\r\nexport interface SubAgent {\r\n id: string;\r\n status: SubAgentStatus;\r\n prompt: string;\r\n context: string;\r\n workingDirectory: string;\r\n complexity: number;\r\n model: string;\r\n startTime: Date;\r\n endTime?: Date;\r\n\r\n // Agent loop state\r\n conversationHistory: AIMessage[];\r\n turnCount: number;\r\n\r\n // Tracking changes\r\n fileOperations: FileOperation[];\r\n toolHistory: ToolExecution[];\r\n\r\n // Results\r\n result?: string;\r\n error?: string;\r\n\r\n // Internal flags\r\n isRead: boolean; // Whether main agent has read the results\r\n abortController?: AbortController; // For cancellation\r\n allowedTools?: string[]; // Optional tool whitelist (undefined = all tools allowed)\r\n}\r\n\r\n// ==================== Constants ====================\r\n\r\nconst MAX_CONCURRENT_SUBAGENTS = 5;\r\nconst SUBAGENT_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes\r\nconst MAX_TURNS_PER_SUBAGENT = 50;\r\n\r\n// Model selection based on complexity\r\nconst SIMPLE_MODEL = 'minimaxai/minimax-m2.5';\r\nconst COMPLEX_MODEL = 'z-ai/glm5';\r\nconst COMPLEXITY_THRESHOLD = 5;\r\n\r\n// ==================== Sub-Agent System Prompt ====================\r\n\r\nconst SUBAGENT_SYSTEM_PROMPT = `You are a sub-agent spawned by a main AI agent to complete a specific task.\r\n\r\nIMPORTANT RULES:\r\n1. You have access to file system tools (read, write, edit, list, grep, find)\r\n2. You have access to command execution tools\r\n3. Focus ONLY on the task assigned to you\r\n4. Always provide reason_text for every tool call to explain what you're doing\r\n5. Call task_complete when you've finished the assigned task\r\n6. Be efficient - don't explore unnecessarily, stick to the task\r\n7. If you encounter errors, try to resolve them or report clearly what went wrong\r\n8. CRITICAL: Your working directory is {WORKING_DIRECTORY}. All file operations (create, edit, write) MUST be within this directory. Use relative paths from this directory. Do NOT create files in other directories.\r\n{TOOL_RESTRICTIONS}\r\nCONTEXT FROM MAIN AGENT:\r\nWorking Directory: {WORKING_DIRECTORY}\r\n\r\n{CONTEXT}\r\n\r\nYOUR TASK:\r\n{PROMPT}\r\n\r\nComplete this task efficiently and call task_complete when done.`;\r\n\r\n// ==================== SubAgentManager Class ====================\r\n\r\nclass SubAgentManagerClass {\r\n private subAgents: Map<string, SubAgent> = new Map();\r\n private toolRegistry?: ToolRegistry;\r\n private onSubAgentCountChange?: (count: number) => void;\r\n private onToolExecutionUpdate?: (update: any) => void;\r\n\r\n /**\r\n * Initialize the manager with a tool registry\r\n */\r\n initialize(toolRegistry: ToolRegistry): void {\r\n this.toolRegistry = toolRegistry;\r\n quickLog(`[${new Date().toISOString()}] [SubAgentManager] Initialized with tool registry\\n`);\r\n }\r\n\r\n /**\r\n * Set callback for sub-agent count changes\r\n */\r\n setOnSubAgentCountChange(callback: (count: number) => void): void {\r\n this.onSubAgentCountChange = callback;\r\n quickLog(`[${new Date().toISOString()}] [SubAgentManager] setOnSubAgentCountChange callback registered\\n`);\r\n }\r\n\r\n /**\r\n * Set callback for tool execution updates (for logging)\r\n */\r\n setOnToolExecutionUpdate(callback: (update: any) => void): void {\r\n this.onToolExecutionUpdate = callback;\r\n }\r\n\r\n /**\r\n * Notify listeners about sub-agent count change\r\n */\r\n private notifyCountChange(): void {\r\n const runningAgents = this.getRunningSubAgents();\r\n const runningCount = runningAgents.length;\r\n const details = runningAgents.map(a => `${a.id}:${a.status}`).join(', ');\r\n\r\n quickLog(`[${new Date().toISOString()}] [SubAgentManager] notifyCountChange called, runningCount=${runningCount}, agents=[${details}]\\n`);\r\n\r\n if (this.onSubAgentCountChange) {\r\n this.onSubAgentCountChange(runningCount);\r\n // quickLog(`Callback invoked`) // Removed redundant log or kept if preferred\r\n }\r\n }\r\n\r\n /**\r\n * Select model based on complexity\r\n */\r\n private selectModel(complexity: number): string {\r\n return complexity <= COMPLEXITY_THRESHOLD ? SIMPLE_MODEL : COMPLEX_MODEL;\r\n }\r\n\r\n /**\r\n * Generate system prompt for sub-agent\r\n */\r\n private generateSystemPrompt(config: SubAgentConfig): string {\r\n let toolRestrictions = '';\r\n if (config.allowedTools && config.allowedTools.length > 0) {\r\n toolRestrictions = `\r\nTOOL RESTRICTIONS (CRITICAL):\r\nYou are ONLY allowed to use the following tools: ${config.allowedTools.join(', ')}, task_complete\r\nIf you need a tool that is not in this list, DO NOT attempt to call it — it will be rejected.\r\nYou must complete your task using only the tools listed above.\r\n`;\r\n }\r\n return SUBAGENT_SYSTEM_PROMPT\r\n .replace('{WORKING_DIRECTORY}', config.workingDirectory)\r\n .replace('{CONTEXT}', config.context || 'No additional context provided.')\r\n .replace('{PROMPT}', config.prompt)\r\n .replace('{TOOL_RESTRICTIONS}', toolRestrictions);\r\n }\r\n\r\n /**\r\n * Spawn a new sub-agent\r\n */\r\n async spawnSubAgent(config: SubAgentConfig): Promise<{ success: boolean; agentId?: string; error?: string }> {\r\n // Check concurrent limit\r\n if (this.getRunningSubAgents().length >= MAX_CONCURRENT_SUBAGENTS) {\r\n return {\r\n success: false,\r\n error: `Maximum ${MAX_CONCURRENT_SUBAGENTS} concurrent sub-agents allowed. Wait for existing sub-agents to complete.`\r\n };\r\n }\r\n\r\n // Check if tool registry is initialized\r\n if (!this.toolRegistry) {\r\n return {\r\n success: false,\r\n error: 'SubAgentManager not initialized. Tool registry is missing.'\r\n };\r\n }\r\n\r\n // Check authentication\r\n if (!apiClient.isAuthenticated()) {\r\n return {\r\n success: false,\r\n error: 'Authentication required for sub-agents.'\r\n };\r\n }\r\n\r\n // Check session quota\r\n if (!sessionQuotaManager.canSendMessage()) {\r\n return {\r\n success: false,\r\n error: 'Session quota exhausted. Cannot spawn sub-agent.'\r\n };\r\n }\r\n\r\n // Create sub-agent\r\n const agentId = `subagent-${randomUUID().substring(0, 8)}`;\r\n const model = config.modelOverride || this.selectModel(config.complexity);\r\n\r\n const subAgent: SubAgent = {\r\n id: agentId,\r\n status: 'pending',\r\n prompt: config.prompt,\r\n context: config.context,\r\n workingDirectory: config.workingDirectory,\r\n complexity: config.complexity,\r\n model,\r\n startTime: new Date(),\r\n conversationHistory: [],\r\n turnCount: 0,\r\n fileOperations: [],\r\n toolHistory: [],\r\n isRead: false,\r\n abortController: new AbortController(),\r\n allowedTools: config.allowedTools,\r\n };\r\n\r\n this.subAgents.set(agentId, subAgent);\r\n quickLog(`[${new Date().toISOString()}] [SubAgentManager] Sub-agent ${agentId} added to map, calling notifyCountChange\\\\n`);\r\n this.notifyCountChange();\r\n\r\n // Start the sub-agent loop in background (don't await)\r\n this.runSubAgentLoop(agentId, config).catch(error => {\r\n logError(`Sub-agent ${agentId} failed:`, error);\r\n const agent = this.subAgents.get(agentId);\r\n if (agent && agent.status === 'running') {\r\n agent.status = 'failed';\r\n agent.error = error.message || String(error);\r\n agent.endTime = new Date();\r\n this.notifyCountChange();\r\n }\r\n });\r\n\r\n return { success: true, agentId };\r\n }\r\n\r\n /**\r\n * Run the sub-agent's agent loop\r\n */\r\n private async runSubAgentLoop(agentId: string, config: SubAgentConfig): Promise<void> {\r\n const subAgent = this.subAgents.get(agentId);\r\n if (!subAgent) {\r\n throw new Error(`Sub-agent ${agentId} not found`);\r\n }\r\n\r\n subAgent.status = 'running';\r\n this.notifyCountChange();\r\n\r\n // Set up timeout\r\n const timeoutId = setTimeout(() => {\r\n const agent = this.subAgents.get(agentId);\r\n if (agent && agent.status === 'running') {\r\n agent.status = 'failed';\r\n agent.error = 'Sub-agent timed out after 10 minutes';\r\n agent.endTime = new Date();\r\n agent.abortController?.abort();\r\n this.notifyCountChange();\r\n }\r\n }, SUBAGENT_TIMEOUT_MS);\r\n\r\n try {\r\n // Initialize conversation with system prompt (as user message since backend injects system)\r\n const systemPrompt = this.generateSystemPrompt(config);\r\n subAgent.conversationHistory.push({\r\n role: 'user',\r\n content: systemPrompt,\r\n });\r\n\r\n let tools = this.toolRegistry!.getSchemas();\r\n\r\n // Filter tool schemas if allowedTools is specified\r\n // Always keep task_complete available regardless of restrictions\r\n if (config.allowedTools && config.allowedTools.length > 0) {\r\n const allowedSet = new Set([...config.allowedTools, 'task_complete']);\r\n tools = tools.filter(t => allowedSet.has(t.name));\r\n }\r\n\r\n // Build execution context\r\n // Use parent's ContextManager if available (for remote sessions like SSH/Docker/WSL),\r\n // otherwise create a new local one\r\n const effectiveContextManager = config.parentContextManager || new ContextManager(config.workingDirectory, process.platform);\r\n\r\n const context: ToolExecutionContext = {\r\n cwd: config.workingDirectory,\r\n contextManager: effectiveContextManager,\r\n requireApproval: async () => true, // Auto-approve for sub-agents (autonomous)\r\n onStreamingOutput: () => { }, // No streaming output for sub-agents (yet)\r\n };\r\n\r\n // Detect environment from the effective context manager's current state\r\n // This ensures the AI model knows the correct OS/shell when in a remote session\r\n const currentCtx = effectiveContextManager.getCurrentContext();\r\n const isRemote = currentCtx && currentCtx.type !== 'local';\r\n let envOs: string;\r\n let envShell: string;\r\n let envHomeDir: string;\r\n let envPlatform: string;\r\n\r\n if (isRemote && currentCtx.metadata) {\r\n // Use remote session's OS/shell info\r\n const remoteOs = currentCtx.metadata.os;\r\n envOs = remoteOs || 'linux'; // Most remote sessions are Linux\r\n envPlatform = envOs === 'windows' ? 'win32' : 'linux';\r\n envShell = currentCtx.metadata.shell || 'bash';\r\n envHomeDir = currentCtx.metadata.homeDirectory || `/home/${currentCtx.metadata.username || 'user'}`;\r\n } else {\r\n // Local environment\r\n envOs = process.platform === 'win32' ? 'windows' : process.platform === 'darwin' ? 'macos' : 'linux';\r\n envPlatform = process.platform;\r\n envShell = process.env.SHELL || 'cmd.exe';\r\n envHomeDir = process.env.USERPROFILE || process.env.HOME || '';\r\n }\r\n\r\n // Agent loop\r\n while (subAgent.turnCount < MAX_TURNS_PER_SUBAGENT && subAgent.status === 'running') {\r\n subAgent.turnCount++;\r\n\r\n // Check quota\r\n sessionQuotaManager.incrementMessageCount();\r\n if (!sessionQuotaManager.canSendMessage()) {\r\n subAgent.status = 'failed';\r\n subAgent.error = 'Session quota exhausted during sub-agent execution';\r\n break;\r\n }\r\n\r\n let assistantMessage = '';\r\n let toolCalls: any[] = [];\r\n\r\n // Stream AI response\r\n try {\r\n for await (const chunk of aiServiceClient.streamChat(\r\n subAgent.model,\r\n subAgent.conversationHistory,\r\n tools,\r\n { cwd: config.workingDirectory, platform: envPlatform, shell: envShell, os: envOs as 'windows' | 'macos' | 'linux', homeDir: envHomeDir },\r\n undefined,\r\n undefined,\r\n subAgent.abortController?.signal\r\n )) {\r\n if (chunk.type === 'error') {\r\n throw new Error(chunk.message);\r\n }\r\n\r\n if (chunk.type === 'text') {\r\n assistantMessage += chunk.content;\r\n }\r\n\r\n if (chunk.type === 'tool_call') {\r\n const toolCall = chunk.toolCall;\r\n // Parse string arguments if needed\r\n if (toolCall.arguments && typeof toolCall.arguments === 'string') {\r\n try {\r\n toolCall.arguments = JSON.parse(toolCall.arguments);\r\n } catch (e) {\r\n // Ignore parsing error\r\n }\r\n }\r\n toolCalls.push(toolCall);\r\n }\r\n\r\n if (chunk.type === 'done') {\r\n break;\r\n }\r\n }\r\n } catch (error: any) {\r\n if (error.name === 'AbortError') {\r\n // Sub-agent was terminated\r\n subAgent.status = 'terminated';\r\n subAgent.endTime = new Date();\r\n break;\r\n }\r\n throw error;\r\n }\r\n\r\n // Add assistant message to history\r\n if (toolCalls.length > 0 || assistantMessage) {\r\n const assistantMsg: AIMessage = {\r\n role: 'assistant',\r\n content: assistantMessage,\r\n };\r\n if (toolCalls.length > 0) {\r\n assistantMsg.tool_calls = toolCalls;\r\n }\r\n subAgent.conversationHistory.push(assistantMsg);\r\n }\r\n\r\n // Execute tool calls\r\n if (toolCalls.length === 0) {\r\n // No tool calls and no task_complete - something went wrong\r\n subAgent.status = 'completed';\r\n subAgent.result = assistantMessage || 'Sub-agent completed without explicit task_complete';\r\n subAgent.endTime = new Date();\r\n break;\r\n }\r\n\r\n for (const toolCall of toolCalls) {\r\n // Check for task_complete\r\n if (toolCall.name === 'task_complete') {\r\n subAgent.status = 'completed';\r\n subAgent.result = assistantMessage || 'Task completed successfully';\r\n subAgent.endTime = new Date();\r\n break;\r\n }\r\n\r\n // Runtime enforcement of allowed tools whitelist\r\n if (subAgent.allowedTools && subAgent.allowedTools.length > 0) {\r\n const allowedSet = new Set([...subAgent.allowedTools, 'task_complete']);\r\n if (!allowedSet.has(toolCall.name)) {\r\n quickLog(`[SubAgent ${subAgent.id}] REJECTED tool \"${toolCall.name}\" — not in allowed list: ${subAgent.allowedTools.join(', ')}`);\r\n const rejectionMsg = `Tool \"${toolCall.name}\" is not allowed for this sub-agent. Allowed tools: ${subAgent.allowedTools.join(', ')}, task_complete. Please use only allowed tools.`;\r\n\r\n // Track the rejection\r\n const rejectedExecution: ToolExecution = {\r\n toolName: toolCall.name,\r\n arguments: toolCall.arguments,\r\n reasonText: toolCall.arguments?.reason_text,\r\n result: rejectionMsg,\r\n success: false,\r\n timestamp: new Date(),\r\n };\r\n subAgent.toolHistory.push(rejectedExecution);\r\n\r\n // Feed rejection back to the AI so it can adjust\r\n subAgent.conversationHistory.push({\r\n role: 'tool',\r\n content: `ERROR: ${rejectionMsg}`,\r\n tool_call_id: toolCall.id,\r\n });\r\n continue;\r\n }\r\n }\r\n\r\n // Track the tool execution\r\n const reasonText = toolCall.arguments?.reason_text;\r\n const toolExecution: ToolExecution = {\r\n toolName: toolCall.name,\r\n arguments: toolCall.arguments,\r\n reasonText,\r\n result: '',\r\n success: false,\r\n timestamp: new Date(),\r\n };\r\n\r\n try {\r\n quickLog(`[SubAgent ${subAgent.id}] Executing tool: ${toolCall.name}`);\r\n\r\n // Execute the tool\r\n const result = await this.toolRegistry!.execute(toolCall.name, toolCall.arguments, context);\r\n\r\n quickLog(`[SubAgent ${subAgent.id}] Tool ${toolCall.name} execution result: ${JSON.stringify(result)}`);\r\n\r\n toolExecution.success = result.success;\r\n toolExecution.result = result.result;\r\n\r\n // Track file operations\r\n this.trackFileOperation(subAgent, toolCall, result);\r\n\r\n // Add tool result to history\r\n subAgent.conversationHistory.push({\r\n role: 'tool',\r\n tool_call_id: toolCall.id,\r\n content: result.success ? result.result : `Error: ${result.error}`,\r\n });\r\n\r\n } catch (error: any) {\r\n toolExecution.success = false;\r\n toolExecution.result = `Error: ${error.message}`;\r\n\r\n subAgent.conversationHistory.push({\r\n role: 'tool',\r\n tool_call_id: toolCall.id,\r\n content: `Error: ${error.message}`,\r\n });\r\n }\r\n\r\n subAgent.toolHistory.push(toolExecution);\r\n }\r\n\r\n // Check if task was completed\r\n if (subAgent.status === 'completed') {\r\n break;\r\n }\r\n }\r\n\r\n // Check if we hit max turns\r\n if (subAgent.turnCount >= MAX_TURNS_PER_SUBAGENT && subAgent.status === 'running') {\r\n subAgent.status = 'failed';\r\n subAgent.error = `Sub-agent exceeded maximum ${MAX_TURNS_PER_SUBAGENT} turns`;\r\n subAgent.endTime = new Date();\r\n }\r\n\r\n } finally {\r\n clearTimeout(timeoutId);\r\n this.notifyCountChange();\r\n\r\n // Free heavy data from completed agents to prevent memory leaks.\r\n // conversationHistory can be megabytes and is only needed during the loop.\r\n if (subAgent.status !== 'running') {\r\n subAgent.conversationHistory = [];\r\n }\r\n\r\n // Clean up old read agents\r\n this.cleanup();\r\n }\r\n\r\n quickLog(`[${new Date().toISOString()}] [SubAgent] ${agentId} finished with status: ${subAgent.status}\\n`);\r\n }\r\n\r\n /**\r\n * Track file operations made by sub-agent\r\n */\r\n private trackFileOperation(subAgent: SubAgent, toolCall: any, result: any): void {\r\n const { name, arguments: args } = toolCall;\r\n\r\n if (name === 'write_to_file') {\r\n subAgent.fileOperations.push({\r\n type: 'create',\r\n path: args.TargetFile || args.target_path || args.file_path || 'unknown_file',\r\n newContent: args.content,\r\n reasonText: args.reason_text,\r\n timestamp: new Date(),\r\n });\r\n } else if (name === 'edit_file' || name === 'multi_edit_file') {\r\n subAgent.fileOperations.push({\r\n type: 'edit',\r\n path: args.file_path || args.target_path || 'unknown_file',\r\n diff: result.success ? result.result : undefined,\r\n reasonText: args.reason_text,\r\n timestamp: new Date(),\r\n });\r\n }\r\n // Note: We could also track delete operations if there was a delete_file tool\r\n }\r\n\r\n /**\r\n * Get a sub-agent by ID\r\n */\r\n getSubAgent(agentId: string): SubAgent | undefined {\r\n return this.subAgents.get(agentId);\r\n }\r\n\r\n /**\r\n * Get all sub-agents\r\n */\r\n getAllSubAgents(): SubAgent[] {\r\n return Array.from(this.subAgents.values());\r\n }\r\n\r\n /**\r\n * Get running sub-agents\r\n */\r\n getRunningSubAgents(): SubAgent[] {\r\n return this.getAllSubAgents().filter(a => a.status === 'running' || a.status === 'pending');\r\n }\r\n\r\n /**\r\n * Get completed but unread sub-agents\r\n */\r\n getCompletedUnreadSubAgents(): SubAgent[] {\r\n return this.getAllSubAgents().filter(a =>\r\n (a.status === 'completed' || a.status === 'failed' || a.status === 'terminated') &&\r\n !a.isRead\r\n );\r\n }\r\n\r\n /**\r\n * Terminate a sub-agent\r\n */\r\n terminateSubAgent(agentId: string): { success: boolean; error?: string } {\r\n const subAgent = this.subAgents.get(agentId);\r\n if (!subAgent) {\r\n return { success: false, error: `Sub-agent ${agentId} not found` };\r\n }\r\n\r\n if (subAgent.status !== 'running' && subAgent.status !== 'pending') {\r\n return { success: false, error: `Sub-agent ${agentId} is not running (status: ${subAgent.status})` };\r\n }\r\n\r\n subAgent.abortController?.abort();\r\n subAgent.status = 'terminated';\r\n subAgent.endTime = new Date();\r\n this.notifyCountChange();\r\n\r\n return { success: true };\r\n }\r\n\r\n /**\r\n * Get formatted diff output for a sub-agent\r\n */\r\n getSubAgentDiff(agentId: string): string {\r\n const subAgent = this.subAgents.get(agentId);\r\n if (!subAgent) {\r\n return `Sub-agent ${agentId} not found`;\r\n }\r\n\r\n const lines: string[] = [];\r\n lines.push(`## Sub-Agent Results: ${agentId}`);\r\n lines.push(`**Status:** ${subAgent.status}`);\r\n lines.push(`**Model:** ${subAgent.model}`);\r\n lines.push(`**Duration:** ${this.formatDuration(subAgent.startTime, subAgent.endTime)}`);\r\n lines.push(`**Turns:** ${subAgent.turnCount}`);\r\n lines.push('');\r\n\r\n if (subAgent.result) {\r\n lines.push(`**Result:** ${subAgent.result}`);\r\n lines.push('');\r\n }\r\n\r\n if (subAgent.error) {\r\n lines.push(`**Error:** ${subAgent.error}`);\r\n lines.push('');\r\n }\r\n\r\n // File operations\r\n if (subAgent.fileOperations.length > 0) {\r\n lines.push('### File Operations');\r\n lines.push('');\r\n\r\n for (const op of subAgent.fileOperations) {\r\n const typeIcon = op.type === 'create' ? 'šŸ“' : op.type === 'edit' ? 'āœļø' : 'šŸ—‘ļø';\r\n lines.push(`${typeIcon} **${op.type.toUpperCase()}**: \\`${op.path}\\``);\r\n\r\n if (op.reasonText) {\r\n lines.push(` *Reason:* ${op.reasonText}`);\r\n }\r\n\r\n if (op.diff) {\r\n lines.push('```diff');\r\n lines.push(op.diff);\r\n lines.push('```');\r\n } else if (op.newContent && op.type === 'create') {\r\n // For new files, show a summary\r\n const lineCount = op.newContent.split('\\n').length;\r\n lines.push(` *Created file with ${lineCount} lines*`);\r\n }\r\n lines.push('');\r\n }\r\n }\r\n\r\n // Tool execution history (summarized)\r\n if (subAgent.toolHistory.length > 0) {\r\n lines.push('### Tool Execution Summary');\r\n lines.push('');\r\n\r\n const toolCounts = new Map<string, number>();\r\n for (const exec of subAgent.toolHistory) {\r\n toolCounts.set(exec.toolName, (toolCounts.get(exec.toolName) || 0) + 1);\r\n }\r\n\r\n for (const [tool, count] of toolCounts) {\r\n lines.push(`- ${tool}: ${count} call(s)`);\r\n }\r\n lines.push('');\r\n }\r\n\r\n const markdownReport = lines.join('\\n');\r\n\r\n // Create structured data for UI\r\n const structuredData = {\r\n agentId: subAgent.id,\r\n status: subAgent.status,\r\n model: subAgent.model,\r\n duration: this.formatDuration(subAgent.startTime, subAgent.endTime),\r\n turnCount: subAgent.turnCount,\r\n fileOperations: subAgent.fileOperations.map(op => ({\r\n type: op.type,\r\n path: op.path\r\n })),\r\n result: subAgent.result,\r\n error: subAgent.error\r\n };\r\n\r\n // Return JSON string containing both checkable text (for AI) and structured data (for UI)\r\n // Wrapped in a format that looks like text but can be parsed as JSON if needed by UI\r\n // Actually, let's just return a JSON object string. The AI can read JSON.\r\n return JSON.stringify({\r\n report: markdownReport,\r\n data: structuredData\r\n }, null, 2);\r\n }\r\n\r\n /**\r\n * Mark sub-agent results as read\r\n */\r\n markSubAgentRead(agentId: string): void {\r\n const subAgent = this.subAgents.get(agentId);\r\n if (subAgent) {\r\n subAgent.isRead = true;\r\n }\r\n }\r\n\r\n /**\r\n * Format duration between two dates\r\n */\r\n private formatDuration(start: Date, end?: Date): string {\r\n const endTime = end || new Date();\r\n const durationMs = endTime.getTime() - start.getTime();\r\n const seconds = Math.floor(durationMs / 1000);\r\n const minutes = Math.floor(seconds / 60);\r\n\r\n if (minutes > 0) {\r\n return `${minutes}m ${seconds % 60}s`;\r\n }\r\n return `${seconds}s`;\r\n }\r\n\r\n /**\r\n * Clean up old completed sub-agents (call periodically)\r\n */\r\n cleanup(): void {\r\n const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);\r\n\r\n for (const [id, agent] of this.subAgents) {\r\n if (agent.endTime && agent.endTime < oneHourAgo && agent.isRead) {\r\n this.subAgents.delete(id);\r\n }\r\n }\r\n }\r\n}\r\n\r\n// Export singleton\r\nexport const SubAgentManager = new SubAgentManagerClass();\r\n"],"mappings":"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","names":[]}
@@ -1,4 +1,5 @@
1
1
  import stripAnsi from "strip-ansi";
2
+ import { matchTunnelableCommand } from "../utils/tunnel-commands-manager.js";
2
3
  const SSH_COMMAND_PATTERNS = [
3
4
  // ssh user@host, ssh -p port user@host, ssh -i key user@host
4
5
  /^ssh\s+(?:(?:-\w+\s+\S+\s+)*)?(?:(\S+)@)?(\S+)/i
@@ -15,8 +16,8 @@ const DOCKER_COMMAND_PATTERNS = [
15
16
  // docker run -it image bash
16
17
  /^docker\s+run\s+(?:(?:-\w+(?:\s+\S+)?\s+)*)?(\S+)/i
17
18
  ];
18
- function detectWarpifySession(command, output) {
19
- const commandSession = detectFromCommand(command);
19
+ function detectWarpifySession(command, output, customTunnelCommands = []) {
20
+ const commandSession = detectFromCommand(command, customTunnelCommands);
20
21
  if (commandSession.type !== "none") {
21
22
  return commandSession;
22
23
  }
@@ -32,7 +33,7 @@ function detectWarpifySession(command, output) {
32
33
  confidence: "high"
33
34
  };
34
35
  }
35
- function detectFromCommand(command) {
36
+ function detectFromCommand(command, customTunnelCommands = []) {
36
37
  if (!command) {
37
38
  return { type: "none", detectedFrom: "none", confidence: "high" };
38
39
  }
@@ -74,6 +75,15 @@ function detectFromCommand(command) {
74
75
  };
75
76
  }
76
77
  }
78
+ const matchedTunnelCommand = matchTunnelableCommand(trimmedCommand, customTunnelCommands);
79
+ if (matchedTunnelCommand) {
80
+ return {
81
+ type: "custom",
82
+ connectionString: matchedTunnelCommand,
83
+ detectedFrom: "command",
84
+ confidence: "high"
85
+ };
86
+ }
77
87
  return { type: "none", detectedFrom: "none", confidence: "high" };
78
88
  }
79
89
  function detectFromOutput(output) {
@@ -120,6 +130,8 @@ function getSessionDescription(session) {
120
130
  return session.connectionString ? `WSL: ${session.connectionString}` : "WSL session detected";
121
131
  case "docker":
122
132
  return session.connectionString ? `Docker container: ${session.connectionString}` : "Docker session detected";
133
+ case "custom":
134
+ return session.connectionString ? `Custom tunnel command: ${session.connectionString}` : "Custom tunnel command detected";
123
135
  default:
124
136
  return "No remote session detected";
125
137
  }
@@ -131,8 +143,8 @@ class WarpifyDetector {
131
143
  * @param command - The shell command that started the session
132
144
  * @param output - Optional PTY output for fallback detection
133
145
  */
134
- detect(command, output) {
135
- const session = detectWarpifySession(command, output);
146
+ detect(command, output, customTunnelCommands = []) {
147
+ const session = detectWarpifySession(command, output, customTunnelCommands);
136
148
  this.lastDetection = session;
137
149
  return session;
138
150
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/warpify-detector.ts"],"sourcesContent":["/**\r\n * Warpify Detector Service\r\n * \r\n * Detects if the current PTY session has an active warpifiable remote session\r\n * (SSH, WSL, Docker) by analyzing:\r\n * 1. The command that started the shell (most reliable)\r\n * 2. Terminal output patterns (fallback)\r\n */\r\n\r\nimport stripAnsi from 'strip-ansi';\r\n\r\nexport type WarpifySessionType = 'ssh' | 'wsl' | 'docker' | 'none';\r\n\r\nexport interface WarpifySession {\r\n type: WarpifySessionType;\r\n connectionString?: string; // e.g., \"user@hostname\" for SSH, \"Ubuntu\" for WSL\r\n detectedFrom: 'command' | 'prompt' | 'env' | 'none';\r\n confidence: 'high' | 'medium' | 'low';\r\n}\r\n\r\n/**\r\n * SSH command patterns - detect based on the shell command\r\n */\r\nconst SSH_COMMAND_PATTERNS = [\r\n // ssh user@host, ssh -p port user@host, ssh -i key user@host\r\n /^ssh\\s+(?:(?:-\\w+\\s+\\S+\\s+)*)?(?:(\\S+)@)?(\\S+)/i,\r\n];\r\n\r\n/**\r\n * WSL command patterns\r\n */\r\nconst WSL_COMMAND_PATTERNS = [\r\n // wsl, wsl -d distro, wsl --distribution distro\r\n /^wsl(?:\\s+(?:-d|--distribution)\\s+(\\S+))?/i,\r\n // bash (when on Windows, often means WSL)\r\n /^bash$/i,\r\n];\r\n\r\n/**\r\n * Docker command patterns\r\n */\r\nconst DOCKER_COMMAND_PATTERNS = [\r\n // docker exec -it container bash\r\n /^docker\\s+exec\\s+(?:(?:-\\w+\\s+)*)?(\\S+)/i,\r\n // docker run -it image bash\r\n /^docker\\s+run\\s+(?:(?:-\\w+(?:\\s+\\S+)?\\s+)*)?(\\S+)/i,\r\n];\r\n\r\n/**\r\n * Detect warpifiable session - PRIMARY METHOD\r\n * Uses the command that started the shell for reliable detection\r\n * \r\n * @param command - The command that started the shell session (e.g., \"ssh rohan@localhost\")\r\n * @param output - Optional PTY output for fallback detection\r\n */\r\nexport function detectWarpifySession(command: string, output?: string): WarpifySession {\r\n // First, try command-based detection (most reliable)\r\n const commandSession = detectFromCommand(command);\r\n if (commandSession.type !== 'none') {\r\n return commandSession;\r\n }\r\n\r\n // Fallback: try output-based detection\r\n if (output) {\r\n const outputSession = detectFromOutput(output);\r\n if (outputSession.type !== 'none') {\r\n return outputSession;\r\n }\r\n }\r\n\r\n return {\r\n type: 'none',\r\n detectedFrom: 'none',\r\n confidence: 'high',\r\n };\r\n}\r\n\r\n/**\r\n * Detect session type from the shell command\r\n */\r\nfunction detectFromCommand(command: string): WarpifySession {\r\n if (!command) {\r\n return { type: 'none', detectedFrom: 'none', confidence: 'high' };\r\n }\r\n\r\n const trimmedCommand = command.trim();\r\n\r\n // Check for SSH command\r\n for (const pattern of SSH_COMMAND_PATTERNS) {\r\n const match = trimmedCommand.match(pattern);\r\n if (match) {\r\n const user = match[1] || 'user';\r\n const host = match[2] || 'remote';\r\n return {\r\n type: 'ssh',\r\n connectionString: `${user}@${host}`,\r\n detectedFrom: 'command',\r\n confidence: 'high',\r\n };\r\n }\r\n }\r\n\r\n // Check for WSL command\r\n for (const pattern of WSL_COMMAND_PATTERNS) {\r\n const match = trimmedCommand.match(pattern);\r\n if (match) {\r\n const distro = match[1] || 'Ubuntu';\r\n return {\r\n type: 'wsl',\r\n connectionString: distro,\r\n detectedFrom: 'command',\r\n confidence: 'high',\r\n };\r\n }\r\n }\r\n\r\n // Check for Docker command\r\n for (const pattern of DOCKER_COMMAND_PATTERNS) {\r\n const match = trimmedCommand.match(pattern);\r\n if (match) {\r\n const container = match[1]?.substring(0, 12) || 'container';\r\n return {\r\n type: 'docker',\r\n connectionString: container,\r\n detectedFrom: 'command',\r\n confidence: 'high',\r\n };\r\n }\r\n }\r\n\r\n return { type: 'none', detectedFrom: 'none', confidence: 'high' };\r\n}\r\n\r\n/**\r\n * Detect session from PTY output (fallback method)\r\n * Used when the command was not an obvious SSH/WSL/Docker command\r\n */\r\nfunction detectFromOutput(output: string): WarpifySession {\r\n // Strip ANSI codes for reliable pattern matching\r\n const cleanOutput = stripAnsi(output);\r\n const recentOutput = getRecentLines(cleanOutput, 30);\r\n\r\n // Check for SSH environment variables (very reliable)\r\n if (/SSH_CLIENT=|SSH_CONNECTION=|SSH_TTY=/.test(recentOutput)) {\r\n // Try to extract connection info from prompt\r\n const promptMatch = recentOutput.match(/([a-zA-Z0-9_-]+)@([a-zA-Z0-9._-]+)/);\r\n return {\r\n type: 'ssh',\r\n connectionString: promptMatch ? `${promptMatch[1]}@${promptMatch[2]}` : undefined,\r\n detectedFrom: 'env',\r\n confidence: 'high',\r\n };\r\n }\r\n\r\n // Check for WSL indicators\r\n if (/\\/mnt\\/[a-z]\\//.test(recentOutput) || /microsoft-standard-WSL/i.test(recentOutput)) {\r\n const promptMatch = recentOutput.match(/([a-zA-Z0-9_-]+)@([a-zA-Z0-9._-]+)/);\r\n return {\r\n type: 'wsl',\r\n connectionString: promptMatch?.[2] || 'WSL',\r\n detectedFrom: 'prompt',\r\n confidence: 'high',\r\n };\r\n }\r\n\r\n // Check for Docker indicators\r\n if (/docker|container/i.test(recentOutput) || /^[a-f0-9]{12}:/.test(recentOutput)) {\r\n const containerMatch = recentOutput.match(/@([a-f0-9]{12})/);\r\n return {\r\n type: 'docker',\r\n connectionString: containerMatch?.[1] || 'container',\r\n detectedFrom: 'prompt',\r\n confidence: 'medium',\r\n };\r\n }\r\n\r\n return { type: 'none', detectedFrom: 'none', confidence: 'high' };\r\n}\r\n\r\n/**\r\n * Get the last N lines of output\r\n */\r\nfunction getRecentLines(output: string, n: number): string {\r\n const lines = output.split('\\n');\r\n return lines.slice(-n).join('\\n');\r\n}\r\n\r\n/**\r\n * Get a human-readable description of the detected session\r\n */\r\nexport function getSessionDescription(session: WarpifySession): string {\r\n switch (session.type) {\r\n case 'ssh':\r\n return session.connectionString\r\n ? `SSH session: ${session.connectionString}`\r\n : 'SSH session detected';\r\n case 'wsl':\r\n return session.connectionString\r\n ? `WSL: ${session.connectionString}`\r\n : 'WSL session detected';\r\n case 'docker':\r\n return session.connectionString\r\n ? `Docker container: ${session.connectionString}`\r\n : 'Docker session detected';\r\n default:\r\n return 'No remote session detected';\r\n }\r\n}\r\n\r\n/**\r\n * WarpifyDetector class for stateful detection\r\n */\r\nexport class WarpifyDetector {\r\n private lastDetection: WarpifySession | null = null;\r\n\r\n /**\r\n * Detect warpifiable session\r\n * @param command - The shell command that started the session\r\n * @param output - Optional PTY output for fallback detection\r\n */\r\n detect(command: string, output?: string): WarpifySession {\r\n const session = detectWarpifySession(command, output);\r\n this.lastDetection = session;\r\n return session;\r\n }\r\n\r\n getLastDetection(): WarpifySession | null {\r\n return this.lastDetection;\r\n }\r\n\r\n reset(): void {\r\n this.lastDetection = null;\r\n }\r\n}\r\n\r\nexport const warpifyDetector = new WarpifyDetector();\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/services/warpify-detector.ts"],"sourcesContent":["/**\r\n * Warpify Detector Service\r\n * \r\n * Detects if the current PTY session has an active warpifiable remote session\r\n * (SSH, WSL, Docker) by analyzing:\r\n * 1. The command that started the shell (most reliable)\r\n * 2. Terminal output patterns (fallback)\r\n */\r\n\r\nimport stripAnsi from 'strip-ansi';\r\nimport { matchTunnelableCommand } from '../utils/tunnel-commands-manager.js';\r\n\r\nexport type WarpifySessionType = 'ssh' | 'wsl' | 'docker' | 'custom' | 'none';\r\n\r\nexport interface WarpifySession {\r\n type: WarpifySessionType;\r\n connectionString?: string; // e.g., \"user@hostname\" for SSH, \"Ubuntu\" for WSL\r\n detectedFrom: 'command' | 'prompt' | 'env' | 'none';\r\n confidence: 'high' | 'medium' | 'low';\r\n}\r\n\r\n/**\r\n * SSH command patterns - detect based on the shell command\r\n */\r\nconst SSH_COMMAND_PATTERNS = [\r\n // ssh user@host, ssh -p port user@host, ssh -i key user@host\r\n /^ssh\\s+(?:(?:-\\w+\\s+\\S+\\s+)*)?(?:(\\S+)@)?(\\S+)/i,\r\n];\r\n\r\n/**\r\n * WSL command patterns\r\n */\r\nconst WSL_COMMAND_PATTERNS = [\r\n // wsl, wsl -d distro, wsl --distribution distro\r\n /^wsl(?:\\s+(?:-d|--distribution)\\s+(\\S+))?/i,\r\n // bash (when on Windows, often means WSL)\r\n /^bash$/i,\r\n];\r\n\r\n/**\r\n * Docker command patterns\r\n */\r\nconst DOCKER_COMMAND_PATTERNS = [\r\n // docker exec -it container bash\r\n /^docker\\s+exec\\s+(?:(?:-\\w+\\s+)*)?(\\S+)/i,\r\n // docker run -it image bash\r\n /^docker\\s+run\\s+(?:(?:-\\w+(?:\\s+\\S+)?\\s+)*)?(\\S+)/i,\r\n];\r\n\r\n/**\r\n * Detect warpifiable session - PRIMARY METHOD\r\n * Uses the command that started the shell for reliable detection\r\n * \r\n * @param command - The command that started the shell session (e.g., \"ssh rohan@localhost\")\r\n * @param output - Optional PTY output for fallback detection\r\n */\r\nexport function detectWarpifySession(command: string, output?: string, customTunnelCommands: string[] = []): WarpifySession {\r\n // First, try command-based detection (most reliable)\r\n const commandSession = detectFromCommand(command, customTunnelCommands);\r\n if (commandSession.type !== 'none') {\r\n return commandSession;\r\n }\r\n\r\n // Fallback: try output-based detection\r\n if (output) {\r\n const outputSession = detectFromOutput(output);\r\n if (outputSession.type !== 'none') {\r\n return outputSession;\r\n }\r\n }\r\n\r\n return {\r\n type: 'none',\r\n detectedFrom: 'none',\r\n confidence: 'high',\r\n };\r\n}\r\n\r\n/**\r\n * Detect session type from the shell command\r\n */\r\nfunction detectFromCommand(command: string, customTunnelCommands: string[] = []): WarpifySession {\r\n if (!command) {\r\n return { type: 'none', detectedFrom: 'none', confidence: 'high' };\r\n }\r\n\r\n const trimmedCommand = command.trim();\r\n\r\n // Check for SSH command\r\n for (const pattern of SSH_COMMAND_PATTERNS) {\r\n const match = trimmedCommand.match(pattern);\r\n if (match) {\r\n const user = match[1] || 'user';\r\n const host = match[2] || 'remote';\r\n return {\r\n type: 'ssh',\r\n connectionString: `${user}@${host}`,\r\n detectedFrom: 'command',\r\n confidence: 'high',\r\n };\r\n }\r\n }\r\n\r\n // Check for WSL command\r\n for (const pattern of WSL_COMMAND_PATTERNS) {\r\n const match = trimmedCommand.match(pattern);\r\n if (match) {\r\n const distro = match[1] || 'Ubuntu';\r\n return {\r\n type: 'wsl',\r\n connectionString: distro,\r\n detectedFrom: 'command',\r\n confidence: 'high',\r\n };\r\n }\r\n }\r\n\r\n // Check for Docker command\r\n for (const pattern of DOCKER_COMMAND_PATTERNS) {\r\n const match = trimmedCommand.match(pattern);\r\n if (match) {\r\n const container = match[1]?.substring(0, 12) || 'container';\r\n return {\r\n type: 'docker',\r\n connectionString: container,\r\n detectedFrom: 'command',\r\n confidence: 'high',\r\n };\r\n }\r\n }\r\n\r\n const matchedTunnelCommand = matchTunnelableCommand(trimmedCommand, customTunnelCommands);\r\n if (matchedTunnelCommand) {\r\n return {\r\n type: 'custom',\r\n connectionString: matchedTunnelCommand,\r\n detectedFrom: 'command',\r\n confidence: 'high',\r\n };\r\n }\r\n\r\n return { type: 'none', detectedFrom: 'none', confidence: 'high' };\r\n}\r\n\r\n/**\r\n * Detect session from PTY output (fallback method)\r\n * Used when the command was not an obvious SSH/WSL/Docker command\r\n */\r\nfunction detectFromOutput(output: string): WarpifySession {\r\n // Strip ANSI codes for reliable pattern matching\r\n const cleanOutput = stripAnsi(output);\r\n const recentOutput = getRecentLines(cleanOutput, 30);\r\n\r\n // Check for SSH environment variables (very reliable)\r\n if (/SSH_CLIENT=|SSH_CONNECTION=|SSH_TTY=/.test(recentOutput)) {\r\n // Try to extract connection info from prompt\r\n const promptMatch = recentOutput.match(/([a-zA-Z0-9_-]+)@([a-zA-Z0-9._-]+)/);\r\n return {\r\n type: 'ssh',\r\n connectionString: promptMatch ? `${promptMatch[1]}@${promptMatch[2]}` : undefined,\r\n detectedFrom: 'env',\r\n confidence: 'high',\r\n };\r\n }\r\n\r\n // Check for WSL indicators\r\n if (/\\/mnt\\/[a-z]\\//.test(recentOutput) || /microsoft-standard-WSL/i.test(recentOutput)) {\r\n const promptMatch = recentOutput.match(/([a-zA-Z0-9_-]+)@([a-zA-Z0-9._-]+)/);\r\n return {\r\n type: 'wsl',\r\n connectionString: promptMatch?.[2] || 'WSL',\r\n detectedFrom: 'prompt',\r\n confidence: 'high',\r\n };\r\n }\r\n\r\n // Check for Docker indicators\r\n if (/docker|container/i.test(recentOutput) || /^[a-f0-9]{12}:/.test(recentOutput)) {\r\n const containerMatch = recentOutput.match(/@([a-f0-9]{12})/);\r\n return {\r\n type: 'docker',\r\n connectionString: containerMatch?.[1] || 'container',\r\n detectedFrom: 'prompt',\r\n confidence: 'medium',\r\n };\r\n }\r\n\r\n return { type: 'none', detectedFrom: 'none', confidence: 'high' };\r\n}\r\n\r\n/**\r\n * Get the last N lines of output\r\n */\r\nfunction getRecentLines(output: string, n: number): string {\r\n const lines = output.split('\\n');\r\n return lines.slice(-n).join('\\n');\r\n}\r\n\r\n/**\r\n * Get a human-readable description of the detected session\r\n */\r\nexport function getSessionDescription(session: WarpifySession): string {\r\n switch (session.type) {\r\n case 'ssh':\r\n return session.connectionString\r\n ? `SSH session: ${session.connectionString}`\r\n : 'SSH session detected';\r\n case 'wsl':\r\n return session.connectionString\r\n ? `WSL: ${session.connectionString}`\r\n : 'WSL session detected';\r\n case 'docker':\r\n return session.connectionString\r\n ? `Docker container: ${session.connectionString}`\r\n : 'Docker session detected';\r\n case 'custom':\r\n return session.connectionString\r\n ? `Custom tunnel command: ${session.connectionString}`\r\n : 'Custom tunnel command detected';\r\n default:\r\n return 'No remote session detected';\r\n }\r\n}\r\n\r\n/**\r\n * WarpifyDetector class for stateful detection\r\n */\r\nexport class WarpifyDetector {\r\n private lastDetection: WarpifySession | null = null;\r\n\r\n /**\r\n * Detect warpifiable session\r\n * @param command - The shell command that started the session\r\n * @param output - Optional PTY output for fallback detection\r\n */\r\n detect(command: string, output?: string, customTunnelCommands: string[] = []): WarpifySession {\r\n const session = detectWarpifySession(command, output, customTunnelCommands);\r\n this.lastDetection = session;\r\n return session;\r\n }\r\n\r\n getLastDetection(): WarpifySession | null {\r\n return this.lastDetection;\r\n }\r\n\r\n reset(): void {\r\n this.lastDetection = null;\r\n }\r\n}\r\n\r\nexport const warpifyDetector = new WarpifyDetector();\r\n"],"mappings":"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","names":[]}
@@ -1,5 +1,7 @@
1
1
  import { BackgroundTaskManager } from "../services/background-task-manager.js";
2
2
  import { runSSHCommand, runLocalPty } from "../utils/editor-utils.js";
3
+ import { processTerminalOutput } from "../utils/terminal-output.js";
4
+ import stripAnsi from "strip-ansi";
3
5
  import * as path from "path";
4
6
  function resolveBackgroundCommandCwd(currentContext, contextCwd, commandCwd) {
5
7
  if (currentContext.type === "local") {
@@ -158,8 +160,9 @@ IMPORTANT:
158
160
  Available tasks:
159
161
  ${taskList}`);
160
162
  }
161
- const fullOutput = task.output;
162
- const lines = fullOutput.split("\n");
163
+ const rawOutput = task.output;
164
+ const cleanOutput = stripAnsi(processTerminalOutput(rawOutput)).replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
165
+ const lines = cleanOutput.split("\n");
163
166
  const outputToShow = lines.slice(-output_lines).join("\n");
164
167
  const truncated = lines.length > output_lines;
165
168
  const durationMs = Date.now() - task.startTime.getTime();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/tools/background-command.ts"],"sourcesContent":["/**\r\n * Background Command Tool\r\n * \r\n * Allows the AI to execute commands in the background (non-blocking),\r\n * check their status, and kill/terminate them.\r\n * \r\n * Uses the existing BackgroundTaskManager singleton.\r\n */\r\n\r\nimport { Tool } from './types.js';\r\nimport { BackgroundTaskManager } from '../services/background-task-manager.js';\r\nimport { runSSHCommand, runLocalPty } from '../utils/editor-utils.js';\r\nimport { ContextManager } from '../context/context-manager.js';\r\nimport * as path from 'path';\r\n\r\nfunction resolveBackgroundCommandCwd(currentContext: any, contextCwd: string, commandCwd: string): string {\r\n if (currentContext.type === 'local') {\r\n return path.resolve(contextCwd, commandCwd);\r\n }\r\n\r\n const remoteBase = currentContext.metadata?.workingDirectory || contextCwd || '~';\r\n return path.posix.isAbsolute(commandCwd)\r\n ? commandCwd\r\n : path.posix.join(remoteBase, commandCwd);\r\n}\r\n\r\n\r\nexport const backgroundCommandTool: Tool = {\r\n schema: {\r\n name: 'background_command',\r\n description: `Execute shell commands in the background, check their status, or kill them. This tool is NON-BLOCKING - when you start a command, the tool returns immediately with a task ID while the command continues running in the background.\r\n\r\nUSE CASES:\r\n- Running development servers (npm run dev, python -m http.server, etc.)\r\n- Running long-running processes (builds, tests, watchers)\r\n- Running multiple commands concurrently\r\n\r\nACTIONS:\r\n1. \"start\" - Start a new background command. Returns a task_id immediately.\r\n2. \"status\" - Check the status and output of a background task. Use this to monitor progress.\r\n3. \"kill\" - Terminate a running background task.\r\n4. \"wait\" - Pause execution for a specified duration. Use this instead of repeatedly checking status.\r\n\r\nIMPORTANT:\r\n- After starting a task, you can continue with other work while it runs.\r\n- Use \"status\" periodically to check on long-running tasks.\r\n- Use \"wait\" to pause before checking status again (e.g., wait 30 seconds for a build to complete).\r\n- Always clean up tasks with \"kill\" when they're no longer needed.\r\n- The task_id returned from \"start\" must be saved to use with \"status\" or \"kill\".`,\r\n parameters: {\r\n type: 'object',\r\n properties: {\r\n reason_text: {\r\n type: 'string',\r\n description: 'REQUIRED: A brief explanation of what you are doing with this background command.',\r\n },\r\n action: {\r\n type: 'string',\r\n enum: ['start', 'status', 'kill', 'wait'],\r\n description: 'The action to perform: \"start\" to run a new command, \"status\" to check a task, \"kill\" to terminate a task, \"wait\" to pause execution.',\r\n },\r\n command: {\r\n type: 'string',\r\n description: 'The shell command to execute. REQUIRED when action is \"start\".',\r\n },\r\n cwd: {\r\n type: 'string',\r\n description: 'The working directory for the command. REQUIRED when action is \"start\".',\r\n },\r\n task_id: {\r\n type: 'string',\r\n description: 'The task ID to check or kill. REQUIRED when action is \"status\" or \"kill\".',\r\n },\r\n output_lines: {\r\n type: 'integer',\r\n description: 'Number of lines of output to return (from the end). Default: 50. Only used with \"status\" action.',\r\n },\r\n wait_seconds: {\r\n type: 'integer',\r\n description: 'Number of seconds to wait. REQUIRED when action is \"wait\". Use this to pause before checking task status again.',\r\n },\r\n },\r\n required: ['reason_text', 'action'],\r\n },\r\n },\r\n async execute(args, context) {\r\n const { action, command, cwd, task_id, output_lines = 50, wait_seconds } = args;\r\n\r\n switch (action) {\r\n case 'start': {\r\n // Validate required parameters for start\r\n if (!command) {\r\n throw new Error('Missing required parameter \"command\" for action \"start\"');\r\n }\r\n if (!cwd) {\r\n throw new Error('Missing required parameter \"cwd\" for action \"start\"');\r\n }\r\n\r\n // Check for remote context\r\n const contextManager = context.contextManager as ContextManager;\r\n const activeContext = contextManager.getCurrentContext();\r\n\r\n // Get the full context stack to handle nested environments\r\n // If getContextStack is not available (older version), fall back to checking current context\r\n let stack: any[] = [];\r\n if (typeof contextManager.getContextStack === 'function') {\r\n stack = contextManager.getContextStack();\r\n } else {\r\n // Fallback for safety\r\n const current = contextManager.getCurrentContext();\r\n if (current) stack = [current];\r\n }\r\n\r\n let currentCommand = command;\r\n\r\n // Iterate from the target context (top of stack) down to the root\r\n for (let i = stack.length - 1; i >= 0; i--) {\r\n const ctx = stack[i];\r\n const isTarget = i === stack.length - 1;\r\n\r\n // Determine the working directory to use for this execution layer\r\n // If this is the target layer, use the user-provided cwd\r\n // If this is an intermediate layer, use its tracked working directory\r\n const executionCwd = isTarget ? cwd : ctx.metadata.workingDirectory;\r\n\r\n // 1. Check if this context has a direct protocol executor (e.g. active SSH client)\r\n if (ctx.type === 'ssh' && ctx.handler?.client) {\r\n const sshClient = ctx.handler.client;\r\n\r\n // Start task tracking\r\n const remoteTask = BackgroundTaskManager.startRemoteTask(\r\n command, // Original command for display\r\n cwd, // Original CWD for display\r\n ctx.type // Context type\r\n );\r\n\r\n // Execute via SSH PTY\r\n const sshPty = runSSHCommand(\r\n sshClient,\r\n currentCommand, // The accumulated (possibly wrapped) command\r\n executionCwd,\r\n remoteTask.onData,\r\n remoteTask.onExit\r\n );\r\n remoteTask.setRemotePty(sshPty);\r\n\r\n return formatStartResponse(remoteTask.id, command, cwd, ctx.type);\r\n }\r\n\r\n // 2. Check if this is the local root context\r\n if (ctx.type === 'local') {\r\n // Start task tracking\r\n const remoteTask = BackgroundTaskManager.startRemoteTask(\r\n command,\r\n cwd,\r\n 'local'\r\n );\r\n\r\n // Execute via Local PTY (which might be running a wrapped command like \"wsl ...\" or \"docker ...\")\r\n const localPty = runLocalPty(\r\n currentCommand,\r\n executionCwd, // Use the local CWD\r\n remoteTask.onData,\r\n remoteTask.onExit\r\n );\r\n remoteTask.setRemotePty(localPty);\r\n\r\n return formatStartResponse(remoteTask.id, command, cwd, 'local');\r\n }\r\n\r\n // 3. Otherwise, WRAP the command for the next iteration (parent)\r\n if (ctx.type === 'docker') {\r\n const containerId = ctx.metadata.containerId;\r\n if (!containerId) throw new Error('Docker context missing containerId');\r\n currentCommand = buildDockerCommand(containerId, executionCwd, currentCommand);\r\n } else if (ctx.type === 'wsl') {\r\n const distro = ctx.metadata.distroName || 'Ubuntu';\r\n currentCommand = buildWSLCommand(distro, executionCwd, currentCommand);\r\n } else if (ctx.type === 'ssh') {\r\n // Fallback for SSH without accessible client (e.g. wrapped command)\r\n currentCommand = buildSSHCommand(ctx.metadata, executionCwd, currentCommand);\r\n } else {\r\n // Unknown context type, try to pass through or error?\r\n // For now we error to be safe, or just wrap in sh -c?\r\n throw new Error(`Unsupported context type for wrapping: ${ctx.type}`);\r\n }\r\n }\r\n\r\n throw new Error('Failed to execute command: Reached end of context stack without execution.');\r\n }\r\n\r\n case 'status': {\r\n // Validate required parameters for status\r\n if (!task_id) {\r\n throw new Error('Missing required parameter \"task_id\" for action \"status\"');\r\n }\r\n\r\n const task = BackgroundTaskManager.getTask(task_id);\r\n if (!task) {\r\n // List all available tasks to help the AI\r\n const allTasks = BackgroundTaskManager.getAllTasks();\r\n if (allTasks.length === 0) {\r\n throw new Error(`Task \"${task_id}\" not found. There are no background tasks currently tracked.`);\r\n }\r\n const taskList = allTasks.map(t => `- ${t.id} (${t.isRunning ? 'running' : 'completed'}): ${t.command}`).join('\\n');\r\n throw new Error(`Task \"${task_id}\" not found.\\n\\nAvailable tasks:\\n${taskList}`);\r\n }\r\n\r\n // Get the output (last N lines)\r\n const fullOutput = task.output;\r\n const lines = fullOutput.split('\\n');\r\n const outputToShow = lines.slice(-output_lines).join('\\n');\r\n const truncated = lines.length > output_lines;\r\n\r\n const durationMs = Date.now() - task.startTime.getTime();\r\n const durationSec = Math.round(durationMs / 1000);\r\n\r\n let statusText = '';\r\n if (task.isRunning) {\r\n statusText = `**Status:** šŸ”„ Running (${durationSec}s)`;\r\n } else if (task.exitCode === 0) {\r\n statusText = `**Status:** āœ… Completed successfully (exit code: 0)`;\r\n } else if (task.exitCode !== undefined) {\r\n statusText = `**Status:** āŒ Failed (exit code: ${task.exitCode})`;\r\n } else if (task.error) {\r\n statusText = `**Status:** āŒ Error: ${task.error}`;\r\n } else {\r\n statusText = `**Status:** ā¹ļø Stopped`;\r\n }\r\n\r\n const remoteLabel = task.remoteContext ? ` [${task.remoteContext}]` : '';\r\n\r\n return `Background Task Status${remoteLabel}\r\n\r\n**Task ID:** ${task_id}\r\n**Command:** ${task.command}\r\n**Working Directory:** ${task.cwd}\r\n${statusText}\r\n\r\n**Output${truncated ? ` (last ${output_lines} lines)` : ''}:**\r\n\\`\\`\\`\r\n${outputToShow || '(no output yet)'}\r\n\\`\\`\\``;\r\n }\r\n\r\n case 'kill': {\r\n // Validate required parameters for kill\r\n if (!task_id) {\r\n throw new Error('Missing required parameter \"task_id\" for action \"kill\"');\r\n }\r\n\r\n const task = BackgroundTaskManager.getTask(task_id);\r\n if (!task) {\r\n // Try to find if it was recently killed or doesn't exist\r\n throw new Error(`Task \"${task_id}\" not found.`);\r\n }\r\n\r\n if (!task.isRunning) {\r\n return `Task \"${task_id}\" is not running (already completed or stopped). No action taken.`;\r\n }\r\n\r\n // For remote tasks, we might need to do special cleanup, \r\n // but BackgroundTaskManager.cancelTask() handles calling the pty.kill()\r\n const success = BackgroundTaskManager.cancelTask(task_id);\r\n if (success) {\r\n return `Background task \"${task_id}\" has been terminated.\r\n\r\n**Command:** ${task.command}\r\n**Status:** ā¹ļø Killed by request`;\r\n } else {\r\n throw new Error(`Failed to kill task \"${task_id}\". The task may have already completed.`);\r\n }\r\n }\r\n\r\n case 'wait': {\r\n // Validate required parameters for wait\r\n if (!wait_seconds) {\r\n throw new Error('Missing required parameter \"wait_seconds\" for action \"wait\"');\r\n }\r\n\r\n if (wait_seconds < 1 || wait_seconds > 300) {\r\n throw new Error('wait_seconds must be between 1 and 300 (5 minutes)');\r\n }\r\n\r\n // Create a promise that resolves after the specified duration\r\n await new Promise(resolve => setTimeout(resolve, wait_seconds * 1000));\r\n\r\n return `Waited for ${wait_seconds} second${wait_seconds !== 1 ? 's' : ''}.\r\n\r\nā±ļø The execution has been paused for the requested duration. You can now continue with the next action.`;\r\n }\r\n\r\n default:\r\n throw new Error(`Unknown action \"${action}\". Valid actions are: \"start\", \"status\", \"kill\", \"wait\".`);\r\n }\r\n },\r\n};\r\n\r\n/**\r\n * Format the successful start response\r\n */\r\nfunction formatStartResponse(taskId: string, command: string, cwd: string, contextType: string): string {\r\n return `Background task started successfully in ${contextType}.\r\n\r\n**Task ID:** ${taskId}\r\n**Command:** ${command}\r\n**Working Directory:** ${cwd}\r\n**Context:** ${contextType}\r\n\r\nThe command is now running in the background. Use action=\"status\" with task_id=\"${taskId}\" to check progress, or action=\"kill\" to terminate it.`;\r\n}\r\n\r\n/**\r\n * Helper to escape a command for use in \"sh -c\" or similar\r\n */\r\nfunction escapeCmd(cmd: string): string {\r\n // Escape backslashes first, then quotes\r\n return cmd.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\r\n}\r\n\r\n/**\r\n * Build a Docker execution command\r\n */\r\nfunction buildDockerCommand(containerId: string, cwd: string, command: string): string {\r\n // docker exec -w <cwd> <id> sh -c \"<cmd>\"\r\n return `docker exec -w \"${cwd}\" ${containerId} sh -c \"${escapeCmd(command)}\"`;\r\n}\r\n\r\n/**\r\n * Build a WSL execution command\r\n */\r\nfunction buildWSLCommand(distro: string, cwd: string, command: string): string {\r\n // wsl -d <distro> -- bash -c \"cd <cwd> && <cmd>\"\r\n return `wsl -d ${distro} -- bash -c \"cd \\\\\"${cwd}\\\\\" && ${escapeCmd(command)}\"`;\r\n}\r\n\r\n/**\r\n * Build an SSH execution command (for wrapping)\r\n */\r\nfunction buildSSHCommand(metadata: any, cwd: string, command: string): string {\r\n const portFlag = metadata.port ? `-p ${metadata.port}` : '';\r\n const userHost = `${metadata.username}@${metadata.hostname}`;\r\n // ssh -p <port> user@host \"cd <cwd> && <cmd>\"\r\n return `ssh ${portFlag} ${userHost} \"cd \\\\\"${cwd}\\\\\" && ${escapeCmd(command)}\"`;\r\n}\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/tools/background-command.ts"],"sourcesContent":["/**\r\n * Background Command Tool\r\n * \r\n * Allows the AI to execute commands in the background (non-blocking),\r\n * check their status, and kill/terminate them.\r\n * \r\n * Uses the existing BackgroundTaskManager singleton.\r\n */\r\n\r\nimport { Tool } from './types.js';\r\nimport { BackgroundTaskManager } from '../services/background-task-manager.js';\r\nimport { runSSHCommand, runLocalPty } from '../utils/editor-utils.js';\r\nimport { ContextManager } from '../context/context-manager.js';\r\nimport { processTerminalOutput } from '../utils/terminal-output.js';\r\nimport stripAnsi from 'strip-ansi';\r\nimport * as path from 'path';\r\n\r\nfunction resolveBackgroundCommandCwd(currentContext: any, contextCwd: string, commandCwd: string): string {\r\n if (currentContext.type === 'local') {\r\n return path.resolve(contextCwd, commandCwd);\r\n }\r\n\r\n const remoteBase = currentContext.metadata?.workingDirectory || contextCwd || '~';\r\n return path.posix.isAbsolute(commandCwd)\r\n ? commandCwd\r\n : path.posix.join(remoteBase, commandCwd);\r\n}\r\n\r\n\r\nexport const backgroundCommandTool: Tool = {\r\n schema: {\r\n name: 'background_command',\r\n description: `Execute shell commands in the background, check their status, or kill them. This tool is NON-BLOCKING - when you start a command, the tool returns immediately with a task ID while the command continues running in the background.\r\n\r\nUSE CASES:\r\n- Running development servers (npm run dev, python -m http.server, etc.)\r\n- Running long-running processes (builds, tests, watchers)\r\n- Running multiple commands concurrently\r\n\r\nACTIONS:\r\n1. \"start\" - Start a new background command. Returns a task_id immediately.\r\n2. \"status\" - Check the status and output of a background task. Use this to monitor progress.\r\n3. \"kill\" - Terminate a running background task.\r\n4. \"wait\" - Pause execution for a specified duration. Use this instead of repeatedly checking status.\r\n\r\nIMPORTANT:\r\n- After starting a task, you can continue with other work while it runs.\r\n- Use \"status\" periodically to check on long-running tasks.\r\n- Use \"wait\" to pause before checking status again (e.g., wait 30 seconds for a build to complete).\r\n- Always clean up tasks with \"kill\" when they're no longer needed.\r\n- The task_id returned from \"start\" must be saved to use with \"status\" or \"kill\".`,\r\n parameters: {\r\n type: 'object',\r\n properties: {\r\n reason_text: {\r\n type: 'string',\r\n description: 'REQUIRED: A brief explanation of what you are doing with this background command.',\r\n },\r\n action: {\r\n type: 'string',\r\n enum: ['start', 'status', 'kill', 'wait'],\r\n description: 'The action to perform: \"start\" to run a new command, \"status\" to check a task, \"kill\" to terminate a task, \"wait\" to pause execution.',\r\n },\r\n command: {\r\n type: 'string',\r\n description: 'The shell command to execute. REQUIRED when action is \"start\".',\r\n },\r\n cwd: {\r\n type: 'string',\r\n description: 'The working directory for the command. REQUIRED when action is \"start\".',\r\n },\r\n task_id: {\r\n type: 'string',\r\n description: 'The task ID to check or kill. REQUIRED when action is \"status\" or \"kill\".',\r\n },\r\n output_lines: {\r\n type: 'integer',\r\n description: 'Number of lines of output to return (from the end). Default: 50. Only used with \"status\" action.',\r\n },\r\n wait_seconds: {\r\n type: 'integer',\r\n description: 'Number of seconds to wait. REQUIRED when action is \"wait\". Use this to pause before checking task status again.',\r\n },\r\n },\r\n required: ['reason_text', 'action'],\r\n },\r\n },\r\n async execute(args, context) {\r\n const { action, command, cwd, task_id, output_lines = 50, wait_seconds } = args;\r\n\r\n switch (action) {\r\n case 'start': {\r\n // Validate required parameters for start\r\n if (!command) {\r\n throw new Error('Missing required parameter \"command\" for action \"start\"');\r\n }\r\n if (!cwd) {\r\n throw new Error('Missing required parameter \"cwd\" for action \"start\"');\r\n }\r\n\r\n // Check for remote context\r\n const contextManager = context.contextManager as ContextManager;\r\n const activeContext = contextManager.getCurrentContext();\r\n\r\n // Get the full context stack to handle nested environments\r\n // If getContextStack is not available (older version), fall back to checking current context\r\n let stack: any[] = [];\r\n if (typeof contextManager.getContextStack === 'function') {\r\n stack = contextManager.getContextStack();\r\n } else {\r\n // Fallback for safety\r\n const current = contextManager.getCurrentContext();\r\n if (current) stack = [current];\r\n }\r\n\r\n let currentCommand = command;\r\n\r\n // Iterate from the target context (top of stack) down to the root\r\n for (let i = stack.length - 1; i >= 0; i--) {\r\n const ctx = stack[i];\r\n const isTarget = i === stack.length - 1;\r\n\r\n // Determine the working directory to use for this execution layer\r\n // If this is the target layer, use the user-provided cwd\r\n // If this is an intermediate layer, use its tracked working directory\r\n const executionCwd = isTarget ? cwd : ctx.metadata.workingDirectory;\r\n\r\n // 1. Check if this context has a direct protocol executor (e.g. active SSH client)\r\n if (ctx.type === 'ssh' && ctx.handler?.client) {\r\n const sshClient = ctx.handler.client;\r\n\r\n // Start task tracking\r\n const remoteTask = BackgroundTaskManager.startRemoteTask(\r\n command, // Original command for display\r\n cwd, // Original CWD for display\r\n ctx.type // Context type\r\n );\r\n\r\n // Execute via SSH PTY\r\n const sshPty = runSSHCommand(\r\n sshClient,\r\n currentCommand, // The accumulated (possibly wrapped) command\r\n executionCwd,\r\n remoteTask.onData,\r\n remoteTask.onExit\r\n );\r\n remoteTask.setRemotePty(sshPty);\r\n\r\n return formatStartResponse(remoteTask.id, command, cwd, ctx.type);\r\n }\r\n\r\n // 2. Check if this is the local root context\r\n if (ctx.type === 'local') {\r\n // Start task tracking\r\n const remoteTask = BackgroundTaskManager.startRemoteTask(\r\n command,\r\n cwd,\r\n 'local'\r\n );\r\n\r\n // Execute via Local PTY (which might be running a wrapped command like \"wsl ...\" or \"docker ...\")\r\n const localPty = runLocalPty(\r\n currentCommand,\r\n executionCwd, // Use the local CWD\r\n remoteTask.onData,\r\n remoteTask.onExit\r\n );\r\n remoteTask.setRemotePty(localPty);\r\n\r\n return formatStartResponse(remoteTask.id, command, cwd, 'local');\r\n }\r\n\r\n // 3. Otherwise, WRAP the command for the next iteration (parent)\r\n if (ctx.type === 'docker') {\r\n const containerId = ctx.metadata.containerId;\r\n if (!containerId) throw new Error('Docker context missing containerId');\r\n currentCommand = buildDockerCommand(containerId, executionCwd, currentCommand);\r\n } else if (ctx.type === 'wsl') {\r\n const distro = ctx.metadata.distroName || 'Ubuntu';\r\n currentCommand = buildWSLCommand(distro, executionCwd, currentCommand);\r\n } else if (ctx.type === 'ssh') {\r\n // Fallback for SSH without accessible client (e.g. wrapped command)\r\n currentCommand = buildSSHCommand(ctx.metadata, executionCwd, currentCommand);\r\n } else {\r\n // Unknown context type, try to pass through or error?\r\n // For now we error to be safe, or just wrap in sh -c?\r\n throw new Error(`Unsupported context type for wrapping: ${ctx.type}`);\r\n }\r\n }\r\n\r\n throw new Error('Failed to execute command: Reached end of context stack without execution.');\r\n }\r\n\r\n case 'status': {\r\n // Validate required parameters for status\r\n if (!task_id) {\r\n throw new Error('Missing required parameter \"task_id\" for action \"status\"');\r\n }\r\n\r\n const task = BackgroundTaskManager.getTask(task_id);\r\n if (!task) {\r\n // List all available tasks to help the AI\r\n const allTasks = BackgroundTaskManager.getAllTasks();\r\n if (allTasks.length === 0) {\r\n throw new Error(`Task \"${task_id}\" not found. There are no background tasks currently tracked.`);\r\n }\r\n const taskList = allTasks.map(t => `- ${t.id} (${t.isRunning ? 'running' : 'completed'}): ${t.command}`).join('\\n');\r\n throw new Error(`Task \"${task_id}\" not found.\\n\\nAvailable tasks:\\n${taskList}`);\r\n }\r\n\r\n // Get the output (last N lines) — sanitize to strip ANSI escape sequences\r\n // and control characters that would corrupt the terminal if rendered\r\n const rawOutput = task.output;\r\n const cleanOutput = stripAnsi(processTerminalOutput(rawOutput))\r\n .replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/g, '');\r\n const lines = cleanOutput.split('\\n');\r\n const outputToShow = lines.slice(-output_lines).join('\\n');\r\n const truncated = lines.length > output_lines;\r\n\r\n const durationMs = Date.now() - task.startTime.getTime();\r\n const durationSec = Math.round(durationMs / 1000);\r\n\r\n let statusText = '';\r\n if (task.isRunning) {\r\n statusText = `**Status:** šŸ”„ Running (${durationSec}s)`;\r\n } else if (task.exitCode === 0) {\r\n statusText = `**Status:** āœ… Completed successfully (exit code: 0)`;\r\n } else if (task.exitCode !== undefined) {\r\n statusText = `**Status:** āŒ Failed (exit code: ${task.exitCode})`;\r\n } else if (task.error) {\r\n statusText = `**Status:** āŒ Error: ${task.error}`;\r\n } else {\r\n statusText = `**Status:** ā¹ļø Stopped`;\r\n }\r\n\r\n const remoteLabel = task.remoteContext ? ` [${task.remoteContext}]` : '';\r\n\r\n return `Background Task Status${remoteLabel}\r\n\r\n**Task ID:** ${task_id}\r\n**Command:** ${task.command}\r\n**Working Directory:** ${task.cwd}\r\n${statusText}\r\n\r\n**Output${truncated ? ` (last ${output_lines} lines)` : ''}:**\r\n\\`\\`\\`\r\n${outputToShow || '(no output yet)'}\r\n\\`\\`\\``;\r\n }\r\n\r\n case 'kill': {\r\n // Validate required parameters for kill\r\n if (!task_id) {\r\n throw new Error('Missing required parameter \"task_id\" for action \"kill\"');\r\n }\r\n\r\n const task = BackgroundTaskManager.getTask(task_id);\r\n if (!task) {\r\n // Try to find if it was recently killed or doesn't exist\r\n throw new Error(`Task \"${task_id}\" not found.`);\r\n }\r\n\r\n if (!task.isRunning) {\r\n return `Task \"${task_id}\" is not running (already completed or stopped). No action taken.`;\r\n }\r\n\r\n // For remote tasks, we might need to do special cleanup, \r\n // but BackgroundTaskManager.cancelTask() handles calling the pty.kill()\r\n const success = BackgroundTaskManager.cancelTask(task_id);\r\n if (success) {\r\n return `Background task \"${task_id}\" has been terminated.\r\n\r\n**Command:** ${task.command}\r\n**Status:** ā¹ļø Killed by request`;\r\n } else {\r\n throw new Error(`Failed to kill task \"${task_id}\". The task may have already completed.`);\r\n }\r\n }\r\n\r\n case 'wait': {\r\n // Validate required parameters for wait\r\n if (!wait_seconds) {\r\n throw new Error('Missing required parameter \"wait_seconds\" for action \"wait\"');\r\n }\r\n\r\n if (wait_seconds < 1 || wait_seconds > 300) {\r\n throw new Error('wait_seconds must be between 1 and 300 (5 minutes)');\r\n }\r\n\r\n // Create a promise that resolves after the specified duration\r\n await new Promise(resolve => setTimeout(resolve, wait_seconds * 1000));\r\n\r\n return `Waited for ${wait_seconds} second${wait_seconds !== 1 ? 's' : ''}.\r\n\r\nā±ļø The execution has been paused for the requested duration. You can now continue with the next action.`;\r\n }\r\n\r\n default:\r\n throw new Error(`Unknown action \"${action}\". Valid actions are: \"start\", \"status\", \"kill\", \"wait\".`);\r\n }\r\n },\r\n};\r\n\r\n/**\r\n * Format the successful start response\r\n */\r\nfunction formatStartResponse(taskId: string, command: string, cwd: string, contextType: string): string {\r\n return `Background task started successfully in ${contextType}.\r\n\r\n**Task ID:** ${taskId}\r\n**Command:** ${command}\r\n**Working Directory:** ${cwd}\r\n**Context:** ${contextType}\r\n\r\nThe command is now running in the background. Use action=\"status\" with task_id=\"${taskId}\" to check progress, or action=\"kill\" to terminate it.`;\r\n}\r\n\r\n/**\r\n * Helper to escape a command for use in \"sh -c\" or similar\r\n */\r\nfunction escapeCmd(cmd: string): string {\r\n // Escape backslashes first, then quotes\r\n return cmd.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\r\n}\r\n\r\n/**\r\n * Build a Docker execution command\r\n */\r\nfunction buildDockerCommand(containerId: string, cwd: string, command: string): string {\r\n // docker exec -w <cwd> <id> sh -c \"<cmd>\"\r\n return `docker exec -w \"${cwd}\" ${containerId} sh -c \"${escapeCmd(command)}\"`;\r\n}\r\n\r\n/**\r\n * Build a WSL execution command\r\n */\r\nfunction buildWSLCommand(distro: string, cwd: string, command: string): string {\r\n // wsl -d <distro> -- bash -c \"cd <cwd> && <cmd>\"\r\n return `wsl -d ${distro} -- bash -c \"cd \\\\\"${cwd}\\\\\" && ${escapeCmd(command)}\"`;\r\n}\r\n\r\n/**\r\n * Build an SSH execution command (for wrapping)\r\n */\r\nfunction buildSSHCommand(metadata: any, cwd: string, command: string): string {\r\n const portFlag = metadata.port ? `-p ${metadata.port}` : '';\r\n const userHost = `${metadata.username}@${metadata.hostname}`;\r\n // ssh -p <port> user@host \"cd <cwd> && <cmd>\"\r\n return `ssh ${portFlag} ${userHost} \"cd \\\\\"${cwd}\\\\\" && ${escapeCmd(command)}\"`;\r\n}\r\n"],"mappings":"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","names":[]}