@wundr.io/cli 1.0.1 → 1.0.2-dev.20260530180455.e1307186
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/bin/wundr.js +13 -5
- package/package.json +30 -9
- package/src/ai/ai-service.ts +6 -4
- package/src/ai/claude-client.ts +6 -2
- package/src/ai/conversation-manager.ts +12 -5
- package/src/cli.ts +42 -13
- package/src/commands/ai.ts +340 -64
- package/src/commands/alignment.ts +1212 -0
- package/src/commands/analyze-optimized.ts +371 -33
- package/src/commands/analyze.ts +8 -6
- package/src/commands/batch.ts +166 -26
- package/src/commands/chat.ts +20 -10
- package/src/commands/claude-init.ts +31 -27
- package/src/commands/claude-setup.ts +761 -81
- package/src/commands/computer-setup.ts +524 -12
- package/src/commands/create-command.ts +3 -3
- package/src/commands/create.ts +9 -6
- package/src/commands/dashboard.ts +11 -6
- package/src/commands/govern.ts +11 -6
- package/src/commands/governance.ts +1005 -0
- package/src/commands/guardian.ts +887 -0
- package/src/commands/init.ts +104 -11
- package/src/commands/orchestrator.ts +789 -0
- package/src/commands/performance-optimizer.ts +15 -10
- package/src/commands/plugins.ts +8 -5
- package/src/commands/project-update.ts +1156 -0
- package/src/commands/rag.ts +1011 -0
- package/src/commands/session.ts +631 -0
- package/src/commands/setup.ts +42 -344
- package/src/commands/test-init.ts +3 -2
- package/src/commands/test.ts +3 -2
- package/src/commands/watch.ts +21 -11
- package/src/commands/worktree.ts +1057 -0
- package/src/context/context-manager.ts +5 -2
- package/src/context/session-manager.ts +18 -7
- package/src/framework/command-interface.ts +520 -0
- package/src/framework/command-registry.ts +942 -0
- package/src/framework/completion-exporter.ts +383 -0
- package/src/framework/debug-logger.ts +519 -0
- package/src/framework/error-handler.ts +867 -0
- package/src/framework/help-generator.ts +540 -0
- package/src/framework/index.ts +169 -0
- package/src/framework/interactive-repl.ts +703 -0
- package/src/framework/output-formatter.ts +834 -0
- package/src/framework/progress-manager.ts +539 -0
- package/src/index.ts +3 -2
- package/src/interactive/interactive-mode.ts +14 -7
- package/src/lib/conflict-resolution.ts +818 -0
- package/src/lib/merge-strategy.ts +550 -0
- package/src/lib/safety-mechanisms.ts +451 -0
- package/src/lib/state-detection.ts +1030 -0
- package/src/nlp/command-mapper.ts +8 -3
- package/src/nlp/command-parser.ts +5 -2
- package/src/nlp/intent-parser.ts +23 -9
- package/src/plugins/plugin-manager.ts +50 -24
- package/src/tests/computer-setup-integration.test.ts +46 -15
- package/src/types/index.ts +1 -1
- package/src/types/modules.d.ts +425 -1
- package/src/utils/backup-rollback-manager.ts +19 -14
- package/src/utils/claude-config-installer.ts +119 -28
- package/src/utils/config-manager.ts +9 -6
- package/src/utils/error-handler.ts +3 -1
- package/src/utils/logger.ts +35 -12
- package/templates/batch/ci-cd.yaml +7 -7
- package/test-suites/api/health.spec.ts +20 -23
- package/test-suites/helpers/test-config.ts +14 -13
- package/test-suites/ui/accessibility.spec.ts +27 -22
- package/test-suites/ui/smoke.spec.ts +26 -21
- package/src/commands/computer-setup-commands.ts +0 -869
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Management CLI Commands
|
|
3
|
+
* Manages coding agent sessions including listing, pausing, resuming, and killing sessions.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'fs/promises';
|
|
7
|
+
import * as os from 'os';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import { Command } from 'commander';
|
|
12
|
+
import ora from 'ora';
|
|
13
|
+
|
|
14
|
+
// Constants
|
|
15
|
+
const SESSIONS_BASE_DIR = path.join(os.homedir(), '.wundr', 'sessions');
|
|
16
|
+
const SESSIONS_STATE_FILE = path.join(SESSIONS_BASE_DIR, 'state.json');
|
|
17
|
+
|
|
18
|
+
// Types
|
|
19
|
+
export type SessionStatus =
|
|
20
|
+
| 'active'
|
|
21
|
+
| 'paused'
|
|
22
|
+
| 'completed'
|
|
23
|
+
| 'error'
|
|
24
|
+
| 'terminated';
|
|
25
|
+
|
|
26
|
+
export interface AgentSession {
|
|
27
|
+
sessionId: string;
|
|
28
|
+
status: SessionStatus;
|
|
29
|
+
startedAt: string;
|
|
30
|
+
pausedAt?: string;
|
|
31
|
+
slotId: string;
|
|
32
|
+
worktreePath: string;
|
|
33
|
+
memoryBankPath?: string;
|
|
34
|
+
subAgents?: SubAgentInfo[];
|
|
35
|
+
taskDescription?: string;
|
|
36
|
+
lastActivity?: string;
|
|
37
|
+
metrics?: SessionMetrics;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface SubAgentInfo {
|
|
41
|
+
agentId: string;
|
|
42
|
+
type: string;
|
|
43
|
+
status: 'active' | 'idle' | 'terminated';
|
|
44
|
+
taskCount: number;
|
|
45
|
+
lastActivity?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface SessionMetrics {
|
|
49
|
+
tasksCompleted: number;
|
|
50
|
+
tasksTotal: number;
|
|
51
|
+
tokensUsed: number;
|
|
52
|
+
duration: number;
|
|
53
|
+
errors: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface SessionsState {
|
|
57
|
+
version: string;
|
|
58
|
+
lastUpdated: string;
|
|
59
|
+
sessions: AgentSession[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Utility functions
|
|
63
|
+
function getTimestamp(): string {
|
|
64
|
+
return new Date().toISOString();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function formatDuration(ms: number): string {
|
|
68
|
+
const seconds = Math.floor(ms / 1000);
|
|
69
|
+
const minutes = Math.floor(seconds / 60);
|
|
70
|
+
const hours = Math.floor(minutes / 60);
|
|
71
|
+
|
|
72
|
+
if (hours > 0) {
|
|
73
|
+
return `${hours}h ${minutes % 60}m`;
|
|
74
|
+
}
|
|
75
|
+
if (minutes > 0) {
|
|
76
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
77
|
+
}
|
|
78
|
+
return `${seconds}s`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function padRight(str: string, length: number): string {
|
|
82
|
+
return str.length >= length
|
|
83
|
+
? str.substring(0, length)
|
|
84
|
+
: str + ' '.repeat(length - str.length);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function truncate(str: string, length: number): string {
|
|
88
|
+
if (str.length <= length) {
|
|
89
|
+
return str;
|
|
90
|
+
}
|
|
91
|
+
return str.substring(0, length - 3) + '...';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function ensureSessionsDir(): Promise<void> {
|
|
95
|
+
await fs.mkdir(SESSIONS_BASE_DIR, { recursive: true });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function loadSessionsState(): Promise<SessionsState> {
|
|
99
|
+
try {
|
|
100
|
+
await ensureSessionsDir();
|
|
101
|
+
const content = await fs.readFile(SESSIONS_STATE_FILE, 'utf-8');
|
|
102
|
+
return JSON.parse(content) as SessionsState;
|
|
103
|
+
} catch {
|
|
104
|
+
// Return empty state if file doesn't exist
|
|
105
|
+
return {
|
|
106
|
+
version: '1.0.0',
|
|
107
|
+
lastUpdated: getTimestamp(),
|
|
108
|
+
sessions: [],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function saveSessionsState(state: SessionsState): Promise<void> {
|
|
114
|
+
await ensureSessionsDir();
|
|
115
|
+
state.lastUpdated = getTimestamp();
|
|
116
|
+
await fs.writeFile(SESSIONS_STATE_FILE, JSON.stringify(state, null, 2));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getStatusColor(status: SessionStatus): (str: string) => string {
|
|
120
|
+
switch (status) {
|
|
121
|
+
case 'active':
|
|
122
|
+
return chalk.green;
|
|
123
|
+
case 'paused':
|
|
124
|
+
return chalk.yellow;
|
|
125
|
+
case 'completed':
|
|
126
|
+
return chalk.blue;
|
|
127
|
+
case 'error':
|
|
128
|
+
return chalk.red;
|
|
129
|
+
case 'terminated':
|
|
130
|
+
return chalk.gray;
|
|
131
|
+
default:
|
|
132
|
+
return chalk.white;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function getStatusIcon(status: SessionStatus): string {
|
|
137
|
+
switch (status) {
|
|
138
|
+
case 'active':
|
|
139
|
+
return '[ACTIVE]';
|
|
140
|
+
case 'paused':
|
|
141
|
+
return '[PAUSED]';
|
|
142
|
+
case 'completed':
|
|
143
|
+
return '[DONE]';
|
|
144
|
+
case 'error':
|
|
145
|
+
return '[ERROR]';
|
|
146
|
+
case 'terminated':
|
|
147
|
+
return '[KILLED]';
|
|
148
|
+
default:
|
|
149
|
+
return '[UNKNOWN]';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Create session command
|
|
154
|
+
export function createSessionCommand(): Command {
|
|
155
|
+
const command = new Command('session')
|
|
156
|
+
.description('Manage coding agent sessions')
|
|
157
|
+
.addHelpText(
|
|
158
|
+
'after',
|
|
159
|
+
chalk.gray(`
|
|
160
|
+
Examples:
|
|
161
|
+
${chalk.green('wundr session list')} List all active sessions
|
|
162
|
+
${chalk.green('wundr session list --all')} List all sessions including completed
|
|
163
|
+
${chalk.green('wundr session info <sessionId>')} Get detailed session information
|
|
164
|
+
${chalk.green('wundr session pause <sessionId>')} Pause a running session
|
|
165
|
+
${chalk.green('wundr session resume <sessionId>')}Resume a paused session
|
|
166
|
+
${chalk.green('wundr session kill <sessionId>')} Terminate a session
|
|
167
|
+
`)
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// List command (default)
|
|
171
|
+
command
|
|
172
|
+
.command('list', { isDefault: true })
|
|
173
|
+
.description('List sessions')
|
|
174
|
+
.option('-a, --all', 'Show all sessions including completed and terminated')
|
|
175
|
+
.option(
|
|
176
|
+
'-s, --status <status>',
|
|
177
|
+
'Filter by status (active, paused, completed, error, terminated)'
|
|
178
|
+
)
|
|
179
|
+
.option('-f, --format <format>', 'Output format (table, json)', 'table')
|
|
180
|
+
.action(async options => {
|
|
181
|
+
await listSessions(options);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Info command
|
|
185
|
+
command
|
|
186
|
+
.command('info <sessionId>')
|
|
187
|
+
.description('Get detailed information about a session')
|
|
188
|
+
.option('-f, --format <format>', 'Output format (table, json)', 'table')
|
|
189
|
+
.action(async (sessionId, options) => {
|
|
190
|
+
await showSessionInfo(sessionId, options);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Pause command
|
|
194
|
+
command
|
|
195
|
+
.command('pause <sessionId>')
|
|
196
|
+
.description('Pause a running session')
|
|
197
|
+
.action(async sessionId => {
|
|
198
|
+
await pauseSession(sessionId);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Resume command
|
|
202
|
+
command
|
|
203
|
+
.command('resume <sessionId>')
|
|
204
|
+
.description('Resume a paused session')
|
|
205
|
+
.action(async sessionId => {
|
|
206
|
+
await resumeSession(sessionId);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Kill command
|
|
210
|
+
command
|
|
211
|
+
.command('kill <sessionId>')
|
|
212
|
+
.description('Terminate a session')
|
|
213
|
+
.option('--force', 'Force termination without confirmation')
|
|
214
|
+
.action(async (sessionId, options) => {
|
|
215
|
+
await killSession(sessionId, options);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
return command;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Command implementations
|
|
222
|
+
async function listSessions(options: {
|
|
223
|
+
all?: boolean;
|
|
224
|
+
status?: SessionStatus;
|
|
225
|
+
format?: 'table' | 'json';
|
|
226
|
+
}): Promise<void> {
|
|
227
|
+
const spinner = ora('Loading sessions...').start();
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const state = await loadSessionsState();
|
|
231
|
+
let sessions = state.sessions;
|
|
232
|
+
|
|
233
|
+
// Filter by status if provided
|
|
234
|
+
if (options.status) {
|
|
235
|
+
sessions = sessions.filter(s => s.status === options.status);
|
|
236
|
+
} else if (!options.all) {
|
|
237
|
+
// By default, only show active and paused sessions
|
|
238
|
+
sessions = sessions.filter(
|
|
239
|
+
s => s.status === 'active' || s.status === 'paused'
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
spinner.stop();
|
|
244
|
+
|
|
245
|
+
if (options.format === 'json') {
|
|
246
|
+
console.log(
|
|
247
|
+
JSON.stringify(
|
|
248
|
+
{
|
|
249
|
+
timestamp: getTimestamp(),
|
|
250
|
+
count: sessions.length,
|
|
251
|
+
sessions: sessions,
|
|
252
|
+
},
|
|
253
|
+
null,
|
|
254
|
+
2
|
|
255
|
+
)
|
|
256
|
+
);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
console.log(chalk.cyan('\nSession List'));
|
|
261
|
+
console.log(chalk.gray('='.repeat(100)));
|
|
262
|
+
|
|
263
|
+
if (sessions.length === 0) {
|
|
264
|
+
console.log(chalk.yellow('\nNo sessions found.'));
|
|
265
|
+
if (!options.all && !options.status) {
|
|
266
|
+
console.log(
|
|
267
|
+
chalk.gray('Use --all to show completed and terminated sessions.')
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
console.log('');
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Table header
|
|
275
|
+
console.log(
|
|
276
|
+
chalk.cyan(
|
|
277
|
+
padRight('Session ID', 20) +
|
|
278
|
+
padRight('Status', 12) +
|
|
279
|
+
padRight('Started At', 22) +
|
|
280
|
+
padRight('Slot ID', 10) +
|
|
281
|
+
padRight('Worktree Path', 36)
|
|
282
|
+
)
|
|
283
|
+
);
|
|
284
|
+
console.log(chalk.gray('-'.repeat(100)));
|
|
285
|
+
|
|
286
|
+
// Table rows
|
|
287
|
+
for (const session of sessions) {
|
|
288
|
+
const statusColor = getStatusColor(session.status);
|
|
289
|
+
const startedAt = new Date(session.startedAt).toLocaleString();
|
|
290
|
+
const worktreePath = truncate(session.worktreePath, 34);
|
|
291
|
+
|
|
292
|
+
console.log(
|
|
293
|
+
padRight(session.sessionId, 20) +
|
|
294
|
+
statusColor(padRight(getStatusIcon(session.status), 12)) +
|
|
295
|
+
padRight(startedAt, 22) +
|
|
296
|
+
padRight(session.slotId, 10) +
|
|
297
|
+
chalk.gray(padRight(worktreePath, 36))
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
console.log(chalk.gray('-'.repeat(100)));
|
|
302
|
+
console.log(chalk.gray(`Total: ${sessions.length} session(s)`));
|
|
303
|
+
console.log('');
|
|
304
|
+
} catch (error) {
|
|
305
|
+
spinner.fail('Failed to load sessions');
|
|
306
|
+
console.error(
|
|
307
|
+
chalk.red(error instanceof Error ? error.message : String(error))
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function showSessionInfo(
|
|
313
|
+
sessionId: string,
|
|
314
|
+
options: { format?: 'table' | 'json' }
|
|
315
|
+
): Promise<void> {
|
|
316
|
+
const spinner = ora(`Loading session ${sessionId}...`).start();
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
const state = await loadSessionsState();
|
|
320
|
+
const session = state.sessions.find(s => s.sessionId === sessionId);
|
|
321
|
+
|
|
322
|
+
if (!session) {
|
|
323
|
+
spinner.fail(`Session not found: ${sessionId}`);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
spinner.stop();
|
|
328
|
+
|
|
329
|
+
if (options.format === 'json') {
|
|
330
|
+
console.log(JSON.stringify(session, null, 2));
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
console.log(chalk.cyan('\nSession Details'));
|
|
335
|
+
console.log(chalk.gray('='.repeat(60)));
|
|
336
|
+
|
|
337
|
+
// Basic info
|
|
338
|
+
const statusColor = getStatusColor(session.status);
|
|
339
|
+
console.log(
|
|
340
|
+
chalk.white('Session ID: ') + chalk.green(session.sessionId)
|
|
341
|
+
);
|
|
342
|
+
console.log(
|
|
343
|
+
chalk.white('Status: ') +
|
|
344
|
+
statusColor(getStatusIcon(session.status))
|
|
345
|
+
);
|
|
346
|
+
console.log(chalk.white('Slot ID: ') + session.slotId);
|
|
347
|
+
console.log(
|
|
348
|
+
chalk.white('Worktree Path: ') + chalk.gray(session.worktreePath)
|
|
349
|
+
);
|
|
350
|
+
console.log(
|
|
351
|
+
chalk.white('Started At: ') +
|
|
352
|
+
new Date(session.startedAt).toLocaleString()
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
if (session.pausedAt) {
|
|
356
|
+
console.log(
|
|
357
|
+
chalk.white('Paused At: ') +
|
|
358
|
+
new Date(session.pausedAt).toLocaleString()
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (session.lastActivity) {
|
|
363
|
+
console.log(
|
|
364
|
+
chalk.white('Last Activity: ') +
|
|
365
|
+
new Date(session.lastActivity).toLocaleString()
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (session.taskDescription) {
|
|
370
|
+
console.log(chalk.white('Task: ') + session.taskDescription);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Memory bank status
|
|
374
|
+
console.log(chalk.gray('\n' + '-'.repeat(60)));
|
|
375
|
+
console.log(chalk.cyan('Memory Bank Status'));
|
|
376
|
+
|
|
377
|
+
if (session.memoryBankPath) {
|
|
378
|
+
console.log(chalk.white('Path: ') + chalk.gray(session.memoryBankPath));
|
|
379
|
+
// Check if memory bank exists
|
|
380
|
+
try {
|
|
381
|
+
await fs.access(session.memoryBankPath);
|
|
382
|
+
console.log(chalk.white('Status: ') + chalk.green('[AVAILABLE]'));
|
|
383
|
+
} catch {
|
|
384
|
+
console.log(chalk.white('Status: ') + chalk.yellow('[NOT FOUND]'));
|
|
385
|
+
}
|
|
386
|
+
} else {
|
|
387
|
+
console.log(chalk.gray('No memory bank configured.'));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Sub-agents
|
|
391
|
+
console.log(chalk.gray('\n' + '-'.repeat(60)));
|
|
392
|
+
console.log(chalk.cyan('Sub-Agents'));
|
|
393
|
+
|
|
394
|
+
if (session.subAgents && session.subAgents.length > 0) {
|
|
395
|
+
console.log(
|
|
396
|
+
chalk.cyan(
|
|
397
|
+
padRight('Agent ID', 15) +
|
|
398
|
+
padRight('Type', 15) +
|
|
399
|
+
padRight('Status', 12) +
|
|
400
|
+
padRight('Tasks', 8)
|
|
401
|
+
)
|
|
402
|
+
);
|
|
403
|
+
console.log(chalk.gray('-'.repeat(50)));
|
|
404
|
+
|
|
405
|
+
for (const agent of session.subAgents) {
|
|
406
|
+
const agentStatusColor =
|
|
407
|
+
agent.status === 'active'
|
|
408
|
+
? chalk.green
|
|
409
|
+
: agent.status === 'idle'
|
|
410
|
+
? chalk.yellow
|
|
411
|
+
: chalk.gray;
|
|
412
|
+
console.log(
|
|
413
|
+
padRight(agent.agentId, 15) +
|
|
414
|
+
padRight(agent.type, 15) +
|
|
415
|
+
agentStatusColor(padRight(`[${agent.status.toUpperCase()}]`, 12)) +
|
|
416
|
+
padRight(String(agent.taskCount), 8)
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
} else {
|
|
420
|
+
console.log(chalk.gray('No sub-agents spawned.'));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Metrics
|
|
424
|
+
if (session.metrics) {
|
|
425
|
+
console.log(chalk.gray('\n' + '-'.repeat(60)));
|
|
426
|
+
console.log(chalk.cyan('Session Metrics'));
|
|
427
|
+
console.log(
|
|
428
|
+
chalk.white('Tasks: ') +
|
|
429
|
+
`${session.metrics.tasksCompleted}/${session.metrics.tasksTotal}`
|
|
430
|
+
);
|
|
431
|
+
console.log(
|
|
432
|
+
chalk.white('Duration: ') + formatDuration(session.metrics.duration)
|
|
433
|
+
);
|
|
434
|
+
console.log(
|
|
435
|
+
chalk.white('Tokens: ') + session.metrics.tokensUsed.toLocaleString()
|
|
436
|
+
);
|
|
437
|
+
console.log(
|
|
438
|
+
chalk.white('Errors: ') +
|
|
439
|
+
(session.metrics.errors > 0 ? chalk.red(session.metrics.errors) : '0')
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
console.log(chalk.gray('='.repeat(60)));
|
|
444
|
+
console.log('');
|
|
445
|
+
} catch (error) {
|
|
446
|
+
spinner.fail('Failed to load session info');
|
|
447
|
+
console.error(
|
|
448
|
+
chalk.red(error instanceof Error ? error.message : String(error))
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async function pauseSession(sessionId: string): Promise<void> {
|
|
454
|
+
const spinner = ora(`Pausing session ${sessionId}...`).start();
|
|
455
|
+
|
|
456
|
+
try {
|
|
457
|
+
const state = await loadSessionsState();
|
|
458
|
+
const sessionIndex = state.sessions.findIndex(
|
|
459
|
+
s => s.sessionId === sessionId
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
if (sessionIndex === -1) {
|
|
463
|
+
spinner.fail(`Session not found: ${sessionId}`);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const session = state.sessions[sessionIndex];
|
|
468
|
+
|
|
469
|
+
if (!session) {
|
|
470
|
+
spinner.fail(`Session not found: ${sessionId}`);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (session.status !== 'active') {
|
|
475
|
+
spinner.fail(`Cannot pause session with status: ${session.status}`);
|
|
476
|
+
console.log(chalk.yellow('Only active sessions can be paused.'));
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Update session status
|
|
481
|
+
session.status = 'paused';
|
|
482
|
+
session.pausedAt = getTimestamp();
|
|
483
|
+
session.lastActivity = getTimestamp();
|
|
484
|
+
|
|
485
|
+
await saveSessionsState(state);
|
|
486
|
+
|
|
487
|
+
spinner.succeed(`Session paused: ${sessionId}`);
|
|
488
|
+
console.log(
|
|
489
|
+
chalk.gray('Use "wundr session resume ' + sessionId + '" to resume.')
|
|
490
|
+
);
|
|
491
|
+
console.log('');
|
|
492
|
+
} catch (error) {
|
|
493
|
+
spinner.fail('Failed to pause session');
|
|
494
|
+
console.error(
|
|
495
|
+
chalk.red(error instanceof Error ? error.message : String(error))
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
async function resumeSession(sessionId: string): Promise<void> {
|
|
501
|
+
const spinner = ora(`Resuming session ${sessionId}...`).start();
|
|
502
|
+
|
|
503
|
+
try {
|
|
504
|
+
const state = await loadSessionsState();
|
|
505
|
+
const sessionIndex = state.sessions.findIndex(
|
|
506
|
+
s => s.sessionId === sessionId
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
if (sessionIndex === -1) {
|
|
510
|
+
spinner.fail(`Session not found: ${sessionId}`);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const session = state.sessions[sessionIndex];
|
|
515
|
+
|
|
516
|
+
if (!session) {
|
|
517
|
+
spinner.fail(`Session not found: ${sessionId}`);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (session.status !== 'paused') {
|
|
522
|
+
spinner.fail(`Cannot resume session with status: ${session.status}`);
|
|
523
|
+
console.log(chalk.yellow('Only paused sessions can be resumed.'));
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Update session status
|
|
528
|
+
session.status = 'active';
|
|
529
|
+
session.pausedAt = undefined;
|
|
530
|
+
session.lastActivity = getTimestamp();
|
|
531
|
+
|
|
532
|
+
await saveSessionsState(state);
|
|
533
|
+
|
|
534
|
+
spinner.succeed(`Session resumed: ${sessionId}`);
|
|
535
|
+
console.log(chalk.green('Session is now active.'));
|
|
536
|
+
console.log('');
|
|
537
|
+
} catch (error) {
|
|
538
|
+
spinner.fail('Failed to resume session');
|
|
539
|
+
console.error(
|
|
540
|
+
chalk.red(error instanceof Error ? error.message : String(error))
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
async function killSession(
|
|
546
|
+
sessionId: string,
|
|
547
|
+
options: { force?: boolean }
|
|
548
|
+
): Promise<void> {
|
|
549
|
+
const spinner = ora(`Terminating session ${sessionId}...`).start();
|
|
550
|
+
|
|
551
|
+
try {
|
|
552
|
+
const state = await loadSessionsState();
|
|
553
|
+
const sessionIndex = state.sessions.findIndex(
|
|
554
|
+
s => s.sessionId === sessionId
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
if (sessionIndex === -1) {
|
|
558
|
+
spinner.fail(`Session not found: ${sessionId}`);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const session = state.sessions[sessionIndex];
|
|
563
|
+
|
|
564
|
+
if (!session) {
|
|
565
|
+
spinner.fail(`Session not found: ${sessionId}`);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (session.status === 'terminated' || session.status === 'completed') {
|
|
570
|
+
spinner.fail(`Session already ${session.status}: ${sessionId}`);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Confirm if not forced
|
|
575
|
+
if (!options.force) {
|
|
576
|
+
spinner.stop();
|
|
577
|
+
const inquirer = await import('inquirer');
|
|
578
|
+
const answers = await inquirer.default.prompt([
|
|
579
|
+
{
|
|
580
|
+
type: 'confirm',
|
|
581
|
+
name: 'confirm',
|
|
582
|
+
message: `Are you sure you want to terminate session "${sessionId}"? This cannot be undone.`,
|
|
583
|
+
default: false,
|
|
584
|
+
},
|
|
585
|
+
]);
|
|
586
|
+
|
|
587
|
+
if (!answers.confirm) {
|
|
588
|
+
console.log(chalk.yellow('Cancelled.'));
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
spinner.start();
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Update session status
|
|
595
|
+
session.status = 'terminated';
|
|
596
|
+
session.lastActivity = getTimestamp();
|
|
597
|
+
|
|
598
|
+
// Terminate sub-agents
|
|
599
|
+
if (session.subAgents) {
|
|
600
|
+
for (const agent of session.subAgents) {
|
|
601
|
+
agent.status = 'terminated';
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
await saveSessionsState(state);
|
|
606
|
+
|
|
607
|
+
spinner.succeed(`Session terminated: ${sessionId}`);
|
|
608
|
+
|
|
609
|
+
// Show cleanup info
|
|
610
|
+
console.log(chalk.gray('\nCleanup completed:'));
|
|
611
|
+
if (session.subAgents && session.subAgents.length > 0) {
|
|
612
|
+
console.log(
|
|
613
|
+
chalk.gray(` - Terminated ${session.subAgents.length} sub-agent(s)`)
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
if (session.memoryBankPath) {
|
|
617
|
+
console.log(
|
|
618
|
+
chalk.gray(` - Memory bank preserved at: ${session.memoryBankPath}`)
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
console.log('');
|
|
622
|
+
} catch (error) {
|
|
623
|
+
spinner.fail('Failed to terminate session');
|
|
624
|
+
console.error(
|
|
625
|
+
chalk.red(error instanceof Error ? error.message : String(error))
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Export for registration
|
|
631
|
+
export default createSessionCommand;
|