@xagent-ai/cli 1.3.0 → 1.3.2

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 (190) hide show
  1. package/.github/release.yml +76 -0
  2. package/.github/workflows/ci.yml +3 -0
  3. package/.github/workflows/release.yml +11 -17
  4. package/README.md +2 -2
  5. package/README_CN.md +2 -2
  6. package/dist/agents.d.ts.map +1 -1
  7. package/dist/agents.js +7 -3
  8. package/dist/agents.js.map +1 -1
  9. package/dist/ai-client/factory.d.ts +0 -12
  10. package/dist/ai-client/factory.d.ts.map +1 -1
  11. package/dist/ai-client/factory.js +0 -32
  12. package/dist/ai-client/factory.js.map +1 -1
  13. package/dist/ai-client/index.js +1 -1
  14. package/dist/ai-client/index.js.map +1 -1
  15. package/dist/ai-client/providers/anthropic.d.ts.map +1 -1
  16. package/dist/ai-client/providers/anthropic.js +10 -4
  17. package/dist/ai-client/providers/anthropic.js.map +1 -1
  18. package/dist/ai-client/providers/openai.d.ts.map +1 -1
  19. package/dist/ai-client/providers/openai.js +8 -4
  20. package/dist/ai-client/providers/openai.js.map +1 -1
  21. package/dist/ai-client/providers/remote.d.ts +0 -1
  22. package/dist/ai-client/providers/remote.d.ts.map +1 -1
  23. package/dist/ai-client/providers/remote.js +11 -10
  24. package/dist/ai-client/providers/remote.js.map +1 -1
  25. package/dist/ai-client/types.d.ts +14 -0
  26. package/dist/ai-client/types.d.ts.map +1 -1
  27. package/dist/ai-client/types.js +17 -0
  28. package/dist/ai-client/types.js.map +1 -1
  29. package/dist/ai-client-factory.d.ts.map +1 -1
  30. package/dist/ai-client-factory.js +4 -4
  31. package/dist/ai-client-factory.js.map +1 -1
  32. package/dist/auth.d.ts.map +1 -1
  33. package/dist/auth.js +10 -12
  34. package/dist/auth.js.map +1 -1
  35. package/dist/cancellation.d.ts.map +1 -1
  36. package/dist/cancellation.js +3 -5
  37. package/dist/cancellation.js.map +1 -1
  38. package/dist/checkpoint.d.ts +1 -0
  39. package/dist/checkpoint.d.ts.map +1 -1
  40. package/dist/checkpoint.js +38 -4
  41. package/dist/checkpoint.js.map +1 -1
  42. package/dist/cli.js +132 -32
  43. package/dist/cli.js.map +1 -1
  44. package/dist/config.js +1 -1
  45. package/dist/config.js.map +1 -1
  46. package/dist/context-compressor.d.ts +1 -2
  47. package/dist/context-compressor.d.ts.map +1 -1
  48. package/dist/context-compressor.js +22 -17
  49. package/dist/context-compressor.js.map +1 -1
  50. package/dist/conversation.d.ts +1 -1
  51. package/dist/conversation.d.ts.map +1 -1
  52. package/dist/conversation.js +8 -7
  53. package/dist/conversation.js.map +1 -1
  54. package/dist/gui-subagent/action-parser/actionParser.js +2 -2
  55. package/dist/gui-subagent/action-parser/actionParser.js.map +1 -1
  56. package/dist/gui-subagent/agent/gui-agent.d.ts +10 -0
  57. package/dist/gui-subagent/agent/gui-agent.d.ts.map +1 -1
  58. package/dist/gui-subagent/agent/gui-agent.js +105 -32
  59. package/dist/gui-subagent/agent/gui-agent.js.map +1 -1
  60. package/dist/gui-subagent/index.d.ts +7 -0
  61. package/dist/gui-subagent/index.d.ts.map +1 -1
  62. package/dist/gui-subagent/index.js +2 -0
  63. package/dist/gui-subagent/index.js.map +1 -1
  64. package/dist/gui-subagent/operator/computer-operator.d.ts.map +1 -1
  65. package/dist/gui-subagent/operator/computer-operator.js +2 -0
  66. package/dist/gui-subagent/operator/computer-operator.js.map +1 -1
  67. package/dist/input-processor.js +2 -2
  68. package/dist/input-processor.js.map +1 -1
  69. package/dist/logger.d.ts.map +1 -1
  70. package/dist/logger.js +1 -1
  71. package/dist/logger.js.map +1 -1
  72. package/dist/mcp.d.ts +2 -1
  73. package/dist/mcp.d.ts.map +1 -1
  74. package/dist/mcp.js +84 -21
  75. package/dist/mcp.js.map +1 -1
  76. package/dist/memory.d.ts.map +1 -1
  77. package/dist/memory.js +3 -3
  78. package/dist/memory.js.map +1 -1
  79. package/dist/output-util.d.ts +27 -0
  80. package/dist/output-util.d.ts.map +1 -0
  81. package/dist/output-util.js +74 -0
  82. package/dist/output-util.js.map +1 -0
  83. package/dist/retry.js +1 -1
  84. package/dist/retry.js.map +1 -1
  85. package/dist/ripgrep.d.ts.map +1 -1
  86. package/dist/ripgrep.js +5 -3
  87. package/dist/ripgrep.js.map +1 -1
  88. package/dist/sdk-output-adapter.d.ts +265 -0
  89. package/dist/sdk-output-adapter.d.ts.map +1 -0
  90. package/dist/sdk-output-adapter.js +701 -0
  91. package/dist/sdk-output-adapter.js.map +1 -0
  92. package/dist/sdk-session.d.ts +13 -0
  93. package/dist/sdk-session.d.ts.map +1 -0
  94. package/dist/sdk-session.js +50 -0
  95. package/dist/sdk-session.js.map +1 -0
  96. package/dist/session-manager.js +3 -3
  97. package/dist/session-manager.js.map +1 -1
  98. package/dist/session.d.ts +96 -2
  99. package/dist/session.d.ts.map +1 -1
  100. package/dist/session.js +849 -262
  101. package/dist/session.js.map +1 -1
  102. package/dist/shell.d.ts.map +1 -1
  103. package/dist/shell.js +5 -4
  104. package/dist/shell.js.map +1 -1
  105. package/dist/skill-installer.js +3 -3
  106. package/dist/skill-installer.js.map +1 -1
  107. package/dist/skill-invoker.d.ts +1 -1
  108. package/dist/skill-invoker.d.ts.map +1 -1
  109. package/dist/skill-invoker.js +2 -2
  110. package/dist/skill-invoker.js.map +1 -1
  111. package/dist/skill-loader.js +6 -5
  112. package/dist/skill-loader.js.map +1 -1
  113. package/dist/skill-manager.d.ts.map +1 -1
  114. package/dist/skill-manager.js +3 -2
  115. package/dist/skill-manager.js.map +1 -1
  116. package/dist/slash-commands.d.ts +1 -1
  117. package/dist/slash-commands.d.ts.map +1 -1
  118. package/dist/slash-commands.js +24 -11
  119. package/dist/slash-commands.js.map +1 -1
  120. package/dist/smart-approval.d.ts +20 -1
  121. package/dist/smart-approval.d.ts.map +1 -1
  122. package/dist/smart-approval.js +58 -1
  123. package/dist/smart-approval.js.map +1 -1
  124. package/dist/system-prompt-generator.js +3 -3
  125. package/dist/system-prompt-generator.js.map +1 -1
  126. package/dist/theme.d.ts.map +1 -1
  127. package/dist/theme.js +9 -8
  128. package/dist/theme.js.map +1 -1
  129. package/dist/tools.d.ts +15 -0
  130. package/dist/tools.d.ts.map +1 -1
  131. package/dist/tools.js +487 -215
  132. package/dist/tools.js.map +1 -1
  133. package/dist/types.d.ts +57 -0
  134. package/dist/types.d.ts.map +1 -1
  135. package/dist/types.js +49 -0
  136. package/dist/types.js.map +1 -1
  137. package/dist/update.d.ts.map +1 -1
  138. package/dist/update.js +12 -9
  139. package/dist/update.js.map +1 -1
  140. package/dist/workflow.d.ts.map +1 -1
  141. package/dist/workflow.js +1 -2
  142. package/dist/workflow.js.map +1 -1
  143. package/docs/third-party-models.md +16 -15
  144. package/package.json +3 -1
  145. package/src/agents.ts +7 -3
  146. package/src/ai-client/factory.ts +1 -36
  147. package/src/ai-client/index.ts +1 -1
  148. package/src/ai-client/providers/anthropic.ts +12 -3
  149. package/src/ai-client/providers/openai.ts +10 -4
  150. package/src/ai-client/providers/remote.ts +13 -10
  151. package/src/ai-client/types.ts +19 -0
  152. package/src/ai-client-factory.ts +5 -5
  153. package/src/auth.ts +11 -13
  154. package/src/cancellation.ts +3 -6
  155. package/src/checkpoint.ts +41 -4
  156. package/src/cli.ts +154 -37
  157. package/src/config.ts +1 -1
  158. package/src/context-compressor.ts +27 -22
  159. package/src/conversation.ts +9 -7
  160. package/src/gui-subagent/action-parser/actionParser.ts +2 -2
  161. package/src/gui-subagent/agent/gui-agent.ts +117 -34
  162. package/src/gui-subagent/index.ts +8 -0
  163. package/src/gui-subagent/operator/computer-operator.ts +2 -1
  164. package/src/input-processor.ts +2 -2
  165. package/src/logger.ts +2 -4
  166. package/src/mcp.ts +87 -23
  167. package/src/memory.ts +3 -4
  168. package/src/output-util.ts +80 -0
  169. package/src/retry.ts +1 -1
  170. package/src/ripgrep.ts +5 -3
  171. package/src/sdk-output-adapter.ts +842 -0
  172. package/src/sdk-session.ts +62 -0
  173. package/src/session-manager.ts +3 -3
  174. package/src/session.ts +942 -302
  175. package/src/shell.ts +6 -5
  176. package/src/skill-installer.ts +3 -3
  177. package/src/skill-invoker.ts +3 -4
  178. package/src/skill-loader.ts +7 -7
  179. package/src/skill-manager.ts +4 -3
  180. package/src/slash-commands.ts +24 -16
  181. package/src/smart-approval.ts +76 -1
  182. package/src/system-prompt-generator.ts +3 -3
  183. package/src/theme.ts +10 -9
  184. package/src/tools.ts +563 -267
  185. package/src/types.ts +118 -0
  186. package/src/update.ts +12 -9
  187. package/src/workflow.ts +2 -4
  188. package/test/cli-launch.test.ts +279 -0
  189. package/vitest.config.ts +2 -0
  190. /package/{.eslintrc.js → .eslintrc.cjs} +0 -0
