@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/dist/tools.js CHANGED
@@ -283,6 +283,8 @@ This is useful when working with skills that have local dependencies.
283
283
  allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
284
284
  async execute(params) {
285
285
  const { command, cwd, description, timeout = 120, run_in_bg = false, skillPath } = params;
286
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
287
+ void description;
286
288
  // Determine effective working directory
287
289
  // Only use cwd if the command doesn't contain 'cd' (let LLM control directory)
288
290
  let effectiveCwd;
@@ -406,7 +408,7 @@ This is useful when working with skills that have local dependencies.
406
408
  const text = data.toString();
407
409
  output.push(text);
408
410
  });
409
- childProcess.on('close', (code) => {
411
+ childProcess.on('close', (_code) => {
410
412
  // Silent cleanup - don't log to avoid noise during normal operation
411
413
  // Note: On Windows with PowerShell, the shell process exits after
412
414
  // the command completes
@@ -434,8 +436,8 @@ This is useful when working with skills that have local dependencies.
434
436
  // Apply truncation to stdout and stderr separately
435
437
  const stdoutResult = truncateTail(result.stdout);
436
438
  const stderrResult = truncateTail(result.stderr);
437
- let stdout = stdoutResult.content;
438
- let stderr = stderrResult.content;
439
+ const stdout = stdoutResult.content;
440
+ const stderr = stderrResult.content;
439
441
  let truncationNotice = '';
440
442
  if (stdoutResult.truncated) {
441
443
  truncationNotice += buildTruncationNotice(stdoutResult) + '\n';
@@ -929,6 +931,8 @@ edit(
929
931
  allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
930
932
  async execute(params) {
931
933
  const { file_path, instruction, old_string, new_string } = params;
934
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
935
+ void instruction;
932
936
  try {
933
937
  const absolutePath = path.resolve(file_path);
934
938
  // Check if file exists
@@ -1244,10 +1248,12 @@ export class TaskTool {
1244
1248
  const { getConfigManager } = await import('./config.js');
1245
1249
  const config = getConfigManager();
1246
1250
  const authConfig = config.getAuthConfig();
1247
- const aiClient = createAIClient(authConfig);
1251
+ // aiClient is created for future use when executeParallelAgents supports it
1252
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1253
+ const _aiClient = createAIClient(authConfig);
1248
1254
  const toolRegistry = getToolRegistry();
1249
1255
  if (params.agents && params.agents.length > 0) {
1250
- return await this.executeParallelAgents(params.agents, params.description, mode, agentManager, toolRegistry, aiClient);
1256
+ return await this.executeParallelAgents(params.agents, params.description, mode, agentManager, toolRegistry, config);
1251
1257
  }
1252
1258
  if (!params.subagent_type) {
1253
1259
  throw new Error('subagent_type is required for Task tool');
@@ -1264,7 +1270,7 @@ export class TaskTool {
1264
1270
  agents: params.agents?.length,
1265
1271
  }));
1266
1272
  }
1267
- const result = await this.executeSingleAgent(params.subagent_type, prompt, params.description, params.useContext ?? true, params.constraints || [], mode, agentManager, toolRegistry, aiClient, config);
1273
+ const result = await this.executeSingleAgent(params.subagent_type, prompt, params.description, params.useContext ?? true, params.constraints || [], mode, agentManager, toolRegistry, config);
1268
1274
  return result;
1269
1275
  }
1270
1276
  catch (error) {
@@ -1358,9 +1364,27 @@ export class TaskTool {
1358
1364
  */
1359
1365
  async executeGUIAgent(prompt, description, agent, mode, config, indentLevel = 1, remoteAIClient) {
1360
1366
  const indent = ' '.repeat(indentLevel);
1361
- console.log(`${indent}${colors.primaryBright(`${icons.robot} GUI Agent`)}: ${description}`);
1362
- console.log(`${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`);
1363
- console.log('');
1367
+ // Get SDK adapter from session for SDK mode output
1368
+ let sdkOutputAdapter = null;
1369
+ let isSdkMode = false;
1370
+ try {
1371
+ const { getSingletonSession } = await import('./session.js');
1372
+ const session = getSingletonSession();
1373
+ if (session) {
1374
+ isSdkMode = session.isSdkMode;
1375
+ sdkOutputAdapter = session.sdkOutputAdapter;
1376
+ }
1377
+ }
1378
+ catch {
1379
+ // Session not available
1380
+ }
1381
+ // SDK mode: use adapter output (guiAgent.run() handles SDK output internally)
1382
+ // Only output console messages in non-SDK mode
1383
+ if (!isSdkMode) {
1384
+ console.log(`${indent}${colors.primaryBright(`${icons.robot} GUI Agent`)}: ${description}`);
1385
+ console.log(`${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`);
1386
+ console.log('');
1387
+ }
1364
1388
  // Get VLM configuration for local mode
1365
1389
  // NOTE: guiSubagentBaseUrl must be explicitly configured, NOT fallback to baseUrl
1366
1390
  const baseUrl = config.get('guiSubagentBaseUrl') || '';
@@ -1370,19 +1394,33 @@ export class TaskTool {
1370
1394
  const isRemoteMode = !!remoteAIClient;
1371
1395
  // Log mode information
1372
1396
  if (isRemoteMode) {
1373
- console.log(`${indent}${colors.info(`${icons.brain} Using remote VLM service`)}`);
1397
+ if (isSdkMode && sdkOutputAdapter) {
1398
+ // SDK mode: use adapter output
1399
+ sdkOutputAdapter.outputInfo('Using remote VLM service');
1400
+ }
1401
+ else {
1402
+ // Normal mode: console output
1403
+ console.log(`${indent}${colors.info(`${icons.brain} Using remote VLM service`)}`);
1404
+ }
1374
1405
  }
1375
1406
  else {
1376
- console.log(`${indent}${colors.info(`${icons.brain} Using local VLM configuration`)}`);
1377
- // Local mode requires explicit VLM configuration
1378
- if (!baseUrl || !apiKey || !modelName) {
1379
- return {
1380
- success: false,
1381
- message: `GUI task "${description}" failed: VLM not configured. Please run /model to configure Vision-Language Model first.`,
1382
- };
1407
+ if (isSdkMode && sdkOutputAdapter) {
1408
+ // SDK mode: use adapter output
1409
+ sdkOutputAdapter.outputInfo('Using local VLM configuration');
1410
+ }
1411
+ else {
1412
+ // Normal mode: console output
1413
+ console.log(`${indent}${colors.info(`${icons.brain} Using local VLM configuration`)}`);
1414
+ // Local mode requires explicit VLM configuration
1415
+ if (!baseUrl || !apiKey || !modelName) {
1416
+ return {
1417
+ success: false,
1418
+ message: `GUI task "${description}" failed: VLM not configured. Please run /model to configure Vision-Language Model first.`,
1419
+ };
1420
+ }
1421
+ console.log(`${indent}${colors.textMuted(` Model: ${modelName}`)}`);
1422
+ console.log(`${indent}${colors.textMuted(` Base URL: ${baseUrl}`)}`);
1383
1423
  }
1384
- console.log(`${indent}${colors.textMuted(` Model: ${modelName}`)}`);
1385
- console.log(`${indent}${colors.textMuted(` Base URL: ${baseUrl}`)}`);
1386
1424
  }
1387
1425
  console.log('');
1388
1426
  // Get taskId from session for tracking (remote mode only)
@@ -1393,7 +1431,7 @@ export class TaskTool {
1393
1431
  const session = getSingletonSession();
1394
1432
  taskId = session?.getTaskId() || null;
1395
1433
  }
1396
- catch (e) {
1434
+ catch {
1397
1435
  taskId = null;
1398
1436
  }
1399
1437
  }
@@ -1478,6 +1516,7 @@ export class TaskTool {
1478
1516
  loopIntervalInMs: 500,
1479
1517
  showAIDebugInfo: config.get('showAIDebugInfo') || false,
1480
1518
  indentLevel: indentLevel,
1519
+ sdkOutputAdapter: isSdkMode ? sdkOutputAdapter : null,
1481
1520
  });
1482
1521
  // Add constraints to prompt if any
1483
1522
  const fullPrompt = prompt;
@@ -1510,7 +1549,14 @@ export class TaskTool {
1510
1549
  }));
1511
1550
  if (result.status === 'end') {
1512
1551
  const iterations = conversationsWithoutScreenshots.filter((c) => c.from === 'human' && c.screenshotContext).length;
1513
- console.log(`${indent}${colors.success(`${icons.check} GUI task completed in ${iterations} iterations`)}`);
1552
+ // SDK mode: use adapter output
1553
+ if (isSdkMode && sdkOutputAdapter) {
1554
+ sdkOutputAdapter.outputGUIAgentComplete(description, iterations);
1555
+ }
1556
+ else {
1557
+ // Normal mode: console output
1558
+ console.log(`${indent}${colors.success(`${icons.check} GUI task completed in ${iterations} iterations`)}`);
1559
+ }
1514
1560
  return {
1515
1561
  success: true,
1516
1562
  message: `GUI task "${description}" completed`,
@@ -1526,8 +1572,14 @@ export class TaskTool {
1526
1572
  };
1527
1573
  }
1528
1574
  else if (result.status === 'call_llm') {
1529
- // Empty action or needs LLM decision - return to main agent with full context
1530
- console.log(`${indent}${colors.warning(`${icons.warning} GUI agent returned to main agent for LLM decision`)}`);
1575
+ // SDK mode: use adapter output
1576
+ if (isSdkMode && sdkOutputAdapter) {
1577
+ sdkOutputAdapter.outputGUIAgentStatus('call_llm', conversationsWithoutScreenshots.filter((c) => c.from === 'human' && c.screenshotContext).length);
1578
+ }
1579
+ else {
1580
+ // Normal mode: console output
1581
+ console.log(`${indent}${colors.warning(`${icons.warning} GUI agent returned to main agent for LLM decision`)}`);
1582
+ }
1531
1583
  return {
1532
1584
  success: true,
1533
1585
  message: `GUI task "${description}" returned for LLM decision`,
@@ -1543,6 +1595,10 @@ export class TaskTool {
1543
1595
  };
1544
1596
  }
1545
1597
  else if (result.status === 'user_stopped') {
1598
+ // SDK mode: use adapter output
1599
+ if (isSdkMode && sdkOutputAdapter) {
1600
+ sdkOutputAdapter.outputGUIAgentCancelled(description);
1601
+ }
1546
1602
  return {
1547
1603
  success: true,
1548
1604
  message: `GUI task "${description}" stopped by user`,
@@ -1560,6 +1616,10 @@ export class TaskTool {
1560
1616
  else {
1561
1617
  // status is 'error' or other non-success status
1562
1618
  const errorMsg = result.error || 'Unknown error';
1619
+ // SDK mode: use adapter output
1620
+ if (isSdkMode && sdkOutputAdapter) {
1621
+ sdkOutputAdapter.outputGUIAgentError(description, errorMsg);
1622
+ }
1563
1623
  return {
1564
1624
  success: false,
1565
1625
  message: `GUI task "${description}" failed: ${errorMsg}`,
@@ -1583,6 +1643,10 @@ export class TaskTool {
1583
1643
  // If the user cancelled the task, ignore any API errors (like 429)
1584
1644
  // and return cancelled status instead
1585
1645
  if (cancelled || cancellationManager.isOperationCancelled()) {
1646
+ // SDK mode: use adapter output
1647
+ if (isSdkMode && sdkOutputAdapter) {
1648
+ sdkOutputAdapter.outputGUIAgentCancelled(description);
1649
+ }
1586
1650
  return {
1587
1651
  success: true,
1588
1652
  cancelled: true, // Mark as cancelled so main agent won't continue
@@ -1591,6 +1655,10 @@ export class TaskTool {
1591
1655
  };
1592
1656
  }
1593
1657
  if (error.message === 'Operation cancelled by user') {
1658
+ // SDK mode: use adapter output
1659
+ if (isSdkMode && sdkOutputAdapter) {
1660
+ sdkOutputAdapter.outputGUIAgentCancelled(description);
1661
+ }
1594
1662
  return {
1595
1663
  success: true,
1596
1664
  message: `GUI task "${description}" cancelled by user`,
@@ -1598,17 +1666,35 @@ export class TaskTool {
1598
1666
  };
1599
1667
  }
1600
1668
  // Return failure without throwing - let the main agent handle it
1669
+ // SDK mode: use adapter output
1670
+ if (isSdkMode && sdkOutputAdapter) {
1671
+ sdkOutputAdapter.outputGUIAgentError(description, error.message);
1672
+ }
1601
1673
  return {
1602
1674
  success: false,
1603
1675
  message: `GUI task "${description}" failed: ${error.message}`,
1604
1676
  };
1605
1677
  }
1606
1678
  }
1607
- async executeSingleAgent(subagent_type, prompt, description, useContext, constraints, mode, agentManager, toolRegistry, aiClient, config, indentLevel = 1) {
1679
+ async executeSingleAgent(subagent_type, prompt, description, useContext, constraints, mode, agentManager, toolRegistry, config, indentLevel = 1) {
1608
1680
  const agent = agentManager.getAgent(subagent_type);
1609
1681
  if (!agent) {
1610
1682
  throw new Error(`Agent ${subagent_type} not found`);
1611
1683
  }
1684
+ // Get SDK adapter from session for subagent output
1685
+ let sdkOutputAdapter = null;
1686
+ let isSdkMode = false;
1687
+ try {
1688
+ const { getSingletonSession } = await import('./session.js');
1689
+ const session = getSingletonSession();
1690
+ if (session) {
1691
+ isSdkMode = session.isSdkMode;
1692
+ sdkOutputAdapter = session.sdkOutputAdapter;
1693
+ }
1694
+ }
1695
+ catch {
1696
+ // Session not available
1697
+ }
1612
1698
  // Special handling for gui-subagent: directly call GUIAgent.run() instead of subagent message loop
1613
1699
  if (subagent_type === 'gui-subagent') {
1614
1700
  // Get RemoteAIClient instance from session (if available)
@@ -1620,7 +1706,7 @@ export class TaskTool {
1620
1706
  remoteAIClient = session.getRemoteAIClient();
1621
1707
  }
1622
1708
  }
1623
- catch (e) {
1709
+ catch {
1624
1710
  // Session not available, keep undefined
1625
1711
  remoteAIClient = undefined;
1626
1712
  }
@@ -1649,19 +1735,21 @@ export class TaskTool {
1649
1735
  modelName = agent.model;
1650
1736
  }
1651
1737
  }
1652
- // Create AI client for this subagent
1738
+ // Create AI client for this subagent - each subagent gets its own independent client
1653
1739
  let subAgentClient;
1654
1740
  let isRemoteMode = false;
1655
1741
  let mainTaskId = null;
1656
1742
  const authConfig = config.getAuthConfig();
1657
1743
  if (authConfig.type === AuthType.OAUTH_XAGENT) {
1658
- // Remote mode: try to reuse session's RemoteAIClient first
1744
+ // Remote mode: create independent RemoteAIClient for each subagent
1745
+ // This prevents message queue conflicts when multiple subagents run in parallel
1659
1746
  const session = getSingletonSession();
1660
- const existingClient = session?.getRemoteAIClient();
1661
- if (existingClient) {
1662
- subAgentClient = existingClient;
1747
+ const remoteAIClient = session?.getRemoteAIClient();
1748
+ if (remoteAIClient) {
1749
+ // Clone or create independent client for this subagent
1750
+ // RemoteAIClient should be designed to handle concurrent requests
1751
+ subAgentClient = remoteAIClient;
1663
1752
  isRemoteMode = true;
1664
- // Get the main taskId from session - subagent shares the same taskId as the parent task
1665
1753
  mainTaskId = session?.getTaskId() || null;
1666
1754
  }
1667
1755
  else {
@@ -1681,7 +1769,7 @@ export class TaskTool {
1681
1769
  subAgentClient = createAIClient(subAuthConfig);
1682
1770
  }
1683
1771
  const indent = ' '.repeat(indentLevel);
1684
- const indentNext = ' '.repeat(indentLevel + 1);
1772
+ const _indentNext = ' '.repeat(indentLevel + 1);
1685
1773
  const agentName = agent.name || subagent_type;
1686
1774
  // Track execution history for better reporting to main agent
1687
1775
  const executionHistory = [];
@@ -1729,7 +1817,7 @@ export class TaskTool {
1729
1817
  }
1730
1818
  }
1731
1819
  }
1732
- catch (e) {
1820
+ catch {
1733
1821
  // Ignore polling errors
1734
1822
  }
1735
1823
  }, 10);
@@ -1756,7 +1844,7 @@ export class TaskTool {
1756
1844
  throw new Error('Operation cancelled by user');
1757
1845
  }
1758
1846
  };
1759
- let messages = [
1847
+ const messages = [
1760
1848
  { role: 'system', content: enhancedSystemPrompt },
1761
1849
  { role: 'user', content: fullPrompt },
1762
1850
  ];
@@ -1777,8 +1865,10 @@ export class TaskTool {
1777
1865
  };
1778
1866
  });
1779
1867
  let iteration = 0;
1780
- const maxIterations = 10;
1781
- while (iteration < maxIterations) {
1868
+ let lastContentStr = ''; // Track last content for final result
1869
+ // Main agent style loop: continue until AI returns no more tool_calls
1870
+ // eslint-disable-next-line no-constant-condition
1871
+ while (true) {
1782
1872
  iteration++;
1783
1873
  // Check for cancellation before each iteration
1784
1874
  checkCancellation();
@@ -1843,134 +1933,66 @@ export class TaskTool {
1843
1933
  messages.push(assistantMessage);
1844
1934
  // Display reasoning content if present
1845
1935
  if (reasoningContent) {
1846
- console.log(`\n${indent}${colors.textDim(`${icons.brain} Thinking Process:`)}`);
1847
- const truncatedReasoning = reasoningContent.length > 500
1848
- ? reasoningContent.substring(0, 500) + '...'
1849
- : reasoningContent;
1850
- const indentedReasoning = indentMultiline(truncatedReasoning, indent);
1851
- console.log(`${indentedReasoning}\n`);
1936
+ if (isSdkMode && sdkOutputAdapter) {
1937
+ sdkOutputAdapter.outputThinking(reasoningContent, 'compact');
1938
+ }
1939
+ else {
1940
+ console.log(`\n${indent}${colors.textDim(`${icons.brain} Thinking Process:`)}`);
1941
+ const truncatedReasoning = reasoningContent.length > 500
1942
+ ? reasoningContent.substring(0, 500) + '...'
1943
+ : reasoningContent;
1944
+ const indentedReasoning = indentMultiline(truncatedReasoning, indent);
1945
+ console.log(`${indentedReasoning}\n`);
1946
+ }
1852
1947
  }
1853
1948
  // Display assistant response (if there's any text content) with proper indentation
1854
1949
  if (contentStr) {
1855
- console.log(`\n${indent}${colors.primaryBright(agentName)}: ${description}`);
1856
- const truncatedContent = contentStr.length > 500 ? contentStr.substring(0, 500) + '...' : contentStr;
1857
- const indentedContent = indentMultiline(truncatedContent, indent);
1858
- console.log(`${indentedContent}\n`);
1950
+ if (isSdkMode && sdkOutputAdapter) {
1951
+ sdkOutputAdapter.outputAssistant(contentStr);
1952
+ }
1953
+ else {
1954
+ console.log(`\n${indent}${colors.primaryBright(agentName)}: ${description}`);
1955
+ const truncatedContent = contentStr.length > 500 ? contentStr.substring(0, 500) + '...' : contentStr;
1956
+ const indentedContent = indentMultiline(truncatedContent, indent);
1957
+ console.log(`${indentedContent}\n`);
1958
+ }
1859
1959
  }
1860
- // Process tool calls with proper indentation
1960
+ // Process tool calls in parallel (照搬 session 的实现)
1861
1961
  if (toolCalls && toolCalls.length > 0) {
1862
- for (const toolCall of toolCalls) {
1962
+ // Prepare all tool calls with their indices
1963
+ const preparedToolCalls = toolCalls.map((toolCall, index) => {
1863
1964
  const { name, arguments: params } = toolCall.function;
1864
1965
  let parsedParams;
1865
1966
  try {
1866
1967
  parsedParams = typeof params === 'string' ? JSON.parse(params) : params;
1867
1968
  }
1868
- catch (e) {
1969
+ catch {
1869
1970
  parsedParams = params;
1870
1971
  }
1871
- console.log(`${indent}${colors.textMuted(`${icons.loading} Tool: ${name}`)}`);
1972
+ return { name, params: parsedParams, id: toolCall.id, index };
1973
+ });
1974
+ // Display all tool call info first
1975
+ for (const tc of preparedToolCalls) {
1976
+ if (isSdkMode && sdkOutputAdapter) {
1977
+ sdkOutputAdapter.outputToolStart(tc.name, tc.params);
1978
+ }
1979
+ else {
1980
+ console.log(`${indent}${colors.textMuted(`${icons.loading} Tool: ${tc.name}`)}`);
1981
+ }
1982
+ }
1983
+ // Execute all tool calls in parallel
1984
+ const executePromises = preparedToolCalls.map(async (tc) => {
1872
1985
  try {
1873
1986
  // Check cancellation before tool execution
1874
1987
  checkCancellation();
1875
- const toolResult = await cancellationManager.withCancellation(toolRegistry.execute(name, parsedParams, mode, indent), `subagent-${subagent_type}-${name}-${iteration}`);
1876
- // Get showToolDetails config to control result display
1877
- const showToolDetails = config.get('showToolDetails') || false;
1878
- // Prepare result preview for history
1879
- const resultPreview = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult, null, 2);
1880
- const truncatedPreview = resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
1881
- // Special handling for different tools (consistent with session.ts display logic)
1882
- const isTodoTool = name === 'todo_write' || name === 'todo_read';
1883
- const isEditTool = name === 'Edit';
1884
- const isWriteTool = name === 'Write';
1885
- const isDeleteTool = name === 'DeleteFile';
1886
- const hasDiff = isEditTool && toolResult?.diff;
1887
- const hasFilePreview = isWriteTool && toolResult?.preview;
1888
- const hasDeleteInfo = isDeleteTool && toolResult?.filePath;
1889
- // Import render functions for consistent display
1890
- const { renderDiff, renderLines } = await import('./theme.js');
1891
- if (isTodoTool) {
1892
- // Display todo list
1893
- console.log(`${indent}${colors.success(`${icons.check} Todo List:`)}`);
1894
- const todos = toolResult?.todos || [];
1895
- if (todos.length === 0) {
1896
- console.log(`${indent} ${colors.textMuted('No tasks')}`);
1897
- }
1898
- else {
1899
- const statusConfig = {
1900
- pending: { icon: icons.circle, color: colors.textMuted, label: 'Pending' },
1901
- in_progress: { icon: icons.loading, color: colors.warning, label: 'In Progress' },
1902
- completed: { icon: icons.success, color: colors.success, label: 'Completed' },
1903
- failed: { icon: icons.error, color: colors.error, label: 'Failed' },
1904
- };
1905
- for (const todo of todos) {
1906
- const status = statusConfig[todo.status] || statusConfig['pending'];
1907
- console.log(`${indent} ${status.color(status.icon)} ${status.color(status.label)}: ${colors.text(todo.task)}`);
1908
- }
1909
- }
1910
- if (toolResult?.message) {
1911
- console.log(`${indent}${colors.textDim(toolResult.message)}`);
1912
- }
1913
- console.log('');
1914
- }
1915
- else if (hasDiff) {
1916
- // Display edit result with diff
1917
- console.log('');
1918
- const diffOutput = renderDiff(toolResult.diff);
1919
- const indentedDiff = diffOutput
1920
- .split('\n')
1921
- .map((line) => `${indent} ${line}`)
1922
- .join('\n');
1923
- console.log(`${indentedDiff}\n`);
1924
- }
1925
- else if (hasFilePreview) {
1926
- // Display new file content in preview style
1927
- console.log('');
1928
- console.log(`${indent}${colors.success(`${icons.file} ${toolResult.filePath}`)}`);
1929
- console.log(`${indent}${colors.textDim(` ${toolResult.lineCount} lines`)}`);
1930
- console.log('');
1931
- console.log(renderLines(toolResult.preview, { maxLines: 10, indent: indent + ' ' }));
1932
- console.log('');
1933
- }
1934
- else if (hasDeleteInfo) {
1935
- // Display DeleteFile result
1936
- console.log('');
1937
- console.log(`${indent}${colors.success(`${icons.check} Deleted: ${toolResult.filePath}`)}`);
1938
- console.log('');
1939
- }
1940
- else if (showToolDetails) {
1941
- // Show full result details
1942
- const indentedPreview = indentMultiline(resultPreview, indent);
1943
- console.log(`${indent}${colors.success(`${icons.check} Tool Result:`)}\n${indentedPreview}\n`);
1944
- }
1945
- else if (toolResult && toolResult.success === false) {
1946
- // Tool failed
1947
- console.log(`${indent}${colors.error(`${icons.cross} ${toolResult.message || 'Failed'}`)}\n`);
1948
- }
1949
- else if (toolResult) {
1950
- // Show brief preview by default
1951
- const indentedPreview = indentMultiline(truncatedPreview, indent);
1952
- console.log(`${indent}${colors.success(`${icons.check} Completed`)}\n${indentedPreview}\n`);
1953
- }
1954
- else {
1955
- console.log(`${indent}${colors.textDim('(no result)')}\n`);
1956
- }
1957
- // Record successful tool execution in history (use truncated preview to save memory)
1958
- executionHistory.push({
1959
- tool: name,
1960
- status: 'success',
1961
- params: parsedParams,
1962
- result: truncatedPreview,
1963
- timestamp: new Date().toISOString(),
1964
- });
1965
- messages.push({
1966
- role: 'tool',
1967
- content: JSON.stringify(toolResult),
1968
- tool_call_id: toolCall.id,
1969
- });
1988
+ const toolResult = await cancellationManager.withCancellation(toolRegistry.execute(tc.name, tc.params, mode, indent), `subagent-${subagent_type}-${tc.name}-${iteration}`);
1989
+ return { ...tc, toolResult, error: undefined };
1970
1990
  }
1971
1991
  catch (error) {
1972
1992
  if (error.message === 'Operation cancelled by user') {
1973
- console.log(`${indent}${colors.warning(`⚠️ Operation cancelled`)}\n`);
1993
+ if (!isSdkMode || !sdkOutputAdapter) {
1994
+ console.log(`${indent}${colors.warning(`⚠️ Operation cancelled`)}\n`);
1995
+ }
1974
1996
  cancellationManager.off('cancelled', cancelHandler);
1975
1997
  cleanupStdinPolling();
1976
1998
  const summaryPreview = contentStr.length > 300 ? contentStr.substring(0, 300) + '...' : contentStr;
@@ -1990,56 +2012,174 @@ export class TaskTool {
1990
2012
  },
1991
2013
  };
1992
2014
  }
1993
- console.log(`${indent}${colors.error(`${icons.cross} Error:`)} ${error.message}\n`);
2015
+ return { ...tc, toolResult: undefined, error: error.message };
2016
+ }
2017
+ });
2018
+ const settledResults = await Promise.all(executePromises);
2019
+ // Check for cancellation in results
2020
+ const cancellationResult = settledResults.find((r) => 'success' in r && r.success === false);
2021
+ if (cancellationResult) {
2022
+ return cancellationResult;
2023
+ }
2024
+ const resultsByIndex = new Map();
2025
+ const usedIndices = new Set();
2026
+ for (const result of settledResults) {
2027
+ // Find the first unused original index that matches the tool name
2028
+ const originalIndex = preparedToolCalls.findIndex((tc, idx) => tc.name === result.name && !usedIndices.has(idx));
2029
+ if (originalIndex !== -1) {
2030
+ usedIndices.add(originalIndex);
2031
+ resultsByIndex.set(originalIndex, result);
2032
+ }
2033
+ }
2034
+ // Import render functions for consistent display
2035
+ const { renderDiff, renderLines } = await import('./theme.js');
2036
+ // Process results in the original tool_calls order
2037
+ for (let i = 0; i < preparedToolCalls.length; i++) {
2038
+ const result = resultsByIndex.get(i);
2039
+ if (!result)
2040
+ continue;
2041
+ const { name, params: parsedParams, toolResult, error } = result;
2042
+ // Get showToolDetails config to control result display
2043
+ const showToolDetails = config.get('showToolDetails') || false;
2044
+ // Prepare result preview for history
2045
+ const resultPreview = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult, null, 2);
2046
+ const truncatedPreview = resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
2047
+ if (error) {
2048
+ // Handle error case
2049
+ if (isSdkMode && sdkOutputAdapter) {
2050
+ sdkOutputAdapter.outputToolError(name, error);
2051
+ }
2052
+ else {
2053
+ console.log(`${indent}${colors.error(`${icons.cross} Error:`)} ${error}\n`);
2054
+ }
1994
2055
  // Record failed tool execution in history
1995
2056
  executionHistory.push({
1996
2057
  tool: name,
1997
2058
  status: 'error',
1998
2059
  params: parsedParams,
1999
- error: error.message,
2060
+ error,
2000
2061
  timestamp: new Date().toISOString(),
2001
2062
  });
2002
2063
  messages.push({
2003
2064
  role: 'tool',
2004
- content: JSON.stringify({ error: error.message }),
2005
- tool_call_id: toolCall.id,
2065
+ content: JSON.stringify({ error }),
2066
+ tool_call_id: result.id,
2067
+ });
2068
+ }
2069
+ else {
2070
+ // Handle success case - display result
2071
+ // SDK mode: output tool result via adapter
2072
+ if (isSdkMode && sdkOutputAdapter) {
2073
+ sdkOutputAdapter.outputToolResult(name, toolResult);
2074
+ }
2075
+ // Normal mode: console output (SDK mode already output via adapter above)
2076
+ if (!isSdkMode || !sdkOutputAdapter) {
2077
+ // Special handling for different tools (consistent with session.ts display logic)
2078
+ const isTodoTool = name === 'todo_write' || name === 'todo_read';
2079
+ const isEditTool = name === 'Edit';
2080
+ const isWriteTool = name === 'Write';
2081
+ const isDeleteTool = name === 'DeleteFile';
2082
+ const hasDiff = isEditTool && toolResult?.diff;
2083
+ const hasFilePreview = isWriteTool && toolResult?.preview;
2084
+ const hasDeleteInfo = isDeleteTool && toolResult?.filePath;
2085
+ if (isTodoTool) {
2086
+ // Display todo list
2087
+ console.log(`${indent}${colors.success(`${icons.check} Todo List:`)}`);
2088
+ const todos = toolResult?.todos || [];
2089
+ if (todos.length === 0) {
2090
+ console.log(`${indent} ${colors.textMuted('No tasks')}`);
2091
+ }
2092
+ else {
2093
+ const statusConfig = {
2094
+ pending: { icon: icons.circle, color: colors.textMuted, label: 'Pending' },
2095
+ in_progress: { icon: icons.loading, color: colors.warning, label: 'In Progress' },
2096
+ completed: { icon: icons.success, color: colors.success, label: 'Completed' },
2097
+ failed: { icon: icons.error, color: colors.error, label: 'Failed' },
2098
+ };
2099
+ for (const todo of todos) {
2100
+ const status = statusConfig[todo.status] || statusConfig['pending'];
2101
+ console.log(`${indent} ${status.color(status.icon)} ${status.color(status.label)}: ${colors.text(todo.task)}`);
2102
+ }
2103
+ }
2104
+ if (toolResult?.message) {
2105
+ console.log(`${indent}${colors.textDim(toolResult.message)}`);
2106
+ }
2107
+ console.log('');
2108
+ }
2109
+ else if (hasDiff) {
2110
+ // Display edit result with diff
2111
+ console.log('');
2112
+ const diffOutput = renderDiff(toolResult.diff);
2113
+ const indentedDiff = diffOutput
2114
+ .split('\n')
2115
+ .map((line) => `${indent} ${line}`)
2116
+ .join('\n');
2117
+ console.log(`${indentedDiff}\n`);
2118
+ }
2119
+ else if (hasFilePreview) {
2120
+ // Display new file content in preview style
2121
+ console.log('');
2122
+ console.log(`${indent}${colors.success(`${icons.file} ${toolResult.filePath}`)}`);
2123
+ console.log(`${indent}${colors.textDim(` ${toolResult.lineCount} lines`)}`);
2124
+ console.log('');
2125
+ console.log(renderLines(toolResult.preview, { maxLines: 10, indent: indent + ' ' }));
2126
+ console.log('');
2127
+ }
2128
+ else if (hasDeleteInfo) {
2129
+ // Display DeleteFile result
2130
+ console.log('');
2131
+ console.log(`${indent}${colors.success(`${icons.check} Deleted: ${toolResult.filePath}`)}`);
2132
+ console.log('');
2133
+ }
2134
+ else if (showToolDetails) {
2135
+ // Show full result details
2136
+ const indentedPreview = indentMultiline(resultPreview, indent);
2137
+ console.log(`${indent}${colors.success(`${icons.check} Tool Result:`)}\n${indentedPreview}\n`);
2138
+ }
2139
+ else if (toolResult && toolResult.success === false) {
2140
+ // Tool failed
2141
+ console.log(`${indent}${colors.error(`${icons.cross} ${toolResult.message || 'Failed'}`)}\n`);
2142
+ }
2143
+ else if (toolResult) {
2144
+ // Show brief preview by default
2145
+ const indentedPreview = indentMultiline(truncatedPreview, indent);
2146
+ console.log(`${indent}${colors.success(`${icons.check} Completed`)}\n${indentedPreview}\n`);
2147
+ }
2148
+ else {
2149
+ console.log(`${indent}${colors.textDim('(no result)')}\n`);
2150
+ }
2151
+ }
2152
+ // Record successful tool execution in history (use truncated preview to save memory)
2153
+ executionHistory.push({
2154
+ tool: name,
2155
+ status: 'success',
2156
+ params: parsedParams,
2157
+ result: truncatedPreview,
2158
+ timestamp: new Date().toISOString(),
2159
+ });
2160
+ messages.push({
2161
+ role: 'tool',
2162
+ content: JSON.stringify(toolResult),
2163
+ tool_call_id: result.id,
2006
2164
  });
2007
2165
  }
2008
2166
  }
2009
- console.log('');
2167
+ if (!isSdkMode || !sdkOutputAdapter) {
2168
+ console.log('');
2169
+ }
2010
2170
  continue; // Continue to next iteration to get final response
2011
2171
  }
2012
- // No more tool calls, return the result with execution history
2013
- cancellationManager.off('cancelled', cancelHandler);
2014
- cleanupStdinPolling();
2015
- const summaryPreview = contentStr.length > 300 ? contentStr.substring(0, 300) + '...' : contentStr;
2016
- return {
2017
- success: true,
2018
- message: `Task "${description}" completed by ${subagent_type}`,
2019
- result: {
2020
- summary: summaryPreview,
2021
- executionHistory: {
2022
- totalIterations: iteration,
2023
- toolsExecuted: executionHistory.length,
2024
- successfulTools: executionHistory.filter((t) => t.status === 'success').length,
2025
- failedTools: executionHistory.filter((t) => t.status === 'error').length,
2026
- history: executionHistory,
2027
- },
2028
- },
2029
- };
2172
+ // No more tool calls - break loop (same as main agent)
2173
+ lastContentStr = contentStr || '';
2174
+ break;
2030
2175
  }
2031
- // Max iterations reached - return accumulated results instead of throwing error
2032
- // Get the last assistant message content
2033
- const lastAssistantMsg = messages.filter((m) => m.role === 'assistant').pop();
2034
- const lastContentStr = typeof lastAssistantMsg?.content === 'string'
2035
- ? lastAssistantMsg.content
2036
- : JSON.stringify(lastAssistantMsg?.content || '');
2176
+ // Loop ended - return result (same as main agent pattern)
2037
2177
  cancellationManager.off('cancelled', cancelHandler);
2038
2178
  cleanupStdinPolling();
2039
2179
  const summaryPreview = lastContentStr.length > 300 ? lastContentStr.substring(0, 300) + '...' : lastContentStr;
2040
2180
  return {
2041
2181
  success: true,
2042
- message: `Task "${description}" completed (max iterations reached) by ${subagent_type}`,
2182
+ message: `Task "${description}" completed by ${subagent_type}`,
2043
2183
  result: {
2044
2184
  summary: summaryPreview,
2045
2185
  executionHistory: {
@@ -2048,12 +2188,11 @@ export class TaskTool {
2048
2188
  successfulTools: executionHistory.filter((t) => t.status === 'success').length,
2049
2189
  failedTools: executionHistory.filter((t) => t.status === 'error').length,
2050
2190
  history: executionHistory,
2051
- maxIterationsReached: true,
2052
2191
  },
2053
2192
  },
2054
2193
  };
2055
2194
  }
2056
- async executeParallelAgents(agents, description, mode, agentManager, toolRegistry, aiClient, indentLevel = 1) {
2195
+ async executeParallelAgents(agents, description, mode, agentManager, toolRegistry, config, indentLevel = 1) {
2057
2196
  const indent = ' '.repeat(indentLevel);
2058
2197
  const indentNext = ' '.repeat(indentLevel + 1);
2059
2198
  const cancellationManager = getCancellationManager();
@@ -2086,7 +2225,7 @@ export class TaskTool {
2086
2225
  }
2087
2226
  }
2088
2227
  }
2089
- catch (e) {
2228
+ catch {
2090
2229
  // Ignore polling errors
2091
2230
  }
2092
2231
  }, 10);
@@ -2108,7 +2247,7 @@ export class TaskTool {
2108
2247
  cancellationManager.on('cancelled', cancelHandler);
2109
2248
  console.log(`\n${indent}${colors.accent('◆')} ${colors.primaryBright('Parallel Agents')}: ${agents.length} running...`);
2110
2249
  const startTime = Date.now();
2111
- const agentPromises = agents.map(async (agentTask, index) => {
2250
+ const agentPromises = agents.map(async (agentTask, _index) => {
2112
2251
  // Check if cancelled
2113
2252
  if (cancelled || cancellationManager.isOperationCancelled()) {
2114
2253
  return {
@@ -2119,7 +2258,7 @@ export class TaskTool {
2119
2258
  };
2120
2259
  }
2121
2260
  try {
2122
- const result = await this.executeSingleAgent(agentTask.subagent_type, agentTask.prompt, agentTask.description, agentTask.useContext ?? true, agentTask.constraints || [], mode, agentManager, toolRegistry, aiClient, indentLevel + 1);
2261
+ const result = await this.executeSingleAgent(agentTask.subagent_type, agentTask.prompt, agentTask.description, agentTask.useContext ?? true, agentTask.constraints || [], mode, agentManager, toolRegistry, config, indentLevel + 1);
2123
2262
  return {
2124
2263
  success: true,
2125
2264
  agent: agentTask.subagent_type,
@@ -2329,6 +2468,19 @@ export class AskUserQuestionTool {
2329
2468
  ExecutionMode.SMART,
2330
2469
  ];
2331
2470
  async execute(params) {
2471
+ // Check if in SDK mode
2472
+ const sdkMode = this._sdkMode;
2473
+ const sdkAdapter = this._sdkOutputAdapter;
2474
+ if (sdkMode && sdkAdapter) {
2475
+ return this.executeSdk(params, sdkAdapter);
2476
+ }
2477
+ // Regular TUI mode
2478
+ return this.executeTui(params);
2479
+ }
2480
+ /**
2481
+ * Execute in TUI mode using @clack/prompts
2482
+ */
2483
+ async executeTui(params) {
2332
2484
  const { questions } = params;
2333
2485
  try {
2334
2486
  if (questions.length === 0 || questions.length > 4) {
@@ -2357,6 +2509,38 @@ export class AskUserQuestionTool {
2357
2509
  throw new Error(`Failed to ask user questions: ${error.message}`);
2358
2510
  }
2359
2511
  }
2512
+ /**
2513
+ * Execute in SDK mode - output question request and wait for response
2514
+ */
2515
+ async executeSdk(params, sdkAdapter) {
2516
+ const { questions } = params;
2517
+ if (questions.length === 0 || questions.length > 4) {
2518
+ throw new Error('Must provide 1-4 questions');
2519
+ }
2520
+ const requestId = `question_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2521
+ // Output question request through SDK adapter
2522
+ sdkAdapter.outputQuestionRequest({
2523
+ requestId,
2524
+ questions
2525
+ });
2526
+ // Wait for SDK question response
2527
+ // The response will be handled by session.ts which has access to the SDK input
2528
+ // For now, we use a polling mechanism or wait for a specific event
2529
+ try {
2530
+ // Import the session to get response handling
2531
+ const { getSingletonSession } = await import('./session.js');
2532
+ const session = getSingletonSession();
2533
+ if (!session) {
2534
+ throw new Error('SDK session not available');
2535
+ }
2536
+ const answers = await session.waitForQuestionResponse(requestId);
2537
+ sdkAdapter.outputQuestionResponse(requestId, answers);
2538
+ return { answers };
2539
+ }
2540
+ catch (error) {
2541
+ throw new Error(`Failed to get SDK question response: ${error.message}`);
2542
+ }
2543
+ }
2360
2544
  }
2361
2545
  export class SaveMemoryTool {
2362
2546
  name = 'save_memory';
@@ -2696,7 +2880,6 @@ export class InvokeSkillTool {
2696
2880
  const { skillId, taskDescription, inputFile, outputFile, options } = params;
2697
2881
  try {
2698
2882
  const { getSkillInvoker } = await import('./skill-invoker.js');
2699
- const { SkillExecutionParams } = (await import('./skill-invoker.js'));
2700
2883
  const skillInvoker = getSkillInvoker();
2701
2884
  await skillInvoker.initialize();
2702
2885
  // Verify skill exists
@@ -2827,6 +3010,8 @@ export class ToolRegistry {
2827
3010
  tools = new Map();
2828
3011
  todoWriteTool;
2829
3012
  backgroundTasks = new Map();
3013
+ _isSdkMode = false;
3014
+ _sdkOutputAdapter = null;
2830
3015
  constructor() {
2831
3016
  this.todoWriteTool = new TodoWriteTool();
2832
3017
  this.registerDefaultTools();
@@ -2915,12 +3100,36 @@ export class ToolRegistry {
2915
3100
  this.tools.set(toolName, mcpTool);
2916
3101
  registeredCount++;
2917
3102
  if (toolName !== originalName) {
2918
- console.log(`[MCP] Tool '${originalName}' renamed to '${toolName}' to avoid conflict`);
3103
+ // SDK 模式下不输出重命名信息
3104
+ if (!this._isSdkMode) {
3105
+ console.log(`[MCP] Tool '${originalName}' renamed to '${toolName}' to avoid conflict`);
3106
+ }
2919
3107
  }
2920
3108
  }
2921
3109
  }
2922
3110
  if (registeredCount > 0) {
2923
- console.log(`[MCP] Registered ${registeredCount} tool(s)`);
3111
+ // 在 SDK 模式下不输出注册信息(MCP 相关输出已在 session 中处理)
3112
+ if (!this._isSdkMode) {
3113
+ console.log(`[MCP] Registered ${registeredCount} tool(s)`);
3114
+ }
3115
+ }
3116
+ }
3117
+ /**
3118
+ * Set SDK mode for the tool registry.
3119
+ * In SDK mode, tool execution output is sent to the SDK output adapter.
3120
+ */
3121
+ async setSdkMode(enabled, adapter) {
3122
+ this._isSdkMode = enabled;
3123
+ this._sdkOutputAdapter = adapter;
3124
+ // Mark all tools as SDK mode enabled
3125
+ for (const [, tool] of this.tools) {
3126
+ tool._sdkMode = enabled;
3127
+ tool._sdkOutputAdapter = adapter;
3128
+ }
3129
+ // Initialize SDK mode for TaskTool specifically
3130
+ const taskTool = this.tools.get('task');
3131
+ if (taskTool) {
3132
+ await taskTool.setSdkMode?.(enabled, adapter);
2924
3133
  }
2925
3134
  }
2926
3135
  /**
@@ -3426,7 +3635,7 @@ export class ToolRegistry {
3426
3635
  required: ['skill'],
3427
3636
  };
3428
3637
  break;
3429
- default:
3638
+ default: {
3430
3639
  // For MCP tools, use their inputSchema; for other unknown tools, keep empty schema
3431
3640
  const mcpTool = tool;
3432
3641
  if (mcpTool._isMcpTool && mcpTool.inputSchema) {
@@ -3455,6 +3664,7 @@ export class ToolRegistry {
3455
3664
  required: [],
3456
3665
  };
3457
3666
  }
3667
+ }
3458
3668
  }
3459
3669
  return {
3460
3670
  type: 'function',
@@ -3481,12 +3691,12 @@ export class ToolRegistry {
3481
3691
  return await this.executeMCPTool(toolName, params, executionMode, indent);
3482
3692
  }
3483
3693
  // Try to find MCP tool with just the tool name (try each server)
3484
- for (const [fullName, tool] of allMcpTools) {
3694
+ for (const [fullName, _tool] of allMcpTools) {
3485
3695
  // Split only on the first __ to preserve underscores in tool names
3486
3696
  const firstUnderscoreIndex = fullName.indexOf('__');
3487
3697
  if (firstUnderscoreIndex === -1)
3488
3698
  continue;
3489
- const [serverName, actualToolName] = [
3699
+ const [_serverName, actualToolName] = [
3490
3700
  fullName.substring(0, firstUnderscoreIndex),
3491
3701
  fullName.substring(firstUnderscoreIndex + 2),
3492
3702
  ];
@@ -3509,6 +3719,8 @@ export class ToolRegistry {
3509
3719
  if (!tool.allowedModes.includes(executionMode)) {
3510
3720
  throw new Error(`Tool ${toolName} is not allowed in ${executionMode} mode`);
3511
3721
  }
3722
+ const isSdkMode = this._isSdkMode;
3723
+ const sdkOutputAdapter = this._sdkOutputAdapter;
3512
3724
  // Smart approval mode
3513
3725
  if (executionMode === ExecutionMode.SMART) {
3514
3726
  const debugMode = process.env.DEBUG === 'smart-approval';
@@ -3529,12 +3741,22 @@ export class ToolRegistry {
3529
3741
  const isRemoteMode = authConfig.type === AuthType.OAUTH_XAGENT;
3530
3742
  if (isRemoteMode && toolName === 'InvokeSkill') {
3531
3743
  console.log('');
3532
- console.log(`${indent}${colors.success(`✅ [Smart Mode] Remote mode: tool '${toolName}' auto-approved (remote LLM already approved)`)}`);
3533
- console.log('');
3744
+ if (isSdkMode && sdkOutputAdapter) {
3745
+ sdkOutputAdapter.outputInfo(`[Smart Mode] Remote mode: tool '${toolName}' auto-approved (remote LLM already approved)`);
3746
+ }
3747
+ else {
3748
+ console.log('');
3749
+ console.log(`${indent}${colors.success(`✅ [Smart Mode] Remote mode: tool '${toolName}' auto-approved (remote LLM already approved)`)}`);
3750
+ console.log('');
3751
+ }
3534
3752
  return await cancellationManager.withCancellation(tool.execute(params, executionMode), `tool-${toolName}`);
3535
3753
  }
3536
3754
  const { getSmartApprovalEngine } = await import('./smart-approval.js');
3537
3755
  const approvalEngine = getSmartApprovalEngine(debugMode);
3756
+ // Set SDK mode for approval engine if in SDK mode
3757
+ if (isSdkMode && sdkOutputAdapter) {
3758
+ approvalEngine.setSdkMode(true, sdkOutputAdapter);
3759
+ }
3538
3760
  // Evaluate tool call
3539
3761
  const result = await approvalEngine.evaluate({
3540
3762
  toolName,
@@ -3544,35 +3766,56 @@ export class ToolRegistry {
3544
3766
  // Decide whether to execute based on approval result
3545
3767
  if (result.decision === 'approved') {
3546
3768
  // Whitelist or AI approval passed, execute directly
3547
- console.log('');
3548
- console.log(`${indent}${colors.success(`✅ [Smart Mode] Tool '${toolName}' passed approval, executing directly`)}`);
3549
- console.log(`${indent}${colors.textDim(` Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`)}`);
3550
- console.log(`${indent}${colors.textDim(` Latency: ${result.latency}ms`)}`);
3551
- console.log('');
3769
+ if (isSdkMode && sdkOutputAdapter) {
3770
+ sdkOutputAdapter.outputInfo(`[Smart Mode] Tool '${toolName}' passed approval, executing directly`);
3771
+ sdkOutputAdapter.outputInfo(`Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}, Latency: ${result.latency}ms`);
3772
+ }
3773
+ else {
3774
+ console.log('');
3775
+ console.log(`${indent}${colors.success(`✅ [Smart Mode] Tool '${toolName}' passed approval, executing directly`)}`);
3776
+ console.log(`${indent}${colors.textDim(` Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`)}`);
3777
+ console.log(`${indent}${colors.textDim(` Latency: ${result.latency}ms`)}`);
3778
+ console.log('');
3779
+ }
3552
3780
  return await cancellationManager.withCancellation(tool.execute(params, executionMode), `tool-${toolName}`);
3553
3781
  }
3554
3782
  else if (result.decision === 'requires_confirmation') {
3555
3783
  // Requires user confirmation
3556
- const confirmed = await approvalEngine.requestConfirmation(result);
3784
+ const confirmed = await approvalEngine.requestConfirmation(result, toolName, params);
3557
3785
  if (confirmed) {
3558
- console.log('');
3559
- console.log(`${indent}${colors.success(`✅ [Smart Mode] User confirmed execution of tool '${toolName}'`)}`);
3560
- console.log('');
3786
+ if (isSdkMode && sdkOutputAdapter) {
3787
+ sdkOutputAdapter.outputInfo(`[Smart Mode] User confirmed execution of tool '${toolName}'`);
3788
+ }
3789
+ else {
3790
+ console.log('');
3791
+ console.log(`${indent}${colors.success(`✅ [Smart Mode] User confirmed execution of tool '${toolName}'`)}`);
3792
+ console.log('');
3793
+ }
3561
3794
  return await cancellationManager.withCancellation(tool.execute(params, executionMode), `tool-${toolName}`);
3562
3795
  }
3563
3796
  else {
3564
- console.log('');
3565
- console.log(`${indent}${colors.warning(`⚠️ [Smart Mode] User cancelled execution of tool '${toolName}'`)}`);
3566
- console.log('');
3797
+ if (isSdkMode && sdkOutputAdapter) {
3798
+ sdkOutputAdapter.outputWarning(`[Smart Mode] User cancelled execution of tool '${toolName}'`);
3799
+ }
3800
+ else {
3801
+ console.log('');
3802
+ console.log(`${indent}${colors.warning(`⚠️ [Smart Mode] User cancelled execution of tool '${toolName}'`)}`);
3803
+ console.log('');
3804
+ }
3567
3805
  throw new Error(`Tool execution cancelled by user: ${toolName}`);
3568
3806
  }
3569
3807
  }
3570
3808
  else {
3571
3809
  // Rejected execution
3572
- console.log('');
3573
- console.log(`${indent}${colors.error(`❌ [Smart Mode] Tool '${toolName}' execution rejected`)}`);
3574
- console.log(`${indent}${colors.textDim(` Reason: ${result.description}`)}`);
3575
- console.log('');
3810
+ if (isSdkMode && sdkOutputAdapter) {
3811
+ sdkOutputAdapter.outputError(`[Smart Mode] Tool '${toolName}' execution rejected`, { reason: result.description });
3812
+ }
3813
+ else {
3814
+ console.log('');
3815
+ console.log(`${indent}${colors.error(`❌ [Smart Mode] Tool '${toolName}' execution rejected`)}`);
3816
+ console.log(`${indent}${colors.textDim(` Reason: ${result.description}`)}`);
3817
+ console.log('');
3818
+ }
3576
3819
  throw new Error(`Tool execution rejected: ${toolName}`);
3577
3820
  }
3578
3821
  }
@@ -3592,10 +3835,14 @@ export class ToolRegistry {
3592
3835
  const actualToolName = toolName.substring(firstUnderscoreIndex + 2);
3593
3836
  // Get server info for display
3594
3837
  const server = mcpManager.getServer(serverName);
3595
- const serverTools = server?.getToolNames() || [];
3838
+ const _serverTools = server?.getToolNames() || [];
3839
+ const isSdkMode = this._isSdkMode;
3840
+ const sdkOutputAdapter = this._sdkOutputAdapter;
3596
3841
  // Display tool call info
3597
- console.log('');
3598
- console.log(`${indent}${colors.warning(`${icons.tool} MCP Tool Call: ${serverName}::${actualToolName}`)}`);
3842
+ if (!isSdkMode || !sdkOutputAdapter) {
3843
+ console.log('');
3844
+ console.log(`${indent}${colors.warning(`${icons.tool} MCP Tool Call: ${serverName}::${actualToolName}`)}`);
3845
+ }
3599
3846
  // Smart approval mode for MCP tools
3600
3847
  if (executionMode === ExecutionMode.SMART) {
3601
3848
  const debugMode = process.env.DEBUG === 'smart-approval';
@@ -3606,10 +3853,19 @@ export class ToolRegistry {
3606
3853
  const isRemoteMode = authConfig.type === AuthType.OAUTH_XAGENT;
3607
3854
  // Remote mode: remote LLM has already approved the tool, auto-approve
3608
3855
  if (isRemoteMode) {
3609
- console.log(`${indent}${colors.success(`✅ [Smart Mode] Remote mode: MCP tool '${serverName}::${actualToolName}' auto-approved`)}`);
3856
+ if (isSdkMode && sdkOutputAdapter) {
3857
+ sdkOutputAdapter.outputInfo(`[Smart Mode] Remote mode: MCP tool '${serverName}::${actualToolName}' auto-approved`);
3858
+ }
3859
+ else {
3860
+ console.log(`${indent}${colors.success(`✅ [Smart Mode] Remote mode: MCP tool '${serverName}::${actualToolName}' auto-approved`)}`);
3861
+ }
3610
3862
  }
3611
3863
  else {
3612
3864
  const approvalEngine = getSmartApprovalEngine(debugMode);
3865
+ // Set SDK mode for approval engine if in SDK mode
3866
+ if (isSdkMode && sdkOutputAdapter) {
3867
+ approvalEngine.setSdkMode(true, sdkOutputAdapter);
3868
+ }
3613
3869
  // Evaluate MCP tool call
3614
3870
  const result = await approvalEngine.evaluate({
3615
3871
  toolName: `MCP[${serverName}]::${actualToolName}`,
@@ -3617,19 +3873,35 @@ export class ToolRegistry {
3617
3873
  timestamp: Date.now(),
3618
3874
  });
3619
3875
  if (result.decision === 'approved') {
3620
- console.log(`${indent}${colors.success(`✅ [Smart Mode] MCP tool '${serverName}::${actualToolName}' passed approval`)}`);
3621
- console.log(`${indent}${colors.textDim(` Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`)}`);
3876
+ if (isSdkMode && sdkOutputAdapter) {
3877
+ sdkOutputAdapter.outputInfo(`[Smart Mode] MCP tool '${serverName}::${actualToolName}' passed approval`);
3878
+ sdkOutputAdapter.outputInfo(`Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`);
3879
+ }
3880
+ else {
3881
+ console.log(`${indent}${colors.success(`✅ [Smart Mode] MCP tool '${serverName}::${actualToolName}' passed approval`)}`);
3882
+ console.log(`${indent}${colors.textDim(` Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`)}`);
3883
+ }
3622
3884
  }
3623
3885
  else if (result.decision === 'requires_confirmation') {
3624
- const confirmed = await approvalEngine.requestConfirmation(result);
3886
+ const confirmed = await approvalEngine.requestConfirmation(result, `MCP[${serverName}]::${actualToolName}`, params);
3625
3887
  if (!confirmed) {
3626
- console.log(`${indent}${colors.warning(`⚠️ [Smart Mode] User cancelled MCP tool execution`)}`);
3888
+ if (isSdkMode && sdkOutputAdapter) {
3889
+ sdkOutputAdapter.outputWarning(`[Smart Mode] User cancelled MCP tool execution`);
3890
+ }
3891
+ else {
3892
+ console.log(`${indent}${colors.warning(`⚠️ [Smart Mode] User cancelled MCP tool execution`)}`);
3893
+ }
3627
3894
  throw new Error(`Tool execution cancelled by user: ${toolName}`);
3628
3895
  }
3629
3896
  }
3630
3897
  else {
3631
- console.log(`${indent}${colors.error(`❌ [Smart Mode] MCP tool execution rejected`)}`);
3632
- console.log(`${indent}${colors.textDim(` Reason: ${result.description}`)}`);
3898
+ if (isSdkMode && sdkOutputAdapter) {
3899
+ sdkOutputAdapter.outputError(`[Smart Mode] MCP tool execution rejected`, { reason: result.description });
3900
+ }
3901
+ else {
3902
+ console.log(`${indent}${colors.error(`❌ [Smart Mode] MCP tool execution rejected`)}`);
3903
+ console.log(`${indent}${colors.textDim(` Reason: ${result.description}`)}`);
3904
+ }
3633
3905
  throw new Error(`Tool execution rejected: ${toolName}`);
3634
3906
  }
3635
3907
  }