@xagent-ai/cli 1.3.0 → 1.3.1

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 +37 -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 +23 -18
  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 +83 -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 +8 -7
  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 +40 -4
  156. package/src/cli.ts +154 -37
  157. package/src/config.ts +1 -1
  158. package/src/context-compressor.ts +28 -23
  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 +86 -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 +9 -8
  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/session.ts CHANGED
@@ -18,10 +18,8 @@ import {
18
18
  ChatMessage,
19
19
  ToolCall,
20
20
  AuthType,
21
- AuthConfig,
22
- AgentConfig,
23
- ToolCallItem,
24
21
  } from './types.js';
22
+ import type { AgentConfig, ToolCallItem } from './types.js';
25
23
  import { createAIClient, type AIClientInterface } from './ai-client-factory.js';
26
24
  import { detectThinkingKeywords, getThinkingTokens } from './ai-client/types.js';
27
25
  import { TokenInvalidError } from './ai-client/types.js';
@@ -29,13 +27,13 @@ import { fetchDefaultModels } from './ai-client/providers/remote.js';
29
27
  import { getConfigManager, ConfigManager } from './config.js';
30
28
  import { AuthService, selectAuthType } from './auth.js';
31
29
  import { getToolRegistry } from './tools.js';
32
- import { getAgentManager, DEFAULT_AGENTS, AgentManager } from './agents.js';
30
+ import { getAgentManager, AgentManager } from './agents.js';
33
31
  import { getMemoryManager, MemoryManager } from './memory.js';
34
32
  import { getMCPManager, MCPManager } from './mcp.js';
35
33
  import { getCheckpointManager, CheckpointManager } from './checkpoint.js';
36
34
  import { getConversationManager, ConversationManager } from './conversation.js';
37
35
  import { getSessionManager, SessionManager } from './session-manager.js';
38
- import { SlashCommandHandler, parseInput, detectImageInput } from './slash-commands.js';
36
+ import { SlashCommandHandler, parseInput } from './slash-commands.js';
39
37
  import { SystemPromptGenerator } from './system-prompt-generator.js';