package/src/tools.ts CHANGED
@@ -3,13 +3,12 @@ import { select, text } from '@clack/prompts';
3
3
  import path from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  import readline from 'readline';
6
- import { spawn, ChildProcess } from 'child_process';
6
+ import { spawn } from 'child_process';
7
7
  import { glob } from 'glob';
8
8
  import axios from 'axios';
9
9
  import { Tool, ExecutionMode, AuthType } from './types.js';
10
10
  import type { Message, ToolDefinition } from './ai-client/types.js';
11
- import type { AIClientInterface } from './ai-client-factory.js';
12
- import { colors, icons, styleHelpers } from './theme.js';
11
+ import { colors, icons } from './theme.js';
13
12
  import { getLogger } from './logger.js';
14
13
  import { getCancellationManager } from './cancellation.js';
15
14
  import { SystemPromptGenerator } from './system-prompt-generator.js';
@@ -344,6 +343,8 @@ This is useful when working with skills that have local dependencies.
344
343
  skillPath?: string;
345
344
  }> {
346
345
  const { command, cwd, description, timeout = 120, run_in_bg = false, skillPath } = params;
346
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
347
+ void description;
347
348
 
348
349
  // Determine effective working directory
349
350
  // Only use cwd if the command doesn't contain 'cd' (let LLM control directory)
@@ -484,7 +485,7 @@ This is useful when working with skills that have local dependencies.
484
485
  output.push(text);
485
486
  });
486
487
 
