snow-ai 0.3.36 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/agents/codebaseIndexAgent.js +1 -0
  2. package/dist/agents/codebaseReviewAgent.d.ts +61 -0
  3. package/dist/agents/codebaseReviewAgent.js +301 -0
  4. package/dist/agents/promptOptimizeAgent.d.ts +54 -0
  5. package/dist/agents/promptOptimizeAgent.js +268 -0
  6. package/dist/api/anthropic.js +1 -0
  7. package/dist/api/chat.js +1 -0
  8. package/dist/api/embedding.js +1 -0
  9. package/dist/api/gemini.js +2 -1
  10. package/dist/api/responses.js +1 -0
  11. package/dist/api/systemPrompt.d.ts +1 -5
  12. package/dist/api/systemPrompt.js +168 -100
  13. package/dist/app.js +14 -6
  14. package/dist/cli.js +1 -1
  15. package/dist/hooks/useCommandPanel.js +48 -46
  16. package/dist/hooks/useConversation.d.ts +2 -1
  17. package/dist/hooks/useConversation.js +116 -30
  18. package/dist/hooks/useGlobalExit.js +4 -2
  19. package/dist/hooks/useStreamingState.d.ts +9 -0
  20. package/dist/hooks/useStreamingState.js +3 -0
  21. package/dist/i18n/I18nContext.d.ts +14 -0
  22. package/dist/i18n/I18nContext.js +24 -0
  23. package/dist/i18n/index.d.ts +3 -0
  24. package/dist/i18n/index.js +2 -0
  25. package/dist/i18n/lang/en.d.ts +2 -0
  26. package/dist/i18n/lang/en.js +483 -0
  27. package/dist/i18n/lang/es.d.ts +2 -0
  28. package/dist/i18n/lang/es.js +483 -0
  29. package/dist/i18n/lang/ja.d.ts +2 -0
  30. package/dist/i18n/lang/ja.js +483 -0
  31. package/dist/i18n/lang/ko.d.ts +2 -0
  32. package/dist/i18n/lang/ko.js +483 -0
  33. package/dist/i18n/lang/zh-TW.d.ts +2 -0
  34. package/dist/i18n/lang/zh-TW.js +483 -0
  35. package/dist/i18n/lang/zh.d.ts +2 -0
  36. package/dist/i18n/lang/zh.js +483 -0
  37. package/dist/i18n/translations.d.ts +2 -0
  38. package/dist/i18n/translations.js +14 -0
  39. package/dist/i18n/types.d.ts +459 -0
  40. package/dist/i18n/types.js +1 -0
  41. package/dist/mcp/aceCodeSearch.d.ts +17 -48
  42. package/dist/mcp/aceCodeSearch.js +24 -56
  43. package/dist/mcp/bash.js +8 -1
  44. package/dist/mcp/codebaseSearch.d.ts +1 -1
  45. package/dist/mcp/codebaseSearch.js +159 -30
  46. package/dist/mcp/filesystem.d.ts +3 -80
  47. package/dist/mcp/filesystem.js +23 -103
  48. package/dist/mcp/subagent.d.ts +2 -1
  49. package/dist/mcp/subagent.js +54 -5
  50. package/dist/ui/components/ChatInput.js +22 -25
  51. package/dist/ui/components/CommandPanel.d.ts +1 -1
  52. package/dist/ui/components/CommandPanel.js +20 -13
  53. package/dist/ui/components/DiffViewer.d.ts +1 -1
  54. package/dist/ui/components/DiffViewer.js +101 -91
  55. package/dist/ui/components/FileList.js +22 -11
  56. package/dist/ui/components/HelpPanel.js +47 -21
  57. package/dist/ui/components/Menu.js +6 -2
  58. package/dist/ui/components/MessageList.d.ts +6 -0
  59. package/dist/ui/components/MessageList.js +1 -1
  60. package/dist/ui/components/ToolConfirmation.d.ts +4 -1
  61. package/dist/ui/components/ToolConfirmation.js +28 -2
  62. package/dist/ui/components/ToolResultPreview.d.ts +2 -1
  63. package/dist/ui/components/ToolResultPreview.js +41 -25
  64. package/dist/ui/pages/ChatScreen.js +177 -56
  65. package/dist/ui/pages/CodeBaseConfigScreen.js +54 -30
  66. package/dist/ui/pages/ConfigScreen.js +138 -98
  67. package/dist/ui/pages/CustomHeadersScreen.js +75 -69
  68. package/dist/ui/pages/LanguageSettingsScreen.d.ts +7 -0
  69. package/dist/ui/pages/LanguageSettingsScreen.js +89 -0
  70. package/dist/ui/pages/ProxyConfigScreen.js +27 -23
  71. package/dist/ui/pages/SensitiveCommandConfigScreen.js +32 -25
  72. package/dist/ui/pages/SubAgentConfigScreen.js +88 -75
  73. package/dist/ui/pages/SystemPromptConfigScreen.js +31 -26
  74. package/dist/ui/pages/WelcomeScreen.js +40 -26
  75. package/dist/utils/apiConfig.d.ts +2 -0
  76. package/dist/utils/codebaseConfig.d.ts +1 -5
  77. package/dist/utils/codebaseConfig.js +2 -10
  78. package/dist/utils/codebaseSearchEvents.d.ts +16 -0
  79. package/dist/utils/codebaseSearchEvents.js +13 -0
  80. package/dist/utils/commands/agent.js +2 -2
  81. package/dist/utils/commands/init.js +1 -1
  82. package/dist/utils/configManager.js +26 -5
  83. package/dist/utils/contextCompressor.js +1 -1
  84. package/dist/utils/languageConfig.d.ts +21 -0
  85. package/dist/utils/languageConfig.js +61 -0
  86. package/dist/utils/mcpToolsManager.js +0 -9
  87. package/dist/utils/notebookManager.js +11 -4
  88. package/dist/utils/sessionConverter.js +13 -3
  89. package/dist/utils/sessionManager.d.ts +1 -0
  90. package/dist/utils/subAgentConfig.d.ts +10 -5
  91. package/dist/utils/subAgentConfig.js +112 -19
  92. package/dist/utils/subAgentExecutor.d.ts +9 -1
  93. package/dist/utils/subAgentExecutor.js +122 -9
  94. package/dist/utils/toolExecutor.d.ts +2 -1
  95. package/dist/utils/toolExecutor.js +1 -2
  96. package/dist/utils/usageLogger.js +18 -3
  97. package/package.json +2 -1
