converse-mcp-server 2.3.1 → 2.4.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.
Files changed (42) hide show
  1. package/README.md +771 -738
  2. package/docs/API.md +10 -1
  3. package/docs/PROVIDERS.md +8 -4
  4. package/package.json +12 -12
  5. package/src/async/asyncJobStore.js +82 -52
  6. package/src/async/eventBus.js +25 -20
  7. package/src/async/fileCache.js +121 -40
  8. package/src/async/jobRunner.js +65 -39
  9. package/src/async/providerStreamNormalizer.js +203 -117
  10. package/src/config.js +374 -102
  11. package/src/continuationStore.js +32 -24
  12. package/src/index.js +45 -25
  13. package/src/prompts/helpPrompt.js +328 -305
  14. package/src/providers/anthropic.js +303 -119
  15. package/src/providers/codex.js +103 -45
  16. package/src/providers/deepseek.js +24 -8
  17. package/src/providers/google.js +337 -93
  18. package/src/providers/index.js +1 -1
  19. package/src/providers/interface.js +16 -11
  20. package/src/providers/mistral.js +179 -69
  21. package/src/providers/openai-compatible.js +231 -94
  22. package/src/providers/openai.js +1094 -914
  23. package/src/providers/openrouter-endpoints-client.js +220 -216
  24. package/src/providers/openrouter.js +426 -381
  25. package/src/providers/xai.js +153 -56
  26. package/src/resources/helpResource.js +70 -67
  27. package/src/router.js +95 -67
  28. package/src/services/summarizationService.js +51 -24
  29. package/src/systemPrompts.js +89 -89
  30. package/src/tools/cancelJob.js +31 -19
  31. package/src/tools/chat.js +997 -883
  32. package/src/tools/checkStatus.js +86 -65
  33. package/src/tools/consensus.js +400 -234
  34. package/src/tools/index.js +39 -16
  35. package/src/transport/httpTransport.js +82 -55
  36. package/src/utils/contextProcessor.js +54 -37
  37. package/src/utils/errorHandler.js +95 -45
  38. package/src/utils/fileValidator.js +107 -98
  39. package/src/utils/formatStatus.js +122 -64
  40. package/src/utils/logger.js +459 -449
  41. package/src/utils/pathUtils.js +2 -2
  42. package/src/utils/tokenLimiter.js +216 -216
@@ -34,7 +34,9 @@ export class FileCacheInterface {
34
34
  * @returns {Promise<void>}
35
35
  */
36
36
  async writeJournalEvent(_jobId, _event) {
37
- throw new Error('writeJournalEvent() method must be implemented by file cache backend');
37
+ throw new Error(
38
+ 'writeJournalEvent() method must be implemented by file cache backend',
39
+ );
38
40
  }
39
41
 
40
42
  /**
@@ -44,7 +46,9 @@ export class FileCacheInterface {
44
46
  * @returns {Promise<void>}
45
47
  */
46
48
  async writeSnapshot(_jobId, _result) {
47
- throw new Error('writeSnapshot() method must be implemented by file cache backend');
49
+ throw new Error(
50
+ 'writeSnapshot() method must be implemented by file cache backend',
51
+ );
48
52
  }
49
53
 
50
54
  /**
@@ -53,7 +57,9 @@ export class FileCacheInterface {
53
57
  * @returns {Promise<object|null>} Job result or null if not found
54
58
  */
55
59
  async readSnapshot(_jobId) {
56
- throw new Error('readSnapshot() method must be implemented by file cache backend');
60
+ throw new Error(
61
+ 'readSnapshot() method must be implemented by file cache backend',
62
+ );
57
63
  }
58
64
 
59
65
  /**
@@ -62,7 +68,9 @@ export class FileCacheInterface {
62
68
  * @returns {Promise<number>} Number of directories cleaned up
63
69
  */
64
70
  async cleanup(_maxAgeMs = 3 * 24 * 60 * 60 * 1000) {
65
- throw new Error('cleanup() method must be implemented by file cache backend');
71
+ throw new Error(
72
+ 'cleanup() method must be implemented by file cache backend',
73
+ );
66
74
  }
67
75
  }
