@ugm/desiagent 0.1.37 → 0.2.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 (115) hide show
  1. package/README.md +298 -156
  2. package/dist/core/execution/agents.d.ts.map +1 -1
  3. package/dist/core/execution/agents.js +18 -4
  4. package/dist/core/execution/agents.js.map +1 -1
  5. package/dist/core/execution/dagExecutor.d.ts +15 -2
  6. package/dist/core/execution/dagExecutor.d.ts.map +1 -1
  7. package/dist/core/execution/dagExecutor.js +164 -30
  8. package/dist/core/execution/dagExecutor.js.map +1 -1
  9. package/dist/core/execution/dags.d.ts +19 -1
  10. package/dist/core/execution/dags.d.ts.map +1 -1
  11. package/dist/core/execution/dags.js +171 -92
  12. package/dist/core/execution/dags.js.map +1 -1
  13. package/dist/core/execution/inference.d.ts +20 -16
  14. package/dist/core/execution/inference.d.ts.map +1 -1
  15. package/dist/core/execution/inference.js +2 -1
  16. package/dist/core/execution/inference.js.map +1 -1
  17. package/dist/core/providers/factory.d.ts +1 -0
  18. package/dist/core/providers/factory.d.ts.map +1 -1
  19. package/dist/core/providers/factory.js +5 -4
  20. package/dist/core/providers/factory.js.map +1 -1
  21. package/dist/core/providers/openrouter.d.ts +9 -2
  22. package/dist/core/providers/openrouter.d.ts.map +1 -1
  23. package/dist/core/providers/openrouter.js +66 -9
  24. package/dist/core/providers/openrouter.js.map +1 -1
  25. package/dist/core/providers/types.d.ts +9 -0
  26. package/dist/core/providers/types.d.ts.map +1 -1
  27. package/dist/core/skills/detector.d.ts +8 -0
  28. package/dist/core/skills/detector.d.ts.map +1 -0
  29. package/dist/core/skills/detector.js +60 -0
  30. package/dist/core/skills/detector.js.map +1 -0
  31. package/dist/core/skills/registry.d.ts +44 -0
  32. package/dist/core/skills/registry.d.ts.map +1 -0
  33. package/dist/core/skills/registry.js +149 -0
  34. package/dist/core/skills/registry.js.map +1 -0
  35. package/dist/core/tools/base.d.ts +14 -1
  36. package/dist/core/tools/base.d.ts.map +1 -1
  37. package/dist/core/tools/base.js.map +1 -1
  38. package/dist/core/tools/bash.js +1 -1
  39. package/dist/core/tools/bash.js.map +1 -1
  40. package/dist/core/tools/edit.js +1 -1
  41. package/dist/core/tools/edit.js.map +1 -1
  42. package/dist/core/tools/executor.d.ts +4 -1
  43. package/dist/core/tools/executor.d.ts.map +1 -1
  44. package/dist/core/tools/executor.js +8 -2
  45. package/dist/core/tools/executor.js.map +1 -1
  46. package/dist/core/tools/fetchPage.d.ts +1 -1
  47. package/dist/core/tools/fetchPage.d.ts.map +1 -1
  48. package/dist/core/tools/fetchPage.js +20 -4
  49. package/dist/core/tools/fetchPage.js.map +1 -1
  50. package/dist/core/tools/glob.js +1 -1
  51. package/dist/core/tools/glob.js.map +1 -1
  52. package/dist/core/tools/grep.js +1 -1
  53. package/dist/core/tools/grep.js.map +1 -1
  54. package/dist/core/tools/llmExecute.d.ts +41 -32
  55. package/dist/core/tools/llmExecute.d.ts.map +1 -1
  56. package/dist/core/tools/llmExecute.js +13 -0
  57. package/dist/core/tools/llmExecute.js.map +1 -1
  58. package/dist/core/tools/readEmail.d.ts +1 -1
  59. package/dist/core/tools/readEmail.d.ts.map +1 -1
  60. package/dist/core/tools/readEmail.js +6 -9
  61. package/dist/core/tools/readEmail.js.map +1 -1
  62. package/dist/core/tools/readFile.js +1 -1
  63. package/dist/core/tools/readFile.js.map +1 -1
  64. package/dist/core/tools/sendEmail.d.ts +7 -7
  65. package/dist/core/tools/sendEmail.d.ts.map +1 -1
  66. package/dist/core/tools/sendEmail.js +12 -17
  67. package/dist/core/tools/sendEmail.js.map +1 -1
  68. package/dist/core/tools/writeFile.d.ts +1 -1
  69. package/dist/core/tools/writeFile.js +1 -1
  70. package/dist/core/tools/writeFile.js.map +1 -1
  71. package/dist/core/workers/statsQueue.d.ts +69 -0
  72. package/dist/core/workers/statsQueue.d.ts.map +1 -0
  73. package/dist/core/workers/statsQueue.js +106 -0
  74. package/dist/core/workers/statsQueue.js.map +1 -0
  75. package/dist/core/workers/statsWorker.d.ts +11 -0
  76. package/dist/core/workers/statsWorker.d.ts.map +1 -0
  77. package/dist/core/workers/statsWorker.js +235 -0
  78. package/dist/core/workers/statsWorker.js.map +1 -0
  79. package/dist/db/client.d.ts +1 -1
  80. package/dist/db/client.d.ts.map +1 -1
  81. package/dist/db/client.js +21 -86
  82. package/dist/db/client.js.map +1 -1
  83. package/dist/index.d.ts +6 -3
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +71 -33
  86. package/dist/index.js.map +1 -1
  87. package/dist/services/agentsSeedData.d.ts.map +1 -1
  88. package/dist/services/agentsSeedData.js +14 -0
  89. package/dist/services/agentsSeedData.js.map +1 -1
  90. package/dist/services/initDB.d.ts +6 -0
  91. package/dist/services/initDB.d.ts.map +1 -1
  92. package/dist/services/initDB.js +2 -2
  93. package/dist/services/initDB.js.map +1 -1
  94. package/dist/test/setup.d.ts +1 -0
  95. package/dist/test/setup.d.ts.map +1 -0
  96. package/dist/test/setup.js +3 -0
  97. package/dist/test/setup.js.map +1 -0
  98. package/dist/types/client.d.ts +1 -0
  99. package/dist/types/client.d.ts.map +1 -1
  100. package/dist/types/config.d.ts +40 -7
  101. package/dist/types/config.d.ts.map +1 -1
  102. package/dist/types/config.js +69 -7
  103. package/dist/types/config.js.map +1 -1
  104. package/dist/types/dag.d.ts +44 -44
  105. package/dist/types/dag.js +7 -7
  106. package/dist/types/dag.js.map +1 -1
  107. package/dist/util/logger.d.ts +1 -8
  108. package/dist/util/logger.d.ts.map +1 -1
  109. package/dist/util/logger.js +11 -60
  110. package/dist/util/logger.js.map +1 -1
  111. package/dist/util/sendEmailTool.d.ts +7 -7
  112. package/dist/util/sendEmailTool.d.ts.map +1 -1
  113. package/dist/util/sendEmailTool.js +8 -0
  114. package/dist/util/sendEmailTool.js.map +1 -1
  115. package/package.json +2 -1
