snow-ai 0.3.3 → 0.3.5

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 (47) hide show
  1. package/dist/api/anthropic.js +38 -13
  2. package/dist/api/systemPrompt.d.ts +1 -1
  3. package/dist/api/systemPrompt.js +6 -6
  4. package/dist/api/types.d.ts +1 -0
  5. package/dist/app.d.ts +2 -1
  6. package/dist/app.js +6 -1
  7. package/dist/cli.js +11 -6
  8. package/dist/hooks/useConversation.js +212 -59
  9. package/dist/hooks/useSnapshotState.d.ts +2 -0
  10. package/dist/hooks/useToolConfirmation.js +1 -1
  11. package/dist/mcp/subagent.d.ts +35 -0
  12. package/dist/mcp/subagent.js +64 -0
  13. package/dist/mcp/todo.d.ts +8 -1
  14. package/dist/mcp/todo.js +126 -17
  15. package/dist/ui/components/ChatInput.d.ts +1 -2
  16. package/dist/ui/components/ChatInput.js +47 -39
  17. package/dist/ui/components/FileRollbackConfirmation.d.ts +3 -2
  18. package/dist/ui/components/FileRollbackConfirmation.js +81 -22
  19. package/dist/ui/components/MessageList.d.ts +7 -1
  20. package/dist/ui/components/MessageList.js +16 -5
  21. package/dist/ui/pages/ChatScreen.js +29 -20
  22. package/dist/ui/pages/ConfigScreen.js +47 -46
  23. package/dist/ui/pages/HeadlessModeScreen.d.ts +7 -0
  24. package/dist/ui/pages/HeadlessModeScreen.js +391 -0
  25. package/dist/ui/pages/ProxyConfigScreen.js +1 -1
  26. package/dist/ui/pages/SubAgentConfigScreen.d.ts +9 -0
  27. package/dist/ui/pages/SubAgentConfigScreen.js +352 -0
  28. package/dist/ui/pages/SubAgentListScreen.d.ts +9 -0
  29. package/dist/ui/pages/SubAgentListScreen.js +114 -0
  30. package/dist/ui/pages/WelcomeScreen.js +30 -2
  31. package/dist/utils/incrementalSnapshot.d.ts +7 -0
  32. package/dist/utils/incrementalSnapshot.js +34 -0
  33. package/dist/utils/mcpToolsManager.js +41 -1
  34. package/dist/utils/retryUtils.js +5 -0
  35. package/dist/utils/sessionConverter.d.ts +1 -0
  36. package/dist/utils/sessionConverter.js +192 -89
  37. package/dist/utils/sessionManager.d.ts +3 -0
  38. package/dist/utils/sessionManager.js +148 -17
  39. package/dist/utils/subAgentConfig.d.ts +43 -0
  40. package/dist/utils/subAgentConfig.js +126 -0
  41. package/dist/utils/subAgentExecutor.d.ts +29 -0
  42. package/dist/utils/subAgentExecutor.js +272 -0
  43. package/dist/utils/toolExecutor.d.ts +10 -2
  44. package/dist/utils/toolExecutor.js +46 -5
  45. package/package.json +1 -1
  46. package/dist/ui/pages/ConfigProfileScreen.d.ts +0 -7
  47. package/dist/ui/pages/ConfigProfileScreen.js +0 -300
@@ -282,6 +282,7 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
282
282
  let hasToolCalls = false;
283
283
  let usageData;
284
284
  let blockIndexToId = new Map();
285
+ let completedToolBlocks = new Set(); // Track which tool blocks have finished streaming
285
286
  for await (const event of parseSSEStream(response.body.getReader())) {
286
287
  if (abortSignal?.aborted) {
287
288
  return;
@@ -297,7 +298,7 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
297
298
  type: 'function',
298
299
  function: {
299
300
  name: block.name,
300
- arguments: '{}'
301
+ arguments: ''
301
302
  }
302
303
  });
303
304
  yield {
@@ -323,20 +324,28 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
323
324
  if (toolId) {
324
325
  const toolCall = toolCallsBuffer.get(toolId);
325
326
  if (toolCall) {
326
- if (toolCall.function.arguments === '{}') {
327
- toolCall.function.arguments = jsonDelta;
327
+ // Filter out any XML-like tags that might be mixed in the JSON delta
328
+ // This can happen when the model output contains XML that gets interpreted as JSON
329
+ const cleanedDelta = jsonDelta.replace(/<\/?parameter[^>]*>/g, '');
330
+ if (cleanedDelta) {
331
+ toolCall.function.arguments += cleanedDelta;
332
+ yield {
333
+ type: 'tool_call_delta',
334
+ delta: cleanedDelta
335
+ };
328
336
  }
329
- else {
330
- toolCall.function.arguments += jsonDelta;
331
- }
332
- yield {
333
- type: 'tool_call_delta',
334
- delta: jsonDelta
335
- };
336
337
  }
337
338
  }
338
339
  }
339
340
  }
