@sparkleideas/integration 3.5.2-patch.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/README.md +270 -0
- package/package.json +55 -0
- package/src/__tests__/agent-adapter.test.ts +271 -0
- package/src/__tests__/agentic-flow-agent.test.ts +176 -0
- package/src/__tests__/token-optimizer.test.ts +176 -0
- package/src/agent-adapter.ts +651 -0
- package/src/agentic-flow-agent.ts +802 -0
- package/src/agentic-flow-bridge.ts +803 -0
- package/src/attention-coordinator.ts +679 -0
- package/src/feature-flags.ts +485 -0
- package/src/index.ts +466 -0
- package/src/long-running-worker.ts +871 -0
- package/src/multi-model-router.ts +1079 -0
- package/src/provider-adapter.ts +1168 -0
- package/src/sdk-bridge.ts +435 -0
- package/src/sona-adapter.ts +824 -0
- package/src/specialized-worker.ts +864 -0
- package/src/swarm-adapter.ts +1112 -0
- package/src/token-optimizer.ts +306 -0
- package/src/types.ts +494 -0
- package/src/worker-base.ts +822 -0
- package/src/worker-pool.ts +933 -0
- package/tmp.json +0 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LongRunningWorker - Checkpoint-Based Long-Running Task Support
|
|
3
|
+
*
|
|
4
|
+
* Extends WorkerBase with checkpoint persistence and resumption
|
|
5
|
+
* capabilities for tasks that may span extended periods.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Automatic checkpoint creation during execution
|
|
9
|
+
* - Resume from checkpoint on failure or restart
|
|
10
|
+
* - Progress tracking and reporting
|
|
11
|
+
* - Timeout management with graceful handling
|
|
12
|
+
* - Resource cleanup on completion or failure
|
|
13
|
+
*
|
|
14
|
+
* Compatible with @sparkleideas/agentic-flow's long-running agent patterns.
|
|
15
|
+
*
|
|
16
|
+
* @module v3/integration/long-running-worker
|
|
17
|
+
* @version 3.0.0-alpha.1
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
WorkerBase,
|
|
22
|
+
WorkerConfig,
|
|
23
|
+
AgentOutput,
|
|
24
|
+
WorkerArtifact,
|
|
25
|
+
} from './worker-base.js';
|
|
26
|
+
import type { Task } from './agentic-flow-agent.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Checkpoint data structure
|
|
30
|
+
*/
|
|
31
|
+
export interface Checkpoint {
|
|
32
|
+
/** Unique checkpoint identifier */
|
|
33
|
+
id: string;
|
|
34
|
+
/** Associated task identifier */
|
|
35
|
+
taskId: string;
|
|
36
|
+
/** Worker identifier */
|
|
37
|
+
workerId: string;
|
|
38
|
+
/** Checkpoint sequence number */
|
|
39
|
+
sequence: number;
|
|
40
|
+
/** Checkpoint creation timestamp */
|
|
41
|
+
timestamp: number;
|
|
42
|
+
/** Checkpoint state data */
|
|
43
|
+
state: CheckpointState;
|
|
44
|
+
/** Execution progress (0.0-1.0) */
|
|
45
|
+
progress: number;
|
|
46
|
+
/** Checkpoint metadata */
|
|
47
|
+
metadata?: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Checkpoint state containing all data needed to resume
|
|
52
|
+
*/
|
|
53
|
+
export interface CheckpointState {
|
|
54
|
+
/** Current execution phase */
|
|
55
|
+
phase: string;
|
|
56
|
+
/** Current step within phase */
|
|
57
|
+
step: number;
|
|
58
|
+
/** Total steps in current phase */
|
|
59
|
+
totalSteps: number;
|
|
60
|
+
/** Partial results accumulated so far */
|
|
61
|
+
partialResults: unknown[];
|
|
62
|
+
/** Context data for resumption */
|
|
63
|
+
context: Record<string, unknown>;
|
|
64
|
+
/** Artifacts generated so far */
|
|
65
|
+
artifacts: WorkerArtifact[];
|
|
66
|
+
/** Custom state data */
|
|
67
|
+
custom?: Record<string, unknown>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Long-running worker configuration
|
|
72
|
+
*/
|
|
73
|
+
export interface LongRunningWorkerConfig extends WorkerConfig {
|
|
74
|
+
/** Checkpoint interval in milliseconds */
|
|
75
|
+
checkpointInterval?: number;
|
|
76
|
+
/** Maximum checkpoints to retain */
|
|
77
|
+
maxCheckpoints?: number;
|
|
78
|
+
/** Enable automatic checkpoint cleanup */
|
|
79
|
+
autoCleanup?: boolean;
|
|
80
|
+
/** Checkpoint storage adapter */
|
|
81
|
+
storage?: CheckpointStorage;
|
|
82
|
+
/** Progress reporting interval in milliseconds */
|
|
83
|
+
progressInterval?: number;
|
|
84
|
+
/** Task timeout in milliseconds (0 = no timeout) */
|
|
85
|
+
taskTimeout?: number;
|
|
86
|
+
/** Enable automatic retry on failure */
|
|
87
|
+
autoRetry?: boolean;
|
|
88
|
+
/** Maximum retry attempts */
|
|
89
|
+
maxRetries?: number;
|
|
90
|
+
/** Retry backoff multiplier */
|
|
91
|
+
retryBackoff?: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Checkpoint storage interface
|
|
96
|
+
*/
|
|
97
|
+
export interface CheckpointStorage {
|
|
98
|
+
/** Save a checkpoint */
|
|
99
|
+
save(checkpoint: Checkpoint): Promise<void>;
|
|
100
|
+
/** Load a checkpoint by ID */
|
|
101
|
+
load(checkpointId: string): Promise<Checkpoint | null>;
|
|
102
|
+
/** Load the latest checkpoint for a task */
|
|
103
|
+
loadLatest(taskId: string, workerId: string): Promise<Checkpoint | null>;
|
|
104
|
+
/** List all checkpoints for a task */
|
|
105
|
+
list(taskId: string, workerId: string): Promise<Checkpoint[]>;
|
|
106
|
+
/** Delete a checkpoint */
|
|
107
|
+
delete(checkpointId: string): Promise<void>;
|
|
108
|
+
/** Delete all checkpoints for a task */
|
|
109
|
+
deleteAll(taskId: string, workerId: string): Promise<void>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Execution phase for long-running tasks
|
|
114
|
+
*/
|
|
115
|
+
export interface ExecutionPhase {
|
|
116
|
+
/** Phase name */
|
|
117
|
+
name: string;
|
|
118
|
+
/** Phase description */
|
|
119
|
+
description?: string;
|
|
120
|
+
/** Estimated steps in this phase */
|
|
121
|
+
estimatedSteps: number;
|
|
122
|
+
/** Phase weight for progress calculation */
|
|
123
|
+
weight?: number;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Progress update event data
|
|
128
|
+
*/
|
|
129
|
+
export interface ProgressUpdate {
|
|
130
|
+
/** Task identifier */
|
|
131
|
+
taskId: string;
|
|
132
|
+
/** Worker identifier */
|
|
133
|
+
workerId: string;
|
|
134
|
+
/** Current phase */
|
|
135
|
+
phase: string;
|
|
136
|
+
/** Current step */
|
|
137
|
+
step: number;
|
|
138
|
+
/** Total steps in phase */
|
|
139
|
+
totalSteps: number;
|
|
140
|
+
/** Overall progress (0.0-1.0) */
|
|
141
|
+
progress: number;
|
|
142
|
+
/** Estimated time remaining in milliseconds */
|
|
143
|
+
estimatedTimeRemaining?: number;
|
|
144
|
+
/** Timestamp */
|
|
145
|
+
timestamp: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Default in-memory checkpoint storage
|
|
150
|
+
*/
|
|
151
|
+
class InMemoryCheckpointStorage implements CheckpointStorage {
|
|
152
|
+
private checkpoints: Map<string, Checkpoint> = new Map();
|
|
153
|
+
|
|
154
|
+
async save(checkpoint: Checkpoint): Promise<void> {
|
|
155
|
+
this.checkpoints.set(checkpoint.id, checkpoint);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async load(checkpointId: string): Promise<Checkpoint | null> {
|
|
159
|
+
return this.checkpoints.get(checkpointId) || null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async loadLatest(taskId: string, workerId: string): Promise<Checkpoint | null> {
|
|
163
|
+
const taskCheckpoints = Array.from(this.checkpoints.values())
|
|
164
|
+
.filter((cp) => cp.taskId === taskId && cp.workerId === workerId)
|
|
165
|
+
.sort((a, b) => b.sequence - a.sequence);
|
|
166
|
+
|
|
167
|
+
return taskCheckpoints[0] || null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async list(taskId: string, workerId: string): Promise<Checkpoint[]> {
|
|
171
|
+
return Array.from(this.checkpoints.values())
|
|
172
|
+
.filter((cp) => cp.taskId === taskId && cp.workerId === workerId)
|
|
173
|
+
.sort((a, b) => a.sequence - b.sequence);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async delete(checkpointId: string): Promise<void> {
|
|
177
|
+
this.checkpoints.delete(checkpointId);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async deleteAll(taskId: string, workerId: string): Promise<void> {
|
|
181
|
+
const entries = Array.from(this.checkpoints.entries());
|
|
182
|
+
for (const [id, cp] of entries) {
|
|
183
|
+
if (cp.taskId === taskId && cp.workerId === workerId) {
|
|
184
|
+
this.checkpoints.delete(id);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* LongRunningWorker - Handles extended task execution with checkpoints
|
|
192
|
+
*
|
|
193
|
+
* Usage:
|
|
194
|
+
* ```typescript
|
|
195
|
+
* const worker = new LongRunningWorker({
|
|
196
|
+
* id: 'long-runner-1',
|
|
197
|
+
* type: 'long-running',
|
|
198
|
+
* capabilities: ['data-processing', 'batch-analysis'],
|
|
199
|
+
* checkpointInterval: 30000, // 30 seconds
|
|
200
|
+
* maxCheckpoints: 10,
|
|
201
|
+
* });
|
|
202
|
+
*
|
|
203
|
+
* await worker.initialize();
|
|
204
|
+
*
|
|
205
|
+
* // Execute task (checkpoints automatically)
|
|
206
|
+
* const result = await worker.execute(task);
|
|
207
|
+
*
|
|
208
|
+
* // Or resume from checkpoint
|
|
209
|
+
* const result = await worker.resumeFromCheckpoint(checkpointId);
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
export class LongRunningWorker extends WorkerBase {
|
|
213
|
+
/** Active checkpoints for current task */
|
|
214
|
+
checkpoints: Checkpoint[] = [];
|
|
215
|
+
|
|
216
|
+
/** Checkpoint storage adapter */
|
|
217
|
+
protected storage: CheckpointStorage;
|
|
218
|
+
|
|
219
|
+
/** Checkpoint interval in milliseconds */
|
|
220
|
+
protected checkpointInterval: number;
|
|
221
|
+
|
|
222
|
+
/** Maximum checkpoints to retain */
|
|
223
|
+
protected maxCheckpoints: number;
|
|
224
|
+
|
|
225
|
+
/** Auto cleanup enabled */
|
|
226
|
+
protected autoCleanup: boolean;
|
|
227
|
+
|
|
228
|
+
/** Progress reporting interval */
|
|
229
|
+
protected progressInterval: number;
|
|
230
|
+
|
|
231
|
+
/** Task timeout */
|
|
232
|
+
protected taskTimeout: number;
|
|
233
|
+
|
|
234
|
+
/** Auto retry on failure */
|
|
235
|
+
protected autoRetry: boolean;
|
|
236
|
+
|
|
237
|
+
/** Maximum retry attempts */
|
|
238
|
+
protected maxRetries: number;
|
|
239
|
+
|
|
240
|
+
/** Retry backoff multiplier */
|
|
241
|
+
protected retryBackoff: number;
|
|
242
|
+
|
|
243
|
+
/** Current task being executed */
|
|
244
|
+
private currentLongTask: Task | null = null;
|
|
245
|
+
|
|
246
|
+
/** Current execution state */
|
|
247
|
+
private currentState: CheckpointState | null = null;
|
|
248
|
+
|
|
249
|
+
/** Checkpoint timer */
|
|
250
|
+
private checkpointTimer: NodeJS.Timeout | null = null;
|
|
251
|
+
|
|
252
|
+
/** Progress timer */
|
|
253
|
+
private progressTimer: NodeJS.Timeout | null = null;
|
|
254
|
+
|
|
255
|
+
/** Execution start time */
|
|
256
|
+
private executionStartTime: number = 0;
|
|
257
|
+
|
|
258
|
+
/** Checkpoint sequence counter */
|
|
259
|
+
private checkpointSequence: number = 0;
|
|
260
|
+
|
|
261
|
+
/** Abort controller for task cancellation */
|
|
262
|
+
private abortController: AbortController | null = null;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Create a new LongRunningWorker instance
|
|
266
|
+
*
|
|
267
|
+
* @param config - Long-running worker configuration
|
|
268
|
+
*/
|
|
269
|
+
constructor(config: LongRunningWorkerConfig) {
|
|
270
|
+
const baseConfig: WorkerConfig = {
|
|
271
|
+
...config,
|
|
272
|
+
type: config.type || 'long-running',
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
super(baseConfig);
|
|
276
|
+
|
|
277
|
+
this.checkpointInterval = config.checkpointInterval ?? 60000; // 1 minute default
|
|
278
|
+
this.maxCheckpoints = config.maxCheckpoints ?? 10;
|
|
279
|
+
this.autoCleanup = config.autoCleanup ?? true;
|
|
280
|
+
this.progressInterval = config.progressInterval ?? 5000; // 5 seconds default
|
|
281
|
+
this.taskTimeout = config.taskTimeout ?? 0; // No timeout by default
|
|
282
|
+
this.autoRetry = config.autoRetry ?? true;
|
|
283
|
+
this.maxRetries = config.maxRetries ?? 3;
|
|
284
|
+
this.retryBackoff = config.retryBackoff ?? 2;
|
|
285
|
+
this.storage = config.storage ?? new InMemoryCheckpointStorage();
|
|
286
|
+
|
|
287
|
+
this.emit('long-running-worker-created', {
|
|
288
|
+
workerId: this.id,
|
|
289
|
+
checkpointInterval: this.checkpointInterval,
|
|
290
|
+
maxCheckpoints: this.maxCheckpoints,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Execute a long-running task with checkpoint support
|
|
296
|
+
*
|
|
297
|
+
* @param task - Task to execute
|
|
298
|
+
* @returns Agent output with results
|
|
299
|
+
*/
|
|
300
|
+
async execute(task: Task): Promise<AgentOutput> {
|
|
301
|
+
this.currentLongTask = task;
|
|
302
|
+
this.executionStartTime = Date.now();
|
|
303
|
+
this.checkpointSequence = 0;
|
|
304
|
+
this.abortController = new AbortController();
|
|
305
|
+
|
|
306
|
+
// Initialize execution state
|
|
307
|
+
this.currentState = {
|
|
308
|
+
phase: 'initialization',
|
|
309
|
+
step: 0,
|
|
310
|
+
totalSteps: 1,
|
|
311
|
+
partialResults: [],
|
|
312
|
+
context: {},
|
|
313
|
+
artifacts: [],
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
this.emit('long-task-started', {
|
|
317
|
+
workerId: this.id,
|
|
318
|
+
taskId: task.id,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
// Start checkpoint timer
|
|
323
|
+
this.startCheckpointTimer();
|
|
324
|
+
|
|
325
|
+
// Start progress timer
|
|
326
|
+
this.startProgressTimer();
|
|
327
|
+
|
|
328
|
+
// Execute with retry support
|
|
329
|
+
let result: AgentOutput;
|
|
330
|
+
let attempt = 0;
|
|
331
|
+
let lastError: Error | undefined;
|
|
332
|
+
|
|
333
|
+
while (attempt < this.maxRetries) {
|
|
334
|
+
try {
|
|
335
|
+
result = await this.executeWithTimeout(task);
|
|
336
|
+
break;
|
|
337
|
+
} catch (error) {
|
|
338
|
+
lastError = error as Error;
|
|
339
|
+
attempt++;
|
|
340
|
+
|
|
341
|
+
if (!this.autoRetry || attempt >= this.maxRetries) {
|
|
342
|
+
throw lastError;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Wait before retry with exponential backoff
|
|
346
|
+
const delay = 1000 * Math.pow(this.retryBackoff, attempt - 1);
|
|
347
|
+
await this.delay(delay);
|
|
348
|
+
|
|
349
|
+
this.emit('task-retry', {
|
|
350
|
+
workerId: this.id,
|
|
351
|
+
taskId: task.id,
|
|
352
|
+
attempt,
|
|
353
|
+
maxRetries: this.maxRetries,
|
|
354
|
+
delay,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Cleanup on success
|
|
360
|
+
if (this.autoCleanup) {
|
|
361
|
+
await this.cleanupCheckpoints();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
this.emit('long-task-completed', {
|
|
365
|
+
workerId: this.id,
|
|
366
|
+
taskId: task.id,
|
|
367
|
+
duration: Date.now() - this.executionStartTime,
|
|
368
|
+
checkpointsCreated: this.checkpointSequence,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
return result!;
|
|
372
|
+
} catch (error) {
|
|
373
|
+
// Save final checkpoint on failure
|
|
374
|
+
await this.saveCheckpoint();
|
|
375
|
+
|
|
376
|
+
this.emit('long-task-failed', {
|
|
377
|
+
workerId: this.id,
|
|
378
|
+
taskId: task.id,
|
|
379
|
+
error: error as Error,
|
|
380
|
+
checkpointId: this.checkpoints[this.checkpoints.length - 1]?.id,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
content: { error: (error as Error).message },
|
|
385
|
+
success: false,
|
|
386
|
+
error: error as Error,
|
|
387
|
+
duration: Date.now() - this.executionStartTime,
|
|
388
|
+
metadata: {
|
|
389
|
+
checkpointId: this.checkpoints[this.checkpoints.length - 1]?.id,
|
|
390
|
+
progress: this.calculateProgress(),
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
} finally {
|
|
394
|
+
this.stopTimers();
|
|
395
|
+
this.currentLongTask = null;
|
|
396
|
+
this.abortController = null;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Save a checkpoint of the current execution state
|
|
402
|
+
*
|
|
403
|
+
* @returns Created checkpoint
|
|
404
|
+
*/
|
|
405
|
+
async saveCheckpoint(): Promise<Checkpoint> {
|
|
406
|
+
if (!this.currentLongTask || !this.currentState) {
|
|
407
|
+
throw new Error('No active task to checkpoint');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
this.checkpointSequence++;
|
|
411
|
+
|
|
412
|
+
const checkpoint: Checkpoint = {
|
|
413
|
+
id: `cp_${this.id}_${this.currentLongTask.id}_${this.checkpointSequence}`,
|
|
414
|
+
taskId: this.currentLongTask.id,
|
|
415
|
+
workerId: this.id,
|
|
416
|
+
sequence: this.checkpointSequence,
|
|
417
|
+
timestamp: Date.now(),
|
|
418
|
+
state: { ...this.currentState },
|
|
419
|
+
progress: this.calculateProgress(),
|
|
420
|
+
metadata: {
|
|
421
|
+
executionDuration: Date.now() - this.executionStartTime,
|
|
422
|
+
},
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// Save to storage
|
|
426
|
+
await this.storage.save(checkpoint);
|
|
427
|
+
|
|
428
|
+
// Update local list
|
|
429
|
+
this.checkpoints.push(checkpoint);
|
|
430
|
+
|
|
431
|
+
// Trim old checkpoints
|
|
432
|
+
await this.trimCheckpoints();
|
|
433
|
+
|
|
434
|
+
this.emit('checkpoint-saved', {
|
|
435
|
+
workerId: this.id,
|
|
436
|
+
taskId: this.currentLongTask.id,
|
|
437
|
+
checkpointId: checkpoint.id,
|
|
438
|
+
sequence: checkpoint.sequence,
|
|
439
|
+
progress: checkpoint.progress,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
return checkpoint;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Resume execution from a checkpoint
|
|
447
|
+
*
|
|
448
|
+
* @param checkpointId - Checkpoint ID to resume from
|
|
449
|
+
* @returns Agent output with results
|
|
450
|
+
*/
|
|
451
|
+
async resumeFromCheckpoint(checkpointId: string): Promise<AgentOutput> {
|
|
452
|
+
const checkpoint = await this.storage.load(checkpointId);
|
|
453
|
+
|
|
454
|
+
if (!checkpoint) {
|
|
455
|
+
throw new Error(`Checkpoint not found: ${checkpointId}`);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
this.emit('resuming-from-checkpoint', {
|
|
459
|
+
workerId: this.id,
|
|
460
|
+
checkpointId,
|
|
461
|
+
taskId: checkpoint.taskId,
|
|
462
|
+
progress: checkpoint.progress,
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// Restore state
|
|
466
|
+
this.currentState = { ...checkpoint.state };
|
|
467
|
+
this.checkpointSequence = checkpoint.sequence;
|
|
468
|
+
this.checkpoints = await this.storage.list(checkpoint.taskId, this.id);
|
|
469
|
+
|
|
470
|
+
// Create a synthetic task from checkpoint
|
|
471
|
+
const resumeTask: Task = {
|
|
472
|
+
id: checkpoint.taskId,
|
|
473
|
+
type: 'resume',
|
|
474
|
+
description: `Resume from checkpoint ${checkpointId}`,
|
|
475
|
+
metadata: {
|
|
476
|
+
resumedFromCheckpoint: checkpointId,
|
|
477
|
+
previousProgress: checkpoint.progress,
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
this.currentLongTask = resumeTask;
|
|
482
|
+
this.executionStartTime = Date.now();
|
|
483
|
+
this.abortController = new AbortController();
|
|
484
|
+
|
|
485
|
+
try {
|
|
486
|
+
// Start timers
|
|
487
|
+
this.startCheckpointTimer();
|
|
488
|
+
this.startProgressTimer();
|
|
489
|
+
|
|
490
|
+
// Execute remaining work
|
|
491
|
+
const result = await this.executeFromState(checkpoint.state);
|
|
492
|
+
|
|
493
|
+
// Cleanup on success
|
|
494
|
+
if (this.autoCleanup) {
|
|
495
|
+
await this.cleanupCheckpoints();
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
this.emit('resumed-task-completed', {
|
|
499
|
+
workerId: this.id,
|
|
500
|
+
taskId: checkpoint.taskId,
|
|
501
|
+
checkpointId,
|
|
502
|
+
duration: Date.now() - this.executionStartTime,
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
return result;
|
|
506
|
+
} catch (error) {
|
|
507
|
+
await this.saveCheckpoint();
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
content: { error: (error as Error).message },
|
|
511
|
+
success: false,
|
|
512
|
+
error: error as Error,
|
|
513
|
+
duration: Date.now() - this.executionStartTime,
|
|
514
|
+
metadata: {
|
|
515
|
+
resumedFromCheckpoint: checkpointId,
|
|
516
|
+
finalCheckpointId: this.checkpoints[this.checkpoints.length - 1]?.id,
|
|
517
|
+
},
|
|
518
|
+
};
|
|
519
|
+
} finally {
|
|
520
|
+
this.stopTimers();
|
|
521
|
+
this.currentLongTask = null;
|
|
522
|
+
this.abortController = null;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Get all checkpoints for the current or specified task
|
|
528
|
+
*
|
|
529
|
+
* @param taskId - Optional task ID (uses current task if not specified)
|
|
530
|
+
* @returns List of checkpoints
|
|
531
|
+
*/
|
|
532
|
+
async getCheckpoints(taskId?: string): Promise<Checkpoint[]> {
|
|
533
|
+
const targetTaskId = taskId || this.currentLongTask?.id;
|
|
534
|
+
|
|
535
|
+
if (!targetTaskId) {
|
|
536
|
+
return this.checkpoints;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return this.storage.list(targetTaskId, this.id);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Cancel the current long-running task
|
|
544
|
+
*/
|
|
545
|
+
async cancelTask(): Promise<void> {
|
|
546
|
+
if (this.abortController) {
|
|
547
|
+
this.abortController.abort();
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Save final checkpoint
|
|
551
|
+
if (this.currentLongTask) {
|
|
552
|
+
await this.saveCheckpoint();
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
this.stopTimers();
|
|
556
|
+
|
|
557
|
+
this.emit('task-cancelled', {
|
|
558
|
+
workerId: this.id,
|
|
559
|
+
taskId: this.currentLongTask?.id,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Update the current execution state
|
|
565
|
+
*
|
|
566
|
+
* @param phase - Current phase name
|
|
567
|
+
* @param step - Current step number
|
|
568
|
+
* @param totalSteps - Total steps in phase
|
|
569
|
+
* @param partialResult - Optional partial result to accumulate
|
|
570
|
+
*/
|
|
571
|
+
protected updateState(
|
|
572
|
+
phase: string,
|
|
573
|
+
step: number,
|
|
574
|
+
totalSteps: number,
|
|
575
|
+
partialResult?: unknown
|
|
576
|
+
): void {
|
|
577
|
+
if (!this.currentState) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
this.currentState.phase = phase;
|
|
582
|
+
this.currentState.step = step;
|
|
583
|
+
this.currentState.totalSteps = totalSteps;
|
|
584
|
+
|
|
585
|
+
if (partialResult !== undefined) {
|
|
586
|
+
this.currentState.partialResults.push(partialResult);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Update context data
|
|
592
|
+
*
|
|
593
|
+
* @param key - Context key
|
|
594
|
+
* @param value - Context value
|
|
595
|
+
*/
|
|
596
|
+
protected updateContext(key: string, value: unknown): void {
|
|
597
|
+
if (this.currentState) {
|
|
598
|
+
this.currentState.context[key] = value;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Add an artifact
|
|
604
|
+
*
|
|
605
|
+
* @param artifact - Artifact to add
|
|
606
|
+
*/
|
|
607
|
+
protected addArtifact(artifact: WorkerArtifact): void {
|
|
608
|
+
if (this.currentState) {
|
|
609
|
+
this.currentState.artifacts.push(artifact);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Check if task should be aborted
|
|
615
|
+
*/
|
|
616
|
+
protected isAborted(): boolean {
|
|
617
|
+
return this.abortController?.signal.aborted ?? false;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Execute task with timeout handling
|
|
622
|
+
*/
|
|
623
|
+
private async executeWithTimeout(task: Task): Promise<AgentOutput> {
|
|
624
|
+
if (this.taskTimeout <= 0) {
|
|
625
|
+
return this.executeCore(task);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return Promise.race([
|
|
629
|
+
this.executeCore(task),
|
|
630
|
+
new Promise<AgentOutput>((_, reject) => {
|
|
631
|
+
setTimeout(() => {
|
|
632
|
+
reject(new Error(`Task timeout after ${this.taskTimeout}ms`));
|
|
633
|
+
}, this.taskTimeout);
|
|
634
|
+
}),
|
|
635
|
+
]);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Core execution logic
|
|
640
|
+
*
|
|
641
|
+
* Override this in subclasses for custom long-running task implementations.
|
|
642
|
+
*
|
|
643
|
+
* @param task - Task to execute
|
|
644
|
+
* @returns Execution output
|
|
645
|
+
*/
|
|
646
|
+
protected async executeCore(task: Task): Promise<AgentOutput> {
|
|
647
|
+
// Default implementation with standard execution phases
|
|
648
|
+
const phases: ExecutionPhase[] = [
|
|
649
|
+
{ name: 'initialization', estimatedSteps: 1 },
|
|
650
|
+
{ name: 'processing', estimatedSteps: 5 },
|
|
651
|
+
{ name: 'finalization', estimatedSteps: 1 },
|
|
652
|
+
];
|
|
653
|
+
|
|
654
|
+
for (const phase of phases) {
|
|
655
|
+
if (this.isAborted()) {
|
|
656
|
+
throw new Error('Task aborted');
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
for (let step = 1; step <= phase.estimatedSteps; step++) {
|
|
660
|
+
this.updateState(phase.name, step, phase.estimatedSteps);
|
|
661
|
+
|
|
662
|
+
// Phase processing time
|
|
663
|
+
await this.delay(100);
|
|
664
|
+
|
|
665
|
+
// Add partial result
|
|
666
|
+
this.updateState(phase.name, step, phase.estimatedSteps, {
|
|
667
|
+
phase: phase.name,
|
|
668
|
+
step,
|
|
669
|
+
timestamp: Date.now(),
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
return {
|
|
675
|
+
content: {
|
|
676
|
+
results: this.currentState?.partialResults || [],
|
|
677
|
+
artifacts: this.currentState?.artifacts || [],
|
|
678
|
+
},
|
|
679
|
+
success: true,
|
|
680
|
+
duration: Date.now() - this.executionStartTime,
|
|
681
|
+
artifacts: this.currentState?.artifacts,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Execute from a restored state
|
|
687
|
+
*
|
|
688
|
+
* @param state - State to resume from
|
|
689
|
+
* @returns Execution output
|
|
690
|
+
*/
|
|
691
|
+
protected async executeFromState(state: CheckpointState): Promise<AgentOutput> {
|
|
692
|
+
// Continue from saved state - subclasses can override for specific behavior
|
|
693
|
+
return this.executeCore(this.currentLongTask!);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Start the checkpoint timer
|
|
698
|
+
*/
|
|
699
|
+
private startCheckpointTimer(): void {
|
|
700
|
+
if (this.checkpointInterval <= 0) {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
this.checkpointTimer = setInterval(async () => {
|
|
705
|
+
if (this.currentLongTask && this.currentState) {
|
|
706
|
+
try {
|
|
707
|
+
await this.saveCheckpoint();
|
|
708
|
+
} catch (error) {
|
|
709
|
+
this.emit('checkpoint-error', {
|
|
710
|
+
workerId: this.id,
|
|
711
|
+
error: error as Error,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}, this.checkpointInterval);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Start the progress timer
|
|
720
|
+
*/
|
|
721
|
+
private startProgressTimer(): void {
|
|
722
|
+
if (this.progressInterval <= 0) {
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
this.progressTimer = setInterval(() => {
|
|
727
|
+
if (this.currentLongTask && this.currentState) {
|
|
728
|
+
const progress = this.calculateProgress();
|
|
729
|
+
const elapsed = Date.now() - this.executionStartTime;
|
|
730
|
+
const estimatedRemaining = progress > 0
|
|
731
|
+
? (elapsed / progress) * (1 - progress)
|
|
732
|
+
: undefined;
|
|
733
|
+
|
|
734
|
+
const update: ProgressUpdate = {
|
|
735
|
+
taskId: this.currentLongTask.id,
|
|
736
|
+
workerId: this.id,
|
|
737
|
+
phase: this.currentState.phase,
|
|
738
|
+
step: this.currentState.step,
|
|
739
|
+
totalSteps: this.currentState.totalSteps,
|
|
740
|
+
progress,
|
|
741
|
+
estimatedTimeRemaining: estimatedRemaining,
|
|
742
|
+
timestamp: Date.now(),
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
this.emit('progress', update);
|
|
746
|
+
}
|
|
747
|
+
}, this.progressInterval);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Stop all timers
|
|
752
|
+
*/
|
|
753
|
+
private stopTimers(): void {
|
|
754
|
+
if (this.checkpointTimer) {
|
|
755
|
+
clearInterval(this.checkpointTimer);
|
|
756
|
+
this.checkpointTimer = null;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (this.progressTimer) {
|
|
760
|
+
clearInterval(this.progressTimer);
|
|
761
|
+
this.progressTimer = null;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Calculate overall progress
|
|
767
|
+
*/
|
|
768
|
+
private calculateProgress(): number {
|
|
769
|
+
if (!this.currentState) {
|
|
770
|
+
return 0;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const { step, totalSteps } = this.currentState;
|
|
774
|
+
if (totalSteps <= 0) {
|
|
775
|
+
return 0;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
return Math.min(1, step / totalSteps);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Trim old checkpoints to stay within limit
|
|
783
|
+
*/
|
|
784
|
+
private async trimCheckpoints(): Promise<void> {
|
|
785
|
+
if (this.checkpoints.length <= this.maxCheckpoints) {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Sort by sequence and remove oldest
|
|
790
|
+
this.checkpoints.sort((a, b) => a.sequence - b.sequence);
|
|
791
|
+
|
|
792
|
+
while (this.checkpoints.length > this.maxCheckpoints) {
|
|
793
|
+
const oldest = this.checkpoints.shift();
|
|
794
|
+
if (oldest) {
|
|
795
|
+
await this.storage.delete(oldest.id);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Cleanup all checkpoints for the current task
|
|
802
|
+
*/
|
|
803
|
+
private async cleanupCheckpoints(): Promise<void> {
|
|
804
|
+
if (!this.currentLongTask) {
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
await this.storage.deleteAll(this.currentLongTask.id, this.id);
|
|
809
|
+
this.checkpoints = [];
|
|
810
|
+
|
|
811
|
+
this.emit('checkpoints-cleaned', {
|
|
812
|
+
workerId: this.id,
|
|
813
|
+
taskId: this.currentLongTask.id,
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* Utility delay function
|
|
819
|
+
*/
|
|
820
|
+
private delay(ms: number): Promise<void> {
|
|
821
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Shutdown with checkpoint cleanup
|
|
826
|
+
*/
|
|
827
|
+
protected async onShutdown(): Promise<void> {
|
|
828
|
+
// Cancel any running task
|
|
829
|
+
if (this.currentLongTask) {
|
|
830
|
+
await this.cancelTask();
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
this.stopTimers();
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Create a long-running worker with the given configuration
|
|
839
|
+
*
|
|
840
|
+
* @param config - Worker configuration
|
|
841
|
+
* @returns Configured LongRunningWorker
|
|
842
|
+
*/
|
|
843
|
+
export function createLongRunningWorker(
|
|
844
|
+
config: Partial<LongRunningWorkerConfig> = {}
|
|
845
|
+
): LongRunningWorker {
|
|
846
|
+
return new LongRunningWorker({
|
|
847
|
+
id: config.id || `long-runner-${Date.now()}`,
|
|
848
|
+
type: 'long-running',
|
|
849
|
+
capabilities: config.capabilities || ['long-running'],
|
|
850
|
+
...config,
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Create a custom checkpoint storage
|
|
856
|
+
*
|
|
857
|
+
* @param options - Storage options
|
|
858
|
+
* @returns Checkpoint storage implementation
|
|
859
|
+
*/
|
|
860
|
+
export function createCheckpointStorage(options?: {
|
|
861
|
+
type?: 'memory' | 'file' | 'custom';
|
|
862
|
+
path?: string;
|
|
863
|
+
custom?: CheckpointStorage;
|
|
864
|
+
}): CheckpointStorage {
|
|
865
|
+
if (options?.custom) {
|
|
866
|
+
return options.custom;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Default to in-memory storage
|
|
870
|
+
return new InMemoryCheckpointStorage();
|
|
871
|
+
}
|