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,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal pool management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Terminal, ITerminalAdapter } from './adapters/base.ts';
|
|
6
|
+
import { ILogger } from '../core/logger.ts';
|
|
7
|
+
import { TerminalError } from '../utils/errors.ts';
|
|
8
|
+
import { delay } from '../utils/helpers.ts';
|
|
9
|
+
|
|
10
|
+
interface PooledTerminal {
|
|
11
|
+
terminal: Terminal;
|
|
12
|
+
useCount: number;
|
|
13
|
+
lastUsed: Date;
|
|
14
|
+
inUse: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Terminal pool for efficient resource management
|
|
19
|
+
*/
|
|
20
|
+
export class TerminalPool {
|
|
21
|
+
private terminals = new Map<string, PooledTerminal>();
|
|
22
|
+
private availableQueue: string[] = [];
|
|
23
|
+
private initializationPromise?: Promise<void>;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
private maxSize: number,
|
|
27
|
+
private recycleAfter: number,
|
|
28
|
+
private adapter: ITerminalAdapter,
|
|
29
|
+
private logger: ILogger,
|
|
30
|
+
) {}
|
|
31
|
+
|
|
32
|
+
async initialize(): Promise<void> {
|
|
33
|
+
if (this.initializationPromise) {
|
|
34
|
+
return this.initializationPromise;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.initializationPromise = this.doInitialize();
|
|
38
|
+
return this.initializationPromise;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private async doInitialize(): Promise<void> {
|
|
42
|
+
this.logger.info('Initializing terminal pool', {
|
|
43
|
+
maxSize: this.maxSize,
|
|
44
|
+
recycleAfter: this.recycleAfter,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Pre-create some terminals
|
|
48
|
+
const preCreateCount = Math.min(2, this.maxSize);
|
|
49
|
+
const promises: Promise<void>[] = [];
|
|
50
|
+
|
|
51
|
+
for (let i = 0; i < preCreateCount; i++) {
|
|
52
|
+
promises.push(this.createPooledTerminal());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await Promise.all(promises);
|
|
56
|
+
|
|
57
|
+
this.logger.info('Terminal pool initialized', {
|
|
58
|
+
created: preCreateCount,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async shutdown(): Promise<void> {
|
|
63
|
+
this.logger.info('Shutting down terminal pool');
|
|
64
|
+
|
|
65
|
+
// Destroy all terminals
|
|
66
|
+
const terminals = Array.from(this.terminals.values());
|
|
67
|
+
await Promise.all(
|
|
68
|
+
terminals.map(({ terminal }) => this.adapter.destroyTerminal(terminal)),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
this.terminals.clear();
|
|
72
|
+
this.availableQueue = [];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async acquire(): Promise<Terminal> {
|
|
76
|
+
// Try to get an available terminal
|
|
77
|
+
while (this.availableQueue.length > 0) {
|
|
78
|
+
const terminalId = this.availableQueue.shift()!;
|
|
79
|
+
const pooled = this.terminals.get(terminalId);
|
|
80
|
+
|
|
81
|
+
if (pooled && pooled.terminal.isAlive()) {
|
|
82
|
+
pooled.inUse = true;
|
|
83
|
+
pooled.lastUsed = new Date();
|
|
84
|
+
|
|
85
|
+
this.logger.debug('Terminal acquired from pool', {
|
|
86
|
+
terminalId,
|
|
87
|
+
useCount: pooled.useCount,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return pooled.terminal;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Terminal is dead, remove it
|
|
94
|
+
if (pooled) {
|
|
95
|
+
this.terminals.delete(terminalId);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// No available terminals, create new one if under limit
|
|
100
|
+
if (this.terminals.size < this.maxSize) {
|
|
101
|
+
await this.createPooledTerminal();
|
|
102
|
+
return this.acquire(); // Recursive call to get the newly created terminal
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Pool is full, wait for a terminal to become available
|
|
106
|
+
this.logger.info('Terminal pool full, waiting for available terminal');
|
|
107
|
+
|
|
108
|
+
const startTime = Date.now();
|
|
109
|
+
const timeout = 30000; // 30 seconds
|
|
110
|
+
|
|
111
|
+
while (Date.now() - startTime < timeout) {
|
|
112
|
+
await delay(100);
|
|
113
|
+
|
|
114
|
+
// Check if any terminal became available
|
|
115
|
+
const available = Array.from(this.terminals.values()).find(
|
|
116
|
+
(pooled) => !pooled.inUse && pooled.terminal.isAlive(),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (available) {
|
|
120
|
+
available.inUse = true;
|
|
121
|
+
available.lastUsed = new Date();
|
|
122
|
+
return available.terminal;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
throw new TerminalError('No terminal available in pool (timeout)');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async release(terminal: Terminal): Promise<void> {
|
|
130
|
+
const pooled = this.terminals.get(terminal.id);
|
|
131
|
+
if (!pooled) {
|
|
132
|
+
this.logger.warn('Attempted to release unknown terminal', {
|
|
133
|
+
terminalId: terminal.id,
|
|
134
|
+
});
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
pooled.useCount++;
|
|
139
|
+
pooled.inUse = false;
|
|
140
|
+
|
|
141
|
+
// Check if terminal should be recycled
|
|
142
|
+
if (pooled.useCount >= this.recycleAfter || !terminal.isAlive()) {
|
|
143
|
+
this.logger.info('Recycling terminal', {
|
|
144
|
+
terminalId: terminal.id,
|
|
145
|
+
useCount: pooled.useCount,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Destroy old terminal
|
|
149
|
+
this.terminals.delete(terminal.id);
|
|
150
|
+
await this.adapter.destroyTerminal(terminal);
|
|
151
|
+
|
|
152
|
+
// Create replacement if under limit
|
|
153
|
+
if (this.terminals.size < this.maxSize) {
|
|
154
|
+
await this.createPooledTerminal();
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
// Return to available queue
|
|
158
|
+
this.availableQueue.push(terminal.id);
|
|
159
|
+
|
|
160
|
+
this.logger.debug('Terminal returned to pool', {
|
|
161
|
+
terminalId: terminal.id,
|
|
162
|
+
useCount: pooled.useCount,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async getHealthStatus(): Promise<{
|
|
168
|
+
healthy: boolean;
|
|
169
|
+
size: number;
|
|
170
|
+
available: number;
|
|
171
|
+
recycled: number;
|
|
172
|
+
}> {
|
|
173
|
+
const aliveTerminals = Array.from(this.terminals.values()).filter(
|
|
174
|
+
(pooled) => pooled.terminal.isAlive(),
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const available = aliveTerminals.filter((pooled) => !pooled.inUse).length;
|
|
178
|
+
const recycled = Array.from(this.terminals.values()).filter(
|
|
179
|
+
(pooled) => pooled.useCount >= this.recycleAfter,
|
|
180
|
+
).length;
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
healthy: aliveTerminals.length > 0,
|
|
184
|
+
size: this.terminals.size,
|
|
185
|
+
available,
|
|
186
|
+
recycled,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async performMaintenance(): Promise<void> {
|
|
191
|
+
this.logger.debug('Performing terminal pool maintenance');
|
|
192
|
+
|
|
193
|
+
// Remove dead terminals
|
|
194
|
+
const deadTerminals: string[] = [];
|
|
195
|
+
for (const [id, pooled] of this.terminals.entries()) {
|
|
196
|
+
if (!pooled.terminal.isAlive()) {
|
|
197
|
+
deadTerminals.push(id);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Clean up dead terminals
|
|
202
|
+
for (const id of deadTerminals) {
|
|
203
|
+
this.logger.warn('Removing dead terminal from pool', { terminalId: id });
|
|
204
|
+
this.terminals.delete(id);
|
|
205
|
+
const index = this.availableQueue.indexOf(id);
|
|
206
|
+
if (index !== -1) {
|
|
207
|
+
this.availableQueue.splice(index, 1);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Ensure minimum pool size
|
|
212
|
+
const currentSize = this.terminals.size;
|
|
213
|
+
const minSize = Math.min(2, this.maxSize);
|
|
214
|
+
|
|
215
|
+
if (currentSize < minSize) {
|
|
216
|
+
const toCreate = minSize - currentSize;
|
|
217
|
+
this.logger.info('Replenishing terminal pool', {
|
|
218
|
+
currentSize,
|
|
219
|
+
minSize,
|
|
220
|
+
creating: toCreate,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const promises: Promise<void>[] = [];
|
|
224
|
+
for (let i = 0; i < toCreate; i++) {
|
|
225
|
+
promises.push(this.createPooledTerminal());
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
await Promise.all(promises);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Check for stale terminals that should be recycled
|
|
232
|
+
const now = Date.now();
|
|
233
|
+
const staleTimeout = 300000; // 5 minutes
|
|
234
|
+
|
|
235
|
+
for (const [id, pooled] of this.terminals.entries()) {
|
|
236
|
+
if (!pooled.inUse && pooled.terminal.isAlive()) {
|
|
237
|
+
const idleTime = now - pooled.lastUsed.getTime();
|
|
238
|
+
if (idleTime > staleTimeout) {
|
|
239
|
+
this.logger.info('Recycling stale terminal', {
|
|
240
|
+
terminalId: id,
|
|
241
|
+
idleTime,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Mark for recycling
|
|
245
|
+
pooled.useCount = this.recycleAfter;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private async createPooledTerminal(): Promise<void> {
|
|
252
|
+
try {
|
|
253
|
+
const terminal = await this.adapter.createTerminal();
|
|
254
|
+
|
|
255
|
+
const pooled: PooledTerminal = {
|
|
256
|
+
terminal,
|
|
257
|
+
useCount: 0,
|
|
258
|
+
lastUsed: new Date(),
|
|
259
|
+
inUse: false,
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
this.terminals.set(terminal.id, pooled);
|
|
263
|
+
this.availableQueue.push(terminal.id);
|
|
264
|
+
|
|
265
|
+
this.logger.debug('Created pooled terminal', { terminalId: terminal.id });
|
|
266
|
+
} catch (error) {
|
|
267
|
+
this.logger.error('Failed to create pooled terminal', error);
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal session management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Terminal } from './adapters/base.ts';
|
|
6
|
+
import { AgentProfile } from '../utils/types.ts';
|
|
7
|
+
import { ILogger } from '../core/logger.ts';
|
|
8
|
+
import { TerminalCommandError } from '../utils/errors.ts';
|
|
9
|
+
import { generateId, timeout } from '../utils/helpers.ts';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Terminal session wrapper
|
|
13
|
+
*/
|
|
14
|
+
export class TerminalSession {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly startTime: Date;
|
|
17
|
+
private initialized = false;
|
|
18
|
+
private commandHistory: string[] = [];
|
|
19
|
+
private lastCommandTime?: Date;
|
|
20
|
+
private outputListeners = new Set<(output: string) => void>();
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
public readonly terminal: Terminal,
|
|
24
|
+
public readonly profile: AgentProfile,
|
|
25
|
+
private commandTimeout: number,
|
|
26
|
+
private logger: ILogger,
|
|
27
|
+
) {
|
|
28
|
+
this.id = generateId('session');
|
|
29
|
+
this.startTime = new Date();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get lastActivity(): Date {
|
|
33
|
+
return this.lastCommandTime || this.startTime;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async initialize(): Promise<void> {
|
|
37
|
+
if (this.initialized) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.logger.debug('Initializing terminal session', {
|
|
42
|
+
sessionId: this.id,
|
|
43
|
+
agentId: this.profile.id,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Set up environment
|
|
48
|
+
await this.setupEnvironment();
|
|
49
|
+
|
|
50
|
+
// Run initialization commands
|
|
51
|
+
await this.runInitializationCommands();
|
|
52
|
+
|
|
53
|
+
this.initialized = true;
|
|
54
|
+
|
|
55
|
+
this.logger.info('Terminal session initialized', {
|
|
56
|
+
sessionId: this.id,
|
|
57
|
+
agentId: this.profile.id,
|
|
58
|
+
});
|
|
59
|
+
} catch (error) {
|
|
60
|
+
this.logger.error('Failed to initialize terminal session', error);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async executeCommand(command: string): Promise<string> {
|
|
66
|
+
if (!this.initialized) {
|
|
67
|
+
throw new TerminalCommandError('Session not initialized');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!this.terminal.isAlive()) {
|
|
71
|
+
throw new TerminalCommandError('Terminal is not alive');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.logger.debug('Executing command', {
|
|
75
|
+
sessionId: this.id,
|
|
76
|
+
command: command.substring(0, 100),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
// Notify listeners of command
|
|
81
|
+
this.notifyOutputListeners(`$ ${command}\n`);
|
|
82
|
+
|
|
83
|
+
// Execute with timeout
|
|
84
|
+
const result = await timeout(
|
|
85
|
+
this.terminal.executeCommand(command),
|
|
86
|
+
this.commandTimeout,
|
|
87
|
+
`Command timeout after ${this.commandTimeout}ms`,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Notify listeners of output
|
|
91
|
+
this.notifyOutputListeners(result);
|
|
92
|
+
|
|
93
|
+
// Update history
|
|
94
|
+
this.commandHistory.push(command);
|
|
95
|
+
this.lastCommandTime = new Date();
|
|
96
|
+
|
|
97
|
+
this.logger.debug('Command executed successfully', {
|
|
98
|
+
sessionId: this.id,
|
|
99
|
+
outputLength: result.length,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return result;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
this.logger.error('Command execution failed', {
|
|
105
|
+
sessionId: this.id,
|
|
106
|
+
command,
|
|
107
|
+
error,
|
|
108
|
+
});
|
|
109
|
+
throw new TerminalCommandError('Command execution failed', {
|
|
110
|
+
command,
|
|
111
|
+
error,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async cleanup(): Promise<void> {
|
|
117
|
+
this.logger.debug('Cleaning up terminal session', { sessionId: this.id });
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
// Run cleanup commands
|
|
121
|
+
await this.runCleanupCommands();
|
|
122
|
+
} catch (error) {
|
|
123
|
+
this.logger.warn('Error during session cleanup', {
|
|
124
|
+
sessionId: this.id,
|
|
125
|
+
error,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
isHealthy(): boolean {
|
|
131
|
+
if (!this.terminal.isAlive()) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Check if terminal is responsive
|
|
136
|
+
if (this.lastCommandTime) {
|
|
137
|
+
const timeSinceLastCommand = Date.now() - this.lastCommandTime.getTime();
|
|
138
|
+
if (timeSinceLastCommand > 300000) { // 5 minutes
|
|
139
|
+
// Terminal might be stale, do a health check
|
|
140
|
+
this.performHealthCheck().catch((error) => {
|
|
141
|
+
this.logger.warn('Health check failed', { sessionId: this.id, error });
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
getCommandHistory(): string[] {
|
|
150
|
+
return [...this.commandHistory];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private async setupEnvironment(): Promise<void> {
|
|
154
|
+
// Set environment variables
|
|
155
|
+
const envVars = {
|
|
156
|
+
CLAUDE_FLOW_SESSION: this.id,
|
|
157
|
+
CLAUDE_FLOW_AGENT: this.profile.id,
|
|
158
|
+
CLAUDE_FLOW_AGENT_TYPE: this.profile.type,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
162
|
+
await this.terminal.executeCommand(`export ${key}="${value}"`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Set working directory if specified
|
|
166
|
+
if (this.profile.metadata?.workingDirectory) {
|
|
167
|
+
await this.terminal.executeCommand(
|
|
168
|
+
`cd "${this.profile.metadata.workingDirectory}"`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private async runInitializationCommands(): Promise<void> {
|
|
174
|
+
// Run any profile-specific initialization commands
|
|
175
|
+
if (this.profile.metadata?.initCommands) {
|
|
176
|
+
const commands = this.profile.metadata.initCommands as string[];
|
|
177
|
+
for (const command of commands) {
|
|
178
|
+
await this.terminal.executeCommand(command);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Set up command prompt
|
|
183
|
+
await this.terminal.executeCommand('export PS1="[claude-flow]$ "');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private async runCleanupCommands(): Promise<void> {
|
|
187
|
+
// Run any profile-specific cleanup commands
|
|
188
|
+
if (this.profile.metadata?.cleanupCommands) {
|
|
189
|
+
const commands = this.profile.metadata.cleanupCommands as string[];
|
|
190
|
+
for (const command of commands) {
|
|
191
|
+
try {
|
|
192
|
+
await this.terminal.executeCommand(command);
|
|
193
|
+
} catch {
|
|
194
|
+
// Ignore cleanup errors
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private async performHealthCheck(): Promise<void> {
|
|
201
|
+
try {
|
|
202
|
+
const result = await timeout(
|
|
203
|
+
this.terminal.executeCommand('echo "HEALTH_CHECK_OK"'),
|
|
204
|
+
5000,
|
|
205
|
+
'Health check timeout',
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
if (!result.includes('HEALTH_CHECK_OK')) {
|
|
209
|
+
throw new Error('Invalid health check response');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this.lastCommandTime = new Date();
|
|
213
|
+
} catch (error) {
|
|
214
|
+
throw new Error(`Health check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Stream terminal output
|
|
220
|
+
*/
|
|
221
|
+
streamOutput(callback: (output: string) => void): () => void {
|
|
222
|
+
this.outputListeners.add(callback);
|
|
223
|
+
|
|
224
|
+
// Set up terminal output listener if supported
|
|
225
|
+
if (this.terminal.addOutputListener) {
|
|
226
|
+
this.terminal.addOutputListener(callback);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Return unsubscribe function
|
|
230
|
+
return () => {
|
|
231
|
+
this.outputListeners.delete(callback);
|
|
232
|
+
if (this.terminal.removeOutputListener) {
|
|
233
|
+
this.terminal.removeOutputListener(callback);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Notify output listeners
|
|
240
|
+
*/
|
|
241
|
+
private notifyOutputListeners(output: string): void {
|
|
242
|
+
this.outputListeners.forEach(listener => {
|
|
243
|
+
try {
|
|
244
|
+
listener(output);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
this.logger.error('Error in output listener', { sessionId: this.id, error });
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|