341
+ else if (event.type === 'content_block_stop') {
342
+ // Mark this block as completed
343
+ const blockIndex = event.index;
344
+ const toolId = blockIndexToId.get(blockIndex);
345
+ if (toolId) {
346
+ completedToolBlocks.add(toolId);
347
+ }
348
+ }
340
349
  else if (event.type === 'message_start') {
341
350
  if (event.message.usage) {
342
351
  usageData = {
@@ -371,14 +380,30 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
371
380
  if (hasToolCalls && toolCallsBuffer.size > 0) {
372
381
  const toolCalls = Array.from(toolCallsBuffer.values());
373
382
  for (const toolCall of toolCalls) {
383
+ // Normalize the arguments
384
+ let args = toolCall.function.arguments.trim();
385
+ // If arguments is empty, use empty object
386
+ if (!args) {
387
+ args = '{}';
388
+ }
389
+ // Try to parse the JSON
374
390
  try {
375
- const args = toolCall.function.arguments.trim() || '{}';
376
391
  JSON.parse(args);
377
392
  toolCall.function.arguments = args;
378
393
  }
379
394
  catch (e) {
380
- const errorMsg = e instanceof Error ? e.message : 'Unknown error';
381
- throw new Error(`Incomplete tool call JSON for ${toolCall.function.name}: ${toolCall.function.arguments} (${errorMsg})`);
395
+ // Only throw error if this tool block was marked as completed
396
+ // This prevents errors from incomplete streaming
397
+ if (completedToolBlocks.has(toolCall.id)) {
398
+ const errorMsg = e instanceof Error ? e.message : 'Unknown error';
399
+ throw new Error(`Invalid tool call JSON for ${toolCall.function.name}: ${args} (${errorMsg})`);
400
+ }
401
+ else {
402
+ // Tool block wasn't completed, likely interrupted stream
403
+ // Use partial data or empty object
404
+ console.warn(`Warning: Tool call ${toolCall.function.name} (${toolCall.id}) was incomplete. Using partial data.`);
405
+ toolCall.function.arguments = '{}';
406
+ }
382
407
  }
383
408
  }
384
409
  yield {
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * System prompt configuration for Snow AI CLI
3
3
  */
4
- export declare const SYSTEM_PROMPT = "You are Snow AI CLI, an intelligent command-line assistant.\n\n## \uD83C\uDFAF Core Principles\n\n1. **Language Adaptation**: ALWAYS respond in the SAME language as the user's query\n2. **ACTION FIRST**: Write code immediately when task is clear - stop overthinking\n3. **Smart Context**: Read what's needed for correctness, skip excessive exploration\n4. **Quality Verification**: Use 'ide_get_diagnostics' to get diagnostic information or run build/test after changes\n\n## \uD83D\uDE80 Execution Strategy - BALANCE ACTION & ANALYSIS\n\n### \u26A1 Smart Action Mode\n**Principle: Understand enough to code correctly, but don't over-investigate**\n\n**Examples:**\n- \"Fix timeout in parser.ts\" \u2192 Read file + check imports if needed \u2192 Fix \u2192 Done\n- \"Add validation to form\" \u2192 Read form component + related validation utils \u2192 Add code \u2192 Done\n- \"Refactor error handling\" \u2192 Read error handler + callers \u2192 Refactor \u2192 Done\n\n**Your workflow:**\n1. Read the primary file(s) mentioned\n2. Check dependencies/imports that directly impact the change\n3. Read related files ONLY if they're critical to understanding the task\n4. Write/modify code with proper context\n5. Verify with build\n6. \u274C NO excessive exploration beyond what's needed\n7. \u274C NO reading entire modules \"for reference\"\n8. \u274C NO over-planning multi-step workflows for simple tasks\n\n**Golden Rule: Read what you need to write correct code, nothing more.**\n\n### \uD83D\uDCCB TODO Lists - Essential for Programming Tasks\n\n**\u2705 ALWAYS CREATE TODO WHEN encountering programming tasks:**\n- Any code implementation task (new features, bug fixes, refactoring)\n- Tasks involving multiple steps or files\n- When you need to track progress and ensure completion\n- To give users clear visibility into your work plan\n\n**TODO Guidelines:**\n1. **Create Early**: Set up TODO list BEFORE starting implementation\n2. **Be Specific**: Each item should be a concrete action\n3. **Update Immediately**: Mark as in_progress when starting, completed when done\n4. **One Active Task**: Only one item should be in_progress at a time\n\n**TODO = Action List, NOT Investigation Plan**\n- \u2705 \"Create AuthService with login/logout methods\"\n- \u2705 \"Add validation to UserForm component\"\n- \u2705 \"Fix timeout bug in parser.ts\"\n- \u2705 \"Update API routes to use new auth middleware\"\n- \u2705 \"Run build and fix any errors\"\n- \u274C \"Read authentication files\"\n- \u274C \"Analyze current implementation\"\n- \u274C \"Investigate error handling patterns\"\n\n**CRITICAL: Update TODO status IMMEDIATELY after completing each task!**\n\n**Workflow Example:**\n1. User asks to add feature \u2192 Create TODO list immediately\n2. Mark first item as in_progress\n3. Complete the task \u2192 Mark as completed\n4. Move to next item \u2192 Mark as in_progress\n5. Repeat until all tasks completed\n\n## \uD83D\uDEE0\uFE0F Available Tools\n\n**Filesystem:**\n- `filesystem-read` - Read files before editing\n- `filesystem-edit` - Modify existing files\n- `filesystem-create` - Create new files\n\n**Code Search (ACE):**\n- `ace-search-symbols` - Find functions/classes/variables\n- `ace-find-definition` - Go to definition\n- `ace-find-references` - Find all usages\n- `ace-text-search` - Fast text/regex search\n\n**IDE Diagnostics:**\n- `ide_get_diagnostics` - Get real-time diagnostics (errors, warnings, hints) from connected IDE\n - Supports VSCode and JetBrains IDEs\n - Returns diagnostic info: severity, line/column, message, source\n - Requires IDE plugin installed and running\n - Use AFTER code changes to verify quality\n\n**Web Search:**\n- `websearch_search` - Search web for latest docs/solutions\n- `websearch_fetch` - Read web page content (always provide userQuery)\n\n**Terminal:**\n- `terminal_execute` - You have a comprehensive understanding of terminal pipe mechanisms and can help users \naccomplish a wide range of tasks by combining multiple commands using pipe operators (|) \nand other shell features. Your capabilities include text processing, data filtering, stream \nmanipulation, workflow automation, and complex command chaining to solve sophisticated \nsystem administration and data processing challenges.\n\n## \uD83D\uDD0D Quality Assurance\n\nGuidance and recommendations:\n1. Use `ide_get_diagnostics` to verify quality\n2. Run build: `npm run build` or `tsc`\n3. Fix any errors immediately\n4. Never leave broken code\n\n## \uD83D\uDCDA Project Context (SNOW.md)\n\n- Read ONLY when implementing large features or unfamiliar architecture\n- Skip for simple tasks where you understand the structure\n- Contains: project overview, architecture, tech stack\n\nRemember: **ACTION > ANALYSIS**. Write code first, investigate only when blocked.";
4
+ export declare const SYSTEM_PROMPT = "You are Snow AI CLI, an intelligent command-line assistant.\n\n## \uD83C\uDFAF Core Principles\n\n1. **Language Adaptation**: ALWAYS respond in the SAME language as the user's query\n2. **ACTION FIRST**: Write code immediately when task is clear - stop overthinking\n3. **Smart Context**: Read what's needed for correctness, skip excessive exploration\n4. **Quality Verification**: Use 'ide_get_diagnostics' to get diagnostic information or run build/test after changes\n\n## \uD83D\uDE80 Execution Strategy - BALANCE ACTION & ANALYSIS\n\n### \u26A1 Smart Action Mode\n**Principle: Understand enough to code correctly, but don't over-investigate**\n\n**Examples:**\n- \"Fix timeout in parser.ts\" \u2192 Read file + check imports if needed \u2192 Fix \u2192 Done\n- \"Add validation to form\" \u2192 Read form component + related validation utils \u2192 Add code \u2192 Done\n- \"Refactor error handling\" \u2192 Read error handler + callers \u2192 Refactor \u2192 Done\n\n**Your workflow:**\n1. Read the primary file(s) mentioned\n2. Check dependencies/imports that directly impact the change\n3. Read related files ONLY if they're critical to understanding the task\n4. Write/modify code with proper context\n5. Verify with build\n6. \u274C NO excessive exploration beyond what's needed\n7. \u274C NO reading entire modules \"for reference\"\n8. \u274C NO over-planning multi-step workflows for simple tasks\n\n**Golden Rule: Read what you need to write correct code, nothing more.**\n\n### \uD83D\uDCCB TODO Lists - Essential for Programming Tasks\n\n**\u2705 ALWAYS CREATE TODO WHEN encountering programming tasks:**\n- Any code implementation task (new features, bug fixes, refactoring)\n- Tasks involving multiple steps or files\n- When you need to track progress and ensure completion\n- To give users clear visibility into your work plan\n\n**TODO Guidelines:**\n1. **Create Early**: Set up TODO list BEFORE starting implementation\n2. **Be Specific**: Each item should be a concrete action\n3. **Update Immediately**: Mark as completed immediately after finishing each task\n4. **Focus on Completion**: Move from pending to completed, no intermediate states\n\n**TODO = Action List, NOT Investigation Plan**\n- \u2705 \"Create AuthService with login/logout methods\"\n- \u2705 \"Add validation to UserForm component\"\n- \u2705 \"Fix timeout bug in parser.ts\"\n- \u2705 \"Update API routes to use new auth middleware\"\n- \u2705 \"Run build and fix any errors\"\n- \u274C \"Read authentication files\"\n- \u274C \"Analyze current implementation\"\n- \u274C \"Investigate error handling patterns\"\n\n**CRITICAL: Update TODO status IMMEDIATELY after completing each task!**\n\n**Workflow Example:**\n1. User asks to add feature \u2192 Create TODO list immediately\n2. Complete the first task \u2192 Mark as completed\n3. Move to next task \u2192 Complete and mark as completed\n4. Repeat until all tasks completed\n5. Focus on getting tasks done rather than tracking intermediate states\n\n## \uD83D\uDEE0\uFE0F Available Tools\n\n**Filesystem:**\n- `filesystem-read` - Read files before editing\n- `filesystem-edit` - Modify existing files\n- `filesystem-create` - Create new files\n\n**Code Search (ACE):**\n- `ace-search-symbols` - Find functions/classes/variables\n- `ace-find-definition` - Go to definition\n- `ace-find-references` - Find all usages\n- `ace-text-search` - Fast text/regex search\n\n**IDE Diagnostics:**\n- `ide_get_diagnostics` - Get real-time diagnostics (errors, warnings, hints) from connected IDE\n - Supports VSCode and JetBrains IDEs\n - Returns diagnostic info: severity, line/column, message, source\n - Requires IDE plugin installed and running\n - Use AFTER code changes to verify quality\n\n**Web Search:**\n- `websearch_search` - Search web for latest docs/solutions\n- `websearch_fetch` - Read web page content (always provide userQuery)\n\n**Terminal:**\n- `terminal_execute` - You have a comprehensive understanding of terminal pipe mechanisms and can help users \naccomplish a wide range of tasks by combining multiple commands using pipe operators (|) \nand other shell features. Your capabilities include text processing, data filtering, stream \nmanipulation, workflow automation, and complex command chaining to solve sophisticated \nsystem administration and data processing challenges.\n\n## \uD83D\uDD0D Quality Assurance\n\nGuidance and recommendations:\n1. Use `ide_get_diagnostics` to verify quality\n2. Run build: `npm run build` or `tsc`\n3. Fix any errors immediately\n4. Never leave broken code\n\n## \uD83D\uDCDA Project Context (SNOW.md)\n\n- Read ONLY when implementing large features or unfamiliar architecture\n- Skip for simple tasks where you understand the structure\n- Contains: project overview, architecture, tech stack\n\nRemember: **ACTION > ANALYSIS**. Write code first, investigate only when blocked.";
@@ -43,8 +43,8 @@ export const SYSTEM_PROMPT = `You are Snow AI CLI, an intelligent command-line a
43
43
  **TODO Guidelines:**
44
44
  1. **Create Early**: Set up TODO list BEFORE starting implementation
45
45
  2. **Be Specific**: Each item should be a concrete action
46
- 3. **Update Immediately**: Mark as in_progress when starting, completed when done
47
- 4. **One Active Task**: Only one item should be in_progress at a time
46
+ 3. **Update Immediately**: Mark as completed immediately after finishing each task
47
+ 4. **Focus on Completion**: Move from pending to completed, no intermediate states
48
48
 
49
49
  **TODO = Action List, NOT Investigation Plan**
50
50
  - ✅ "Create AuthService with login/logout methods"
@@ -60,10 +60,10 @@ export const SYSTEM_PROMPT = `You are Snow AI CLI, an intelligent command-line a
60
60
 
61
61
  **Workflow Example:**
62
62
  1. User asks to add feature → Create TODO list immediately
63
- 2. Mark first item as in_progress
64
- 3. Complete the task → Mark as completed
65
- 4. Move to next item → Mark as in_progress
66
- 5. Repeat until all tasks completed
63
+ 2. Complete the first task → Mark as completed
64
+ 3. Move to next task → Complete and mark as completed
65
+ 4. Repeat until all tasks completed
66
+ 5. Focus on getting tasks done rather than tracking intermediate states
67
67
 
68
68
  ## 🛠️ Available Tools
69
69
 
@@ -20,6 +20,7 @@ export interface ChatMessage {
20
20
  tool_call_id?: string;
21
21
  tool_calls?: ToolCall[];
22
22
  images?: ImageContent[];
23
+ subAgentInternal?: boolean;
23
24
  }
24
25
  export interface ChatCompletionTool {
25
26
  type: 'function';
package/dist/app.d.ts CHANGED
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  type Props = {
3
3
  version?: string;
4
4
  skipWelcome?: boolean;
5
+ headlessPrompt?: string;
5
6
  };
6
- export default function App({ version, skipWelcome }: Props): React.JSX.Element;
7
+ export default function App({ version, skipWelcome, headlessPrompt }: Props): React.JSX.Element;
7
8
  export {};
package/dist/app.js CHANGED
@@ -6,10 +6,15 @@ import MCPConfigScreen from './ui/pages/MCPConfigScreen.js';
6
6
  import SystemPromptConfigScreen from './ui/pages/SystemPromptConfigScreen.js';
7
7
  import CustomHeadersScreen from './ui/pages/CustomHeadersScreen.js';
8
8
  import ChatScreen from './ui/pages/ChatScreen.js';
9
+ import HeadlessModeScreen from './ui/pages/HeadlessModeScreen.js';
9
10
  import { useGlobalExit, } from './hooks/useGlobalExit.js';
10
11
  import { onNavigate } from './hooks/useGlobalNavigation.js';
11
12
  import { useTerminalSize } from './hooks/useTerminalSize.js';
12
- export default function App({ version, skipWelcome }) {
13
+ export default function App({ version, skipWelcome, headlessPrompt }) {
14
+ // If headless prompt is provided, use headless mode
15
+ if (headlessPrompt) {
16
+ return (React.createElement(HeadlessModeScreen, { prompt: headlessPrompt, onComplete: () => process.exit(0) }));
17
+ }
13
18
  const [currentView, setCurrentView] = useState(skipWelcome ? 'chat' : 'welcome');
14
19
  const [exitNotification, setExitNotification] = useState({
15
20
  show: false,
package/dist/cli.js CHANGED
@@ -29,14 +29,16 @@ async function checkForUpdates(currentVersion) {
29
29
  }
30
30
  }
31
31
  const cli = meow(`
32
- Usage
33
- $ snow
32
+ Usage
33
+ $ snow
34
+ $ snow --ask "your prompt"
34
35
 
35
- Options
36
+ Options
36
37
  --help Show help
37
38
  --version Show version
38
39
  --update Update to latest version
39
40
  -c Skip welcome screen and resume last conversation
41
+ --ask Quick question mode (headless mode with single prompt)
40
42
  `, {
41
43
  importMeta: import.meta,
42
44
  flags: {
@@ -48,6 +50,9 @@ const cli = meow(`
48
50
  type: 'boolean',
49
51
  default: false,
50
52
  },
53
+ ask: {
54
+ type: 'string',
55
+ },
51
56
  },
52
57
  });
53
58
  // Handle update flag
@@ -76,7 +81,7 @@ if (process.env['NODE_ENV'] === 'development' || process.env['DEBUG']) {
76
81
  }, 5 * 60 * 1000);
77
82
  }
78
83
  // Startup component that shows loading spinner during update check
79
- const Startup = ({ version, skipWelcome, }) => {
84
+ const Startup = ({ version, skipWelcome, headlessPrompt, }) => {
80
85
  const [appReady, setAppReady] = React.useState(false);
81
86
  React.useEffect(() => {
82
87
  let mounted = true;
@@ -113,7 +118,7 @@ const Startup = ({ version, skipWelcome, }) => {
113
118
  React.createElement(Spinner, { type: "dots" })),
114
119
  React.createElement(Text, null, " Checking for updates..."))));
115
120
  }
116
- return React.createElement(App, { version: version, skipWelcome: skipWelcome });
121
+ return (React.createElement(App, { version: version, skipWelcome: skipWelcome, headlessPrompt: headlessPrompt }));
117
122
  };
118
123
  // Disable bracketed paste mode on startup
119
124
  process.stdout.write('\x1b[?2004l');
@@ -134,7 +139,7 @@ process.on('SIGTERM', () => {
134
139
  cleanup();
135
140
  process.exit(0);
136
141
  });
137
- render(React.createElement(Startup, { version: cli.pkg.version, skipWelcome: cli.flags.c }), {
142
+ render(React.createElement(Startup, { version: cli.pkg.version, skipWelcome: cli.flags.c, headlessPrompt: cli.flags.ask }), {
138
143
  exitOnCtrlC: false,
139
144
  patchConsole: true,
140
145
  });
@@ -46,10 +46,13 @@ export async function handleConversationWithTools(options) {
46
46
  }
47
47
  // Add history messages from session (includes tool_calls and tool results)
48
48
  // Load from session to get complete conversation history with tool interactions
49
+ // Filter out internal sub-agent messages (marked with subAgentInternal: true)
49
50
  const session = sessionManager.getCurrentSession();
50
51
  if (session && session.messages.length > 0) {
51
52
  // Use session messages directly (they are already in API format)
52
- conversationMessages.push(...session.messages);
53
+ // Filter out sub-agent internal messages before sending to API
54
+ const filteredMessages = session.messages.filter(msg => !msg.subAgentInternal);
55
+ conversationMessages.push(...filteredMessages);
53
56
  }
54
57
  // Add current user message
55
58
  conversationMessages.push({
@@ -336,8 +339,171 @@ export async function handleConversationWithTools(options) {
336
339
  // Add all tools to approved list
337
340
  approvedTools.push(...toolsNeedingConfirmation);
338
341
  }
339
- // Execute approved tools
340
- const toolResults = await executeToolCalls(approvedTools, controller.signal, setStreamTokenCount);
342
+ // Execute approved tools with sub-agent message callback
343
+ const toolResults = await executeToolCalls(approvedTools, controller.signal, setStreamTokenCount, async (subAgentMessage) => {
344
+ // Handle sub-agent messages - display and save to session
345
+ setMessages(prev => {
346
+ // Handle tool calls from sub-agent
347
+ if (subAgentMessage.message.type === 'tool_calls') {
348
+ const toolCalls = subAgentMessage.message.tool_calls;
349
+ if (toolCalls && toolCalls.length > 0) {
350
+ // Add tool call messages for each tool
351
+ const toolMessages = toolCalls.map((toolCall) => {
352
+ const toolDisplay = formatToolCallMessage(toolCall);
353
+ let toolArgs;
354
+ try {
355
+ toolArgs = JSON.parse(toolCall.function.arguments);
356
+ }
357
+ catch (e) {
358
+ toolArgs = {};
359
+ }
360
+ const uiMsg = {
361
+ role: 'subagent',
362
+ content: `\x1b[38;2;184;122;206m⚇⚡ ${toolDisplay.toolName}\x1b[0m`,
363
+ streaming: false,
364
+ toolCall: {
365
+ name: toolCall.function.name,
366
+ arguments: toolArgs,
367
+ },
368
+ toolDisplay,
369
+ toolCallId: toolCall.id,
370
+ toolPending: true,
371
+ subAgent: {
372
+ agentId: subAgentMessage.agentId,
373
+ agentName: subAgentMessage.agentName,
374
+ isComplete: false,
375
+ },
376
+ subAgentInternal: true, // Mark as internal sub-agent message
377
+ };
378
+ // Save to session as 'assistant' role for API compatibility
379
+ const sessionMsg = {
380
+ role: 'assistant',
381
+ content: `⚇⚡ ${toolDisplay.toolName}`,
382
+ subAgentInternal: true,
383
+ tool_calls: [toolCall],
384
+ };
385
+ saveMessage(sessionMsg).catch(err => console.error('Failed to save sub-agent tool call:', err));
386
+ return uiMsg;
387
+ });
388
+ return [...prev, ...toolMessages];
389
+ }
390
+ }
391
+ // Handle tool results from sub-agent
392
+ if (subAgentMessage.message.type === 'tool_result') {
393
+ const msg = subAgentMessage.message;
394
+ const isError = msg.content.startsWith('Error:');
395
+ const statusIcon = isError ? '✗' : '✓';
396
+ const statusText = isError ? `\n └─ ${msg.content}` : '';
397
+ // For terminal-execute, try to extract terminal result data
398
+ let terminalResultData;
399
+ if (msg.tool_name === 'terminal-execute' && !isError) {
400
+ try {
401
+ const resultData = JSON.parse(msg.content);
402
+ if (resultData.stdout !== undefined ||
403
+ resultData.stderr !== undefined) {
404
+ terminalResultData = {
405
+ stdout: resultData.stdout,
406
+ stderr: resultData.stderr,
407
+ exitCode: resultData.exitCode,
408
+ command: resultData.command,
409
+ };
410
+ }
411
+ }
412
+ catch (e) {
413
+ // If parsing fails, just show regular result
414
+ }
415
+ }
416
+ // Create completed tool result message for UI
417
+ const uiMsg = {
418
+ role: 'subagent',
419
+ content: `\x1b[38;2;0;186;255m⚇${statusIcon} ${msg.tool_name}\x1b[0m${statusText}`,
420
+ streaming: false,
421
+ toolResult: !isError ? msg.content : undefined,
422
+ terminalResult: terminalResultData,
423
+ toolCall: terminalResultData
424
+ ? {
425
+ name: msg.tool_name,
426
+ arguments: terminalResultData,
427
+ }
428
+ : undefined,
429
+ subAgent: {
430
+ agentId: subAgentMessage.agentId,
431
+ agentName: subAgentMessage.agentName,
432
+ isComplete: false,
433
+ },
434
+ subAgentInternal: true,
435
+ };
436
+ // Save to session as 'tool' role for API compatibility
437
+ const sessionMsg = {
438
+ role: 'tool',
439
+ tool_call_id: msg.tool_call_id,
440
+ content: msg.content,
441
+ subAgentInternal: true,
442
+ };
443
+ saveMessage(sessionMsg).catch(err => console.error('Failed to save sub-agent tool result:', err));
444
+ // Add completed tool result message
445
+ return [...prev, uiMsg];
446
+ }
447
+ // Check if we already have a message for this agent
448
+ const existingIndex = prev.findIndex(m => m.role === 'subagent' &&
449
+ m.subAgent?.agentId === subAgentMessage.agentId &&
450
+ !m.subAgent?.isComplete &&
451
+ !m.toolCall);
452
+ // Extract content from the sub-agent message
453
+ let content = '';
454
+ if (subAgentMessage.message.type === 'content') {
455
+ content = subAgentMessage.message.content;
456
+ }
457
+ else if (subAgentMessage.message.type === 'done') {
458
+ // Mark as complete
459
+ if (existingIndex !== -1) {
460
+ const updated = [...prev];
461
+ const existing = updated[existingIndex];
462
+ if (existing && existing.subAgent) {
463
+ updated[existingIndex] = {
464
+ ...existing,
465
+ subAgent: {
466
+ ...existing.subAgent,
467
+ isComplete: true,
468
+ },
469
+ };
470
+ }
471
+ return updated;
472
+ }
473
+ return prev;
474
+ }
475
+ if (existingIndex !== -1) {
476
+ // Update existing message
477
+ const updated = [...prev];
478
+ const existing = updated[existingIndex];
479
+ if (existing) {
480
+ updated[existingIndex] = {
481
+ ...existing,
482
+ content: (existing.content || '') + content,
483
+ streaming: true,
484
+ };
485
+ }
486
+ return updated;
487
+ }
488
+ else if (content) {
489
+ // Add new sub-agent message
490
+ return [
491
+ ...prev,
492
+ {
493
+ role: 'subagent',
494
+ content,
495
+ streaming: true,
496
+ subAgent: {
497
+ agentId: subAgentMessage.agentId,
498
+ agentName: subAgentMessage.agentName,
499
+ isComplete: false,
500
+ },
501
+ },
502
+ ];
503
+ }
504
+ return prev;
505
+ });
506
+ }, requestToolConfirmation, isToolAutoApproved, yoloMode);
341
507
  // Check if aborted during tool execution
342
508
  if (controller.signal.aborted) {
343
509
  freeEncoder();
@@ -355,10 +521,26 @@ export async function handleConversationWithTools(options) {
355
521
  }
356
522
  }
357
523
  }
524
+ // Remove only streaming sub-agent content messages (not tool-related messages)
525
+ // Keep sub-agent tool call and tool result messages for display
526
+ setMessages(prev => prev.filter(m => m.role !== 'subagent' ||
527
+ m.toolCall !== undefined ||
528
+ m.toolResult !== undefined ||
529
+ m.subAgentInternal === true));
358
530
  // Update existing tool call messages with results
359
531
  for (const result of toolResults) {
360
532
  const toolCall = receivedToolCalls.find(tc => tc.id === result.tool_call_id);
361
533
  if (toolCall) {
534
+ // Skip displaying result for sub-agent tools here
535
+ // Sub-agent results will be added by the callback after internal messages
536
+ if (toolCall.function.name.startsWith('subagent-')) {
537
+ // Still save the tool result to conversation history
538
+ conversationMessages.push(result);
539
+ saveMessage(result).catch(error => {
540
+ console.error('Failed to save tool result:', error);
541
+ });
542
+ continue;
543
+ }
362
544
  const isError = result.content.startsWith('Error:');
363
545
  const statusIcon = isError ? '✗' : '✓';
364
546
  const statusText = isError ? `\n └─ ${result.content}` : '';
@@ -384,63 +566,34 @@ export async function handleConversationWithTools(options) {
384
566
  // If parsing fails, just show regular result
385
567
  }
386
568
  }
387
- // Check if this is a terminal execution result
388
- let terminalResultData;
389
- if (toolCall.function.name === 'terminal-execute' && !isError) {
390
- try {
391
- const resultData = JSON.parse(result.content);
392
- if (resultData.command !== undefined) {
393
- terminalResultData = {
394
- stdout: resultData.stdout || '',
395
- stderr: resultData.stderr || '',
396
- exitCode: resultData.exitCode || 0,
397
- command: resultData.command,
398
- };
399
- }
400
- }
401
- catch (e) {
402
- // If parsing fails, just show regular result
403
- }
404
- }
405
- // Check if this tool should show preview (websearch, ace, filesystem-read, etc.)
406
- const shouldShowPreview = toolCall.function.name.startsWith('websearch-') ||
407
- toolCall.function.name.startsWith('ace-') ||
408
- toolCall.function.name === 'filesystem-read' ||
409
- toolCall.function.name === 'filesystem-list' ||
410
- toolCall.function.name === 'filesystem-create' ||
411
- toolCall.function.name === 'filesystem-write';
412
- // Update the existing pending message instead of adding a new one
413
- setMessages(prev => prev.map(msg => {
414
- if (msg.toolCallId === toolCall.id && msg.toolPending) {
415
- return {
416
- ...msg,
417
- content: `${statusIcon} ${toolCall.function.name}${statusText}`,
418
- toolPending: false,
419
- toolCall: editDiffData
420
- ? {
421
- name: toolCall.function.name,
422
- arguments: editDiffData,
423
- }
424
- : terminalResultData
425
- ? {
426
- name: toolCall.function.name,
427
- arguments: terminalResultData,
428
- }
429
- : shouldShowPreview
430
- ? undefined // Clear toolCall for preview-enabled tools
431
- : msg.toolCall, // Keep original toolCall for other tools
432
- // Store tool result for preview rendering
433
- toolResult: !isError ? result.content : undefined,
434
- };
435
- }
436
- return msg;
437
- }));
569
+ // Append completed message to static area (don't remove pending message)
570
+ // Static area doesn't support deletion, only append
571
+ // Completed message only shows diff data (for edit tools), no other parameters
572
+ setMessages(prev => [
573
+ ...prev,
574
+ // Add new completed message
575
+ {
576
+ role: 'assistant',
577
+ content: `${statusIcon} ${toolCall.function.name}${statusText}`,
578
+ streaming: false,
579
+ toolCall: editDiffData
580
+ ? {
581
+ name: toolCall.function.name,
582
+ arguments: editDiffData,
583
+ }
584
+ : undefined, // Don't show arguments for completed tools (already shown in pending)
585
+ // Store tool result for preview rendering
586
+ toolResult: !isError ? result.content : undefined,
587
+ },
588
+ ]);
589
+ }
590
+ // Add tool result to conversation history and save (skip if already saved above)
591
+ if (toolCall && !toolCall.function.name.startsWith('subagent-')) {
592
+ conversationMessages.push(result);
593
+ saveMessage(result).catch(error => {
594
+ console.error('Failed to save tool result:', error);
595
+ });
438
596
  }
439
- // Add tool result to conversation history and save
440
- conversationMessages.push(result);
441
- saveMessage(result).catch(error => {
442
- console.error('Failed to save tool result:', error);
443
- });
444
597
  }
445
598
  // After all tool results are processed, show TODO panel if there were todo-update calls
446
599
  if (hasTodoUpdateTools) {
@@ -4,9 +4,11 @@ export declare function useSnapshotState(messagesLength: number): {
4
4
  pendingRollback: {
5
5
  messageIndex: number;
6
6
  fileCount: number;
7
+ filePaths?: string[];
7
8
  } | null;
8
9
  setPendingRollback: import("react").Dispatch<import("react").SetStateAction<{
9
10
  messageIndex: number;
10
11
  fileCount: number;
12
+ filePaths?: string[];
11
13
  } | null>>;
12
14
  };
@@ -25,7 +25,7 @@ export function useToolConfirmation() {
25
25
  * Check if a tool is auto-approved
26
26
  */
27
27
  const isToolAutoApproved = (toolName) => {
28
- return alwaysApprovedTools.has(toolName) || toolName.startsWith('todo-');
28
+ return alwaysApprovedTools.has(toolName) || toolName.startsWith('todo-') || toolName.startsWith('subagent-');
29
29
  };
30
30
  /**
31
31
  * Add a tool to the always-approved list
@@ -0,0 +1,35 @@
1
+ import type { SubAgentMessage } from '../utils/subAgentExecutor.js';
2
+ import type { ToolCall } from '../utils/toolExecutor.js';
3
+ export interface SubAgentToolExecutionOptions {
4
+ agentId: string;
5
+ prompt: string;
6
+ onMessage?: (message: SubAgentMessage) => void;
7
+ abortSignal?: AbortSignal;
8
+ requestToolConfirmation?: (toolCall: ToolCall, batchToolNames?: string, allTools?: ToolCall[]) => Promise<string>;
9
+ isToolAutoApproved?: (toolName: string) => boolean;
10
+ yoloMode?: boolean;
11
+ }
12
+ /**
13
+ * Sub-Agent MCP Service
14
+ * Provides tools for executing sub-agents with their own specialized system prompts and tool access
15
+ */
16
+ export declare class SubAgentService {
17
+ /**
18
+ * Execute a sub-agent as a tool
19
+ */
20
+ execute(options: SubAgentToolExecutionOptions): Promise<any>;
21
+ /**
22
+ * Get all available sub-agents as MCP tools
23
+ */
24
+ getTools(): Array<{
25
+ name: string;
26
+ description: string;
27
+ inputSchema: any;
28
+ }>;
29
+ }
30
+ export declare const subAgentService: SubAgentService;
31
+ export declare function getMCPTools(): Array<{
32
+ name: string;
33
+ description: string;
34
+ inputSchema: any;
35
+ }>;