@@ -4,7 +4,8 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, unlink
4
4
  import { loadConfig, saveConfig } from './apiConfig.js';
5
5
  const CONFIG_DIR = join(homedir(), '.snow');
6
6
  const PROFILES_DIR = join(CONFIG_DIR, 'profiles');
7
- const ACTIVE_PROFILE_FILE = join(CONFIG_DIR, 'active-profile.txt');
7
+ const ACTIVE_PROFILE_FILE = join(CONFIG_DIR, 'active-profile.json');
8
+ const LEGACY_ACTIVE_PROFILE_FILE = join(CONFIG_DIR, 'active-profile.txt');
8
9
  const LEGACY_CONFIG_FILE = join(CONFIG_DIR, 'config.json');
9
10
  /**
10
11
  * Ensure the profiles directory exists
@@ -22,12 +23,29 @@ function ensureProfilesDirectory() {
22
23
  */
23
24
  export function getActiveProfileName() {
24
25
  ensureProfilesDirectory();
26
+ // Auto-migrate from legacy .txt format to new .json format
27
+ if (!existsSync(ACTIVE_PROFILE_FILE) &&
28
+ existsSync(LEGACY_ACTIVE_PROFILE_FILE)) {
29
+ try {
30
+ const legacyProfileName = readFileSync(LEGACY_ACTIVE_PROFILE_FILE, 'utf8').trim();
31
+ const profileName = legacyProfileName || 'default';
32
+ // Save in new JSON format
33
+ setActiveProfileName(profileName);
34
+ // Delete old .txt file
35
+ unlinkSync(LEGACY_ACTIVE_PROFILE_FILE);
36
+ return profileName;
37
+ }
38
+ catch {
39
+ // If migration fails, continue with default
40
+ }
41
+ }
25
42
  if (!existsSync(ACTIVE_PROFILE_FILE)) {
26
43
  return 'default';
27
44
  }
28
45
  try {
29
- const profileName = readFileSync(ACTIVE_PROFILE_FILE, 'utf8').trim();
30
- return profileName || 'default';
46
+ const fileContent = readFileSync(ACTIVE_PROFILE_FILE, 'utf8').trim();
47
+ const data = JSON.parse(fileContent);
48
+ return data.activeProfile || 'default';
31
49
  }
32
50
  catch {
33
51
  return 'default';
@@ -39,7 +57,8 @@ export function getActiveProfileName() {
39
57
  function setActiveProfileName(profileName) {
40
58
  ensureProfilesDirectory();
41
59
  try {
42
- writeFileSync(ACTIVE_PROFILE_FILE, profileName, 'utf8');
60
+ const data = { activeProfile: profileName };
61
+ writeFileSync(ACTIVE_PROFILE_FILE, JSON.stringify(data, null, 2), 'utf8');
43
62
  }
44
63
  catch (error) {
45
64
  throw new Error(`Failed to set active profile: ${error}`);
@@ -180,7 +199,9 @@ export function switchProfile(profileName) {
180
199
  export function createProfile(profileName, config) {
181
200
  ensureProfilesDirectory();
182
201
  // Validate profile name
183
- if (!profileName.trim() || profileName.includes('/') || profileName.includes('\\')) {
202
+ if (!profileName.trim() ||
203
+ profileName.includes('/') ||
204
+ profileName.includes('\\')) {
184
205
  throw new Error('Invalid profile name');
185
206
  }
186
207
  const profilePath = getProfilePath(profileName);
@@ -104,7 +104,7 @@ function prepareMessagesForCompression(conversationMessages, customSystemPrompt)
104
104
  if (customSystemPrompt) {
105
105
  // If custom system prompt exists: custom as system, default as first user message
106
106
  messages.push({ role: 'system', content: customSystemPrompt });
107
- messages.push({ role: 'user', content: getSystemPrompt() });
107
+ // messages.push({role: 'user', content: getSystemPrompt()});
108
108
  }
109
109
  else {
110
110
  // No custom system prompt: default as system
@@ -0,0 +1,21 @@
1
+ export type Language = 'en' | 'zh' | 'zh-TW' | 'ja' | 'ko' | 'es';
2
+ interface LanguageConfig {
3
+ language: Language;
4
+ }
5
+ /**
6
+ * Load language configuration from file system
7
+ */
8
+ export declare function loadLanguageConfig(): LanguageConfig;
9
+ /**
10
+ * Save language configuration to file system
11
+ */
12
+ export declare function saveLanguageConfig(config: LanguageConfig): void;
13
+ /**
14
+ * Get current language setting
15
+ */
16
+ export declare function getCurrentLanguage(): Language;
17
+ /**
18
+ * Set language and persist to file system
19
+ */
20
+ export declare function setCurrentLanguage(language: Language): void;
21
+ export {};
@@ -0,0 +1,61 @@
1
+ import { homedir } from 'os';
2
+ import { join } from 'path';
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
4
+ const CONFIG_DIR = join(homedir(), '.snow');
5
+ const LANGUAGE_CONFIG_FILE = join(CONFIG_DIR, 'language.json');
6
+ const DEFAULT_CONFIG = {
7
+ language: 'en',
8
+ };
9
+ function ensureConfigDirectory() {
10
+ if (!existsSync(CONFIG_DIR)) {
11
+ mkdirSync(CONFIG_DIR, { recursive: true });
12
+ }
13
+ }
14
+ /**
15
+ * Load language configuration from file system
16
+ */
17
+ export function loadLanguageConfig() {
18
+ ensureConfigDirectory();
19
+ if (!existsSync(LANGUAGE_CONFIG_FILE)) {
20
+ saveLanguageConfig(DEFAULT_CONFIG);
21
+ return DEFAULT_CONFIG;
22
+ }
23
+ try {
24
+ const configData = readFileSync(LANGUAGE_CONFIG_FILE, 'utf-8');
25
+ const config = JSON.parse(configData);
26
+ return {
27
+ ...DEFAULT_CONFIG,
28
+ ...config,
29
+ };
30
+ }
31
+ catch (error) {
32
+ // If config file is corrupted, return default config
33
+ return DEFAULT_CONFIG;
34
+ }
35
+ }
36
+ /**
37
+ * Save language configuration to file system
38
+ */
39
+ export function saveLanguageConfig(config) {
40
+ ensureConfigDirectory();
41
+ try {
42
+ const configData = JSON.stringify(config, null, 2);
43
+ writeFileSync(LANGUAGE_CONFIG_FILE, configData, 'utf-8');
44
+ }
45
+ catch (error) {
46
+ console.error('Failed to save language config:', error);
47
+ }
48
+ }
49
+ /**
50
+ * Get current language setting
51
+ */
52
+ export function getCurrentLanguage() {
53
+ const config = loadLanguageConfig();
54
+ return config.language;
55
+ }
56
+ /**
57
+ * Set language and persist to file system
58
+ */
59
+ export function setCurrentLanguage(language) {
60
+ saveLanguageConfig({ language });
61
+ }
@@ -703,15 +703,6 @@ export async function executeMCPTool(toolName, args, abortSignal, onTokenUpdate)
703
703
  return await filesystemService.getFileContent(args.filePath, args.startLine, args.endLine);
704
704
  case 'create':
705
705
  return await filesystemService.createFile(args.filePath, args.content, args.createDirectories);
706
- case 'delete':
707
- // Support both filePath (legacy) and filePaths (new) parameters
708
- const pathsToDelete = args.filePaths || args.filePath;
709
- if (!pathsToDelete) {
710
- throw new Error('Missing required parameter: filePath or filePaths');
711
- }
712
- return await filesystemService.deleteFile(pathsToDelete);
713
- case 'list':
714
- return await filesystemService.listFiles(args.dirPath);
715
706
  case 'exists':
716
707
  return await filesystemService.exists(args.filePath);
717
708
  case 'info':
@@ -60,8 +60,13 @@ function normalizePath(filePath) {
60
60
  if (path.isAbsolute(filePath)) {
61
61
  return path.relative(projectRoot, filePath).replace(/\\/g, '/');
62
62
  }
63
- // 已经是相对路径,规范化斜杠
64
- return filePath.replace(/\\/g, '/');
63
+ // 已经是相对路径,规范化斜杠并移除 ./ 前缀
64
+ let normalized = filePath.replace(/\\/g, '/');
65
+ // 移除开头的 ./ 前缀
66
+ if (normalized.startsWith('./')) {
67
+ normalized = normalized.substring(2);
68
+ }
69
+ return normalized;
65
70
  }
66
71
  /**
67
72
  * 添加备忘录
@@ -103,8 +108,10 @@ export function addNotebook(filePath, note) {
103
108
  export function queryNotebook(filePathPattern = '', topN = 10) {
104
109
  const data = readNotebookData();
105
110
  const results = [];
106
- // 规范化搜索模式
107
- const normalizedPattern = filePathPattern.toLowerCase().replace(/\\/g, '/');
111
+ // 规范化搜索模式(移除 ./ 前缀等)
112
+ const normalizedPattern = filePathPattern
113
+ ? normalizePath(filePathPattern).toLowerCase()
114
+ : '';
108
115
  // 遍历所有文件路径
109
116
  for (const [filePath, entries] of Object.entries(data)) {
110
117
  // 如果没有指定模式,或者文件路径包含模式
@@ -36,7 +36,7 @@ export function convertSessionMessagesToUI(sessionMessages) {
36
36
  name: toolCall.function.name,
37
37
  arguments: toolArgs,
38
38
  },
39
- toolDisplay,
39
+ // Don't include toolDisplay for sub-agent tools to avoid showing parameters
40
40
  toolCallId: toolCall.id,
41
41
  toolPending: false,
42
42
  subAgentInternal: true,
@@ -154,8 +154,18 @@ export function convertSessionMessagesToUI(sessionMessages) {
154
154
  // Handle regular tool result messages (non-subagent)
155
155
  if (msg.role === 'tool' && msg.tool_call_id && !msg.subAgentInternal) {
156
156
  const isError = msg.content.startsWith('Error:');
157
- const statusIcon = isError ? '✗' : '✓';
158
- const statusText = isError ? `\n └─ ${msg.content}` : '';
157
+ const isRejectedWithReply = msg.content.includes('Tool execution rejected by user:');
158
+ const statusIcon = isError || isRejectedWithReply ? '✗' : '';
159
+ let statusText = '';
160
+ if (isError) {
161
+ statusText = `\n └─ ${msg.content}`;
162
+ }
163
+ else if (isRejectedWithReply) {
164
+ // Extract rejection reason
165
+ const reason = msg.content.split('Tool execution rejected by user:')[1]?.trim() ||
166
+ '';
167
+ statusText = reason ? `\n └─ Rejection reason: ${reason}` : '';
168
+ }
159
169
  // Find tool name and args from previous assistant message
160
170
  let toolName = 'tool';
161
171
  let toolArgs = {};
@@ -1,6 +1,7 @@
1
1
  import type { ChatMessage as APIChatMessage } from '../api/chat.js';
2
2
  export interface ChatMessage extends APIChatMessage {
3
3
  timestamp: number;
4
+ originalContent?: string;
4
5
  }
5
6
  export interface Session {
6
7
  id: string;
@@ -6,24 +6,29 @@ export interface SubAgent {
6
6
  tools: string[];
7
7
  createdAt: string;
8
8
  updatedAt: string;
9
+ builtin?: boolean;
9
10
  }
10
11
  export interface SubAgentsConfig {
11
12
  agents: SubAgent[];
12
13
  }
13
14
  /**
14
- * Get all sub-agents
15
+ * Get user-configured sub-agents only (exported for MCP tool generation)
16
+ */
17
+ export declare function getUserSubAgents(): SubAgent[];
18
+ /**
19
+ * Get all sub-agents (built-in + user-configured)
15
20
  */
16
21
  export declare function getSubAgents(): SubAgent[];
17
22
  /**
18
- * Get a sub-agent by ID
23
+ * Get a sub-agent by ID (checks both built-in and user-configured)
19
24
  */
20
25
  export declare function getSubAgent(id: string): SubAgent | null;
21
26
  /**
22
- * Create a new sub-agent
27
+ * Create a new sub-agent (user-configured only)
23
28
  */
24
29
  export declare function createSubAgent(name: string, description: string, tools: string[], role?: string): SubAgent;
25
30
  /**
26
- * Update an existing sub-agent
31
+ * Update an existing sub-agent (only user-configured agents can be updated)
27
32
  */
28
33
  export declare function updateSubAgent(id: string, updates: {
29
34
  name?: string;
@@ -32,7 +37,7 @@ export declare function updateSubAgent(id: string, updates: {
32
37
  tools?: string[];
33
38
  }): SubAgent | null;
34
39
  /**
35
- * Delete a sub-agent
40
+ * Delete a sub-agent (only user-configured agents can be deleted)
36
41
  */
37
42
  export declare function deleteSubAgent(id: string): boolean;
38
43
  /**
@@ -3,6 +3,77 @@ import { join } from 'path';
3
3
  import { homedir } from 'os';
4
4
  const CONFIG_DIR = join(homedir(), '.snow');
5
5
  const SUB_AGENTS_CONFIG_FILE = join(CONFIG_DIR, 'sub-agents.json');
6
+ /**
7
+ * Built-in sub-agents (hardcoded, always available)
8
+ */
9
+ const BUILTIN_AGENTS = [
10
+ {
11
+ id: 'agent_explore',
12
+ name: 'Explore Agent',
13
+ description: 'Specialized for quickly exploring and understanding codebases. Excels at searching code, finding definitions, analyzing code structure and dependencies. Read-only operations.',
14
+ role: 'You are a specialized code exploration agent. Your task is to help users understand codebase structure, locate specific code, and analyze dependencies. Use search and analysis tools to explore code, but do not modify any files or execute commands. Focus on code discovery and understanding.',
15
+ tools: [
16
+ 'filesystem-read',
17
+ 'ace-find_definition',
18
+ 'ace-find_references',
19
+ 'ace-semantic_search',
20
+ 'ace-text_search',
21
+ 'ace-file_outline',
22
+ 'codebase-search',
23
+ 'websearch-search',
24
+ 'websearch-fetch',
25
+ ],
26
+ createdAt: '2024-01-01T00:00:00.000Z',
27
+ updatedAt: '2024-01-01T00:00:00.000Z',
28
+ builtin: true,
29
+ },
30
+ {
31
+ id: 'agent_plan',
32
+ name: 'Plan Agent',
33
+ description: 'Specialized for planning complex tasks. Analyzes requirements, explores code, identifies relevant files, and creates detailed implementation plans. Read-only operations.',
34
+ role: 'You are a specialized task planning agent. Your task is to analyze user requirements, explore existing codebase, identify relevant files and dependencies, and then create detailed implementation plans. Use search and analysis tools to gather information, check diagnostics to understand current state, but do not execute actual modifications. Output clear step-by-step plans including files to modify, suggested implementation approaches, and important considerations.',
35
+ tools: [
36
+ 'filesystem-read',
37
+ 'ace-find_definition',
38
+ 'ace-find_references',
39
+ 'ace-semantic_search',
40
+ 'ace-text_search',
41
+ 'ace-file_outline',
42
+ 'ide-get_diagnostics',
43
+ 'codebase-search',
44
+ 'websearch-search',
45
+ 'websearch-fetch',
46
+ ],
47
+ createdAt: '2024-01-01T00:00:00.000Z',
48
+ updatedAt: '2024-01-01T00:00:00.000Z',
49
+ builtin: true,
50
+ },
51
+ {
52
+ id: 'agent_general',
53
+ name: 'General Purpose Agent',
54
+ description: 'General-purpose multi-step task execution agent. Has complete tool access for searching, modifying files, and executing commands. Best for complex tasks requiring actual operations.',
55
+ role: 'You are a general-purpose task execution agent. You can perform various complex multi-step tasks, including searching code, modifying files, executing commands, etc. When given a task, systematically break it down and execute. You have access to all tools and should select appropriate tools as needed to complete tasks efficiently.',
56
+ tools: [
57
+ 'filesystem-read',
58
+ 'filesystem-create',
59
+ 'filesystem-edit',
60
+ 'filesystem-edit_search',
61
+ 'terminal-execute',
62
+ 'ace-find_definition',
63
+ 'ace-find_references',
64
+ 'ace-semantic_search',
65
+ 'ace-text_search',
66
+ 'ace-file_outline',
67
+ 'websearch-search',
68
+ 'websearch-fetch',
69
+ 'ide-get_diagnostics',
70
+ 'codebase-search',
71
+ ],
72
+ createdAt: '2024-01-01T00:00:00.000Z',
73
+ updatedAt: '2024-01-01T00:00:00.000Z',
74
+ builtin: true,
75
+ },
76
+ ];
6
77
  function ensureConfigDirectory() {
7
78
  if (!existsSync(CONFIG_DIR)) {
8
79
  mkdirSync(CONFIG_DIR, { recursive: true });
@@ -12,9 +83,9 @@ function generateId() {
12
83
  return `agent_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
13
84
  }
14
85
  /**
15
- * Get all sub-agents
86
+ * Get user-configured sub-agents only (exported for MCP tool generation)
16
87
  */
17
- export function getSubAgents() {
88
+ export function getUserSubAgents() {
18
89
  try {
19
90
  ensureConfigDirectory();
20
91
  if (!existsSync(SUB_AGENTS_CONFIG_FILE)) {
@@ -30,19 +101,29 @@ export function getSubAgents() {
30
101
  }
31
102
  }
32
103
  /**
33
- * Get a sub-agent by ID
104
+ * Get all sub-agents (built-in + user-configured)
105
+ */
106
+ export function getSubAgents() {
107
+ const userAgents = getUserSubAgents();
108
+ // Return built-in agents first, then user-configured agents
109
+ return [...BUILTIN_AGENTS, ...userAgents];
110
+ }
111
+ /**
112
+ * Get a sub-agent by ID (checks both built-in and user-configured)
34
113
  */
35
114
  export function getSubAgent(id) {
36
115
  const agents = getSubAgents();
37
116
  return agents.find(agent => agent.id === id) || null;
38
117
  }
39
118
  /**
40
- * Save all sub-agents
119
+ * Save user-configured sub-agents only (never saves built-in agents)
41
120
  */
42
121
  function saveSubAgents(agents) {
43
122
  try {
44
123
  ensureConfigDirectory();
45
- const config = { agents };
124
+ // Filter out built-in agents (should never be saved to config)
125
+ const userAgents = agents.filter(agent => !agent.builtin);
126
+ const config = { agents: userAgents };
46
127
  const configData = JSON.stringify(config, null, 2);
47
128
  writeFileSync(SUB_AGENTS_CONFIG_FILE, configData, 'utf8');
48
129
  }
@@ -51,10 +132,10 @@ function saveSubAgents(agents) {
51
132
  }
52
133
  }
53
134
  /**
54
- * Create a new sub-agent
135
+ * Create a new sub-agent (user-configured only)
55
136
  */
56
137
  export function createSubAgent(name, description, tools, role) {
57
- const agents = getSubAgents();
138
+ const userAgents = getUserSubAgents();
58
139
  const now = new Date().toISOString();
59
140
  const newAgent = {
60
141
  id: generateId(),
@@ -64,21 +145,27 @@ export function createSubAgent(name, description, tools, role) {
64
145
  tools,
65
146
  createdAt: now,
66
147
  updatedAt: now,
148
+ builtin: false,
67
149
  };
68
- agents.push(newAgent);
69
- saveSubAgents(agents);
150
+ userAgents.push(newAgent);
151
+ saveSubAgents(userAgents);
70
152
  return newAgent;
71
153
  }
72
154
  /**
73
- * Update an existing sub-agent
155
+ * Update an existing sub-agent (only user-configured agents can be updated)
74
156
  */
75
157
  export function updateSubAgent(id, updates) {
76
- const agents = getSubAgents();
77
- const index = agents.findIndex(agent => agent.id === id);
158
+ // Prevent updating built-in agents
159
+ const agent = getSubAgent(id);
160
+ if (agent?.builtin) {
161
+ throw new Error('Cannot update built-in agents');
162
+ }
163
+ const userAgents = getUserSubAgents();
164
+ const index = userAgents.findIndex(agent => agent.id === id);
78
165
  if (index === -1) {
79
166
  return null;
80
167
  }
81
- const existingAgent = agents[index];
168
+ const existingAgent = userAgents[index];
82
169
  if (!existingAgent) {
83
170
  return null;
84
171
  }
@@ -90,18 +177,24 @@ export function updateSubAgent(id, updates) {
90
177
  tools: updates.tools ?? existingAgent.tools,
91
178
  createdAt: existingAgent.createdAt,
92
179
  updatedAt: new Date().toISOString(),
180
+ builtin: false,
93
181
  };
94
- agents[index] = updatedAgent;
95
- saveSubAgents(agents);
182
+ userAgents[index] = updatedAgent;
183
+ saveSubAgents(userAgents);
96
184
  return updatedAgent;
97
185
  }
98
186
  /**
99
- * Delete a sub-agent
187
+ * Delete a sub-agent (only user-configured agents can be deleted)
100
188
  */
101
189
  export function deleteSubAgent(id) {
102
- const agents = getSubAgents();
103
- const filteredAgents = agents.filter(agent => agent.id !== id);
104
- if (filteredAgents.length === agents.length) {
190
+ // Prevent deleting built-in agents
191
+ const agent = getSubAgent(id);
192
+ if (agent?.builtin) {
193
+ throw new Error('Cannot delete built-in agents');
194
+ }
195
+ const userAgents = getUserSubAgents();
196
+ const filteredAgents = userAgents.filter(agent => agent.id !== id);
197
+ if (filteredAgents.length === userAgents.length) {
105
198
  return false; // Agent not found
106
199
  }
107
200
  saveSubAgents(filteredAgents);
@@ -1,16 +1,24 @@
1
+ import type { ConfirmationResult } from '../ui/components/ToolConfirmation.js';
1
2
  export interface SubAgentMessage {
2
3
  type: 'sub_agent_message';
3
4
  agentId: string;
4
5
  agentName: string;
5
6
  message: any;
6
7
  }
8
+ export interface TokenUsage {
9
+ inputTokens: number;
10
+ outputTokens: number;
11
+ cacheCreationInputTokens?: number;
12
+ cacheReadInputTokens?: number;
13
+ }
7
14
  export interface SubAgentResult {
8
15
  success: boolean;
9
16
  result: string;
10
17
  error?: string;
18
+ usage?: TokenUsage;
11
19
  }
12
20
  export interface ToolConfirmationCallback {
13
- (toolName: string, toolArgs: any): Promise<string>;
21
+ (toolName: string, toolArgs: any): Promise<ConfirmationResult>;
14
22
  }
15
23
  export interface ToolApprovalChecker {
16
24
  (toolName: string): boolean;