cmdr-agent 1.0.0
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/LICENSE +21 -0
- package/README.md +238 -0
- package/dist/bin/cmdr.d.ts +9 -0
- package/dist/bin/cmdr.d.ts.map +1 -0
- package/dist/bin/cmdr.js +49 -0
- package/dist/bin/cmdr.js.map +1 -0
- package/dist/src/cli/args.d.ts +19 -0
- package/dist/src/cli/args.d.ts.map +1 -0
- package/dist/src/cli/args.js +89 -0
- package/dist/src/cli/args.js.map +1 -0
- package/dist/src/cli/commands.d.ts +12 -0
- package/dist/src/cli/commands.d.ts.map +1 -0
- package/dist/src/cli/commands.js +400 -0
- package/dist/src/cli/commands.js.map +1 -0
- package/dist/src/cli/renderer.d.ts +8 -0
- package/dist/src/cli/renderer.d.ts.map +1 -0
- package/dist/src/cli/renderer.js +47 -0
- package/dist/src/cli/renderer.js.map +1 -0
- package/dist/src/cli/repl.d.ts +18 -0
- package/dist/src/cli/repl.d.ts.map +1 -0
- package/dist/src/cli/repl.js +751 -0
- package/dist/src/cli/repl.js.map +1 -0
- package/dist/src/cli/spinner.d.ts +16 -0
- package/dist/src/cli/spinner.d.ts.map +1 -0
- package/dist/src/cli/spinner.js +233 -0
- package/dist/src/cli/spinner.js.map +1 -0
- package/dist/src/cli/theme.d.ts +95 -0
- package/dist/src/cli/theme.d.ts.map +1 -0
- package/dist/src/cli/theme.js +178 -0
- package/dist/src/cli/theme.js.map +1 -0
- package/dist/src/communication/message-bus.d.ts +35 -0
- package/dist/src/communication/message-bus.d.ts.map +1 -0
- package/dist/src/communication/message-bus.js +60 -0
- package/dist/src/communication/message-bus.js.map +1 -0
- package/dist/src/communication/shared-memory.d.ts +19 -0
- package/dist/src/communication/shared-memory.d.ts.map +1 -0
- package/dist/src/communication/shared-memory.js +37 -0
- package/dist/src/communication/shared-memory.js.map +1 -0
- package/dist/src/communication/task-queue.d.ts +50 -0
- package/dist/src/communication/task-queue.d.ts.map +1 -0
- package/dist/src/communication/task-queue.js +158 -0
- package/dist/src/communication/task-queue.js.map +1 -0
- package/dist/src/config/config-loader.d.ts +23 -0
- package/dist/src/config/config-loader.d.ts.map +1 -0
- package/dist/src/config/config-loader.js +91 -0
- package/dist/src/config/config-loader.js.map +1 -0
- package/dist/src/config/defaults.d.ts +6 -0
- package/dist/src/config/defaults.d.ts.map +1 -0
- package/dist/src/config/defaults.js +21 -0
- package/dist/src/config/defaults.js.map +1 -0
- package/dist/src/config/schema.d.ts +135 -0
- package/dist/src/config/schema.d.ts.map +1 -0
- package/dist/src/config/schema.js +35 -0
- package/dist/src/config/schema.js.map +1 -0
- package/dist/src/config/telemetry.d.ts +41 -0
- package/dist/src/config/telemetry.d.ts.map +1 -0
- package/dist/src/config/telemetry.js +57 -0
- package/dist/src/config/telemetry.js.map +1 -0
- package/dist/src/core/agent-pool.d.ts +40 -0
- package/dist/src/core/agent-pool.d.ts.map +1 -0
- package/dist/src/core/agent-pool.js +66 -0
- package/dist/src/core/agent-pool.js.map +1 -0
- package/dist/src/core/agent-runner.d.ts +51 -0
- package/dist/src/core/agent-runner.d.ts.map +1 -0
- package/dist/src/core/agent-runner.js +251 -0
- package/dist/src/core/agent-runner.js.map +1 -0
- package/dist/src/core/agent.d.ts +34 -0
- package/dist/src/core/agent.d.ts.map +1 -0
- package/dist/src/core/agent.js +143 -0
- package/dist/src/core/agent.js.map +1 -0
- package/dist/src/core/intent.d.ts +33 -0
- package/dist/src/core/intent.d.ts.map +1 -0
- package/dist/src/core/intent.js +91 -0
- package/dist/src/core/intent.js.map +1 -0
- package/dist/src/core/orchestrator.d.ts +43 -0
- package/dist/src/core/orchestrator.d.ts.map +1 -0
- package/dist/src/core/orchestrator.js +115 -0
- package/dist/src/core/orchestrator.js.map +1 -0
- package/dist/src/core/permissions.d.ts +36 -0
- package/dist/src/core/permissions.d.ts.map +1 -0
- package/dist/src/core/permissions.js +129 -0
- package/dist/src/core/permissions.js.map +1 -0
- package/dist/src/core/presets.d.ts +12 -0
- package/dist/src/core/presets.d.ts.map +1 -0
- package/dist/src/core/presets.js +148 -0
- package/dist/src/core/presets.js.map +1 -0
- package/dist/src/core/team.d.ts +44 -0
- package/dist/src/core/team.d.ts.map +1 -0
- package/dist/src/core/team.js +156 -0
- package/dist/src/core/team.js.map +1 -0
- package/dist/src/core/types.d.ts +257 -0
- package/dist/src/core/types.d.ts.map +1 -0
- package/dist/src/core/types.js +7 -0
- package/dist/src/core/types.js.map +1 -0
- package/dist/src/index.d.ts +44 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +45 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/llm/adapter.d.ts +5 -0
- package/dist/src/llm/adapter.d.ts.map +1 -0
- package/dist/src/llm/adapter.js +5 -0
- package/dist/src/llm/adapter.js.map +1 -0
- package/dist/src/llm/model-registry.d.ts +14 -0
- package/dist/src/llm/model-registry.d.ts.map +1 -0
- package/dist/src/llm/model-registry.js +30 -0
- package/dist/src/llm/model-registry.js.map +1 -0
- package/dist/src/llm/ollama.d.ts +26 -0
- package/dist/src/llm/ollama.d.ts.map +1 -0
- package/dist/src/llm/ollama.js +375 -0
- package/dist/src/llm/ollama.js.map +1 -0
- package/dist/src/llm/token-counter.d.ts +11 -0
- package/dist/src/llm/token-counter.d.ts.map +1 -0
- package/dist/src/llm/token-counter.js +35 -0
- package/dist/src/llm/token-counter.js.map +1 -0
- package/dist/src/plugins/mcp-client.d.ts +38 -0
- package/dist/src/plugins/mcp-client.d.ts.map +1 -0
- package/dist/src/plugins/mcp-client.js +113 -0
- package/dist/src/plugins/mcp-client.js.map +1 -0
- package/dist/src/plugins/plugin-manager.d.ts +37 -0
- package/dist/src/plugins/plugin-manager.d.ts.map +1 -0
- package/dist/src/plugins/plugin-manager.js +146 -0
- package/dist/src/plugins/plugin-manager.js.map +1 -0
- package/dist/src/scheduling/semaphore.d.ts +20 -0
- package/dist/src/scheduling/semaphore.d.ts.map +1 -0
- package/dist/src/scheduling/semaphore.js +52 -0
- package/dist/src/scheduling/semaphore.js.map +1 -0
- package/dist/src/scheduling/strategies.d.ts +39 -0
- package/dist/src/scheduling/strategies.d.ts.map +1 -0
- package/dist/src/scheduling/strategies.js +88 -0
- package/dist/src/scheduling/strategies.js.map +1 -0
- package/dist/src/session/compaction.d.ts +32 -0
- package/dist/src/session/compaction.d.ts.map +1 -0
- package/dist/src/session/compaction.js +172 -0
- package/dist/src/session/compaction.js.map +1 -0
- package/dist/src/session/cost-tracker.d.ts +41 -0
- package/dist/src/session/cost-tracker.d.ts.map +1 -0
- package/dist/src/session/cost-tracker.js +76 -0
- package/dist/src/session/cost-tracker.js.map +1 -0
- package/dist/src/session/project-context.d.ts +6 -0
- package/dist/src/session/project-context.d.ts.map +1 -0
- package/dist/src/session/project-context.js +147 -0
- package/dist/src/session/project-context.js.map +1 -0
- package/dist/src/session/prompt-builder.d.ts +11 -0
- package/dist/src/session/prompt-builder.d.ts.map +1 -0
- package/dist/src/session/prompt-builder.js +30 -0
- package/dist/src/session/prompt-builder.js.map +1 -0
- package/dist/src/session/session-manager.d.ts +32 -0
- package/dist/src/session/session-manager.d.ts.map +1 -0
- package/dist/src/session/session-manager.js +84 -0
- package/dist/src/session/session-manager.js.map +1 -0
- package/dist/src/session/session-persistence.d.ts +44 -0
- package/dist/src/session/session-persistence.d.ts.map +1 -0
- package/dist/src/session/session-persistence.js +150 -0
- package/dist/src/session/session-persistence.js.map +1 -0
- package/dist/src/session/undo-manager.d.ts +35 -0
- package/dist/src/session/undo-manager.d.ts.map +1 -0
- package/dist/src/session/undo-manager.js +79 -0
- package/dist/src/session/undo-manager.js.map +1 -0
- package/dist/src/tools/built-in/ask-user.d.ts +7 -0
- package/dist/src/tools/built-in/ask-user.d.ts.map +1 -0
- package/dist/src/tools/built-in/ask-user.js +28 -0
- package/dist/src/tools/built-in/ask-user.js.map +1 -0
- package/dist/src/tools/built-in/bash.d.ts +9 -0
- package/dist/src/tools/built-in/bash.d.ts.map +1 -0
- package/dist/src/tools/built-in/bash.js +67 -0
- package/dist/src/tools/built-in/bash.js.map +1 -0
- package/dist/src/tools/built-in/file-edit.d.ts +9 -0
- package/dist/src/tools/built-in/file-edit.d.ts.map +1 -0
- package/dist/src/tools/built-in/file-edit.js +39 -0
- package/dist/src/tools/built-in/file-edit.js.map +1 -0
- package/dist/src/tools/built-in/file-read.d.ts +9 -0
- package/dist/src/tools/built-in/file-read.d.ts.map +1 -0
- package/dist/src/tools/built-in/file-read.js +41 -0
- package/dist/src/tools/built-in/file-read.js.map +1 -0
- package/dist/src/tools/built-in/file-write.d.ts +8 -0
- package/dist/src/tools/built-in/file-write.d.ts.map +1 -0
- package/dist/src/tools/built-in/file-write.js +29 -0
- package/dist/src/tools/built-in/file-write.js.map +1 -0
- package/dist/src/tools/built-in/git-commit.d.ts +12 -0
- package/dist/src/tools/built-in/git-commit.d.ts.map +1 -0
- package/dist/src/tools/built-in/git-commit.js +96 -0
- package/dist/src/tools/built-in/git-commit.js.map +1 -0
- package/dist/src/tools/built-in/git-diff.d.ts +8 -0
- package/dist/src/tools/built-in/git-diff.d.ts.map +1 -0
- package/dist/src/tools/built-in/git-diff.js +43 -0
- package/dist/src/tools/built-in/git-diff.js.map +1 -0
- package/dist/src/tools/built-in/git-log.d.ts +8 -0
- package/dist/src/tools/built-in/git-log.d.ts.map +1 -0
- package/dist/src/tools/built-in/git-log.js +39 -0
- package/dist/src/tools/built-in/git-log.js.map +1 -0
- package/dist/src/tools/built-in/glob.d.ts +8 -0
- package/dist/src/tools/built-in/glob.d.ts.map +1 -0
- package/dist/src/tools/built-in/glob.js +60 -0
- package/dist/src/tools/built-in/glob.js.map +1 -0
- package/dist/src/tools/built-in/grep.d.ts +9 -0
- package/dist/src/tools/built-in/grep.d.ts.map +1 -0
- package/dist/src/tools/built-in/grep.js +115 -0
- package/dist/src/tools/built-in/grep.js.map +1 -0
- package/dist/src/tools/built-in/index.d.ts +21 -0
- package/dist/src/tools/built-in/index.d.ts.map +1 -0
- package/dist/src/tools/built-in/index.js +30 -0
- package/dist/src/tools/built-in/index.js.map +1 -0
- package/dist/src/tools/built-in/think.d.ts +7 -0
- package/dist/src/tools/built-in/think.d.ts.map +1 -0
- package/dist/src/tools/built-in/think.js +18 -0
- package/dist/src/tools/built-in/think.js.map +1 -0
- package/dist/src/tools/built-in/web-fetch.d.ts +11 -0
- package/dist/src/tools/built-in/web-fetch.d.ts.map +1 -0
- package/dist/src/tools/built-in/web-fetch.js +70 -0
- package/dist/src/tools/built-in/web-fetch.js.map +1 -0
- package/dist/src/tools/executor.d.ts +25 -0
- package/dist/src/tools/executor.d.ts.map +1 -0
- package/dist/src/tools/executor.js +61 -0
- package/dist/src/tools/executor.js.map +1 -0
- package/dist/src/tools/registry.d.ts +25 -0
- package/dist/src/tools/registry.d.ts.map +1 -0
- package/dist/src/tools/registry.js +135 -0
- package/dist/src/tools/registry.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive REPL — the primary cmdr interface.
|
|
3
|
+
*
|
|
4
|
+
* Streaming output, tool execution display, markdown rendering,
|
|
5
|
+
* AMOLED black + green/purple aesthetic.
|
|
6
|
+
*/
|
|
7
|
+
import * as readline from 'readline';
|
|
8
|
+
import { Agent } from '../core/agent.js';
|
|
9
|
+
import { OllamaAdapter } from '../llm/ollama.js';
|
|
10
|
+
import { ToolRegistry } from '../tools/registry.js';
|
|
11
|
+
import { registerBuiltInTools } from '../tools/built-in/index.js';
|
|
12
|
+
import { SOLO_CODER, getTeamPreset } from '../core/presets.js';
|
|
13
|
+
import { Orchestrator } from '../core/orchestrator.js';
|
|
14
|
+
import { SessionManager } from '../session/session-manager.js';
|
|
15
|
+
import { discoverProject } from '../session/project-context.js';
|
|
16
|
+
import { buildSystemPrompt } from '../session/prompt-builder.js';
|
|
17
|
+
import { startThinking, startToolExec, stopSpinner, spinnerSuccess, spinnerFail, getCompletionSummary } from './spinner.js';
|
|
18
|
+
import { renderWelcome, renderToolExec, renderError, PROMPT_SYMBOL, GREEN, PURPLE, DIM, WHITE, CYAN, renderInfo, GREEN_DIM, YELLOW, RED, SUCCESS_SYMBOL, ERROR_SYMBOL, } from './theme.js';
|
|
19
|
+
import { isSlashCommand, parseSlashCommand, getCommand, } from './commands.js';
|
|
20
|
+
import { PermissionManager } from '../core/permissions.js';
|
|
21
|
+
import { saveSession, loadSession, findRecentSession, DebouncedSaver } from '../session/session-persistence.js';
|
|
22
|
+
import { PluginManager } from '../plugins/plugin-manager.js';
|
|
23
|
+
import { McpClient } from '../plugins/mcp-client.js';
|
|
24
|
+
import { loadConfig } from '../config/config-loader.js';
|
|
25
|
+
import { CostTracker } from '../session/cost-tracker.js';
|
|
26
|
+
import { UndoManager } from '../session/undo-manager.js';
|
|
27
|
+
export async function startRepl(options) {
|
|
28
|
+
const cwd = process.cwd();
|
|
29
|
+
const verbose = options.verbose ?? false;
|
|
30
|
+
// --- Setup ---
|
|
31
|
+
const adapter = new OllamaAdapter(options.ollamaUrl);
|
|
32
|
+
// Check Ollama connectivity
|
|
33
|
+
const healthy = await adapter.healthCheck();
|
|
34
|
+
if (!healthy) {
|
|
35
|
+
console.error(renderError(`Cannot connect to Ollama at ${options.ollamaUrl}\n` +
|
|
36
|
+
` Make sure Ollama is running: ollama serve\n` +
|
|
37
|
+
` Then pull a model: ollama pull ${options.model}`));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
// Discover project
|
|
41
|
+
const projectContext = await discoverProject(cwd);
|
|
42
|
+
const projectInfo = projectContext.language !== 'unknown'
|
|
43
|
+
? `${projectContext.language}${projectContext.framework ? ' / ' + projectContext.framework : ''}`
|
|
44
|
+
: cwd.split('/').pop() || 'unknown';
|
|
45
|
+
// Session
|
|
46
|
+
const session = new SessionManager(projectContext);
|
|
47
|
+
// Build system prompt with project context
|
|
48
|
+
const systemPrompt = buildSystemPrompt({
|
|
49
|
+
basePrompt: SOLO_CODER.systemPrompt,
|
|
50
|
+
projectContext,
|
|
51
|
+
model: options.model,
|
|
52
|
+
});
|
|
53
|
+
// Tool registry
|
|
54
|
+
const toolRegistry = new ToolRegistry();
|
|
55
|
+
registerBuiltInTools(toolRegistry);
|
|
56
|
+
// Load config
|
|
57
|
+
const config = await loadConfig(cwd);
|
|
58
|
+
// Plugin manager
|
|
59
|
+
const pluginManager = new PluginManager();
|
|
60
|
+
for (const pluginSource of config.plugins) {
|
|
61
|
+
try {
|
|
62
|
+
await pluginManager.load(pluginSource);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
66
|
+
console.log(` ${DIM('⚠ Plugin load failed:')} ${msg}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
pluginManager.registerTools(toolRegistry);
|
|
70
|
+
// MCP client
|
|
71
|
+
const mcpClient = new McpClient();
|
|
72
|
+
for (const server of config.mcp.servers) {
|
|
73
|
+
try {
|
|
74
|
+
await mcpClient.connect(server);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
78
|
+
console.log(` ${DIM('⚠ MCP connect failed:')} ${msg}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
mcpClient.registerTools(toolRegistry);
|
|
82
|
+
// Cost tracker
|
|
83
|
+
const costTracker = new CostTracker();
|
|
84
|
+
// Undo manager
|
|
85
|
+
const undoManager = new UndoManager();
|
|
86
|
+
// Permission manager
|
|
87
|
+
const permissionManager = new PermissionManager(options.dangerouslySkipPermissions ? 'yolo' : 'normal');
|
|
88
|
+
await permissionManager.loadSettings();
|
|
89
|
+
// CLI flag overrides persisted mode
|
|
90
|
+
if (options.dangerouslySkipPermissions) {
|
|
91
|
+
permissionManager.setMode('yolo');
|
|
92
|
+
}
|
|
93
|
+
// Orchestrator for team mode
|
|
94
|
+
const orchestrator = new Orchestrator(adapter, toolRegistry, {
|
|
95
|
+
maxConcurrency: 2,
|
|
96
|
+
defaultModel: options.model,
|
|
97
|
+
}, cwd, permissionManager);
|
|
98
|
+
let activeTeamConfig;
|
|
99
|
+
if (options.team) {
|
|
100
|
+
activeTeamConfig = getTeamPreset(options.team);
|
|
101
|
+
if (!activeTeamConfig) {
|
|
102
|
+
console.error(renderError(`Unknown team preset: ${options.team}. Use: review, fullstack, security`));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Create agent
|
|
107
|
+
let currentModel = options.model;
|
|
108
|
+
const agent = new Agent({ ...SOLO_CODER, model: currentModel, systemPrompt }, adapter, toolRegistry, cwd, permissionManager);
|
|
109
|
+
// --- Welcome ---
|
|
110
|
+
const modeLabel = permissionManager.getMode() === 'yolo'
|
|
111
|
+
? YELLOW('⚠ yolo (all tools auto-approved)')
|
|
112
|
+
: permissionManager.getMode() === 'strict'
|
|
113
|
+
? RED('strict (all tools require approval)')
|
|
114
|
+
: GREEN('normal (write tools require approval)');
|
|
115
|
+
console.log(renderWelcome(currentModel, projectInfo));
|
|
116
|
+
console.log(` ${DIM('Permissions:')} ${modeLabel}`);
|
|
117
|
+
if (activeTeamConfig) {
|
|
118
|
+
const teamAgents = activeTeamConfig.agents.map(a => a.name).join(', ');
|
|
119
|
+
console.log(` ${DIM('Team:')} ${PURPLE(activeTeamConfig.name)} ${DIM(`(${teamAgents})`)}`);
|
|
120
|
+
}
|
|
121
|
+
// Show CMDR.md loading status
|
|
122
|
+
if (projectContext.cmdrInstructions) {
|
|
123
|
+
const lineCount = projectContext.cmdrInstructions.split('\n').length;
|
|
124
|
+
console.log(` ${DIM(`CMDR.md loaded (${lineCount} lines)`)}`);
|
|
125
|
+
}
|
|
126
|
+
// --- Resume session if requested ---
|
|
127
|
+
if (options.resume) {
|
|
128
|
+
const saved = await loadSession(options.resume);
|
|
129
|
+
if (saved) {
|
|
130
|
+
agent.replaceMessages(saved.messages);
|
|
131
|
+
session.syncFromAgent(saved.messages);
|
|
132
|
+
console.log(renderInfo(`Resumed session ${DIM(saved.id)} (${saved.messages.length} messages)`));
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
console.log(renderError(`Session not found: ${options.resume}`));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else if (options.continue) {
|
|
139
|
+
const saved = await findRecentSession(cwd);
|
|
140
|
+
if (saved) {
|
|
141
|
+
agent.replaceMessages(saved.messages);
|
|
142
|
+
session.syncFromAgent(saved.messages);
|
|
143
|
+
console.log(renderInfo(`Continued session ${DIM(saved.id)} (${saved.messages.length} messages)`));
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
console.log(DIM(' No previous session found for this directory.'));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Debounced auto-save (max once per 5s)
|
|
150
|
+
const autoSaver = new DebouncedSaver(5000);
|
|
151
|
+
const doSave = async () => {
|
|
152
|
+
session.syncFromAgent(agent.getHistory());
|
|
153
|
+
if (session.messages.length > 0) {
|
|
154
|
+
await saveSession(session.getState(), currentModel);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
console.log('');
|
|
158
|
+
// --- Handle initial prompt if provided ---
|
|
159
|
+
if (options.initialPrompt) {
|
|
160
|
+
await handleUserMessage(options.initialPrompt, agent, session, currentModel, permissionManager, verbose, adapter, costTracker, undoManager);
|
|
161
|
+
await doSave();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
// --- Interactive REPL ---
|
|
165
|
+
const rl = readline.createInterface({
|
|
166
|
+
input: process.stdin,
|
|
167
|
+
output: process.stdout,
|
|
168
|
+
prompt: PROMPT_SYMBOL,
|
|
169
|
+
terminal: true,
|
|
170
|
+
});
|
|
171
|
+
rl.prompt();
|
|
172
|
+
// --- Paste detection ---
|
|
173
|
+
// Buffer lines arriving within 50ms of each other and join as single input
|
|
174
|
+
let pasteBuffer = [];
|
|
175
|
+
let pasteTimer = null;
|
|
176
|
+
const PASTE_THRESHOLD_MS = 50;
|
|
177
|
+
// Async queue — ensures commands are processed sequentially
|
|
178
|
+
let processing = false;
|
|
179
|
+
let closed = false;
|
|
180
|
+
async function processInput(input) {
|
|
181
|
+
processing = true;
|
|
182
|
+
try {
|
|
183
|
+
await processLine(input);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
processing = false;
|
|
187
|
+
if (closed) {
|
|
188
|
+
process.exit(0);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
rl.prompt();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function flushPasteBuffer() {
|
|
196
|
+
pasteTimer = null;
|
|
197
|
+
if (pasteBuffer.length === 0)
|
|
198
|
+
return;
|
|
199
|
+
const lines = pasteBuffer.slice();
|
|
200
|
+
pasteBuffer = [];
|
|
201
|
+
if (lines.length > 1) {
|
|
202
|
+
console.log(` ${DIM(`+${lines.length} lines pasted`)}`);
|
|
203
|
+
}
|
|
204
|
+
const merged = lines.join('\n').trim();
|
|
205
|
+
if (!merged) {
|
|
206
|
+
rl.prompt();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
processInput(merged);
|
|
210
|
+
}
|
|
211
|
+
async function processLine(input) {
|
|
212
|
+
// Slash commands
|
|
213
|
+
if (isSlashCommand(input)) {
|
|
214
|
+
const { name, args } = parseSlashCommand(input);
|
|
215
|
+
const cmd = getCommand(name);
|
|
216
|
+
if (!cmd) {
|
|
217
|
+
console.log(renderError(`Unknown command: /${name}. Type /help for available commands.`));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const result = await cmd.execute(args, {
|
|
221
|
+
session: session.getState(),
|
|
222
|
+
switchModel: (model) => {
|
|
223
|
+
currentModel = model;
|
|
224
|
+
},
|
|
225
|
+
clearHistory: () => {
|
|
226
|
+
session.clear();
|
|
227
|
+
agent.reset();
|
|
228
|
+
permissionManager.resetSession();
|
|
229
|
+
},
|
|
230
|
+
ollamaUrl: options.ollamaUrl,
|
|
231
|
+
adapter,
|
|
232
|
+
model: currentModel,
|
|
233
|
+
agentTokenUsage: agent.getState().tokenUsage,
|
|
234
|
+
permissionManager,
|
|
235
|
+
});
|
|
236
|
+
if (result === '__QUIT__') {
|
|
237
|
+
autoSaver.cancel();
|
|
238
|
+
session.syncFromAgent(agent.getHistory());
|
|
239
|
+
if (session.messages.length > 0) {
|
|
240
|
+
const sid = await saveSession(session.getState(), currentModel);
|
|
241
|
+
console.log(`\n ${DIM('Session saved:')} ${DIM(sid)}`);
|
|
242
|
+
}
|
|
243
|
+
console.log(`\n ${PURPLE('Goodbye.')} ${DIM('Session ended.')}\n`);
|
|
244
|
+
closed = true;
|
|
245
|
+
rl.close();
|
|
246
|
+
process.exit(0);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (result === '__COMPACT__') {
|
|
250
|
+
session.syncFromAgent(agent.getHistory());
|
|
251
|
+
const beforeTokens = session.tokenCount;
|
|
252
|
+
const stats = await session.compact(adapter, currentModel);
|
|
253
|
+
agent.replaceMessages(session.messages);
|
|
254
|
+
const afterTokens = session.tokenCount;
|
|
255
|
+
console.log(renderInfo(`${DIM(`◇ compacted: ${stats.before} messages → ${stats.after} messages (saved ~${stats.tokensSaved} tokens)`)}`));
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (result === '__DIFF__') {
|
|
259
|
+
const gitTool = toolRegistry.get('git_diff');
|
|
260
|
+
if (gitTool) {
|
|
261
|
+
const diffResult = await gitTool.execute({ staged: false }, {
|
|
262
|
+
agent: { name: 'cmdr', role: 'assistant', model: currentModel },
|
|
263
|
+
cwd,
|
|
264
|
+
});
|
|
265
|
+
console.log(`\n${WHITE(diffResult.data)}\n`);
|
|
266
|
+
}
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (result === '__SESSION_SAVE__') {
|
|
270
|
+
session.syncFromAgent(agent.getHistory());
|
|
271
|
+
if (session.messages.length > 0) {
|
|
272
|
+
const sid = await saveSession(session.getState(), currentModel);
|
|
273
|
+
console.log(renderInfo(`Session saved: ${DIM(sid)}`));
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
console.log(renderInfo('No messages to save.'));
|
|
277
|
+
}
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (typeof result === 'string' && result.startsWith('__SESSION_RESUME__:')) {
|
|
281
|
+
const sessionId = result.slice('__SESSION_RESUME__:'.length);
|
|
282
|
+
const saved = await loadSession(sessionId);
|
|
283
|
+
if (saved) {
|
|
284
|
+
agent.replaceMessages(saved.messages);
|
|
285
|
+
session.syncFromAgent(saved.messages);
|
|
286
|
+
console.log(renderInfo(`Resumed session ${DIM(saved.id)} (${saved.messages.length} messages)`));
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
console.log(renderError(`Session not found: ${sessionId}`));
|
|
290
|
+
}
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (typeof result === 'string' && result.startsWith('__TEAM_SWITCH__:')) {
|
|
294
|
+
const preset = result.slice('__TEAM_SWITCH__:'.length);
|
|
295
|
+
const teamCfg = getTeamPreset(preset);
|
|
296
|
+
if (teamCfg) {
|
|
297
|
+
activeTeamConfig = teamCfg;
|
|
298
|
+
const teamAgents = teamCfg.agents.map(a => a.name).join(', ');
|
|
299
|
+
console.log(renderInfo(`Switched to team: ${PURPLE(teamCfg.name)} (${teamAgents})`));
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
console.log(renderError(`Unknown team: ${preset}. Use: review, fullstack, security`));
|
|
303
|
+
}
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (result === '__AGENTS_STATUS__') {
|
|
307
|
+
if (!activeTeamConfig) {
|
|
308
|
+
console.log(renderInfo(`Solo mode (agent: ${GREEN('cmdr')}). Use /team <preset> for multi-agent.`));
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
const status = orchestrator.getStatus();
|
|
312
|
+
const lines = ['', ` ${PURPLE.bold(`Team: ${activeTeamConfig.name}`)}`, ''];
|
|
313
|
+
for (const agentCfg of activeTeamConfig.agents) {
|
|
314
|
+
const agentStatus = status?.agents.find(a => a.name === agentCfg.name);
|
|
315
|
+
const statusLabel = agentStatus ? DIM(agentStatus.status) : DIM('idle');
|
|
316
|
+
lines.push(` ${GREEN('•')} ${WHITE(agentCfg.name.padEnd(12))} ${statusLabel}`);
|
|
317
|
+
}
|
|
318
|
+
lines.push('');
|
|
319
|
+
console.log(lines.join('\n'));
|
|
320
|
+
}
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (result === '__TASKS_STATUS__') {
|
|
324
|
+
const status = orchestrator.getStatus();
|
|
325
|
+
if (!status) {
|
|
326
|
+
console.log(renderInfo('No active team or tasks.'));
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
const s = status.tasks;
|
|
330
|
+
if (s) {
|
|
331
|
+
console.log(renderInfo(`Tasks: ${GREEN(`${s.completed} done`)} · ${YELLOW(`${s.in_progress} running`)} · ${DIM(`${s.pending} pending`)} · ${s.failed > 0 ? RED(`${s.failed} failed`) : DIM('0 failed')}`));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (result === '__COST__') {
|
|
337
|
+
const summary = costTracker.getSummary();
|
|
338
|
+
const elapsed = costTracker.formatElapsed();
|
|
339
|
+
const lines = [
|
|
340
|
+
'',
|
|
341
|
+
` ${PURPLE.bold('Token usage')}`,
|
|
342
|
+
'',
|
|
343
|
+
` ${DIM('Model:')} ${WHITE(summary.model)}`,
|
|
344
|
+
` ${DIM('Turns:')} ${WHITE(String(summary.turns))}`,
|
|
345
|
+
` ${DIM('Input tokens:')} ${WHITE(String(summary.totalInputTokens))}`,
|
|
346
|
+
` ${DIM('Output tokens:')} ${WHITE(String(summary.totalOutputTokens))}`,
|
|
347
|
+
` ${DIM('Total tokens:')} ${GREEN(String(summary.totalTokens))}`,
|
|
348
|
+
` ${DIM('Tool calls:')} ${WHITE(String(summary.totalToolCalls))}`,
|
|
349
|
+
` ${DIM('Avg/turn:')} ${WHITE(String(summary.avgTokensPerTurn))}`,
|
|
350
|
+
` ${DIM('Session time:')} ${WHITE(elapsed)}`,
|
|
351
|
+
'',
|
|
352
|
+
];
|
|
353
|
+
console.log(lines.join('\n'));
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (result === '__UNDO__') {
|
|
357
|
+
if (undoManager.count === 0) {
|
|
358
|
+
console.log(renderInfo('Nothing to undo.'));
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
const change = await undoManager.undoLast();
|
|
362
|
+
if (change) {
|
|
363
|
+
const action = change.originalContent === null ? 'deleted' : 'restored';
|
|
364
|
+
const fname = change.path.split('/').pop() ?? change.path;
|
|
365
|
+
console.log(renderInfo(`Undid ${change.type} on ${GREEN(fname)} (${action})`));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (typeof result === 'string' && result.startsWith('__PLUGIN__:')) {
|
|
371
|
+
const sub = result.slice('__PLUGIN__:'.length).trim();
|
|
372
|
+
if (sub === 'list' || !sub) {
|
|
373
|
+
const plugins = pluginManager.list();
|
|
374
|
+
if (plugins.length === 0) {
|
|
375
|
+
console.log(renderInfo('No plugins loaded. Add plugins to ~/.cmdr/config.toml'));
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
const lines = ['', ` ${PURPLE.bold('Loaded plugins')}`, ''];
|
|
379
|
+
for (const p of plugins) {
|
|
380
|
+
const hooks = p.hooks ? Object.keys(p.hooks).length : 0;
|
|
381
|
+
const tools = p.tools?.length ?? 0;
|
|
382
|
+
console.log(` ${GREEN('•')} ${WHITE(p.name)} v${p.version} ${DIM(`(${hooks} hooks, ${tools} tools)`)}`);
|
|
383
|
+
}
|
|
384
|
+
lines.push('');
|
|
385
|
+
console.log(lines.join('\n'));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (typeof result === 'string' && result.startsWith('__MCP__:')) {
|
|
391
|
+
const sub = result.slice('__MCP__:'.length).trim().split(/\s+/);
|
|
392
|
+
const action = sub[0];
|
|
393
|
+
if (action === 'list' || !action) {
|
|
394
|
+
const conns = mcpClient.listConnections();
|
|
395
|
+
if (conns.length === 0) {
|
|
396
|
+
console.log(renderInfo('No MCP servers connected. Add to ~/.cmdr/config.toml or use /mcp connect <name> <url>'));
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
const lines = ['', ` ${PURPLE.bold('MCP servers')}`, ''];
|
|
400
|
+
for (const c of conns) {
|
|
401
|
+
const status = c.connected ? GREEN('connected') : RED('disconnected');
|
|
402
|
+
lines.push(` ${GREEN('•')} ${WHITE(c.name)} ${DIM(c.url)} ${status} ${DIM(`(${c.tools} tools)`)}`);
|
|
403
|
+
}
|
|
404
|
+
lines.push('');
|
|
405
|
+
console.log(lines.join('\n'));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
else if (action === 'connect') {
|
|
409
|
+
const name = sub[1];
|
|
410
|
+
const url = sub[2];
|
|
411
|
+
if (!name || !url) {
|
|
412
|
+
console.log(renderInfo('Usage: /mcp connect <name> <url>'));
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
try {
|
|
416
|
+
const tools = await mcpClient.connect({ name, url });
|
|
417
|
+
mcpClient.registerTools(toolRegistry);
|
|
418
|
+
console.log(renderInfo(`Connected to ${name}: ${tools.length} tools discovered`));
|
|
419
|
+
}
|
|
420
|
+
catch (err) {
|
|
421
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
422
|
+
console.log(renderError(msg));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
else if (action === 'disconnect') {
|
|
427
|
+
const name = sub[1];
|
|
428
|
+
if (name && mcpClient.disconnect(name)) {
|
|
429
|
+
console.log(renderInfo(`Disconnected from ${name}`));
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
console.log(renderError(`MCP server "${name}" not found`));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (result)
|
|
438
|
+
console.log(result);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
// Regular message — team mode or solo mode
|
|
442
|
+
if (activeTeamConfig) {
|
|
443
|
+
await handleTeamMessage(input, orchestrator, activeTeamConfig, currentModel, verbose);
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
await handleUserMessage(input, agent, session, currentModel, permissionManager, verbose, adapter, costTracker, undoManager, () => {
|
|
447
|
+
autoSaver.schedule(doSave);
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
rl.on('line', (line) => {
|
|
452
|
+
pasteBuffer.push(line);
|
|
453
|
+
if (pasteTimer)
|
|
454
|
+
clearTimeout(pasteTimer);
|
|
455
|
+
pasteTimer = setTimeout(flushPasteBuffer, PASTE_THRESHOLD_MS);
|
|
456
|
+
});
|
|
457
|
+
rl.on('close', async () => {
|
|
458
|
+
autoSaver.cancel();
|
|
459
|
+
if (!closed) {
|
|
460
|
+
session.syncFromAgent(agent.getHistory());
|
|
461
|
+
if (session.messages.length > 0) {
|
|
462
|
+
try {
|
|
463
|
+
const sid = await saveSession(session.getState(), currentModel);
|
|
464
|
+
console.log(`\n ${DIM('Session saved:')} ${DIM(sid)}`);
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
// best effort
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
console.log(`\n ${PURPLE('Goodbye.')} ${DIM('Session ended.')}\n`);
|
|
471
|
+
}
|
|
472
|
+
if (processing) {
|
|
473
|
+
closed = true;
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
process.exit(0);
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
// ---------------------------------------------------------------------------
|
|
481
|
+
// Approval prompt — asks the user to approve a tool call
|
|
482
|
+
// ---------------------------------------------------------------------------
|
|
483
|
+
function promptApproval(toolName, input, riskLevel) {
|
|
484
|
+
return new Promise((resolve) => {
|
|
485
|
+
const riskColor = riskLevel === 'dangerous' ? RED : YELLOW;
|
|
486
|
+
const riskLabel = riskColor(riskLevel.toUpperCase());
|
|
487
|
+
// Show the tool call details
|
|
488
|
+
console.log('');
|
|
489
|
+
console.log(` ${YELLOW('⚠')} ${WHITE('Tool approval required')} ${DIM('[')}${riskLabel}${DIM(']')}`);
|
|
490
|
+
console.log(` ${DIM('Tool:')} ${CYAN(toolName)}`);
|
|
491
|
+
// Show a summary of key arguments
|
|
492
|
+
for (const [key, val] of Object.entries(input)) {
|
|
493
|
+
const display = typeof val === 'string'
|
|
494
|
+
? val.length > 120 ? val.slice(0, 120) + DIM('...') : val
|
|
495
|
+
: JSON.stringify(val).slice(0, 120);
|
|
496
|
+
console.log(` ${DIM(key + ':')} ${WHITE(display)}`);
|
|
497
|
+
}
|
|
498
|
+
console.log('');
|
|
499
|
+
console.log(` ${GREEN('y')}${DIM('es')} ${DIM('/')} ${RED('n')}${DIM('o')} ${DIM('/')} ${PURPLE('a')}${DIM('lways allow this tool')}`);
|
|
500
|
+
// Create a one-shot readline for the approval prompt
|
|
501
|
+
const approvalRl = readline.createInterface({
|
|
502
|
+
input: process.stdin,
|
|
503
|
+
output: process.stdout,
|
|
504
|
+
terminal: true,
|
|
505
|
+
});
|
|
506
|
+
approvalRl.question(` ${YELLOW('?')} `, (answer) => {
|
|
507
|
+
approvalRl.close();
|
|
508
|
+
const trimmed = answer.trim().toLowerCase();
|
|
509
|
+
if (trimmed === 'y' || trimmed === 'yes' || trimmed === '') {
|
|
510
|
+
resolve('allow');
|
|
511
|
+
}
|
|
512
|
+
else if (trimmed === 'a' || trimmed === 'always') {
|
|
513
|
+
resolve('allow-always');
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
resolve('deny');
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
// ---------------------------------------------------------------------------
|
|
522
|
+
// Tool result summary — collapsed single-line display
|
|
523
|
+
// ---------------------------------------------------------------------------
|
|
524
|
+
function summarizeToolResult(toolName, input, content, isError) {
|
|
525
|
+
const lineCount = content.split('\n').length;
|
|
526
|
+
const prefix = isError ? ERROR_SYMBOL : SUCCESS_SYMBOL;
|
|
527
|
+
let summary;
|
|
528
|
+
switch (toolName) {
|
|
529
|
+
case 'file_read': {
|
|
530
|
+
const file = input.path ?? 'unknown';
|
|
531
|
+
const fname = file.split('/').pop() ?? file;
|
|
532
|
+
summary = `${fname} (${lineCount} lines)`;
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
case 'glob': {
|
|
536
|
+
const pattern = input.pattern ?? '*';
|
|
537
|
+
const matches = content === '(no matches)' ? 0 : lineCount;
|
|
538
|
+
summary = `${pattern} (${matches} matches)`;
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
case 'bash': {
|
|
542
|
+
const cmd = input.command ?? '';
|
|
543
|
+
const truncCmd = cmd.length > 60 ? cmd.slice(0, 57) + '...' : cmd;
|
|
544
|
+
// Extract exit code from result if present
|
|
545
|
+
const exitMatch = content.match(/\[exit code: (\d+)\]/);
|
|
546
|
+
const exitCode = exitMatch ? exitMatch[1] : '0';
|
|
547
|
+
summary = `\`${truncCmd}\` exit=${exitCode} (${lineCount} lines)`;
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
case 'grep': {
|
|
551
|
+
const pattern = input.pattern ?? '';
|
|
552
|
+
const matches = content === '(no matches)' ? 0 : lineCount;
|
|
553
|
+
summary = `/${pattern}/ (${matches} matches)`;
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
case 'think': {
|
|
557
|
+
const thought = input.thought ?? '';
|
|
558
|
+
const preview = thought.length > 60 ? thought.slice(0, 57) + '...' : thought;
|
|
559
|
+
summary = preview;
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
default: {
|
|
563
|
+
const bytes = Buffer.byteLength(content, 'utf-8');
|
|
564
|
+
summary = `${bytes > 1024 ? Math.round(bytes / 1024) + ' KB' : bytes + ' B'}`;
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return ` ${prefix} ${DIM(toolName)} ${DIM(summary)}`;
|
|
569
|
+
}
|
|
570
|
+
// ---------------------------------------------------------------------------
|
|
571
|
+
// Message handler — streaming output with tool execution display
|
|
572
|
+
// ---------------------------------------------------------------------------
|
|
573
|
+
async function handleUserMessage(message, agent, session, model, permissionManager, verbose, adapter, costTracker, undoManager, onAfterResponse) {
|
|
574
|
+
console.log(''); // spacing
|
|
575
|
+
startThinking();
|
|
576
|
+
let fullOutput = '';
|
|
577
|
+
let firstText = true;
|
|
578
|
+
let currentTool = '';
|
|
579
|
+
let currentToolInput = {};
|
|
580
|
+
let toolCallCount = 0;
|
|
581
|
+
// Build callbacks with the approval gate
|
|
582
|
+
const callbacks = {
|
|
583
|
+
onToolApproval: (toolName, input, riskLevel) => promptApproval(toolName, input, riskLevel),
|
|
584
|
+
};
|
|
585
|
+
try {
|
|
586
|
+
for await (const event of agent.stream(message, callbacks)) {
|
|
587
|
+
switch (event.type) {
|
|
588
|
+
case 'text': {
|
|
589
|
+
if (firstText) {
|
|
590
|
+
stopSpinner();
|
|
591
|
+
process.stdout.write(`\n ${PURPLE('│')} `);
|
|
592
|
+
firstText = false;
|
|
593
|
+
}
|
|
594
|
+
const chunk = event.data;
|
|
595
|
+
fullOutput += chunk;
|
|
596
|
+
// Stream raw text token-by-token (no markdown on partial chunks)
|
|
597
|
+
// Handle newlines by adding the prefix
|
|
598
|
+
const formatted = chunk.replace(/\n/g, `\n ${PURPLE('│')} `);
|
|
599
|
+
process.stdout.write(formatted);
|
|
600
|
+
break;
|
|
601
|
+
}
|
|
602
|
+
case 'tool_use': {
|
|
603
|
+
stopSpinner();
|
|
604
|
+
if (!firstText) {
|
|
605
|
+
// Terminate previous text stream line
|
|
606
|
+
process.stdout.write('\n');
|
|
607
|
+
firstText = true;
|
|
608
|
+
}
|
|
609
|
+
const block = event.data;
|
|
610
|
+
currentTool = block.name;
|
|
611
|
+
currentToolInput = block.input;
|
|
612
|
+
toolCallCount++;
|
|
613
|
+
// Record file state for undo before write/edit tools
|
|
614
|
+
if (undoManager && (block.name === 'file_write' || block.name === 'file_edit')) {
|
|
615
|
+
const filePath = (block.input.path ?? block.input.file_path);
|
|
616
|
+
if (filePath) {
|
|
617
|
+
await undoManager.recordBefore(filePath, block.name === 'file_write' ? 'write' : 'edit');
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
console.log(renderToolExec(block.name, block.input));
|
|
621
|
+
startToolExec(block.name);
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
case 'tool_result': {
|
|
625
|
+
const block = event.data;
|
|
626
|
+
if (block.is_error) {
|
|
627
|
+
spinnerFail(currentTool);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
spinnerSuccess(currentTool);
|
|
631
|
+
}
|
|
632
|
+
if (verbose) {
|
|
633
|
+
// Full output in verbose mode
|
|
634
|
+
const truncated = block.content.length > 2000
|
|
635
|
+
? block.content.slice(0, 2000) + DIM('\n... (truncated)')
|
|
636
|
+
: block.content;
|
|
637
|
+
const prefix = block.is_error ? ERROR_SYMBOL : SUCCESS_SYMBOL;
|
|
638
|
+
console.log(` ${prefix} ${DIM(currentTool + ':')} ${block.is_error ? RED(truncated) : DIM(truncated)}`);
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
console.log(summarizeToolResult(currentTool, currentToolInput, block.content, block.is_error));
|
|
642
|
+
}
|
|
643
|
+
currentTool = '';
|
|
644
|
+
currentToolInput = {};
|
|
645
|
+
startThinking();
|
|
646
|
+
firstText = true;
|
|
647
|
+
break;
|
|
648
|
+
}
|
|
649
|
+
case 'done': {
|
|
650
|
+
stopSpinner();
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
case 'error': {
|
|
654
|
+
stopSpinner();
|
|
655
|
+
const err = event.data;
|
|
656
|
+
console.error(renderError(err.message));
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
catch (err) {
|
|
663
|
+
stopSpinner();
|
|
664
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
665
|
+
console.error(renderError(msg));
|
|
666
|
+
}
|
|
667
|
+
// Ensure we end the text block with a newline
|
|
668
|
+
if (!firstText) {
|
|
669
|
+
process.stdout.write('\n');
|
|
670
|
+
}
|
|
671
|
+
// Add spacing after response
|
|
672
|
+
if (fullOutput) {
|
|
673
|
+
console.log('');
|
|
674
|
+
}
|
|
675
|
+
// Show turn summary with whimsical verb
|
|
676
|
+
const state = agent.getState();
|
|
677
|
+
const tokens = state.tokenUsage;
|
|
678
|
+
const summary = getCompletionSummary();
|
|
679
|
+
const tokenInfo = tokens.input_tokens > 0 || tokens.output_tokens > 0
|
|
680
|
+
? ` ${DIM('·')} ${DIM(`${tokens.input_tokens} in / ${tokens.output_tokens} out`)}`
|
|
681
|
+
: '';
|
|
682
|
+
console.log(` ${DIM(summary)}${tokenInfo}`);
|
|
683
|
+
// Record cost data
|
|
684
|
+
costTracker?.record(model, tokens.input_tokens, tokens.output_tokens, toolCallCount);
|
|
685
|
+
// Sync agent messages into session for compaction tracking
|
|
686
|
+
session.syncFromAgent(agent.getHistory());
|
|
687
|
+
// Auto-compact if context is getting full
|
|
688
|
+
if (session.shouldCompact()) {
|
|
689
|
+
try {
|
|
690
|
+
const stats = await session.compact(adapter, model);
|
|
691
|
+
agent.replaceMessages(session.messages);
|
|
692
|
+
console.log(` ${DIM(`◇ compacted: ${stats.before} messages → ${stats.after} messages (saved ~${stats.tokensSaved} tokens)`)}`);
|
|
693
|
+
}
|
|
694
|
+
catch {
|
|
695
|
+
// best effort — don't break the REPL
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
// Trigger debounced auto-save
|
|
699
|
+
onAfterResponse?.();
|
|
700
|
+
console.log(GREEN_DIM('─'.repeat(60)));
|
|
701
|
+
console.log('');
|
|
702
|
+
}
|
|
703
|
+
// ---------------------------------------------------------------------------
|
|
704
|
+
// Team message handler — runs goal through the orchestrator
|
|
705
|
+
// ---------------------------------------------------------------------------
|
|
706
|
+
async function handleTeamMessage(goal, orchestrator, teamConfig, model, verbose) {
|
|
707
|
+
console.log('');
|
|
708
|
+
console.log(` ${PURPLE('◈')} Running team ${PURPLE.bold(teamConfig.name)} with ${teamConfig.agents.length} agents...`);
|
|
709
|
+
console.log('');
|
|
710
|
+
startThinking();
|
|
711
|
+
try {
|
|
712
|
+
const result = await orchestrator.runTeam(teamConfig, goal);
|
|
713
|
+
stopSpinner();
|
|
714
|
+
// Display results from each agent
|
|
715
|
+
for (const [agentName, agentResult] of result.agentResults) {
|
|
716
|
+
const status = agentResult.success ? GREEN('✓') : RED('✗');
|
|
717
|
+
console.log(` ${status} ${CYAN(agentName)}`);
|
|
718
|
+
if (agentResult.output) {
|
|
719
|
+
const lines = agentResult.output.split('\n');
|
|
720
|
+
const displayLines = verbose ? lines : lines.slice(0, 20);
|
|
721
|
+
for (const line of displayLines) {
|
|
722
|
+
console.log(` ${PURPLE('│')} ${line}`);
|
|
723
|
+
}
|
|
724
|
+
if (!verbose && lines.length > 20) {
|
|
725
|
+
console.log(` ${PURPLE('│')} ${DIM(`... ${lines.length - 20} more lines (use --verbose)`)}`);
|
|
726
|
+
}
|
|
727
|
+
console.log('');
|
|
728
|
+
}
|
|
729
|
+
// Tool call summary
|
|
730
|
+
if (agentResult.toolCalls.length > 0) {
|
|
731
|
+
const tools = agentResult.toolCalls.map(t => t.toolName);
|
|
732
|
+
const unique = [...new Set(tools)];
|
|
733
|
+
console.log(` ${DIM(` tools: ${unique.join(', ')} (${tools.length} calls)`)}`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
// Summary
|
|
737
|
+
const usage = result.totalTokenUsage;
|
|
738
|
+
const summary = getCompletionSummary();
|
|
739
|
+
const tokenInfo = `${usage.input_tokens} in / ${usage.output_tokens} out`;
|
|
740
|
+
console.log(` ${DIM(summary)} ${DIM('·')} ${DIM(tokenInfo)}`);
|
|
741
|
+
console.log(` ${result.success ? GREEN('Team completed successfully') : RED('Team had failures')}`);
|
|
742
|
+
}
|
|
743
|
+
catch (err) {
|
|
744
|
+
stopSpinner();
|
|
745
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
746
|
+
console.error(renderError(msg));
|
|
747
|
+
}
|
|
748
|
+
console.log(GREEN_DIM('─'.repeat(60)));
|
|
749
|
+
console.log('');
|
|
750
|
+
}
|
|
751
|
+
//# sourceMappingURL=repl.js.map
|