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,595 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session management commands for Claude-Flow
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from '@cliffy/command';
|
|
6
|
+
import { colors } from '@cliffy/ansi/colors';
|
|
7
|
+
import { Table } from '@cliffy/table';
|
|
8
|
+
import { Confirm, Input } from '@cliffy/prompt';
|
|
9
|
+
import { formatDuration, formatStatusIndicator } from '../formatter.ts';
|
|
10
|
+
import { generateId } from '../../utils/helpers.ts';
|
|
11
|
+
|
|
12
|
+
export const sessionCommand = new Command()
|
|
13
|
+
.description('Manage Claude-Flow sessions')
|
|
14
|
+
.action(() => {
|
|
15
|
+
sessionCommand.showHelp();
|
|
16
|
+
})
|
|
17
|
+
.command('list', new Command()
|
|
18
|
+
.description('List all saved sessions')
|
|
19
|
+
.option('-a, --active', 'Show only active sessions')
|
|
20
|
+
.option('--format <format:string>', 'Output format (table, json)', { default: 'table' })
|
|
21
|
+
.action(async (options: any) => {
|
|
22
|
+
await listSessions(options);
|
|
23
|
+
}),
|
|
24
|
+
)
|
|
25
|
+
.command('save', new Command()
|
|
26
|
+
.description('Save current session state')
|
|
27
|
+
.arguments('[name:string]')
|
|
28
|
+
.option('-d, --description <desc:string>', 'Session description')
|
|
29
|
+
.option('-t, --tags <tags:string>', 'Comma-separated tags')
|
|
30
|
+
.option('--auto', 'Auto-generate session name')
|
|
31
|
+
.action(async (options: any, name: string | undefined) => {
|
|
32
|
+
await saveSession(name, options);
|
|
33
|
+
}),
|
|
34
|
+
)
|
|
35
|
+
.command('restore', new Command()
|
|
36
|
+
.description('Restore a saved session')
|
|
37
|
+
.arguments('<session-id:string>')
|
|
38
|
+
.option('-f, --force', 'Force restore without confirmation')
|
|
39
|
+
.option('--merge', 'Merge with current session instead of replacing')
|
|
40
|
+
.action(async (options: any, sessionId: string) => {
|
|
41
|
+
await restoreSession(sessionId, options);
|
|
42
|
+
}),
|
|
43
|
+
)
|
|
44
|
+
.command('delete', new Command()
|
|
45
|
+
.description('Delete a saved session')
|
|
46
|
+
.arguments('<session-id:string>')
|
|
47
|
+
.option('-f, --force', 'Skip confirmation prompt')
|
|
48
|
+
.action(async (options: any, sessionId: string) => {
|
|
49
|
+
await deleteSession(sessionId, options);
|
|
50
|
+
}),
|
|
51
|
+
)
|
|
52
|
+
.command('export', new Command()
|
|
53
|
+
.description('Export session to file')
|
|
54
|
+
.arguments('<session-id:string> <output-file:string>')
|
|
55
|
+
.option('--format <format:string>', 'Export format (json, yaml)', { default: 'json' })
|
|
56
|
+
.option('--include-memory', 'Include agent memory in export')
|
|
57
|
+
.action(async (options: any, sessionId: string, outputFile: string) => {
|
|
58
|
+
await exportSession(sessionId, outputFile, options);
|
|
59
|
+
}),
|
|
60
|
+
)
|
|
61
|
+
.command('import', new Command()
|
|
62
|
+
.description('Import session from file')
|
|
63
|
+
.arguments('<input-file:string>')
|
|
64
|
+
.option('-n, --name <name:string>', 'Custom session name')
|
|
65
|
+
.option('--overwrite', 'Overwrite existing session with same ID')
|
|
66
|
+
.action(async (options: any, inputFile: string) => {
|
|
67
|
+
await importSession(inputFile, options);
|
|
68
|
+
}),
|
|
69
|
+
)
|
|
70
|
+
.command('info', new Command()
|
|
71
|
+
.description('Show detailed session information')
|
|
72
|
+
.arguments('<session-id:string>')
|
|
73
|
+
.action(async (options: any, sessionId: string) => {
|
|
74
|
+
await showSessionInfo(sessionId);
|
|
75
|
+
}),
|
|
76
|
+
)
|
|
77
|
+
.command('clean', new Command()
|
|
78
|
+
.description('Clean up old or orphaned sessions')
|
|
79
|
+
.option('--older-than <days:number>', 'Delete sessions older than N days', { default: 30 })
|
|
80
|
+
.option('--dry-run', 'Show what would be deleted without deleting')
|
|
81
|
+
.option('--orphaned', 'Only clean orphaned sessions')
|
|
82
|
+
.action(async (options: any) => {
|
|
83
|
+
await cleanSessions(options);
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
interface SessionData {
|
|
88
|
+
id: string;
|
|
89
|
+
name: string;
|
|
90
|
+
description?: string;
|
|
91
|
+
tags: string[];
|
|
92
|
+
createdAt: Date;
|
|
93
|
+
updatedAt: Date;
|
|
94
|
+
state: {
|
|
95
|
+
agents: any[];
|
|
96
|
+
tasks: any[];
|
|
97
|
+
memory: any[];
|
|
98
|
+
configuration: any;
|
|
99
|
+
};
|
|
100
|
+
metadata: {
|
|
101
|
+
version: string;
|
|
102
|
+
platform: string;
|
|
103
|
+
checksum: string;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const SESSION_DIR = '.claude-flow/sessions';
|
|
108
|
+
|
|
109
|
+
async function ensureSessionDir(): Promise<void> {
|
|
110
|
+
try {
|
|
111
|
+
await Deno.mkdir(SESSION_DIR, { recursive: true });
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (!(error instanceof Deno.errors.AlreadyExists)) {
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function listSessions(options: any): Promise<void> {
|
|
120
|
+
try {
|
|
121
|
+
await ensureSessionDir();
|
|
122
|
+
const sessions = await loadAllSessions();
|
|
123
|
+
|
|
124
|
+
let filteredSessions = sessions;
|
|
125
|
+
if (options.active) {
|
|
126
|
+
// In production, this would check if the session is currently active
|
|
127
|
+
filteredSessions = sessions.filter(s => (s.metadata as any).active);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (options.format === 'json') {
|
|
131
|
+
console.log(JSON.stringify(filteredSessions, null, 2));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (filteredSessions.length === 0) {
|
|
136
|
+
console.log(colors.gray('No sessions found'));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
console.log(colors.cyan.bold(`Sessions (${filteredSessions.length})`));
|
|
141
|
+
console.log('─'.repeat(60));
|
|
142
|
+
|
|
143
|
+
const table = new Table()
|
|
144
|
+
.header(['ID', 'Name', 'Description', 'Agents', 'Tasks', 'Created'])
|
|
145
|
+
.border(true);
|
|
146
|
+
|
|
147
|
+
for (const session of filteredSessions) {
|
|
148
|
+
table.push([
|
|
149
|
+
colors.gray(session.id.substring(0, 8) + '...'),
|
|
150
|
+
colors.white(session.name),
|
|
151
|
+
session.description ? session.description.substring(0, 30) + (session.description.length > 30 ? '...' : '') : '-',
|
|
152
|
+
session.state.agents.length.toString(),
|
|
153
|
+
session.state.tasks.length.toString(),
|
|
154
|
+
session.createdAt.toLocaleDateString()
|
|
155
|
+
]);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
table.render();
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error(colors.red('Failed to list sessions:'), (error as Error).message);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function saveSession(name: string | undefined, options: any): Promise<void> {
|
|
165
|
+
try {
|
|
166
|
+
// Get current session state (mock for now)
|
|
167
|
+
const currentState = await getCurrentSessionState();
|
|
168
|
+
|
|
169
|
+
if (!name) {
|
|
170
|
+
if (options.auto) {
|
|
171
|
+
name = `session-${new Date().toISOString().split('T')[0]}-${Date.now().toString().slice(-4)}`;
|
|
172
|
+
} else {
|
|
173
|
+
name = await Input.prompt({
|
|
174
|
+
message: 'Enter session name:',
|
|
175
|
+
default: `session-${new Date().toISOString().split('T')[0]}`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const session: SessionData = {
|
|
181
|
+
id: generateId('session'),
|
|
182
|
+
name,
|
|
183
|
+
description: options.description,
|
|
184
|
+
tags: options.tags ? options.tags.split(',').map((t: string) => t.trim()) : [],
|
|
185
|
+
createdAt: new Date(),
|
|
186
|
+
updatedAt: new Date(),
|
|
187
|
+
state: currentState,
|
|
188
|
+
metadata: {
|
|
189
|
+
version: '1.0.0',
|
|
190
|
+
platform: Deno.build.os,
|
|
191
|
+
checksum: await calculateChecksum(currentState)
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
await ensureSessionDir();
|
|
196
|
+
const filePath = `${SESSION_DIR}/${session.id}.json`;
|
|
197
|
+
await Deno.writeTextFile(filePath, JSON.stringify(session, null, 2));
|
|
198
|
+
|
|
199
|
+
console.log(colors.green('✓ Session saved successfully'));
|
|
200
|
+
console.log(`${colors.white('ID:')} ${session.id}`);
|
|
201
|
+
console.log(`${colors.white('Name:')} ${session.name}`);
|
|
202
|
+
console.log(`${colors.white('File:')} ${filePath}`);
|
|
203
|
+
|
|
204
|
+
if (session.description) {
|
|
205
|
+
console.log(`${colors.white('Description:')} ${session.description}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
console.log(`${colors.white('Agents:')} ${session.state.agents.length}`);
|
|
209
|
+
console.log(`${colors.white('Tasks:')} ${session.state.tasks.length}`);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error(colors.red('Failed to save session:'), (error as Error).message);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function restoreSession(sessionId: string, options: any): Promise<void> {
|
|
216
|
+
try {
|
|
217
|
+
const session = await loadSession(sessionId);
|
|
218
|
+
|
|
219
|
+
if (!session) {
|
|
220
|
+
console.error(colors.red(`Session '${sessionId}' not found`));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Show session info
|
|
225
|
+
console.log(colors.cyan.bold('Session to restore:'));
|
|
226
|
+
console.log(`${colors.white('Name:')} ${session.name}`);
|
|
227
|
+
console.log(`${colors.white('Description:')} ${session.description || 'None'}`);
|
|
228
|
+
console.log(`${colors.white('Agents:')} ${session.state.agents.length}`);
|
|
229
|
+
console.log(`${colors.white('Tasks:')} ${session.state.tasks.length}`);
|
|
230
|
+
console.log(`${colors.white('Created:')} ${session.createdAt.toLocaleString()}`);
|
|
231
|
+
|
|
232
|
+
// Confirmation
|
|
233
|
+
if (!options.force) {
|
|
234
|
+
const action = options.merge ? 'merge with current session' : 'replace current session';
|
|
235
|
+
const confirmed = await Confirm.prompt({
|
|
236
|
+
message: `Are you sure you want to ${action}?`,
|
|
237
|
+
default: false,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
if (!confirmed) {
|
|
241
|
+
console.log(colors.gray('Restore cancelled'));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Validate session integrity
|
|
247
|
+
const expectedChecksum = await calculateChecksum(session.state);
|
|
248
|
+
if (session.metadata.checksum !== expectedChecksum) {
|
|
249
|
+
console.log(colors.yellow('⚠ Warning: Session checksum mismatch. Data may be corrupted.'));
|
|
250
|
+
|
|
251
|
+
if (!options.force) {
|
|
252
|
+
const proceed = await Confirm.prompt({
|
|
253
|
+
message: 'Continue anyway?',
|
|
254
|
+
default: false,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (!proceed) {
|
|
258
|
+
console.log(colors.gray('Restore cancelled'));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Restore session (mock for now)
|
|
265
|
+
console.log(colors.yellow('Restoring session...'));
|
|
266
|
+
|
|
267
|
+
if (options.merge) {
|
|
268
|
+
console.log(colors.blue('• Merging agents...'));
|
|
269
|
+
console.log(colors.blue('• Merging tasks...'));
|
|
270
|
+
console.log(colors.blue('• Merging memory...'));
|
|
271
|
+
} else {
|
|
272
|
+
console.log(colors.blue('• Stopping current agents...'));
|
|
273
|
+
console.log(colors.blue('• Clearing current tasks...'));
|
|
274
|
+
console.log(colors.blue('• Restoring agents...'));
|
|
275
|
+
console.log(colors.blue('• Restoring tasks...'));
|
|
276
|
+
console.log(colors.blue('• Restoring memory...'));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Update session metadata
|
|
280
|
+
session.updatedAt = new Date();
|
|
281
|
+
const filePath = `${SESSION_DIR}/${session.id}.json`;
|
|
282
|
+
await Deno.writeTextFile(filePath, JSON.stringify(session, null, 2));
|
|
283
|
+
|
|
284
|
+
console.log(colors.green('✓ Session restored successfully'));
|
|
285
|
+
console.log(colors.yellow('Note: This is a mock implementation. In production, this would connect to the orchestrator.'));
|
|
286
|
+
} catch (error) {
|
|
287
|
+
console.error(colors.red('Failed to restore session:'), (error as Error).message);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function deleteSession(sessionId: string, options: any): Promise<void> {
|
|
292
|
+
try {
|
|
293
|
+
const session = await loadSession(sessionId);
|
|
294
|
+
|
|
295
|
+
if (!session) {
|
|
296
|
+
console.error(colors.red(`Session '${sessionId}' not found`));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Confirmation
|
|
301
|
+
if (!options.force) {
|
|
302
|
+
console.log(`${colors.white('Session:')} ${session.name}`);
|
|
303
|
+
console.log(`${colors.white('Created:')} ${session.createdAt.toLocaleString()}`);
|
|
304
|
+
|
|
305
|
+
const confirmed = await Confirm.prompt({
|
|
306
|
+
message: 'Are you sure you want to delete this session?',
|
|
307
|
+
default: false,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
if (!confirmed) {
|
|
311
|
+
console.log(colors.gray('Delete cancelled'));
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const filePath = `${SESSION_DIR}/${session.id}.json`;
|
|
317
|
+
await Deno.remove(filePath);
|
|
318
|
+
|
|
319
|
+
console.log(colors.green('✓ Session deleted successfully'));
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.error(colors.red('Failed to delete session:'), (error as Error).message);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function exportSession(sessionId: string, outputFile: string, options: any): Promise<void> {
|
|
326
|
+
try {
|
|
327
|
+
const session = await loadSession(sessionId);
|
|
328
|
+
|
|
329
|
+
if (!session) {
|
|
330
|
+
console.error(colors.red(`Session '${sessionId}' not found`));
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
let exportData = session;
|
|
335
|
+
|
|
336
|
+
if (!options.includeMemory) {
|
|
337
|
+
exportData = {
|
|
338
|
+
...session,
|
|
339
|
+
state: {
|
|
340
|
+
...session.state,
|
|
341
|
+
memory: [] // Exclude memory data
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
let content: string;
|
|
347
|
+
if (options.format === 'yaml') {
|
|
348
|
+
// In production, you'd use a YAML library
|
|
349
|
+
console.log(colors.yellow('YAML export not implemented yet, using JSON'));
|
|
350
|
+
content = JSON.stringify(exportData, null, 2);
|
|
351
|
+
} else {
|
|
352
|
+
content = JSON.stringify(exportData, null, 2);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
await Deno.writeTextFile(outputFile, content);
|
|
356
|
+
|
|
357
|
+
console.log(colors.green('✓ Session exported successfully'));
|
|
358
|
+
console.log(`${colors.white('File:')} ${outputFile}`);
|
|
359
|
+
console.log(`${colors.white('Format:')} ${options.format}`);
|
|
360
|
+
console.log(`${colors.white('Size:')} ${new Blob([content]).size} bytes`);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.error(colors.red('Failed to export session:'), (error as Error).message);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function importSession(inputFile: string, options: any): Promise<void> {
|
|
367
|
+
try {
|
|
368
|
+
const content = await Deno.readTextFile(inputFile);
|
|
369
|
+
const sessionData = JSON.parse(content) as SessionData;
|
|
370
|
+
|
|
371
|
+
// Validate session data structure
|
|
372
|
+
if (!sessionData.id || !sessionData.name || !sessionData.state) {
|
|
373
|
+
throw new Error('Invalid session file format');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Generate new ID if not overwriting
|
|
377
|
+
if (!options.overwrite) {
|
|
378
|
+
sessionData.id = generateId('session');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Update name if specified
|
|
382
|
+
if (options.name) {
|
|
383
|
+
sessionData.name = options.name;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Check if session already exists
|
|
387
|
+
const existingSession = await loadSession(sessionData.id);
|
|
388
|
+
if (existingSession && !options.overwrite) {
|
|
389
|
+
console.error(colors.red('Session with this ID already exists'));
|
|
390
|
+
console.log(colors.gray('Use --overwrite to replace it'));
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Update timestamps
|
|
395
|
+
if (options.overwrite && existingSession) {
|
|
396
|
+
sessionData.updatedAt = new Date();
|
|
397
|
+
} else {
|
|
398
|
+
sessionData.createdAt = new Date();
|
|
399
|
+
sessionData.updatedAt = new Date();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
await ensureSessionDir();
|
|
403
|
+
const filePath = `${SESSION_DIR}/${sessionData.id}.json`;
|
|
404
|
+
await Deno.writeTextFile(filePath, JSON.stringify(sessionData, null, 2));
|
|
405
|
+
|
|
406
|
+
console.log(colors.green('✓ Session imported successfully'));
|
|
407
|
+
console.log(`${colors.white('ID:')} ${sessionData.id}`);
|
|
408
|
+
console.log(`${colors.white('Name:')} ${sessionData.name}`);
|
|
409
|
+
console.log(`${colors.white('Action:')} ${options.overwrite ? 'Overwritten' : 'Created'}`);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
console.error(colors.red('Failed to import session:'), (error as Error).message);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async function showSessionInfo(sessionId: string): Promise<void> {
|
|
416
|
+
try {
|
|
417
|
+
const session = await loadSession(sessionId);
|
|
418
|
+
|
|
419
|
+
if (!session) {
|
|
420
|
+
console.error(colors.red(`Session '${sessionId}' not found`));
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
console.log(colors.cyan.bold('Session Information'));
|
|
425
|
+
console.log('─'.repeat(40));
|
|
426
|
+
console.log(`${colors.white('ID:')} ${session.id}`);
|
|
427
|
+
console.log(`${colors.white('Name:')} ${session.name}`);
|
|
428
|
+
console.log(`${colors.white('Description:')} ${session.description || 'None'}`);
|
|
429
|
+
console.log(`${colors.white('Tags:')} ${session.tags.join(', ') || 'None'}`);
|
|
430
|
+
console.log(`${colors.white('Created:')} ${session.createdAt.toLocaleString()}`);
|
|
431
|
+
console.log(`${colors.white('Updated:')} ${session.updatedAt.toLocaleString()}`);
|
|
432
|
+
console.log();
|
|
433
|
+
|
|
434
|
+
console.log(colors.cyan.bold('State Summary'));
|
|
435
|
+
console.log('─'.repeat(40));
|
|
436
|
+
console.log(`${colors.white('Agents:')} ${session.state.agents.length}`);
|
|
437
|
+
console.log(`${colors.white('Tasks:')} ${session.state.tasks.length}`);
|
|
438
|
+
console.log(`${colors.white('Memory Entries:')} ${session.state.memory.length}`);
|
|
439
|
+
console.log();
|
|
440
|
+
|
|
441
|
+
console.log(colors.cyan.bold('Metadata'));
|
|
442
|
+
console.log('─'.repeat(40));
|
|
443
|
+
console.log(`${colors.white('Version:')} ${session.metadata.version}`);
|
|
444
|
+
console.log(`${colors.white('Platform:')} ${session.metadata.platform}`);
|
|
445
|
+
console.log(`${colors.white('Checksum:')} ${session.metadata.checksum}`);
|
|
446
|
+
|
|
447
|
+
// Verify integrity
|
|
448
|
+
const currentChecksum = await calculateChecksum(session.state);
|
|
449
|
+
const integrity = currentChecksum === session.metadata.checksum;
|
|
450
|
+
const integrityIcon = formatStatusIndicator(integrity ? 'success' : 'error');
|
|
451
|
+
console.log(`${colors.white('Integrity:')} ${integrityIcon} ${integrity ? 'Valid' : 'Corrupted'}`);
|
|
452
|
+
|
|
453
|
+
// File info
|
|
454
|
+
const filePath = `${SESSION_DIR}/${session.id}.json`;
|
|
455
|
+
try {
|
|
456
|
+
const fileInfo = await Deno.stat(filePath);
|
|
457
|
+
console.log();
|
|
458
|
+
console.log(colors.cyan.bold('File Information'));
|
|
459
|
+
console.log('─'.repeat(40));
|
|
460
|
+
console.log(`${colors.white('Path:')} ${filePath}`);
|
|
461
|
+
console.log(`${colors.white('Size:')} ${fileInfo.size} bytes`);
|
|
462
|
+
console.log(`${colors.white('Modified:')} ${fileInfo.mtime?.toLocaleString() || 'Unknown'}`);
|
|
463
|
+
} catch {
|
|
464
|
+
console.log(colors.red('Warning: Session file not found'));
|
|
465
|
+
}
|
|
466
|
+
} catch (error) {
|
|
467
|
+
console.error(colors.red('Failed to show session info:'), (error as Error).message);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function cleanSessions(options: any): Promise<void> {
|
|
472
|
+
try {
|
|
473
|
+
await ensureSessionDir();
|
|
474
|
+
const sessions = await loadAllSessions();
|
|
475
|
+
|
|
476
|
+
const cutoffDate = new Date();
|
|
477
|
+
cutoffDate.setDate(cutoffDate.getDate() - options.olderThan);
|
|
478
|
+
|
|
479
|
+
let toDelete = sessions.filter(session => session.createdAt < cutoffDate);
|
|
480
|
+
|
|
481
|
+
if (options.orphaned) {
|
|
482
|
+
// In production, check if sessions have valid references
|
|
483
|
+
toDelete = toDelete.filter(session => (session.metadata as any).orphaned);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (toDelete.length === 0) {
|
|
487
|
+
console.log(colors.gray('No sessions to clean'));
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
console.log(colors.cyan.bold(`Sessions to clean (${toDelete.length})`));
|
|
492
|
+
console.log('─'.repeat(50));
|
|
493
|
+
|
|
494
|
+
for (const session of toDelete) {
|
|
495
|
+
const age = Math.floor((Date.now() - session.createdAt.getTime()) / (1000 * 60 * 60 * 24));
|
|
496
|
+
console.log(`• ${session.name} (${colors.gray(session.id.substring(0, 8) + '...')}) - ${age} days old`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (options.dryRun) {
|
|
500
|
+
console.log('\n' + colors.yellow('Dry run mode - no files were deleted'));
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
console.log();
|
|
505
|
+
const confirmed = await Confirm.prompt({
|
|
506
|
+
message: `Delete ${toDelete.length} sessions?`,
|
|
507
|
+
default: false,
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
if (!confirmed) {
|
|
511
|
+
console.log(colors.gray('Clean cancelled'));
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
let deleted = 0;
|
|
516
|
+
for (const session of toDelete) {
|
|
517
|
+
try {
|
|
518
|
+
const filePath = `${SESSION_DIR}/${session.id}.json`;
|
|
519
|
+
await Deno.remove(filePath);
|
|
520
|
+
deleted++;
|
|
521
|
+
} catch (error) {
|
|
522
|
+
console.error(colors.red(`Failed to delete ${session.name}:`), (error as Error).message);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
console.log(colors.green(`✓ Cleaned ${deleted} sessions`));
|
|
527
|
+
} catch (error) {
|
|
528
|
+
console.error(colors.red('Failed to clean sessions:'), (error as Error).message);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
async function loadAllSessions(): Promise<SessionData[]> {
|
|
533
|
+
const sessions: SessionData[] = [];
|
|
534
|
+
|
|
535
|
+
try {
|
|
536
|
+
for await (const entry of Deno.readDir(SESSION_DIR)) {
|
|
537
|
+
if (entry.isFile && entry.name.endsWith('.json')) {
|
|
538
|
+
try {
|
|
539
|
+
const content = await Deno.readTextFile(`${SESSION_DIR}/${entry.name}`);
|
|
540
|
+
const session = JSON.parse(content) as SessionData;
|
|
541
|
+
|
|
542
|
+
// Convert date strings back to Date objects
|
|
543
|
+
session.createdAt = new Date(session.createdAt);
|
|
544
|
+
session.updatedAt = new Date(session.updatedAt);
|
|
545
|
+
|
|
546
|
+
sessions.push(session);
|
|
547
|
+
} catch (error) {
|
|
548
|
+
console.warn(colors.yellow(`Warning: Failed to load session file ${entry.name}:`), (error as Error).message);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
} catch (error) {
|
|
553
|
+
if (!(error instanceof Deno.errors.NotFound)) {
|
|
554
|
+
throw error;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return sessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
async function loadSession(sessionId: string): Promise<SessionData | null> {
|
|
562
|
+
const sessions = await loadAllSessions();
|
|
563
|
+
return sessions.find(s => s.id === sessionId || s.id.startsWith(sessionId)) || null;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
async function getCurrentSessionState(): Promise<any> {
|
|
567
|
+
// Mock current session state - in production, this would connect to the orchestrator
|
|
568
|
+
return {
|
|
569
|
+
agents: [
|
|
570
|
+
{ id: 'agent-001', type: 'coordinator', status: 'active' },
|
|
571
|
+
{ id: 'agent-002', type: 'researcher', status: 'active' }
|
|
572
|
+
],
|
|
573
|
+
tasks: [
|
|
574
|
+
{ id: 'task-001', type: 'research', status: 'running' },
|
|
575
|
+
{ id: 'task-002', type: 'analysis', status: 'pending' }
|
|
576
|
+
],
|
|
577
|
+
memory: [
|
|
578
|
+
{ id: 'memory-001', type: 'conversation', agentId: 'agent-001' },
|
|
579
|
+
{ id: 'memory-002', type: 'result', agentId: 'agent-002' }
|
|
580
|
+
],
|
|
581
|
+
configuration: {
|
|
582
|
+
orchestrator: { maxAgents: 10 },
|
|
583
|
+
memory: { backend: 'hybrid' }
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
async function calculateChecksum(data: any): Promise<string> {
|
|
589
|
+
const content = JSON.stringify(data, null, 0);
|
|
590
|
+
const encoder = new TextEncoder();
|
|
591
|
+
const dataBuffer = encoder.encode(content);
|
|
592
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer);
|
|
593
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
594
|
+
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('').substring(0, 16);
|
|
595
|
+
}
|