@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.
- package/.github/release.yml +76 -0
- package/.github/workflows/ci.yml +3 -0
- package/.github/workflows/release.yml +11 -17
- package/README.md +2 -2
- package/README_CN.md +2 -2
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +7 -3
- package/dist/agents.js.map +1 -1
- package/dist/ai-client/factory.d.ts +0 -12
- package/dist/ai-client/factory.d.ts.map +1 -1
- package/dist/ai-client/factory.js +0 -32
- package/dist/ai-client/factory.js.map +1 -1
- package/dist/ai-client/index.js +1 -1
- package/dist/ai-client/index.js.map +1 -1
- package/dist/ai-client/providers/anthropic.d.ts.map +1 -1
- package/dist/ai-client/providers/anthropic.js +10 -4
- package/dist/ai-client/providers/anthropic.js.map +1 -1
- package/dist/ai-client/providers/openai.d.ts.map +1 -1
- package/dist/ai-client/providers/openai.js +8 -4
- package/dist/ai-client/providers/openai.js.map +1 -1
- package/dist/ai-client/providers/remote.d.ts +0 -1
- package/dist/ai-client/providers/remote.d.ts.map +1 -1
- package/dist/ai-client/providers/remote.js +11 -10
- package/dist/ai-client/providers/remote.js.map +1 -1
- package/dist/ai-client/types.d.ts +14 -0
- package/dist/ai-client/types.d.ts.map +1 -1
- package/dist/ai-client/types.js +17 -0
- package/dist/ai-client/types.js.map +1 -1
- package/dist/ai-client-factory.d.ts.map +1 -1
- package/dist/ai-client-factory.js +4 -4
- package/dist/ai-client-factory.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +10 -12
- package/dist/auth.js.map +1 -1
- package/dist/cancellation.d.ts.map +1 -1
- package/dist/cancellation.js +3 -5
- package/dist/cancellation.js.map +1 -1
- package/dist/checkpoint.d.ts +1 -0
- package/dist/checkpoint.d.ts.map +1 -1
- package/dist/checkpoint.js +37 -4
- package/dist/checkpoint.js.map +1 -1
- package/dist/cli.js +132 -32
- package/dist/cli.js.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/context-compressor.d.ts +1 -2
- package/dist/context-compressor.d.ts.map +1 -1
- package/dist/context-compressor.js +23 -18
- package/dist/context-compressor.js.map +1 -1
- package/dist/conversation.d.ts +1 -1
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +8 -7
- package/dist/conversation.js.map +1 -1
- package/dist/gui-subagent/action-parser/actionParser.js +2 -2
- package/dist/gui-subagent/action-parser/actionParser.js.map +1 -1
- package/dist/gui-subagent/agent/gui-agent.d.ts +10 -0
- package/dist/gui-subagent/agent/gui-agent.d.ts.map +1 -1
- package/dist/gui-subagent/agent/gui-agent.js +105 -32
- package/dist/gui-subagent/agent/gui-agent.js.map +1 -1
- package/dist/gui-subagent/index.d.ts +7 -0
- package/dist/gui-subagent/index.d.ts.map +1 -1
- package/dist/gui-subagent/index.js +2 -0
- package/dist/gui-subagent/index.js.map +1 -1
- package/dist/gui-subagent/operator/computer-operator.d.ts.map +1 -1
- package/dist/gui-subagent/operator/computer-operator.js +2 -0
- package/dist/gui-subagent/operator/computer-operator.js.map +1 -1
- package/dist/input-processor.js +2 -2
- package/dist/input-processor.js.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +83 -21
- package/dist/mcp.js.map +1 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +3 -3
- package/dist/memory.js.map +1 -1
- package/dist/output-util.d.ts +27 -0
- package/dist/output-util.d.ts.map +1 -0
- package/dist/output-util.js +74 -0
- package/dist/output-util.js.map +1 -0
- package/dist/retry.js +1 -1
- package/dist/retry.js.map +1 -1
- package/dist/ripgrep.d.ts.map +1 -1
- package/dist/ripgrep.js +5 -3
- package/dist/ripgrep.js.map +1 -1
- package/dist/sdk-output-adapter.d.ts +265 -0
- package/dist/sdk-output-adapter.d.ts.map +1 -0
- package/dist/sdk-output-adapter.js +701 -0
- package/dist/sdk-output-adapter.js.map +1 -0
- package/dist/sdk-session.d.ts +13 -0
- package/dist/sdk-session.d.ts.map +1 -0
- package/dist/sdk-session.js +50 -0
- package/dist/sdk-session.js.map +1 -0
- package/dist/session-manager.js +3 -3
- package/dist/session-manager.js.map +1 -1
- package/dist/session.d.ts +96 -2
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +849 -262
- package/dist/session.js.map +1 -1
- package/dist/shell.d.ts.map +1 -1
- package/dist/shell.js +5 -4
- package/dist/shell.js.map +1 -1
- package/dist/skill-installer.js +3 -3
- package/dist/skill-installer.js.map +1 -1
- package/dist/skill-invoker.d.ts +1 -1
- package/dist/skill-invoker.d.ts.map +1 -1
- package/dist/skill-invoker.js +2 -2
- package/dist/skill-invoker.js.map +1 -1
- package/dist/skill-loader.js +6 -5
- package/dist/skill-loader.js.map +1 -1
- package/dist/skill-manager.d.ts.map +1 -1
- package/dist/skill-manager.js +3 -2
- package/dist/skill-manager.js.map +1 -1
- package/dist/slash-commands.d.ts +1 -1
- package/dist/slash-commands.d.ts.map +1 -1
- package/dist/slash-commands.js +24 -11
- package/dist/slash-commands.js.map +1 -1
- package/dist/smart-approval.d.ts +20 -1
- package/dist/smart-approval.d.ts.map +1 -1
- package/dist/smart-approval.js +58 -1
- package/dist/smart-approval.js.map +1 -1
- package/dist/system-prompt-generator.js +3 -3
- package/dist/system-prompt-generator.js.map +1 -1
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +8 -7
- package/dist/theme.js.map +1 -1
- package/dist/tools.d.ts +15 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +487 -215
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +57 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +49 -0
- package/dist/types.js.map +1 -1
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +12 -9
- package/dist/update.js.map +1 -1
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +1 -2
- package/dist/workflow.js.map +1 -1
- package/docs/third-party-models.md +16 -15
- package/package.json +3 -1
- package/src/agents.ts +7 -3
- package/src/ai-client/factory.ts +1 -36
- package/src/ai-client/index.ts +1 -1
- package/src/ai-client/providers/anthropic.ts +12 -3
- package/src/ai-client/providers/openai.ts +10 -4
- package/src/ai-client/providers/remote.ts +13 -10
- package/src/ai-client/types.ts +19 -0
- package/src/ai-client-factory.ts +5 -5
- package/src/auth.ts +11 -13
- package/src/cancellation.ts +3 -6
- package/src/checkpoint.ts +40 -4
- package/src/cli.ts +154 -37
- package/src/config.ts +1 -1
- package/src/context-compressor.ts +28 -23
- package/src/conversation.ts +9 -7
- package/src/gui-subagent/action-parser/actionParser.ts +2 -2
- package/src/gui-subagent/agent/gui-agent.ts +117 -34
- package/src/gui-subagent/index.ts +8 -0
- package/src/gui-subagent/operator/computer-operator.ts +2 -1
- package/src/input-processor.ts +2 -2
- package/src/logger.ts +2 -4
- package/src/mcp.ts +86 -23
- package/src/memory.ts +3 -4
- package/src/output-util.ts +80 -0
- package/src/retry.ts +1 -1
- package/src/ripgrep.ts +5 -3
- package/src/sdk-output-adapter.ts +842 -0
- package/src/sdk-session.ts +62 -0
- package/src/session-manager.ts +3 -3
- package/src/session.ts +942 -302
- package/src/shell.ts +6 -5
- package/src/skill-installer.ts +3 -3
- package/src/skill-invoker.ts +3 -4
- package/src/skill-loader.ts +7 -7
- package/src/skill-manager.ts +4 -3
- package/src/slash-commands.ts +24 -16
- package/src/smart-approval.ts +76 -1
- package/src/system-prompt-generator.ts +3 -3
- package/src/theme.ts +9 -8
- package/src/tools.ts +563 -267
- package/src/types.ts +118 -0
- package/src/update.ts +12 -9
- package/src/workflow.ts +2 -4
- package/test/cli-launch.test.ts +279 -0
- package/vitest.config.ts +2 -0
- /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
|
-
|
|
264
|
+
if (!this.isSdkMode) {
|
|
265
|
+
console.log(colors.textMuted(' 🔄 Skills updated from CLI'));
|
|
266
|
+
}
|
|
184
267
|
}
|
|
185
268
|
}
|
|
186
269
|
}
|
|
187
|
-
catch
|
|
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
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
' '.repeat(
|
|
208
|
-
'
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
colors.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
|
322
|
+
let _escCleanup;
|
|
232
323
|
if (process.stdin.isTTY) {
|
|
233
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
781
|
-
|
|
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
|
-
|
|
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
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
console.log(`${indent}${colors.
|
|
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,
|
|
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
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
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
|
-
|
|
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
|
-
|
|
978
|
-
|
|
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
|
-
|
|
984
|
-
|
|
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
|
-
|
|
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:',
|
|
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
|
|
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
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
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
|
-
|
|
1162
|
-
|
|
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
|
|
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
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
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
|
-
//
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
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 (
|
|
1280
|
-
|
|
1281
|
-
console.log(
|
|
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
|
-
|
|
1285
|
-
console.log(
|
|
1286
|
-
|
|
1287
|
-
|
|
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
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
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
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
const
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
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
|
-
|
|
1325
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
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
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
2435
|
+
catch {
|
|
1849
2436
|
// Silently ignore update check failures
|
|
1850
2437
|
}
|
|
1851
2438
|
await session.start();
|