centaurus-cli 3.1.3 → 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 (138) hide show
  1. package/dist/cli-adapter.js +685 -153
  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 +4 -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/api-client.js.map +1 -1
  16. package/dist/services/background-task-manager.js +59 -0
  17. package/dist/services/background-task-manager.js.map +1 -1
  18. package/dist/services/local-chat-storage.js +2 -0
  19. package/dist/services/local-chat-storage.js.map +1 -1
  20. package/dist/services/skill-storage.js +141 -0
  21. package/dist/services/skill-storage.js.map +1 -0
  22. package/dist/services/sub-agent-manager.js +49 -8
  23. package/dist/services/sub-agent-manager.js.map +1 -1
  24. package/dist/services/warpify-detector.js +17 -5
  25. package/dist/services/warpify-detector.js.map +1 -1
  26. package/dist/tools/background-command.js +5 -2
  27. package/dist/tools/background-command.js.map +1 -1
  28. package/dist/tools/command.js +367 -109
  29. package/dist/tools/command.js.map +1 -1
  30. package/dist/tools/file-ops.js +23 -6
  31. package/dist/tools/file-ops.js.map +1 -1
  32. package/dist/tools/plan-mode.js +184 -336
  33. package/dist/tools/plan-mode.js.map +1 -1
  34. package/dist/tools/sub-agent.js +24 -5
  35. package/dist/tools/sub-agent.js.map +1 -1
  36. package/dist/tools/todo-list.js +157 -0
  37. package/dist/tools/todo-list.js.map +1 -0
  38. package/dist/types/skill.js +30 -0
  39. package/dist/types/skill.js.map +1 -0
  40. package/dist/ui/components/App.js +956 -162
  41. package/dist/ui/components/App.js.map +1 -1
  42. package/dist/ui/components/AuthScreen.js +3 -1
  43. package/dist/ui/components/AuthScreen.js.map +1 -1
  44. package/dist/ui/components/AuthWelcomeScreen.js +3 -1
  45. package/dist/ui/components/AuthWelcomeScreen.js.map +1 -1
  46. package/dist/ui/components/CodeBlock.js +3 -1
  47. package/dist/ui/components/CodeBlock.js.map +1 -1
  48. package/dist/ui/components/CompactShellPreview.js +44 -0
  49. package/dist/ui/components/CompactShellPreview.js.map +1 -0
  50. package/dist/ui/components/ConfigViewer.js +3 -1
  51. package/dist/ui/components/ConfigViewer.js.map +1 -1
  52. package/dist/ui/components/ConfirmPrompt.js +3 -1
  53. package/dist/ui/components/ConfirmPrompt.js.map +1 -1
  54. package/dist/ui/components/ConnectionStatusMessage.js +3 -1
  55. package/dist/ui/components/ConnectionStatusMessage.js.map +1 -1
  56. package/dist/ui/components/DetailedPlanReviewScreen.js +84 -74
  57. package/dist/ui/components/DetailedPlanReviewScreen.js.map +1 -1
  58. package/dist/ui/components/DiffViewer.js +6 -3
  59. package/dist/ui/components/DiffViewer.js.map +1 -1
  60. package/dist/ui/components/FileCreationPreview.js.map +1 -1
  61. package/dist/ui/components/FileTagAutocomplete.js +4 -2
  62. package/dist/ui/components/FileTagAutocomplete.js.map +1 -1
  63. package/dist/ui/components/InputBox.js +243 -40
  64. package/dist/ui/components/InputBox.js.map +1 -1
  65. package/dist/ui/components/InteractiveShell.js +5 -3
  66. package/dist/ui/components/InteractiveShell.js.map +1 -1
  67. package/dist/ui/components/KeyboardHelp.js +4 -1
  68. package/dist/ui/components/KeyboardHelp.js.map +1 -1
  69. package/dist/ui/components/LoadingIndicator.js +3 -1
  70. package/dist/ui/components/LoadingIndicator.js.map +1 -1
  71. package/dist/ui/components/MCPAddScreen.js +63 -13
  72. package/dist/ui/components/MCPAddScreen.js.map +1 -1
  73. package/dist/ui/components/MarkdownRenderer.js +3 -1
  74. package/dist/ui/components/MarkdownRenderer.js.map +1 -1
  75. package/dist/ui/components/MessageDisplay.js +9 -7
  76. package/dist/ui/components/MessageDisplay.js.map +1 -1
  77. package/dist/ui/components/ModelPicker.js +170 -0
  78. package/dist/ui/components/ModelPicker.js.map +1 -0
  79. package/dist/ui/components/MonitorModeAIPanel.js +3 -1
  80. package/dist/ui/components/MonitorModeAIPanel.js.map +1 -1
  81. package/dist/ui/components/PlanAcceptedMessage.js +12 -6
  82. package/dist/ui/components/PlanAcceptedMessage.js.map +1 -1
  83. package/dist/ui/components/PlanQuestionMessage.js +37 -0
  84. package/dist/ui/components/PlanQuestionMessage.js.map +1 -0
  85. package/dist/ui/components/PlanQuestionScreen.js +138 -0
  86. package/dist/ui/components/PlanQuestionScreen.js.map +1 -0
  87. package/dist/ui/components/PlanReviewScreen.js +7 -9
  88. package/dist/ui/components/PlanReviewScreen.js.map +1 -1
  89. package/dist/ui/components/RulesEditorScreen.js +65 -28
  90. package/dist/ui/components/RulesEditorScreen.js.map +1 -1
  91. package/dist/ui/components/SelectPrompt.js +3 -1
  92. package/dist/ui/components/SelectPrompt.js.map +1 -1
  93. package/dist/ui/components/SkillCreatorScreen.js +217 -0
  94. package/dist/ui/components/SkillCreatorScreen.js.map +1 -0
  95. package/dist/ui/components/SlashCommandAutocomplete.js +4 -2
  96. package/dist/ui/components/SlashCommandAutocomplete.js.map +1 -1
  97. package/dist/ui/components/StatusBar.js +4 -2
  98. package/dist/ui/components/StatusBar.js.map +1 -1
  99. package/dist/ui/components/StreamingMessageDisplay.js +5 -3
  100. package/dist/ui/components/StreamingMessageDisplay.js.map +1 -1
  101. package/dist/ui/components/SubAgentListScreen.js +65 -0
  102. package/dist/ui/components/SubAgentListScreen.js.map +1 -0
  103. package/dist/ui/components/SubAgentViewScreen.js +123 -0
  104. package/dist/ui/components/SubAgentViewScreen.js.map +1 -0
  105. package/dist/ui/components/TaskCompletedMessage.js +40 -8
  106. package/dist/ui/components/TaskCompletedMessage.js.map +1 -1
  107. package/dist/ui/components/TaskProgressIndicator.js +6 -4
  108. package/dist/ui/components/TaskProgressIndicator.js.map +1 -1
  109. package/dist/ui/components/TextEditor.js +297 -0
  110. package/dist/ui/components/TextEditor.js.map +1 -0
  111. package/dist/ui/components/TodoListMessage.js +59 -0
  112. package/dist/ui/components/TodoListMessage.js.map +1 -0
  113. package/dist/ui/components/ToolExecutionMessage.js +134 -84
  114. package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
  115. package/dist/ui/components/ToolExecutionStatus.js +3 -1
  116. package/dist/ui/components/ToolExecutionStatus.js.map +1 -1
  117. package/dist/ui/components/WelcomeBanner.js +33 -33
  118. package/dist/ui/components/WelcomeBanner.js.map +1 -1
  119. package/dist/ui/components/WorkflowCreatorScreen.js +5 -3
  120. package/dist/ui/components/WorkflowCreatorScreen.js.map +1 -1
  121. package/dist/ui/theme.js +97 -0
  122. package/dist/ui/theme.js.map +1 -0
  123. package/dist/ui/utils/chat-history-limit.js +247 -0
  124. package/dist/ui/utils/chat-history-limit.js.map +1 -0
  125. package/dist/utils/chat-formatter.js +22 -9
  126. package/dist/utils/chat-formatter.js.map +1 -1
  127. package/dist/utils/input-classifier.js +11 -1
  128. package/dist/utils/input-classifier.js.map +1 -1
  129. package/dist/utils/output-truncation.js +175 -0
  130. package/dist/utils/output-truncation.js.map +1 -0
  131. package/dist/utils/rule-reference-resolver.js +3 -3
  132. package/dist/utils/rule-reference-resolver.js.map +1 -1
  133. package/dist/utils/tunnel-commands-manager.js +134 -0
  134. package/dist/utils/tunnel-commands-manager.js.map +1 -0
  135. package/package.json +91 -90
  136. package/postinstall.js +4 -11
  137. package/dist/ui/components/MultiLineInput.js +0 -255
  138. package/dist/ui/components/MultiLineInput.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/local-chat-storage.ts"],"sourcesContent":["/**\r\n * Local Chat Storage Service\r\n * \r\n * Provides persistent local storage for CLI conversations.\r\n * Stores chats in ~/.centaurus/chats/ directory as JSON files.\r\n */\r\n\r\nimport * as fs from 'fs';\r\nimport * as path from 'path';\r\nimport * as os from 'os';\r\nimport { logError } from '../utils/logger.js';\r\n\r\n/**\r\n * Message interface matching the AIMessage type from ai-service-client\r\n */\r\nexport interface StoredMessage {\r\n role: 'user' | 'assistant' | 'system' | 'tool';\r\n content: string;\r\n tool_calls?: Array<{\r\n id: string;\r\n name: string;\r\n arguments: Record<string, any>;\r\n }>;\r\n tool_call_id?: string;\r\n}\r\n\r\n/**\r\n * UI Message for restoring full chat history display\r\n * This is a serializable version of the Message type from types/index.ts\r\n */\r\nexport interface StoredUIMessage {\r\n id: string;\r\n role: 'user' | 'assistant' | 'system' | 'tool';\r\n content: string;\r\n timestamp?: string; // ISO string (Date is not JSON serializable)\r\n toolExecution?: {\r\n toolName: string;\r\n status: 'pending' | 'executing' | 'completed' | 'error';\r\n result?: string;\r\n error?: string;\r\n arguments?: Record<string, any>;\r\n };\r\n shouldStream?: boolean;\r\n isCommandMode?: boolean;\r\n tool_call_id?: string;\r\n tool_calls?: Array<{ id: string; name: string; arguments: Record<string, any> }>;\r\n thinkingDuration?: number;\r\n taskCompletion?: {\r\n taskNumber: number;\r\n totalTasks: number;\r\n taskDescription: string;\r\n completionNote?: string;\r\n };\r\n planAccepted?: {\r\n planTitle?: string;\r\n totalTasks?: number;\r\n };\r\n connectionStatus?: { // For SSH/WSL/Docker connection status\r\n type: 'ssh' | 'wsl' | 'docker';\r\n status: 'connecting' | 'connected' | 'error' | 'disconnected';\r\n connectionString?: string;\r\n error?: string;\r\n };\r\n}\r\n\r\n/**\r\n * Remote environment context for SSH/WSL/Docker sessions\r\n * Used to restore remote session state when resuming a chat\r\n */\r\nexport interface StoredRemoteContext {\r\n type: 'ssh' | 'wsl' | 'docker';\r\n connectionCommand: string; // Original command to reconnect (e.g., \"ssh user@host\" or \"wsl\")\r\n remoteCwd: string; // CWD in the remote environment\r\n localCwdBeforeRemote: string; // Local CWD before entering remote session\r\n metadata?: {\r\n hostname?: string; // For SSH\r\n username?: string; // For SSH \r\n distroName?: string; // For WSL\r\n containerId?: string; // For Docker\r\n port?: number; // For SSH\r\n };\r\n}\r\n\r\n/**\r\n * Serialized checkpoint metadata stored with chat history.\r\n * This allows checkpoint state to be restored even if the checkpoint index file is missing.\r\n */\r\nexport interface StoredCheckpointMeta {\r\n id: string;\r\n prompt: string;\r\n createdAt: string;\r\n createdAtMs: number;\r\n cwd: string;\r\n contextType: 'local' | 'ssh' | 'wsl' | 'docker';\r\n remoteSessionInfo?: {\r\n hostname?: string;\r\n username?: string;\r\n sessionId: string;\r\n connectionString?: string;\r\n };\r\n conversationIndex: number;\r\n uiMessageIndex?: number;\r\n uiMessageId?: string;\r\n snapshotDir: string;\r\n manifestPath: string;\r\n changes?: {\r\n added: string[];\r\n modified: string[];\r\n deleted: string[];\r\n };\r\n commands: string[];\r\n toolCalls: Array<{\r\n id?: string;\r\n name: string;\r\n arguments?: Record<string, any>;\r\n timestamp: string;\r\n }>;\r\n status: 'active' | 'finalized' | 'discarded';\r\n}\r\n\r\n/**\r\n * Local chat metadata (without messages, for listing)\r\n */\r\nexport interface LocalChatMeta {\r\n id: string;\r\n title: string;\r\n createdAt: string;\r\n updatedAt: string;\r\n messageCount: number;\r\n environment?: string; // 'local' or 'ssh:user@host' or 'wsl:distro' or 'docker:container'\r\n}\r\n\r\n/**\r\n * Full local chat with messages\r\n */\r\nexport interface LocalChat extends LocalChatMeta {\r\n messages: StoredMessage[];\r\n uiMessages?: StoredUIMessage[]; // Full UI history for restoration\r\n cwd?: string; // Working directory at time of save (for restoration)\r\n remoteContext?: StoredRemoteContext; // Remote context if saved while in SSH/WSL/Docker (backward compat)\r\n remoteContextStack?: StoredRemoteContext[]; // Stack of nested contexts (for SSH>SSH, SSH>Docker, etc.)\r\n checkpointIndex?: StoredCheckpointMeta[]; // Serialized checkpoint metadata for resume/revert restoration\r\n backendConversationId?: string; // Backend conversation UUID (for file storage/deletion)\r\n}\r\n\r\n/**\r\n * LocalChatStorage class for managing local conversation persistence\r\n */\r\nexport class LocalChatStorage {\r\n private chatsDir: string;\r\n private indexPath: string;\r\n private chatIndex: Map<string, LocalChatMeta> = new Map();\r\n private indexLoaded: boolean = false;\r\n\r\n constructor() {\r\n const homeDir = os.homedir();\r\n this.chatsDir = path.join(homeDir, '.centaurus', 'chats');\r\n this.indexPath = path.join(this.chatsDir, 'index.json');\r\n\r\n // Ensure chats directory exists\r\n if (!fs.existsSync(this.chatsDir)) {\r\n fs.mkdirSync(this.chatsDir, { recursive: true });\r\n }\r\n\r\n // Load or build the index\r\n this.loadIndex();\r\n }\r\n\r\n /**\r\n * Load the metadata index from disk, or rebuild it from chat files if missing\r\n */\r\n private loadIndex(): void {\r\n if (this.indexLoaded) return;\r\n\r\n try {\r\n if (fs.existsSync(this.indexPath)) {\r\n const data = fs.readFileSync(this.indexPath, 'utf-8');\r\n const indexArray: LocalChatMeta[] = JSON.parse(data);\r\n this.chatIndex.clear();\r\n for (const meta of indexArray) {\r\n this.chatIndex.set(meta.id, meta);\r\n }\r\n this.indexLoaded = true;\r\n return;\r\n }\r\n } catch (error) {\r\n // Index file corrupted, rebuild it\r\n }\r\n\r\n // Rebuild index from chat files (one-time migration)\r\n this.rebuildIndex();\r\n }\r\n\r\n /**\r\n * Rebuild the index by reading metadata from all chat files\r\n * This is a one-time migration that reads full files, but only happens once\r\n */\r\n private rebuildIndex(): void {\r\n this.chatIndex.clear();\r\n\r\n if (!fs.existsSync(this.chatsDir)) {\r\n this.indexLoaded = true;\r\n return;\r\n }\r\n\r\n const files = fs.readdirSync(this.chatsDir)\r\n .filter(f => f.endsWith('.json') && f !== 'index.json');\r\n\r\n for (const file of files) {\r\n try {\r\n const filePath = path.join(this.chatsDir, file);\r\n const data = fs.readFileSync(filePath, 'utf-8');\r\n const chat = JSON.parse(data) as LocalChat;\r\n\r\n // Determine environment string from remoteContext\r\n let environment = 'local';\r\n if (chat.remoteContext) {\r\n const rc = chat.remoteContext;\r\n if (rc.type === 'ssh' && rc.metadata?.hostname) {\r\n environment = `ssh:${rc.metadata.username || 'user'}@${rc.metadata.hostname}`;\r\n } else if (rc.type === 'wsl') {\r\n environment = `wsl:${rc.metadata?.distroName || 'Ubuntu'}`;\r\n } else if (rc.type === 'docker' && rc.metadata?.containerId) {\r\n environment = `docker:${rc.metadata.containerId.substring(0, 12)}`;\r\n } else {\r\n environment = rc.type;\r\n }\r\n }\r\n\r\n // Store metadata only\r\n this.chatIndex.set(chat.id, {\r\n id: chat.id,\r\n title: chat.title,\r\n createdAt: chat.createdAt,\r\n updatedAt: chat.updatedAt,\r\n messageCount: chat.messageCount,\r\n environment,\r\n });\r\n } catch (error) {\r\n // Skip invalid files\r\n continue;\r\n }\r\n }\r\n\r\n this.indexLoaded = true;\r\n this.saveIndex();\r\n }\r\n\r\n /**\r\n * Save the metadata index to disk\r\n */\r\n private saveIndex(): void {\r\n try {\r\n const indexArray = Array.from(this.chatIndex.values());\r\n fs.writeFileSync(this.indexPath, JSON.stringify(indexArray, null, 2), 'utf-8');\r\n } catch (error) {\r\n logError('Failed to save chat index', error as Error);\r\n }\r\n }\r\n\r\n /**\r\n * Update the index entry for a chat\r\n */\r\n private updateIndexEntry(chat: LocalChat): void {\r\n let environment = 'local';\r\n if (chat.remoteContext) {\r\n const rc = chat.remoteContext;\r\n if (rc.type === 'ssh' && rc.metadata?.hostname) {\r\n environment = `ssh:${rc.metadata.username || 'user'}@${rc.metadata.hostname}`;\r\n } else if (rc.type === 'wsl') {\r\n environment = `wsl:${rc.metadata?.distroName || 'Ubuntu'}`;\r\n } else if (rc.type === 'docker' && rc.metadata?.containerId) {\r\n environment = `docker:${rc.metadata.containerId.substring(0, 12)}`;\r\n } else {\r\n environment = rc.type;\r\n }\r\n }\r\n\r\n this.chatIndex.set(chat.id, {\r\n id: chat.id,\r\n title: chat.title,\r\n createdAt: chat.createdAt,\r\n updatedAt: chat.updatedAt,\r\n messageCount: chat.messageCount,\r\n environment,\r\n });\r\n this.saveIndex();\r\n }\r\n\r\n /**\r\n * Generate a unique chat ID based on timestamp\r\n */\r\n generateChatId(): string {\r\n const now = new Date();\r\n const timestamp = now.toISOString().replace(/[:.]/g, '-').slice(0, 19);\r\n const random = Math.random().toString(36).substring(2, 6);\r\n return `chat-${timestamp}-${random}`;\r\n }\r\n\r\n /**\r\n * Generate a title from the first user message\r\n */\r\n generateTitle(messages: StoredMessage[]): string {\r\n const firstUserMessage = messages.find(m => m.role === 'user');\r\n if (!firstUserMessage || !firstUserMessage.content) {\r\n return 'New Chat';\r\n }\r\n\r\n // Take first 60 chars, trim to last word boundary\r\n let title = firstUserMessage.content.substring(0, 60);\r\n if (firstUserMessage.content.length > 60) {\r\n const lastSpace = title.lastIndexOf(' ');\r\n if (lastSpace > 30) {\r\n title = title.substring(0, lastSpace);\r\n }\r\n title += '...';\r\n }\r\n\r\n // Clean up whitespace and newlines\r\n title = title.replace(/\\s+/g, ' ').trim();\r\n\r\n return title || 'New Chat';\r\n }\r\n\r\n /**\r\n * Get the file path for a chat\r\n */\r\n private getChatPath(chatId: string): string {\r\n return path.join(this.chatsDir, `${chatId}.json`);\r\n }\r\n\r\n /**\r\n * Save a chat to disk\r\n * @param remoteContext - undefined = preserve existing, null = clear, StoredRemoteContext = set new value\r\n * @param remoteContextStack - Stack of nested contexts for SSH>SSH, SSH>Docker etc.\r\n * @param checkpointIndex - undefined = preserve existing, null = clear, array = set latest checkpoint metadata\r\n */\r\n saveChat(\r\n chatId: string,\r\n messages: StoredMessage[],\r\n uiMessages?: StoredUIMessage[],\r\n cwd?: string,\r\n remoteContext?: StoredRemoteContext | null,\r\n remoteContextStack?: StoredRemoteContext[] | null,\r\n checkpointIndex?: StoredCheckpointMeta[] | null\r\n ): LocalChat {\r\n const chatPath = this.getChatPath(chatId);\r\n const now = new Date().toISOString();\r\n\r\n let chat: LocalChat;\r\n\r\n if (fs.existsSync(chatPath)) {\r\n // Update existing chat\r\n const existing = this.loadChat(chatId);\r\n if (existing) {\r\n chat = {\r\n ...existing,\r\n messages,\r\n uiMessages: uiMessages || existing.uiMessages,\r\n cwd: cwd || existing.cwd, // Preserve CWD if not provided\r\n // remoteContext: null = clear it, undefined = preserve existing\r\n remoteContext: remoteContext === null ? undefined : (remoteContext !== undefined ? remoteContext : existing.remoteContext),\r\n // remoteContextStack: null = clear it, undefined = preserve existing\r\n remoteContextStack: remoteContextStack === null ? undefined : (remoteContextStack !== undefined ? remoteContextStack : existing.remoteContextStack),\r\n // checkpointIndex: null = clear it, undefined = preserve existing\r\n checkpointIndex: checkpointIndex === null ? undefined : (checkpointIndex !== undefined ? checkpointIndex : existing.checkpointIndex),\r\n // Preserve backendConversationId if it exists\r\n backendConversationId: existing.backendConversationId,\r\n messageCount: messages.length,\r\n updatedAt: now,\r\n };\r\n } else {\r\n // File exists but couldn't be parsed, create new\r\n chat = {\r\n id: chatId,\r\n title: this.generateTitle(messages),\r\n createdAt: now,\r\n updatedAt: now,\r\n messageCount: messages.length,\r\n messages,\r\n uiMessages,\r\n cwd,\r\n remoteContext: remoteContext ?? undefined,\r\n remoteContextStack: remoteContextStack ?? undefined,\r\n checkpointIndex: checkpointIndex ?? undefined,\r\n };\r\n }\r\n } else {\r\n // Create new chat\r\n chat = {\r\n id: chatId,\r\n title: this.generateTitle(messages),\r\n createdAt: now,\r\n updatedAt: now,\r\n messageCount: messages.length,\r\n messages,\r\n uiMessages,\r\n cwd,\r\n remoteContext: remoteContext ?? undefined,\r\n remoteContextStack: remoteContextStack ?? undefined,\r\n checkpointIndex: checkpointIndex ?? undefined,\r\n };\r\n }\r\n\r\n fs.writeFileSync(chatPath, JSON.stringify(chat, null, 2), 'utf-8');\r\n\r\n // Update the metadata index\r\n this.updateIndexEntry(chat);\r\n\r\n return chat;\r\n }\r\n\r\n /**\r\n * Set the backend conversation ID for a chat\r\n * This is used to associate local chats with backend file storage\r\n */\r\n setBackendConversationId(chatId: string, backendConversationId: string): boolean {\r\n const chat = this.loadChat(chatId);\r\n if (!chat) {\r\n return false;\r\n }\r\n\r\n try {\r\n chat.backendConversationId = backendConversationId;\r\n chat.updatedAt = new Date().toISOString();\r\n const chatPath = this.getChatPath(chatId);\r\n fs.writeFileSync(chatPath, JSON.stringify(chat, null, 2), 'utf-8');\r\n return true;\r\n } catch (error) {\r\n logError(`Failed to set backend conversation ID for chat ${chatId}`, error as Error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Get the backend conversation ID for a chat (if any)\r\n */\r\n getBackendConversationId(chatId: string): string | undefined {\r\n const chat = this.loadChat(chatId);\r\n return chat?.backendConversationId;\r\n }\r\n\r\n /**\r\n * Load a chat by ID\r\n */\r\n loadChat(chatId: string): LocalChat | null {\r\n const chatPath = this.getChatPath(chatId);\r\n\r\n if (!fs.existsSync(chatPath)) {\r\n return null;\r\n }\r\n\r\n try {\r\n const data = fs.readFileSync(chatPath, 'utf-8');\r\n return JSON.parse(data) as LocalChat;\r\n } catch (error) {\r\n logError(`Failed to load chat ${chatId}`, error as Error);\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * List all chats (metadata only, sorted by updatedAt descending)\r\n * Uses the in-memory index to avoid loading full chat files\r\n */\r\n listChats(): LocalChatMeta[] {\r\n // Ensure index is loaded\r\n this.loadIndex();\r\n\r\n // Return sorted metadata from the cache\r\n const chats = Array.from(this.chatIndex.values());\r\n\r\n // Sort by updatedAt descending (most recent first)\r\n chats.sort((a, b) =>\r\n new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()\r\n );\r\n\r\n return chats;\r\n }\r\n\r\n /**\r\n * Delete a chat by ID\r\n */\r\n deleteChat(chatId: string): boolean {\r\n const chatPath = this.getChatPath(chatId);\r\n const checkpointsPath = path.join(os.homedir(), '.centaurus', 'checkpoints', chatId);\r\n\r\n if (!fs.existsSync(chatPath)) {\r\n // Best-effort cleanup for orphaned checkpoint data.\r\n if (fs.existsSync(checkpointsPath)) {\r\n try {\r\n fs.rmSync(checkpointsPath, { recursive: true, force: true });\r\n } catch {\r\n // Ignore cleanup errors for missing chats.\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n try {\r\n fs.unlinkSync(chatPath);\r\n if (fs.existsSync(checkpointsPath)) {\r\n fs.rmSync(checkpointsPath, { recursive: true, force: true });\r\n }\r\n\r\n // Remove from index\r\n this.chatIndex.delete(chatId);\r\n this.saveIndex();\r\n\r\n return true;\r\n } catch (error) {\r\n logError(`Failed to delete chat ${chatId}`, error as Error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Import a full chat object (used for restoring from cloud sync)\r\n * Overwrites existing chat with same ID if present\r\n */\r\n importChat(chat: LocalChat): boolean {\r\n if (!chat || !chat.id) {\r\n return false;\r\n }\r\n\r\n try {\r\n const chatPath = this.getChatPath(chat.id);\r\n\r\n // Ensure the chat has required fields\r\n const now = new Date().toISOString();\r\n const importedChat: LocalChat = {\r\n id: chat.id,\r\n title: chat.title || 'Imported Chat',\r\n createdAt: chat.createdAt || now,\r\n updatedAt: now, // Always update the timestamp on import\r\n messageCount: chat.messages?.length || chat.messageCount || 0,\r\n messages: chat.messages || [],\r\n uiMessages: chat.uiMessages,\r\n cwd: chat.cwd,\r\n remoteContext: chat.remoteContext,\r\n remoteContextStack: chat.remoteContextStack,\r\n checkpointIndex: chat.checkpointIndex,\r\n backendConversationId: chat.backendConversationId,\r\n environment: chat.environment,\r\n };\r\n\r\n fs.writeFileSync(chatPath, JSON.stringify(importedChat, null, 2), 'utf-8');\r\n\r\n // Update the index\r\n this.updateIndexEntry(importedChat);\r\n\r\n return true;\r\n } catch (error) {\r\n logError(`Failed to import chat ${chat.id}`, error as Error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Rename a chat by ID\r\n */\r\n renameChat(chatId: string, newTitle: string): boolean {\r\n const chat = this.loadChat(chatId);\r\n if (!chat) {\r\n return false;\r\n }\r\n\r\n try {\r\n chat.title = newTitle;\r\n chat.updatedAt = new Date().toISOString();\r\n const chatPath = this.getChatPath(chatId);\r\n fs.writeFileSync(chatPath, JSON.stringify(chat, null, 2), 'utf-8');\r\n\r\n // Update the index\r\n this.updateIndexEntry(chat);\r\n\r\n return true;\r\n } catch (error) {\r\n logError(`Failed to rename chat ${chatId}`, error as Error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Get the chats directory path\r\n */\r\n getChatsDir(): string {\r\n return this.chatsDir;\r\n }\r\n}\r\n\r\n// Export singleton instance\r\nexport const localChatStorage = new LocalChatStorage();\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/services/local-chat-storage.ts"],"sourcesContent":["/**\r\n * Local Chat Storage Service\r\n * \r\n * Provides persistent local storage for CLI conversations.\r\n * Stores chats in ~/.centaurus/chats/ directory as JSON files.\r\n */\r\n\r\nimport * as fs from 'fs';\r\nimport * as path from 'path';\r\nimport * as os from 'os';\r\nimport { logError } from '../utils/logger.js';\r\nimport { cleanupToolOutputsForChat } from '../utils/output-truncation.js';\r\n\r\n/**\r\n * Message interface matching the AIMessage type from ai-service-client\r\n */\r\nexport interface StoredMessage {\r\n role: 'user' | 'assistant' | 'system' | 'tool';\r\n content: string;\r\n tool_calls?: Array<{\r\n id: string;\r\n name: string;\r\n arguments: Record<string, any>;\r\n }>;\r\n tool_call_id?: string;\r\n}\r\n\r\n/**\r\n * UI Message for restoring full chat history display\r\n * This is a serializable version of the Message type from types/index.ts\r\n */\r\nexport interface StoredUIMessage {\r\n id: string;\r\n role: 'user' | 'assistant' | 'system' | 'tool';\r\n content: string;\r\n timestamp?: string; // ISO string (Date is not JSON serializable)\r\n toolExecution?: {\r\n toolName: string;\r\n status: 'pending' | 'executing' | 'completed' | 'error';\r\n result?: string;\r\n error?: string;\r\n arguments?: Record<string, any>;\r\n };\r\n shouldStream?: boolean;\r\n isCommandMode?: boolean;\r\n tool_call_id?: string;\r\n tool_calls?: Array<{ id: string; name: string; arguments: Record<string, any> }>;\r\n thinkingDuration?: number;\r\n taskCompletion?: {\r\n completedStepNumber: number;\r\n completedStepDescription: string;\r\n completedCount: number;\r\n totalCount: number;\r\n completionNote?: string;\r\n allSteps?: Array<{\r\n id: number;\r\n description: string;\r\n status: 'pending' | 'completed';\r\n }>;\r\n };\r\n planAccepted?: {\r\n planTitle?: string;\r\n totalSteps?: number;\r\n steps?: Array<{\r\n id: number;\r\n description: string;\r\n status: 'pending' | 'completed';\r\n }>;\r\n };\r\n planQuestion?: {\r\n question: string;\r\n options: string[];\r\n userAnswer: string;\r\n wasSkipped: boolean;\r\n };\r\n connectionStatus?: { // For SSH/WSL/Docker connection status\r\n type: 'ssh' | 'wsl' | 'docker';\r\n status: 'connecting' | 'connected' | 'error' | 'disconnected';\r\n connectionString?: string;\r\n error?: string;\r\n };\r\n}\r\n\r\n/**\r\n * Remote environment context for SSH/WSL/Docker sessions\r\n * Used to restore remote session state when resuming a chat\r\n */\r\nexport interface StoredRemoteContext {\r\n type: 'ssh' | 'wsl' | 'docker';\r\n connectionCommand: string; // Original command to reconnect (e.g., \"ssh user@host\" or \"wsl\")\r\n remoteCwd: string; // CWD in the remote environment\r\n localCwdBeforeRemote: string; // Local CWD before entering remote session\r\n metadata?: {\r\n hostname?: string; // For SSH\r\n username?: string; // For SSH \r\n distroName?: string; // For WSL\r\n containerId?: string; // For Docker\r\n port?: number; // For SSH\r\n };\r\n}\r\n\r\n/**\r\n * Serialized checkpoint metadata stored with chat history.\r\n * This allows checkpoint state to be restored even if the checkpoint index file is missing.\r\n */\r\nexport interface StoredCheckpointMeta {\r\n id: string;\r\n prompt: string;\r\n createdAt: string;\r\n createdAtMs: number;\r\n cwd: string;\r\n contextType: 'local' | 'ssh' | 'wsl' | 'docker';\r\n remoteSessionInfo?: {\r\n hostname?: string;\r\n username?: string;\r\n sessionId: string;\r\n connectionString?: string;\r\n };\r\n conversationIndex: number;\r\n uiMessageIndex?: number;\r\n uiMessageId?: string;\r\n snapshotDir: string;\r\n manifestPath: string;\r\n changes?: {\r\n added: string[];\r\n modified: string[];\r\n deleted: string[];\r\n };\r\n commands: string[];\r\n toolCalls: Array<{\r\n id?: string;\r\n name: string;\r\n arguments?: Record<string, any>;\r\n timestamp: string;\r\n }>;\r\n status: 'active' | 'finalized' | 'discarded';\r\n}\r\n\r\n/**\r\n * Local chat metadata (without messages, for listing)\r\n */\r\nexport interface LocalChatMeta {\r\n id: string;\r\n title: string;\r\n createdAt: string;\r\n updatedAt: string;\r\n messageCount: number;\r\n environment?: string; // 'local' or 'ssh:user@host' or 'wsl:distro' or 'docker:container'\r\n}\r\n\r\n/**\r\n * Full local chat with messages\r\n */\r\nexport interface LocalChat extends LocalChatMeta {\r\n messages: StoredMessage[];\r\n uiMessages?: StoredUIMessage[]; // Full UI history for restoration\r\n cwd?: string; // Working directory at time of save (for restoration)\r\n remoteContext?: StoredRemoteContext; // Remote context if saved while in SSH/WSL/Docker (backward compat)\r\n remoteContextStack?: StoredRemoteContext[]; // Stack of nested contexts (for SSH>SSH, SSH>Docker, etc.)\r\n checkpointIndex?: StoredCheckpointMeta[]; // Serialized checkpoint metadata for resume/revert restoration\r\n backendConversationId?: string; // Backend conversation UUID (for file storage/deletion)\r\n}\r\n\r\n/**\r\n * LocalChatStorage class for managing local conversation persistence\r\n */\r\nexport class LocalChatStorage {\r\n private chatsDir: string;\r\n private indexPath: string;\r\n private chatIndex: Map<string, LocalChatMeta> = new Map();\r\n private indexLoaded: boolean = false;\r\n\r\n constructor() {\r\n const homeDir = os.homedir();\r\n this.chatsDir = path.join(homeDir, '.centaurus', 'chats');\r\n this.indexPath = path.join(this.chatsDir, 'index.json');\r\n\r\n // Ensure chats directory exists\r\n if (!fs.existsSync(this.chatsDir)) {\r\n fs.mkdirSync(this.chatsDir, { recursive: true });\r\n }\r\n\r\n // Load or build the index\r\n this.loadIndex();\r\n }\r\n\r\n /**\r\n * Load the metadata index from disk, or rebuild it from chat files if missing\r\n */\r\n private loadIndex(): void {\r\n if (this.indexLoaded) return;\r\n\r\n try {\r\n if (fs.existsSync(this.indexPath)) {\r\n const data = fs.readFileSync(this.indexPath, 'utf-8');\r\n const indexArray: LocalChatMeta[] = JSON.parse(data);\r\n this.chatIndex.clear();\r\n for (const meta of indexArray) {\r\n this.chatIndex.set(meta.id, meta);\r\n }\r\n this.indexLoaded = true;\r\n return;\r\n }\r\n } catch (error) {\r\n // Index file corrupted, rebuild it\r\n }\r\n\r\n // Rebuild index from chat files (one-time migration)\r\n this.rebuildIndex();\r\n }\r\n\r\n /**\r\n * Rebuild the index by reading metadata from all chat files\r\n * This is a one-time migration that reads full files, but only happens once\r\n */\r\n private rebuildIndex(): void {\r\n this.chatIndex.clear();\r\n\r\n if (!fs.existsSync(this.chatsDir)) {\r\n this.indexLoaded = true;\r\n return;\r\n }\r\n\r\n const files = fs.readdirSync(this.chatsDir)\r\n .filter(f => f.endsWith('.json') && f !== 'index.json');\r\n\r\n for (const file of files) {\r\n try {\r\n const filePath = path.join(this.chatsDir, file);\r\n const data = fs.readFileSync(filePath, 'utf-8');\r\n const chat = JSON.parse(data) as LocalChat;\r\n\r\n // Determine environment string from remoteContext\r\n let environment = 'local';\r\n if (chat.remoteContext) {\r\n const rc = chat.remoteContext;\r\n if (rc.type === 'ssh' && rc.metadata?.hostname) {\r\n environment = `ssh:${rc.metadata.username || 'user'}@${rc.metadata.hostname}`;\r\n } else if (rc.type === 'wsl') {\r\n environment = `wsl:${rc.metadata?.distroName || 'Ubuntu'}`;\r\n } else if (rc.type === 'docker' && rc.metadata?.containerId) {\r\n environment = `docker:${rc.metadata.containerId.substring(0, 12)}`;\r\n } else {\r\n environment = rc.type;\r\n }\r\n }\r\n\r\n // Store metadata only\r\n this.chatIndex.set(chat.id, {\r\n id: chat.id,\r\n title: chat.title,\r\n createdAt: chat.createdAt,\r\n updatedAt: chat.updatedAt,\r\n messageCount: chat.messageCount,\r\n environment,\r\n });\r\n } catch (error) {\r\n // Skip invalid files\r\n continue;\r\n }\r\n }\r\n\r\n this.indexLoaded = true;\r\n this.saveIndex();\r\n }\r\n\r\n /**\r\n * Save the metadata index to disk\r\n */\r\n private saveIndex(): void {\r\n try {\r\n const indexArray = Array.from(this.chatIndex.values());\r\n fs.writeFileSync(this.indexPath, JSON.stringify(indexArray, null, 2), 'utf-8');\r\n } catch (error) {\r\n logError('Failed to save chat index', error as Error);\r\n }\r\n }\r\n\r\n /**\r\n * Update the index entry for a chat\r\n */\r\n private updateIndexEntry(chat: LocalChat): void {\r\n let environment = 'local';\r\n if (chat.remoteContext) {\r\n const rc = chat.remoteContext;\r\n if (rc.type === 'ssh' && rc.metadata?.hostname) {\r\n environment = `ssh:${rc.metadata.username || 'user'}@${rc.metadata.hostname}`;\r\n } else if (rc.type === 'wsl') {\r\n environment = `wsl:${rc.metadata?.distroName || 'Ubuntu'}`;\r\n } else if (rc.type === 'docker' && rc.metadata?.containerId) {\r\n environment = `docker:${rc.metadata.containerId.substring(0, 12)}`;\r\n } else {\r\n environment = rc.type;\r\n }\r\n }\r\n\r\n this.chatIndex.set(chat.id, {\r\n id: chat.id,\r\n title: chat.title,\r\n createdAt: chat.createdAt,\r\n updatedAt: chat.updatedAt,\r\n messageCount: chat.messageCount,\r\n environment,\r\n });\r\n this.saveIndex();\r\n }\r\n\r\n /**\r\n * Generate a unique chat ID based on timestamp\r\n */\r\n generateChatId(): string {\r\n const now = new Date();\r\n const timestamp = now.toISOString().replace(/[:.]/g, '-').slice(0, 19);\r\n const random = Math.random().toString(36).substring(2, 6);\r\n return `chat-${timestamp}-${random}`;\r\n }\r\n\r\n /**\r\n * Generate a title from the first user message\r\n */\r\n generateTitle(messages: StoredMessage[]): string {\r\n const firstUserMessage = messages.find(m => m.role === 'user');\r\n if (!firstUserMessage || !firstUserMessage.content) {\r\n return 'New Chat';\r\n }\r\n\r\n // Take first 60 chars, trim to last word boundary\r\n let title = firstUserMessage.content.substring(0, 60);\r\n if (firstUserMessage.content.length > 60) {\r\n const lastSpace = title.lastIndexOf(' ');\r\n if (lastSpace > 30) {\r\n title = title.substring(0, lastSpace);\r\n }\r\n title += '...';\r\n }\r\n\r\n // Clean up whitespace and newlines\r\n title = title.replace(/\\s+/g, ' ').trim();\r\n\r\n return title || 'New Chat';\r\n }\r\n\r\n /**\r\n * Get the file path for a chat\r\n */\r\n private getChatPath(chatId: string): string {\r\n return path.join(this.chatsDir, `${chatId}.json`);\r\n }\r\n\r\n /**\r\n * Save a chat to disk\r\n * @param remoteContext - undefined = preserve existing, null = clear, StoredRemoteContext = set new value\r\n * @param remoteContextStack - Stack of nested contexts for SSH>SSH, SSH>Docker etc.\r\n * @param checkpointIndex - undefined = preserve existing, null = clear, array = set latest checkpoint metadata\r\n */\r\n saveChat(\r\n chatId: string,\r\n messages: StoredMessage[],\r\n uiMessages?: StoredUIMessage[],\r\n cwd?: string,\r\n remoteContext?: StoredRemoteContext | null,\r\n remoteContextStack?: StoredRemoteContext[] | null,\r\n checkpointIndex?: StoredCheckpointMeta[] | null\r\n ): LocalChat {\r\n const chatPath = this.getChatPath(chatId);\r\n const now = new Date().toISOString();\r\n\r\n let chat: LocalChat;\r\n\r\n if (fs.existsSync(chatPath)) {\r\n // Update existing chat\r\n const existing = this.loadChat(chatId);\r\n if (existing) {\r\n chat = {\r\n ...existing,\r\n messages,\r\n uiMessages: uiMessages || existing.uiMessages,\r\n cwd: cwd || existing.cwd, // Preserve CWD if not provided\r\n // remoteContext: null = clear it, undefined = preserve existing\r\n remoteContext: remoteContext === null ? undefined : (remoteContext !== undefined ? remoteContext : existing.remoteContext),\r\n // remoteContextStack: null = clear it, undefined = preserve existing\r\n remoteContextStack: remoteContextStack === null ? undefined : (remoteContextStack !== undefined ? remoteContextStack : existing.remoteContextStack),\r\n // checkpointIndex: null = clear it, undefined = preserve existing\r\n checkpointIndex: checkpointIndex === null ? undefined : (checkpointIndex !== undefined ? checkpointIndex : existing.checkpointIndex),\r\n // Preserve backendConversationId if it exists\r\n backendConversationId: existing.backendConversationId,\r\n messageCount: messages.length,\r\n updatedAt: now,\r\n };\r\n } else {\r\n // File exists but couldn't be parsed, create new\r\n chat = {\r\n id: chatId,\r\n title: this.generateTitle(messages),\r\n createdAt: now,\r\n updatedAt: now,\r\n messageCount: messages.length,\r\n messages,\r\n uiMessages,\r\n cwd,\r\n remoteContext: remoteContext ?? undefined,\r\n remoteContextStack: remoteContextStack ?? undefined,\r\n checkpointIndex: checkpointIndex ?? undefined,\r\n };\r\n }\r\n } else {\r\n // Create new chat\r\n chat = {\r\n id: chatId,\r\n title: this.generateTitle(messages),\r\n createdAt: now,\r\n updatedAt: now,\r\n messageCount: messages.length,\r\n messages,\r\n uiMessages,\r\n cwd,\r\n remoteContext: remoteContext ?? undefined,\r\n remoteContextStack: remoteContextStack ?? undefined,\r\n checkpointIndex: checkpointIndex ?? undefined,\r\n };\r\n }\r\n\r\n fs.writeFileSync(chatPath, JSON.stringify(chat, null, 2), 'utf-8');\r\n\r\n // Update the metadata index\r\n this.updateIndexEntry(chat);\r\n\r\n return chat;\r\n }\r\n\r\n /**\r\n * Set the backend conversation ID for a chat\r\n * This is used to associate local chats with backend file storage\r\n */\r\n setBackendConversationId(chatId: string, backendConversationId: string): boolean {\r\n const chat = this.loadChat(chatId);\r\n if (!chat) {\r\n return false;\r\n }\r\n\r\n try {\r\n chat.backendConversationId = backendConversationId;\r\n chat.updatedAt = new Date().toISOString();\r\n const chatPath = this.getChatPath(chatId);\r\n fs.writeFileSync(chatPath, JSON.stringify(chat, null, 2), 'utf-8');\r\n return true;\r\n } catch (error) {\r\n logError(`Failed to set backend conversation ID for chat ${chatId}`, error as Error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Get the backend conversation ID for a chat (if any)\r\n */\r\n getBackendConversationId(chatId: string): string | undefined {\r\n const chat = this.loadChat(chatId);\r\n return chat?.backendConversationId;\r\n }\r\n\r\n /**\r\n * Load a chat by ID\r\n */\r\n loadChat(chatId: string): LocalChat | null {\r\n const chatPath = this.getChatPath(chatId);\r\n\r\n if (!fs.existsSync(chatPath)) {\r\n return null;\r\n }\r\n\r\n try {\r\n const data = fs.readFileSync(chatPath, 'utf-8');\r\n return JSON.parse(data) as LocalChat;\r\n } catch (error) {\r\n logError(`Failed to load chat ${chatId}`, error as Error);\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * List all chats (metadata only, sorted by updatedAt descending)\r\n * Uses the in-memory index to avoid loading full chat files\r\n */\r\n listChats(): LocalChatMeta[] {\r\n // Ensure index is loaded\r\n this.loadIndex();\r\n\r\n // Return sorted metadata from the cache\r\n const chats = Array.from(this.chatIndex.values());\r\n\r\n // Sort by updatedAt descending (most recent first)\r\n chats.sort((a, b) =>\r\n new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()\r\n );\r\n\r\n return chats;\r\n }\r\n\r\n /**\r\n * Delete a chat by ID\r\n */\r\n deleteChat(chatId: string): boolean {\r\n const chatPath = this.getChatPath(chatId);\r\n const checkpointsPath = path.join(os.homedir(), '.centaurus', 'checkpoints', chatId);\r\n\r\n if (!fs.existsSync(chatPath)) {\r\n // Best-effort cleanup for orphaned checkpoint data.\r\n if (fs.existsSync(checkpointsPath)) {\r\n try {\r\n fs.rmSync(checkpointsPath, { recursive: true, force: true });\r\n } catch {\r\n // Ignore cleanup errors for missing chats.\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n try {\r\n fs.unlinkSync(chatPath);\r\n if (fs.existsSync(checkpointsPath)) {\r\n fs.rmSync(checkpointsPath, { recursive: true, force: true });\r\n }\r\n\r\n // Clean up tool output files for this chat\r\n cleanupToolOutputsForChat(chatId);\r\n\r\n // Remove from index\r\n this.chatIndex.delete(chatId);\r\n this.saveIndex();\r\n\r\n return true;\r\n } catch (error) {\r\n logError(`Failed to delete chat ${chatId}`, error as Error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Import a full chat object (used for restoring from cloud sync)\r\n * Overwrites existing chat with same ID if present\r\n */\r\n importChat(chat: LocalChat): boolean {\r\n if (!chat || !chat.id) {\r\n return false;\r\n }\r\n\r\n try {\r\n const chatPath = this.getChatPath(chat.id);\r\n\r\n // Ensure the chat has required fields\r\n const now = new Date().toISOString();\r\n const importedChat: LocalChat = {\r\n id: chat.id,\r\n title: chat.title || 'Imported Chat',\r\n createdAt: chat.createdAt || now,\r\n updatedAt: now, // Always update the timestamp on import\r\n messageCount: chat.messages?.length || chat.messageCount || 0,\r\n messages: chat.messages || [],\r\n uiMessages: chat.uiMessages,\r\n cwd: chat.cwd,\r\n remoteContext: chat.remoteContext,\r\n remoteContextStack: chat.remoteContextStack,\r\n checkpointIndex: chat.checkpointIndex,\r\n backendConversationId: chat.backendConversationId,\r\n environment: chat.environment,\r\n };\r\n\r\n fs.writeFileSync(chatPath, JSON.stringify(importedChat, null, 2), 'utf-8');\r\n\r\n // Update the index\r\n this.updateIndexEntry(importedChat);\r\n\r\n return true;\r\n } catch (error) {\r\n logError(`Failed to import chat ${chat.id}`, error as Error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Rename a chat by ID\r\n */\r\n renameChat(chatId: string, newTitle: string): boolean {\r\n const chat = this.loadChat(chatId);\r\n if (!chat) {\r\n return false;\r\n }\r\n\r\n try {\r\n chat.title = newTitle;\r\n chat.updatedAt = new Date().toISOString();\r\n const chatPath = this.getChatPath(chatId);\r\n fs.writeFileSync(chatPath, JSON.stringify(chat, null, 2), 'utf-8');\r\n\r\n // Update the index\r\n this.updateIndexEntry(chat);\r\n\r\n return true;\r\n } catch (error) {\r\n logError(`Failed to rename chat ${chatId}`, error as Error);\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Get the chats directory path\r\n */\r\n getChatsDir(): string {\r\n return this.chatsDir;\r\n }\r\n}\r\n\r\n// Export singleton instance\r\nexport const localChatStorage = new LocalChatStorage();\r\n"],"mappings":"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","names":[]}
@@ -0,0 +1,141 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as os from "os";
4
+ import {
5
+ SKILL_TOOL_PRESETS
6
+ } from "../types/skill.js";
7
+ const SKILLS_DIR = path.join(os.homedir(), ".centaurus", "skills");
8
+ function ensureSkillsDir() {
9
+ if (!fs.existsSync(SKILLS_DIR)) {
10
+ fs.mkdirSync(SKILLS_DIR, { recursive: true });
11
+ }
12
+ }
13
+ function truncatePreview(content, maxLength = 80) {
14
+ const firstLine = content.split("\n").find((l) => l.trim()) || "";
15
+ return firstLine.length > maxLength ? firstLine.slice(0, maxLength - 1) + "\u2026" : firstLine;
16
+ }
17
+ class SkillStorageService {
18
+ static instance;
19
+ static getInstance() {
20
+ if (!SkillStorageService.instance) {
21
+ SkillStorageService.instance = new SkillStorageService();
22
+ }
23
+ return SkillStorageService.instance;
24
+ }
25
+ /** Sanitize a skill name for use as a filename and slash command */
26
+ sanitizeName(name) {
27
+ return name.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
28
+ }
29
+ getSkillPath(name) {
30
+ return path.join(SKILLS_DIR, `${this.sanitizeName(name)}.json`);
31
+ }
32
+ /** Check if a skill exists */
33
+ exists(name) {
34
+ return fs.existsSync(this.getSkillPath(name));
35
+ }
36
+ /** Load a single skill by name */
37
+ load(name) {
38
+ try {
39
+ const filePath = this.getSkillPath(name);
40
+ if (!fs.existsSync(filePath)) return null;
41
+ const raw = fs.readFileSync(filePath, "utf-8");
42
+ return JSON.parse(raw);
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+ /** List all saved skills */
48
+ list() {
49
+ ensureSkillsDir();
50
+ try {
51
+ const files = fs.readdirSync(SKILLS_DIR).filter((f) => f.endsWith(".json"));
52
+ const skills = [];
53
+ for (const file of files) {
54
+ try {
55
+ const filePath = path.join(SKILLS_DIR, file);
56
+ const raw = fs.readFileSync(filePath, "utf-8");
57
+ const skill = JSON.parse(raw);
58
+ skills.push({
59
+ name: skill.name,
60
+ promptPreview: truncatePreview(skill.prompt),
61
+ accessLevel: skill.accessLevel,
62
+ model: skill.model,
63
+ createdAt: skill.createdAt,
64
+ updatedAt: skill.updatedAt
65
+ });
66
+ } catch {
67
+ }
68
+ }
69
+ return skills.sort(
70
+ (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
71
+ );
72
+ } catch {
73
+ return [];
74
+ }
75
+ }
76
+ /** Get all skill names (for slash command detection) */
77
+ getSkillNames() {
78
+ return this.list().map((s) => s.name);
79
+ }
80
+ /** Save (create or update) a skill */
81
+ save(skill, previousName) {
82
+ ensureSkillsDir();
83
+ const normalizedName = this.sanitizeName(skill.name);
84
+ if (!normalizedName) {
85
+ return { success: false, error: "Skill name is required." };
86
+ }
87
+ if (!skill.prompt || !skill.prompt.trim()) {
88
+ return { success: false, error: "Skill prompt is required." };
89
+ }
90
+ const normalizedPreviousName = previousName ? this.sanitizeName(previousName) : void 0;
91
+ const isRename = !!normalizedPreviousName && normalizedPreviousName !== normalizedName;
92
+ if (isRename || !normalizedPreviousName) {
93
+ if (this.exists(normalizedName)) {
94
+ return { success: false, error: `A skill named "${normalizedName}" already exists.` };
95
+ }
96
+ }
97
+ const now = (/* @__PURE__ */ new Date()).toISOString();
98
+ const savedSkill = {
99
+ ...skill,
100
+ name: normalizedName,
101
+ allowedTools: skill.allowedTools.length > 0 ? skill.allowedTools : SKILL_TOOL_PRESETS[skill.accessLevel],
102
+ updatedAt: now,
103
+ createdAt: skill.createdAt || now
104
+ };
105
+ try {
106
+ const filePath = this.getSkillPath(normalizedName);
107
+ fs.writeFileSync(filePath, JSON.stringify(savedSkill, null, 2), "utf-8");
108
+ if (isRename && normalizedPreviousName) {
109
+ const oldPath = this.getSkillPath(normalizedPreviousName);
110
+ if (fs.existsSync(oldPath)) {
111
+ fs.unlinkSync(oldPath);
112
+ }
113
+ }
114
+ return {
115
+ success: true,
116
+ savedName: normalizedName,
117
+ renamedFrom: isRename ? normalizedPreviousName : void 0
118
+ };
119
+ } catch (error) {
120
+ return { success: false, error: error.message || "Failed to save skill." };
121
+ }
122
+ }
123
+ /** Delete a skill */
124
+ delete(name) {
125
+ const filePath = this.getSkillPath(name);
126
+ if (!fs.existsSync(filePath)) {
127
+ return { success: false, error: `Skill "${name}" not found.` };
128
+ }
129
+ try {
130
+ fs.unlinkSync(filePath);
131
+ return { success: true };
132
+ } catch (error) {
133
+ return { success: false, error: error.message };
134
+ }
135
+ }
136
+ }
137
+ const skillStorage = SkillStorageService.getInstance();
138
+ export {
139
+ skillStorage
140
+ };
141
+ //# sourceMappingURL=skill-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/services/skill-storage.ts"],"sourcesContent":["/**\r\n * Skill Storage Service\r\n *\r\n * Manages CRUD operations for user-defined skills stored as JSON files\r\n * in ~/.centaurus/skills/\r\n */\r\n\r\nimport * as fs from 'fs';\r\nimport * as path from 'path';\r\nimport * as os from 'os';\r\nimport {\r\n SavedSkill,\r\n SkillMeta,\r\n SaveSkillResult,\r\n SkillAccessLevel,\r\n SKILL_TOOL_PRESETS,\r\n} from '../types/skill.js';\r\n\r\nconst SKILLS_DIR = path.join(os.homedir(), '.centaurus', 'skills');\r\n\r\nfunction ensureSkillsDir(): void {\r\n if (!fs.existsSync(SKILLS_DIR)) {\r\n fs.mkdirSync(SKILLS_DIR, { recursive: true });\r\n }\r\n}\r\n\r\nfunction truncatePreview(content: string, maxLength = 80): string {\r\n const firstLine = content.split('\\n').find(l => l.trim()) || '';\r\n return firstLine.length > maxLength\r\n ? firstLine.slice(0, maxLength - 1) + '\\u2026'\r\n : firstLine;\r\n}\r\n\r\nclass SkillStorageService {\r\n private static instance: SkillStorageService;\r\n\r\n static getInstance(): SkillStorageService {\r\n if (!SkillStorageService.instance) {\r\n SkillStorageService.instance = new SkillStorageService();\r\n }\r\n return SkillStorageService.instance;\r\n }\r\n\r\n /** Sanitize a skill name for use as a filename and slash command */\r\n sanitizeName(name: string): string {\r\n return name\r\n .toLowerCase()\r\n .replace(/[^a-z0-9_-]/g, '-')\r\n .replace(/-+/g, '-')\r\n .replace(/^-|-$/g, '');\r\n }\r\n\r\n private getSkillPath(name: string): string {\r\n return path.join(SKILLS_DIR, `${this.sanitizeName(name)}.json`);\r\n }\r\n\r\n /** Check if a skill exists */\r\n exists(name: string): boolean {\r\n return fs.existsSync(this.getSkillPath(name));\r\n }\r\n\r\n /** Load a single skill by name */\r\n load(name: string): SavedSkill | null {\r\n try {\r\n const filePath = this.getSkillPath(name);\r\n if (!fs.existsSync(filePath)) return null;\r\n const raw = fs.readFileSync(filePath, 'utf-8');\r\n return JSON.parse(raw) as SavedSkill;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /** List all saved skills */\r\n list(): SkillMeta[] {\r\n ensureSkillsDir();\r\n try {\r\n const files = fs.readdirSync(SKILLS_DIR).filter(f => f.endsWith('.json'));\r\n const skills: SkillMeta[] = [];\r\n\r\n for (const file of files) {\r\n try {\r\n const filePath = path.join(SKILLS_DIR, file);\r\n const raw = fs.readFileSync(filePath, 'utf-8');\r\n const skill = JSON.parse(raw) as SavedSkill;\r\n skills.push({\r\n name: skill.name,\r\n promptPreview: truncatePreview(skill.prompt),\r\n accessLevel: skill.accessLevel,\r\n model: skill.model,\r\n createdAt: skill.createdAt,\r\n updatedAt: skill.updatedAt,\r\n });\r\n } catch {\r\n // Skip malformed files\r\n }\r\n }\r\n\r\n return skills.sort((a, b) =>\r\n new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()\r\n );\r\n } catch {\r\n return [];\r\n }\r\n }\r\n\r\n /** Get all skill names (for slash command detection) */\r\n getSkillNames(): string[] {\r\n return this.list().map(s => s.name);\r\n }\r\n\r\n /** Save (create or update) a skill */\r\n save(skill: SavedSkill, previousName?: string): SaveSkillResult {\r\n ensureSkillsDir();\r\n\r\n const normalizedName = this.sanitizeName(skill.name);\r\n if (!normalizedName) {\r\n return { success: false, error: 'Skill name is required.' };\r\n }\r\n if (!skill.prompt || !skill.prompt.trim()) {\r\n return { success: false, error: 'Skill prompt is required.' };\r\n }\r\n\r\n const normalizedPreviousName = previousName ? this.sanitizeName(previousName) : undefined;\r\n const isRename = !!normalizedPreviousName && normalizedPreviousName !== normalizedName;\r\n\r\n // Check for conflicts\r\n if (isRename || !normalizedPreviousName) {\r\n if (this.exists(normalizedName)) {\r\n return { success: false, error: `A skill named \"${normalizedName}\" already exists.` };\r\n }\r\n }\r\n\r\n const now = new Date().toISOString();\r\n const savedSkill: SavedSkill = {\r\n ...skill,\r\n name: normalizedName,\r\n allowedTools: skill.allowedTools.length > 0\r\n ? skill.allowedTools\r\n : SKILL_TOOL_PRESETS[skill.accessLevel],\r\n updatedAt: now,\r\n createdAt: skill.createdAt || now,\r\n };\r\n\r\n try {\r\n const filePath = this.getSkillPath(normalizedName);\r\n fs.writeFileSync(filePath, JSON.stringify(savedSkill, null, 2), 'utf-8');\r\n\r\n // Handle rename\r\n if (isRename && normalizedPreviousName) {\r\n const oldPath = this.getSkillPath(normalizedPreviousName);\r\n if (fs.existsSync(oldPath)) {\r\n fs.unlinkSync(oldPath);\r\n }\r\n }\r\n\r\n return {\r\n success: true,\r\n savedName: normalizedName,\r\n renamedFrom: isRename ? normalizedPreviousName : undefined,\r\n };\r\n } catch (error: any) {\r\n return { success: false, error: error.message || 'Failed to save skill.' };\r\n }\r\n }\r\n\r\n /** Delete a skill */\r\n delete(name: string): { success: boolean; error?: string } {\r\n const filePath = this.getSkillPath(name);\r\n if (!fs.existsSync(filePath)) {\r\n return { success: false, error: `Skill \"${name}\" not found.` };\r\n }\r\n try {\r\n fs.unlinkSync(filePath);\r\n return { success: true };\r\n } catch (error: any) {\r\n return { success: false, error: error.message };\r\n }\r\n }\r\n}\r\n\r\nexport const skillStorage = SkillStorageService.getInstance();\r\n"],"mappings":"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","names":[]}
@@ -8,20 +8,21 @@ import { ContextManager } from "../context/context-manager.js";
8
8
  const MAX_CONCURRENT_SUBAGENTS = 5;
9
9
  const SUBAGENT_TIMEOUT_MS = 10 * 60 * 1e3;
10
10
  const MAX_TURNS_PER_SUBAGENT = 50;
11
- const SIMPLE_MODEL = "gemini-3-flash-preview";
12
- const COMPLEX_MODEL = "gemini-3.1-pro-preview";
11
+ const SIMPLE_MODEL = "minimaxai/minimax-m2.5";
12
+ const COMPLEX_MODEL = "z-ai/glm5";
13
13
  const COMPLEXITY_THRESHOLD = 5;
14
14
  const SUBAGENT_SYSTEM_PROMPT = `You are a sub-agent spawned by a main AI agent to complete a specific task.
15
15
 
16
16
  IMPORTANT RULES:
17
- 1. You have full access to file system tools (read, write, edit, list, grep, find)
17
+ 1. You have access to file system tools (read, write, edit, list, grep, find)
18
18
  2. You have access to command execution tools
19
19
  3. Focus ONLY on the task assigned to you
20
20
  4. Always provide reason_text for every tool call to explain what you're doing
21
21
  5. Call task_complete when you've finished the assigned task
22
22
  6. Be efficient - don't explore unnecessarily, stick to the task
23
23
  7. If you encounter errors, try to resolve them or report clearly what went wrong
24
-
24
+ 8. 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.
25
+ {TOOL_RESTRICTIONS}
25
26
  CONTEXT FROM MAIN AGENT:
26
27
  Working Directory: {WORKING_DIRECTORY}
27
28
 
@@ -81,7 +82,16 @@ class SubAgentManagerClass {
81
82
  * Generate system prompt for sub-agent
82
83
  */
83
84
  generateSystemPrompt(config) {
84
- return SUBAGENT_SYSTEM_PROMPT.replace("{WORKING_DIRECTORY}", config.workingDirectory).replace("{CONTEXT}", config.context || "No additional context provided.").replace("{PROMPT}", config.prompt);
85
+ let toolRestrictions = "";
86
+ if (config.allowedTools && config.allowedTools.length > 0) {
87
+ toolRestrictions = `
88
+ TOOL RESTRICTIONS (CRITICAL):
89
+ You are ONLY allowed to use the following tools: ${config.allowedTools.join(", ")}, task_complete
90
+ If you need a tool that is not in this list, DO NOT attempt to call it \u2014 it will be rejected.
91
+ You must complete your task using only the tools listed above.
92
+ `;
93
+ }
94
+ return SUBAGENT_SYSTEM_PROMPT.replace("{WORKING_DIRECTORY}", config.workingDirectory).replace("{CONTEXT}", config.context || "No additional context provided.").replace("{PROMPT}", config.prompt).replace("{TOOL_RESTRICTIONS}", toolRestrictions);
85
95
  }
86
96
  /**
87
97
  * Spawn a new sub-agent
@@ -112,7 +122,7 @@ class SubAgentManagerClass {
112
122
  };
113
123
  }
114
124
  const agentId = `subagent-${randomUUID().substring(0, 8)}`;
115
- const model = this.selectModel(config.complexity);
125
+ const model = config.modelOverride || this.selectModel(config.complexity);
116
126
  const subAgent = {
117
127
  id: agentId,
118
128
  status: "pending",
@@ -127,7 +137,8 @@ class SubAgentManagerClass {
127
137
  fileOperations: [],
128
138
  toolHistory: [],
129
139
  isRead: false,
130
- abortController: new AbortController()
140
+ abortController: new AbortController(),
141
+ allowedTools: config.allowedTools
131
142
  };
132
143
  this.subAgents.set(agentId, subAgent);
133
144
  quickLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] [SubAgentManager] Sub-agent ${agentId} added to map, calling notifyCountChange\\n`);
@@ -170,7 +181,11 @@ class SubAgentManagerClass {
170
181
  role: "user",
171
182
  content: systemPrompt
172
183
  });
173
- const tools = this.toolRegistry.getSchemas();
184
+ let tools = this.toolRegistry.getSchemas();
185
+ if (config.allowedTools && config.allowedTools.length > 0) {
186
+ const allowedSet = /* @__PURE__ */ new Set([...config.allowedTools, "task_complete"]);
187
+ tools = tools.filter((t) => allowedSet.has(t.name));
188
+ }
174
189
  const effectiveContextManager = config.parentContextManager || new ContextManager(config.workingDirectory, process.platform);
175
190
  const context = {
176
191
  cwd: config.workingDirectory,
@@ -270,6 +285,28 @@ class SubAgentManagerClass {
270
285
  subAgent.endTime = /* @__PURE__ */ new Date();
271
286
  break;
272
287
  }
288
+ if (subAgent.allowedTools && subAgent.allowedTools.length > 0) {
289
+ const allowedSet = /* @__PURE__ */ new Set([...subAgent.allowedTools, "task_complete"]);
290
+ if (!allowedSet.has(toolCall.name)) {
291
+ quickLog(`[SubAgent ${subAgent.id}] REJECTED tool "${toolCall.name}" \u2014 not in allowed list: ${subAgent.allowedTools.join(", ")}`);
292
+ const rejectionMsg = `Tool "${toolCall.name}" is not allowed for this sub-agent. Allowed tools: ${subAgent.allowedTools.join(", ")}, task_complete. Please use only allowed tools.`;
293
+ const rejectedExecution = {
294
+ toolName: toolCall.name,
295
+ arguments: toolCall.arguments,
296
+ reasonText: toolCall.arguments?.reason_text,
297
+ result: rejectionMsg,
298
+ success: false,
299
+ timestamp: /* @__PURE__ */ new Date()
300
+ };
301
+ subAgent.toolHistory.push(rejectedExecution);
302
+ subAgent.conversationHistory.push({
303
+ role: "tool",
304
+ content: `ERROR: ${rejectionMsg}`,
305
+ tool_call_id: toolCall.id
306
+ });
307
+ continue;
308
+ }
309
+ }
273
310
  const reasonText = toolCall.arguments?.reason_text;
274
311
  const toolExecution = {
275
312
  toolName: toolCall.name,
@@ -314,6 +351,10 @@ class SubAgentManagerClass {
314
351
  } finally {
315
352
  clearTimeout(timeoutId);
316
353
  this.notifyCountChange();
354
+ if (subAgent.status !== "running") {
355
+ subAgent.conversationHistory = [];
356
+ }
357
+ this.cleanup();
317
358
  }
318
359
  quickLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] [SubAgent] ${agentId} finished with status: ${subAgent.status}
319
360
  `);
@@ -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":[]}