@anastops/mcp-server 1.1.2 → 2.0.1
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/dist/handlers/handlers.base.d.ts +20 -4
- package/dist/handlers/handlers.base.d.ts.map +1 -1
- package/dist/handlers/handlers.base.js +32 -17
- package/dist/handlers/handlers.base.js.map +1 -1
- package/dist/handlers/index.d.ts +5 -4
- package/dist/handlers/index.d.ts.map +1 -1
- package/dist/handlers/index.js +126 -135
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/tool-definitions.d.ts +1016 -1564
- package/dist/handlers/tool-definitions.d.ts.map +1 -1
- package/dist/handlers/tool-definitions.js +617 -388
- package/dist/handlers/tool-definitions.js.map +1 -1
- package/dist/handlers/types.d.ts +34 -0
- package/dist/handlers/types.d.ts.map +1 -1
- package/dist/handlers/v3/agent-tools.d.ts +280 -0
- package/dist/handlers/v3/agent-tools.d.ts.map +1 -0
- package/dist/handlers/v3/agent-tools.js +460 -0
- package/dist/handlers/v3/agent-tools.js.map +1 -0
- package/dist/handlers/v3/index.d.ts +31 -0
- package/dist/handlers/v3/index.d.ts.map +1 -0
- package/dist/handlers/v3/index.js +56 -0
- package/dist/handlers/v3/index.js.map +1 -0
- package/dist/handlers/v3/routing-integration-example.d.ts +258 -0
- package/dist/handlers/v3/routing-integration-example.d.ts.map +1 -0
- package/dist/handlers/v3/routing-integration-example.js +454 -0
- package/dist/handlers/v3/routing-integration-example.js.map +1 -0
- package/dist/handlers/v3/routing-middleware.d.ts +191 -0
- package/dist/handlers/v3/routing-middleware.d.ts.map +1 -0
- package/dist/handlers/v3/routing-middleware.js +425 -0
- package/dist/handlers/v3/routing-middleware.js.map +1 -0
- package/dist/handlers/v3/session-tools.d.ts +303 -0
- package/dist/handlers/v3/session-tools.d.ts.map +1 -0
- package/dist/handlers/v3/session-tools.js +945 -0
- package/dist/handlers/v3/session-tools.js.map +1 -0
- package/dist/handlers/v3/task-tools.d.ts +18 -0
- package/dist/handlers/v3/task-tools.d.ts.map +1 -0
- package/dist/handlers/v3/task-tools.js +1947 -0
- package/dist/handlers/v3/task-tools.js.map +1 -0
- package/dist/handlers/v3/utility-tools.d.ts +99 -0
- package/dist/handlers/v3/utility-tools.d.ts.map +1 -0
- package/dist/handlers/v3/utility-tools.js +878 -0
- package/dist/handlers/v3/utility-tools.js.map +1 -0
- package/dist/handlers.d.ts +1 -1
- package/dist/handlers.d.ts.map +1 -1
- package/dist/handlers.js +1 -1
- package/dist/handlers.js.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/persistence.d.ts +11 -0
- package/dist/persistence.d.ts.map +1 -1
- package/dist/persistence.js +77 -0
- package/dist/persistence.js.map +1 -1
- package/dist/schemas.d.ts +95 -8
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +97 -2
- package/dist/schemas.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,945 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP v3.0 Session Management Tools
|
|
3
|
+
* Consolidated session management following aggressive consolidation plan
|
|
4
|
+
*
|
|
5
|
+
* Tools:
|
|
6
|
+
* - session_manage: Create, fork, archive, delete operations
|
|
7
|
+
* - session_query: Status, list, report query operations
|
|
8
|
+
* - session_queue_config: Configure and query queue settings
|
|
9
|
+
*
|
|
10
|
+
* @see docs/MCP-V3-AGGRESSIVE-CONSOLIDATION.md
|
|
11
|
+
*/
|
|
12
|
+
import { SessionManager, SessionForkService, addBreadcrumb, } from '@anastops/core';
|
|
13
|
+
import { getPersistence } from '../../persistence.js';
|
|
14
|
+
import { safePersist, formatSessionReport, tasks, agents, artifacts } from '../handlers.base.js';
|
|
15
|
+
// Shared instances
|
|
16
|
+
const sessionManager = new SessionManager();
|
|
17
|
+
const forkService = new SessionForkService(sessionManager);
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// TOOL SCHEMAS
|
|
20
|
+
// ============================================================================
|
|
21
|
+
/**
|
|
22
|
+
* session_manage tool schema
|
|
23
|
+
*
|
|
24
|
+
* Handles create, fork, archive, and delete operations on sessions.
|
|
25
|
+
*
|
|
26
|
+
* @example Create a new session
|
|
27
|
+
* ```typescript
|
|
28
|
+
* session_manage({
|
|
29
|
+
* operation: 'create',
|
|
30
|
+
* objective: 'Build authentication system',
|
|
31
|
+
* concurrency: 3,
|
|
32
|
+
* auto_execute: true
|
|
33
|
+
* })
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @example Fork a session
|
|
37
|
+
* ```typescript
|
|
38
|
+
* session_manage({
|
|
39
|
+
* operation: 'fork',
|
|
40
|
+
* session_id: 'abc123',
|
|
41
|
+
* reason: 'Explore OAuth approach'
|
|
42
|
+
* })
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @example Archive a session
|
|
46
|
+
* ```typescript
|
|
47
|
+
* session_manage({
|
|
48
|
+
* operation: 'archive',
|
|
49
|
+
* session_id: 'abc123'
|
|
50
|
+
* })
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @example Delete a session
|
|
54
|
+
* ```typescript
|
|
55
|
+
* session_manage({
|
|
56
|
+
* operation: 'delete',
|
|
57
|
+
* session_id: 'abc123',
|
|
58
|
+
* confirm: true
|
|
59
|
+
* })
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export const sessionManageSchema = {
|
|
63
|
+
name: 'session_manage',
|
|
64
|
+
description: 'Manage sessions: create new sessions, fork existing sessions, archive completed sessions, or delete sessions permanently',
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
operation: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
enum: ['create', 'fork', 'archive', 'delete'],
|
|
71
|
+
description: 'Operation to perform: create=spawn new session, fork=create alternative branch, archive=mark completed, delete=permanently remove',
|
|
72
|
+
},
|
|
73
|
+
// CREATE operation parameters
|
|
74
|
+
objective: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
description: 'Required for create: High-level objective for the session',
|
|
77
|
+
},
|
|
78
|
+
context_files: {
|
|
79
|
+
type: 'array',
|
|
80
|
+
items: { type: 'string' },
|
|
81
|
+
description: 'For create: Array of file paths to include in session context',
|
|
82
|
+
},
|
|
83
|
+
parent_session: {
|
|
84
|
+
type: 'string',
|
|
85
|
+
description: 'For create: Parent session ID if this is a child session',
|
|
86
|
+
},
|
|
87
|
+
concurrency: {
|
|
88
|
+
type: 'number',
|
|
89
|
+
description: 'For create: Max concurrent running tasks (1-10, default: 1)',
|
|
90
|
+
},
|
|
91
|
+
auto_execute: {
|
|
92
|
+
type: 'boolean',
|
|
93
|
+
description: 'For create: Auto-execute queued tasks when slots available (default: false)',
|
|
94
|
+
},
|
|
95
|
+
// FORK operation parameters
|
|
96
|
+
session_id: {
|
|
97
|
+
type: 'string',
|
|
98
|
+
description: 'Required for fork/archive/delete: Session ID to operate on',
|
|
99
|
+
},
|
|
100
|
+
fork_point: {
|
|
101
|
+
type: 'number',
|
|
102
|
+
description: 'For fork: Task number to fork from (optional)',
|
|
103
|
+
},
|
|
104
|
+
reason: {
|
|
105
|
+
type: 'string',
|
|
106
|
+
description: 'For fork: Reason for forking (e.g., "Explore OAuth approach")',
|
|
107
|
+
},
|
|
108
|
+
// DELETE operation parameters
|
|
109
|
+
session_ids: {
|
|
110
|
+
type: 'array',
|
|
111
|
+
items: { type: 'string' },
|
|
112
|
+
description: 'For delete: Array of session IDs to delete (bulk deletion)',
|
|
113
|
+
},
|
|
114
|
+
status: {
|
|
115
|
+
type: 'string',
|
|
116
|
+
enum: ['active', 'completed', 'archived'],
|
|
117
|
+
description: 'For delete: Delete all sessions with this status',
|
|
118
|
+
},
|
|
119
|
+
older_than_days: {
|
|
120
|
+
type: 'number',
|
|
121
|
+
description: 'For delete: Delete sessions older than N days',
|
|
122
|
+
},
|
|
123
|
+
confirm: {
|
|
124
|
+
type: 'boolean',
|
|
125
|
+
description: 'Required for delete: Must be true to execute deletion (safety check)',
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
required: ['operation'],
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* session_query tool schema
|
|
133
|
+
*
|
|
134
|
+
* Queries session information: get status, list sessions, or generate reports.
|
|
135
|
+
*
|
|
136
|
+
* @example Get session status
|
|
137
|
+
* ```typescript
|
|
138
|
+
* session_query({
|
|
139
|
+
* operation: 'status',
|
|
140
|
+
* session_id: 'abc123'
|
|
141
|
+
* })
|
|
142
|
+
* ```
|
|
143
|
+
*
|
|
144
|
+
* @example List active sessions
|
|
145
|
+
* ```typescript
|
|
146
|
+
* session_query({
|
|
147
|
+
* operation: 'list',
|
|
148
|
+
* status_filter: 'active',
|
|
149
|
+
* format: 'table'
|
|
150
|
+
* })
|
|
151
|
+
* ```
|
|
152
|
+
*
|
|
153
|
+
* @example Generate session report
|
|
154
|
+
* ```typescript
|
|
155
|
+
* session_query({
|
|
156
|
+
* operation: 'report',
|
|
157
|
+
* session_id: 'abc123',
|
|
158
|
+
* include_output: true,
|
|
159
|
+
* include_artifacts: false
|
|
160
|
+
* })
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export const sessionQuerySchema = {
|
|
164
|
+
name: 'session_query',
|
|
165
|
+
description: 'Query session information: get status for a specific session, list all sessions with filters, or generate comprehensive reports',
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: 'object',
|
|
168
|
+
properties: {
|
|
169
|
+
operation: {
|
|
170
|
+
type: 'string',
|
|
171
|
+
enum: ['status', 'list', 'report'],
|
|
172
|
+
description: 'Type of query: status=get session details, list=list sessions with filters, report=generate comprehensive report',
|
|
173
|
+
},
|
|
174
|
+
// STATUS operation parameters
|
|
175
|
+
session_id: {
|
|
176
|
+
type: 'string',
|
|
177
|
+
description: 'Required for status: Session ID to query. Optional for report: omit to get all sessions',
|
|
178
|
+
},
|
|
179
|
+
// LIST operation parameters
|
|
180
|
+
status_filter: {
|
|
181
|
+
type: 'string',
|
|
182
|
+
enum: ['active', 'completed', 'archived'],
|
|
183
|
+
description: 'For list/report: Filter sessions by status',
|
|
184
|
+
},
|
|
185
|
+
include_forks: {
|
|
186
|
+
type: 'boolean',
|
|
187
|
+
description: 'For list: Include forked sessions in results (default: false)',
|
|
188
|
+
},
|
|
189
|
+
limit: {
|
|
190
|
+
type: 'number',
|
|
191
|
+
description: 'For list/report: Maximum number of sessions to return (default: 20 for report)',
|
|
192
|
+
},
|
|
193
|
+
format: {
|
|
194
|
+
type: 'string',
|
|
195
|
+
enum: ['table', 'json'],
|
|
196
|
+
description: 'For list: Output format (table=ASCII table, json=raw JSON, default: json)',
|
|
197
|
+
},
|
|
198
|
+
// REPORT operation parameters
|
|
199
|
+
include_output: {
|
|
200
|
+
type: 'boolean',
|
|
201
|
+
description: 'For report: Include full task output content (default: true)',
|
|
202
|
+
},
|
|
203
|
+
include_artifacts: {
|
|
204
|
+
type: 'boolean',
|
|
205
|
+
description: 'For report: Include artifact content, not just metadata (default: false)',
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
required: ['operation'],
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* session_queue_config tool schema
|
|
213
|
+
*
|
|
214
|
+
* Configure queue settings or query queue status for a session.
|
|
215
|
+
*
|
|
216
|
+
* @example Configure queue settings
|
|
217
|
+
* ```typescript
|
|
218
|
+
* session_queue_config({
|
|
219
|
+
* operation: 'configure',
|
|
220
|
+
* session_id: 'abc123',
|
|
221
|
+
* concurrency: 5,
|
|
222
|
+
* auto_execute: true
|
|
223
|
+
* })
|
|
224
|
+
* ```
|
|
225
|
+
*
|
|
226
|
+
* @example Query queue status
|
|
227
|
+
* ```typescript
|
|
228
|
+
* session_queue_config({
|
|
229
|
+
* operation: 'status',
|
|
230
|
+
* session_id: 'abc123'
|
|
231
|
+
* })
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
export const sessionQueueConfigSchema = {
|
|
235
|
+
name: 'session_queue_config',
|
|
236
|
+
description: 'Configure session queue settings (concurrency, auto-execution) or query queue status (running/queued counts, available slots)',
|
|
237
|
+
inputSchema: {
|
|
238
|
+
type: 'object',
|
|
239
|
+
properties: {
|
|
240
|
+
operation: {
|
|
241
|
+
type: 'string',
|
|
242
|
+
enum: ['configure', 'status'],
|
|
243
|
+
description: 'Operation: configure=update queue settings, status=get queue status and ready tasks',
|
|
244
|
+
},
|
|
245
|
+
session_id: {
|
|
246
|
+
type: 'string',
|
|
247
|
+
description: 'Required: Session ID to configure or query',
|
|
248
|
+
},
|
|
249
|
+
// CONFIGURE operation parameters
|
|
250
|
+
concurrency: {
|
|
251
|
+
type: 'number',
|
|
252
|
+
description: 'For configure: Max concurrent running tasks (1-10)',
|
|
253
|
+
},
|
|
254
|
+
auto_execute: {
|
|
255
|
+
type: 'boolean',
|
|
256
|
+
description: 'For configure: Auto-execute queued tasks when slots available',
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
required: ['operation', 'session_id'],
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
/**
|
|
263
|
+
* session_resume tool schema
|
|
264
|
+
*
|
|
265
|
+
* Discover active sessions with incomplete tasks and display them as a checklist.
|
|
266
|
+
*
|
|
267
|
+
* @example Resume specific session
|
|
268
|
+
* ```typescript
|
|
269
|
+
* session_resume({
|
|
270
|
+
* session_id: 'abc123'
|
|
271
|
+
* })
|
|
272
|
+
* ```
|
|
273
|
+
*
|
|
274
|
+
* @example Discover all resumable sessions
|
|
275
|
+
* ```typescript
|
|
276
|
+
* session_resume({})
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
export const sessionResumeSchema = {
|
|
280
|
+
name: 'session_resume',
|
|
281
|
+
description: 'Discover active sessions with incomplete tasks and display them in a Claude-style checklist format. Returns sessions with pending, queued, running, or failed tasks.',
|
|
282
|
+
inputSchema: {
|
|
283
|
+
type: 'object',
|
|
284
|
+
properties: {
|
|
285
|
+
session_id: {
|
|
286
|
+
type: 'string',
|
|
287
|
+
description: 'Optional: Specific session ID to resume. If omitted, discovers all active sessions with incomplete tasks.',
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
// ============================================================================
|
|
293
|
+
// HANDLER IMPLEMENTATIONS
|
|
294
|
+
// ============================================================================
|
|
295
|
+
/**
|
|
296
|
+
* Validates operation-specific required fields
|
|
297
|
+
* Throws error if required fields are missing for the operation
|
|
298
|
+
*/
|
|
299
|
+
function validateSessionManageArgs(operation, args) {
|
|
300
|
+
switch (operation) {
|
|
301
|
+
case 'create':
|
|
302
|
+
if (typeof args['objective'] !== 'string' || args['objective'] === '') {
|
|
303
|
+
throw new Error('operation=create requires: objective (string)');
|
|
304
|
+
}
|
|
305
|
+
break;
|
|
306
|
+
case 'fork':
|
|
307
|
+
if (typeof args['session_id'] !== 'string' || args['session_id'] === '') {
|
|
308
|
+
throw new Error('operation=fork requires: session_id (string)');
|
|
309
|
+
}
|
|
310
|
+
break;
|
|
311
|
+
case 'archive':
|
|
312
|
+
if (typeof args['session_id'] !== 'string' || args['session_id'] === '') {
|
|
313
|
+
throw new Error('operation=archive requires: session_id (string)');
|
|
314
|
+
}
|
|
315
|
+
break;
|
|
316
|
+
case 'delete': {
|
|
317
|
+
if (args['confirm'] !== true) {
|
|
318
|
+
throw new Error('operation=delete requires: confirm=true (safety check)');
|
|
319
|
+
}
|
|
320
|
+
// Must provide at least one filter: session_id, session_ids, status, or older_than_days
|
|
321
|
+
const hasFilter = (typeof args['session_id'] === 'string' && args['session_id'] !== '') ||
|
|
322
|
+
(Array.isArray(args['session_ids']) && args['session_ids'].length > 0) ||
|
|
323
|
+
(typeof args['status'] === 'string' && args['status'] !== '') ||
|
|
324
|
+
typeof args['older_than_days'] === 'number';
|
|
325
|
+
if (!hasFilter) {
|
|
326
|
+
throw new Error('operation=delete requires at least one filter: session_id, session_ids, status, or older_than_days');
|
|
327
|
+
}
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
default:
|
|
331
|
+
throw new Error(`Unknown operation: ${operation}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function validateSessionQueryArgs(operation, args) {
|
|
335
|
+
switch (operation) {
|
|
336
|
+
case 'status':
|
|
337
|
+
if (typeof args['session_id'] !== 'string' || args['session_id'] === '') {
|
|
338
|
+
throw new Error('operation=status requires: session_id (string)');
|
|
339
|
+
}
|
|
340
|
+
break;
|
|
341
|
+
case 'list':
|
|
342
|
+
// No required fields for list - all optional
|
|
343
|
+
break;
|
|
344
|
+
case 'report':
|
|
345
|
+
// session_id is optional for report (omit to get all sessions)
|
|
346
|
+
break;
|
|
347
|
+
default:
|
|
348
|
+
throw new Error(`Unknown operation: ${operation}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function validateQueueConfigArgs(operation, args) {
|
|
352
|
+
// session_id is always required (enforced in schema)
|
|
353
|
+
if (typeof args['session_id'] !== 'string' || args['session_id'] === '') {
|
|
354
|
+
throw new Error('session_id (string) is required');
|
|
355
|
+
}
|
|
356
|
+
switch (operation) {
|
|
357
|
+
case 'configure':
|
|
358
|
+
// Validate concurrency if provided
|
|
359
|
+
if (args['concurrency'] !== undefined) {
|
|
360
|
+
const concurrency = args['concurrency'];
|
|
361
|
+
if (concurrency < 1 || concurrency > 10) {
|
|
362
|
+
throw new Error('concurrency must be between 1 and 10');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
break;
|
|
366
|
+
case 'status':
|
|
367
|
+
// No additional validation needed
|
|
368
|
+
break;
|
|
369
|
+
default:
|
|
370
|
+
throw new Error(`Unknown operation: ${operation}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* session_manage handler
|
|
375
|
+
* Routes to appropriate handler based on operation parameter
|
|
376
|
+
*/
|
|
377
|
+
export async function handleSessionManage(args) {
|
|
378
|
+
const operation = args['operation'];
|
|
379
|
+
// Validate operation-specific required fields
|
|
380
|
+
validateSessionManageArgs(operation, args);
|
|
381
|
+
switch (operation) {
|
|
382
|
+
case 'create':
|
|
383
|
+
return handleSessionCreate(args);
|
|
384
|
+
case 'fork':
|
|
385
|
+
return handleSessionFork(args);
|
|
386
|
+
case 'archive':
|
|
387
|
+
return handleSessionArchive(args);
|
|
388
|
+
case 'delete':
|
|
389
|
+
return handleSessionDelete(args);
|
|
390
|
+
default:
|
|
391
|
+
throw new Error(`Unknown operation: ${operation}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* session_query handler
|
|
396
|
+
* Routes to appropriate handler based on operation parameter
|
|
397
|
+
*/
|
|
398
|
+
export async function handleSessionQuery(args) {
|
|
399
|
+
const operation = args['operation'];
|
|
400
|
+
// Validate operation-specific required fields
|
|
401
|
+
validateSessionQueryArgs(operation, args);
|
|
402
|
+
switch (operation) {
|
|
403
|
+
case 'status':
|
|
404
|
+
return handleSessionStatus(args);
|
|
405
|
+
case 'list':
|
|
406
|
+
return handleSessionList(args);
|
|
407
|
+
case 'report':
|
|
408
|
+
return handleSessionReport(args);
|
|
409
|
+
default:
|
|
410
|
+
throw new Error(`Unknown operation: ${operation}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* session_queue_config handler
|
|
415
|
+
* Routes to configure or status operation
|
|
416
|
+
*/
|
|
417
|
+
export async function handleSessionQueueConfig(args) {
|
|
418
|
+
const operation = args['operation'];
|
|
419
|
+
// Validate operation-specific required fields
|
|
420
|
+
validateQueueConfigArgs(operation, args);
|
|
421
|
+
switch (operation) {
|
|
422
|
+
case 'configure':
|
|
423
|
+
return handleQueueConfigure(args);
|
|
424
|
+
case 'status':
|
|
425
|
+
return handleQueueStatus(args);
|
|
426
|
+
default:
|
|
427
|
+
throw new Error(`Unknown operation: ${operation}`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// ============================================================================
|
|
431
|
+
// OPERATION IMPLEMENTATIONS
|
|
432
|
+
// ============================================================================
|
|
433
|
+
/**
|
|
434
|
+
* Create a new session
|
|
435
|
+
*/
|
|
436
|
+
function handleSessionCreate(args) {
|
|
437
|
+
const objective = args['objective'];
|
|
438
|
+
addBreadcrumb('Creating session', { objective }, 'info', 'session');
|
|
439
|
+
const session = sessionManager.spawn({
|
|
440
|
+
objective,
|
|
441
|
+
...(args['context_files'] !== undefined && {
|
|
442
|
+
context_files: args['context_files'],
|
|
443
|
+
}),
|
|
444
|
+
...(args['parent_session'] !== undefined && {
|
|
445
|
+
parent_session: args['parent_session'],
|
|
446
|
+
}),
|
|
447
|
+
...(args['concurrency'] !== undefined && {
|
|
448
|
+
concurrency: args['concurrency'],
|
|
449
|
+
}),
|
|
450
|
+
...(args['auto_execute'] !== undefined && {
|
|
451
|
+
auto_execute: args['auto_execute'],
|
|
452
|
+
}),
|
|
453
|
+
});
|
|
454
|
+
addBreadcrumb('Session created', { session_id: session.id }, 'info', 'session');
|
|
455
|
+
// Persist to MongoDB
|
|
456
|
+
safePersist(getPersistence().saveSession(session));
|
|
457
|
+
return Promise.resolve({
|
|
458
|
+
session_id: session.id,
|
|
459
|
+
objective: session.objective,
|
|
460
|
+
status: session.status,
|
|
461
|
+
queue_config: session.queue_config,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Fork an existing session
|
|
466
|
+
*/
|
|
467
|
+
function handleSessionFork(args) {
|
|
468
|
+
const sessionId = args['session_id'];
|
|
469
|
+
const reason = args['reason'] ?? 'unknown';
|
|
470
|
+
addBreadcrumb('Forking session', { session_id: sessionId, reason }, 'info', 'session');
|
|
471
|
+
const forked = forkService.fork({
|
|
472
|
+
session_id: sessionId,
|
|
473
|
+
...(args['fork_point'] !== undefined && {
|
|
474
|
+
fork_point: args['fork_point'],
|
|
475
|
+
}),
|
|
476
|
+
...(args['reason'] !== undefined && { reason }),
|
|
477
|
+
});
|
|
478
|
+
addBreadcrumb('Session forked', { forked_session_id: forked.id, parent_id: forked.parent_session_id }, 'info', 'session');
|
|
479
|
+
// Persist to MongoDB
|
|
480
|
+
safePersist(getPersistence().saveSession(forked));
|
|
481
|
+
return Promise.resolve({
|
|
482
|
+
forked_session_id: forked.id,
|
|
483
|
+
parent_id: forked.parent_session_id,
|
|
484
|
+
fork_point: forked.fork_point,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Archive a session
|
|
489
|
+
*/
|
|
490
|
+
function handleSessionArchive(args) {
|
|
491
|
+
const sessionId = args['session_id'];
|
|
492
|
+
const session = sessionManager.updateStatus(sessionId, 'archived');
|
|
493
|
+
// Persist to MongoDB
|
|
494
|
+
safePersist(getPersistence().saveSession(session));
|
|
495
|
+
return Promise.resolve({
|
|
496
|
+
session_id: session.id,
|
|
497
|
+
status: session.status,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Delete session(s) permanently
|
|
502
|
+
*/
|
|
503
|
+
async function handleSessionDelete(args) {
|
|
504
|
+
const sessionId = args['session_id'];
|
|
505
|
+
const sessionIds = args['session_ids'];
|
|
506
|
+
const statusFilter = args['status'];
|
|
507
|
+
const olderThanDays = args['older_than_days'];
|
|
508
|
+
const persistence = getPersistence();
|
|
509
|
+
// Single session deletion
|
|
510
|
+
if (sessionId !== undefined && sessionId !== '') {
|
|
511
|
+
// Remove from in-memory cache
|
|
512
|
+
sessionManager.remove(sessionId);
|
|
513
|
+
// Remove related tasks from cache
|
|
514
|
+
for (const [taskId, task] of tasks.entries()) {
|
|
515
|
+
if (task.session_id === sessionId) {
|
|
516
|
+
tasks.delete(taskId);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
// Remove related agents from cache
|
|
520
|
+
for (const [agentId, agent] of agents.entries()) {
|
|
521
|
+
if (agent.session_id === sessionId) {
|
|
522
|
+
agents.delete(agentId);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
// Remove related artifacts from cache
|
|
526
|
+
for (const [artifactId, artifact] of artifacts.entries()) {
|
|
527
|
+
if (artifact.session_id === sessionId) {
|
|
528
|
+
artifacts.delete(artifactId);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const deleted = await persistence.deleteSession(sessionId);
|
|
532
|
+
return { deleted: deleted, session_id: sessionId };
|
|
533
|
+
}
|
|
534
|
+
// Build filters for bulk deletion
|
|
535
|
+
const filters = {};
|
|
536
|
+
if (sessionIds !== undefined && sessionIds.length > 0) {
|
|
537
|
+
filters.session_ids = sessionIds;
|
|
538
|
+
}
|
|
539
|
+
if (statusFilter !== undefined && statusFilter !== '') {
|
|
540
|
+
filters.status = statusFilter;
|
|
541
|
+
}
|
|
542
|
+
if (olderThanDays !== undefined) {
|
|
543
|
+
const cutoffDate = new Date();
|
|
544
|
+
cutoffDate.setDate(cutoffDate.getDate() - olderThanDays);
|
|
545
|
+
filters.older_than = cutoffDate;
|
|
546
|
+
}
|
|
547
|
+
const result = await persistence.deleteSessions(filters);
|
|
548
|
+
// Clean up in-memory caches for deleted sessions
|
|
549
|
+
for (const deletedId of result.session_ids) {
|
|
550
|
+
sessionManager.remove(deletedId);
|
|
551
|
+
for (const [taskId, task] of tasks.entries()) {
|
|
552
|
+
if (task.session_id === deletedId) {
|
|
553
|
+
tasks.delete(taskId);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
for (const [agentId, agent] of agents.entries()) {
|
|
557
|
+
if (agent.session_id === deletedId) {
|
|
558
|
+
agents.delete(agentId);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
for (const [artifactId, artifact] of artifacts.entries()) {
|
|
562
|
+
if (artifact.session_id === deletedId) {
|
|
563
|
+
artifacts.delete(artifactId);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return {
|
|
568
|
+
deleted_count: result.deleted_count,
|
|
569
|
+
session_ids: result.session_ids,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Get session status
|
|
574
|
+
*/
|
|
575
|
+
function handleSessionStatus(args) {
|
|
576
|
+
const sessionId = args['session_id'];
|
|
577
|
+
const status = sessionManager.getStatus(sessionId);
|
|
578
|
+
// TOON encoding applied in handleToolCall
|
|
579
|
+
return Promise.resolve(status);
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* List sessions with filters
|
|
583
|
+
*/
|
|
584
|
+
async function handleSessionList(args) {
|
|
585
|
+
// Get from in-memory first
|
|
586
|
+
const inMemorySessions = sessionManager.list({
|
|
587
|
+
...(args['status_filter'] !== undefined && {
|
|
588
|
+
status: args['status_filter'],
|
|
589
|
+
}),
|
|
590
|
+
...(args['include_forks'] !== undefined && {
|
|
591
|
+
include_forks: args['include_forks'],
|
|
592
|
+
}),
|
|
593
|
+
...(args['limit'] !== undefined && {
|
|
594
|
+
limit: args['limit'],
|
|
595
|
+
}),
|
|
596
|
+
});
|
|
597
|
+
// Also get from MongoDB for persistence
|
|
598
|
+
const persistedSessions = await getPersistence().listSessions({
|
|
599
|
+
...(args['status_filter'] !== undefined && {
|
|
600
|
+
status: args['status_filter'],
|
|
601
|
+
}),
|
|
602
|
+
...(args['limit'] !== undefined && {
|
|
603
|
+
limit: args['limit'],
|
|
604
|
+
}),
|
|
605
|
+
});
|
|
606
|
+
// Merge: in-memory takes precedence, then add persisted ones not in memory
|
|
607
|
+
const inMemoryIds = new Set(inMemorySessions.map((s) => s.id));
|
|
608
|
+
const sessions = [
|
|
609
|
+
...inMemorySessions,
|
|
610
|
+
...persistedSessions.filter((s) => !inMemoryIds.has(s.id)),
|
|
611
|
+
];
|
|
612
|
+
// Return raw result for table formatting to be applied in handleToolCall
|
|
613
|
+
const result = {
|
|
614
|
+
count: sessions.length,
|
|
615
|
+
sessions: sessions.map((s) => ({
|
|
616
|
+
id: s.id,
|
|
617
|
+
objective: s.objective,
|
|
618
|
+
status: s.status,
|
|
619
|
+
created_at: s.created_at,
|
|
620
|
+
})),
|
|
621
|
+
};
|
|
622
|
+
// Don't apply TOON encoding here - let handleToolCall handle formatting
|
|
623
|
+
return result;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Generate session report
|
|
627
|
+
*/
|
|
628
|
+
async function handleSessionReport(args) {
|
|
629
|
+
const sessionId = args['session_id'];
|
|
630
|
+
const statusFilter = args['status_filter'];
|
|
631
|
+
const includeOutput = args['include_output'] ?? true;
|
|
632
|
+
const includeArtifactContent = args['include_artifacts'] ?? false;
|
|
633
|
+
const limit = args['limit'] ?? 20;
|
|
634
|
+
const persistence = getPersistence();
|
|
635
|
+
if (sessionId !== undefined && sessionId !== '') {
|
|
636
|
+
// Single session report
|
|
637
|
+
const report = await persistence.getSessionReport(sessionId);
|
|
638
|
+
if (report === null) {
|
|
639
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
640
|
+
}
|
|
641
|
+
return formatSessionReport(report, includeOutput, includeArtifactContent);
|
|
642
|
+
}
|
|
643
|
+
// All sessions report
|
|
644
|
+
const reportFilters = { limit };
|
|
645
|
+
if (statusFilter !== undefined) {
|
|
646
|
+
reportFilters.status = statusFilter;
|
|
647
|
+
}
|
|
648
|
+
const reports = await persistence.getAllSessionReports(reportFilters);
|
|
649
|
+
return {
|
|
650
|
+
total_sessions: reports.length,
|
|
651
|
+
sessions: reports.map((r) => formatSessionReport(r, includeOutput, includeArtifactContent)),
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Configure queue settings
|
|
656
|
+
*/
|
|
657
|
+
function handleQueueConfigure(args) {
|
|
658
|
+
const sessionId = args['session_id'];
|
|
659
|
+
const concurrency = args['concurrency'];
|
|
660
|
+
const autoExecute = args['auto_execute'];
|
|
661
|
+
// Build updates object
|
|
662
|
+
const updates = {};
|
|
663
|
+
if (concurrency !== undefined) {
|
|
664
|
+
updates.concurrency = concurrency;
|
|
665
|
+
}
|
|
666
|
+
if (autoExecute !== undefined) {
|
|
667
|
+
updates.auto_execute = autoExecute;
|
|
668
|
+
}
|
|
669
|
+
if (Object.keys(updates).length === 0) {
|
|
670
|
+
// No updates provided, just return current config
|
|
671
|
+
const session = sessionManager.getSession(sessionId);
|
|
672
|
+
return Promise.resolve({
|
|
673
|
+
session_id: sessionId,
|
|
674
|
+
queue_config: session.queue_config,
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
// Update queue config
|
|
678
|
+
const session = sessionManager.updateQueueConfig(sessionId, updates);
|
|
679
|
+
safePersist(getPersistence().saveSession(session));
|
|
680
|
+
return Promise.resolve({
|
|
681
|
+
session_id: sessionId,
|
|
682
|
+
queue_config: session.queue_config,
|
|
683
|
+
updated: true,
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Get queue status
|
|
688
|
+
*/
|
|
689
|
+
async function handleQueueStatus(args) {
|
|
690
|
+
const sessionId = args['session_id'];
|
|
691
|
+
const session = sessionManager.getSession(sessionId);
|
|
692
|
+
// Get tasks for the session
|
|
693
|
+
const sessionTasks = [];
|
|
694
|
+
for (const [, task] of tasks.entries()) {
|
|
695
|
+
if (task.session_id === sessionId) {
|
|
696
|
+
sessionTasks.push(task);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
// Also get from MongoDB
|
|
700
|
+
const persistedTasks = await getPersistence().listTasks({ session_id: sessionId });
|
|
701
|
+
const inMemoryIds = new Set(sessionTasks.map((t) => t.id));
|
|
702
|
+
const allTasks = [...sessionTasks, ...persistedTasks.filter((t) => !inMemoryIds.has(t.id))];
|
|
703
|
+
// Count by status
|
|
704
|
+
const runningCount = allTasks.filter((t) => t.status === 'running').length;
|
|
705
|
+
const queuedCount = allTasks.filter((t) => t.status === 'queued').length;
|
|
706
|
+
const pendingCount = allTasks.filter((t) => t.status === 'pending').length;
|
|
707
|
+
const completedCount = allTasks.filter((t) => t.status === 'completed').length;
|
|
708
|
+
const failedCount = allTasks.filter((t) => t.status === 'failed').length;
|
|
709
|
+
const availableSlots = Math.max(0, session.queue_config.concurrency - runningCount);
|
|
710
|
+
// Find ready tasks (queued with met dependencies)
|
|
711
|
+
const queuedTasks = allTasks.filter((t) => t.status === 'queued');
|
|
712
|
+
const taskMap = new Map(allTasks.map((t) => [t.id, t]));
|
|
713
|
+
const readyTasks = queuedTasks
|
|
714
|
+
.filter((task) => {
|
|
715
|
+
if (task.dependencies.length === 0)
|
|
716
|
+
return true;
|
|
717
|
+
return task.dependencies.every((depId) => {
|
|
718
|
+
const depTask = taskMap.get(depId);
|
|
719
|
+
return depTask !== undefined && depTask.status === 'completed';
|
|
720
|
+
});
|
|
721
|
+
})
|
|
722
|
+
.sort((a, b) => {
|
|
723
|
+
// Sort by priority (higher first), then by created_at (earlier first)
|
|
724
|
+
if (a.priority !== b.priority)
|
|
725
|
+
return b.priority - a.priority;
|
|
726
|
+
return a.created_at.getTime() - b.created_at.getTime();
|
|
727
|
+
})
|
|
728
|
+
.slice(0, availableSlots)
|
|
729
|
+
.map((t) => t.id);
|
|
730
|
+
return {
|
|
731
|
+
session_id: sessionId,
|
|
732
|
+
queue_config: session.queue_config,
|
|
733
|
+
running_count: runningCount,
|
|
734
|
+
queued_count: queuedCount,
|
|
735
|
+
pending_count: pendingCount,
|
|
736
|
+
completed_count: completedCount,
|
|
737
|
+
failed_count: failedCount,
|
|
738
|
+
available_slots: availableSlots,
|
|
739
|
+
ready_tasks: readyTasks,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Resume a session - discover active sessions with incomplete tasks
|
|
744
|
+
*/
|
|
745
|
+
async function handleSessionResume(args) {
|
|
746
|
+
const sessionId = args['session_id'];
|
|
747
|
+
const persistence = getPersistence();
|
|
748
|
+
let sessionsToCheck = [];
|
|
749
|
+
if (sessionId !== undefined && sessionId !== '') {
|
|
750
|
+
// Resume specific session
|
|
751
|
+
const session = sessionManager.getSession(sessionId);
|
|
752
|
+
sessionsToCheck = [session];
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
// Discover all active sessions
|
|
756
|
+
const inMemorySessions = sessionManager.list({ status: 'active' });
|
|
757
|
+
const persistedSessions = await persistence.listSessions({ status: 'active' });
|
|
758
|
+
// Merge sessions
|
|
759
|
+
const inMemoryIds = new Set(inMemorySessions.map((s) => s.id));
|
|
760
|
+
sessionsToCheck = [
|
|
761
|
+
...inMemorySessions,
|
|
762
|
+
...persistedSessions.filter((s) => !inMemoryIds.has(s.id)),
|
|
763
|
+
];
|
|
764
|
+
}
|
|
765
|
+
// Filter sessions with incomplete tasks
|
|
766
|
+
const resumableSessions = [];
|
|
767
|
+
for (const session of sessionsToCheck) {
|
|
768
|
+
// Get tasks for the session from both memory and persistence
|
|
769
|
+
const sessionTasks = [];
|
|
770
|
+
for (const [, task] of tasks.entries()) {
|
|
771
|
+
if (task.session_id === session.id) {
|
|
772
|
+
sessionTasks.push(task);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
const persistedTasks = await persistence.listTasks({ session_id: session.id });
|
|
776
|
+
const inMemoryIds = new Set(sessionTasks.map((t) => t.id));
|
|
777
|
+
const allTasks = [...sessionTasks, ...persistedTasks.filter((t) => !inMemoryIds.has(t.id))];
|
|
778
|
+
// Count by status
|
|
779
|
+
const completed = allTasks.filter((t) => t.status === 'completed').length;
|
|
780
|
+
const pending = allTasks.filter((t) => t.status === 'pending').length;
|
|
781
|
+
const running = allTasks.filter((t) => t.status === 'running').length;
|
|
782
|
+
const failed = allTasks.filter((t) => t.status === 'failed').length;
|
|
783
|
+
const queued = allTasks.filter((t) => t.status === 'queued').length;
|
|
784
|
+
// Only include sessions with incomplete tasks
|
|
785
|
+
const incompleteTasks = pending + running + failed + queued;
|
|
786
|
+
if (incompleteTasks === 0) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
// Build task map for dependency checking
|
|
790
|
+
const taskMap = new Map(allTasks.map((t) => [t.id, t]));
|
|
791
|
+
// Build task info array
|
|
792
|
+
const taskInfos = allTasks
|
|
793
|
+
.filter((t) => t.status !== 'completed' && t.status !== 'cancelled')
|
|
794
|
+
.map((task) => {
|
|
795
|
+
// Check if task is blocked by dependencies
|
|
796
|
+
const blocked = task.dependencies.length > 0 &&
|
|
797
|
+
task.dependencies.some((depId) => {
|
|
798
|
+
const depTask = taskMap.get(depId);
|
|
799
|
+
return depTask === undefined || depTask.status !== 'completed';
|
|
800
|
+
});
|
|
801
|
+
return {
|
|
802
|
+
id: task.id,
|
|
803
|
+
description: task.description,
|
|
804
|
+
status: task.status,
|
|
805
|
+
blocked,
|
|
806
|
+
dependencies: task.dependencies,
|
|
807
|
+
};
|
|
808
|
+
});
|
|
809
|
+
resumableSessions.push({
|
|
810
|
+
session_id: session.id,
|
|
811
|
+
objective: session.objective,
|
|
812
|
+
total_tasks: allTasks.length,
|
|
813
|
+
completed,
|
|
814
|
+
pending,
|
|
815
|
+
running,
|
|
816
|
+
failed,
|
|
817
|
+
queued,
|
|
818
|
+
tasks: taskInfos,
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
// Format output
|
|
822
|
+
if (resumableSessions.length === 0) {
|
|
823
|
+
return {
|
|
824
|
+
message: sessionId !== null && sessionId !== undefined
|
|
825
|
+
? `Session ${sessionId} has no incomplete tasks to resume`
|
|
826
|
+
: 'No active sessions with incomplete tasks found',
|
|
827
|
+
sessions: [],
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
if (resumableSessions.length === 1) {
|
|
831
|
+
// Single session - show full checklist
|
|
832
|
+
const firstSession = resumableSessions[0];
|
|
833
|
+
if (firstSession === undefined) {
|
|
834
|
+
throw new Error('Unexpected error: session not found in array');
|
|
835
|
+
}
|
|
836
|
+
return {
|
|
837
|
+
message: formatSingleSessionResume(firstSession),
|
|
838
|
+
sessions: resumableSessions,
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
// Multiple sessions - show summary cards
|
|
842
|
+
return {
|
|
843
|
+
message: formatMultiSessionResume(resumableSessions),
|
|
844
|
+
sessions: resumableSessions,
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Format a single session as a full checklist (Claude-style ASCII box)
|
|
849
|
+
*/
|
|
850
|
+
function formatSingleSessionResume(session) {
|
|
851
|
+
const lines = [];
|
|
852
|
+
// Header
|
|
853
|
+
lines.push('┌─────────────────────────────────────────────────────────────────────┐');
|
|
854
|
+
lines.push(`│ Resume Session: ${session.session_id.padEnd(47)} │`);
|
|
855
|
+
lines.push('├─────────────────────────────────────────────────────────────────────┤');
|
|
856
|
+
lines.push(`│ ${session.objective.slice(0, 67).padEnd(67)} │`);
|
|
857
|
+
lines.push('├─────────────────────────────────────────────────────────────────────┤');
|
|
858
|
+
// Progress summary
|
|
859
|
+
const totalPercent = Math.round((session.completed / session.total_tasks) * 100).toString();
|
|
860
|
+
const progressBar = createProgressBar(session.completed, session.total_tasks, 50);
|
|
861
|
+
lines.push(`│ Progress: ${progressBar} ${totalPercent.padStart(3)}% │`);
|
|
862
|
+
lines.push(`│ Tasks: ${session.total_tasks} total, ${session.completed} completed, ${session.pending + session.running + session.failed + session.queued} remaining${' '.repeat(Math.max(0, 67 - `Tasks: ${session.total_tasks} total, ${session.completed} completed, ${session.pending + session.running + session.failed + session.queued} remaining`.length))} │`);
|
|
863
|
+
lines.push('├─────────────────────────────────────────────────────────────────────┤');
|
|
864
|
+
// Task list
|
|
865
|
+
for (const task of session.tasks) {
|
|
866
|
+
const icon = getTaskIcon(task.status, task.blocked);
|
|
867
|
+
const statusLabel = task.blocked ? 'blocked' : task.status;
|
|
868
|
+
const taskLine = `${icon} [${statusLabel}] ${task.description.slice(0, 50)}`;
|
|
869
|
+
lines.push(`│ ${taskLine.padEnd(67)} │`);
|
|
870
|
+
// Show dependencies for blocked tasks
|
|
871
|
+
if (task.blocked && task.dependencies.length > 0) {
|
|
872
|
+
const depLine = ` ↳ Waiting on: ${task.dependencies.join(', ').slice(0, 45)}`;
|
|
873
|
+
lines.push(`│ ${depLine.padEnd(67)} │`);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
lines.push('└─────────────────────────────────────────────────────────────────────┘');
|
|
877
|
+
return lines.join('\n');
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Format multiple sessions as summary cards
|
|
881
|
+
*/
|
|
882
|
+
function formatMultiSessionResume(sessions) {
|
|
883
|
+
const lines = [];
|
|
884
|
+
lines.push('┌─────────────────────────────────────────────────────────────────────┐');
|
|
885
|
+
lines.push(`│ Found ${sessions.length} active sessions with incomplete tasks${' '.repeat(Math.max(0, 67 - `Found ${sessions.length} active sessions with incomplete tasks`.length))} │`);
|
|
886
|
+
lines.push('└─────────────────────────────────────────────────────────────────────┘');
|
|
887
|
+
lines.push('');
|
|
888
|
+
for (const session of sessions) {
|
|
889
|
+
lines.push('┌─────────────────────────────────────────────────────────────────────┐');
|
|
890
|
+
lines.push(`│ Session: ${session.session_id.padEnd(58)} │`);
|
|
891
|
+
lines.push('├─────────────────────────────────────────────────────────────────────┤');
|
|
892
|
+
lines.push(`│ ${session.objective.slice(0, 67).padEnd(67)} │`);
|
|
893
|
+
lines.push('├─────────────────────────────────────────────────────────────────────┤');
|
|
894
|
+
const totalPercent = Math.round((session.completed / session.total_tasks) * 100).toString();
|
|
895
|
+
const progressBar = createProgressBar(session.completed, session.total_tasks, 50);
|
|
896
|
+
lines.push(`│ Progress: ${progressBar} ${totalPercent.padStart(3)}% │`);
|
|
897
|
+
lines.push(`│ ${session.completed}/${session.total_tasks} completed | ${session.running} running | ${session.pending} pending | ${session.failed} failed${' '.repeat(Math.max(0, 67 - `${session.completed}/${session.total_tasks} completed | ${session.running} running | ${session.pending} pending | ${session.failed} failed`.length))} │`);
|
|
898
|
+
lines.push('├─────────────────────────────────────────────────────────────────────┤');
|
|
899
|
+
// Show first 3-4 incomplete tasks
|
|
900
|
+
const tasksToShow = session.tasks.slice(0, 4);
|
|
901
|
+
for (const task of tasksToShow) {
|
|
902
|
+
const icon = getTaskIcon(task.status, task.blocked);
|
|
903
|
+
const statusLabel = task.blocked ? 'blocked' : task.status;
|
|
904
|
+
const taskLine = `${icon} [${statusLabel}] ${task.description.slice(0, 50)}`;
|
|
905
|
+
lines.push(`│ ${taskLine.padEnd(67)} │`);
|
|
906
|
+
}
|
|
907
|
+
if (session.tasks.length > 4) {
|
|
908
|
+
const remaining = session.tasks.length - 4;
|
|
909
|
+
lines.push(`│ ... and ${remaining} more task${remaining > 1 ? 's' : ''}${' '.repeat(Math.max(0, 67 - `... and ${remaining} more task${remaining > 1 ? 's' : ''}`.length))} │`);
|
|
910
|
+
}
|
|
911
|
+
lines.push('└─────────────────────────────────────────────────────────────────────┘');
|
|
912
|
+
lines.push('');
|
|
913
|
+
}
|
|
914
|
+
return lines.join('\n');
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Get icon for task status
|
|
918
|
+
*/
|
|
919
|
+
function getTaskIcon(status, blocked) {
|
|
920
|
+
if (blocked)
|
|
921
|
+
return '⏸️';
|
|
922
|
+
switch (status) {
|
|
923
|
+
case 'completed':
|
|
924
|
+
return '✅';
|
|
925
|
+
case 'running':
|
|
926
|
+
return '🔧';
|
|
927
|
+
case 'failed':
|
|
928
|
+
return '❗';
|
|
929
|
+
default:
|
|
930
|
+
return '❌';
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Create a progress bar string
|
|
935
|
+
*/
|
|
936
|
+
function createProgressBar(completed, total, width) {
|
|
937
|
+
const filledWidth = Math.round((completed / total) * width);
|
|
938
|
+
const emptyWidth = width - filledWidth;
|
|
939
|
+
return '█'.repeat(filledWidth) + '░'.repeat(emptyWidth);
|
|
940
|
+
}
|
|
941
|
+
// ============================================================================
|
|
942
|
+
// EXPORTS
|
|
943
|
+
// ============================================================================
|
|
944
|
+
export { sessionManager, forkService, handleSessionResume };
|
|
945
|
+
//# sourceMappingURL=session-tools.js.map
|