@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/session.js CHANGED
@@ -58,6 +58,17 @@ export class InteractiveSession {
58
58
  currentTaskId = null;
59
59
  taskCompleted = false;
60
60
  isFirstApiCall = true;
61
+ sdkOutputAdapter = null;
62
+ isSdkMode = false;
63
+ sdkInputBuffer = [];
64
+ resolveInput = null;
65
+ _currentRequestId = null;
66
+ heartbeatTimeout = null;
67
+ heartbeatTimeoutMs = 300000; // 5 minutes timeout for long AI responses
68
+ lastActivityTime = Date.now();
69
+ // SDK response handling for approvals and questions
70
+ approvalPromises = new Map();
71
+ questionPromises = new Map();
61
72
  constructor(indentLevel = 0) {
62
73
  this.rl = readline.createInterface({
63
74
  input: process.stdin,
@@ -129,6 +140,64 @@ export class InteractiveSession {
129
140
  setExecutionMode(mode) {
130
141
  this.executionMode = mode;
131
142
  }
143
+ /**
144
+ * Set SDK mode for programmatic access.
145
+ * In SDK mode, output is formatted as JSON to stdout.
146
+ */
147
+ setSdkMode(adapter) {
148
+ this.isSdkMode = true;
149
+ this.sdkOutputAdapter = adapter;
150
+ // Initialize SDK mode for other modules using centralized output util
151
+ const { initOutputMode } = require('./output-util.js');
152
+ initOutputMode(true, adapter);
153
+ // Initialize SmartApprovalEngine in SDK mode
154
+ this.initSmartApprovalSdkMode(adapter).catch(() => {
155
+ // Silently ignore errors - not critical
156
+ });
157
+ // Initialize tool registry in SDK mode (fire and forget, doesn't need to await)
158
+ this.initToolRegistrySdkMode(adapter).catch(() => {
159
+ // Silently ignore errors - tool registry init is not critical
160
+ });
161
+ }
162
+ /**
163
+ * Initialize SmartApprovalEngine in SDK mode.
164
+ */
165
+ async initSmartApprovalSdkMode(adapter) {
166
+ const { getSmartApprovalEngine } = await import('./smart-approval.js');
167
+ const approvalEngine = getSmartApprovalEngine();
168
+ approvalEngine.setSdkMode(true, adapter);
169
+ }
170
+ /**
171
+ * Initialize tool registry in SDK mode.
172
+ */
173
+ async initToolRegistrySdkMode(adapter) {
174
+ const toolRegistry = getToolRegistry();
175
+ await toolRegistry.setSdkMode(true, adapter);
176
+ }
177
+ /**
178
+ * Get SDK mode status.
179
+ */
180
+ getIsSdkMode() {
181
+ return this.isSdkMode;
182
+ }
183
+ /**
184
+ * Output assistant response - handles SDK mode and normal mode differently.
185
+ */
186
+ outputAssistant(content, reasoningContent) {
187
+ if (this.isSdkMode && this.sdkOutputAdapter) {
188
+ this.sdkOutputAdapter.outputAssistant(content, reasoningContent);
189
+ }
190
+ else {
191
+ const indent = this.getIndent();
192
+ console.log('');
193
+ console.log(`${indent}${colors.primaryBright(`${icons.robot} Assistant:`)}`);
194
+ console.log(`${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`);
195
+ console.log('');
196
+ const renderedContent = renderMarkdown(content, (process.stdout.columns || 80) - indent.length * 2);
197
+ console.log(`${indent}${renderedContent.replace(/^/gm, indent)}`);
198
+ console.log('');
199
+ }
200
+ }
132
201
  /**
133
202
  * Update system prompt to reflect MCP changes (called after add/remove MCP)
134
203
  */
@@ -169,6 +238,18 @@ export class InteractiveSession {
169
238
  return; // No user skills path configured
170
239
  }
171
240
  let lastUpdateTime = 0;
241
+ // Initialize with current state to avoid triggering update on first check
242
+ try {
243
+ const { existsSync, readFileSync } = require('fs');
244
+ if (existsSync(stateFilePath)) {
245
+ const content = readFileSync(stateFilePath, 'utf-8');
246
+ const state = JSON.parse(content);
247
+ lastUpdateTime = state.lastSkillUpdate || 0;
248
+ }
249
+ }
250
+ catch {
251
+ // Silent fail
252
+ }
172
253
  // Check for updates every 2 seconds
173
254
  const checkInterval = setInterval(async () => {
174
255
  try {
@@ -180,11 +261,13 @@ export class InteractiveSession {
180
261
  lastUpdateTime = state.lastSkillUpdate;
181
262
  // Update system prompt with new skills
182
263
  await this.updateSystemPrompt();
183
- console.log(colors.textMuted(' 🔄 Skills updated from CLI'));
264
+ if (!this.isSdkMode) {
265
+ console.log(colors.textMuted(' 🔄 Skills updated from CLI'));
266
+ }
184
267
  }
185
268
  }
186
269
  }
187
- catch (error) {
270
+ catch {
188
271
  // Silent fail - watcher is optional
189
272
  }
190
273
  }, 2000);
@@ -199,38 +282,46 @@ export class InteractiveSession {
199
282
  setSingletonSession(this);
200
283
  // Initialize taskId for GUI operations
201
284
  this.currentTaskId = crypto.randomUUID();
202
- const separator = icons.separator.repeat(60);
203
- console.log('');
204
- console.log(colors.gradient('╔════════════════════════════════════════════════════════════╗'));
205
- console.log(colors.gradient('') + ' '.repeat(58) + colors.gradient(' ║'));
206
- console.log(colors.gradient('') +
207
- ' '.repeat(13) +
208
- '🤖 ' +
209
- colors.gradient('XAGENT CLI') +
210
- ' '.repeat(32) +
211
- colors.gradient(''));
212
- console.log(colors.gradient('║') +
213
- ' '.repeat(16) +
214
- colors.textMuted(`v${packageJson.version}`) +
215
- ' '.repeat(36) +
216
- colors.gradient(' ║'));
217
- console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
218
- console.log(colors.gradient('╚════════════════════════════════════════════════════════════╝'));
219
- console.log(colors.textMuted(' AI-powered command-line assistant'));
220
- // Show initialization message if skills were initialized
221
- if (initializedCount > 0) {
222
- console.log(colors.textMuted(` ✨ Initialized ${initializedCount} built-in skills`));
285
+ const _separator = icons.separator.repeat(60);
286
+ if (!this.isSdkMode) {
287
+ // Normal mode: show ASCII art welcome
288
+ console.log('');
289
+ console.log(colors.gradient('╔════════════════════════════════════════════════════════════╗'));
290
+ console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
291
+ console.log(colors.gradient('') +
292
+ ' '.repeat(13) +
293
+ '🤖 ' +
294
+ colors.gradient('XAGENT CLI') +
295
+ ' '.repeat(32) +
296
+ colors.gradient(''));
297
+ console.log(colors.gradient('║') +
298
+ ' '.repeat(16) +
299
+ colors.textMuted(`v${packageJson.version}`) +
300
+ ' '.repeat(36) +
301
+ colors.gradient(''));
302
+ console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
303
+ console.log(colors.gradient('╚════════════════════════════════════════════════════════════╝'));
304
+ console.log(colors.textMuted(' AI-powered command-line assistant'));
305
+ // Show initialization message if skills were initialized
306
+ if (initializedCount > 0) {
307
+ console.log(colors.textMuted(` ✨ Initialized ${initializedCount} built-in skills`));
308
+ }
309
+ console.log('');
223
310
  }
224
- console.log('');
225
311
  await this.initialize();
226
312
  this.showWelcomeMessage();
227
313
  // Start watching for skill updates from CLI
228
314
  this.startSkillUpdateWatcher();
315
+ // SDK 模式初始化:设置输出适配器
316
+ if (this.isSdkMode) {
317
+ // Start heartbeat timeout monitoring in SDK mode
318
+ this.startHeartbeatMonitoring();
319
+ }
229
320
  // Set up ESC key handler using the terminal module
230
321
  // This avoids conflicts with readline and provides clean ESC detection
231
- let escCleanup;
322
+ let _escCleanup;
232
323
  if (process.stdin.isTTY) {
233
- escCleanup = setupEscKeyHandler(() => {
324
+ _escCleanup = setupEscKeyHandler(() => {
234
325
  if (this._isOperationInProgress) {
235
326
  // An operation is running, let it be cancelled
236
327
  this.cancellationManager.cancel();
@@ -384,24 +475,45 @@ export class InteractiveSession {
384
475
  this.slashCommandHandler.setConversationHistory(this.conversation);
385
476
  const mcpServers = this.configManager.getMcpServers();
386
477
  Object.entries(mcpServers).forEach(([name, config]) => {
387
- console.log(`📝 Registering MCP server: ${name} (${config.transport})`);
478
+ if (this.isSdkMode && this.sdkOutputAdapter) {
479
+ this.sdkOutputAdapter.outputMCPRegistering(name, config.transport || 'stdio');
480
+ }
481
+ else {
482
+ console.log(`📝 Registering MCP server: ${name} (${config.transport})`);
483
+ }
388
484
  this.mcpManager.registerServer(name, config);
389
485
  });
390
486
  // Eagerly connect to MCP servers to get tool definitions
391
487
  if (mcpServers && Object.keys(mcpServers).length > 0) {
392
488
  try {
393
- console.log(`${colors.info(`${icons.brain} Connecting to ${Object.keys(mcpServers).length} MCP server(s)...`)}`);
489
+ const serverCount = Object.keys(mcpServers).length;
490
+ if (this.isSdkMode && this.sdkOutputAdapter) {
491
+ this.sdkOutputAdapter.outputMCPLoading(serverCount);
492
+ }
493
+ else {
494
+ console.log(`${colors.info(`${icons.brain} Connecting to ${serverCount} MCP server(s)...`)}`);
495
+ }
394
496
  await this.mcpManager.connectAllServers();
395
497
  const connectedCount = Array.from(this.mcpManager.getAllServers()).filter((s) => s.isServerConnected()).length;
396
498
  const mcpTools = this.mcpManager.getToolDefinitions();
397
- console.log(`${colors.success(`✓ ${connectedCount}/${Object.keys(mcpServers).length} MCP server(s) connected (${mcpTools.length} tools available)`)}`);
499
+ if (this.isSdkMode && this.sdkOutputAdapter) {
500
+ this.sdkOutputAdapter.outputMCPConnected(serverCount, connectedCount, mcpTools.length);
501
+ }
502
+ else {
503
+ console.log(`${colors.success(`✓ ${connectedCount}/${serverCount} MCP server(s) connected (${mcpTools.length} tools available)`)}`);
504
+ }
398
505
  // Register MCP tools with the tool registry (hide MCP origin from LLM)
399
506
  const toolRegistry = getToolRegistry();
400
507
  const allMcpTools = this.mcpManager.getAllTools();
401
508
  toolRegistry.registerMCPTools(allMcpTools);
402
509
  }
403
510
  catch (error) {
404
- console.log(`${colors.warning(`⚠ MCP connection failed: ${error.message}`)}`);
511
+ if (this.isSdkMode && this.sdkOutputAdapter) {
512
+ this.sdkOutputAdapter.outputMCPConnectionFailed(error.message);
513
+ }
514
+ else {
515
+ console.log(`${colors.warning(`⚠ MCP connection failed: ${error.message}`)}`);
516
+ }
405
517
  }
406
518
  }
407
519
  const checkpointingConfig = this.configManager.getCheckpointingConfig();
@@ -410,7 +522,12 @@ export class InteractiveSession {
410
522
  await this.checkpointManager.initialize();
411
523
  }
412
524
  this.currentAgent = this.agentManager.getAgent('general-purpose') ?? null;
413
- console.log(colors.success('✔ Initialization complete'));
525
+ if (this.isSdkMode && this.sdkOutputAdapter) {
526
+ this.sdkOutputAdapter.outputSuccess('Initialization complete');
527
+ }
528
+ else {
529
+ console.log(colors.success('✔ Initialization complete'));
530
+ }
414
531
  }
415
532
  catch (error) {
416
533
  const spinner = ora({ text: '', spinner: 'dots', color: 'red' }).start();
@@ -468,7 +585,7 @@ export class InteractiveSession {
468
585
  return null;
469
586
  }
470
587
  }
471
- catch (error) {
588
+ catch {
472
589
  return null;
473
590
  }
474
591
  }
@@ -504,10 +621,15 @@ export class InteractiveSession {
504
621
  // VLM configuration is optional - only show for non-OAuth (local) mode
505
622
  // Remote mode uses backend VLM configuration
506
623
  if (authType !== AuthType.OAUTH_XAGENT) {
507
- console.log('');
508
- console.log(colors.info(`${icons.info} VLM configuration is optional.`));
509
- console.log(colors.info(`You can configure it later using the /model command if needed.`));
510
- console.log('');
624
+ if (this.isSdkMode && this.sdkOutputAdapter) {
625
+ this.sdkOutputAdapter.outputInfo('VLM configuration is optional. You can configure it later using the /model command if needed.');
626
+ }
627
+ else {
628
+ console.log('');
629
+ console.log(colors.info(`${icons.info} VLM configuration is optional.`));
630
+ console.log(colors.info(`You can configure it later using the /model command if needed.`));
631
+ console.log('');
632
+ }
511
633
  }
512
634
  this.configManager.setAuthConfig(authConfig);
513
635
  // Set default remote model settings if not already set
@@ -524,19 +646,72 @@ export class InteractiveSession {
524
646
  showWelcomeMessage() {
525
647
  const language = this.configManager.getLanguage();
526
648
  const separator = icons.separator.repeat(40);
527
- console.log('');
528
- console.log(colors.border(separator));
529
- if (language === 'zh') {
530
- console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
531
- console.log(colors.textMuted('Type /help to see available commands'));
532
- }
533
- else {
534
- console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
535
- console.log(colors.textMuted('Type /help to see available commands'));
649
+ if (!this.isSdkMode) {
650
+ console.log('');
651
+ console.log(colors.border(separator));
652
+ if (language === 'zh') {
653
+ console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
654
+ console.log(colors.textMuted('Type /help to see available commands'));
655
+ }
656
+ else {
657
+ console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
658
+ console.log(colors.textMuted('Type /help to see available commands'));
659
+ }
660
+ console.log(colors.border(separator));
661
+ console.log('');
536
662
  }
537
- console.log(colors.border(separator));
538
- console.log('');
539
663
  this.showExecutionMode();
664
+ // In SDK mode, output ready signal
665
+ if (this.isSdkMode && this.sdkOutputAdapter) {
666
+ this.sdkOutputAdapter.outputReady();
667
+ }
668
+ }
669
+ /**
670
+ * Start heartbeat timeout monitoring in SDK mode
671
+ */
672
+ startHeartbeatMonitoring() {
673
+ this.stopHeartbeatMonitoring();
674
+ // Check heartbeat timeout periodically
675
+ this.heartbeatTimeout = setInterval(() => {
676
+ const elapsed = Date.now() - this.lastActivityTime;
677
+ if (elapsed > this.heartbeatTimeoutMs) {
678
+ // Heartbeat timeout - no activity for too long
679
+ this.sdkOutputAdapter?.output({
680
+ type: 'error',
681
+ subtype: 'heartbeat_timeout',
682
+ timestamp: Date.now(),
683
+ data: {
684
+ message: 'Heartbeat timeout - no activity detected',
685
+ elapsed_ms: elapsed,
686
+ timeout_ms: this.heartbeatTimeoutMs
687
+ }
688
+ });
689
+ // Stop monitoring
690
+ this.stopHeartbeatMonitoring();
691
+ }
692
+ }, 30000); // Check every 30 seconds
693
+ }
694
+ /**
695
+ * Stop heartbeat timeout monitoring
696
+ */
697
+ stopHeartbeatMonitoring() {
698
+ if (this.heartbeatTimeout) {
699
+ clearInterval(this.heartbeatTimeout);
700
+ this.heartbeatTimeout = null;
701
+ }
702
+ }
703
+ /**
704
+ * Reset heartbeat timeout (called on activity)
705
+ */
706
+ resetHeartbeatTimeout() {
707
+ this.lastActivityTime = Date.now();
708
+ }
709
+ /**
710
+ * Stop heartbeat monitoring (public method for cleanup)
711
+ * Used by SDK session to clean up when session ends
712
+ */
713
+ stopHeartbeatMonitor() {
714
+ this.stopHeartbeatMonitoring();
540
715
  }
541
716
  showExecutionMode() {
542
717
  const modeConfig = {
@@ -568,15 +743,32 @@ export class InteractiveSession {
568
743
  };
569
744
  const config = modeConfig[this.executionMode];
570
745
  const modeName = this.executionMode;
571
- console.log(colors.textMuted(`${icons.info} Current Mode:`));
572
- console.log(` ${config.color(config.icon)} ${styleHelpers.text.bold(config.color(modeName))}`);
573
- console.log(` ${colors.textDim(` ${config.description}`)}`);
574
- console.log('');
746
+ if (!this.isSdkMode) {
747
+ console.log(colors.textMuted(`${icons.info} Current Mode:`));
748
+ console.log(` ${config.color(config.icon)} ${styleHelpers.text.bold(config.color(modeName))}`);
749
+ console.log(` ${colors.textDim(` ${config.description}`)}`);
750
+ console.log('');
751
+ }
575
752
  this.showRemoteModelInfo();
576
753
  }
577
754
  showRemoteModelInfo() {
578
755
  const authConfig = this.configManager.getAuthConfig();
579
756
  const isRemote = authConfig.type === AuthType.OAUTH_XAGENT;
757
+ if (this.isSdkMode && this.sdkOutputAdapter) {
758
+ // SDK 模式:通过 adapter 输出
759
+ if (isRemote) {
760
+ const llmModel = authConfig.remote_llmModelName || 'Not set';
761
+ const vlmModel = authConfig.remote_vlmModelName || 'Not set';
762
+ this.sdkOutputAdapter.outputInfo(`Remote Models - LLM: ${llmModel}, VLM: ${vlmModel}`);
763
+ }
764
+ else {
765
+ const modelName = authConfig.modelName || 'Not set';
766
+ const guiSubagentModel = this.configManager.get('guiSubagentModel') || 'Not set';
767
+ this.sdkOutputAdapter.outputInfo(`Local Models - LLM: ${modelName}, VLM: ${guiSubagentModel}`);
768
+ }
769
+ return;
770
+ }
771
+ // 正常模式:控制台输出
580
772
  if (isRemote) {
581
773
  const llmModel = authConfig.remote_llmModelName || colors.textMuted('Not set');
582
774
  const vlmModel = authConfig.remote_vlmModelName || colors.textMuted('Not set');
@@ -598,6 +790,11 @@ export class InteractiveSession {
598
790
  if (this._isShuttingDown) {
599
791
  return;
600
792
  }
793
+ // In SDK mode, use a different input loop
794
+ if (this.isSdkMode) {
795
+ await this.sdkPromptLoop();
796
+ return;
797
+ }
601
798
  // Recreate readline interface for input
602
799
  if (this.rl) {
603
800
  this.rl.close();
@@ -624,10 +821,53 @@ export class InteractiveSession {
624
821
  });
625
822
  }
626
823
  async handleInput(input) {
824
+ // Reset heartbeat timeout on any input activity
825
+ this.resetHeartbeatTimeout();
627
826
  const trimmedInput = input.trim();
628
827
  if (!trimmedInput) {
629
828
  return;
630
829
  }
830
+ // Check for SDK JSON message format
831
+ if (this.isSdkMode) {
832
+ const { isSdkMessage, parseSdkMessage } = await import('./types.js');
833
+ if (isSdkMessage(trimmedInput)) {
834
+ const sdkMessage = parseSdkMessage(trimmedInput);
835
+ if (sdkMessage) {
836
+ if (sdkMessage.type === 'ping') {
837
+ // Handle ping - respond with pong
838
+ await this.handlePing(sdkMessage);
839
+ return;
840
+ }
841
+ else if (sdkMessage.type === 'control_request') {
842
+ // Handle control request
843
+ await this.handleControlRequest(sdkMessage);
844
+ return;
845
+ }
846
+ else if (sdkMessage.type === 'user') {
847
+ // Store request_id for tracking
848
+ this._currentRequestId = sdkMessage.request_id || null;
849
+ // Handle user message from SDK
850
+ await this.processUserMessage(sdkMessage.content);
851
+ return;
852
+ }
853
+ else if (sdkMessage.type === 'approval_response') {
854
+ // Handle approval response
855
+ await this.handleApprovalResponse(sdkMessage);
856
+ return;
857
+ }
858
+ else if (sdkMessage.type === 'question_response') {
859
+ // Handle question response
860
+ await this.handleQuestionResponse(sdkMessage);
861
+ return;
862
+ }
863
+ }
864
+ }
865
+ else {
866
+ // Not a JSON SDK message, treat as regular text
867
+ await this.processUserMessage(trimmedInput);
868
+ return;
869
+ }
870
+ }
631
871
  if (trimmedInput.startsWith('/')) {
632
872
  const handled = await this.slashCommandHandler.handleCommand(trimmedInput);
633
873
  if (handled) {
@@ -644,6 +884,297 @@ export class InteractiveSession {
644
884
  }
645
885
  await this.processUserMessage(trimmedInput);
646
886
  }
887
+ /**
888
+ * SDK prompt loop - reads input from stdin without showing prompt
889
+ */
890
+ async sdkPromptLoop() {
891
+ // Read input from stdin directly without outputting prompt
892
+ const input = await this.readSdkInput();
893
+ if (this._isShuttingDown || input === null) {
894
+ return;
895
+ }
896
+ try {
897
+ await this.handleInput(input);
898
+ }
899
+ catch (err) {
900
+ this.sdkOutputAdapter?.output({
901
+ type: 'error',
902
+ subtype: 'general',
903
+ timestamp: Date.now(),
904
+ data: { message: err.message }
905
+ });
906
+ }
907
+ // Continue the loop
908
+ this.sdkPromptLoop();
909
+ }
910
+ sdkRl = null;
911
+ sdkInputProcessing = false;
912
+ /**
913
+ * Check if a line is an SDK control message (approval_response, question_response)
914
+ */
915
+ isSdkControlMessage(line) {
916
+ try {
917
+ const parsed = JSON.parse(line);
918
+ return parsed.type === 'approval_response' || parsed.type === 'question_response';
919
+ }
920
+ catch {
921
+ return false;
922
+ }
923
+ }
924
+ /**
925
+ * Process an SDK input line (returns true if processed)
926
+ */
927
+ async processSdkInputLine(line) {
928
+ const { isSdkMessage, parseSdkMessage } = await import('./types.js');
929
+ if (isSdkMessage(line)) {
930
+ const sdkMessage = parseSdkMessage(line);
931
+ if (sdkMessage) {
932
+ if (sdkMessage.type === 'ping') {
933
+ await this.handlePing(sdkMessage);
934
+ return true;
935
+ }
936
+ else if (sdkMessage.type === 'control_request') {
937
+ await this.handleControlRequest(sdkMessage);
938
+ return true;
939
+ }
940
+ else if (sdkMessage.type === 'user') {
941
+ this._currentRequestId = sdkMessage.request_id || null;
942
+ await this.processUserMessage(sdkMessage.content);
943
+ return true;
944
+ }
945
+ else if (sdkMessage.type === 'approval_response') {
946
+ await this.handleApprovalResponse(sdkMessage);
947
+ return true;
948
+ }
949
+ else if (sdkMessage.type === 'question_response') {
950
+ await this.handleQuestionResponse(sdkMessage);
951
+ return true;
952
+ }
953
+ }
954
+ }
955
+ return false;
956
+ }
957
+ /**
958
+ * Read a line of input from stdin (SDK mode)
959
+ * Uses readline 'line' event for reliable stdin reading
960
+ * SDK control messages (approval_response, question_response) bypass the main loop
961
+ */
962
+ readSdkInput() {
963
+ return new Promise((resolve) => {
964
+ // Create readline interface if not exists
965
+ if (!this.sdkRl) {
966
+ this.sdkRl = readline.createInterface({
967
+ input: process.stdin,
968
+ crlfDelay: Infinity,
969
+ });
970
+ // Handle line events - SDK control messages bypass normal flow
971
+ this.sdkRl.on('line', async (line) => {
972
+ const cleanLine = line
973
+ .replace(/^\uFEFF/, '')
974
+ // eslint-disable-next-line no-control-regex
975
+ .replace(/[\x00-\x1F\x7F-\x9F]/g, '');
976
+ // Check if this is an SDK control message
977
+ if (this.isSdkControlMessage(cleanLine)) {
978
+ // Process immediately, don't wait for main loop
979
+ // We need to set a flag to prevent re-entrancy issues
980
+ if (this.sdkInputProcessing) {
981
+ // Already processing, queue it
982
+ this.sdkInputBuffer.push(cleanLine);
983
+ }
984
+ else {
985
+ this.sdkInputProcessing = true;
986
+ try {
987
+ await this.processSdkInputLine(cleanLine);
988
+ }
989
+ finally {
990
+ this.sdkInputProcessing = false;
991
+ }
992
+ }
993
+ return;
994
+ }
995
+ // Regular input
996
+ if (this.resolveInput) {
997
+ // Immediate handler available, resolve immediately
998
+ this.resolveInput(cleanLine);
999
+ this.resolveInput = null;
1000
+ }
1001
+ else {
1002
+ // No handler available, queue the message
1003
+ this.sdkInputBuffer.push(cleanLine);
1004
+ }
1005
+ });
1006
+ // Handle close events
1007
+ this.sdkRl.on('close', () => {
1008
+ if (this.resolveInput) {
1009
+ this.resolveInput(null);
1010
+ this.resolveInput = null;
1011
+ }
1012
+ });
1013
+ // Handle errors
1014
+ this.sdkRl.on('error', () => {
1015
+ if (this.resolveInput) {
1016
+ this.resolveInput(null);
1017
+ this.resolveInput = null;
1018
+ }
1019
+ });
1020
+ }
1021
+ // Check for SDK control messages in buffer first
1022
+ for (let i = 0; i < this.sdkInputBuffer.length; i++) {
1023
+ const line = this.sdkInputBuffer[i];
1024
+ if (this.isSdkControlMessage(line)) {
1025
+ this.sdkInputBuffer.splice(i, 1);
1026
+ resolve(line);
1027
+ return;
1028
+ }
1029
+ }
1030
+ if (this.sdkInputBuffer.length > 0) {
1031
+ const line = this.sdkInputBuffer.shift();
1032
+ resolve(line);
1033
+ return;
1034
+ }
1035
+ // Set up the resolve callback
1036
+ this.resolveInput = (value) => {
1037
+ resolve(value);
1038
+ };
1039
+ });
1040
+ }
1041
+ /**
1042
+ * Handle SDK ping messages (heartbeat)
1043
+ */
1044
+ async handlePing(pingMessage) {
1045
+ const requestId = pingMessage.request_id || `ping_${Date.now()}`;
1046
+ // Reset activity timestamp on ping (heartbeat activity)
1047
+ this.lastActivityTime = Date.now();
1048
+ // Send pong response through SDK adapter for consistency
1049
+ this.sdkOutputAdapter?.output({
1050
+ type: 'system',
1051
+ subtype: 'pong',
1052
+ timestamp: Date.now(),
1053
+ data: {
1054
+ type: 'pong',
1055
+ request_id: requestId,
1056
+ timestamp: Date.now()
1057
+ }
1058
+ });
1059
+ }
1060
+ /**
1061
+ * Handle SDK control requests
1062
+ */
1063
+ async handleControlRequest(request) {
1064
+ // Update activity to prevent heartbeat timeout during control requests
1065
+ this.lastActivityTime = Date.now();
1066
+ const { request_id, request: req } = request;
1067
+ switch (req.subtype) {
1068
+ case 'interrupt':
1069
+ this.sdkOutputAdapter?.outputSystem('interrupt', { request_id });
1070
+ this._isShuttingDown = true;
1071
+ process.exit(0);
1072
+ break;
1073
+ case 'set_permission_mode':
1074
+ {
1075
+ const { ExecutionMode } = await import('./types.js');
1076
+ const modeMap = {
1077
+ 'default': ExecutionMode.DEFAULT,
1078
+ 'acceptEdits': ExecutionMode.ACCEPT_EDITS,
1079
+ 'plan': ExecutionMode.PLAN,
1080
+ 'bypassPermissions': ExecutionMode.YOLO,
1081
+ };
1082
+ const mode = modeMap[req.mode] || ExecutionMode.SMART;
1083
+ this.executionMode = mode;
1084
+ this.sdkOutputAdapter?.outputSystem('permission_mode_changed', {
1085
+ request_id,
1086
+ mode: req.mode
1087
+ });
1088
+ }
1089
+ break;
1090
+ case 'set_model':
1091
+ this.sdkOutputAdapter?.outputSystem('model_changed', {
1092
+ request_id,
1093
+ model: req.model
1094
+ });
1095
+ break;
1096
+ default:
1097
+ this.sdkOutputAdapter?.outputWarning(`Unknown control request: ${req.subtype}`);
1098
+ }
1099
+ }
1100
+ /**
1101
+ * Handle SDK approval responses
1102
+ */
1103
+ async handleApprovalResponse(response) {
1104
+ const { request_id, approved } = response;
1105
+ const pending = this.approvalPromises.get(request_id);
1106
+ if (pending) {
1107
+ pending.resolve(approved);
1108
+ this.approvalPromises.delete(request_id);
1109
+ }
1110
+ else {
1111
+ this.sdkOutputAdapter?.outputWarning(`Unknown approval request ID: ${request_id}`);
1112
+ }
1113
+ }
1114
+ /**
1115
+ * Handle SDK question responses
1116
+ */
1117
+ async handleQuestionResponse(response) {
1118
+ const { request_id, answers } = response;
1119
+ const pending = this.questionPromises.get(request_id);
1120
+ if (pending) {
1121
+ pending.resolve(answers);
1122
+ this.questionPromises.delete(request_id);
1123
+ }
1124
+ else {
1125
+ this.sdkOutputAdapter?.outputWarning(`Unknown question request ID: ${request_id}`);
1126
+ }
1127
+ }
1128
+ /**
1129
+ * Wait for SDK approval response
1130
+ */
1131
+ async waitForApprovalResponse(requestId, timeoutMs = 300000) {
1132
+ return new Promise((resolve, reject) => {
1133
+ const timeout = setTimeout(() => {
1134
+ const pending = this.approvalPromises.get(requestId);
1135
+ if (pending) {
1136
+ pending.reject(new Error('Approval request timeout'));
1137
+ this.approvalPromises.delete(requestId);
1138
+ }
1139
+ resolve(false);
1140
+ }, timeoutMs);
1141
+ this.approvalPromises.set(requestId, {
1142
+ resolve: (approved) => {
1143
+ clearTimeout(timeout);
1144
+ resolve(approved);
1145
+ },
1146
+ reject: (err) => {
1147
+ clearTimeout(timeout);
1148
+ reject(err);
1149
+ }
1150
+ });
1151
+ });
1152
+ }
1153
+ /**
1154
+ * Wait for SDK question response
1155
+ */
1156
+ async waitForQuestionResponse(requestId, timeoutMs = 300000) {
1157
+ return new Promise((resolve, reject) => {
1158
+ const timeout = setTimeout(() => {
1159
+ const pending = this.questionPromises.get(requestId);
1160
+ if (pending) {
1161
+ pending.reject(new Error('Question request timeout'));
1162
+ this.questionPromises.delete(requestId);
1163
+ }
1164
+ resolve([]); // Return empty array on timeout instead of rejecting
1165
+ }, timeoutMs);
1166
+ this.questionPromises.set(requestId, {
1167
+ resolve: (answers) => {
1168
+ clearTimeout(timeout);
1169
+ resolve(answers);
1170
+ },
1171
+ reject: (err) => {
1172
+ clearTimeout(timeout);
1173
+ reject(err);
1174
+ }
1175
+ });
1176
+ });
1177
+ }
647
1178
  async handleSubAgentCommand(input) {
648
1179
  const [agentType, ...taskParts] = input.slice(1).split(' ');
649
1180
  const task = taskParts.join(' ');
@@ -662,8 +1193,8 @@ export class InteractiveSession {
662
1193
  this.currentAgent = agent;
663
1194
  await this.processUserMessage(task, agent);
664
1195
  }
665
- async processUserMessage(message, agent) {
666
- const inputs = parseInput(message);
1196
+ async processUserMessage(message, _agent) {
1197
+ const inputs = await parseInput(message);
667
1198
  const textInput = inputs.find((i) => i.type === 'text');
668
1199
  const fileInputs = inputs.filter((i) => i.type === 'file');
669
1200
  const commandInput = inputs.find((i) => i.type === 'command');
@@ -723,6 +1254,11 @@ export class InteractiveSession {
723
1254
  const indent = this.getIndent();
724
1255
  const thinkingConfig = this.configManager.getThinkingConfig();
725
1256
  const displayMode = thinkingConfig.displayMode || 'compact';
1257
+ // SDK 模式:使用 adapter 输出
1258
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1259
+ this.sdkOutputAdapter.outputThinking(reasoningContent, displayMode);
1260
+ return;
1261
+ }
726
1262
  const separator = icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length);
727
1263
  console.log('');
728
1264
  console.log(`${indent}${colors.border(separator)}`);
@@ -733,7 +1269,7 @@ export class InteractiveSession {
733
1269
  console.log('');
734
1270
  console.log(`${indent}${colors.textDim(reasoningContent.replace(/^/gm, indent))}`);
735
1271
  break;
736
- case 'compact':
1272
+ case 'compact': {
737
1273
  // Compact display, truncate partial content
738
1274
  const maxLength = 500;
739
1275
  const truncatedContent = reasoningContent.length > maxLength
@@ -744,6 +1280,7 @@ export class InteractiveSession {
744
1280
  console.log(`${indent}${colors.textDim(truncatedContent.replace(/^/gm, indent))}`);
745
1281
  console.log(`${indent}${colors.textDim(`[${reasoningContent.length} chars total]`)}`);
746
1282
  break;
1283
+ }
747
1284
  case 'indicator':
748
1285
  // Show indicator only
749
1286
  console.log(`${indent}${colors.textDim(`${icons.brain} Thinking process completed`)}`);
@@ -766,7 +1303,7 @@ export class InteractiveSession {
766
1303
  return;
767
1304
  }
768
1305
  const indent = this.getIndent();
769
- const currentTokens = this.contextCompressor.estimateContextTokens(this.conversation);
1306
+ const _currentTokens = this.contextCompressor.estimateContextTokens(this.conversation);
770
1307
  const currentMessages = this.conversation.length;
771
1308
  const { needsCompression, reason, tokenCount } = this.contextCompressor.needsCompression(this.conversation, compressionConfig);
772
1309
  if (!needsCompression) {
@@ -777,8 +1314,13 @@ export class InteractiveSession {
777
1314
  const contextWindowMatch = reason.match(/contextWindow:\s*(\d+)/);
778
1315
  const threshold = thresholdMatch ? parseInt(thresholdMatch[1], 10) : 0;
779
1316
  const contextWindow = contextWindowMatch ? parseInt(contextWindowMatch[1], 10) : 0;
780
- console.log('');
781
- console.log(`${indent}${colors.success(`${icons.sparkles} Compressing context (${currentMessages} msgs, ${tokenCount.toLocaleString()} > ${threshold.toLocaleString()}/${contextWindow.toLocaleString()} tokens, ${Math.round((tokenCount / contextWindow) * 100)}% of context window)...`)}`);
1317
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1318
+ this.sdkOutputAdapter.outputContextCompressionTriggered(reason);
1319
+ }
1320
+ else {
1321
+ console.log('');
1322
+ console.log(`${indent}${colors.success(`${icons.sparkles} Compressing context (${currentMessages} msgs, ${tokenCount.toLocaleString()} > ${threshold.toLocaleString()}/${contextWindow.toLocaleString()} tokens, ${Math.round((tokenCount / contextWindow) * 100)}% of context window)...`)}`);
1323
+ }
782
1324
  const toolRegistry = getToolRegistry();
783
1325
  const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are a helpful AI assistant.';
784
1326
  const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode);
@@ -787,7 +1329,12 @@ export class InteractiveSession {
787
1329
  if (result.wasCompressed) {
788
1330
  this.conversation = result.compressedMessages;
789
1331
  const reductionPercent = Math.round((1 - result.compressedSize / result.originalSize) * 100);
790
- console.log(`${indent}${colors.success(`${icons.success} Compressed ${result.originalMessageCount} → ${result.compressedMessageCount} messages (${reductionPercent}% smaller)`)}`);
1332
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1333
+ this.sdkOutputAdapter.outputContextCompressionResult(result.originalSize, result.compressedSize, reductionPercent, result.originalMessageCount, result.compressedMessageCount);
1334
+ }
1335
+ else {
1336
+ console.log(`${indent}${colors.success(`${icons.success} Compressed ${result.originalMessageCount} → ${result.compressedMessageCount} messages (${reductionPercent}% smaller)`)}`);
1337
+ }
791
1338
  // Summary is embedded in first user message, look for it
792
1339
  // The format is: "[Conversation Summary - X messages compressed]\n\n${summary}"
793
1340
  let summaryMessage = result.compressedMessages.find((m) => m.role === 'user' && m.content.includes('[Conversation Summary'));
@@ -809,16 +1356,21 @@ export class InteractiveSession {
809
1356
  if (isTruncated) {
810
1357
  summaryContent = summaryContent.substring(0, maxPreviewLength) + '\n...';
811
1358
  }
812
- console.log('');
813
- console.log(`${indent}${theme.predefinedStyles.title(`${icons.sparkles} Conversation Summary`)}`);
814
- const separator = icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length * 2);
815
- console.log(`${indent}${colors.border(separator)}`);
816
- const renderedSummary = renderMarkdown(summaryContent, (process.stdout.columns || 80) - indent.length * 4);
817
- console.log(`${indent}${theme.predefinedStyles.dim(renderedSummary).replace(/^/gm, indent)}`);
818
- if (isTruncated) {
819
- console.log(`${indent}${colors.textMuted(`(... ${summaryMessage.content.length - maxPreviewLength} more chars hidden)`)}`);
1359
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1360
+ this.sdkOutputAdapter.outputContextCompressionSummary('Conversation compressed successfully', summaryContent, isTruncated, summaryMessage.content.length);
1361
+ }
1362
+ else {
1363
+ console.log('');
1364
+ console.log(`${indent}${theme.predefinedStyles.title(`${icons.sparkles} Conversation Summary`)}`);
1365
+ const separator = icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length * 2);
1366
+ console.log(`${indent}${colors.border(separator)}`);
1367
+ const renderedSummary = renderMarkdown(summaryContent, (process.stdout.columns || 80) - indent.length * 4);
1368
+ console.log(`${indent}${theme.predefinedStyles.dim(renderedSummary).replace(/^/gm, indent)}`);
1369
+ if (isTruncated) {
1370
+ console.log(`${indent}${colors.textMuted(`(... ${summaryMessage.content.length - maxPreviewLength} more chars hidden)`)}`);
1371
+ }
1372
+ console.log(`${indent}${colors.border(separator)}`);
820
1373
  }
821
- console.log(`${indent}${colors.border(separator)}`);
822
1374
  }
823
1375
  // Sync compressed conversation history to slashCommandHandler
824
1376
  this.slashCommandHandler.setConversationHistory(this.conversation);
@@ -885,7 +1437,7 @@ export class InteractiveSession {
885
1437
  /**
886
1438
  * Create remote mode LLM caller
887
1439
  */
888
- createRemoteCaller(taskId, status) {
1440
+ createRemoteCaller(taskId, _status) {
889
1441
  const client = this.remoteAIClient;
890
1442
  return {
891
1443
  chatCompletion: (messages, options) => {
@@ -931,16 +1483,22 @@ export class InteractiveSession {
931
1483
  }
932
1484
  // Mark that an operation is in progress
933
1485
  this._isOperationInProgress = true;
934
- const indent = this.getIndent();
935
1486
  const thinkingText = colors.textMuted(`Thinking... (Press ESC to cancel)`);
936
1487
  const icon = colors.primary(icons.brain);
937
1488
  const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
938
1489
  let frameIndex = 0;
1490
+ // SDK 模式下不显示 spinner
1491
+ const showThinkingSpinner = !this.isSdkMode;
1492
+ let spinnerInterval = null;
939
1493
  // Custom spinner: only icon rotates, text stays static
940
- const spinnerInterval = setInterval(() => {
941
- process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${icon} ${thinkingText}`);
942
- frameIndex = (frameIndex + 1) % frames.length;
943
- }, 120);
1494
+ if (showThinkingSpinner) {
1495
+ spinnerInterval = setInterval(() => {
1496
+ process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${icon} ${thinkingText}`);
1497
+ frameIndex = (frameIndex + 1) % frames.length;
1498
+ }, 120);
1499
+ }
1500
+ let content = '';
1501
+ let reasoningContent = '';
944
1502
  try {
945
1503
  const memory = await this.memoryManager.loadMemory();
946
1504
  const toolRegistry = getToolRegistry();
@@ -971,22 +1529,18 @@ export class InteractiveSession {
971
1529
  }), operationId);
972
1530
  // Mark that first API call is complete
973
1531
  this.isFirstApiCall = false;
974
- clearInterval(spinnerInterval);
1532
+ if (spinnerInterval)
1533
+ clearInterval(spinnerInterval);
975
1534
  process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r'); // Clear spinner line
976
1535
  const assistantMessage = response.choices[0].message;
977
- const content = typeof assistantMessage.content === 'string' ? assistantMessage.content : '';
978
- const reasoningContent = assistantMessage.reasoning_content || '';
1536
+ content = typeof assistantMessage.content === 'string' ? assistantMessage.content : '';
1537
+ reasoningContent = assistantMessage.reasoning_content || '';
979
1538
  // Display reasoning content if available and thinking mode is enabled
980
1539
  if (reasoningContent && this.configManager.getThinkingConfig().enabled) {
981
1540
  this.displayThinkingContent(reasoningContent);
982
1541
  }
983
- console.log('');
984
- console.log(`${indent}${colors.primaryBright(`${icons.robot} Assistant:`)}`);
985
- console.log(`${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`);
986
- console.log('');
987
- const renderedContent = renderMarkdown(content, (process.stdout.columns || 80) - indent.length * 2);
988
- console.log(`${indent}${renderedContent.replace(/^/gm, indent)}`);
989
- console.log('');
1542
+ // Output assistant response
1543
+ this.outputAssistant(content, reasoningContent);
990
1544
  this.conversation.push({
991
1545
  role: 'assistant',
992
1546
  content,
@@ -1011,14 +1565,26 @@ export class InteractiveSession {
1011
1565
  if (this.checkpointManager.isEnabled()) {
1012
1566
  await this.checkpointManager.createCheckpoint(`Response generated at ${new Date().toLocaleString()}`, [...this.conversation], [...this.tool_calls]);
1013
1567
  }
1568
+ // Signal request completion to SDK (no tools to execute)
1569
+ if (this.isSdkMode && this.sdkOutputAdapter && this._currentRequestId) {
1570
+ this.sdkOutputAdapter.outputRequestDone(this._currentRequestId, 'success');
1571
+ this._currentRequestId = null;
1572
+ }
1014
1573
  // Operation completed successfully, clear the flag
1015
1574
  this._isOperationInProgress = false;
1016
1575
  }
1017
1576
  catch (error) {
1018
- clearInterval(spinnerInterval);
1577
+ if (spinnerInterval)
1578
+ clearInterval(spinnerInterval);
1019
1579
  process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
1020
1580
  // Clear the operation flag
1021
1581
  this._isOperationInProgress = false;
1582
+ // Signal request completion to SDK
1583
+ if (this.isSdkMode && this.sdkOutputAdapter && this._currentRequestId) {
1584
+ const status = error.message === 'Operation cancelled by user' ? 'cancelled' : 'error';
1585
+ this.sdkOutputAdapter.outputRequestDone(this._currentRequestId, status);
1586
+ this._currentRequestId = null;
1587
+ }
1022
1588
  if (error.message === 'Operation cancelled by user') {
1023
1589
  // Notify backend to cancel the task
1024
1590
  if (this.remoteAIClient && this.currentTaskId) {
@@ -1095,7 +1661,7 @@ export class InteractiveSession {
1095
1661
  this.configManager.load();
1096
1662
  const authConfig = this.configManager.getAuthConfig();
1097
1663
  logger.debug('[DEBUG generateRemoteResponse] After re-auth:');
1098
- logger.debug(' - authConfig.apiKey exists:', !!authConfig.apiKey ? 'true' : 'false');
1664
+ logger.debug(' - authConfig.apiKey exists:', authConfig.apiKey ? 'true' : 'false');
1099
1665
  // Recreate readline interface after interactive prompt
1100
1666
  this.rl.close();
1101
1667
  this.rl = readline.createInterface({
@@ -1145,21 +1711,28 @@ export class InteractiveSession {
1145
1711
  try {
1146
1712
  parsedParams = typeof params === 'string' ? JSON.parse(params) : params;
1147
1713
  }
1148
- catch (e) {
1714
+ catch {
1149
1715
  parsedParams = params;
1150
1716
  }
1151
1717
  return { name, params: parsedParams, index, id: toolCall.id };
1152
1718
  });
1153
1719
  // Display all tool calls info
1154
1720
  for (const { name, params } of preparedToolCalls) {
1155
- if (showToolDetails) {
1156
- console.log('');
1157
- console.log(`${indent}${colors.warning(`${icons.tool} Tool Call: ${name}`)}`);
1158
- console.log(`${indent}${colors.textDim(JSON.stringify(params, null, 2))}`);
1721
+ // SDK mode: use adapter output
1722
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1723
+ this.sdkOutputAdapter.outputToolStart(name, params);
1159
1724
  }
1160
1725
  else {
1161
- const toolDescription = this.getToolDescription(name, params);
1162
- console.log(`${indent}${colors.textMuted(`${icons.loading} ${toolDescription}`)}`);
1726
+ // Normal mode: console output
1727
+ if (showToolDetails) {
1728
+ console.log('');
1729
+ console.log(`${indent}${colors.warning(`${icons.tool} Tool Call: ${name}`)}`);
1730
+ console.log(`${indent}${colors.textDim(JSON.stringify(params, null, 2))}`);
1731
+ }
1732
+ else {
1733
+ const toolDescription = this.getToolDescription(name, params);
1734
+ console.log(`${indent}${colors.textMuted(`${icons.loading} ${toolDescription}`)}`);
1735
+ }
1163
1736
  }
1164
1737
  }
1165
1738
  // Execute all tools in parallel
@@ -1176,7 +1749,7 @@ export class InteractiveSession {
1176
1749
  }
1177
1750
  }
1178
1751
  // Process results in the original tool_calls order (critical for Anthropic format APIs)
1179
- let hasError = false;
1752
+ let _hasError = false;
1180
1753
  for (let i = 0; i < preparedToolCalls.length; i++) {
1181
1754
  const toolCall = preparedToolCalls[i];
1182
1755
  const { name: tool, params } = toolCall;
@@ -1194,9 +1767,16 @@ export class InteractiveSession {
1194
1767
  this._isOperationInProgress = false;
1195
1768
  return;
1196
1769
  }
1197
- hasError = true;
1198
- console.log('');
1199
- console.log(`${indent}${colors.error(`${icons.cross} Tool Error: ${tool} - ${error}`)}`);
1770
+ _hasError = true;
1771
+ // SDK mode: use adapter output
1772
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1773
+ this.sdkOutputAdapter.outputToolError(tool, error);
1774
+ }
1775
+ else {
1776
+ // Normal mode: console output
1777
+ console.log('');
1778
+ console.log(`${indent}${colors.error(`${icons.cross} Tool Error: ${tool} - ${error}`)}`);
1779
+ }
1200
1780
  // Add detailed error info including tool name and params for AI understanding and correction
1201
1781
  this.conversation.push({
1202
1782
  role: 'tool',
@@ -1210,191 +1790,198 @@ export class InteractiveSession {
1210
1790
  });
1211
1791
  }
1212
1792
  else {
1793
+ // SDK mode: output tool result via adapter
1794
+ if (this.isSdkMode && this.sdkOutputAdapter) {
1795
+ this.sdkOutputAdapter.outputToolResult(tool, result);
1796
+ }
1213
1797
  // Use correct indent for gui-subagent tasks
1214
1798
  const isGuiSubagent = tool === 'task' && params?.subagent_type === 'gui-subagent';
1215
1799
  const displayIndent = isGuiSubagent ? indent + ' ' : indent;
1216
- // Always show details for todo tools so users can see their task lists
1217
- const isTodoTool = tool === 'todo_write' || tool === 'todo_read';
1218
- // Special handling for edit tool with diff
1219
- const isEditTool = tool === 'Edit';
1220
- const hasDiff = isEditTool && result?.diff;
1221
- // Special handling for Write tool with file preview
1222
- const isWriteTool = tool === 'Write';
1223
- const hasFilePreview = isWriteTool && result?.preview;
1224
- // Special handling for DeleteFile tool
1225
- const isDeleteTool = tool === 'DeleteFile';
1226
- const hasDeleteInfo = isDeleteTool && result?.filePath;
1227
- // Special handling for task tool (subagent)
1228
- const isTaskTool = tool === 'task' && params?.subagent_type;
1229
- // Check if tool is an MCP wrapper tool by looking up in tool registry
1230
- const { getToolRegistry } = await import('./tools.js');
1231
- const toolRegistry = getToolRegistry();
1232
- const toolDef = toolRegistry.get(tool);
1233
- const isMcpTool = toolDef && toolDef._isMcpTool === true;
1234
- if (isTodoTool) {
1235
- console.log('');
1236
- console.log(`${displayIndent}${colors.success(`${icons.check} Todo List:`)}`);
1237
- console.log(this.renderTodoList(result?.todos || [], displayIndent));
1238
- // Show summary if available
1239
- if (result?.message) {
1240
- console.log(`${displayIndent}${colors.textDim(result.message)}`);
1241
- }
1242
- }
1243
- else if (hasDiff) {
1244
- // Show edit result with diff
1245
- console.log('');
1246
- const diffOutput = renderDiff(result.diff);
1247
- const indentedDiff = diffOutput
1248
- .split('\n')
1249
- .map((line) => `${displayIndent} ${line}`)
1250
- .join('\n');
1251
- console.log(`${indentedDiff}`);
1252
- }
1253
- else if (hasFilePreview) {
1254
- // Show new file content in diff-like style
1255
- console.log('');
1256
- console.log(`${displayIndent}${colors.success(`${icons.file} ${result.filePath}`)}`);
1257
- console.log(`${displayIndent}${colors.textDim(` ${result.lineCount} lines`)}`);
1258
- console.log('');
1259
- console.log(renderLines(result.preview, { maxLines: 10, indent: displayIndent + ' ' }));
1260
- }
1261
- else if (hasDeleteInfo) {
1262
- // Show DeleteFile result
1263
- console.log('');
1264
- console.log(`${displayIndent}${colors.success(`${icons.check} Deleted: ${result.filePath}`)}`);
1265
- }
1266
- else if (isTaskTool) {
1267
- // Special handling for task tool (subagent) - show friendly summary
1268
- console.log('');
1269
- const subagentType = params.subagent_type;
1270
- const subagentName = params.description ||
1271
- (params.prompt ? params.prompt.substring(0, 50).replace(/\n/g, ' ') : 'Unknown task');
1272
- if (result?.success) {
1273
- console.log(`${displayIndent}${colors.success(`${icons.check} ${subagentType}: Completed`)}`);
1274
- console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1275
- if (result.message) {
1276
- console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
1800
+ // Normal mode: console output (SDK mode already output via adapter above)
1801
+ if (!this.isSdkMode || !this.sdkOutputAdapter) {
1802
+ // Always show details for todo tools so users can see their task lists
1803
+ const isTodoTool = tool === 'todo_write' || tool === 'todo_read';
1804
+ // Special handling for edit tool with diff
1805
+ const isEditTool = tool === 'Edit';
1806
+ const hasDiff = isEditTool && result?.diff;
1807
+ // Special handling for Write tool with file preview
1808
+ const isWriteTool = tool === 'Write';
1809
+ const hasFilePreview = isWriteTool && result?.preview;
1810
+ // Special handling for DeleteFile tool
1811
+ const isDeleteTool = tool === 'DeleteFile';
1812
+ const hasDeleteInfo = isDeleteTool && result?.filePath;
1813
+ // Special handling for task tool (subagent)
1814
+ const isTaskTool = tool === 'task' && params?.subagent_type;
1815
+ // Check if tool is an MCP wrapper tool by looking up in tool registry
1816
+ const { getToolRegistry } = await import('./tools.js');
1817
+ const toolRegistry = getToolRegistry();
1818
+ const toolDef = toolRegistry.get(tool);
1819
+ const isMcpTool = toolDef && toolDef._isMcpTool === true;
1820
+ if (isTodoTool) {
1821
+ console.log('');
1822
+ console.log(`${displayIndent}${colors.success(`${icons.check} Todo List:`)}`);
1823
+ console.log(this.renderTodoList(result?.todos || [], displayIndent));
1824
+ // Show summary if available
1825
+ if (result?.message) {
1826
+ console.log(`${displayIndent}${colors.textDim(result.message)}`);
1277
1827
  }
1278
1828
  }
1279
- else if (result?.cancelled) {
1280
- console.log(`${displayIndent}${colors.warning(`${icons.cross} ${subagentType}: Cancelled`)}`);
1281
- console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1829
+ else if (hasDiff) {
1830
+ // Show edit result with diff
1831
+ console.log('');
1832
+ const diffOutput = renderDiff(result.diff);
1833
+ const indentedDiff = diffOutput
1834
+ .split('\n')
1835
+ .map((line) => `${displayIndent} ${line}`)
1836
+ .join('\n');
1837
+ console.log(`${indentedDiff}`);
1282
1838
  }
1283
- else {
1284
- console.log(`${displayIndent}${colors.error(`${icons.cross} ${subagentType}: Failed`)}`);
1285
- console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1286
- if (result?.message) {
1287
- console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
1288
- }
1839
+ else if (hasFilePreview) {
1840
+ // Show new file content in diff-like style
1841
+ console.log('');
1842
+ console.log(`${displayIndent}${colors.success(`${icons.file} ${result.filePath}`)}`);
1843
+ console.log(`${displayIndent}${colors.textDim(` ${result.lineCount} lines`)}`);
1844
+ console.log('');
1845
+ console.log(renderLines(result.preview, { maxLines: 10, indent: displayIndent + ' ' }));
1289
1846
  }
1290
- }
1291
- else if (isMcpTool) {
1292
- // Special handling for MCP tools - show friendly summary
1293
- console.log('');
1294
- // Extract server name and tool name from tool name (format: serverName__toolName)
1295
- let serverName = 'MCP';
1296
- let toolDisplayName = tool;
1297
- if (tool.includes('__')) {
1298
- const parts = tool.split('__');
1299
- serverName = parts[0];
1300
- toolDisplayName = parts.slice(1).join('__');
1847
+ else if (hasDeleteInfo) {
1848
+ // Show DeleteFile result
1849
+ console.log('');
1850
+ console.log(`${displayIndent}${colors.success(`${icons.check} Deleted: ${result.filePath}`)}`);
1301
1851
  }
1302
- // Try to extract meaningful content from MCP result
1303
- let summary = '';
1304
- if (result?.content && Array.isArray(result.content) && result.content.length > 0) {
1305
- const firstBlock = result.content[0];
1306
- if (firstBlock?.type === 'text' && firstBlock?.text) {
1307
- const text = firstBlock.text;
1308
- if (typeof text === 'string') {
1309
- // Detect HTML content
1310
- if (text.trim().startsWith('<!DOCTYPE') || text.trim().startsWith('<html')) {
1311
- summary = '[HTML content fetched]';
1312
- }
1313
- else {
1314
- // Try to parse if it's JSON
1315
- try {
1316
- const parsed = JSON.parse(text);
1317
- if (Array.isArray(parsed) && parsed.length > 0 && parsed[0]?.title) {
1318
- // Search results format
1319
- summary = `Found ${parsed.length} result(s)`;
1320
- }
1321
- else if (parsed?.message) {
1322
- summary = parsed.message;
1852
+ else if (isTaskTool) {
1853
+ // Special handling for task tool (subagent) - show friendly summary
1854
+ console.log('');
1855
+ const subagentType = params.subagent_type;
1856
+ const subagentName = params.description ||
1857
+ (params.prompt ? params.prompt.substring(0, 50).replace(/\n/g, ' ') : 'Unknown task');
1858
+ if (result?.success) {
1859
+ console.log(`${displayIndent}${colors.success(`${icons.check} ${subagentType}: Completed`)}`);
1860
+ console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1861
+ if (result.message) {
1862
+ console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
1863
+ }
1864
+ }
1865
+ else if (result?.cancelled) {
1866
+ console.log(`${displayIndent}${colors.warning(`${icons.cross} ${subagentType}: Cancelled`)}`);
1867
+ console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1868
+ }
1869
+ else {
1870
+ console.log(`${displayIndent}${colors.error(`${icons.cross} ${subagentType}: Failed`)}`);
1871
+ console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1872
+ if (result?.message) {
1873
+ console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
1874
+ }
1875
+ }
1876
+ }
1877
+ else if (isMcpTool) {
1878
+ // Special handling for MCP tools - show friendly summary
1879
+ console.log('');
1880
+ // Extract server name and tool name from tool name (format: serverName__toolName)
1881
+ let serverName = 'MCP';
1882
+ let toolDisplayName = tool;
1883
+ if (tool.includes('__')) {
1884
+ const parts = tool.split('__');
1885
+ serverName = parts[0];
1886
+ toolDisplayName = parts.slice(1).join('__');
1887
+ }
1888
+ // Try to extract meaningful content from MCP result
1889
+ let summary = '';
1890
+ if (result?.content && Array.isArray(result.content) && result.content.length > 0) {
1891
+ const firstBlock = result.content[0];
1892
+ if (firstBlock?.type === 'text' && firstBlock?.text) {
1893
+ const text = firstBlock.text;
1894
+ if (typeof text === 'string') {
1895
+ // Detect HTML content
1896
+ if (text.trim().startsWith('<!DOCTYPE') || text.trim().startsWith('<html')) {
1897
+ summary = '[HTML content fetched]';
1898
+ }
1899
+ else {
1900
+ // Try to parse if it's JSON
1901
+ try {
1902
+ const parsed = JSON.parse(text);
1903
+ if (Array.isArray(parsed) && parsed.length > 0 && parsed[0]?.title) {
1904
+ // Search results format
1905
+ summary = `Found ${parsed.length} result(s)`;
1906
+ }
1907
+ else if (parsed?.message) {
1908
+ summary = parsed.message;
1909
+ }
1910
+ else if (typeof parsed === 'string') {
1911
+ summary = parsed.substring(0, 100);
1912
+ }
1323
1913
  }
1324
- else if (typeof parsed === 'string') {
1325
- summary = parsed.substring(0, 100);
1914
+ catch {
1915
+ // Not JSON, use as-is with truncation
1916
+ summary = text.substring(0, 100);
1326
1917
  }
1327
1918
  }
1328
- catch {
1329
- // Not JSON, use as-is with truncation
1330
- summary = text.substring(0, 100);
1331
- }
1332
1919
  }
1333
1920
  }
1334
1921
  }
1335
- }
1336
- else if (result?.message) {
1337
- summary = result.message;
1338
- }
1339
- if (result?.success !== false) {
1340
- console.log(`${displayIndent}${colors.success(`${icons.check} ${serverName}: Success`)}`);
1341
- console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
1342
- if (summary) {
1343
- console.log(`${displayIndent}${colors.textDim(` ${summary}`)}`);
1922
+ else if (result?.message) {
1923
+ summary = result.message;
1344
1924
  }
1345
- }
1346
- else {
1347
- console.log(`${displayIndent}${colors.error(`${icons.cross} ${serverName}: Failed`)}`);
1348
- console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
1349
- if (result?.message || result?.error) {
1350
- console.log(`${displayIndent}${colors.textDim(` ${result?.message || result?.error}`)}`);
1925
+ if (result?.success !== false) {
1926
+ console.log(`${displayIndent}${colors.success(`${icons.check} ${serverName}: Success`)}`);
1927
+ console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
1928
+ if (summary) {
1929
+ console.log(`${displayIndent}${colors.textDim(` ${summary}`)}`);
1930
+ }
1931
+ }
1932
+ else {
1933
+ console.log(`${displayIndent}${colors.error(`${icons.cross} ${serverName}: Failed`)}`);
1934
+ console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
1935
+ if (result?.message || result?.error) {
1936
+ console.log(`${displayIndent}${colors.textDim(` ${result?.message || result?.error}`)}`);
1937
+ }
1351
1938
  }
1352
1939
  }
1353
- }
1354
- else if (tool === 'InvokeSkill') {
1355
- // Special handling for InvokeSkill - show friendly summary
1356
- console.log('');
1357
- const skillName = params?.skillId || 'Unknown skill';
1358
- const taskDesc = params?.taskDescription || '';
1359
- if (result?.success) {
1360
- console.log(`${displayIndent}${colors.success(`${icons.check} Skill: Completed`)}`);
1361
- console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
1362
- if (taskDesc) {
1363
- const truncatedTask = taskDesc.length > 60 ? taskDesc.substring(0, 60) + '...' : taskDesc;
1364
- console.log(`${displayIndent}${colors.textDim(` Task: ${truncatedTask}`)}`);
1940
+ else if (tool === 'InvokeSkill') {
1941
+ // Special handling for InvokeSkill - show friendly summary
1942
+ console.log('');
1943
+ const skillName = params?.skillId || 'Unknown skill';
1944
+ const taskDesc = params?.taskDescription || '';
1945
+ if (result?.success) {
1946
+ console.log(`${displayIndent}${colors.success(`${icons.check} Skill: Completed`)}`);
1947
+ console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
1948
+ if (taskDesc) {
1949
+ const truncatedTask = taskDesc.length > 60 ? taskDesc.substring(0, 60) + '...' : taskDesc;
1950
+ console.log(`${displayIndent}${colors.textDim(` Task: ${truncatedTask}`)}`);
1951
+ }
1365
1952
  }
1953
+ else {
1954
+ console.log(`${displayIndent}${colors.error(`${icons.cross} Skill: Failed`)}`);
1955
+ console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
1956
+ if (result?.message) {
1957
+ console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
1958
+ }
1959
+ }
1960
+ }
1961
+ else if (showToolDetails) {
1962
+ console.log('');
1963
+ console.log(`${displayIndent}${colors.success(`${icons.check} Tool Result:`)}`);
1964
+ console.log(`${displayIndent}${colors.textDim(JSON.stringify(result, null, 2))}`);
1965
+ }
1966
+ else if (result && result.success === false) {
1967
+ // GUI task or other tool failed
1968
+ console.log(`${displayIndent}${colors.error(`${icons.cross} ${result.message || 'Failed'}`)}`);
1969
+ }
1970
+ else if (result) {
1971
+ // Show brief preview by default (consistent with subagent behavior)
1972
+ const resultPreview = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
1973
+ const truncatedPreview = resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
1974
+ // Indent the preview
1975
+ const indentedPreview = truncatedPreview
1976
+ .split('\n')
1977
+ .map((line) => `${displayIndent} ${line}`)
1978
+ .join('\n');
1979
+ console.log(`${indentedPreview}`);
1366
1980
  }
1367
1981
  else {
1368
- console.log(`${displayIndent}${colors.error(`${icons.cross} Skill: Failed`)}`);
1369
- console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
1370
- if (result?.message) {
1371
- console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
1372
- }
1982
+ console.log(`${displayIndent}${colors.textDim('(no result)')}`);
1373
1983
  }
1374
1984
  }
1375
- else if (showToolDetails) {
1376
- console.log('');
1377
- console.log(`${displayIndent}${colors.success(`${icons.check} Tool Result:`)}`);
1378
- console.log(`${displayIndent}${colors.textDim(JSON.stringify(result, null, 2))}`);
1379
- }
1380
- else if (result && result.success === false) {
1381
- // GUI task or other tool failed
1382
- console.log(`${displayIndent}${colors.error(`${icons.cross} ${result.message || 'Failed'}`)}`);
1383
- }
1384
- else if (result) {
1385
- // Show brief preview by default (consistent with subagent behavior)
1386
- const resultPreview = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
1387
- const truncatedPreview = resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
1388
- // Indent the preview
1389
- const indentedPreview = truncatedPreview
1390
- .split('\n')
1391
- .map((line) => `${displayIndent} ${line}`)
1392
- .join('\n');
1393
- console.log(`${indentedPreview}`);
1394
- }
1395
- else {
1396
- console.log(`${displayIndent}${colors.textDim('(no result)')}`);
1397
- }
1398
1985
  const toolCallRecord = {
1399
1986
  tool,
1400
1987
  params,
@@ -1768,7 +2355,7 @@ async function initializeSkillsOnDemand() {
1768
2355
  return skillsToInstall.length;
1769
2356
  }
1770
2357
  // Synchronous version (kept for backwards compatibility)
1771
- function copyDirectoryRecursive(src, dest) {
2358
+ function _copyDirectoryRecursive(src, dest) {
1772
2359
  if (!fs.existsSync(dest)) {
1773
2360
  fs.mkdirSync(dest, { recursive: true });
1774
2361
  }
@@ -1777,7 +2364,7 @@ function copyDirectoryRecursive(src, dest) {
1777
2364
  const srcPath = path.join(src, entry.name);
1778
2365
  const destPath = path.join(dest, entry.name);
1779
2366
  if (entry.isDirectory()) {
1780
- copyDirectoryRecursive(srcPath, destPath);
2367
+ _copyDirectoryRecursive(srcPath, destPath);
1781
2368
  }
1782
2369
  else if (entry.isFile()) {
1783
2370
  fs.copyFileSync(srcPath, destPath);
@@ -1845,7 +2432,7 @@ export async function startInteractiveSession() {
1845
2432
  const { checkUpdatesOnStartup } = await import('./update.js');
1846
2433
  await checkUpdatesOnStartup();
1847
2434
  }
1848
- catch (error) {
2435
+ catch {
1849
2436
  // Silently ignore update check failures
1850
2437
  }
1851
2438
  await session.start();