claude-flow 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 +612 -0
- package/bin/claude-flow +0 -0
- package/bin/claude-flow-simple +0 -0
- package/bin/claude-flow-typecheck +0 -0
- package/deno.json +84 -0
- package/package.json +45 -0
- package/scripts/check-links.ts +274 -0
- package/scripts/check-performance-regression.ts +168 -0
- package/scripts/claude-sparc.sh +562 -0
- package/scripts/coverage-report.ts +692 -0
- package/scripts/demo-task-system.ts +224 -0
- package/scripts/install.js +72 -0
- package/scripts/test-batch-tasks.ts +29 -0
- package/scripts/test-coordination-features.ts +238 -0
- package/scripts/test-mcp.ts +251 -0
- package/scripts/test-runner.ts +571 -0
- package/scripts/validate-examples.ts +288 -0
- package/src/cli/cli-core.ts +273 -0
- package/src/cli/commands/agent.ts +83 -0
- package/src/cli/commands/config.ts +442 -0
- package/src/cli/commands/help.ts +765 -0
- package/src/cli/commands/index.ts +963 -0
- package/src/cli/commands/mcp.ts +191 -0
- package/src/cli/commands/memory.ts +74 -0
- package/src/cli/commands/monitor.ts +403 -0
- package/src/cli/commands/session.ts +595 -0
- package/src/cli/commands/start.ts +156 -0
- package/src/cli/commands/status.ts +345 -0
- package/src/cli/commands/task.ts +79 -0
- package/src/cli/commands/workflow.ts +763 -0
- package/src/cli/completion.ts +553 -0
- package/src/cli/formatter.ts +310 -0
- package/src/cli/index.ts +211 -0
- package/src/cli/main.ts +23 -0
- package/src/cli/repl.ts +1050 -0
- package/src/cli/simple-cli.js +211 -0
- package/src/cli/simple-cli.ts +211 -0
- package/src/coordination/README.md +400 -0
- package/src/coordination/advanced-scheduler.ts +487 -0
- package/src/coordination/circuit-breaker.ts +366 -0
- package/src/coordination/conflict-resolution.ts +490 -0
- package/src/coordination/dependency-graph.ts +475 -0
- package/src/coordination/index.ts +63 -0
- package/src/coordination/manager.ts +460 -0
- package/src/coordination/messaging.ts +290 -0
- package/src/coordination/metrics.ts +585 -0
- package/src/coordination/resources.ts +322 -0
- package/src/coordination/scheduler.ts +390 -0
- package/src/coordination/work-stealing.ts +224 -0
- package/src/core/config.ts +627 -0
- package/src/core/event-bus.ts +186 -0
- package/src/core/json-persistence.ts +183 -0
- package/src/core/logger.ts +262 -0
- package/src/core/orchestrator-fixed.ts +312 -0
- package/src/core/orchestrator.ts +1234 -0
- package/src/core/persistence.ts +276 -0
- package/src/mcp/auth.ts +438 -0
- package/src/mcp/claude-flow-tools.ts +1280 -0
- package/src/mcp/load-balancer.ts +510 -0
- package/src/mcp/router.ts +240 -0
- package/src/mcp/server.ts +548 -0
- package/src/mcp/session-manager.ts +418 -0
- package/src/mcp/tools.ts +180 -0
- package/src/mcp/transports/base.ts +21 -0
- package/src/mcp/transports/http.ts +457 -0
- package/src/mcp/transports/stdio.ts +254 -0
- package/src/memory/backends/base.ts +22 -0
- package/src/memory/backends/markdown.ts +283 -0
- package/src/memory/backends/sqlite.ts +329 -0
- package/src/memory/cache.ts +238 -0
- package/src/memory/indexer.ts +238 -0
- package/src/memory/manager.ts +572 -0
- package/src/terminal/adapters/base.ts +29 -0
- package/src/terminal/adapters/native.ts +504 -0
- package/src/terminal/adapters/vscode.ts +340 -0
- package/src/terminal/manager.ts +308 -0
- package/src/terminal/pool.ts +271 -0
- package/src/terminal/session.ts +250 -0
- package/src/terminal/vscode-bridge.ts +242 -0
- package/src/utils/errors.ts +231 -0
- package/src/utils/helpers.ts +476 -0
- package/src/utils/types.ts +493 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VSCode terminal adapter implementation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ITerminalAdapter, Terminal } from './base.ts';
|
|
6
|
+
import { ILogger } from '../../core/logger.ts';
|
|
7
|
+
import { TerminalError } from '../../utils/errors.ts';
|
|
8
|
+
import { generateId, delay, timeout, createDeferred } from '../../utils/helpers.ts';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* VSCode API interface (injected via extension)
|
|
12
|
+
*/
|
|
13
|
+
interface VSCodeAPI {
|
|
14
|
+
window: {
|
|
15
|
+
createTerminal(options: {
|
|
16
|
+
name: string;
|
|
17
|
+
shellPath?: string;
|
|
18
|
+
shellArgs?: string[];
|
|
19
|
+
env?: Record<string, string>;
|
|
20
|
+
}): VSCodeTerminal;
|
|
21
|
+
onDidCloseTerminal(listener: (terminal: VSCodeTerminal) => void): { dispose(): void };
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface VSCodeTerminal {
|
|
26
|
+
name: string;
|
|
27
|
+
processId: Promise<number | undefined>;
|
|
28
|
+
sendText(text: string, addNewLine?: boolean): void;
|
|
29
|
+
show(preserveFocus?: boolean): void;
|
|
30
|
+
hide(): void;
|
|
31
|
+
dispose(): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* VSCode terminal implementation
|
|
36
|
+
*/
|
|
37
|
+
class VSCodeTerminalWrapper implements Terminal {
|
|
38
|
+
id: string;
|
|
39
|
+
pid?: number;
|
|
40
|
+
private vscodeTerminal?: VSCodeTerminal;
|
|
41
|
+
private outputBuffer = '';
|
|
42
|
+
private commandMarker: string;
|
|
43
|
+
private outputDeferred = createDeferred<string>();
|
|
44
|
+
private isDisposed = false;
|
|
45
|
+
|
|
46
|
+
constructor(
|
|
47
|
+
private vscodeApi: VSCodeAPI,
|
|
48
|
+
private shellType: string,
|
|
49
|
+
private logger: ILogger,
|
|
50
|
+
) {
|
|
51
|
+
this.id = generateId('vscode-term');
|
|
52
|
+
this.commandMarker = `__CLAUDE_FLOW_${this.id}__`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async initialize(): Promise<void> {
|
|
56
|
+
try {
|
|
57
|
+
// Create VSCode terminal
|
|
58
|
+
const shellPath = this.getShellPath();
|
|
59
|
+
const terminalOptions: any = {
|
|
60
|
+
name: `Claude-Flow Terminal ${this.id}`,
|
|
61
|
+
shellArgs: this.getShellArgs(),
|
|
62
|
+
env: {
|
|
63
|
+
CLAUDE_FLOW_TERMINAL: 'true',
|
|
64
|
+
CLAUDE_FLOW_TERMINAL_ID: this.id,
|
|
65
|
+
PS1: '$ ', // Simple prompt
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
if (shellPath !== undefined) {
|
|
69
|
+
terminalOptions.shellPath = shellPath;
|
|
70
|
+
}
|
|
71
|
+
this.vscodeTerminal = this.vscodeApi.window.createTerminal(terminalOptions);
|
|
72
|
+
|
|
73
|
+
// Get process ID
|
|
74
|
+
const processId = await this.vscodeTerminal.processId;
|
|
75
|
+
if (processId !== undefined) {
|
|
76
|
+
this.pid = processId;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Show terminal (but don't steal focus)
|
|
80
|
+
this.vscodeTerminal.show(true);
|
|
81
|
+
|
|
82
|
+
// Wait for terminal to be ready
|
|
83
|
+
await this.waitForReady();
|
|
84
|
+
|
|
85
|
+
this.logger.debug('VSCode terminal initialized', { id: this.id, pid: this.pid });
|
|
86
|
+
} catch (error) {
|
|
87
|
+
throw new TerminalError('Failed to create VSCode terminal', { error });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async executeCommand(command: string): Promise<string> {
|
|
92
|
+
if (!this.vscodeTerminal || !this.isAlive()) {
|
|
93
|
+
throw new TerminalError('Terminal is not alive');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
// Clear output buffer
|
|
98
|
+
this.outputBuffer = '';
|
|
99
|
+
this.outputDeferred = createDeferred<string>();
|
|
100
|
+
|
|
101
|
+
// Send command with marker
|
|
102
|
+
const markedCommand = `${command} && echo "${this.commandMarker}"`;
|
|
103
|
+
this.vscodeTerminal.sendText(markedCommand, true);
|
|
104
|
+
|
|
105
|
+
// Wait for command to complete
|
|
106
|
+
const output = await timeout(
|
|
107
|
+
this.outputDeferred.promise,
|
|
108
|
+
30000,
|
|
109
|
+
'Command execution timeout',
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
return output;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw new TerminalError('Failed to execute command', { command, error });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async write(data: string): Promise<void> {
|
|
119
|
+
if (!this.vscodeTerminal || !this.isAlive()) {
|
|
120
|
+
throw new TerminalError('Terminal is not alive');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
this.vscodeTerminal.sendText(data, false);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async read(): Promise<string> {
|
|
127
|
+
if (!this.vscodeTerminal || !this.isAlive()) {
|
|
128
|
+
throw new TerminalError('Terminal is not alive');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Return buffered output
|
|
132
|
+
const output = this.outputBuffer;
|
|
133
|
+
this.outputBuffer = '';
|
|
134
|
+
return output;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
isAlive(): boolean {
|
|
138
|
+
return !this.isDisposed && this.vscodeTerminal !== undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async kill(): Promise<void> {
|
|
142
|
+
if (this.vscodeTerminal && !this.isDisposed) {
|
|
143
|
+
try {
|
|
144
|
+
// Try graceful shutdown first
|
|
145
|
+
this.vscodeTerminal.sendText('exit', true);
|
|
146
|
+
await delay(500);
|
|
147
|
+
|
|
148
|
+
// Dispose terminal
|
|
149
|
+
this.vscodeTerminal.dispose();
|
|
150
|
+
this.isDisposed = true;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
this.logger.warn('Error killing VSCode terminal', { id: this.id, error });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Process terminal output (called by extension)
|
|
159
|
+
*/
|
|
160
|
+
processOutput(data: string): void {
|
|
161
|
+
this.outputBuffer += data;
|
|
162
|
+
|
|
163
|
+
// Check for command completion marker
|
|
164
|
+
const markerIndex = this.outputBuffer.indexOf(this.commandMarker);
|
|
165
|
+
if (markerIndex !== -1) {
|
|
166
|
+
// Extract output before marker
|
|
167
|
+
const output = this.outputBuffer.substring(0, markerIndex).trim();
|
|
168
|
+
|
|
169
|
+
// Clear buffer up to after marker
|
|
170
|
+
this.outputBuffer = this.outputBuffer.substring(
|
|
171
|
+
markerIndex + this.commandMarker.length,
|
|
172
|
+
).trim();
|
|
173
|
+
|
|
174
|
+
// Resolve pending command
|
|
175
|
+
this.outputDeferred.resolve(output);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private getShellPath(): string | undefined {
|
|
180
|
+
switch (this.shellType) {
|
|
181
|
+
case 'bash':
|
|
182
|
+
return '/bin/bash';
|
|
183
|
+
case 'zsh':
|
|
184
|
+
return '/bin/zsh';
|
|
185
|
+
case 'powershell':
|
|
186
|
+
return Deno.build.os === 'windows' ? 'powershell.exe' : 'pwsh';
|
|
187
|
+
case 'cmd':
|
|
188
|
+
return Deno.build.os === 'windows' ? 'cmd.exe' : undefined;
|
|
189
|
+
default:
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private getShellArgs(): string[] {
|
|
195
|
+
switch (this.shellType) {
|
|
196
|
+
case 'bash':
|
|
197
|
+
return ['--norc', '--noprofile'];
|
|
198
|
+
case 'zsh':
|
|
199
|
+
return ['--no-rcs'];
|
|
200
|
+
case 'powershell':
|
|
201
|
+
return ['-NoProfile', '-NonInteractive'];
|
|
202
|
+
case 'cmd':
|
|
203
|
+
return ['/Q'];
|
|
204
|
+
default:
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private async waitForReady(): Promise<void> {
|
|
210
|
+
// Send a test command to ensure terminal is ready
|
|
211
|
+
this.vscodeTerminal!.sendText('echo "READY"', true);
|
|
212
|
+
|
|
213
|
+
const startTime = Date.now();
|
|
214
|
+
while (Date.now() - startTime < 5000) {
|
|
215
|
+
if (this.outputBuffer.includes('READY')) {
|
|
216
|
+
this.outputBuffer = '';
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
await delay(100);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
throw new TerminalError('Terminal failed to become ready');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* VSCode terminal adapter
|
|
228
|
+
*/
|
|
229
|
+
export class VSCodeAdapter implements ITerminalAdapter {
|
|
230
|
+
private terminals = new Map<string, VSCodeTerminalWrapper>();
|
|
231
|
+
private vscodeApi?: VSCodeAPI;
|
|
232
|
+
private shellType: string;
|
|
233
|
+
private terminalCloseListener?: { dispose(): void };
|
|
234
|
+
|
|
235
|
+
constructor(private logger: ILogger) {
|
|
236
|
+
this.shellType = this.detectShell();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async initialize(): Promise<void> {
|
|
240
|
+
this.logger.info('Initializing VSCode terminal adapter');
|
|
241
|
+
|
|
242
|
+
// Check if running in VSCode extension context
|
|
243
|
+
if (!this.isVSCodeExtensionContext()) {
|
|
244
|
+
throw new TerminalError('Not running in VSCode extension context');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Get VSCode API from global
|
|
248
|
+
this.vscodeApi = (globalThis as any).vscode;
|
|
249
|
+
if (!this.vscodeApi) {
|
|
250
|
+
throw new TerminalError('VSCode API not available');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Register terminal close listener
|
|
254
|
+
this.terminalCloseListener = this.vscodeApi.window.onDidCloseTerminal((terminal) => {
|
|
255
|
+
// Find and clean up closed terminal
|
|
256
|
+
for (const [id, wrapper] of this.terminals.entries()) {
|
|
257
|
+
if ((wrapper as any).vscodeTerminal === terminal) {
|
|
258
|
+
this.logger.info('VSCode terminal closed', { id });
|
|
259
|
+
this.terminals.delete(id);
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
this.logger.info('VSCode terminal adapter initialized');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async shutdown(): Promise<void> {
|
|
269
|
+
this.logger.info('Shutting down VSCode terminal adapter');
|
|
270
|
+
|
|
271
|
+
// Dispose listener
|
|
272
|
+
if (this.terminalCloseListener) {
|
|
273
|
+
this.terminalCloseListener.dispose();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Kill all terminals
|
|
277
|
+
const terminals = Array.from(this.terminals.values());
|
|
278
|
+
await Promise.all(terminals.map(term => term.kill()));
|
|
279
|
+
|
|
280
|
+
this.terminals.clear();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async createTerminal(): Promise<Terminal> {
|
|
284
|
+
if (!this.vscodeApi) {
|
|
285
|
+
throw new TerminalError('VSCode API not initialized');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const terminal = new VSCodeTerminalWrapper(
|
|
289
|
+
this.vscodeApi,
|
|
290
|
+
this.shellType,
|
|
291
|
+
this.logger,
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
await terminal.initialize();
|
|
295
|
+
this.terminals.set(terminal.id, terminal);
|
|
296
|
+
|
|
297
|
+
// Register output processor if extension provides it
|
|
298
|
+
const outputProcessor = (globalThis as any).registerTerminalOutputProcessor;
|
|
299
|
+
if (outputProcessor) {
|
|
300
|
+
outputProcessor(terminal.id, (data: string) => terminal.processOutput(data));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return terminal;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async destroyTerminal(terminal: Terminal): Promise<void> {
|
|
307
|
+
await terminal.kill();
|
|
308
|
+
this.terminals.delete(terminal.id);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private isVSCodeExtensionContext(): boolean {
|
|
312
|
+
// Check for VSCode extension environment
|
|
313
|
+
return typeof (globalThis as any).vscode !== 'undefined' &&
|
|
314
|
+
typeof (globalThis as any).vscode.window !== 'undefined';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private detectShell(): string {
|
|
318
|
+
// Get default shell from VSCode settings or environment
|
|
319
|
+
const platform = Deno.build.os;
|
|
320
|
+
|
|
321
|
+
if (platform === 'windows') {
|
|
322
|
+
// Windows defaults
|
|
323
|
+
const comspec = Deno.env.get('COMSPEC');
|
|
324
|
+
if (comspec?.toLowerCase().includes('powershell')) {
|
|
325
|
+
return 'powershell';
|
|
326
|
+
}
|
|
327
|
+
return 'cmd';
|
|
328
|
+
} else {
|
|
329
|
+
// Unix-like defaults
|
|
330
|
+
const shell = Deno.env.get('SHELL');
|
|
331
|
+
if (shell) {
|
|
332
|
+
const shellName = shell.split('/').pop();
|
|
333
|
+
if (shellName && ['bash', 'zsh', 'fish', 'sh'].includes(shellName)) {
|
|
334
|
+
return shellName;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return 'bash';
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal manager interface and implementation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { AgentProfile, AgentSession, TerminalConfig } from '../utils/types.ts';
|
|
6
|
+
import { IEventBus } from '../core/event-bus.ts';
|
|
7
|
+
import { ILogger } from '../core/logger.ts';
|
|
8
|
+
import { TerminalError, TerminalSpawnError } from '../utils/errors.ts';
|
|
9
|
+
import { ITerminalAdapter } from './adapters/base.ts';
|
|
10
|
+
import { VSCodeAdapter } from './adapters/vscode.ts';
|
|
11
|
+
import { NativeAdapter } from './adapters/native.ts';
|
|
12
|
+
import { TerminalPool } from './pool.ts';
|
|
13
|
+
import { TerminalSession } from './session.ts';
|
|
14
|
+
|
|
15
|
+
export interface ITerminalManager {
|
|
16
|
+
initialize(): Promise<void>;
|
|
17
|
+
shutdown(): Promise<void>;
|
|
18
|
+
spawnTerminal(profile: AgentProfile): Promise<string>;
|
|
19
|
+
terminateTerminal(terminalId: string): Promise<void>;
|
|
20
|
+
executeCommand(terminalId: string, command: string): Promise<string>;
|
|
21
|
+
getHealthStatus(): Promise<{ healthy: boolean; error?: string; metrics?: Record<string, number> }>;
|
|
22
|
+
performMaintenance(): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Terminal manager implementation
|
|
27
|
+
*/
|
|
28
|
+
export class TerminalManager implements ITerminalManager {
|
|
29
|
+
private adapter: ITerminalAdapter;
|
|
30
|
+
private pool: TerminalPool;
|
|
31
|
+
private sessions = new Map<string, TerminalSession>();
|
|
32
|
+
private initialized = false;
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
private config: TerminalConfig,
|
|
36
|
+
private eventBus: IEventBus,
|
|
37
|
+
private logger: ILogger,
|
|
38
|
+
) {
|
|
39
|
+
// Select adapter based on configuration
|
|
40
|
+
this.adapter = this.createAdapter();
|
|
41
|
+
|
|
42
|
+
// Create terminal pool
|
|
43
|
+
this.pool = new TerminalPool(
|
|
44
|
+
this.config.poolSize,
|
|
45
|
+
this.config.recycleAfter,
|
|
46
|
+
this.adapter,
|
|
47
|
+
this.logger,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async initialize(): Promise<void> {
|
|
52
|
+
if (this.initialized) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.logger.info('Initializing terminal manager...');
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// Initialize adapter
|
|
60
|
+
await this.adapter.initialize();
|
|
61
|
+
|
|
62
|
+
// Initialize pool
|
|
63
|
+
await this.pool.initialize();
|
|
64
|
+
|
|
65
|
+
this.initialized = true;
|
|
66
|
+
this.logger.info('Terminal manager initialized');
|
|
67
|
+
} catch (error) {
|
|
68
|
+
this.logger.error('Failed to initialize terminal manager', error);
|
|
69
|
+
throw new TerminalError('Terminal manager initialization failed', { error });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async shutdown(): Promise<void> {
|
|
74
|
+
if (!this.initialized) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.logger.info('Shutting down terminal manager...');
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
// Terminate all sessions
|
|
82
|
+
const sessionIds = Array.from(this.sessions.keys());
|
|
83
|
+
await Promise.all(
|
|
84
|
+
sessionIds.map((id) => this.terminateTerminal(id)),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Shutdown pool
|
|
88
|
+
await this.pool.shutdown();
|
|
89
|
+
|
|
90
|
+
// Shutdown adapter
|
|
91
|
+
await this.adapter.shutdown();
|
|
92
|
+
|
|
93
|
+
this.initialized = false;
|
|
94
|
+
this.logger.info('Terminal manager shutdown complete');
|
|
95
|
+
} catch (error) {
|
|
96
|
+
this.logger.error('Error during terminal manager shutdown', error);
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async spawnTerminal(profile: AgentProfile): Promise<string> {
|
|
102
|
+
if (!this.initialized) {
|
|
103
|
+
throw new TerminalError('Terminal manager not initialized');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.logger.debug('Spawning terminal', { agentId: profile.id });
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
// Get terminal from pool
|
|
110
|
+
const terminal = await this.pool.acquire();
|
|
111
|
+
|
|
112
|
+
// Create session
|
|
113
|
+
const session = new TerminalSession(
|
|
114
|
+
terminal,
|
|
115
|
+
profile,
|
|
116
|
+
this.config.commandTimeout,
|
|
117
|
+
this.logger,
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Initialize session
|
|
121
|
+
await session.initialize();
|
|
122
|
+
|
|
123
|
+
// Store session
|
|
124
|
+
this.sessions.set(session.id, session);
|
|
125
|
+
|
|
126
|
+
this.logger.info('Terminal spawned', {
|
|
127
|
+
terminalId: session.id,
|
|
128
|
+
agentId: profile.id,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return session.id;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
this.logger.error('Failed to spawn terminal', error);
|
|
134
|
+
throw new TerminalSpawnError('Failed to spawn terminal', { error });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async terminateTerminal(terminalId: string): Promise<void> {
|
|
139
|
+
const session = this.sessions.get(terminalId);
|
|
140
|
+
if (!session) {
|
|
141
|
+
throw new TerminalError(`Terminal not found: ${terminalId}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.logger.debug('Terminating terminal', { terminalId });
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
// Cleanup session
|
|
148
|
+
await session.cleanup();
|
|
149
|
+
|
|
150
|
+
// Return terminal to pool
|
|
151
|
+
await this.pool.release(session.terminal);
|
|
152
|
+
|
|
153
|
+
// Remove session
|
|
154
|
+
this.sessions.delete(terminalId);
|
|
155
|
+
|
|
156
|
+
this.logger.info('Terminal terminated', { terminalId });
|
|
157
|
+
} catch (error) {
|
|
158
|
+
this.logger.error('Failed to terminate terminal', error);
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async executeCommand(terminalId: string, command: string): Promise<string> {
|
|
164
|
+
const session = this.sessions.get(terminalId);
|
|
165
|
+
if (!session) {
|
|
166
|
+
throw new TerminalError(`Terminal not found: ${terminalId}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return await session.executeCommand(command);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async getHealthStatus(): Promise<{
|
|
173
|
+
healthy: boolean;
|
|
174
|
+
error?: string;
|
|
175
|
+
metrics?: Record<string, number>;
|
|
176
|
+
}> {
|
|
177
|
+
try {
|
|
178
|
+
const poolHealth = await this.pool.getHealthStatus();
|
|
179
|
+
const activeSessions = this.sessions.size;
|
|
180
|
+
const healthySessions = Array.from(this.sessions.values())
|
|
181
|
+
.filter((session) => session.isHealthy()).length;
|
|
182
|
+
|
|
183
|
+
const metrics = {
|
|
184
|
+
activeSessions,
|
|
185
|
+
healthySessions,
|
|
186
|
+
poolSize: poolHealth.size,
|
|
187
|
+
availableTerminals: poolHealth.available,
|
|
188
|
+
recycledTerminals: poolHealth.recycled,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const healthy = poolHealth.healthy && healthySessions === activeSessions;
|
|
192
|
+
|
|
193
|
+
if (healthy) {
|
|
194
|
+
return {
|
|
195
|
+
healthy,
|
|
196
|
+
metrics,
|
|
197
|
+
};
|
|
198
|
+
} else {
|
|
199
|
+
return {
|
|
200
|
+
healthy,
|
|
201
|
+
metrics,
|
|
202
|
+
error: 'Some terminals are unhealthy',
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
} catch (error) {
|
|
206
|
+
return {
|
|
207
|
+
healthy: false,
|
|
208
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async performMaintenance(): Promise<void> {
|
|
214
|
+
if (!this.initialized) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
this.logger.debug('Performing terminal manager maintenance');
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
// Clean up dead sessions
|
|
222
|
+
const deadSessions = Array.from(this.sessions.entries())
|
|
223
|
+
.filter(([_, session]) => !session.isHealthy());
|
|
224
|
+
|
|
225
|
+
for (const [terminalId, _] of deadSessions) {
|
|
226
|
+
this.logger.warn('Cleaning up dead terminal session', { terminalId });
|
|
227
|
+
await this.terminateTerminal(terminalId).catch(error =>
|
|
228
|
+
this.logger.error('Failed to clean up terminal', { terminalId, error })
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Perform pool maintenance
|
|
233
|
+
await this.pool.performMaintenance();
|
|
234
|
+
|
|
235
|
+
// Emit maintenance event
|
|
236
|
+
this.eventBus.emit('terminal:maintenance', {
|
|
237
|
+
deadSessions: deadSessions.length,
|
|
238
|
+
activeSessions: this.sessions.size,
|
|
239
|
+
poolStatus: await this.pool.getHealthStatus(),
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
this.logger.debug('Terminal manager maintenance completed');
|
|
243
|
+
} catch (error) {
|
|
244
|
+
this.logger.error('Error during terminal manager maintenance', error);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get all active sessions
|
|
250
|
+
*/
|
|
251
|
+
getActiveSessions(): AgentSession[] {
|
|
252
|
+
return Array.from(this.sessions.values()).map(session => ({
|
|
253
|
+
id: session.id,
|
|
254
|
+
agentId: session.profile.id,
|
|
255
|
+
terminalId: session.terminal.id,
|
|
256
|
+
startTime: session.startTime,
|
|
257
|
+
status: session.isHealthy() ? 'active' : 'error',
|
|
258
|
+
lastActivity: session.lastActivity,
|
|
259
|
+
memoryBankId: '', // TODO: Link to memory bank
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Get session by ID
|
|
265
|
+
*/
|
|
266
|
+
getSession(sessionId: string): TerminalSession | undefined {
|
|
267
|
+
return this.sessions.get(sessionId);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Stream terminal output
|
|
272
|
+
*/
|
|
273
|
+
async streamOutput(terminalId: string, callback: (output: string) => void): Promise<() => void> {
|
|
274
|
+
const session = this.sessions.get(terminalId);
|
|
275
|
+
if (!session) {
|
|
276
|
+
throw new TerminalError(`Terminal not found: ${terminalId}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return session.streamOutput(callback);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private createAdapter(): ITerminalAdapter {
|
|
283
|
+
switch (this.config.type) {
|
|
284
|
+
case 'vscode':
|
|
285
|
+
return new VSCodeAdapter(this.logger);
|
|
286
|
+
case 'native':
|
|
287
|
+
return new NativeAdapter(this.logger);
|
|
288
|
+
case 'auto':
|
|
289
|
+
// Detect environment and choose appropriate adapter
|
|
290
|
+
if (this.isVSCodeEnvironment()) {
|
|
291
|
+
this.logger.info('Detected VSCode environment, using VSCode adapter');
|
|
292
|
+
return new VSCodeAdapter(this.logger);
|
|
293
|
+
} else {
|
|
294
|
+
this.logger.info('Using native terminal adapter');
|
|
295
|
+
return new NativeAdapter(this.logger);
|
|
296
|
+
}
|
|
297
|
+
default:
|
|
298
|
+
throw new TerminalError(`Unknown terminal type: ${this.config.type}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private isVSCodeEnvironment(): boolean {
|
|
303
|
+
// Check for VSCode-specific environment variables
|
|
304
|
+
return Deno.env.get('TERM_PROGRAM') === 'vscode' ||
|
|
305
|
+
Deno.env.get('VSCODE_PID') !== undefined ||
|
|
306
|
+
Deno.env.get('VSCODE_IPC_HOOK') !== undefined;
|
|
307
|
+
}
|
|
308
|
+
}
|