@sparkleideas/integration 3.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.
@@ -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
+ }