@@ -16,6 +16,7 @@ import { DecomposerJobSchema } from '../../types/dag.js';
16
16
  import { createLLMProvider } from '../providers/factory.js';
17
17
  import { LlmExecuteTool } from '../tools/llmExecute.js';
18
18
  import { DAGExecutor } from './dagExecutor.js';
19
+ import { MinimalSkillDetector } from '../skills/detector.js';
19
20
  export function generateDAGId() {
20
21
  return `dag_${nanoid(21)}`;
21
22
  }
@@ -32,6 +33,12 @@ export class DAGsService {
32
33
  agentsService;
33
34
  scheduler;
34
35
  artifactsDir;
36
+ staleExecutionMinutes;
37
+ apiKey;
38
+ ollamaBaseUrl;
39
+ skipGenerationStats;
40
+ skillRegistry;
41
+ statsQueue;
35
42
  logger = getLogger();
36
43
  constructor(deps) {
37
44
  this.db = deps.db;
@@ -39,7 +46,13 @@ export class DAGsService {
39
46
  this.toolRegistry = deps.toolRegistry;
40
47
  this.agentsService = deps.agentsService;
41
48
  this.scheduler = deps.scheduler;
42
- this.artifactsDir = deps.artifactsDir || process.env.ARTIFACTS_DIR || './artifacts';
49
+ this.artifactsDir = deps.artifactsDir;
50
+ this.staleExecutionMinutes = deps.staleExecutionMinutes ?? 5;
51
+ this.apiKey = deps.apiKey;
52
+ this.ollamaBaseUrl = deps.ollamaBaseUrl;
53
+ this.skipGenerationStats = deps.skipGenerationStats;
54
+ this.skillRegistry = deps.skillRegistry;
55
+ this.statsQueue = deps.statsQueue;
43
56
  }
44
57
  buildGlobalContext(job) {
45
58
  const entitiesStr = job.entities.length > 0
@@ -157,7 +170,7 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
157
170
  let activeLLMProvider;
158
171
  if (activeProvider && activeModel) {
159
172
  this.logger.info({ requestedProvider: activeProvider, requestedModel: activeModel }, 'Creating custom LLM provider');
160
- activeLLMProvider = createLLMProvider({ provider: activeProvider, model: activeModel });
173
+ activeLLMProvider = createLLMProvider({ provider: activeProvider, model: activeModel, apiKey: this.apiKey, baseUrl: this.ollamaBaseUrl, skipGenerationStats: this.skipGenerationStats });
161
174
  const validationResult = await activeLLMProvider.validateToolCallSupport(activeModel);
162
175
  if (!validationResult.supported) {
163
176
  this.logger.warn({ model: activeModel, reason: validationResult.message }, 'Model does not support tool calling');
@@ -167,10 +180,20 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
167
180
  activeLLMProvider = this.llmProvider;
168
181
  this.logger.debug('Using default LLM provider');
169
182
  }
183
+ // Detect relevant skills and build skills list for prompt injection
184
+ const skillsList = this.skillRegistry ? this.skillRegistry.getFormattedList() : '';
185
+ if (this.skillRegistry) {
186
+ const detector = new MinimalSkillDetector();
187
+ const detectedSkills = detector.detect(goalText, this.skillRegistry.getAll());
188
+ if (detectedSkills.length > 0) {
189
+ this.logger.info({ detectedSkills }, 'Skills detected for goal');
190
+ }
191
+ }
170
192
  const toolDefinitions = this.toolRegistry.getAllDefinitions();
171
193
  const systemPrompt = agent.systemPrompt
172
194
  .replace(/\{\{tools\}\}/g, JSON.stringify(toolDefinitions))
173
- .replace(/\{\{currentDate\}\}/g, new Date().toLocaleString());
195
+ .replace(/\{\{currentDate\}\}/g, new Date().toLocaleString())
196
+ .replace(/\{\{skills\}\}/g, skillsList);
174
197
  if (systemPrompt.length < 100) {
175
198
  this.logger.warn('System prompt is empty after replacement');
176
199
  throw new ValidationError('System prompt seems short! ', 'systemPrompt', systemPrompt);
@@ -195,10 +218,13 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
195
218
  temperature,
196
219
  maxTokens,
197
220
  abortSignal: options.abortSignal,
221
+ deferGenerationStats: true,
198
222
  });
199
223
  const attemptUsage = response.usage;
200
224
  const attemptCost = response.costUsd;
201
225
  const attemptGenStats = response.generationStats;
226
+ const attemptGenStatsPromise = response.generationStatsPromise;
227
+ const attemptGenerationId = response.generationId;
202
228
  if (attemptUsage) {
203
229
  planningUsageTotal.promptTokens += attemptUsage.promptTokens ?? 0;
204
230
  planningUsageTotal.completionTokens += attemptUsage.completionTokens ?? 0;
@@ -273,7 +299,6 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
273
299
  continue;
274
300
  }
275
301
  const usage = response.usage ?? null;
276
- const generationStats = response.generationStats ?? null;
277
302
  const validatedResult = DecomposerJobSchema.safeParse(result);
278
303
  if (!validatedResult.success) {
279
304
  planningAttempts.push({
@@ -343,13 +368,12 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
343
368
  const dagId = generateDAGId();
344
369
  const now = new Date();
345
370
  this.logger.info({ dagId }, 'DagID Generated for clarification');
346
- const titleMasterPromise = this.generateTitleAsync(activeLLMProvider, goalText, options.abortSignal);
347
371
  const baseInsertData = {
348
372
  id: dagId,
349
373
  status: 'pending',
350
374
  result: dag,
351
375
  usage: usage,
352
- generationStats: generationStats,
376
+ generationStats: null,
353
377
  attempts: attempt,
354
378
  agentName,
355
379
  dagTitle: null,
@@ -371,28 +395,6 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
371
395
  createdAt: now,
372
396
  updatedAt: now,
373
397
  };
374
- const titleResult = await titleMasterPromise;
375
- if (titleResult) {
376
- this.logger.info('TitleMaster generated Result for clarification');
377
- baseInsertData.dagTitle = titleResult.title;
378
- planningAttempts.push({
379
- attempt,
380
- reason: 'title_master',
381
- usage: titleResult.usage,
382
- costUsd: titleResult.costUsd,
383
- generationStats: titleResult.generationStats,
384
- });
385
- if (titleResult.usage) {
386
- planningUsageTotal.promptTokens += titleResult.usage.promptTokens ?? 0;
387
- planningUsageTotal.completionTokens += titleResult.usage.completionTokens ?? 0;
388
- planningUsageTotal.totalTokens += titleResult.usage.totalTokens ?? 0;
389
- }
390
- if (titleResult.costUsd != null) {
391
- planningCostTotal += titleResult.costUsd;
392
- }
393
- baseInsertData.planningTotalUsage = planningUsageTotal;
394
- baseInsertData.planningTotalCostUsd = planningCostTotal.toString();
395
- }
396
398
  try {
397
399
  await this.db.insert(dags).values(baseInsertData);
398
400
  }
@@ -402,6 +404,9 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
402
404
  throw new Error(`Database insert failed for clarification DAG ${dagId}: ${errorMessage}`);
403
405
  }
404
406
  this.logger.info({ dagId, agentName, clarificationQuery: dag.clarification_query }, 'Clarification DAG saved to database');
407
+ // Fire-and-forget: update title and generation stats in the background
408
+ const titleMasterPromise = this.generateTitleAsync(activeLLMProvider, goalText, options.abortSignal);
409
+ this.backgroundUpdateDag(dagId, attemptGenStatsPromise, attemptGenerationId, titleMasterPromise, [...planningAttempts], { ...planningUsageTotal }, planningCostTotal, attempt);
405
410
  return {
406
411
  status: 'clarification_required',
407
412
  dagId,
@@ -414,15 +419,12 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
414
419
  const dagId = generateDAGId();
415
420
  const now = new Date();
416
421
  this.logger.info({ dagId }, "DagID Generated");
417
- // Run TitleMaster generation in parallel with preparing insert data
418
- const titleMasterPromise = this.generateTitleAsync(activeLLMProvider, goalText, options.abortSignal);
419
- // Prepare base insert data (doesn't need title yet)
420
422
  const baseInsertData = {
421
423
  id: dagId,
422
424
  status: 'success',
423
425
  result: dag,
424
426
  usage: usage,
425
- generationStats: generationStats,
427
+ generationStats: null,
426
428
  attempts: attempt,
427
429
  agentName,
428
430
  dagTitle: null,
@@ -444,31 +446,6 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
444
446
  createdAt: now,
445
447
  updatedAt: now,
446
448
  };
447
- // Wait for title generation (runs in parallel with data prep above)
448
- const titleResult = await titleMasterPromise;
449
- if (titleResult) {
450
- this.logger.info('TitleMaster generated Result');
451
- baseInsertData.dagTitle = titleResult.title;
452
- planningAttempts.push({
453
- attempt,
454
- reason: 'title_master',
455
- usage: titleResult.usage,
456
- costUsd: titleResult.costUsd,
457
- generationStats: titleResult.generationStats,
458
- });
459
- if (titleResult.usage) {
460
- planningUsageTotal.promptTokens += titleResult.usage.promptTokens ?? 0;
461
- planningUsageTotal.completionTokens += titleResult.usage.completionTokens ?? 0;
462
- planningUsageTotal.totalTokens += titleResult.usage.totalTokens ?? 0;
463
- }
464
- if (titleResult.costUsd != null) {
465
- planningCostTotal += titleResult.costUsd;
466
- }
467
- // Update the totals in insert data
468
- baseInsertData.planningTotalUsage = planningUsageTotal;
469
- baseInsertData.planningTotalCostUsd = planningCostTotal.toString();
470
- }
471
- this.logger.info('Base insert data prepared');
472
449
  try {
473
450
  await this.db.insert(dags).values(baseInsertData);
474
451
  }
@@ -477,14 +454,7 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
477
454
  this.logger.error({ err: dbError, dagId, goalText: showGoalText }, 'Failed to insert DAG into database');
478
455
  throw new Error(`Database insert failed for DAG ${dagId}: ${errorMessage}`);
479
456
  }
480
- this.logger.info({
481
- dagId,
482
- agentName,
483
- goalText: showGoalText,
484
- cronSchedule,
485
- scheduleActive,
486
- planningCost: planningCostTotal,
487
- }, 'DAG saved to database');
457
+ this.logger.info({ dagId, agentName, goalText: showGoalText, cronSchedule, scheduleActive }, 'DAG saved to database');
488
458
  if (this.scheduler && cronSchedule && scheduleActive) {
489
459
  this.scheduler.registerDAGSchedule({
490
460
  id: dagId,
@@ -494,6 +464,9 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
494
464
  });
495
465
  this.logger.info({ dagId, cronSchedule, timezone }, 'DAG schedule registered');
496
466
  }
467
+ // Fire-and-forget: update title and generation stats in the background
468
+ const titleMasterPromise = this.generateTitleAsync(activeLLMProvider, goalText, options.abortSignal);
469
+ this.backgroundUpdateDag(dagId, attemptGenStatsPromise, attemptGenerationId, titleMasterPromise, [...planningAttempts], { ...planningUsageTotal }, planningCostTotal, attempt);
497
470
  return { status: 'success', dagId };
498
471
  }
499
472
  if (dag.validation.gaps && dag.validation.gaps.length > 0) {
@@ -508,13 +481,12 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
508
481
  const dagId = generateDAGId();
509
482
  const now = new Date();
510
483
  this.logger.info({ dagId }, 'DagID Generated for low-coverage DAG');
511
- const titleMasterPromise = this.generateTitleAsync(activeLLMProvider, goalText, options.abortSignal);
512
484
  const baseInsertData = {
513
485
  id: dagId,
514
486
  status: 'success',
515
487
  result: dag,
516
488
  usage: usage,
517
- generationStats: generationStats,
489
+ generationStats: null,
518
490
  attempts: attempt,
519
491
  agentName,
520
492
  dagTitle: null,
@@ -536,28 +508,6 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
536
508
  createdAt: now,
537
509
  updatedAt: now,
538
510
  };
539
- const titleResult = await titleMasterPromise;
540
- if (titleResult) {
541
- this.logger.info('TitleMaster generated Result for low-coverage DAG');
542
- baseInsertData.dagTitle = titleResult.title;
543
- planningAttempts.push({
544
- attempt,
545
- reason: 'title_master',
546
- usage: titleResult.usage,
547
- costUsd: titleResult.costUsd,
548
- generationStats: titleResult.generationStats,
549
- });
550
- if (titleResult.usage) {
551
- planningUsageTotal.promptTokens += titleResult.usage.promptTokens ?? 0;
552
- planningUsageTotal.completionTokens += titleResult.usage.completionTokens ?? 0;
553
- planningUsageTotal.totalTokens += titleResult.usage.totalTokens ?? 0;
554
- }
555
- if (titleResult.costUsd != null) {
556
- planningCostTotal += titleResult.costUsd;
557
- }
558
- baseInsertData.planningTotalUsage = planningUsageTotal;
559
- baseInsertData.planningTotalCostUsd = planningCostTotal.toString();
560
- }
561
511
  try {
562
512
  await this.db.insert(dags).values(baseInsertData);
563
513
  }
@@ -567,6 +517,9 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
567
517
  throw new Error(`Database insert failed for low-coverage DAG ${dagId}: ${errorMessage}`);
568
518
  }
569
519
  this.logger.info({ dagId, agentName }, 'Low-coverage DAG saved to database');
520
+ // Fire-and-forget: update title and generation stats in the background
521
+ const titleMasterPromise = this.generateTitleAsync(activeLLMProvider, goalText, options.abortSignal);
522
+ this.backgroundUpdateDag(dagId, attemptGenStatsPromise, attemptGenerationId, titleMasterPromise, [...planningAttempts], { ...planningUsageTotal }, planningCostTotal, attempt);
570
523
  return { status: 'success', dagId };
571
524
  }
572
525
  throw new ValidationError(`Failed to create DAG after ${maxAttempts} attempts`, 'attempts', maxAttempts);
@@ -597,7 +550,7 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
597
550
  this.logger.info({ dagId, originalGoalText: truncateForLog(originalGoalText) }, 'Resuming clarification DAG');
598
551
  const planningResult = await this.createFromGoal({
599
552
  goalText: augmentedGoalText,
600
- agentName: params?.agentName || existingDag.agentName || 'Decomposer',
553
+ agentName: params?.agentName || existingDag.agentName || process.env.DEFAULT_DECOMPOSER_AGENT || 'DecomposerV8',
601
554
  provider: params?.provider,
602
555
  model: params?.model,
603
556
  temperature: params?.temperature ?? 0.7,
@@ -694,6 +647,11 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
694
647
  llmProvider: this.llmProvider,
695
648
  toolRegistry: this.toolRegistry,
696
649
  artifactsDir: this.artifactsDir,
650
+ apiKey: this.apiKey,
651
+ ollamaBaseUrl: this.ollamaBaseUrl,
652
+ skipGenerationStats: this.skipGenerationStats,
653
+ skillRegistry: this.skillRegistry,
654
+ statsQueue: this.statsQueue,
697
655
  });
698
656
  // Start execution in background - don't await
699
657
  dagExecutor.execute(job, executionId, dagId, originalGoalText, _options?.executionConfig).catch((error) => {
@@ -711,7 +669,7 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
711
669
  }
712
670
  // For 'running' executions, only allow resume if they are stale (no recent update)
713
671
  if (execution.status === 'running') {
714
- const staleMinutes = parseInt(process.env.STALE_EXECUTION_MINUTES || '5', 10);
672
+ const staleMinutes = this.staleExecutionMinutes;
715
673
  const staleThreshold = new Date(Date.now() - staleMinutes * 60 * 1000);
716
674
  const lastActivity = execution.updatedAt || execution.startedAt;
717
675
  if (lastActivity && new Date(lastActivity) > staleThreshold) {
@@ -751,6 +709,11 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
751
709
  llmProvider: this.llmProvider,
752
710
  toolRegistry: this.toolRegistry,
753
711
  artifactsDir: this.artifactsDir,
712
+ apiKey: this.apiKey,
713
+ ollamaBaseUrl: this.ollamaBaseUrl,
714
+ skipGenerationStats: this.skipGenerationStats,
715
+ skillRegistry: this.skillRegistry,
716
+ statsQueue: this.statsQueue,
754
717
  });
755
718
  // Start execution in background - don't await
756
719
  dagExecutor.execute(job, executionId, execution.dagId, originalGoalText, executionConfig).catch((error) => {
@@ -810,7 +773,7 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
810
773
  taskResults.set(taskId, snapshot.result);
811
774
  }
812
775
  const globalContext = this.buildGlobalContext(job);
813
- const llmExecuteTool = new LlmExecuteTool();
776
+ const llmExecuteTool = new LlmExecuteTool({ apiKey: this.apiKey, baseUrl: this.ollamaBaseUrl, skipGenerationStats: this.skipGenerationStats });
814
777
  const overrideProvider = params?.provider;
815
778
  const overrideModel = params?.model;
816
779
  try {
@@ -865,6 +828,7 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
865
828
  runId: `redo-inference-${Date.now()}`,
866
829
  executionId,
867
830
  subStepId: pendingSubStepId,
831
+ artifactsDir: this.artifactsDir,
868
832
  });
869
833
  await this.db.update(dagSubSteps)
870
834
  .set({
@@ -1118,6 +1082,7 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
1118
1082
  temperature: 0.7,
1119
1083
  maxTokens: 100,
1120
1084
  abortSignal,
1085
+ deferGenerationStats: true,
1121
1086
  });
1122
1087
  const title = titleResponse.content.trim();
1123
1088
  this.logger.info({ dagTitle: title }, 'Generated DAG title from TitleMaster');
@@ -1133,6 +1098,120 @@ Respond with ONLY the expected output format. Build upon dependencies for cohere
1133
1098
  return null;
1134
1099
  }
1135
1100
  }
1101
+ /**
1102
+ * Fire-and-forget: resolves generation stats + title in the background and updates the DAG row.
1103
+ */
1104
+ backgroundUpdateDag(dagId, genStatsPromise, generationId, titlePromise, planningAttempts, planningUsageTotal, planningCostTotal, attempt) {
1105
+ // When statsQueue is available, offload generation stats to the background worker
1106
+ if (this.statsQueue && generationId) {
1107
+ this.statsQueue.enqueue({
1108
+ table: 'dags',
1109
+ id: dagId,
1110
+ generationId,
1111
+ attemptIndex: planningAttempts.length - 1,
1112
+ });
1113
+ // Still handle title in main thread (per requirement)
1114
+ const run = async () => {
1115
+ const updateData = {};
1116
+ try {
1117
+ const titleResult = await titlePromise;
1118
+ if (titleResult) {
1119
+ updateData.dagTitle = titleResult.title;
1120
+ planningAttempts.push({
1121
+ attempt,
1122
+ reason: 'title_master',
1123
+ usage: titleResult.usage,
1124
+ costUsd: titleResult.costUsd,
1125
+ generationStats: titleResult.generationStats,
1126
+ });
1127
+ if (titleResult.usage) {
1128
+ planningUsageTotal.promptTokens += titleResult.usage.promptTokens ?? 0;
1129
+ planningUsageTotal.completionTokens += titleResult.usage.completionTokens ?? 0;
1130
+ planningUsageTotal.totalTokens += titleResult.usage.totalTokens ?? 0;
1131
+ }
1132
+ if (titleResult.costUsd != null) {
1133
+ planningCostTotal += titleResult.costUsd;
1134
+ }
1135
+ }
1136
+ }
1137
+ catch (err) {
1138
+ this.logger.warn({ err, dagId }, 'Background: failed to generate title');
1139
+ }
1140
+ updateData.planningAttempts = planningAttempts;
1141
+ updateData.planningTotalUsage = planningUsageTotal;
1142
+ updateData.planningTotalCostUsd = planningCostTotal.toString();
1143
+ updateData.updatedAt = new Date();
1144
+ try {
1145
+ await this.db.update(dags).set(updateData).where(eq(dags.id, dagId));
1146
+ this.logger.info({ dagId, dagTitle: updateData.dagTitle }, 'Background: DAG updated with title (stats deferred to worker)');
1147
+ }
1148
+ catch (err) {
1149
+ this.logger.error({ err, dagId }, 'Background: failed to update DAG row');
1150
+ }
1151
+ };
1152
+ run().catch(err => this.logger.error({ err, dagId }, 'Background DAG update failed'));
1153
+ return;
1154
+ }
1155
+ // Fallback: original inline behavior when no statsQueue
1156
+ const run = async () => {
1157
+ const updateData = {};
1158
+ // Resolve main generation stats
1159
+ if (genStatsPromise) {
1160
+ try {
1161
+ const stats = await genStatsPromise;
1162
+ if (stats.generationStats) {
1163
+ updateData.generationStats = stats.generationStats;
1164
+ }
1165
+ if (stats.costUsd != null) {
1166
+ planningAttempts[planningAttempts.length - 1].costUsd = stats.costUsd;
1167
+ planningAttempts[planningAttempts.length - 1].generationStats = stats.generationStats;
1168
+ planningCostTotal += stats.costUsd;
1169
+ }
1170
+ }
1171
+ catch (err) {
1172
+ this.logger.warn({ err, dagId }, 'Background: failed to fetch generation stats');
1173
+ }
1174
+ }
1175
+ // Resolve title
1176
+ try {
1177
+ const titleResult = await titlePromise;
1178
+ if (titleResult) {
1179
+ updateData.dagTitle = titleResult.title;
1180
+ planningAttempts.push({
1181
+ attempt,
1182
+ reason: 'title_master',
1183
+ usage: titleResult.usage,
1184
+ costUsd: titleResult.costUsd,
1185
+ generationStats: titleResult.generationStats,
1186
+ });
1187
+ if (titleResult.usage) {
1188
+ planningUsageTotal.promptTokens += titleResult.usage.promptTokens ?? 0;
1189
+ planningUsageTotal.completionTokens += titleResult.usage.completionTokens ?? 0;
1190
+ planningUsageTotal.totalTokens += titleResult.usage.totalTokens ?? 0;
1191
+ }
1192
+ if (titleResult.costUsd != null) {
1193
+ planningCostTotal += titleResult.costUsd;
1194
+ }
1195
+ }
1196
+ }
1197
+ catch (err) {
1198
+ this.logger.warn({ err, dagId }, 'Background: failed to generate title');
1199
+ }
1200
+ // Update DB row
1201
+ updateData.planningAttempts = planningAttempts;
1202
+ updateData.planningTotalUsage = planningUsageTotal;
1203
+ updateData.planningTotalCostUsd = planningCostTotal.toString();
1204
+ updateData.updatedAt = new Date();
1205
+ try {
1206
+ await this.db.update(dags).set(updateData).where(eq(dags.id, dagId));
1207
+ this.logger.info({ dagId, dagTitle: updateData.dagTitle }, 'Background: DAG updated with title and stats');
1208
+ }
1209
+ catch (err) {
1210
+ this.logger.error({ err, dagId }, 'Background: failed to update DAG row');
1211
+ }
1212
+ };
1213
+ run().catch(err => this.logger.error({ err, dagId }, 'Background DAG update failed'));
1214
+ }
1136
1215
  mapDAG(record) {
1137
1216
  return {
1138
1217
  id: record.id,