40
38
  import {
41
39
  theme,
@@ -45,7 +43,6 @@ import {
45
43
  renderMarkdown,
46
44
  renderDiff,
47
45
  renderLines,
48
- TERMINAL_BG,
49
46
  } from './theme.js';
50
47
  import { getCancellationManager, CancellationManager } from './cancellation.js';
51
48
  import {
@@ -53,8 +50,9 @@ import {
53
50
  ContextCompressor,
54
51
  CompressionResult,
55
52
  } from './context-compressor.js';
56
- import { Logger, LogLevel, getLogger } from './logger.js';
53
+ import { getLogger } from './logger.js';
57
54
  import { ensureTtySane, setupEscKeyHandler } from './terminal.js';
55
+ import { SdkOutputAdapter } from './sdk-output-adapter.js';
58
56
 
59
57
  // Type aliases for backward compatibility
60
58
  type AIClient = AIClientInterface;
@@ -86,6 +84,18 @@ export class InteractiveSession {
86
84
  private currentTaskId: string | null = null;
87
85
  private taskCompleted: boolean = false;
88
86
  private isFirstApiCall: boolean = true;
87
+ private sdkOutputAdapter: SdkOutputAdapter | null = null;
88
+ private isSdkMode: boolean = false;
89
+ private sdkInputBuffer: string[] = [];
90
+ private resolveInput: ((value: string | null) => void) | null = null;
91
+ private _currentRequestId: string | null = null;
92
+ private heartbeatTimeout: NodeJS.Timeout | null = null;
93
+ private heartbeatTimeoutMs: number = 300000; // 5 minutes timeout for long AI responses
94
+ private lastActivityTime: number = Date.now();
95
+
96
+ // SDK response handling for approvals and questions
97
+ private approvalPromises: Map<string, { resolve: (approved: boolean) => void; reject: (err: Error) => void }> = new Map();
98
+ private questionPromises: Map<string, { resolve: (answers: string[]) => void; reject: (err: Error) => void }> = new Map();
89
99
 
90
100
  constructor(indentLevel: number = 0) {
91
101
  this.rl = readline.createInterface({
@@ -173,6 +183,76 @@ export class InteractiveSession {
173
183
  this.executionMode = mode;
174
184
  }
175
185
 
186
+ /**
187
+ * Set SDK mode for programmatic access.
188
+ * In SDK mode, output is formatted as JSON to stdout.
189
+ */
190
+ setSdkMode(adapter: SdkOutputAdapter): void {
191
+ this.isSdkMode = true;
192
+ this.sdkOutputAdapter = adapter;
193
+
194
+ // Initialize SDK mode for other modules using centralized output util
195
+ const { initOutputMode } = require('./output-util.js');
196
+ initOutputMode(true, adapter);
197
+
198
+ // Initialize SmartApprovalEngine in SDK mode
199
+ this.initSmartApprovalSdkMode(adapter).catch(() => {
200
+ // Silently ignore errors - not critical
201
+ });
202
+
203
+ // Initialize tool registry in SDK mode (fire and forget, doesn't need to await)
204
+ this.initToolRegistrySdkMode(adapter).catch(() => {
205
+ // Silently ignore errors - tool registry init is not critical
206
+ });
207
+ }
208
+
209
+ /**
210
+ * Initialize SmartApprovalEngine in SDK mode.
211
+ */
212
+ private async initSmartApprovalSdkMode(adapter: SdkOutputAdapter): Promise<void> {
213
+ const { getSmartApprovalEngine } = await import('./smart-approval.js');
214
+ const approvalEngine = getSmartApprovalEngine();
215
+ approvalEngine.setSdkMode(true, adapter);
216
+ }
217
+
218
+ /**
219
+ * Initialize tool registry in SDK mode.
220
+ */
221
+ private async initToolRegistrySdkMode(adapter: SdkOutputAdapter): Promise<void> {
222
+ const toolRegistry = getToolRegistry();
223
+ await toolRegistry.setSdkMode(true, adapter);
224
+ }
225
+
226
+ /**
227
+ * Get SDK mode status.
228
+ */
229
+ getIsSdkMode(): boolean {
230
+ return this.isSdkMode;
231
+ }
232
+
233
+ /**
234
+ * Output assistant response - handles SDK mode and normal mode differently.
235
+ */
236
+ private outputAssistant(content: string, reasoningContent?: string): void {
237
+ if (this.isSdkMode && this.sdkOutputAdapter) {
238
+ this.sdkOutputAdapter.outputAssistant(content, reasoningContent);
239
+ } else {
240
+ const indent = this.getIndent();
241
+ console.log('');
242
+ console.log(`${indent}${colors.primaryBright(`${icons.robot} Assistant:`)}`);
243
+ console.log(
244
+ `${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`
245
+ );
246
+ console.log('');
247
+ const renderedContent = renderMarkdown(
248
+ content,
249
+ (process.stdout.columns || 80) - indent.length * 2
250
+ );
251
+ console.log(`${indent}${renderedContent.replace(/^/gm, indent)}`);
252
+ console.log('');
253
+ }
254
+ }
255
+
176
256
  /**
177
257
  * Update system prompt to reflect MCP changes (called after add/remove MCP)
178
258
  */
@@ -228,6 +308,18 @@ export class InteractiveSession {
228
308
 
229
309
  let lastUpdateTime = 0;
230
310
 
311
+ // Initialize with current state to avoid triggering update on first check
312
+ try {
313
+ const { existsSync, readFileSync } = require('fs');
314
+ if (existsSync(stateFilePath)) {
315
+ const content = readFileSync(stateFilePath, 'utf-8');
316
+ const state = JSON.parse(content);
317
+ lastUpdateTime = state.lastSkillUpdate || 0;
318
+ }
319
+ } catch {
320
+ // Silent fail
321
+ }
322
+
231
323
  // Check for updates every 2 seconds
232
324
  const checkInterval = setInterval(async () => {
233
325
  try {
@@ -238,14 +330,16 @@ export class InteractiveSession {
238
330
 
239
331
  if (state.lastSkillUpdate && state.lastSkillUpdate > lastUpdateTime) {
240
332
  lastUpdateTime = state.lastSkillUpdate;
241
-
333
+
242
334
  // Update system prompt with new skills
243
335
  await this.updateSystemPrompt();
244
-
245
- console.log(colors.textMuted(' 🔄 Skills updated from CLI'));
336
+
337
+ if (!this.isSdkMode) {
338
+ console.log(colors.textMuted(' 🔄 Skills updated from CLI'));
339
+ }
246
340
  }
247
341
  }
248
- } catch (error) {
342
+ } catch {
249
343
  // Silent fail - watcher is optional
250
344
  }
251
345
  }, 2000);
@@ -265,45 +359,56 @@ export class InteractiveSession {
265
359
  // Initialize taskId for GUI operations
266
360
  this.currentTaskId = crypto.randomUUID();
267
361
 
268
- const separator = icons.separator.repeat(60);
269
- console.log('');
270
- console.log(colors.gradient('╔════════════════════════════════════════════════════════════╗'));
271
- console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
272
- console.log(
273
- colors.gradient('║') +
274
- ' '.repeat(13) +
275
- '🤖 ' +
276
- colors.gradient('XAGENT CLI') +
277
- ' '.repeat(32) +
278
- colors.gradient(' ║')
279
- );
280
- console.log(
281
- colors.gradient('║') +
282
- ' '.repeat(16) +
283
- colors.textMuted(`v${packageJson.version}`) +
284
- ' '.repeat(36) +
285
- colors.gradient(' ║')
286
- );
287
- console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
288
- console.log(colors.gradient('╚════════════════════════════════════════════════════════════╝'));
289
- console.log(colors.textMuted(' AI-powered command-line assistant'));
362
+ const _separator = icons.separator.repeat(60);
290
363
 
291
- // Show initialization message if skills were initialized
292
- if (initializedCount > 0) {
293
- console.log(colors.textMuted(` ✨ Initialized ${initializedCount} built-in skills`));
364
+ if (!this.isSdkMode) {
365
+ // Normal mode: show ASCII art welcome
366
+ console.log('');
367
+ console.log(colors.gradient('╔════════════════════════════════════════════════════════════╗'));
368
+ console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
369
+ console.log(
370
+ colors.gradient('║') +
371
+ ' '.repeat(13) +
372
+ '🤖 ' +
373
+ colors.gradient('XAGENT CLI') +
374
+ ' '.repeat(32) +
375
+ colors.gradient(' ║')
376
+ );
377
+ console.log(
378
+ colors.gradient('║') +
379
+ ' '.repeat(16) +
380
+ colors.textMuted(`v${packageJson.version}`) +
381
+ ' '.repeat(36) +
382
+ colors.gradient(' ║')
383
+ );
384
+ console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
385
+ console.log(colors.gradient('╚════════════════════════════════════════════════════════════╝'));
386
+ console.log(colors.textMuted(' AI-powered command-line assistant'));
387
+
388
+ // Show initialization message if skills were initialized
389
+ if (initializedCount > 0) {
390
+ console.log(colors.textMuted(` ✨ Initialized ${initializedCount} built-in skills`));
391
+ }
392
+ console.log('');
294
393
  }
295
- console.log('');
296
394
 
297
395
  await this.initialize();
298
396
  this.showWelcomeMessage();
299
397
 
300
398
  // Start watching for skill updates from CLI
301
399
  this.startSkillUpdateWatcher();
400
+
401
+ // SDK 模式初始化:设置输出适配器
402
+ if (this.isSdkMode) {
403
+ // Start heartbeat timeout monitoring in SDK mode
404
+ this.startHeartbeatMonitoring();
405
+ }
406
+
302
407
  // Set up ESC key handler using the terminal module
303
408
  // This avoids conflicts with readline and provides clean ESC detection
304
- let escCleanup: (() => void) | undefined;
409
+ let _escCleanup: (() => void) | undefined;
305
410
  if (process.stdin.isTTY) {
306
- escCleanup = setupEscKeyHandler(() => {
411
+ _escCleanup = setupEscKeyHandler(() => {
307
412
  if ((this as any)._isOperationInProgress) {
308
413
  // An operation is running, let it be cancelled
309
414
  this.cancellationManager.cancel();
@@ -490,31 +595,49 @@ export class InteractiveSession {
490
595
 
491
596
  const mcpServers = this.configManager.getMcpServers();
492
597
  Object.entries(mcpServers).forEach(([name, config]) => {
493
- console.log(`📝 Registering MCP server: ${name} (${config.transport})`);
598
+ if (this.isSdkMode && this.sdkOutputAdapter) {
599
+ this.sdkOutputAdapter.outputMCPRegistering(name, config.transport || 'stdio');
600
+ } else {
601
+ console.log(`📝 Registering MCP server: ${name} (${config.transport})`);
602
+ }
494
603
  this.mcpManager.registerServer(name, config);
495
604
  });
496
605
 
497
606
  // Eagerly connect to MCP servers to get tool definitions
498
607
  if (mcpServers && Object.keys(mcpServers).length > 0) {
499
608
  try {
500
- console.log(
501
- `${colors.info(`${icons.brain} Connecting to ${Object.keys(mcpServers).length} MCP server(s)...`)}`
502
- );
609
+ const serverCount = Object.keys(mcpServers).length;
610
+ if (this.isSdkMode && this.sdkOutputAdapter) {
611
+ this.sdkOutputAdapter.outputMCPLoading(serverCount);
612
+ } else {
613
+ console.log(
614
+ `${colors.info(`${icons.brain} Connecting to ${serverCount} MCP server(s)...`)}`
615
+ );
616
+ }
503
617
  await this.mcpManager.connectAllServers();
504
618
  const connectedCount = Array.from(this.mcpManager.getAllServers()).filter((s: any) =>
505
619
  s.isServerConnected()
506
620
  ).length;
507
621
  const mcpTools = this.mcpManager.getToolDefinitions();
508
- console.log(
509
- `${colors.success(`✓ ${connectedCount}/${Object.keys(mcpServers).length} MCP server(s) connected (${mcpTools.length} tools available)`)}`
510
- );
622
+
623
+ if (this.isSdkMode && this.sdkOutputAdapter) {
624
+ this.sdkOutputAdapter.outputMCPConnected(serverCount, connectedCount, mcpTools.length);
625
+ } else {
626
+ console.log(
627
+ `${colors.success(`✓ ${connectedCount}/${serverCount} MCP server(s) connected (${mcpTools.length} tools available)`)}`
628
+ );
629
+ }
511
630
 
512
631
  // Register MCP tools with the tool registry (hide MCP origin from LLM)
513
632
  const toolRegistry = getToolRegistry();
514
633
  const allMcpTools = this.mcpManager.getAllTools();
515
634
  toolRegistry.registerMCPTools(allMcpTools);
516
635
  } catch (error: any) {
517
- console.log(`${colors.warning(`⚠ MCP connection failed: ${error.message}`)}`);
636
+ if (this.isSdkMode && this.sdkOutputAdapter) {
637
+ this.sdkOutputAdapter.outputMCPConnectionFailed(error.message);
638
+ } else {
639
+ console.log(`${colors.warning(`⚠ MCP connection failed: ${error.message}`)}`);
640
+ }
518
641
  }
519
642
  }
520
643
 
@@ -530,7 +653,11 @@ export class InteractiveSession {
530
653
 
531
654
  this.currentAgent = this.agentManager.getAgent('general-purpose') ?? null;
532
655
 
533
- console.log(colors.success('✔ Initialization complete'));
656
+ if (this.isSdkMode && this.sdkOutputAdapter) {
657
+ this.sdkOutputAdapter.outputSuccess('Initialization complete');
658
+ } else {
659
+ console.log(colors.success('✔ Initialization complete'));
660
+ }
534
661
  } catch (error: any) {
535
662
  const spinner = ora({ text: '', spinner: 'dots', color: 'red' }).start();
536
663
  spinner.fail(colors.error(`Initialization failed: ${error.message}`));
@@ -596,7 +723,7 @@ export class InteractiveSession {
596
723
  } else {
597
724
  return null;
598
725
  }
599
- } catch (error: any) {
726
+ } catch {
600
727
  return null;
601
728
  }
602
729
  }
@@ -641,10 +768,14 @@ export class InteractiveSession {
641
768
  // VLM configuration is optional - only show for non-OAuth (local) mode
642
769
  // Remote mode uses backend VLM configuration
643
770
  if (authType !== AuthType.OAUTH_XAGENT) {
644
- console.log('');
645
- console.log(colors.info(`${icons.info} VLM configuration is optional.`));
646
- console.log(colors.info(`You can configure it later using the /model command if needed.`));
647
- console.log('');
771
+ if (this.isSdkMode && this.sdkOutputAdapter) {
772
+ this.sdkOutputAdapter.outputInfo('VLM configuration is optional. You can configure it later using the /model command if needed.');
773
+ } else {
774
+ console.log('');
775
+ console.log(colors.info(`${icons.info} VLM configuration is optional.`));
776
+ console.log(colors.info(`You can configure it later using the /model command if needed.`));
777
+ console.log('');
778
+ }
648
779
  }
649
780
 
650
781
  this.configManager.setAuthConfig(authConfig);
@@ -665,21 +796,82 @@ export class InteractiveSession {
665
796
  const language = this.configManager.getLanguage();
666
797
  const separator = icons.separator.repeat(40);
667
798
 
668
- console.log('');
669
- console.log(colors.border(separator));
799
+ if (!this.isSdkMode) {
800
+ console.log('');
801
+ console.log(colors.border(separator));
670
802
 
671
- if (language === 'zh') {
672
- console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
673
- console.log(colors.textMuted('Type /help to see available commands'));
674
- } else {
675
- console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
676
- console.log(colors.textMuted('Type /help to see available commands'));
677
- }
803
+ if (language === 'zh') {
804
+ console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
805
+ console.log(colors.textMuted('Type /help to see available commands'));
806
+ } else {
807
+ console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
808
+ console.log(colors.textMuted('Type /help to see available commands'));
809
+ }
678
810
 
679
- console.log(colors.border(separator));
680
- console.log('');
811
+ console.log(colors.border(separator));
812
+ console.log('');
813
+ }
681
814
 
682
815
  this.showExecutionMode();
816
+
817
+ // In SDK mode, output ready signal
818
+ if (this.isSdkMode && this.sdkOutputAdapter) {
819
+ this.sdkOutputAdapter.outputReady();
820
+ }
821
+ }
822
+
823
+ /**
824
+ * Start heartbeat timeout monitoring in SDK mode
825
+ */
826
+ private startHeartbeatMonitoring(): void {
827
+ this.stopHeartbeatMonitoring();
828
+
829
+ // Check heartbeat timeout periodically
830
+ this.heartbeatTimeout = setInterval(() => {
831
+ const elapsed = Date.now() - this.lastActivityTime;
832
+
833
+ if (elapsed > this.heartbeatTimeoutMs) {
834
+ // Heartbeat timeout - no activity for too long
835
+ this.sdkOutputAdapter?.output({
836
+ type: 'error',
837
+ subtype: 'heartbeat_timeout',
838
+ timestamp: Date.now(),
839
+ data: {
840
+ message: 'Heartbeat timeout - no activity detected',
841
+ elapsed_ms: elapsed,
842
+ timeout_ms: this.heartbeatTimeoutMs
843
+ }
844
+ });
845
+
846
+ // Stop monitoring
847
+ this.stopHeartbeatMonitoring();
848
+ }
849
+ }, 30000); // Check every 30 seconds
850
+ }
851
+
852
+ /**
853
+ * Stop heartbeat timeout monitoring
854
+ */
855
+ private stopHeartbeatMonitoring(): void {
856
+ if (this.heartbeatTimeout) {
857
+ clearInterval(this.heartbeatTimeout);
858
+ this.heartbeatTimeout = null;
859
+ }
860
+ }
861
+
862
+ /**
863
+ * Reset heartbeat timeout (called on activity)
864
+ */
865
+ private resetHeartbeatTimeout(): void {
866
+ this.lastActivityTime = Date.now();
867
+ }
868
+
869
+ /**
870
+ * Stop heartbeat monitoring (public method for cleanup)
871
+ * Used by SDK session to clean up when session ends
872
+ */
873
+ public stopHeartbeatMonitor(): void {
874
+ this.stopHeartbeatMonitoring();
683
875
  }
684
876
 
685
877
  private showExecutionMode(): void {
@@ -714,10 +906,12 @@ export class InteractiveSession {
714
906
  const config = modeConfig[this.executionMode];
715
907
  const modeName = this.executionMode;
716
908
 
717
- console.log(colors.textMuted(`${icons.info} Current Mode:`));
718
- console.log(` ${config.color(config.icon)} ${styleHelpers.text.bold(config.color(modeName))}`);
719
- console.log(` ${colors.textDim(` ${config.description}`)}`);
720
- console.log('');
909
+ if (!this.isSdkMode) {
910
+ console.log(colors.textMuted(`${icons.info} Current Mode:`));
911
+ console.log(` ${config.color(config.icon)} ${styleHelpers.text.bold(config.color(modeName))}`);
912
+ console.log(` ${colors.textDim(` ${config.description}`)}`);
913
+ console.log('');
914
+ }
721
915
 
722
916
  this.showRemoteModelInfo();
723
917
  }
@@ -726,6 +920,21 @@ export class InteractiveSession {
726
920
  const authConfig = this.configManager.getAuthConfig();
727
921
  const isRemote = authConfig.type === AuthType.OAUTH_XAGENT;
728
922
 
923
+ if (this.isSdkMode && this.sdkOutputAdapter) {
924
+ // SDK 模式:通过 adapter 输出
925
+ if (isRemote) {
926
+ const llmModel = authConfig.remote_llmModelName || 'Not set';
927
+ const vlmModel = authConfig.remote_vlmModelName || 'Not set';
928
+ this.sdkOutputAdapter.outputInfo(`Remote Models - LLM: ${llmModel}, VLM: ${vlmModel}`);
929
+ } else {
930
+ const modelName = authConfig.modelName || 'Not set';
931
+ const guiSubagentModel = this.configManager.get('guiSubagentModel') || 'Not set';
932
+ this.sdkOutputAdapter.outputInfo(`Local Models - LLM: ${modelName}, VLM: ${guiSubagentModel}`);
933
+ }
934
+ return;
935
+ }
936
+
937
+ // 正常模式:控制台输出
729
938
  if (isRemote) {
730
939
  const llmModel = authConfig.remote_llmModelName || colors.textMuted('Not set');
731
940
  const vlmModel = authConfig.remote_vlmModelName || colors.textMuted('Not set');
@@ -748,6 +957,12 @@ export class InteractiveSession {
748
957
  return;
749
958
  }
750
959
 
960
+ // In SDK mode, use a different input loop
961
+ if (this.isSdkMode) {
962
+ await this.sdkPromptLoop();
963
+ return;
964
+ }
965
+
751
966
  // Recreate readline interface for input
752
967
  if (this.rl) {
753
968
  this.rl.close();
@@ -779,12 +994,54 @@ export class InteractiveSession {
779
994
  }
780
995
 
781
996
  private async handleInput(input: string): Promise<void> {
997
+ // Reset heartbeat timeout on any input activity
998
+ this.resetHeartbeatTimeout();
999
+
782
1000
  const trimmedInput = input.trim();
783
1001
 
784
1002
  if (!trimmedInput) {
785
1003
  return;
786
1004
  }
787
1005
 
1006
+ // Check for SDK JSON message format
1007
+ if (this.isSdkMode) {
1008
+ const { isSdkMessage, parseSdkMessage } = await import('./types.js');
1009
+
1010
+ if (isSdkMessage(trimmedInput)) {
1011
+ const sdkMessage = parseSdkMessage(trimmedInput);
1012
+
1013
+ if (sdkMessage) {
1014
+ if (sdkMessage.type === 'ping') {
1015
+ // Handle ping - respond with pong
1016
+ await this.handlePing(sdkMessage);
1017
+ return;
1018
+ } else if (sdkMessage.type === 'control_request') {
1019
+ // Handle control request
1020
+ await this.handleControlRequest(sdkMessage);
1021
+ return;
1022
+ } else if (sdkMessage.type === 'user') {
1023
+ // Store request_id for tracking
1024
+ this._currentRequestId = sdkMessage.request_id || null;
1025
+ // Handle user message from SDK
1026
+ await this.processUserMessage(sdkMessage.content);
1027
+ return;
1028
+ } else if (sdkMessage.type === 'approval_response') {
1029
+ // Handle approval response
1030
+ await this.handleApprovalResponse(sdkMessage);
1031
+ return;
1032
+ } else if (sdkMessage.type === 'question_response') {
1033
+ // Handle question response
1034
+ await this.handleQuestionResponse(sdkMessage);
1035
+ return;
1036
+ }
1037
+ }
1038
+ } else {
1039
+ // Not a JSON SDK message, treat as regular text
1040
+ await this.processUserMessage(trimmedInput);
1041
+ return;
1042
+ }
1043
+ }
1044
+
788
1045
  if (trimmedInput.startsWith('/')) {
789
1046
  const handled = await this.slashCommandHandler.handleCommand(trimmedInput);
790
1047
  if (handled) {
@@ -804,6 +1061,327 @@ export class InteractiveSession {
804
1061
  await this.processUserMessage(trimmedInput);
805
1062
  }
806
1063
 
1064
+ /**
1065
+ * SDK prompt loop - reads input from stdin without showing prompt
1066
+ */
1067
+ private async sdkPromptLoop(): Promise<void> {
1068
+ // Read input from stdin directly without outputting prompt
1069
+ const input = await this.readSdkInput();
1070
+
1071
+ if ((this as any)._isShuttingDown || input === null) {
1072
+ return;
1073
+ }
1074
+
1075
+ try {
1076
+ await this.handleInput(input);
1077
+ } catch (err: any) {
1078
+ this.sdkOutputAdapter?.output({
1079
+ type: 'error',
1080
+ subtype: 'general',
1081
+ timestamp: Date.now(),
1082
+ data: { message: err.message }
1083
+ });
1084
+ }
1085
+
1086
+ // Continue the loop
1087
+ this.sdkPromptLoop();
1088
+ }
1089
+
1090
+ private sdkRl: readline.Interface | null = null;
1091
+ private sdkInputProcessing: boolean = false;
1092
+
1093
+ /**
1094
+ * Check if a line is an SDK control message (approval_response, question_response)
1095
+ */
1096
+ private isSdkControlMessage(line: string): boolean {
1097
+ try {
1098
+ const parsed = JSON.parse(line);
1099
+ return parsed.type === 'approval_response' || parsed.type === 'question_response';
1100
+ } catch {
1101
+ return false;
1102
+ }
1103
+ }
1104
+
1105
+ /**
1106
+ * Process an SDK input line (returns true if processed)
1107
+ */
1108
+ private async processSdkInputLine(line: string): Promise<boolean> {
1109
+ const { isSdkMessage, parseSdkMessage } = await import('./types.js');
1110
+
1111
+ if (isSdkMessage(line)) {
1112
+ const sdkMessage = parseSdkMessage(line);
1113
+
1114
+ if (sdkMessage) {
1115
+ if (sdkMessage.type === 'ping') {
1116
+ await this.handlePing(sdkMessage);
1117
+ return true;
1118
+ } else if (sdkMessage.type === 'control_request') {
1119
+ await this.handleControlRequest(sdkMessage);
1120
+ return true;
1121
+ } else if (sdkMessage.type === 'user') {
1122
+ this._currentRequestId = sdkMessage.request_id || null;
1123
+ await this.processUserMessage(sdkMessage.content);
1124
+ return true;
1125
+ } else if (sdkMessage.type === 'approval_response') {
1126
+ await this.handleApprovalResponse(sdkMessage);
1127
+ return true;
1128
+ } else if (sdkMessage.type === 'question_response') {
1129
+ await this.handleQuestionResponse(sdkMessage);
1130
+ return true;
1131
+ }
1132
+ }
1133
+ }
1134
+ return false;
1135
+ }
1136
+
1137
+ /**
1138
+ * Read a line of input from stdin (SDK mode)
1139
+ * Uses readline 'line' event for reliable stdin reading
1140
+ * SDK control messages (approval_response, question_response) bypass the main loop
1141
+ */
1142
+ private readSdkInput(): Promise<string | null> {
1143
+ return new Promise((resolve) => {
1144
+ // Create readline interface if not exists
1145
+ if (!this.sdkRl) {
1146
+ this.sdkRl = readline.createInterface({
1147
+ input: process.stdin,
1148
+ crlfDelay: Infinity,
1149
+ });
1150
+
1151
+ // Handle line events - SDK control messages bypass normal flow
1152
+ this.sdkRl.on('line', async (line) => {
1153
+ const cleanLine = line
1154
+ .replace(/^\uFEFF/, '')
1155
+ // eslint-disable-next-line no-control-regex
1156
+ .replace(/[\x00-\x1F\x7F-\x9F]/g, '');
1157
+
1158
+ // Check if this is an SDK control message
1159
+ if (this.isSdkControlMessage(cleanLine)) {
1160
+ // Process immediately, don't wait for main loop
1161
+ // We need to set a flag to prevent re-entrancy issues
1162
+ if (this.sdkInputProcessing) {
1163
+ // Already processing, queue it
1164
+ this.sdkInputBuffer.push(cleanLine);
1165
+ } else {
1166
+ this.sdkInputProcessing = true;
1167
+ try {
1168
+ await this.processSdkInputLine(cleanLine);
1169
+ } finally {
1170
+ this.sdkInputProcessing = false;
1171
+ }
1172
+ }
1173
+ return;
1174
+ }
1175
+
1176
+ // Regular input
1177
+ if (this.resolveInput) {
1178
+ // Immediate handler available, resolve immediately
1179
+ this.resolveInput(cleanLine);
1180
+ this.resolveInput = null;
1181
+ } else {
1182
+ // No handler available, queue the message
1183
+ this.sdkInputBuffer.push(cleanLine);
1184
+ }
1185
+ });
1186
+
1187
+ // Handle close events
1188
+ this.sdkRl.on('close', () => {
1189
+ if (this.resolveInput) {
1190
+ this.resolveInput(null);
1191
+ this.resolveInput = null;
1192
+ }
1193
+ });
1194
+
1195
+ // Handle errors
1196
+ this.sdkRl.on('error', () => {
1197
+ if (this.resolveInput) {
1198
+ this.resolveInput(null);
1199
+ this.resolveInput = null;
1200
+ }
1201
+ });
1202
+ }
1203
+
1204
+ // Check for SDK control messages in buffer first
1205
+ for (let i = 0; i < this.sdkInputBuffer.length; i++) {
1206
+ const line = this.sdkInputBuffer[i];
1207
+ if (this.isSdkControlMessage(line)) {
1208
+ this.sdkInputBuffer.splice(i, 1);
1209
+ resolve(line);
1210
+ return;
1211
+ }
1212
+ }
1213
+
1214
+ if (this.sdkInputBuffer.length > 0) {
1215
+ const line = this.sdkInputBuffer.shift()!;
1216
+ resolve(line);
1217
+ return;
1218
+ }
1219
+
1220
+ // Set up the resolve callback
1221
+ this.resolveInput = (value: string | null) => {
1222
+ resolve(value);
1223
+ };
1224
+ });
1225
+ }
1226
+
1227
+ /**
1228
+ * Handle SDK ping messages (heartbeat)
1229
+ */
1230
+ private async handlePing(pingMessage: any): Promise<void> {
1231
+ const requestId = pingMessage.request_id || `ping_${Date.now()}`;
1232
+
1233
+ // Reset activity timestamp on ping (heartbeat activity)
1234
+ this.lastActivityTime = Date.now();
1235
+
1236
+ // Send pong response through SDK adapter for consistency
1237
+ this.sdkOutputAdapter?.output({
1238
+ type: 'system',
1239
+ subtype: 'pong',
1240
+ timestamp: Date.now(),
1241
+ data: {
1242
+ type: 'pong',
1243
+ request_id: requestId,
1244
+ timestamp: Date.now()
1245
+ }
1246
+ });
1247
+ }
1248
+
1249
+ /**
1250
+ * Handle SDK control requests
1251
+ */
1252
+ private async handleControlRequest(request: any): Promise<void> {
1253
+ // Update activity to prevent heartbeat timeout during control requests
1254
+ this.lastActivityTime = Date.now();
1255
+
1256
+ const { request_id, request: req } = request;
1257
+
1258
+ switch (req.subtype) {
1259
+ case 'interrupt':
1260
+ this.sdkOutputAdapter?.outputSystem('interrupt', { request_id });
1261
+ (this as any)._isShuttingDown = true;
1262
+ process.exit(0);
1263
+ break;
1264
+
1265
+ case 'set_permission_mode':
1266
+ {
1267
+ const { ExecutionMode } = await import('./types.js');
1268
+ const modeMap: Record<string, ExecutionMode> = {
1269
+ 'default': ExecutionMode.DEFAULT,
1270
+ 'acceptEdits': ExecutionMode.ACCEPT_EDITS,
1271
+ 'plan': ExecutionMode.PLAN,
1272
+ 'bypassPermissions': ExecutionMode.YOLO,
1273
+ };
1274
+ const mode = modeMap[req.mode] || ExecutionMode.SMART;
1275
+ this.executionMode = mode;
1276
+ this.sdkOutputAdapter?.outputSystem('permission_mode_changed', {
1277
+ request_id,
1278
+ mode: req.mode
1279
+ });
1280
+ }
1281
+ break;
1282
+
1283
+ case 'set_model':
1284
+ this.sdkOutputAdapter?.outputSystem('model_changed', {
1285
+ request_id,
1286
+ model: req.model
1287
+ });
1288
+ break;
1289
+
1290
+ default:
1291
+ this.sdkOutputAdapter?.outputWarning(`Unknown control request: ${req.subtype}`);
1292
+ }
1293
+ }
1294
+
1295
+ /**
1296
+ * Handle SDK approval responses
1297
+ */
1298
+ private async handleApprovalResponse(response: {
1299
+ request_id: string;
1300
+ approved: boolean;
1301
+ }): Promise<void> {
1302
+ const { request_id, approved } = response;
1303
+
1304
+ const pending = this.approvalPromises.get(request_id);
1305
+ if (pending) {
1306
+ pending.resolve(approved);
1307
+ this.approvalPromises.delete(request_id);
1308
+ } else {
1309
+ this.sdkOutputAdapter?.outputWarning(`Unknown approval request ID: ${request_id}`);
1310
+ }
1311
+ }
1312
+
1313
+ /**
1314
+ * Handle SDK question responses
1315
+ */
1316
+ private async handleQuestionResponse(response: {
1317
+ request_id: string;
1318
+ answers: string[];
1319
+ }): Promise<void> {
1320
+ const { request_id, answers } = response;
1321
+
1322
+ const pending = this.questionPromises.get(request_id);
1323
+ if (pending) {
1324
+ pending.resolve(answers);
1325
+ this.questionPromises.delete(request_id);
1326
+ } else {
1327
+ this.sdkOutputAdapter?.outputWarning(`Unknown question request ID: ${request_id}`);
1328
+ }
1329
+ }
1330
+
1331
+ /**
1332
+ * Wait for SDK approval response
1333
+ */
1334
+ async waitForApprovalResponse(requestId: string, timeoutMs: number = 300000): Promise<boolean> {
1335
+ return new Promise((resolve, reject) => {
1336
+ const timeout = setTimeout(() => {
1337
+ const pending = this.approvalPromises.get(requestId);
1338
+ if (pending) {
1339
+ pending.reject(new Error('Approval request timeout'));
1340
+ this.approvalPromises.delete(requestId);
1341
+ }
1342
+ resolve(false);
1343
+ }, timeoutMs);
1344
+
1345
+ this.approvalPromises.set(requestId, {
1346
+ resolve: (approved: boolean) => {
1347
+ clearTimeout(timeout);
1348
+ resolve(approved);
1349
+ },
1350
+ reject: (err: Error) => {
1351
+ clearTimeout(timeout);
1352
+ reject(err);
1353
+ }
1354
+ });
1355
+ });
1356
+ }
1357
+
1358
+ /**
1359
+ * Wait for SDK question response
1360
+ */
1361
+ async waitForQuestionResponse(requestId: string, timeoutMs: number = 300000): Promise<string[]> {
1362
+ return new Promise((resolve, reject) => {
1363
+ const timeout = setTimeout(() => {
1364
+ const pending = this.questionPromises.get(requestId);
1365
+ if (pending) {
1366
+ pending.reject(new Error('Question request timeout'));
1367
+ this.questionPromises.delete(requestId);
1368
+ }
1369
+ resolve([]); // Return empty array on timeout instead of rejecting
1370
+ }, timeoutMs);
1371
+
1372
+ this.questionPromises.set(requestId, {
1373
+ resolve: (answers: string[]) => {
1374
+ clearTimeout(timeout);
1375
+ resolve(answers);
1376
+ },
1377
+ reject: (err: Error) => {
1378
+ clearTimeout(timeout);
1379
+ reject(err);
1380
+ }
1381
+ });
1382
+ });
1383
+ }
1384
+
807
1385
  private async handleSubAgentCommand(input: string): Promise<void> {
808
1386
  const [agentType, ...taskParts] = input.slice(1).split(' ');
809
1387
  const task = taskParts.join(' ');
@@ -829,8 +1407,8 @@ export class InteractiveSession {
829
1407
  await this.processUserMessage(task, agent);
830
1408
  }
831
1409
 
832
- public async processUserMessage(message: string, agent?: AgentConfig): Promise<void> {
833
- const inputs = parseInput(message);
1410
+ public async processUserMessage(message: string, _agent?: AgentConfig): Promise<void> {
1411
+ const inputs = await parseInput(message);
834
1412
  const textInput = inputs.find((i) => i.type === 'text');
835
1413
  const fileInputs = inputs.filter((i) => i.type === 'file');
836
1414
  const commandInput = inputs.find((i) => i.type === 'command');
@@ -908,6 +1486,12 @@ export class InteractiveSession {
908
1486
  const thinkingConfig = this.configManager.getThinkingConfig();
909
1487
  const displayMode = thinkingConfig.displayMode || 'compact';
910
1488
 
1489
+ // SDK 模式:使用 adapter 输出
1490
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1491
+ this.sdkOutputAdapter.outputThinking(reasoningContent, displayMode);
1492
+ return;
1493
+ }
1494
+
911
1495
  const separator = icons.separator.repeat(
912
1496
  Math.min(60, process.stdout.columns || 80) - indent.length
913
1497
  );
@@ -923,7 +1507,7 @@ export class InteractiveSession {
923
1507
  console.log(`${indent}${colors.textDim(reasoningContent.replace(/^/gm, indent))}`);
924
1508
  break;
925
1509
 
926
- case 'compact':
1510
+ case 'compact': {
927
1511
  // Compact display, truncate partial content
928
1512
  const maxLength = 500;
929
1513
  const truncatedContent =
@@ -936,6 +1520,7 @@ export class InteractiveSession {
936
1520
  console.log(`${indent}${colors.textDim(truncatedContent.replace(/^/gm, indent))}`);
937
1521
  console.log(`${indent}${colors.textDim(`[${reasoningContent.length} chars total]`)}`);
938
1522
  break;
1523
+ }
939
1524
 
940
1525
  case 'indicator':
941
1526
  // Show indicator only
@@ -966,7 +1551,7 @@ export class InteractiveSession {
966
1551
  }
967
1552
 
968
1553
  const indent = this.getIndent();
969
- const currentTokens = this.contextCompressor.estimateContextTokens(this.conversation);
1554
+ const _currentTokens = this.contextCompressor.estimateContextTokens(this.conversation);
970
1555
  const currentMessages = this.conversation.length;
971
1556
  const { needsCompression, reason, tokenCount } = this.contextCompressor.needsCompression(
972
1557
  this.conversation,
@@ -983,10 +1568,14 @@ export class InteractiveSession {
983
1568
  const threshold = thresholdMatch ? parseInt(thresholdMatch[1], 10) : 0;
984
1569
  const contextWindow = contextWindowMatch ? parseInt(contextWindowMatch[1], 10) : 0;
985
1570
 
986
- console.log('');
987
- console.log(
988
- `${indent}${colors.success(`${icons.sparkles} Compressing context (${currentMessages} msgs, ${tokenCount.toLocaleString()} > ${threshold.toLocaleString()}/${contextWindow.toLocaleString()} tokens, ${Math.round((tokenCount / contextWindow) * 100)}% of context window)...`)}`
989
- );
1571
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1572
+ this.sdkOutputAdapter.outputContextCompressionTriggered(reason);
1573
+ } else {
1574
+ console.log('');
1575
+ console.log(
1576
+ `${indent}${colors.success(`${icons.sparkles} Compressing context (${currentMessages} msgs, ${tokenCount.toLocaleString()} > ${threshold.toLocaleString()}/${contextWindow.toLocaleString()} tokens, ${Math.round((tokenCount / contextWindow) * 100)}% of context window)...`)}`
1577
+ );
1578
+ }
990
1579
 
991
1580
  const toolRegistry = getToolRegistry();
992
1581
  const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are a helpful AI assistant.';
@@ -1003,9 +1592,20 @@ export class InteractiveSession {
1003
1592
  if (result.wasCompressed) {
1004
1593
  this.conversation = result.compressedMessages;
1005
1594
  const reductionPercent = Math.round((1 - result.compressedSize / result.originalSize) * 100);
1006
- console.log(
1007
- `${indent}${colors.success(`${icons.success} Compressed ${result.originalMessageCount} → ${result.compressedMessageCount} messages (${reductionPercent}% smaller)`)}`
1008
- );
1595
+
1596
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1597
+ this.sdkOutputAdapter.outputContextCompressionResult(
1598
+ result.originalSize,
1599
+ result.compressedSize,
1600
+ reductionPercent,
1601
+ result.originalMessageCount,
1602
+ result.compressedMessageCount
1603
+ );
1604
+ } else {
1605
+ console.log(
1606
+ `${indent}${colors.success(`${icons.success} Compressed ${result.originalMessageCount} → ${result.compressedMessageCount} messages (${reductionPercent}% smaller)`)}`
1607
+ );
1608
+ }
1009
1609
 
1010
1610
  // Summary is embedded in first user message, look for it
1011
1611
  // The format is: "[Conversation Summary - X messages compressed]\n\n${summary}"
@@ -1034,27 +1634,36 @@ export class InteractiveSession {
1034
1634
  summaryContent = summaryContent.substring(0, maxPreviewLength) + '\n...';
1035
1635
  }
1036
1636
 
1037
- console.log('');
1038
- console.log(
1039
- `${indent}${theme.predefinedStyles.title(`${icons.sparkles} Conversation Summary`)}`
1040
- );
1041
- const separator = icons.separator.repeat(
1042
- Math.min(60, process.stdout.columns || 80) - indent.length * 2
1043
- );
1044
- console.log(`${indent}${colors.border(separator)}`);
1045
- const renderedSummary = renderMarkdown(
1046
- summaryContent,
1047
- (process.stdout.columns || 80) - indent.length * 4
1048
- );
1049
- console.log(
1050
- `${indent}${theme.predefinedStyles.dim(renderedSummary).replace(/^/gm, indent)}`
1051
- );
1052
- if (isTruncated) {
1637
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1638
+ this.sdkOutputAdapter.outputContextCompressionSummary(
1639
+ 'Conversation compressed successfully',
1640
+ summaryContent,
1641
+ isTruncated,
1642
+ summaryMessage.content.length
1643
+ );
1644
+ } else {
1645
+ console.log('');
1053
1646
  console.log(
1054
- `${indent}${colors.textMuted(`(... ${summaryMessage.content.length - maxPreviewLength} more chars hidden)`)}`
1647
+ `${indent}${theme.predefinedStyles.title(`${icons.sparkles} Conversation Summary`)}`
1055
1648
  );
1649
+ const separator = icons.separator.repeat(
1650
+ Math.min(60, process.stdout.columns || 80) - indent.length * 2
1651
+ );
1652
+ console.log(`${indent}${colors.border(separator)}`);
1653
+ const renderedSummary = renderMarkdown(
1654
+ summaryContent,
1655
+ (process.stdout.columns || 80) - indent.length * 4
1656
+ );
1657
+ console.log(
1658
+ `${indent}${theme.predefinedStyles.dim(renderedSummary).replace(/^/gm, indent)}`
1659
+ );
1660
+ if (isTruncated) {
1661
+ console.log(
1662
+ `${indent}${colors.textMuted(`(... ${summaryMessage.content.length - maxPreviewLength} more chars hidden)`)}`
1663
+ );
1664
+ }
1665
+ console.log(`${indent}${colors.border(separator)}`);
1056
1666
  }
1057
- console.log(`${indent}${colors.border(separator)}`);
1058
1667
  }
1059
1668
 
1060
1669
  // Sync compressed conversation history to slashCommandHandler
@@ -1135,7 +1744,7 @@ export class InteractiveSession {
1135
1744
  /**
1136
1745
  * Create remote mode LLM caller
1137
1746
  */
1138
- private createRemoteCaller(taskId: string, status: 'begin' | 'continue') {
1747
+ private createRemoteCaller(taskId: string, _status: 'begin' | 'continue') {
1139
1748
  const client = this.remoteAIClient!;
1140
1749
 
1141
1750
 
@@ -1195,17 +1804,25 @@ export class InteractiveSession {
1195
1804
  // Mark that an operation is in progress
1196
1805
  (this as any)._isOperationInProgress = true;
1197
1806
 
1198
- const indent = this.getIndent();
1199
1807
  const thinkingText = colors.textMuted(`Thinking... (Press ESC to cancel)`);
1200
1808
  const icon = colors.primary(icons.brain);
1201
1809
  const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
1202
1810
  let frameIndex = 0;
1203
1811
 
1812
+ // SDK 模式下不显示 spinner
1813
+ const showThinkingSpinner = !this.isSdkMode;
1814
+ let spinnerInterval: NodeJS.Timeout | null = null;
1815
+
1204
1816
  // Custom spinner: only icon rotates, text stays static
1205
- const spinnerInterval = setInterval(() => {
1206
- process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${icon} ${thinkingText}`);
1207
- frameIndex = (frameIndex + 1) % frames.length;
1208
- }, 120);
1817
+ if (showThinkingSpinner) {
1818
+ spinnerInterval = setInterval(() => {
1819
+ process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${icon} ${thinkingText}`);
1820
+ frameIndex = (frameIndex + 1) % frames.length;
1821
+ }, 120);
1822
+ }
1823
+
1824
+ let content = '';
1825
+ let reasoningContent = '';
1209
1826
 
1210
1827
  try {
1211
1828
  const memory = await this.memoryManager.loadMemory();
@@ -1257,30 +1874,20 @@ export class InteractiveSession {
1257
1874
  // Mark that first API call is complete
1258
1875
  this.isFirstApiCall = false;
1259
1876
 
1260
- clearInterval(spinnerInterval);
1877
+ if (spinnerInterval) clearInterval(spinnerInterval);
1261
1878
  process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r'); // Clear spinner line
1262
1879
 
1263
1880
  const assistantMessage = response.choices[0].message;
1264
1881
 
1265
- const content = typeof assistantMessage.content === 'string' ? assistantMessage.content : '';
1266
- const reasoningContent = assistantMessage.reasoning_content || '';
1882
+ content = typeof assistantMessage.content === 'string' ? assistantMessage.content : '';
1883
+ reasoningContent = assistantMessage.reasoning_content || '';
1267
1884
  // Display reasoning content if available and thinking mode is enabled
1268
1885
  if (reasoningContent && this.configManager.getThinkingConfig().enabled) {
1269
1886
  this.displayThinkingContent(reasoningContent);
1270
1887
  }
1271
1888
 
1272
- console.log('');
1273
- console.log(`${indent}${colors.primaryBright(`${icons.robot} Assistant:`)}`);
1274
- console.log(
1275
- `${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`
1276
- );
1277
- console.log('');
1278
- const renderedContent = renderMarkdown(
1279
- content,
1280
- (process.stdout.columns || 80) - indent.length * 2
1281
- );
1282
- console.log(`${indent}${renderedContent.replace(/^/gm, indent)}`);
1283
- console.log('');
1889
+ // Output assistant response
1890
+ this.outputAssistant(content, reasoningContent);
1284
1891
 
1285
1892
  this.conversation.push({
1286
1893
  role: 'assistant',
@@ -1313,15 +1920,28 @@ export class InteractiveSession {
1313
1920
  );
1314
1921
  }
1315
1922
 
1923
+ // Signal request completion to SDK (no tools to execute)
1924
+ if (this.isSdkMode && this.sdkOutputAdapter && this._currentRequestId) {
1925
+ this.sdkOutputAdapter.outputRequestDone(this._currentRequestId, 'success');
1926
+ this._currentRequestId = null;
1927
+ }
1928
+
1316
1929
  // Operation completed successfully, clear the flag
1317
1930
  (this as any)._isOperationInProgress = false;
1318
1931
  } catch (error: any) {
1319
- clearInterval(spinnerInterval);
1932
+ if (spinnerInterval) clearInterval(spinnerInterval);
1320
1933
  process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
1321
1934
 
1322
1935
  // Clear the operation flag
1323
1936
  (this as any)._isOperationInProgress = false;
1324
1937
 
1938
+ // Signal request completion to SDK
1939
+ if (this.isSdkMode && this.sdkOutputAdapter && this._currentRequestId) {
1940
+ const status = error.message === 'Operation cancelled by user' ? 'cancelled' : 'error';
1941
+ this.sdkOutputAdapter.outputRequestDone(this._currentRequestId, status);
1942
+ this._currentRequestId = null;
1943
+ }
1944
+
1325
1945
  if (error.message === 'Operation cancelled by user') {
1326
1946
  // Notify backend to cancel the task
1327
1947
  if (this.remoteAIClient && this.currentTaskId) {
@@ -1423,7 +2043,7 @@ export class InteractiveSession {
1423
2043
  const authConfig = this.configManager.getAuthConfig();
1424
2044
 
1425
2045
  logger.debug('[DEBUG generateRemoteResponse] After re-auth:');
1426
- logger.debug(' - authConfig.apiKey exists:', !!authConfig.apiKey ? 'true' : 'false');
2046
+ logger.debug(' - authConfig.apiKey exists:', authConfig.apiKey ? 'true' : 'false');
1427
2047
 
1428
2048
  // Recreate readline interface after interactive prompt
1429
2049
  this.rl.close();
@@ -1491,7 +2111,7 @@ export class InteractiveSession {
1491
2111
  let parsedParams: any;
1492
2112
  try {
1493
2113
  parsedParams = typeof params === 'string' ? JSON.parse(params) : params;
1494
- } catch (e) {
2114
+ } catch {
1495
2115
  parsedParams = params;
1496
2116
  }
1497
2117
 
@@ -1500,13 +2120,19 @@ export class InteractiveSession {
1500
2120
 
1501
2121
  // Display all tool calls info
1502
2122
  for (const { name, params } of preparedToolCalls) {
1503
- if (showToolDetails) {
1504
- console.log('');
1505
- console.log(`${indent}${colors.warning(`${icons.tool} Tool Call: ${name}`)}`);
1506
- console.log(`${indent}${colors.textDim(JSON.stringify(params, null, 2))}`);
2123
+ // SDK mode: use adapter output
2124
+ if (this.isSdkMode && this.sdkOutputAdapter) {
2125
+ this.sdkOutputAdapter.outputToolStart(name, params);
1507
2126
  } else {
1508
- const toolDescription = this.getToolDescription(name, params);
1509
- console.log(`${indent}${colors.textMuted(`${icons.loading} ${toolDescription}`)}`);
2127
+ // Normal mode: console output
2128
+ if (showToolDetails) {
2129
+ console.log('');
2130
+ console.log(`${indent}${colors.warning(`${icons.tool} Tool Call: ${name}`)}`);
2131
+ console.log(`${indent}${colors.textDim(JSON.stringify(params, null, 2))}`);
2132
+ } else {
2133
+ const toolDescription = this.getToolDescription(name, params);
2134
+ console.log(`${indent}${colors.textMuted(`${icons.loading} ${toolDescription}`)}`);
2135
+ }
1510
2136
  }
1511
2137
  }
1512
2138
 
@@ -1532,7 +2158,7 @@ export class InteractiveSession {
1532
2158
  }
1533
2159
 
1534
2160
  // Process results in the original tool_calls order (critical for Anthropic format APIs)
1535
- let hasError = false;
2161
+ let _hasError = false;
1536
2162
  for (let i = 0; i < preparedToolCalls.length; i++) {
1537
2163
  const toolCall = preparedToolCalls[i];
1538
2164
  const { name: tool, params } = toolCall;
@@ -1555,10 +2181,16 @@ export class InteractiveSession {
1555
2181
  return;
1556
2182
  }
1557
2183
 
1558
- hasError = true;
2184
+ _hasError = true;
1559
2185
 
1560
- console.log('');
1561
- console.log(`${indent}${colors.error(`${icons.cross} Tool Error: ${tool} - ${error}`)}`);
2186
+ // SDK mode: use adapter output
2187
+ if (this.isSdkMode && this.sdkOutputAdapter) {
2188
+ this.sdkOutputAdapter.outputToolError(tool, error);
2189
+ } else {
2190
+ // Normal mode: console output
2191
+ console.log('');
2192
+ console.log(`${indent}${colors.error(`${icons.cross} Tool Error: ${tool} - ${error}`)}`);
2193
+ }
1562
2194
 
1563
2195
  // Add detailed error info including tool name and params for AI understanding and correction
1564
2196
  this.conversation.push({
@@ -1572,200 +2204,208 @@ export class InteractiveSession {
1572
2204
  timestamp: Date.now(),
1573
2205
  });
1574
2206
  } else {
2207
+ // SDK mode: output tool result via adapter
2208
+ if (this.isSdkMode && this.sdkOutputAdapter) {
2209
+ this.sdkOutputAdapter.outputToolResult(tool, result);
2210
+ }
2211
+
1575
2212
  // Use correct indent for gui-subagent tasks
1576
2213
  const isGuiSubagent = tool === 'task' && params?.subagent_type === 'gui-subagent';
1577
2214
  const displayIndent = isGuiSubagent ? indent + ' ' : indent;
1578
2215
 
1579
- // Always show details for todo tools so users can see their task lists
1580
- const isTodoTool = tool === 'todo_write' || tool === 'todo_read';
2216
+ // Normal mode: console output (SDK mode already output via adapter above)
2217
+ if (!this.isSdkMode || !this.sdkOutputAdapter) {
2218
+ // Always show details for todo tools so users can see their task lists
2219
+ const isTodoTool = tool === 'todo_write' || tool === 'todo_read';
1581
2220
 
1582
- // Special handling for edit tool with diff
1583
- const isEditTool = tool === 'Edit';
1584
- const hasDiff = isEditTool && result?.diff;
2221
+ // Special handling for edit tool with diff
2222
+ const isEditTool = tool === 'Edit';
2223
+ const hasDiff = isEditTool && result?.diff;
1585
2224
 
1586
- // Special handling for Write tool with file preview
1587
- const isWriteTool = tool === 'Write';
1588
- const hasFilePreview = isWriteTool && result?.preview;
2225
+ // Special handling for Write tool with file preview
2226
+ const isWriteTool = tool === 'Write';
2227
+ const hasFilePreview = isWriteTool && result?.preview;
1589
2228
 
1590
- // Special handling for DeleteFile tool
1591
- const isDeleteTool = tool === 'DeleteFile';
1592
- const hasDeleteInfo = isDeleteTool && result?.filePath;
2229
+ // Special handling for DeleteFile tool
2230
+ const isDeleteTool = tool === 'DeleteFile';
2231
+ const hasDeleteInfo = isDeleteTool && result?.filePath;
1593
2232
 
1594
- // Special handling for task tool (subagent)
1595
- const isTaskTool = tool === 'task' && params?.subagent_type;
2233
+ // Special handling for task tool (subagent)
2234
+ const isTaskTool = tool === 'task' && params?.subagent_type;
1596
2235
 
1597
- // Check if tool is an MCP wrapper tool by looking up in tool registry
1598
- const { getToolRegistry } = await import('./tools.js');
1599
- const toolRegistry = getToolRegistry();
1600
- const toolDef = toolRegistry.get(tool);
1601
- const isMcpTool = toolDef && (toolDef as any)._isMcpTool === true;
1602
-
1603
- if (isTodoTool) {
1604
- console.log('');
1605
- console.log(`${displayIndent}${colors.success(`${icons.check} Todo List:`)}`);
1606
- console.log(this.renderTodoList(result?.todos || [], displayIndent));
1607
- // Show summary if available
1608
- if (result?.message) {
1609
- console.log(`${displayIndent}${colors.textDim(result.message)}`);
1610
- }
1611
- } else if (hasDiff) {
1612
- // Show edit result with diff
1613
- console.log('');
1614
- const diffOutput = renderDiff(result.diff);
1615
- const indentedDiff = diffOutput
1616
- .split('\n')
1617
- .map((line) => `${displayIndent} ${line}`)
1618
- .join('\n');
1619
- console.log(`${indentedDiff}`);
1620
- } else if (hasFilePreview) {
1621
- // Show new file content in diff-like style
1622
- console.log('');
1623
- console.log(`${displayIndent}${colors.success(`${icons.file} ${result.filePath}`)}`);
1624
- console.log(`${displayIndent}${colors.textDim(` ${result.lineCount} lines`)}`);
1625
- console.log('');
1626
- console.log(renderLines(result.preview, { maxLines: 10, indent: displayIndent + ' ' }));
1627
- } else if (hasDeleteInfo) {
1628
- // Show DeleteFile result
1629
- console.log('');
1630
- console.log(
1631
- `${displayIndent}${colors.success(`${icons.check} Deleted: ${result.filePath}`)}`
1632
- );
1633
- } else if (isTaskTool) {
1634
- // Special handling for task tool (subagent) - show friendly summary
1635
- console.log('');
1636
- const subagentType = params.subagent_type;
1637
- const subagentName =
1638
- params.description ||
1639
- (params.prompt ? params.prompt.substring(0, 50).replace(/\n/g, ' ') : 'Unknown task');
1640
-
1641
- if (result?.success) {
1642
- console.log(
1643
- `${displayIndent}${colors.success(`${icons.check} ${subagentType}: Completed`)}`
1644
- );
1645
- console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1646
- if (result.message) {
1647
- console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
2236
+ // Check if tool is an MCP wrapper tool by looking up in tool registry
2237
+ const { getToolRegistry } = await import('./tools.js');
2238
+ const toolRegistry = getToolRegistry();
2239
+ const toolDef = toolRegistry.get(tool);
2240
+ const isMcpTool = toolDef && (toolDef as any)._isMcpTool === true;
2241
+
2242
+ if (isTodoTool) {
2243
+ console.log('');
2244
+ console.log(`${displayIndent}${colors.success(`${icons.check} Todo List:`)}`);
2245
+ console.log(this.renderTodoList(result?.todos || [], displayIndent));
2246
+ // Show summary if available
2247
+ if (result?.message) {
2248
+ console.log(`${displayIndent}${colors.textDim(result.message)}`);
1648
2249
  }
1649
- } else if (result?.cancelled) {
2250
+ } else if (hasDiff) {
2251
+ // Show edit result with diff
2252
+ console.log('');
2253
+ const diffOutput = renderDiff(result.diff);
2254
+ const indentedDiff = diffOutput
2255
+ .split('\n')
2256
+ .map((line) => `${displayIndent} ${line}`)
2257
+ .join('\n');
2258
+ console.log(`${indentedDiff}`);
2259
+ } else if (hasFilePreview) {
2260
+ // Show new file content in diff-like style
2261
+ console.log('');
2262
+ console.log(`${displayIndent}${colors.success(`${icons.file} ${result.filePath}`)}`);
2263
+ console.log(`${displayIndent}${colors.textDim(` ${result.lineCount} lines`)}`);
2264
+ console.log('');
2265
+ console.log(renderLines(result.preview, { maxLines: 10, indent: displayIndent + ' ' }));
2266
+ } else if (hasDeleteInfo) {
2267
+ // Show DeleteFile result
2268
+ console.log('');
1650
2269
  console.log(
1651
- `${displayIndent}${colors.warning(`${icons.cross} ${subagentType}: Cancelled`)}`
2270
+ `${displayIndent}${colors.success(`${icons.check} Deleted: ${result.filePath}`)}`
1652
2271
  );
1653
- console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1654
- } else {
1655
- console.log(
1656
- `${displayIndent}${colors.error(`${icons.cross} ${subagentType}: Failed`)}`
1657
- );
1658
- console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1659
- if (result?.message) {
1660
- console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
2272
+ } else if (isTaskTool) {
2273
+ // Special handling for task tool (subagent) - show friendly summary
2274
+ console.log('');
2275
+ const subagentType = params.subagent_type;
2276
+ const subagentName =
2277
+ params.description ||
2278
+ (params.prompt ? params.prompt.substring(0, 50).replace(/\n/g, ' ') : 'Unknown task');
2279
+
2280
+ if (result?.success) {
2281
+ console.log(
2282
+ `${displayIndent}${colors.success(`${icons.check} ${subagentType}: Completed`)}`
2283
+ );
2284
+ console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
2285
+ if (result.message) {
2286
+ console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
2287
+ }
2288
+ } else if (result?.cancelled) {
2289
+ console.log(
2290
+ `${displayIndent}${colors.warning(`${icons.cross} ${subagentType}: Cancelled`)}`
2291
+ );
2292
+ console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
2293
+ } else {
2294
+ console.log(
2295
+ `${displayIndent}${colors.error(`${icons.cross} ${subagentType}: Failed`)}`
2296
+ );
2297
+ console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
2298
+ if (result?.message) {
2299
+ console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
2300
+ }
2301
+ }
2302
+ } else if (isMcpTool) {
2303
+ // Special handling for MCP tools - show friendly summary
2304
+ console.log('');
2305
+ // Extract server name and tool name from tool name (format: serverName__toolName)
2306
+ let serverName = 'MCP';
2307
+ let toolDisplayName = tool;
2308
+ if (tool.includes('__')) {
2309
+ const parts = tool.split('__');
2310
+ serverName = parts[0];
2311
+ toolDisplayName = parts.slice(1).join('__');
1661
2312
  }
1662
- }
1663
- } else if (isMcpTool) {
1664
- // Special handling for MCP tools - show friendly summary
1665
- console.log('');
1666
- // Extract server name and tool name from tool name (format: serverName__toolName)
1667
- let serverName = 'MCP';
1668
- let toolDisplayName = tool;
1669
- if (tool.includes('__')) {
1670
- const parts = tool.split('__');
1671
- serverName = parts[0];
1672
- toolDisplayName = parts.slice(1).join('__');
1673
- }
1674
2313
 
1675
- // Try to extract meaningful content from MCP result
1676
- let summary = '';
1677
- if (result?.content && Array.isArray(result.content) && result.content.length > 0) {
1678
- const firstBlock = result.content[0];
1679
- if (firstBlock?.type === 'text' && firstBlock?.text) {
1680
- const text = firstBlock.text;
1681
- if (typeof text === 'string') {
1682
- // Detect HTML content
1683
- if (text.trim().startsWith('<!DOCTYPE') || text.trim().startsWith('<html')) {
1684
- summary = '[HTML content fetched]';
1685
- } else {
1686
- // Try to parse if it's JSON
1687
- try {
1688
- const parsed = JSON.parse(text);
1689
- if (Array.isArray(parsed) && parsed.length > 0 && parsed[0]?.title) {
1690
- // Search results format
1691
- summary = `Found ${parsed.length} result(s)`;
1692
- } else if (parsed?.message) {
1693
- summary = parsed.message;
1694
- } else if (typeof parsed === 'string') {
1695
- summary = parsed.substring(0, 100);
2314
+ // Try to extract meaningful content from MCP result
2315
+ let summary = '';
2316
+ if (result?.content && Array.isArray(result.content) && result.content.length > 0) {
2317
+ const firstBlock = result.content[0];
2318
+ if (firstBlock?.type === 'text' && firstBlock?.text) {
2319
+ const text = firstBlock.text;
2320
+ if (typeof text === 'string') {
2321
+ // Detect HTML content
2322
+ if (text.trim().startsWith('<!DOCTYPE') || text.trim().startsWith('<html')) {
2323
+ summary = '[HTML content fetched]';
2324
+ } else {
2325
+ // Try to parse if it's JSON
2326
+ try {
2327
+ const parsed = JSON.parse(text);
2328
+ if (Array.isArray(parsed) && parsed.length > 0 && parsed[0]?.title) {
2329
+ // Search results format
2330
+ summary = `Found ${parsed.length} result(s)`;
2331
+ } else if (parsed?.message) {
2332
+ summary = parsed.message;
2333
+ } else if (typeof parsed === 'string') {
2334
+ summary = parsed.substring(0, 100);
2335
+ }
2336
+ } catch {
2337
+ // Not JSON, use as-is with truncation
2338
+ summary = text.substring(0, 100);
1696
2339
  }
1697
- } catch {
1698
- // Not JSON, use as-is with truncation
1699
- summary = text.substring(0, 100);
1700
2340
  }
1701
2341
  }
1702
2342
  }
2343
+ } else if (result?.message) {
2344
+ summary = result.message;
1703
2345
  }
1704
- } else if (result?.message) {
1705
- summary = result.message;
1706
- }
1707
2346
 
1708
- if (result?.success !== false) {
1709
- console.log(
1710
- `${displayIndent}${colors.success(`${icons.check} ${serverName}: Success`)}`
1711
- );
1712
- console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
1713
- if (summary) {
1714
- console.log(`${displayIndent}${colors.textDim(` ${summary}`)}`);
1715
- }
1716
- } else {
1717
- console.log(`${displayIndent}${colors.error(`${icons.cross} ${serverName}: Failed`)}`);
1718
- console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
1719
- if (result?.message || result?.error) {
2347
+ if (result?.success !== false) {
1720
2348
  console.log(
1721
- `${displayIndent}${colors.textDim(` ${result?.message || result?.error}`)}`
2349
+ `${displayIndent}${colors.success(`${icons.check} ${serverName}: Success`)}`
1722
2350
  );
2351
+ console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
2352
+ if (summary) {
2353
+ console.log(`${displayIndent}${colors.textDim(` ${summary}`)}`);
2354
+ }
2355
+ } else {
2356
+ console.log(`${displayIndent}${colors.error(`${icons.cross} ${serverName}: Failed`)}`);
2357
+ console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
2358
+ if (result?.message || result?.error) {
2359
+ console.log(
2360
+ `${displayIndent}${colors.textDim(` ${result?.message || result?.error}`)}`
2361
+ );
2362
+ }
1723
2363
  }
1724
- }
1725
- } else if (tool === 'InvokeSkill') {
1726
- // Special handling for InvokeSkill - show friendly summary
1727
- console.log('');
1728
- const skillName = params?.skillId || 'Unknown skill';
1729
- const taskDesc = params?.taskDescription || '';
1730
-
1731
- if (result?.success) {
1732
- console.log(`${displayIndent}${colors.success(`${icons.check} Skill: Completed`)}`);
1733
- console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
1734
- if (taskDesc) {
1735
- const truncatedTask =
1736
- taskDesc.length > 60 ? taskDesc.substring(0, 60) + '...' : taskDesc;
1737
- console.log(`${displayIndent}${colors.textDim(` Task: ${truncatedTask}`)}`);
2364
+ } else if (tool === 'InvokeSkill') {
2365
+ // Special handling for InvokeSkill - show friendly summary
2366
+ console.log('');
2367
+ const skillName = params?.skillId || 'Unknown skill';
2368
+ const taskDesc = params?.taskDescription || '';
2369
+
2370
+ if (result?.success) {
2371
+ console.log(`${displayIndent}${colors.success(`${icons.check} Skill: Completed`)}`);
2372
+ console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
2373
+ if (taskDesc) {
2374
+ const truncatedTask =
2375
+ taskDesc.length > 60 ? taskDesc.substring(0, 60) + '...' : taskDesc;
2376
+ console.log(`${displayIndent}${colors.textDim(` Task: ${truncatedTask}`)}`);
2377
+ }
2378
+ } else {
2379
+ console.log(`${displayIndent}${colors.error(`${icons.cross} Skill: Failed`)}`);
2380
+ console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
2381
+ if (result?.message) {
2382
+ console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
2383
+ }
1738
2384
  }
2385
+ } else if (showToolDetails) {
2386
+ console.log('');
2387
+ console.log(`${displayIndent}${colors.success(`${icons.check} Tool Result:`)}`);
2388
+ console.log(`${displayIndent}${colors.textDim(JSON.stringify(result, null, 2))}`);
2389
+ } else if (result && result.success === false) {
2390
+ // GUI task or other tool failed
2391
+ console.log(
2392
+ `${displayIndent}${colors.error(`${icons.cross} ${result.message || 'Failed'}`)}`
2393
+ );
2394
+ } else if (result) {
2395
+ // Show brief preview by default (consistent with subagent behavior)
2396
+ const resultPreview =
2397
+ typeof result === 'string' ? result : JSON.stringify(result, null, 2);
2398
+ const truncatedPreview =
2399
+ resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
2400
+ // Indent the preview
2401
+ const indentedPreview = truncatedPreview
2402
+ .split('\n')
2403
+ .map((line) => `${displayIndent} ${line}`)
2404
+ .join('\n');
2405
+ console.log(`${indentedPreview}`);
1739
2406
  } else {
1740
- console.log(`${displayIndent}${colors.error(`${icons.cross} Skill: Failed`)}`);
1741
- console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
1742
- if (result?.message) {
1743
- console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
1744
- }
2407
+ console.log(`${displayIndent}${colors.textDim('(no result)')}`);
1745
2408
  }
1746
- } else if (showToolDetails) {
1747
- console.log('');
1748
- console.log(`${displayIndent}${colors.success(`${icons.check} Tool Result:`)}`);
1749
- console.log(`${displayIndent}${colors.textDim(JSON.stringify(result, null, 2))}`);
1750
- } else if (result && result.success === false) {
1751
- // GUI task or other tool failed
1752
- console.log(
1753
- `${displayIndent}${colors.error(`${icons.cross} ${result.message || 'Failed'}`)}`
1754
- );
1755
- } else if (result) {
1756
- // Show brief preview by default (consistent with subagent behavior)
1757
- const resultPreview =
1758
- typeof result === 'string' ? result : JSON.stringify(result, null, 2);
1759
- const truncatedPreview =
1760
- resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
1761
- // Indent the preview
1762
- const indentedPreview = truncatedPreview
1763
- .split('\n')
1764
- .map((line) => `${displayIndent} ${line}`)
1765
- .join('\n');
1766
- console.log(`${indentedPreview}`);
1767
- } else {
1768
- console.log(`${displayIndent}${colors.textDim('(no result)')}`);
1769
2409
  }
1770
2410
 
1771
2411
  const toolCallRecord: ToolCall = {
@@ -2188,7 +2828,7 @@ async function initializeSkillsOnDemand(): Promise<number> {
2188
2828
  }
2189
2829
 
2190
2830
  // Synchronous version (kept for backwards compatibility)
2191
- function copyDirectoryRecursive(src: string, dest: string): void {
2831
+ function _copyDirectoryRecursive(src: string, dest: string): void {
2192
2832
  if (!fs.existsSync(dest)) {
2193
2833
  fs.mkdirSync(dest, { recursive: true });
2194
2834
  }
@@ -2199,7 +2839,7 @@ function copyDirectoryRecursive(src: string, dest: string): void {
2199
2839
  const destPath = path.join(dest, entry.name);
2200
2840
 
2201
2841
  if (entry.isDirectory()) {
2202
- copyDirectoryRecursive(srcPath, destPath);
2842
+ _copyDirectoryRecursive(srcPath, destPath);
2203
2843
  } else if (entry.isFile()) {
2204
2844
  fs.copyFileSync(srcPath, destPath);
2205
2845
  }
@@ -2279,7 +2919,7 @@ export async function startInteractiveSession(): Promise<void> {
2279
2919
  try {
2280
2920
  const { checkUpdatesOnStartup } = await import('./update.js');
2281
2921
  await checkUpdatesOnStartup();
2282
- } catch (error) {
2922
+ } catch {
2283
2923
  // Silently ignore update check failures
2284
2924
  }
2285
2925