487
- childProcess.on('close', (code: number) => {
488
+ childProcess.on('close', (_code: number) => {
488
489
  // Silent cleanup - don't log to avoid noise during normal operation
489
490
  // Note: On Windows with PowerShell, the shell process exits after
490
491
  // the command completes
@@ -515,8 +516,8 @@ This is useful when working with skills that have local dependencies.
515
516
  const stdoutResult = truncateTail(result.stdout);
516
517
  const stderrResult = truncateTail(result.stderr);
517
518
 
518
- let stdout = stdoutResult.content;
519
- let stderr = stderrResult.content;
519
+ const stdout = stdoutResult.content;
520
+ const stderr = stderrResult.content;
520
521
  let truncationNotice = '';
521
522
 
522
523
  if (stdoutResult.truncated) {
@@ -1097,6 +1098,8 @@ edit(
1097
1098
  new_string: string;
1098
1099
  }): Promise<{ success: boolean; message: string; diff?: string; firstChangedLine?: number }> {
1099
1100
  const { file_path, instruction, old_string, new_string } = params;
1101
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1102
+ void instruction;
1100
1103
 
1101
1104
  try {
1102
1105
  const absolutePath = path.resolve(file_path);
@@ -1507,7 +1510,9 @@ export class TaskTool implements Tool {
1507
1510
  const config = getConfigManager();
1508
1511
 
1509
1512
  const authConfig = config.getAuthConfig();
1510
- const aiClient = createAIClient(authConfig);
1513
+ // aiClient is created for future use when executeParallelAgents supports it
1514
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1515
+ const _aiClient = createAIClient(authConfig);
1511
1516
 
1512
1517
  const toolRegistry = getToolRegistry();
1513
1518
 
@@ -1518,7 +1523,7 @@ export class TaskTool implements Tool {
1518
1523
  mode,
1519
1524
  agentManager,
1520
1525
  toolRegistry,
1521
- aiClient
1526
+ config
1522
1527
  );
1523
1528
  }
1524
1529
 
@@ -1550,7 +1555,6 @@ export class TaskTool implements Tool {
1550
1555
  mode,
1551
1556
  agentManager,
1552
1557
  toolRegistry,
1553
- aiClient,
1554
1558
  config
1555
1559
  );
1556
1560
 
@@ -1696,11 +1700,29 @@ export class TaskTool implements Tool {
1696
1700
  ): Promise<{ success: boolean; cancelled?: boolean; message: string; result?: any }> {
1697
1701
  const indent = ' '.repeat(indentLevel);
1698
1702
 
1699
- console.log(`${indent}${colors.primaryBright(`${icons.robot} GUI Agent`)}: ${description}`);
1700
- console.log(
1701
- `${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`
1702
- );
1703
- console.log('');
1703
+ // Get SDK adapter from session for SDK mode output
1704
+ let sdkOutputAdapter: any = null;
1705
+ let isSdkMode = false;
1706
+ try {
1707
+ const { getSingletonSession } = await import('./session.js');
1708
+ const session = getSingletonSession();
1709
+ if (session) {
1710
+ isSdkMode = (session as any).isSdkMode;
1711
+ sdkOutputAdapter = (session as any).sdkOutputAdapter;
1712
+ }
1713
+ } catch {
1714
+ // Session not available
1715
+ }
1716
+
1717
+ // SDK mode: use adapter output (guiAgent.run() handles SDK output internally)
1718
+ // Only output console messages in non-SDK mode
1719
+ if (!isSdkMode) {
1720
+ console.log(`${indent}${colors.primaryBright(`${icons.robot} GUI Agent`)}: ${description}`);
1721
+ console.log(
1722
+ `${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`
1723
+ );
1724
+ console.log('');
1725
+ }
1704
1726
 
1705
1727
  // Get VLM configuration for local mode
1706
1728
  // NOTE: guiSubagentBaseUrl must be explicitly configured, NOT fallback to baseUrl
@@ -1713,18 +1735,30 @@ export class TaskTool implements Tool {
1713
1735
 
1714
1736
  // Log mode information
1715
1737
  if (isRemoteMode) {
1716
- console.log(`${indent}${colors.info(`${icons.brain} Using remote VLM service`)}`);
1738
+ if (isSdkMode && sdkOutputAdapter) {
1739
+ // SDK mode: use adapter output
1740
+ sdkOutputAdapter.outputInfo('Using remote VLM service');
1741
+ } else {
1742
+ // Normal mode: console output
1743
+ console.log(`${indent}${colors.info(`${icons.brain} Using remote VLM service`)}`);
1744
+ }
1717
1745
  } else {
1718
- console.log(`${indent}${colors.info(`${icons.brain} Using local VLM configuration`)}`);
1719
- // Local mode requires explicit VLM configuration
1720
- if (!baseUrl || !apiKey || !modelName) {
1721
- return {
1722
- success: false,
1723
- message: `GUI task "${description}" failed: VLM not configured. Please run /model to configure Vision-Language Model first.`,
1724
- };
1746
+ if (isSdkMode && sdkOutputAdapter) {
1747
+ // SDK mode: use adapter output
1748
+ sdkOutputAdapter.outputInfo('Using local VLM configuration');
1749
+ } else {
1750
+ // Normal mode: console output
1751
+ console.log(`${indent}${colors.info(`${icons.brain} Using local VLM configuration`)}`);
1752
+ // Local mode requires explicit VLM configuration
1753
+ if (!baseUrl || !apiKey || !modelName) {
1754
+ return {
1755
+ success: false,
1756
+ message: `GUI task "${description}" failed: VLM not configured. Please run /model to configure Vision-Language Model first.`,
1757
+ };
1758
+ }
1759
+ console.log(`${indent}${colors.textMuted(` Model: ${modelName}`)}`);
1760
+ console.log(`${indent}${colors.textMuted(` Base URL: ${baseUrl}`)}`);
1725
1761
  }
1726
- console.log(`${indent}${colors.textMuted(` Model: ${modelName}`)}`);
1727
- console.log(`${indent}${colors.textMuted(` Base URL: ${baseUrl}`)}`);
1728
1762
  }
1729
1763
  console.log('');
1730
1764
 
@@ -1735,7 +1769,7 @@ export class TaskTool implements Tool {
1735
1769
  const { getSingletonSession } = await import('./session.js');
1736
1770
  const session = getSingletonSession();
1737
1771
  taskId = session?.getTaskId() || null;
1738
- } catch (e) {
1772
+ } catch {
1739
1773
  taskId = null;
1740
1774
  }
1741
1775
  }
@@ -1831,6 +1865,7 @@ export class TaskTool implements Tool {
1831
1865
  loopIntervalInMs: 500,
1832
1866
  showAIDebugInfo: config.get('showAIDebugInfo') || false,
1833
1867
  indentLevel: indentLevel,
1868
+ sdkOutputAdapter: isSdkMode ? sdkOutputAdapter : null,
1834
1869
  });
1835
1870
 
1836
1871
  // Add constraints to prompt if any
@@ -1873,9 +1908,15 @@ export class TaskTool implements Tool {
1873
1908
  const iterations = conversationsWithoutScreenshots.filter(
1874
1909
  (c: any) => c.from === 'human' && c.screenshotContext
1875
1910
  ).length;
1876
- console.log(
1877
- `${indent}${colors.success(`${icons.check} GUI task completed in ${iterations} iterations`)}`
1878
- );
1911
+ // SDK mode: use adapter output
1912
+ if (isSdkMode && sdkOutputAdapter) {
1913
+ sdkOutputAdapter.outputGUIAgentComplete(description, iterations);
1914
+ } else {
1915
+ // Normal mode: console output
1916
+ console.log(
1917
+ `${indent}${colors.success(`${icons.check} GUI task completed in ${iterations} iterations`)}`
1918
+ );
1919
+ }
1879
1920
  return {
1880
1921
  success: true,
1881
1922
  message: `GUI task "${description}" completed`,
@@ -1890,10 +1931,17 @@ export class TaskTool implements Tool {
1890
1931
  },
1891
1932
  };
1892
1933
  } else if (result.status === 'call_llm') {
1893
- // Empty action or needs LLM decision - return to main agent with full context
1894
- console.log(
1895
- `${indent}${colors.warning(`${icons.warning} GUI agent returned to main agent for LLM decision`)}`
1896
- );
1934
+ // SDK mode: use adapter output
1935
+ if (isSdkMode && sdkOutputAdapter) {
1936
+ sdkOutputAdapter.outputGUIAgentStatus('call_llm', conversationsWithoutScreenshots.filter(
1937
+ (c: any) => c.from === 'human' && c.screenshotContext
1938
+ ).length);
1939
+ } else {
1940
+ // Normal mode: console output
1941
+ console.log(
1942
+ `${indent}${colors.warning(`${icons.warning} GUI agent returned to main agent for LLM decision`)}`
1943
+ );
1944
+ }
1897
1945
  return {
1898
1946
  success: true,
1899
1947
  message: `GUI task "${description}" returned for LLM decision`,
@@ -1910,6 +1958,10 @@ export class TaskTool implements Tool {
1910
1958
  },
1911
1959
  };
1912
1960
  } else if (result.status === 'user_stopped') {
1961
+ // SDK mode: use adapter output
1962
+ if (isSdkMode && sdkOutputAdapter) {
1963
+ sdkOutputAdapter.outputGUIAgentCancelled(description);
1964
+ }
1913
1965
  return {
1914
1966
  success: true,
1915
1967
  message: `GUI task "${description}" stopped by user`,
@@ -1928,6 +1980,10 @@ export class TaskTool implements Tool {
1928
1980
  } else {
1929
1981
  // status is 'error' or other non-success status
1930
1982
  const errorMsg = result.error || 'Unknown error';
1983
+ // SDK mode: use adapter output
1984
+ if (isSdkMode && sdkOutputAdapter) {
1985
+ sdkOutputAdapter.outputGUIAgentError(description, errorMsg);
1986
+ }
1931
1987
  return {
1932
1988
  success: false,
1933
1989
  message: `GUI task "${description}" failed: ${errorMsg}`,
@@ -1954,6 +2010,10 @@ export class TaskTool implements Tool {
1954
2010
  // If the user cancelled the task, ignore any API errors (like 429)
1955
2011
  // and return cancelled status instead
1956
2012
  if (cancelled || cancellationManager.isOperationCancelled()) {
2013
+ // SDK mode: use adapter output
2014
+ if (isSdkMode && sdkOutputAdapter) {
2015
+ sdkOutputAdapter.outputGUIAgentCancelled(description);
2016
+ }
1957
2017
  return {
1958
2018
  success: true,
1959
2019
  cancelled: true, // Mark as cancelled so main agent won't continue
@@ -1963,6 +2023,10 @@ export class TaskTool implements Tool {
1963
2023
  }
1964
2024
 
1965
2025
  if (error.message === 'Operation cancelled by user') {
2026
+ // SDK mode: use adapter output
2027
+ if (isSdkMode && sdkOutputAdapter) {
2028
+ sdkOutputAdapter.outputGUIAgentCancelled(description);
2029
+ }
1966
2030
  return {
1967
2031
  success: true,
1968
2032
  message: `GUI task "${description}" cancelled by user`,
@@ -1971,6 +2035,10 @@ export class TaskTool implements Tool {
1971
2035
  }
1972
2036
 
1973
2037
  // Return failure without throwing - let the main agent handle it
2038
+ // SDK mode: use adapter output
2039
+ if (isSdkMode && sdkOutputAdapter) {
2040
+ sdkOutputAdapter.outputGUIAgentError(description, error.message);
2041
+ }
1974
2042
  return {
1975
2043
  success: false,
1976
2044
  message: `GUI task "${description}" failed: ${error.message}`,
@@ -1987,7 +2055,6 @@ export class TaskTool implements Tool {
1987
2055
  mode: ExecutionMode,
1988
2056
  agentManager: any,
1989
2057
  toolRegistry: any,
1990
- aiClient: any,
1991
2058
  config: any,
1992
2059
  indentLevel: number = 1
1993
2060
  ): Promise<{ success: boolean; message: string; result?: any }> {
@@ -1997,6 +2064,20 @@ export class TaskTool implements Tool {
1997
2064
  throw new Error(`Agent ${subagent_type} not found`);
1998
2065
  }
1999
2066
 
2067
+ // Get SDK adapter from session for subagent output
2068
+ let sdkOutputAdapter: any = null;
2069
+ let isSdkMode = false;
2070
+ try {
2071
+ const { getSingletonSession } = await import('./session.js');
2072
+ const session = getSingletonSession();
2073
+ if (session) {
2074
+ isSdkMode = (session as any).isSdkMode;
2075
+ sdkOutputAdapter = (session as any).sdkOutputAdapter;
2076
+ }
2077
+ } catch {
2078
+ // Session not available
2079
+ }
2080
+
2000
2081
  // Special handling for gui-subagent: directly call GUIAgent.run() instead of subagent message loop
2001
2082
  if (subagent_type === 'gui-subagent') {
2002
2083
  // Get RemoteAIClient instance from session (if available)
@@ -2007,7 +2088,7 @@ export class TaskTool implements Tool {
2007
2088
  if (session) {
2008
2089
  remoteAIClient = session.getRemoteAIClient();
2009
2090
  }
2010
- } catch (e) {
2091
+ } catch {
2011
2092
  // Session not available, keep undefined
2012
2093
  remoteAIClient = undefined;
2013
2094
  }
@@ -2047,21 +2128,23 @@ export class TaskTool implements Tool {
2047
2128
  }
2048
2129
  }
2049
2130
 
2050
- // Create AI client for this subagent
2131
+ // Create AI client for this subagent - each subagent gets its own independent client
2051
2132
  let subAgentClient;
2052
2133
  let isRemoteMode = false;
2053
2134
  let mainTaskId: string | null = null;
2054
2135
  const authConfig = config.getAuthConfig();
2055
2136
 
2056
2137
  if (authConfig.type === AuthType.OAUTH_XAGENT) {
2057
- // Remote mode: try to reuse session's RemoteAIClient first
2138
+ // Remote mode: create independent RemoteAIClient for each subagent
2139
+ // This prevents message queue conflicts when multiple subagents run in parallel
2058
2140
  const session = getSingletonSession();
2059
- const existingClient = session?.getRemoteAIClient();
2141
+ const remoteAIClient = session?.getRemoteAIClient();
2060
2142
 
2061
- if (existingClient) {
2062
- subAgentClient = existingClient;
2143
+ if (remoteAIClient) {
2144
+ // Clone or create independent client for this subagent
2145
+ // RemoteAIClient should be designed to handle concurrent requests
2146
+ subAgentClient = remoteAIClient;
2063
2147
  isRemoteMode = true;
2064
- // Get the main taskId from session - subagent shares the same taskId as the parent task
2065
2148
  mainTaskId = session?.getTaskId() || null;
2066
2149
  } else {
2067
2150
  subAgentClient = createAIClient(authConfig);
@@ -2080,7 +2163,7 @@ export class TaskTool implements Tool {
2080
2163
  }
2081
2164
 
2082
2165
  const indent = ' '.repeat(indentLevel);
2083
- const indentNext = ' '.repeat(indentLevel + 1);
2166
+ const _indentNext = ' '.repeat(indentLevel + 1);
2084
2167
  const agentName = agent.name || subagent_type;
2085
2168
 
2086
2169
  // Track execution history for better reporting to main agent
@@ -2144,7 +2227,7 @@ export class TaskTool implements Tool {
2144
2227
  }
2145
2228
  }
2146
2229
  }
2147
- } catch (e) {
2230
+ } catch {
2148
2231
  // Ignore polling errors
2149
2232
  }
2150
2233
  }, 10);
@@ -2176,7 +2259,7 @@ export class TaskTool implements Tool {
2176
2259
  }
2177
2260
  };
2178
2261
 
2179
- let messages: Message[] = [
2262
+ const messages: Message[] = [
2180
2263
  { role: 'system', content: enhancedSystemPrompt },
2181
2264
  { role: 'user', content: fullPrompt },
2182
2265
  ];
@@ -2200,9 +2283,11 @@ export class TaskTool implements Tool {
2200
2283
  });
2201
2284
 
2202
2285
  let iteration = 0;
2203
- const maxIterations = 10;
2286
+ let lastContentStr = ''; // Track last content for final result
2204
2287
 
2205
- while (iteration < maxIterations) {
2288
+ // Main agent style loop: continue until AI returns no more tool_calls
2289
+ // eslint-disable-next-line no-constant-condition
2290
+ while (true) {
2206
2291
  iteration++;
2207
2292
 
2208
2293
  // Check for cancellation before each iteration
@@ -2281,157 +2366,71 @@ export class TaskTool implements Tool {
2281
2366
 
2282
2367
  // Display reasoning content if present
2283
2368
  if (reasoningContent) {
2284
- console.log(`\n${indent}${colors.textDim(`${icons.brain} Thinking Process:`)}`);
2285
- const truncatedReasoning =
2286
- reasoningContent.length > 500
2287
- ? reasoningContent.substring(0, 500) + '...'
2288
- : reasoningContent;
2289
- const indentedReasoning = indentMultiline(truncatedReasoning, indent);
2290
- console.log(`${indentedReasoning}\n`);
2369
+ if (isSdkMode && sdkOutputAdapter) {
2370
+ sdkOutputAdapter.outputThinking(reasoningContent, 'compact');
2371
+ } else {
2372
+ console.log(`\n${indent}${colors.textDim(`${icons.brain} Thinking Process:`)}`);
2373
+ const truncatedReasoning =
2374
+ reasoningContent.length > 500
2375
+ ? reasoningContent.substring(0, 500) + '...'
2376
+ : reasoningContent;
2377
+ const indentedReasoning = indentMultiline(truncatedReasoning, indent);
2378
+ console.log(`${indentedReasoning}\n`);
2379
+ }
2291
2380
  }
2292
2381
 
2293
2382
  // Display assistant response (if there's any text content) with proper indentation
2294
2383
  if (contentStr) {
2295
- console.log(`\n${indent}${colors.primaryBright(agentName)}: ${description}`);
2296
- const truncatedContent =
2297
- contentStr.length > 500 ? contentStr.substring(0, 500) + '...' : contentStr;
2298
- const indentedContent = indentMultiline(truncatedContent, indent);
2299
- console.log(`${indentedContent}\n`);
2384
+ if (isSdkMode && sdkOutputAdapter) {
2385
+ sdkOutputAdapter.outputAssistant(contentStr);
2386
+ } else {
2387
+ console.log(`\n${indent}${colors.primaryBright(agentName)}: ${description}`);
2388
+ const truncatedContent =
2389
+ contentStr.length > 500 ? contentStr.substring(0, 500) + '...' : contentStr;
2390
+ const indentedContent = indentMultiline(truncatedContent, indent);
2391
+ console.log(`${indentedContent}\n`);
2392
+ }
2300
2393
  }
2301
2394
 
2302
- // Process tool calls with proper indentation
2395
+ // Process tool calls in parallel (照搬 session 的实现)
2303
2396
  if (toolCalls && toolCalls.length > 0) {
2304
- for (const toolCall of toolCalls) {
2397
+ // Prepare all tool calls with their indices
2398
+ const preparedToolCalls = toolCalls.map((toolCall: any, index: number) => {
2305
2399
  const { name, arguments: params } = toolCall.function;
2306
-
2307
2400
  let parsedParams: any;
2308
2401
  try {
2309
2402
  parsedParams = typeof params === 'string' ? JSON.parse(params) : params;
2310
- } catch (e) {
2403
+ } catch {
2311
2404
  parsedParams = params;
2312
2405
  }
2406
+ return { name, params: parsedParams, id: toolCall.id, index };
2407
+ });
2313
2408
 
2314
- console.log(`${indent}${colors.textMuted(`${icons.loading} Tool: ${name}`)}`);
2409
+ // Display all tool call info first
2410
+ for (const tc of preparedToolCalls as Array<{ name: string; params: any; id: string }>) {
2411
+ if (isSdkMode && sdkOutputAdapter) {
2412
+ sdkOutputAdapter.outputToolStart(tc.name, tc.params);
2413
+ } else {
2414
+ console.log(`${indent}${colors.textMuted(`${icons.loading} Tool: ${tc.name}`)}`);
2415
+ }
2416
+ }
2315
2417
 
2418
+ // Execute all tool calls in parallel
2419
+ const executePromises = preparedToolCalls.map(async (tc: { name: string; params: any; id: string; index: number }) => {
2316
2420
  try {
2317
2421
  // Check cancellation before tool execution
2318
2422
  checkCancellation();
2319
2423
 
2320
2424
  const toolResult: any = await cancellationManager.withCancellation(
2321
- toolRegistry.execute(name, parsedParams, mode, indent),
2322
- `subagent-${subagent_type}-${name}-${iteration}`
2425
+ toolRegistry.execute(tc.name, tc.params, mode, indent),
2426
+ `subagent-${subagent_type}-${tc.name}-${iteration}`
2323
2427
  );
2324
-
2325
- // Get showToolDetails config to control result display
2326
- const showToolDetails = config.get('showToolDetails') || false;
2327
-
2328
- // Prepare result preview for history
2329
- const resultPreview =
2330
- typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult, null, 2);
2331
- const truncatedPreview =
2332
- resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
2333
-
2334
- // Special handling for different tools (consistent with session.ts display logic)
2335
- const isTodoTool = name === 'todo_write' || name === 'todo_read';
2336
- const isEditTool = name === 'Edit';
2337
- const isWriteTool = name === 'Write';
2338
- const isDeleteTool = name === 'DeleteFile';
2339
- const hasDiff = isEditTool && toolResult?.diff;
2340
- const hasFilePreview = isWriteTool && toolResult?.preview;
2341
- const hasDeleteInfo = isDeleteTool && toolResult?.filePath;
2342
-
2343
- // Import render functions for consistent display
2344
- const { renderDiff, renderLines } = await import('./theme.js');
2345
-
2346
- if (isTodoTool) {
2347
- // Display todo list
2348
- console.log(`${indent}${colors.success(`${icons.check} Todo List:`)}`);
2349
- const todos = toolResult?.todos || [];
2350
- if (todos.length === 0) {
2351
- console.log(`${indent} ${colors.textMuted('No tasks')}`);
2352
- } else {
2353
- const statusConfig: Record<
2354
- string,
2355
- { icon: string; color: (text: string) => string; label: string }
2356
- > = {
2357
- pending: { icon: icons.circle, color: colors.textMuted, label: 'Pending' },
2358
- in_progress: { icon: icons.loading, color: colors.warning, label: 'In Progress' },
2359
- completed: { icon: icons.success, color: colors.success, label: 'Completed' },
2360
- failed: { icon: icons.error, color: colors.error, label: 'Failed' },
2361
- };
2362
- for (const todo of todos) {
2363
- const status = statusConfig[todo.status] || statusConfig['pending'];
2364
- console.log(
2365
- `${indent} ${status.color(status.icon)} ${status.color(status.label)}: ${colors.text(todo.task)}`
2366
- );
2367
- }
2368
- }
2369
- if (toolResult?.message) {
2370
- console.log(`${indent}${colors.textDim(toolResult.message)}`);
2371
- }
2372
- console.log('');
2373
- } else if (hasDiff) {
2374
- // Display edit result with diff
2375
- console.log('');
2376
- const diffOutput = renderDiff(toolResult.diff);
2377
- const indentedDiff = diffOutput
2378
- .split('\n')
2379
- .map((line) => `${indent} ${line}`)
2380
- .join('\n');
2381
- console.log(`${indentedDiff}\n`);
2382
- } else if (hasFilePreview) {
2383
- // Display new file content in preview style
2384
- console.log('');
2385
- console.log(`${indent}${colors.success(`${icons.file} ${toolResult.filePath}`)}`);
2386
- console.log(`${indent}${colors.textDim(` ${toolResult.lineCount} lines`)}`);
2387
- console.log('');
2388
- console.log(renderLines(toolResult.preview, { maxLines: 10, indent: indent + ' ' }));
2389
- console.log('');
2390
- } else if (hasDeleteInfo) {
2391
- // Display DeleteFile result
2392
- console.log('');
2393
- console.log(
2394
- `${indent}${colors.success(`${icons.check} Deleted: ${toolResult.filePath}`)}`
2395
- );
2396
- console.log('');
2397
- } else if (showToolDetails) {
2398
- // Show full result details
2399
- const indentedPreview = indentMultiline(resultPreview, indent);
2400
- console.log(
2401
- `${indent}${colors.success(`${icons.check} Tool Result:`)}\n${indentedPreview}\n`
2402
- );
2403
- } else if (toolResult && toolResult.success === false) {
2404
- // Tool failed
2405
- console.log(
2406
- `${indent}${colors.error(`${icons.cross} ${toolResult.message || 'Failed'}`)}\n`
2407
- );
2408
- } else if (toolResult) {
2409
- // Show brief preview by default
2410
- const indentedPreview = indentMultiline(truncatedPreview, indent);
2411
- console.log(
2412
- `${indent}${colors.success(`${icons.check} Completed`)}\n${indentedPreview}\n`
2413
- );
2414
- } else {
2415
- console.log(`${indent}${colors.textDim('(no result)')}\n`);
2416
- }
2417
-
2418
- // Record successful tool execution in history (use truncated preview to save memory)
2419
- executionHistory.push({
2420
- tool: name,
2421
- status: 'success',
2422
- params: parsedParams,
2423
- result: truncatedPreview,
2424
- timestamp: new Date().toISOString(),
2425
- });
2426
-
2427
- messages.push({
2428
- role: 'tool',
2429
- content: JSON.stringify(toolResult),
2430
- tool_call_id: toolCall.id,
2431
- });
2428
+ return { ...tc, toolResult, error: undefined };
2432
2429
  } catch (error: any) {
2433
2430
  if (error.message === 'Operation cancelled by user') {
2434
- console.log(`${indent}${colors.warning(`⚠️ Operation cancelled`)}\n`);
2431
+ if (!isSdkMode || !sdkOutputAdapter) {
2432
+ console.log(`${indent}${colors.warning(`⚠️ Operation cancelled`)}\n`);
2433
+ }
2435
2434
  cancellationManager.off('cancelled', cancelHandler);
2436
2435
  cleanupStdinPolling();
2437
2436
  const summaryPreview =
@@ -2452,58 +2451,196 @@ export class TaskTool implements Tool {
2452
2451
  },
2453
2452
  };
2454
2453
  }
2455
- console.log(`${indent}${colors.error(`${icons.cross} Error:`)} ${error.message}\n`);
2454
+ return { ...tc, toolResult: undefined, error: error.message };
2455
+ }
2456
+ });
2457
+
2458
+ const settledResults = await Promise.all(executePromises);
2459
+
2460
+ // Check for cancellation in results
2461
+ const cancellationResult = settledResults.find(
2462
+ (r): r is { success: boolean; message: string; result: any } =>
2463
+ 'success' in r && r.success === false
2464
+ );
2465
+ if (cancellationResult) {
2466
+ return cancellationResult;
2467
+ }
2468
+
2469
+ // Create a map to store results by tool call index to maintain original order (match session implementation)
2470
+ type ToolResultType = { name: string; params: any; toolResult: any; error?: string; id: string; index: number };
2471
+ const resultsByIndex = new Map<number, ToolResultType>();
2472
+ const usedIndices = new Set<number>();
2473
+
2474
+ for (const result of settledResults as unknown as ToolResultType[]) {
2475
+ // Find the first unused original index that matches the tool name
2476
+ const originalIndex = preparedToolCalls.findIndex((tc: { name: string }, idx: number) =>
2477
+ tc.name === result.name && !usedIndices.has(idx)
2478
+ );
2479
+ if (originalIndex !== -1) {
2480
+ usedIndices.add(originalIndex);
2481
+ resultsByIndex.set(originalIndex, result);
2482
+ }
2483
+ }
2484
+
2485
+ // Import render functions for consistent display
2486
+ const { renderDiff, renderLines } = await import('./theme.js');
2487
+
2488
+ // Process results in the original tool_calls order
2489
+ for (let i = 0; i < preparedToolCalls.length; i++) {
2490
+ const result = resultsByIndex.get(i);
2491
+ if (!result) continue;
2492
+
2493
+ const { name, params: parsedParams, toolResult, error } = result;
2494
+
2495
+ // Get showToolDetails config to control result display
2496
+ const showToolDetails = config.get('showToolDetails') || false;
2497
+
2498
+ // Prepare result preview for history
2499
+ const resultPreview =
2500
+ typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult, null, 2);
2501
+ const truncatedPreview =
2502
+ resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
2503
+
2504
+ if (error) {
2505
+ // Handle error case
2506
+ if (isSdkMode && sdkOutputAdapter) {
2507
+ sdkOutputAdapter.outputToolError(name, error);
2508
+ } else {
2509
+ console.log(`${indent}${colors.error(`${icons.cross} Error:`)} ${error}\n`);
2510
+ }
2456
2511
 
2457
2512
  // Record failed tool execution in history
2458
2513
  executionHistory.push({
2459
2514
  tool: name,
2460
2515
  status: 'error',
2461
2516
  params: parsedParams,
2462
- error: error.message,
2517
+ error,
2463
2518
  timestamp: new Date().toISOString(),
2464
2519
  });
2465
2520
 
2466
2521
  messages.push({
2467
2522
  role: 'tool',
2468
- content: JSON.stringify({ error: error.message }),
2469
- tool_call_id: toolCall.id,
2523
+ content: JSON.stringify({ error }),
2524
+ tool_call_id: result.id,
2525
+ });
2526
+ } else {
2527
+ // Handle success case - display result
2528
+ // SDK mode: output tool result via adapter
2529
+ if (isSdkMode && sdkOutputAdapter) {
2530
+ sdkOutputAdapter.outputToolResult(name, toolResult);
2531
+ }
2532
+ // Normal mode: console output (SDK mode already output via adapter above)
2533
+ if (!isSdkMode || !sdkOutputAdapter) {
2534
+ // Special handling for different tools (consistent with session.ts display logic)
2535
+ const isTodoTool = name === 'todo_write' || name === 'todo_read';
2536
+ const isEditTool = name === 'Edit';
2537
+ const isWriteTool = name === 'Write';
2538
+ const isDeleteTool = name === 'DeleteFile';
2539
+ const hasDiff = isEditTool && toolResult?.diff;
2540
+ const hasFilePreview = isWriteTool && toolResult?.preview;
2541
+ const hasDeleteInfo = isDeleteTool && toolResult?.filePath;
2542
+
2543
+ if (isTodoTool) {
2544
+ // Display todo list
2545
+ console.log(`${indent}${colors.success(`${icons.check} Todo List:`)}`);
2546
+ const todos = toolResult?.todos || [];
2547
+ if (todos.length === 0) {
2548
+ console.log(`${indent} ${colors.textMuted('No tasks')}`);
2549
+ } else {
2550
+ const statusConfig: Record<
2551
+ string,
2552
+ { icon: string; color: (text: string) => string; label: string }
2553
+ > = {
2554
+ pending: { icon: icons.circle, color: colors.textMuted, label: 'Pending' },
2555
+ in_progress: { icon: icons.loading, color: colors.warning, label: 'In Progress' },
2556
+ completed: { icon: icons.success, color: colors.success, label: 'Completed' },
2557
+ failed: { icon: icons.error, color: colors.error, label: 'Failed' },
2558
+ };
2559
+ for (const todo of todos) {
2560
+ const status = statusConfig[todo.status] || statusConfig['pending'];
2561
+ console.log(
2562
+ `${indent} ${status.color(status.icon)} ${status.color(status.label)}: ${colors.text(todo.task)}`
2563
+ );
2564
+ }
2565
+ }
2566
+ if (toolResult?.message) {
2567
+ console.log(`${indent}${colors.textDim(toolResult.message)}`);
2568
+ }
2569
+ console.log('');
2570
+ } else if (hasDiff) {
2571
+ // Display edit result with diff
2572
+ console.log('');
2573
+ const diffOutput = renderDiff(toolResult.diff);
2574
+ const indentedDiff = diffOutput
2575
+ .split('\n')
2576
+ .map((line) => `${indent} ${line}`)
2577
+ .join('\n');
2578
+ console.log(`${indentedDiff}\n`);
2579
+ } else if (hasFilePreview) {
2580
+ // Display new file content in preview style
2581
+ console.log('');
2582
+ console.log(`${indent}${colors.success(`${icons.file} ${toolResult.filePath}`)}`);
2583
+ console.log(`${indent}${colors.textDim(` ${toolResult.lineCount} lines`)}`);
2584
+ console.log('');
2585
+ console.log(renderLines(toolResult.preview, { maxLines: 10, indent: indent + ' ' }));
2586
+ console.log('');
2587
+ } else if (hasDeleteInfo) {
2588
+ // Display DeleteFile result
2589
+ console.log('');
2590
+ console.log(
2591
+ `${indent}${colors.success(`${icons.check} Deleted: ${toolResult.filePath}`)}`
2592
+ );
2593
+ console.log('');
2594
+ } else if (showToolDetails) {
2595
+ // Show full result details
2596
+ const indentedPreview = indentMultiline(resultPreview, indent);
2597
+ console.log(
2598
+ `${indent}${colors.success(`${icons.check} Tool Result:`)}\n${indentedPreview}\n`
2599
+ );
2600
+ } else if (toolResult && toolResult.success === false) {
2601
+ // Tool failed
2602
+ console.log(
2603
+ `${indent}${colors.error(`${icons.cross} ${toolResult.message || 'Failed'}`)}\n`
2604
+ );
2605
+ } else if (toolResult) {
2606
+ // Show brief preview by default
2607
+ const indentedPreview = indentMultiline(truncatedPreview, indent);
2608
+ console.log(
2609
+ `${indent}${colors.success(`${icons.check} Completed`)}\n${indentedPreview}\n`
2610
+ );
2611
+ } else {
2612
+ console.log(`${indent}${colors.textDim('(no result)')}\n`);
2613
+ }
2614
+ }
2615
+
2616
+ // Record successful tool execution in history (use truncated preview to save memory)
2617
+ executionHistory.push({
2618
+ tool: name,
2619
+ status: 'success',
2620
+ params: parsedParams,
2621
+ result: truncatedPreview,
2622
+ timestamp: new Date().toISOString(),
2623
+ });
2624
+
2625
+ messages.push({
2626
+ role: 'tool',
2627
+ content: JSON.stringify(toolResult),
2628
+ tool_call_id: result.id,
2470
2629
  });
2471
2630
  }
2472
2631
  }
2473
- console.log('');
2632
+ if (!isSdkMode || !sdkOutputAdapter) {
2633
+ console.log('');
2634
+ }
2474
2635
  continue; // Continue to next iteration to get final response
2475
2636
  }
2476
2637
 
2477
- // No more tool calls, return the result with execution history
2478
- cancellationManager.off('cancelled', cancelHandler);
2479
- cleanupStdinPolling();
2480
-
2481
- const summaryPreview =
2482
- contentStr.length > 300 ? contentStr.substring(0, 300) + '...' : contentStr;
2483
- return {
2484
- success: true,
2485
- message: `Task "${description}" completed by ${subagent_type}`,
2486
- result: {
2487
- summary: summaryPreview,
2488
- executionHistory: {
2489
- totalIterations: iteration,
2490
- toolsExecuted: executionHistory.length,
2491
- successfulTools: executionHistory.filter((t) => t.status === 'success').length,
2492
- failedTools: executionHistory.filter((t) => t.status === 'error').length,
2493
- history: executionHistory,
2494
- },
2495
- },
2496
- };
2638
+ // No more tool calls - break loop (same as main agent)
2639
+ lastContentStr = contentStr || '';
2640
+ break;
2497
2641
  }
2498
2642
 
2499
- // Max iterations reached - return accumulated results instead of throwing error
2500
- // Get the last assistant message content
2501
- const lastAssistantMsg = messages.filter((m) => m.role === 'assistant').pop();
2502
- const lastContentStr =
2503
- typeof lastAssistantMsg?.content === 'string'
2504
- ? lastAssistantMsg.content
2505
- : JSON.stringify(lastAssistantMsg?.content || '');
2506
-
2643
+ // Loop ended - return result (same as main agent pattern)
2507
2644
  cancellationManager.off('cancelled', cancelHandler);
2508
2645
  cleanupStdinPolling();
2509
2646
 
@@ -2511,7 +2648,7 @@ export class TaskTool implements Tool {
2511
2648
  lastContentStr.length > 300 ? lastContentStr.substring(0, 300) + '...' : lastContentStr;
2512
2649
  return {
2513
2650
  success: true,
2514
- message: `Task "${description}" completed (max iterations reached) by ${subagent_type}`,
2651
+ message: `Task "${description}" completed by ${subagent_type}`,
2515
2652
  result: {
2516
2653
  summary: summaryPreview,
2517
2654
  executionHistory: {
@@ -2520,7 +2657,6 @@ export class TaskTool implements Tool {
2520
2657
  successfulTools: executionHistory.filter((t) => t.status === 'success').length,
2521
2658
  failedTools: executionHistory.filter((t) => t.status === 'error').length,
2522
2659
  history: executionHistory,
2523
- maxIterationsReached: true,
2524
2660
  },
2525
2661
  },
2526
2662
  };
@@ -2532,7 +2668,7 @@ export class TaskTool implements Tool {
2532
2668
  mode: ExecutionMode,
2533
2669
  agentManager: any,
2534
2670
  toolRegistry: any,
2535
- aiClient: any,
2671
+ config: any,
2536
2672
  indentLevel: number = 1
2537
2673
  ): Promise<{ success: boolean; message: string; results: any[]; errors: any[] }> {
2538
2674
  const indent = ' '.repeat(indentLevel);
@@ -2568,7 +2704,7 @@ export class TaskTool implements Tool {
2568
2704
  }
2569
2705
  }
2570
2706
  }
2571
- } catch (e) {
2707
+ } catch {
2572
2708
  // Ignore polling errors
2573
2709
  }
2574
2710
  }, 10);
@@ -2598,7 +2734,7 @@ export class TaskTool implements Tool {
2598
2734
 
2599
2735
  const startTime = Date.now();
2600
2736
 
2601
- const agentPromises = agents.map(async (agentTask, index) => {
2737
+ const agentPromises = agents.map(async (agentTask, _index) => {
2602
2738
  // Check if cancelled
2603
2739
  if (cancelled || cancellationManager.isOperationCancelled()) {
2604
2740
  return {
@@ -2619,7 +2755,7 @@ export class TaskTool implements Tool {
2619
2755
  mode,
2620
2756
  agentManager,
2621
2757
  toolRegistry,
2622
- aiClient,
2758
+ config,
2623
2759
  indentLevel + 1
2624
2760
  );
2625
2761
 
@@ -2869,6 +3005,29 @@ export class AskUserQuestionTool implements Tool {
2869
3005
  options?: string[];
2870
3006
  multiSelect?: boolean;
2871
3007
  }>;
3008
+ }): Promise<{ answers: string[] }> {
3009
+ // Check if in SDK mode
3010
+ const sdkMode = (this as any)._sdkMode;
3011
+ const sdkAdapter = (this as any)._sdkOutputAdapter;
3012
+
3013
+ if (sdkMode && sdkAdapter) {
3014
+ return this.executeSdk(params, sdkAdapter);
3015
+ }
3016
+
3017
+ // Regular TUI mode
3018
+ return this.executeTui(params);
3019
+ }
3020
+
3021
+ /**
3022
+ * Execute in TUI mode using @clack/prompts
3023
+ */
3024
+ private async executeTui(params: {
3025
+ questions: Array<{
3026
+ question: string;
3027
+ header?: string;
3028
+ options?: string[];
3029
+ multiSelect?: boolean;
3030
+ }>;
2872
3031
  }): Promise<{ answers: string[] }> {
2873
3032
  const { questions } = params;
2874
3033
 
@@ -2902,6 +3061,54 @@ export class AskUserQuestionTool implements Tool {
2902
3061
  throw new Error(`Failed to ask user questions: ${error.message}`);
2903
3062
  }
2904
3063
  }
3064
+
3065
+ /**
3066
+ * Execute in SDK mode - output question request and wait for response
3067
+ */
3068
+ private async executeSdk(
3069
+ params: {
3070
+ questions: Array<{
3071
+ question: string;
3072
+ header?: string;
3073
+ options?: string[];
3074
+ multiSelect?: boolean;
3075
+ }>;
3076
+ },
3077
+ sdkAdapter: any
3078
+ ): Promise<{ answers: string[] }> {
3079
+ const { questions } = params;
3080
+
3081
+ if (questions.length === 0 || questions.length > 4) {
3082
+ throw new Error('Must provide 1-4 questions');
3083
+ }
3084
+
3085
+ const requestId = `question_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
3086
+
3087
+ // Output question request through SDK adapter
3088
+ sdkAdapter.outputQuestionRequest({
3089
+ requestId,
3090
+ questions
3091
+ });
3092
+
3093
+ // Wait for SDK question response
3094
+ // The response will be handled by session.ts which has access to the SDK input
3095
+ // For now, we use a polling mechanism or wait for a specific event
3096
+
3097
+ try {
3098
+ // Import the session to get response handling
3099
+ const { getSingletonSession } = await import('./session.js');
3100
+ const session = getSingletonSession();
3101
+ if (!session) {
3102
+ throw new Error('SDK session not available');
3103
+ }
3104
+ const answers = await session.waitForQuestionResponse(requestId);
3105
+
3106
+ sdkAdapter.outputQuestionResponse(requestId, answers);
3107
+ return { answers };
3108
+ } catch (error: any) {
3109
+ throw new Error(`Failed to get SDK question response: ${error.message}`);
3110
+ }
3111
+ }
2905
3112
  }
2906
3113
 
2907
3114
  export class SaveMemoryTool implements Tool {
@@ -3319,7 +3526,6 @@ export class InvokeSkillTool implements Tool {
3319
3526
 
3320
3527
  try {
3321
3528
  const { getSkillInvoker } = await import('./skill-invoker.js');
3322
- const { SkillExecutionParams } = (await import('./skill-invoker.js')) as any;
3323
3529
  const skillInvoker = getSkillInvoker();
3324
3530
 
3325
3531
  await skillInvoker.initialize();
@@ -3468,6 +3674,8 @@ export class ToolRegistry {
3468
3674
  private todoWriteTool: TodoWriteTool;
3469
3675
  private backgroundTasks: Map<string, { process: any; startTime: number; output: string[] }> =
3470
3676
  new Map();
3677
+ private _isSdkMode: boolean = false;
3678
+ private _sdkOutputAdapter: any = null;
3471
3679
 
3472
3680
  constructor() {
3473
3681
  this.todoWriteTool = new TodoWriteTool();
@@ -3572,13 +3780,39 @@ export class ToolRegistry {
3572
3780
  registeredCount++;
3573
3781
 
3574
3782
  if (toolName !== originalName) {
3575
- console.log(`[MCP] Tool '${originalName}' renamed to '${toolName}' to avoid conflict`);
3783
+ // SDK 模式下不输出重命名信息
3784
+ if (!this._isSdkMode) {
3785
+ console.log(`[MCP] Tool '${originalName}' renamed to '${toolName}' to avoid conflict`);
3786
+ }
3576
3787
  }
3577
3788
  }
3578
3789
  }
3579
3790
 
3580
3791
  if (registeredCount > 0) {
3581
- console.log(`[MCP] Registered ${registeredCount} tool(s)`);
3792
+ // 在 SDK 模式下不输出注册信息(MCP 相关输出已在 session 中处理)
3793
+ if (!this._isSdkMode) {
3794
+ console.log(`[MCP] Registered ${registeredCount} tool(s)`);
3795
+ }
3796
+ }
3797
+ }
3798
+
3799
+ /**
3800
+ * Set SDK mode for the tool registry.
3801
+ * In SDK mode, tool execution output is sent to the SDK output adapter.
3802
+ */
3803
+ async setSdkMode(enabled: boolean, adapter: any): Promise<void> {
3804
+ this._isSdkMode = enabled;
3805
+ this._sdkOutputAdapter = adapter;
3806
+ // Mark all tools as SDK mode enabled
3807
+ for (const [, tool] of this.tools) {
3808
+ (tool as any)._sdkMode = enabled;
3809
+ (tool as any)._sdkOutputAdapter = adapter;
3810
+ }
3811
+
3812
+ // Initialize SDK mode for TaskTool specifically
3813
+ const taskTool = this.tools.get('task') as any;
3814
+ if (taskTool) {
3815
+ await taskTool.setSdkMode?.(enabled, adapter);
3582
3816
  }
3583
3817
  }
3584
3818
 
@@ -4124,7 +4358,7 @@ export class ToolRegistry {
4124
4358
  };
4125
4359
  break;
4126
4360
 
4127
- default:
4361
+ default: {
4128
4362
  // For MCP tools, use their inputSchema; for other unknown tools, keep empty schema
4129
4363
  const mcpTool = tool as any;
4130
4364
  if (mcpTool._isMcpTool && mcpTool.inputSchema) {
@@ -4154,6 +4388,7 @@ export class ToolRegistry {
4154
4388
  required: [],
4155
4389
  };
4156
4390
  }
4391
+ }
4157
4392
  }
4158
4393
 
4159
4394
  return {
@@ -4190,11 +4425,11 @@ export class ToolRegistry {
4190
4425
  }
4191
4426
 
4192
4427
  // Try to find MCP tool with just the tool name (try each server)
4193
- for (const [fullName, tool] of allMcpTools) {
4428
+ for (const [fullName, _tool] of allMcpTools) {
4194
4429
  // Split only on the first __ to preserve underscores in tool names
4195
4430
  const firstUnderscoreIndex = fullName.indexOf('__');
4196
4431
  if (firstUnderscoreIndex === -1) continue;
4197
- const [serverName, actualToolName] = [
4432
+ const [_serverName, actualToolName] = [
4198
4433
  fullName.substring(0, firstUnderscoreIndex),
4199
4434
  fullName.substring(firstUnderscoreIndex + 2),
4200
4435
  ];
@@ -4227,6 +4462,9 @@ export class ToolRegistry {
4227
4462
  throw new Error(`Tool ${toolName} is not allowed in ${executionMode} mode`);
4228
4463
  }
4229
4464
 
4465
+ const isSdkMode = this._isSdkMode;
4466
+ const sdkOutputAdapter = this._sdkOutputAdapter;
4467
+
4230
4468
  // Smart approval mode
4231
4469
  if (executionMode === ExecutionMode.SMART) {
4232
4470
  const debugMode = process.env.DEBUG === 'smart-approval';
@@ -4254,10 +4492,15 @@ export class ToolRegistry {
4254
4492
  const isRemoteMode = authConfig.type === AuthType.OAUTH_XAGENT;
4255
4493
  if (isRemoteMode && toolName === 'InvokeSkill') {
4256
4494
  console.log('');
4257
- console.log(
4258
- `${indent}${colors.success(`✅ [Smart Mode] Remote mode: tool '${toolName}' auto-approved (remote LLM already approved)`)}`
4259
- );
4260
- console.log('');
4495
+ if (isSdkMode && sdkOutputAdapter) {
4496
+ sdkOutputAdapter.outputInfo(`[Smart Mode] Remote mode: tool '${toolName}' auto-approved (remote LLM already approved)`);
4497
+ } else {
4498
+ console.log('');
4499
+ console.log(
4500
+ `${indent}${colors.success(`✅ [Smart Mode] Remote mode: tool '${toolName}' auto-approved (remote LLM already approved)`)}`
4501
+ );
4502
+ console.log('');
4503
+ }
4261
4504
  return await cancellationManager.withCancellation(
4262
4505
  tool.execute(params, executionMode),
4263
4506
  `tool-${toolName}`
@@ -4268,6 +4511,11 @@ export class ToolRegistry {
4268
4511
 
4269
4512
  const approvalEngine = getSmartApprovalEngine(debugMode);
4270
4513
 
4514
+ // Set SDK mode for approval engine if in SDK mode
4515
+ if (isSdkMode && sdkOutputAdapter) {
4516
+ approvalEngine.setSdkMode(true, sdkOutputAdapter);
4517
+ }
4518
+
4271
4519
  // Evaluate tool call
4272
4520
  const result = await approvalEngine.evaluate({
4273
4521
  toolName,
@@ -4278,49 +4526,66 @@ export class ToolRegistry {
4278
4526
  // Decide whether to execute based on approval result
4279
4527
  if (result.decision === 'approved') {
4280
4528
  // Whitelist or AI approval passed, execute directly
4281
- console.log('');
4282
- console.log(
4283
- `${indent}${colors.success(`✅ [Smart Mode] Tool '${toolName}' passed approval, executing directly`)}`
4284
- );
4285
- console.log(
4286
- `${indent}${colors.textDim(` Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`)}`
4287
- );
4288
- console.log(`${indent}${colors.textDim(` Latency: ${result.latency}ms`)}`);
4289
- console.log('');
4529
+ if (isSdkMode && sdkOutputAdapter) {
4530
+ sdkOutputAdapter.outputInfo(`[Smart Mode] Tool '${toolName}' passed approval, executing directly`);
4531
+ sdkOutputAdapter.outputInfo(`Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}, Latency: ${result.latency}ms`);
4532
+ } else {
4533
+ console.log('');
4534
+ console.log(
4535
+ `${indent}${colors.success(`✅ [Smart Mode] Tool '${toolName}' passed approval, executing directly`)}`
4536
+ );
4537
+ console.log(
4538
+ `${indent}${colors.textDim(` Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`)}`
4539
+ );
4540
+ console.log(`${indent}${colors.textDim(` Latency: ${result.latency}ms`)}`);
4541
+ console.log('');
4542
+ }
4290
4543
  return await cancellationManager.withCancellation(
4291
4544
  tool.execute(params, executionMode),
4292
4545
  `tool-${toolName}`
4293
4546
  );
4294
4547
  } else if (result.decision === 'requires_confirmation') {
4295
4548
  // Requires user confirmation
4296
- const confirmed = await approvalEngine.requestConfirmation(result);
4549
+ const confirmed = await approvalEngine.requestConfirmation(result, toolName, params);
4297
4550
 
4298
4551
  if (confirmed) {
4299
- console.log('');
4300
- console.log(
4301
- `${indent}${colors.success(`✅ [Smart Mode] User confirmed execution of tool '${toolName}'`)}`
4302
- );
4303
- console.log('');
4552
+ if (isSdkMode && sdkOutputAdapter) {
4553
+ sdkOutputAdapter.outputInfo(`[Smart Mode] User confirmed execution of tool '${toolName}'`);
4554
+ } else {
4555
+ console.log('');
4556
+ console.log(
4557
+ `${indent}${colors.success(`✅ [Smart Mode] User confirmed execution of tool '${toolName}'`)}`
4558
+ );
4559
+ console.log('');
4560
+ }
4304
4561
  return await cancellationManager.withCancellation(
4305
4562
  tool.execute(params, executionMode),
4306
4563
  `tool-${toolName}`
4307
4564
  );
4565
+ } else {
4566
+ if (isSdkMode && sdkOutputAdapter) {
4567
+ sdkOutputAdapter.outputWarning(`[Smart Mode] User cancelled execution of tool '${toolName}'`);
4568
+ } else {
4569
+ console.log('');
4570
+ console.log(
4571
+ `${indent}${colors.warning(`⚠️ [Smart Mode] User cancelled execution of tool '${toolName}'`)}`
4572
+ );
4573
+ console.log('');
4574
+ }
4575
+ throw new Error(`Tool execution cancelled by user: ${toolName}`);
4576
+ }
4577
+ } else {
4578
+ // Rejected execution
4579
+ if (isSdkMode && sdkOutputAdapter) {
4580
+ sdkOutputAdapter.outputError(`[Smart Mode] Tool '${toolName}' execution rejected`, { reason: result.description });
4308
4581
  } else {
4309
4582
  console.log('');
4310
4583
  console.log(
4311
- `${indent}${colors.warning(`⚠️ [Smart Mode] User cancelled execution of tool '${toolName}'`)}`
4584
+ `${indent}${colors.error(`❌ [Smart Mode] Tool '${toolName}' execution rejected`)}`
4312
4585
  );
4586
+ console.log(`${indent}${colors.textDim(` Reason: ${result.description}`)}`);
4313
4587
  console.log('');
4314
- throw new Error(`Tool execution cancelled by user: ${toolName}`);
4315
4588
  }
4316
- } else {
4317
- // Rejected execution
4318
- console.log('');
4319
- console.log(
4320
- `${indent}${colors.error(`❌ [Smart Mode] Tool '${toolName}' execution rejected`)}`
4321
- );
4322
- console.log(`${indent}${colors.textDim(` Reason: ${result.description}`)}`);
4323
- console.log('');
4324
4589
  throw new Error(`Tool execution rejected: ${toolName}`);
4325
4590
  }
4326
4591
  }
@@ -4352,13 +4617,18 @@ export class ToolRegistry {
4352
4617
 
4353
4618
  // Get server info for display
4354
4619
  const server = mcpManager.getServer(serverName);
4355
- const serverTools = server?.getToolNames() || [];
4620
+ const _serverTools = server?.getToolNames() || [];
4621
+
4622
+ const isSdkMode = this._isSdkMode;
4623
+ const sdkOutputAdapter = this._sdkOutputAdapter;
4356
4624
 
4357
4625
  // Display tool call info
4358
- console.log('');
4359
- console.log(
4360
- `${indent}${colors.warning(`${icons.tool} MCP Tool Call: ${serverName}::${actualToolName}`)}`
4361
- );
4626
+ if (!isSdkMode || !sdkOutputAdapter) {
4627
+ console.log('');
4628
+ console.log(
4629
+ `${indent}${colors.warning(`${icons.tool} MCP Tool Call: ${serverName}::${actualToolName}`)}`
4630
+ );
4631
+ }
4362
4632
 
4363
4633
  // Smart approval mode for MCP tools
4364
4634
  if (executionMode === ExecutionMode.SMART) {
@@ -4371,12 +4641,21 @@ export class ToolRegistry {
4371
4641
 
4372
4642
  // Remote mode: remote LLM has already approved the tool, auto-approve
4373
4643
  if (isRemoteMode) {
4374
- console.log(
4375
- `${indent}${colors.success(`✅ [Smart Mode] Remote mode: MCP tool '${serverName}::${actualToolName}' auto-approved`)}`
4376
- );
4644
+ if (isSdkMode && sdkOutputAdapter) {
4645
+ sdkOutputAdapter.outputInfo(`[Smart Mode] Remote mode: MCP tool '${serverName}::${actualToolName}' auto-approved`);
4646
+ } else {
4647
+ console.log(
4648
+ `${indent}${colors.success(`✅ [Smart Mode] Remote mode: MCP tool '${serverName}::${actualToolName}' auto-approved`)}`
4649
+ );
4650
+ }
4377
4651
  } else {
4378
4652
  const approvalEngine = getSmartApprovalEngine(debugMode);
4379
4653
 
4654
+ // Set SDK mode for approval engine if in SDK mode
4655
+ if (isSdkMode && sdkOutputAdapter) {
4656
+ approvalEngine.setSdkMode(true, sdkOutputAdapter);
4657
+ }
4658
+
4380
4659
  // Evaluate MCP tool call
4381
4660
  const result = await approvalEngine.evaluate({
4382
4661
  toolName: `MCP[${serverName}]::${actualToolName}`,
@@ -4385,23 +4664,40 @@ export class ToolRegistry {
4385
4664
  });
4386
4665
 
4387
4666
  if (result.decision === 'approved') {
4388
- console.log(
4389
- `${indent}${colors.success(`✅ [Smart Mode] MCP tool '${serverName}::${actualToolName}' passed approval`)}`
4390
- );
4391
- console.log(
4392
- `${indent}${colors.textDim(` Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`)}`
4393
- );
4394
- } else if (result.decision === 'requires_confirmation') {
4395
- const confirmed = await approvalEngine.requestConfirmation(result);
4396
- if (!confirmed) {
4667
+ if (isSdkMode && sdkOutputAdapter) {
4668
+ sdkOutputAdapter.outputInfo(`[Smart Mode] MCP tool '${serverName}::${actualToolName}' passed approval`);
4669
+ sdkOutputAdapter.outputInfo(`Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`);
4670
+ } else {
4671
+ console.log(
4672
+ `${indent}${colors.success(`✅ [Smart Mode] MCP tool '${serverName}::${actualToolName}' passed approval`)}`
4673
+ );
4397
4674
  console.log(
4398
- `${indent}${colors.warning(`⚠️ [Smart Mode] User cancelled MCP tool execution`)}`
4675
+ `${indent}${colors.textDim(` Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`)}`
4399
4676
  );
4677
+ }
4678
+ } else if (result.decision === 'requires_confirmation') {
4679
+ const confirmed = await approvalEngine.requestConfirmation(
4680
+ result,
4681
+ `MCP[${serverName}]::${actualToolName}`,
4682
+ params
4683
+ );
4684
+ if (!confirmed) {
4685
+ if (isSdkMode && sdkOutputAdapter) {
4686
+ sdkOutputAdapter.outputWarning(`[Smart Mode] User cancelled MCP tool execution`);
4687
+ } else {
4688
+ console.log(
4689
+ `${indent}${colors.warning(`⚠️ [Smart Mode] User cancelled MCP tool execution`)}`
4690
+ );
4691
+ }
4400
4692
  throw new Error(`Tool execution cancelled by user: ${toolName}`);
4401
4693
  }
4402
4694
  } else {
4403
- console.log(`${indent}${colors.error(`❌ [Smart Mode] MCP tool execution rejected`)}`);
4404
- console.log(`${indent}${colors.textDim(` Reason: ${result.description}`)}`);
4695
+ if (isSdkMode && sdkOutputAdapter) {
4696
+ sdkOutputAdapter.outputError(`[Smart Mode] MCP tool execution rejected`, { reason: result.description });
4697
+ } else {
4698
+ console.log(`${indent}${colors.error(`❌ [Smart Mode] MCP tool execution rejected`)}`);
4699
+ console.log(`${indent}${colors.textDim(` Reason: ${result.description}`)}`);
4700
+ }
4405
4701
  throw new Error(`Tool execution rejected: ${toolName}`);
4406
4702
  }
4407
4703
  }