68
76
 
@@ -72,11 +80,16 @@ export class FileCacheInterface {
72
80
  export class FileCache extends FileCacheInterface {
73
81
  constructor(options = {}) {
74
82
  super();
75
- this.baseDir = options.baseDir || process.env.ASYNC_CACHE_DIR || path.join(process.cwd(), 'cache', 'async');
83
+ this.baseDir =
84
+ options.baseDir ||
85
+ process.env.ASYNC_CACHE_DIR ||
86
+ path.join(process.cwd(), 'cache', 'async');
76
87
  this.cleanupInterval = options.cleanupInterval || 10 * 60 * 1000; // 10 minutes
77
88
 
78
89
  // Check environment variable for disk TTL
79
- const envDiskTTL = process.env.ASYNC_DISK_TTL_MS ? parseInt(process.env.ASYNC_DISK_TTL_MS, 10) : null;
90
+ const envDiskTTL = process.env.ASYNC_DISK_TTL_MS
91
+ ? parseInt(process.env.ASYNC_DISK_TTL_MS, 10)
92
+ : null;
80
93
  this.maxAge = options.maxAge || envDiskTTL || 3 * 24 * 60 * 60 * 1000; // 3 days default
81
94
 
82
95
  this.cleanupTimer = null;
@@ -84,7 +97,10 @@ export class FileCache extends FileCacheInterface {
84
97
  // Start cleanup timer
85
98
  this.startCleanupTimer();
86
99
 
87
- debugLog('FileCache', `Initialized with baseDir: ${this.baseDir}, maxAge: ${this.maxAge}ms`);
100
+ debugLog(
101
+ 'FileCache',
102
+ `Initialized with baseDir: ${this.baseDir}, maxAge: ${this.maxAge}ms`,
103
+ );
88
104
  }
89
105
 
90
106
  /**
@@ -100,7 +116,10 @@ export class FileCache extends FileCacheInterface {
100
116
  try {
101
117
  const cleaned = await this.cleanup();
102
118
  if (cleaned > 0) {
103
- debugLog('FileCache', `Cleanup completed: ${cleaned} directories removed`);
119
+ debugLog(
120
+ 'FileCache',
121
+ `Cleanup completed: ${cleaned} directories removed`,
122
+ );
104
123
  }
105
124
  } catch (error) {
106
125
  debugError('FileCache', 'Cleanup timer error:', error);
@@ -167,7 +186,7 @@ export class FileCache extends FileCacheInterface {
167
186
  throw new FileCacheError(
168
187
  `Failed to create directory: ${dirPath}`,
169
188
  ERROR_CODES.CACHE_DIRECTORY_CREATION_FAILED,
170
- { dirPath, originalError: error.message }
189
+ { dirPath, originalError: error.message },
171
190
  );
172
191
  }
173
192
  }
@@ -183,7 +202,7 @@ export class FileCache extends FileCacheInterface {
183
202
  throw new FileCacheError(
184
203
  'Job ID must be a non-empty string',
185
204
  ERROR_CODES.CACHE_WRITE_FAILED,
186
- { jobId }
205
+ { jobId },
187
206
  );
188
207
  }
189
208
 
@@ -191,7 +210,7 @@ export class FileCache extends FileCacheInterface {
191
210
  throw new FileCacheError(
192
211
  'Event must be an object',
193
212
  ERROR_CODES.CACHE_WRITE_FAILED,
194
- { jobId, event }
213
+ { jobId, event },
195
214
  );
196
215
  }
197
216
 
@@ -206,24 +225,32 @@ export class FileCache extends FileCacheInterface {
206
225
  const eventWithMeta = {
207
226
  ts: Date.now(),
208
227
  jobId,
209
- ...event
228
+ ...event,
210
229
  };
211
230
 
212
231
  // Append NDJSON line
213
232
  const ndjsonLine = JSON.stringify(eventWithMeta) + '\n';
214
233
  await fs.appendFile(journalPath, ndjsonLine, 'utf8');
215
234
 
216
- debugLog('FileCache', `Journal event written for job ${jobId}:`, event.type || 'unknown');
235
+ debugLog(
236
+ 'FileCache',
237
+ `Journal event written for job ${jobId}:`,
238
+ event.type || 'unknown',
239
+ );
217
240
  } catch (error) {
218
241
  if (error instanceof FileCacheError) {
219
242
  throw error;
220
243
  }
221
244
 
222
- debugError('FileCache', `Failed to write journal event for job ${jobId}:`, error);
245
+ debugError(
246
+ 'FileCache',
247
+ `Failed to write journal event for job ${jobId}:`,
248
+ error,
249
+ );
223
250
  throw new FileCacheError(
224
251
  `Failed to write journal event for job ${jobId}`,
225
252
  ERROR_CODES.CACHE_WRITE_FAILED,
226
- { jobId, event, originalError: error.message }
253
+ { jobId, event, originalError: error.message },
227
254
  );
228
255
  }
229
256
  }
@@ -239,7 +266,7 @@ export class FileCache extends FileCacheInterface {
239
266
  throw new FileCacheError(
240
267
  'Job ID must be a non-empty string',
241
268
  ERROR_CODES.CACHE_WRITE_FAILED,
242
- { jobId }
269
+ { jobId },
243
270
  );
244
271
  }
245
272
 
@@ -247,7 +274,7 @@ export class FileCache extends FileCacheInterface {
247
274
  throw new FileCacheError(
248
275
  'Result must be an object',
249
276
  ERROR_CODES.CACHE_WRITE_FAILED,
250
- { jobId, result }
277
+ { jobId, result },
251
278
  );
252
279
  }
253
280
 
@@ -262,7 +289,7 @@ export class FileCache extends FileCacheInterface {
262
289
  const snapshot = {
263
290
  jobId,
264
291
  completedAt: Date.now(),
265
- ...result
292
+ ...result,
266
293
  };
267
294
 
268
295
  // Write pretty-printed JSON
@@ -275,11 +302,15 @@ export class FileCache extends FileCacheInterface {
275
302
  throw error;
276
303
  }
277
304
 
278
- debugError('FileCache', `Failed to write snapshot for job ${jobId}:`, error);
305
+ debugError(
306
+ 'FileCache',
307
+ `Failed to write snapshot for job ${jobId}:`,
308
+ error,
309
+ );
279
310
  throw new FileCacheError(
280
311
  `Failed to write snapshot for job ${jobId}`,
281
312
  ERROR_CODES.CACHE_WRITE_FAILED,
282
- { jobId, result, originalError: error.message }
313
+ { jobId, result, originalError: error.message },
283
314
  );
284
315
  }
285
316
  }
@@ -294,7 +325,7 @@ export class FileCache extends FileCacheInterface {
294
325
  throw new FileCacheError(
295
326
  'Job ID must be a non-empty string',
296
327
  ERROR_CODES.CACHE_READ_FAILED,
297
- { jobId }
328
+ { jobId },
298
329
  );
299
330
  }
300
331
 
@@ -311,12 +342,18 @@ export class FileCache extends FileCacheInterface {
311
342
  const lastUpdate = snapshot.updated_at || snapshot.ended_at;
312
343
  const age = Date.now() - lastUpdate;
313
344
  if (age > this.maxAge) {
314
- debugLog('FileCache', `Snapshot for job ${jobId} has expired (age: ${age}ms, maxAge: ${this.maxAge}ms)`);
345
+ debugLog(
346
+ 'FileCache',
347
+ `Snapshot for job ${jobId} has expired (age: ${age}ms, maxAge: ${this.maxAge}ms)`,
348
+ );
315
349
  return null;
316
350
  }
317
351
  }
318
352
 
319
- debugLog('FileCache', `Snapshot read for job ${jobId} from current date`);
353
+ debugLog(
354
+ 'FileCache',
355
+ `Snapshot read for job ${jobId} from current date`,
356
+ );
320
357
  return snapshot;
321
358
  } catch (_currentDateError) {
322
359
  // If not found in current date, search in recent directories
@@ -327,12 +364,18 @@ export class FileCache extends FileCacheInterface {
327
364
  const lastUpdate = result.updated_at || result.ended_at;
328
365
  const age = Date.now() - lastUpdate;
329
366
  if (age > this.maxAge) {
330
- debugLog('FileCache', `Snapshot for job ${jobId} has expired (age: ${age}ms, maxAge: ${this.maxAge}ms)`);
367
+ debugLog(
368
+ 'FileCache',
369
+ `Snapshot for job ${jobId} has expired (age: ${age}ms, maxAge: ${this.maxAge}ms)`,
370
+ );
331
371
  return null;
332
372
  }
333
373
  }
334
374
 
335
- debugLog('FileCache', `Snapshot read for job ${jobId} from recent directories`);
375
+ debugLog(
376
+ 'FileCache',
377
+ `Snapshot read for job ${jobId} from recent directories`,
378
+ );
336
379
  return result;
337
380
  }
338
381
 
@@ -345,11 +388,15 @@ export class FileCache extends FileCacheInterface {
345
388
  throw error;
346
389
  }
347
390
 
348
- debugError('FileCache', `Failed to read snapshot for job ${jobId}:`, error);
391
+ debugError(
392
+ 'FileCache',
393
+ `Failed to read snapshot for job ${jobId}:`,
394
+ error,
395
+ );
349
396
  throw new FileCacheError(
350
397
  `Failed to read snapshot for job ${jobId}`,
351
398
  ERROR_CODES.CACHE_READ_FAILED,
352
- { jobId, originalError: error.message }
399
+ { jobId, originalError: error.message },
353
400
  );
354
401
  }
355
402
  }
@@ -365,12 +412,20 @@ export class FileCache extends FileCacheInterface {
365
412
  // Get directories in base directory
366
413
  const entries = await fs.readdir(this.baseDir, { withFileTypes: true });
367
414
  const dateDirs = entries
368
- .filter(entry => entry.isDirectory() && /^\d{4}-\d{2}-\d{2}$/.test(entry.name))
415
+ .filter(
416
+ (entry) =>
417
+ entry.isDirectory() && /^\d{4}-\d{2}-\d{2}$/.test(entry.name),
418
+ )
369
419
  .sort((a, b) => b.name.localeCompare(a.name)) // Most recent first
370
420
  .slice(0, 10); // Only check last 10 days
371
421
 
372
422
  for (const dateDir of dateDirs) {
373
- const snapshotPath = path.join(this.baseDir, dateDir.name, jobId, 'result.json');
423
+ const snapshotPath = path.join(
424
+ this.baseDir,
425
+ dateDir.name,
426
+ jobId,
427
+ 'result.json',
428
+ );
374
429
  try {
375
430
  const content = await fs.readFile(snapshotPath, 'utf8');
376
431
  return JSON.parse(content);
@@ -409,7 +464,10 @@ export class FileCache extends FileCacheInterface {
409
464
  // Get directories in base directory
410
465
  const entries = await fs.readdir(this.baseDir, { withFileTypes: true });
411
466
  const dateDirs = entries
412
- .filter(entry => entry.isDirectory() && /^\d{4}-\d{2}-\d{2}$/.test(entry.name))
467
+ .filter(
468
+ (entry) =>
469
+ entry.isDirectory() && /^\d{4}-\d{2}-\d{2}$/.test(entry.name),
470
+ )
413
471
  .sort((a, b) => b.name.localeCompare(a.name)) // Most recent first
414
472
  .slice(0, daysBack); // Only check specified days back
415
473
 
@@ -421,9 +479,11 @@ export class FileCache extends FileCacheInterface {
421
479
 
422
480
  try {
423
481
  // Get all job directories in this date
424
- const jobEntries = await fs.readdir(dateDirPath, { withFileTypes: true });
482
+ const jobEntries = await fs.readdir(dateDirPath, {
483
+ withFileTypes: true,
484
+ });
425
485
  const jobDirs = jobEntries
426
- .filter(entry => entry.isDirectory())
486
+ .filter((entry) => entry.isDirectory())
427
487
  .sort((a, b) => {
428
488
  // Try to sort by modification time if possible
429
489
  try {
@@ -441,14 +501,23 @@ export class FileCache extends FileCacheInterface {
441
501
  for (const jobDir of jobDirs) {
442
502
  if (jobs.length >= limit) break;
443
503
 
444
- const snapshotPath = path.join(dateDirPath, jobDir.name, 'result.json');
504
+ const snapshotPath = path.join(
505
+ dateDirPath,
506
+ jobDir.name,
507
+ 'result.json',
508
+ );
445
509
 
446
510
  try {
447
511
  const content = await fs.readFile(snapshotPath, 'utf8');
448
512
  const snapshot = JSON.parse(content);
449
513
 
450
514
  // Add the job to our list if it's completed
451
- if (snapshot && (snapshot.status === 'completed' || snapshot.status === 'failed' || snapshot.status === 'cancelled')) {
515
+ if (
516
+ snapshot &&
517
+ (snapshot.status === 'completed' ||
518
+ snapshot.status === 'failed' ||
519
+ snapshot.status === 'cancelled')
520
+ ) {
452
521
  jobs.push(snapshot);
453
522
  }
454
523
  } catch {
@@ -457,7 +526,11 @@ export class FileCache extends FileCacheInterface {
457
526
  }
458
527
  }
459
528
  } catch (error) {
460
- debugError('FileCache', `Failed to read date directory ${dateDir.name}:`, error);
529
+ debugError(
530
+ 'FileCache',
531
+ `Failed to read date directory ${dateDir.name}:`,
532
+ error,
533
+ );
461
534
  // Continue with other directories
462
535
  }
463
536
  }
@@ -486,14 +559,18 @@ export class FileCache extends FileCacheInterface {
486
559
  await fs.access(this.baseDir);
487
560
  } catch {
488
561
  // Base directory doesn't exist, nothing to clean
489
- debugLog('FileCache', 'Base directory does not exist, skipping cleanup');
562
+ debugLog(
563
+ 'FileCache',
564
+ 'Base directory does not exist, skipping cleanup',
565
+ );
490
566
  return 0;
491
567
  }
492
568
 
493
569
  // Get all date directories
494
570
  const entries = await fs.readdir(this.baseDir, { withFileTypes: true });
495
- const dateDirs = entries.filter(entry =>
496
- entry.isDirectory() && /^\d{4}-\d{2}-\d{2}$/.test(entry.name)
571
+ const dateDirs = entries.filter(
572
+ (entry) =>
573
+ entry.isDirectory() && /^\d{4}-\d{2}-\d{2}$/.test(entry.name),
497
574
  );
498
575
 
499
576
  for (const dateDir of dateDirs) {
@@ -509,7 +586,11 @@ export class FileCache extends FileCacheInterface {
509
586
  debugLog('FileCache', `Cleaned up old directory: ${dateDir.name}`);
510
587
  }
511
588
  } catch (error) {
512
- debugError('FileCache', `Failed to clean directory ${dateDir.name}:`, error);
589
+ debugError(
590
+ 'FileCache',
591
+ `Failed to clean directory ${dateDir.name}:`,
592
+ error,
593
+ );
513
594
  // Continue with other directories
514
595
  }
515
596
  }
@@ -520,7 +601,7 @@ export class FileCache extends FileCacheInterface {
520
601
  throw new FileCacheError(
521
602
  'Failed to cleanup old cache directories',
522
603
  ERROR_CODES.CACHE_CLEANUP_FAILED,
523
- { maxAgeMs, originalError: error.message }
604
+ { maxAgeMs, originalError: error.message },
524
605
  );
525
606
  }
526
607
  }
@@ -44,7 +44,7 @@ export class JobRunner extends EventEmitter {
44
44
  if (!dependencies || !dependencies.asyncJobStore) {
45
45
  throw new JobRunnerError(
46
46
  'AsyncJobStore is required',
47
- 'MISSING_DEPENDENCIES'
47
+ 'MISSING_DEPENDENCIES',
48
48
  );
49
49
  }
50
50
 
@@ -72,7 +72,9 @@ export class JobRunner extends EventEmitter {
72
72
  activeCount: 0,
73
73
  };
74
74
 
75
- debugLog(`JobRunner: Initialized with concurrency limit ${this.concurrency}`);
75
+ debugLog(
76
+ `JobRunner: Initialized with concurrency limit ${this.concurrency}`,
77
+ );
76
78
  }
77
79
 
78
80
  /**
@@ -96,27 +98,24 @@ export class JobRunner extends EventEmitter {
96
98
  if (!jobSpec || !jobSpec.tool) {
97
99
  throw new JobRunnerError(
98
100
  'Invalid job specification: tool is required',
99
- 'INVALID_JOB_SPEC'
101
+ 'INVALID_JOB_SPEC',
100
102
  );
101
103
  }
102
104
 
103
105
  if (typeof runFunction !== 'function') {
104
106
  throw new JobRunnerError(
105
107
  'runFunction must be a callable function',
106
- 'INVALID_RUN_FUNCTION'
108
+ 'INVALID_RUN_FUNCTION',
107
109
  );
108
110
  }
109
111
 
110
112
  // Create job in store
111
- const jobId = await this.asyncJobStore.create(
112
- jobSpec.tool,
113
- {
114
- sessionId: jobSpec.sessionId,
115
- timeout: options.timeout || this.defaultTimeout,
116
- priority: options.priority || false,
117
- ...jobSpec.options,
118
- }
119
- );
113
+ const jobId = await this.asyncJobStore.create(jobSpec.tool, {
114
+ sessionId: jobSpec.sessionId,
115
+ timeout: options.timeout || this.defaultTimeout,
116
+ priority: options.priority || false,
117
+ ...jobSpec.options,
118
+ });
120
119
 
121
120
  // Set up abort controller for timeout and cancellation
122
121
  const abortController = new globalThis.AbortController();
@@ -138,13 +137,16 @@ export class JobRunner extends EventEmitter {
138
137
 
139
138
  // Submit to limiter queue for background execution
140
139
  const limitedExecution = this.limiter(() =>
141
- this._executeJob(jobId, runFunction, options, abortController.signal)
140
+ this._executeJob(jobId, runFunction, options, abortController.signal),
142
141
  );
143
142
 
144
143
  // Don't await - execute in background
145
144
  globalThis.setImmediate(() => {
146
145
  limitedExecution.catch((error) => {
147
- debugError(`JobRunner: Background execution failed for ${jobId}:`, error);
146
+ debugError(
147
+ `JobRunner: Background execution failed for ${jobId}:`,
148
+ error,
149
+ );
148
150
  });
149
151
  });
150
152
 
@@ -154,14 +156,13 @@ export class JobRunner extends EventEmitter {
154
156
 
155
157
  debugLog(`JobRunner: Submitted job ${jobId} for background execution`);
156
158
  return jobId;
157
-
158
159
  } catch (error) {
159
160
  if (error instanceof JobRunnerError) {
160
161
  throw error;
161
162
  }
162
163
  throw new JobRunnerError(
163
164
  `Failed to submit job: ${error.message}`,
164
- 'SUBMISSION_ERROR'
165
+ 'SUBMISSION_ERROR',
165
166
  );
166
167
  }
167
168
  }
@@ -215,7 +216,6 @@ export class JobRunner extends EventEmitter {
215
216
 
216
217
  debugLog(`JobRunner: Cancelled job ${jobId}`);
217
218
  return true;
218
-
219
219
  } catch (error) {
220
220
  debugError(`JobRunner: Failed to cancel job ${jobId}:`, error);
221
221
  return false;
@@ -251,7 +251,10 @@ export class JobRunner extends EventEmitter {
251
251
  // Get current job state
252
252
  jobState = await this.asyncJobStore.get(jobId);
253
253
  if (!jobState) {
254
- throw new JobRunnerError(`Job ${jobId} not found in store`, 'JOB_NOT_FOUND');
254
+ throw new JobRunnerError(
255
+ `Job ${jobId} not found in store`,
256
+ 'JOB_NOT_FOUND',
257
+ );
255
258
  }
256
259
 
257
260
  // Check if already cancelled or aborted
@@ -269,7 +272,7 @@ export class JobRunner extends EventEmitter {
269
272
 
270
273
  this.activeJobs.set(jobId, {
271
274
  startedAt: Date.now(),
272
- tool: jobState.tool
275
+ tool: jobState.tool,
273
276
  });
274
277
 
275
278
  // Emit job started event through EventBus
@@ -289,7 +292,9 @@ export class JobRunner extends EventEmitter {
289
292
  const timeoutHandle = setTimeout(() => {
290
293
  if (!signal.aborted) {
291
294
  debugLog(`JobRunner: Job ${jobId} timed out after ${timeout}ms`);
292
- this.abortControllers.get(jobId)?.abort(`Job timed out after ${timeout}ms`);
295
+ this.abortControllers
296
+ .get(jobId)
297
+ ?.abort(`Job timed out after ${timeout}ms`);
293
298
  }
294
299
  }, timeout);
295
300
 
@@ -300,7 +305,8 @@ export class JobRunner extends EventEmitter {
300
305
  tool: jobState.tool,
301
306
  signal,
302
307
  updateJob: (updates) => this.asyncJobStore.update(jobId, updates),
303
- emitEvent: (eventType, data) => this.emit(eventType, { jobId, ...data }),
308
+ emitEvent: (eventType, data) =>
309
+ this.emit(eventType, { jobId, ...data }),
304
310
  };
305
311
 
306
312
  // Execute the job function
@@ -337,11 +343,16 @@ export class JobRunner extends EventEmitter {
337
343
  provider: finalJobState.provider,
338
344
  model: finalJobState.model,
339
345
  title: finalJobState.title,
340
- final_summary: finalJobState.final_summary
346
+ final_summary: finalJobState.final_summary,
341
347
  });
342
- debugLog(`JobRunner: Written snapshot to FileCache for completed job ${jobId}`);
348
+ debugLog(
349
+ `JobRunner: Written snapshot to FileCache for completed job ${jobId}`,
350
+ );
343
351
  } catch (cacheError) {
344
- debugError(`JobRunner: Failed to write to FileCache for job ${jobId}:`, cacheError);
352
+ debugError(
353
+ `JobRunner: Failed to write to FileCache for job ${jobId}:`,
354
+ cacheError,
355
+ );
345
356
  // Continue even if cache write fails
346
357
  }
347
358
  }
@@ -359,7 +370,6 @@ export class JobRunner extends EventEmitter {
359
370
 
360
371
  this.stats.completed++;
361
372
  debugLog(`JobRunner: Completed job ${jobId}`);
362
-
363
373
  } catch (executionError) {
364
374
  // Clear timeout
365
375
  clearTimeout(timeoutHandle);
@@ -392,11 +402,16 @@ export class JobRunner extends EventEmitter {
392
402
  startedAt: cancelledJobState.startedAt,
393
403
  provider: cancelledJobState.provider,
394
404
  model: cancelledJobState.model,
395
- title: cancelledJobState.title
405
+ title: cancelledJobState.title,
396
406
  });
397
- debugLog(`JobRunner: Written snapshot to FileCache for cancelled job ${jobId}`);
407
+ debugLog(
408
+ `JobRunner: Written snapshot to FileCache for cancelled job ${jobId}`,
409
+ );
398
410
  } catch (cacheError) {
399
- debugError(`JobRunner: Failed to write to FileCache for job ${jobId}:`, cacheError);
411
+ debugError(
412
+ `JobRunner: Failed to write to FileCache for job ${jobId}:`,
413
+ cacheError,
414
+ );
400
415
  // Continue even if cache write fails
401
416
  }
402
417
  }
@@ -437,11 +452,16 @@ export class JobRunner extends EventEmitter {
437
452
  startedAt: failedJobState.startedAt,
438
453
  provider: failedJobState.provider,
439
454
  model: failedJobState.model,
440
- title: failedJobState.title
455
+ title: failedJobState.title,
441
456
  });
442
- debugLog(`JobRunner: Written snapshot to FileCache for failed job ${jobId}`);
457
+ debugLog(
458
+ `JobRunner: Written snapshot to FileCache for failed job ${jobId}`,
459
+ );
443
460
  } catch (cacheError) {
444
- debugError(`JobRunner: Failed to write to FileCache for job ${jobId}:`, cacheError);
461
+ debugError(
462
+ `JobRunner: Failed to write to FileCache for job ${jobId}:`,
463
+ cacheError,
464
+ );
445
465
  // Continue even if cache write fails
446
466
  }
447
467
  }
@@ -460,7 +480,6 @@ export class JobRunner extends EventEmitter {
460
480
  this.stats.failed++;
461
481
  debugError(`JobRunner: Job ${jobId} failed:`, executionError);
462
482
  }
463
-
464
483
  } catch (error) {
465
484
  // Handle system-level errors
466
485
  debugError('JobRunner: System error during job execution:', error);
@@ -481,12 +500,14 @@ export class JobRunner extends EventEmitter {
481
500
  timestamp: Date.now(),
482
501
  });
483
502
  } catch (updateError) {
484
- debugError(`JobRunner: Failed to update job ${jobId} after system error:`, updateError);
503
+ debugError(
504
+ `JobRunner: Failed to update job ${jobId} after system error:`,
505
+ updateError,
506
+ );
485
507
  }
486
508
  }
487
509
 
488
510
  this.stats.failed++;
489
-
490
511
  } finally {
491
512
  // Clean up tracking
492
513
  this.activeJobs.delete(jobId);
@@ -510,12 +531,17 @@ export class JobRunner extends EventEmitter {
510
531
 
511
532
  // Wait for active jobs to complete or timeout
512
533
  const shutdownStart = Date.now();
513
- while (this.stats.activeCount > 0 && (Date.now() - shutdownStart) < timeoutMs) {
514
- await new Promise(resolve => setTimeout(resolve, 100));
534
+ while (
535
+ this.stats.activeCount > 0 &&
536
+ Date.now() - shutdownStart < timeoutMs
537
+ ) {
538
+ await new Promise((resolve) => setTimeout(resolve, 100));
515
539
  }
516
540
 
517
541
  if (this.stats.activeCount > 0) {
518
- debugLog(`JobRunner: Forced shutdown - ${this.stats.activeCount} jobs still active`);
542
+ debugLog(
543
+ `JobRunner: Forced shutdown - ${this.stats.activeCount} jobs still active`,
544
+ );
519
545
  }
520
546
 
521
547
  // Clear all tracking
@@ -561,7 +587,7 @@ export function setJobRunner(runner) {
561
587
  if (runner !== null && !(runner instanceof JobRunner)) {
562
588
  throw new JobRunnerError(
563
589
  'Runner must be a JobRunner instance',
564
- 'INVALID_RUNNER'
590
+ 'INVALID_RUNNER',
565
591
  );
566
592
  }
567
593
  globalJobRunner = runner;