@falai/agent 1.1.0 → 1.1.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 (85) hide show
  1. package/dist/adapters/MemoryAdapter.d.ts.map +1 -1
  2. package/dist/adapters/MemoryAdapter.js +2 -1
  3. package/dist/adapters/MemoryAdapter.js.map +1 -1
  4. package/dist/adapters/MongoAdapter.d.ts.map +1 -1
  5. package/dist/adapters/MongoAdapter.js +2 -1
  6. package/dist/adapters/MongoAdapter.js.map +1 -1
  7. package/dist/adapters/OpenSearchAdapter.d.ts.map +1 -1
  8. package/dist/adapters/OpenSearchAdapter.js +2 -1
  9. package/dist/adapters/OpenSearchAdapter.js.map +1 -1
  10. package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
  11. package/dist/adapters/PostgreSQLAdapter.js +2 -1
  12. package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
  13. package/dist/adapters/PrismaAdapter.d.ts.map +1 -1
  14. package/dist/adapters/PrismaAdapter.js +2 -1
  15. package/dist/adapters/PrismaAdapter.js.map +1 -1
  16. package/dist/adapters/RedisAdapter.d.ts.map +1 -1
  17. package/dist/adapters/RedisAdapter.js.map +1 -1
  18. package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
  19. package/dist/adapters/SQLiteAdapter.js.map +1 -1
  20. package/dist/cjs/adapters/MemoryAdapter.d.ts.map +1 -1
  21. package/dist/cjs/adapters/MemoryAdapter.js +2 -1
  22. package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
  23. package/dist/cjs/adapters/MongoAdapter.d.ts.map +1 -1
  24. package/dist/cjs/adapters/MongoAdapter.js +2 -1
  25. package/dist/cjs/adapters/MongoAdapter.js.map +1 -1
  26. package/dist/cjs/adapters/OpenSearchAdapter.d.ts.map +1 -1
  27. package/dist/cjs/adapters/OpenSearchAdapter.js +2 -1
  28. package/dist/cjs/adapters/OpenSearchAdapter.js.map +1 -1
  29. package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -1
  30. package/dist/cjs/adapters/PostgreSQLAdapter.js +2 -1
  31. package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
  32. package/dist/cjs/adapters/PrismaAdapter.d.ts.map +1 -1
  33. package/dist/cjs/adapters/PrismaAdapter.js +2 -1
  34. package/dist/cjs/adapters/PrismaAdapter.js.map +1 -1
  35. package/dist/cjs/adapters/RedisAdapter.d.ts.map +1 -1
  36. package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
  37. package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
  38. package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
  39. package/dist/cjs/core/BatchExecutor.d.ts.map +1 -1
  40. package/dist/cjs/core/BatchExecutor.js +1 -4
  41. package/dist/cjs/core/BatchExecutor.js.map +1 -1
  42. package/dist/cjs/core/SessionManager.d.ts.map +1 -1
  43. package/dist/cjs/core/SessionManager.js +4 -11
  44. package/dist/cjs/core/SessionManager.js.map +1 -1
  45. package/dist/cjs/index.d.ts +1 -1
  46. package/dist/cjs/index.d.ts.map +1 -1
  47. package/dist/cjs/index.js +3 -2
  48. package/dist/cjs/index.js.map +1 -1
  49. package/dist/cjs/utils/session.d.ts +6 -0
  50. package/dist/cjs/utils/session.d.ts.map +1 -1
  51. package/dist/cjs/utils/session.js +26 -8
  52. package/dist/cjs/utils/session.js.map +1 -1
  53. package/dist/core/BatchExecutor.d.ts.map +1 -1
  54. package/dist/core/BatchExecutor.js +2 -5
  55. package/dist/core/BatchExecutor.js.map +1 -1
  56. package/dist/core/SessionManager.d.ts.map +1 -1
  57. package/dist/core/SessionManager.js +4 -11
  58. package/dist/core/SessionManager.js.map +1 -1
  59. package/dist/index.d.ts +1 -1
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +1 -1
  62. package/dist/index.js.map +1 -1
  63. package/dist/utils/session.d.ts +6 -0
  64. package/dist/utils/session.d.ts.map +1 -1
  65. package/dist/utils/session.js +26 -8
  66. package/dist/utils/session.js.map +1 -1
  67. package/docs/api/README.md +31 -2
  68. package/docs/api/overview.md +8 -4
  69. package/docs/core/conversation-flows/data-collection.md +7 -1
  70. package/docs/core/conversation-flows/route-dsl.md +8 -1
  71. package/docs/core/persistence/session-storage.md +12 -0
  72. package/examples/integrations/database-integration.ts +10 -9
  73. package/examples/persistence/custom-adapter.ts +12 -15
  74. package/package.json +1 -1
  75. package/src/adapters/MemoryAdapter.ts +6 -8
  76. package/src/adapters/MongoAdapter.ts +6 -8
  77. package/src/adapters/OpenSearchAdapter.ts +6 -8
  78. package/src/adapters/PostgreSQLAdapter.ts +6 -8
  79. package/src/adapters/PrismaAdapter.ts +4 -6
  80. package/src/adapters/RedisAdapter.ts +4 -7
  81. package/src/adapters/SQLiteAdapter.ts +6 -9
  82. package/src/core/BatchExecutor.ts +124 -127
  83. package/src/core/SessionManager.ts +4 -13
  84. package/src/index.ts +11 -11
  85. package/src/utils/session.ts +48 -10
@@ -5,12 +5,12 @@
5
5
  * and orchestrating their execution with a single LLM call.
6
6
  */
7
7
 
8
- import type {
9
- BatchResult,
10
- StoppedReason,
11
- StepOptions,
12
- BatchExecutionError,
13
- BatchExecutionResult,
8
+ import type {
9
+ BatchResult,
10
+ StoppedReason,
11
+ StepOptions,
12
+ BatchExecutionError,
13
+ BatchExecutionResult,
14
14
  StepRef,
15
15
  BatchExecutionEvent,
16
16
  BatchExecutionEventListener,
@@ -22,7 +22,7 @@ import type { StructuredSchema } from '../types/schema';
22
22
  import { Step } from './Step';
23
23
  import { Route } from './Route';
24
24
  import { END_ROUTE_ID } from '../constants';
25
- import { logger, createTemplateContext, mergeCollected } from '../utils';
25
+ import { logger, createTemplateContext, mergeCollected, createSession } from '../utils';
26
26
 
27
27
  /**
28
28
  * Step configuration relevant for needs-input detection
@@ -201,7 +201,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
201
201
  details,
202
202
  };
203
203
  this.emitEvent(event);
204
-
204
+
205
205
  // Also log the event when debug mode is enabled
206
206
  logger.debug(`[BatchExecutor] Event: ${type}`, details);
207
207
  }
@@ -230,14 +230,14 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
230
230
  async determineBatch(params: DetermineBatchParams<TContext, TData>): Promise<BatchResult<TContext, TData>> {
231
231
  const { route, currentStep, sessionData, context, maxSteps = 1 } = params;
232
232
  const startTime = Date.now();
233
-
233
+
234
234
  const batchSteps: StepOptions<TContext, TData>[] = [];
235
235
  let stoppedReason: StoppedReason = 'route_complete';
236
236
  let stoppedAtStep: StepOptions<TContext, TData> | undefined;
237
-
237
+
238
238
  // Get all steps in the route for traversal
239
239
  const allSteps = route.getAllSteps();
240
-
240
+
241
241
  // Find starting position
242
242
  let startIndex = 0;
243
243
  if (currentStep) {
@@ -246,40 +246,37 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
246
246
  startIndex = currentIndex;
247
247
  }
248
248
  }
249
-
249
+
250
250
  // Log batch determination start (Requirement 11.1)
251
251
  logger.debug(`[BatchExecutor] Starting batch determination from step index ${startIndex}, total steps: ${allSteps.length}`);
252
-
252
+
253
253
  // Emit batch_start event (Requirement 11.3)
254
254
  this.emitBatchEvent('batch_start', {
255
255
  stepId: currentStep?.id,
256
256
  reason: `Starting batch determination from ${currentStep?.id || 'initial step'}`,
257
257
  batchSize: 0,
258
258
  });
259
-
259
+
260
260
  // Create template context for condition evaluation
261
261
  const templateContext = createTemplateContext<TContext, TData>({
262
262
  context,
263
263
  data: sessionData,
264
- session: {
265
- id: `batch-${Date.now()}`,
266
- data: sessionData
267
- } as SessionState<TData>,
264
+ session: createSession<TData>({ data: sessionData }),
268
265
  });
269
-
266
+
270
267
  // Walk through steps starting from current position
271
268
  for (let i = startIndex; i < allSteps.length; i++) {
272
269
  const step = allSteps[i];
273
270
  const stepOptions = step.toOptions();
274
-
271
+
275
272
  // Check for END_ROUTE (Requirement 2.2)
276
273
  if (step.id === END_ROUTE_ID) {
277
274
  stoppedReason = 'end_route';
278
275
  stoppedAtStep = stepOptions;
279
-
276
+
280
277
  // Log stopping reason (Requirement 11.2)
281
278
  logger.debug(`[BatchExecutor] Reached END_ROUTE, stopping batch`);
282
-
279
+
283
280
  // Emit batch_stop event (Requirement 11.3)
284
281
  this.emitBatchEvent('batch_stop', {
285
282
  stepId: step.id,
@@ -289,14 +286,14 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
289
286
  });
290
287
  break;
291
288
  }
292
-
289
+
293
290
  // Evaluate skipIf condition (Requirements 7.1, 7.2, 7.3)
294
291
  let shouldSkip = false;
295
292
  if (step.skipIf) {
296
293
  try {
297
294
  const skipResult = await step.evaluateSkipIf(templateContext);
298
295
  shouldSkip = skipResult.shouldSkip;
299
-
296
+
300
297
  // Log skipIf evaluation (Requirement 11.1)
301
298
  logger.debug(`[BatchExecutor] Step ${step.id} skipIf evaluated to: ${shouldSkip}`);
302
299
  } catch (error) {
@@ -305,12 +302,12 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
305
302
  shouldSkip = false;
306
303
  }
307
304
  }
308
-
305
+
309
306
  // If skipIf is true, skip this step and continue (Requirement 7.2)
310
307
  if (shouldSkip) {
311
308
  // Log step skip (Requirement 11.1)
312
309
  logger.debug(`[BatchExecutor] Skipping step ${step.id} due to skipIf condition`);
313
-
310
+
314
311
  // Emit step_skipped event (Requirement 11.3)
315
312
  this.emitBatchEvent('step_skipped', {
316
313
  stepId: step.id,
@@ -318,15 +315,15 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
318
315
  });
319
316
  continue;
320
317
  }
321
-
318
+
322
319
  // Evaluate needsInput (Requirements 1.2, 1.3)
323
320
  const stepNeedsInput = needsInput(step, sessionData);
324
-
321
+
325
322
  if (stepNeedsInput) {
326
323
  // Requirement 1.5: Stop when a step needs input
327
324
  stoppedReason = 'needs_input';
328
325
  stoppedAtStep = stepOptions;
329
-
326
+
330
327
  // Log stopping reason with details (Requirement 11.1, 11.2)
331
328
  const missingRequires = step.requires?.filter(
332
329
  field => (sessionData as Record<string, unknown>)[String(field)] === undefined
@@ -343,7 +340,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
343
340
  }
344
341
 
345
342
  logger.debug(`[BatchExecutor] Step ${step.id} needs input, stopping batch. Missing requires: [${missingRequires.join(', ')}], Collect fields: [${collectFields.join(', ')}]`);
346
-
343
+
347
344
  // Emit batch_stop event (Requirement 11.3)
348
345
  this.emitBatchEvent('batch_stop', {
349
346
  stepId: step.id,
@@ -353,26 +350,26 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
353
350
  });
354
351
  break;
355
352
  }
356
-
353
+
357
354
  // Requirement 1.4: Step doesn't need input, include in batch
358
355
  batchSteps.push(stepOptions);
359
-
356
+
360
357
  // Log step inclusion with reason (Requirement 11.1)
361
358
  logger.debug(`[BatchExecutor] Including step ${step.id} in batch (all requirements satisfied)`);
362
-
359
+
363
360
  // Emit step_included event (Requirement 11.3)
364
361
  this.emitBatchEvent('step_included', {
365
362
  stepId: step.id,
366
363
  reason: 'All requirements satisfied, no input needed',
367
364
  batchSize: batchSteps.length,
368
365
  });
369
-
366
+
370
367
  // Check if we've reached the max steps limit
371
368
  if (batchSteps.length >= maxSteps) {
372
369
  stoppedReason = 'max_steps_reached';
373
-
370
+
374
371
  logger.debug(`[BatchExecutor] Reached maxStepsPerBatch limit (${maxSteps}), stopping batch`);
375
-
372
+
376
373
  this.emitBatchEvent('batch_stop', {
377
374
  stepId: step.id,
378
375
  reason: `Reached maxStepsPerBatch limit (${maxSteps})`,
@@ -381,16 +378,16 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
381
378
  });
382
379
  break;
383
380
  }
384
-
381
+
385
382
  // Move to next step in the sequence
386
383
  const transitions = step.getTransitions();
387
384
  if (transitions.length === 0) {
388
385
  // No more transitions, route is complete
389
386
  stoppedReason = 'route_complete';
390
-
387
+
391
388
  // Log stopping reason (Requirement 11.2)
392
389
  logger.debug(`[BatchExecutor] No more transitions from step ${step.id}, route complete`);
393
-
390
+
394
391
  // Emit batch_stop event (Requirement 11.3)
395
392
  this.emitBatchEvent('batch_stop', {
396
393
  stepId: step.id,
@@ -400,7 +397,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
400
397
  });
401
398
  break;
402
399
  }
403
-
400
+
404
401
  // For linear routes, follow the first transition
405
402
  // For branching routes, we'd need more complex logic
406
403
  const nextStep = transitions[0];
@@ -414,10 +411,10 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
414
411
  // Next step is END_ROUTE
415
412
  stoppedReason = 'end_route';
416
413
  stoppedAtStep = nextStep.toOptions();
417
-
414
+
418
415
  // Log stopping reason (Requirement 11.2)
419
416
  logger.debug(`[BatchExecutor] Next step is END_ROUTE, stopping batch`);
420
-
417
+
421
418
  // Emit batch_stop event (Requirement 11.3)
422
419
  this.emitBatchEvent('batch_stop', {
423
420
  stepId: nextStep.id,
@@ -429,11 +426,11 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
429
426
  }
430
427
  }
431
428
  }
432
-
429
+
433
430
  // Log batch determination complete with timing (Requirement 11.1, 11.2)
434
431
  const determinationTime = Date.now() - startTime;
435
432
  logger.debug(`[BatchExecutor] Batch determination complete. Steps: ${batchSteps.length}, Stopped reason: ${stoppedReason}, Time: ${determinationTime}ms`);
436
-
433
+
437
434
  return {
438
435
  steps: batchSteps,
439
436
  stoppedReason,
@@ -455,13 +452,13 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
455
452
  async executePrepareHooks(params: ExecuteHooksParams<TContext, TData>): Promise<HookExecutionResult> {
456
453
  const { steps, context, data, executeHook } = params;
457
454
  const executedSteps: string[] = [];
458
-
455
+
459
456
  logger.debug(`[BatchExecutor] Executing prepare hooks for ${steps.length} steps`);
460
-
457
+
461
458
  for (const step of steps) {
462
459
  if (step.prepare) {
463
460
  logger.debug(`[BatchExecutor] Executing prepare hook for step: ${step.id}`);
464
-
461
+
465
462
  try {
466
463
  await executeHook(step.prepare, context, data, step);
467
464
  executedSteps.push(step.id || 'unknown');
@@ -470,7 +467,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
470
467
  // Requirement 5.4: Stop on prepare hook failure
471
468
  const errorMessage = error instanceof Error ? error.message : String(error);
472
469
  logger.error(`[BatchExecutor] Prepare hook failed for step ${step.id}: ${errorMessage}`);
473
-
470
+
474
471
  return {
475
472
  success: false,
476
473
  executedSteps,
@@ -484,7 +481,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
484
481
  }
485
482
  }
486
483
  }
487
-
484
+
488
485
  logger.debug(`[BatchExecutor] All prepare hooks completed successfully`);
489
486
  return {
490
487
  success: true,
@@ -508,13 +505,13 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
508
505
  const { steps, context, data, executeHook } = params;
509
506
  const executedSteps: string[] = [];
510
507
  const errors: Array<{ stepId: string; error: BatchExecutionError }> = [];
511
-
508
+
512
509
  logger.debug(`[BatchExecutor] Executing finalize hooks for ${steps.length} steps`);
513
-
510
+
514
511
  for (const step of steps) {
515
512
  if (step.finalize) {
516
513
  logger.debug(`[BatchExecutor] Executing finalize hook for step: ${step.id}`);
517
-
514
+
518
515
  try {
519
516
  await executeHook(step.finalize, context, data, step);
520
517
  executedSteps.push(step.id || 'unknown');
@@ -523,7 +520,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
523
520
  // Requirement 5.5: Log error and continue with remaining hooks
524
521
  const errorMessage = error instanceof Error ? error.message : String(error);
525
522
  logger.error(`[BatchExecutor] Finalize hook failed for step ${step.id}: ${errorMessage}`);
526
-
523
+
527
524
  errors.push({
528
525
  stepId: step.id || 'unknown',
529
526
  error: {
@@ -533,18 +530,18 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
533
530
  details: error,
534
531
  },
535
532
  });
536
-
533
+
537
534
  // Continue to next step despite error
538
535
  }
539
536
  }
540
537
  }
541
-
538
+
542
539
  if (errors.length > 0) {
543
540
  logger.warn(`[BatchExecutor] ${errors.length} finalize hook(s) failed, but execution continued`);
544
541
  } else {
545
542
  logger.debug(`[BatchExecutor] All finalize hooks completed successfully`);
546
543
  }
547
-
544
+
548
545
  // Always return success for finalize hooks (errors are logged but don't stop execution)
549
546
  return {
550
547
  success: true,
@@ -592,36 +589,36 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
592
589
  * **Validates: Requirements 9.1, 9.2, 9.3, 2.4**
593
590
  */
594
591
  async executeBatch(params: ExecuteBatchParams<TContext, TData>): Promise<BatchExecutionResult<TData>> {
595
- const {
596
- batch,
597
- session: initialSession,
598
- context,
599
- executeHook,
600
- generateMessage,
592
+ const {
593
+ batch,
594
+ session: initialSession,
595
+ context,
596
+ executeHook,
597
+ generateMessage,
601
598
  schema,
602
599
  routeId,
603
600
  } = params;
604
-
601
+
605
602
  // Track timing for each phase (Requirement 11.1)
606
603
  const timing: BatchExecutionTiming = {
607
604
  totalMs: 0,
608
605
  };
609
606
  const batchStartTime = Date.now();
610
-
607
+
611
608
  // Track the last successful session state for error recovery
612
609
  let lastSuccessfulSession = initialSession;
613
610
  let currentSession = initialSession;
614
-
611
+
615
612
  // Track executed steps for the response
616
613
  const executedSteps: StepRef[] = [];
617
-
614
+
618
615
  // Log batch execution start with details (Requirement 11.1)
619
616
  logger.debug(`[BatchExecutor] Starting batch execution with ${batch.steps.length} steps, route: ${routeId || 'unknown'}`);
620
-
617
+
621
618
  // If batch is empty, return early with appropriate reason
622
619
  if (batch.steps.length === 0) {
623
620
  logger.debug(`[BatchExecutor] Empty batch, returning with stopped reason: ${batch.stoppedReason}`);
624
-
621
+
625
622
  // Emit batch_complete event for empty batch (Requirement 11.3)
626
623
  timing.totalMs = Date.now() - batchStartTime;
627
624
  this.emitBatchEvent('batch_complete', {
@@ -630,7 +627,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
630
627
  reason: 'Empty batch',
631
628
  timing,
632
629
  });
633
-
630
+
634
631
  return {
635
632
  message: '',
636
633
  session: currentSession,
@@ -639,26 +636,26 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
639
636
  collectedData: {},
640
637
  };
641
638
  }
642
-
639
+
643
640
  // PHASE 1: Execute prepare hooks (Requirement 5.4 - stop on failure)
644
641
  const prepareStartTime = Date.now();
645
642
  logger.debug(`[BatchExecutor] Phase 1: Executing prepare hooks`);
646
-
643
+
647
644
  const prepareResult = await this.executePrepareHooks({
648
645
  steps: batch.steps,
649
646
  context,
650
647
  data: currentSession.data,
651
648
  executeHook,
652
649
  });
653
-
650
+
654
651
  timing.prepareHooksMs = Date.now() - prepareStartTime;
655
652
  logger.debug(`[BatchExecutor] Prepare hooks completed in ${timing.prepareHooksMs}ms`);
656
-
653
+
657
654
  if (!prepareResult.success) {
658
655
  // Requirement 9.3: Preserve partial progress on errors
659
656
  // Requirement 2.4: Stop and include error information
660
657
  logger.error(`[BatchExecutor] Prepare hook failed:`, prepareResult.error);
661
-
658
+
662
659
  // Emit batch_complete event with error (Requirement 11.3)
663
660
  timing.totalMs = Date.now() - batchStartTime;
664
661
  this.emitBatchEvent('batch_complete', {
@@ -667,7 +664,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
667
664
  reason: `Prepare hook failed: ${prepareResult.error?.message}`,
668
665
  timing,
669
666
  });
670
-
667
+
671
668
  return {
672
669
  message: '',
673
670
  session: lastSuccessfulSession, // Return last successful state
@@ -679,22 +676,22 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
679
676
  error: prepareResult.error,
680
677
  };
681
678
  }
682
-
679
+
683
680
  // Update last successful session after prepare hooks complete
684
681
  lastSuccessfulSession = currentSession;
685
-
682
+
686
683
  // PHASE 2: Make LLM call (Requirement 9.1 - preserve session state on failure)
687
684
  const llmStartTime = Date.now();
688
685
  logger.debug(`[BatchExecutor] Phase 2: Making LLM call`);
689
-
686
+
690
687
  let llmResponse: Record<string, unknown>;
691
688
  let message: string;
692
-
689
+
693
690
  try {
694
691
  const result = await generateMessage();
695
692
  llmResponse = result.structured || {};
696
693
  message = result.message || '';
697
-
694
+
698
695
  timing.llmCallMs = Date.now() - llmStartTime;
699
696
  logger.debug(`[BatchExecutor] LLM call successful in ${timing.llmCallMs}ms`);
700
697
  } catch (error) {
@@ -702,7 +699,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
702
699
  const errorMessage = error instanceof Error ? error.message : String(error);
703
700
  timing.llmCallMs = Date.now() - llmStartTime;
704
701
  logger.error(`[BatchExecutor] LLM call failed after ${timing.llmCallMs}ms:`, errorMessage);
705
-
702
+
706
703
  // Emit batch_complete event with error (Requirement 11.3)
707
704
  timing.totalMs = Date.now() - batchStartTime;
708
705
  this.emitBatchEvent('batch_complete', {
@@ -711,7 +708,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
711
708
  reason: `LLM call failed: ${errorMessage}`,
712
709
  timing,
713
710
  });
714
-
711
+
715
712
  return {
716
713
  message: '',
717
714
  session: lastSuccessfulSession, // Preserve session state
@@ -724,50 +721,50 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
724
721
  },
725
722
  };
726
723
  }
727
-
724
+
728
725
  // Update last successful session after LLM call
729
726
  lastSuccessfulSession = currentSession;
730
-
727
+
731
728
  // PHASE 3: Collect and validate data (Requirement 9.2 - include validation errors)
732
729
  const collectStartTime = Date.now();
733
730
  logger.debug(`[BatchExecutor] Phase 3: Collecting and validating data`);
734
-
731
+
735
732
  const collectResult = this.collectBatchData({
736
733
  steps: batch.steps,
737
734
  llmResponse,
738
735
  session: currentSession,
739
736
  schema,
740
737
  });
741
-
738
+
742
739
  timing.dataCollectionMs = Date.now() - collectStartTime;
743
740
  logger.debug(`[BatchExecutor] Data collection completed in ${timing.dataCollectionMs}ms`);
744
-
741
+
745
742
  // Update session with collected data (even if validation failed)
746
743
  currentSession = collectResult.session;
747
-
744
+
748
745
  // Track collected data for response
749
746
  const collectedData = collectResult.collectedData;
750
-
747
+
751
748
  // Check for validation errors
752
749
  let validationError: BatchExecutionError | undefined;
753
750
  if (!collectResult.success && collectResult.validationErrors && collectResult.validationErrors.length > 0) {
754
751
  // Requirement 9.2: Include validation errors in response
755
752
  logger.warn(`[BatchExecutor] Data validation failed:`, collectResult.validationErrors);
756
-
753
+
757
754
  validationError = {
758
755
  type: 'data_validation',
759
756
  message: `Validation failed for ${collectResult.validationErrors.length} field(s): ${collectResult.validationErrors.map(e => e.field).join(', ')}`,
760
757
  details: collectResult.validationErrors,
761
758
  };
762
-
759
+
763
760
  // Note: We continue execution despite validation errors
764
761
  // The error is included in the response for the caller to handle
765
762
  }
766
-
763
+
767
764
  // Update last successful session after data collection
768
765
  // (even with validation errors, we preserve the collected data)
769
766
  lastSuccessfulSession = currentSession;
770
-
767
+
771
768
  // Build executed steps list
772
769
  for (const step of batch.steps) {
773
770
  if (step.id) {
@@ -777,26 +774,26 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
777
774
  });
778
775
  }
779
776
  }
780
-
777
+
781
778
  // PHASE 4: Execute finalize hooks (Requirement 5.5 - continue on failure)
782
779
  const finalizeStartTime = Date.now();
783
780
  logger.debug(`[BatchExecutor] Phase 4: Executing finalize hooks`);
784
-
781
+
785
782
  const finalizeResult = await this.executeFinalizeHooks({
786
783
  steps: batch.steps,
787
784
  context,
788
785
  data: currentSession.data,
789
786
  executeHook,
790
787
  });
791
-
788
+
792
789
  timing.finalizeHooksMs = Date.now() - finalizeStartTime;
793
790
  logger.debug(`[BatchExecutor] Finalize hooks completed in ${timing.finalizeHooksMs}ms`);
794
-
791
+
795
792
  // Log finalize errors but don't fail the batch
796
793
  let finalizeError: BatchExecutionError | undefined;
797
794
  if (finalizeResult.errors && finalizeResult.errors.length > 0) {
798
795
  logger.warn(`[BatchExecutor] Some finalize hooks failed:`, finalizeResult.errors);
799
-
796
+
800
797
  // Create a summary error for finalize failures
801
798
  finalizeError = {
802
799
  type: 'finalize_hook',
@@ -804,12 +801,12 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
804
801
  details: finalizeResult.errors,
805
802
  };
806
803
  }
807
-
804
+
808
805
  // Determine the final stopped reason
809
806
  // Priority: validation_error > finalize_error > batch.stoppedReason
810
807
  let finalStoppedReason: StoppedReason = batch.stoppedReason;
811
808
  let finalError: BatchExecutionError | undefined;
812
-
809
+
813
810
  if (validationError) {
814
811
  finalStoppedReason = 'validation_error';
815
812
  finalError = validationError;
@@ -818,11 +815,11 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
818
815
  // but include the error in the response
819
816
  finalError = finalizeError;
820
817
  }
821
-
818
+
822
819
  // Calculate total time and log completion (Requirement 11.1, 11.2)
823
820
  timing.totalMs = Date.now() - batchStartTime;
824
821
  logger.debug(`[BatchExecutor] Batch execution complete. Stopped reason: ${finalStoppedReason}, Executed steps: ${executedSteps.length}, Total time: ${timing.totalMs}ms`);
825
-
822
+
826
823
  // Emit batch_complete event (Requirement 11.3)
827
824
  this.emitBatchEvent('batch_complete', {
828
825
  batchSize: executedSteps.length,
@@ -830,7 +827,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
830
827
  reason: `Batch completed with ${executedSteps.length} steps`,
831
828
  timing,
832
829
  });
833
-
830
+
834
831
  return {
835
832
  message,
836
833
  session: currentSession,
@@ -857,9 +854,9 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
857
854
  */
858
855
  collectBatchData(params: CollectBatchDataParams<TData>): CollectBatchDataResult<TData> {
859
856
  const { steps, llmResponse, session, schema } = params;
860
-
857
+
861
858
  logger.debug(`[BatchExecutor] Collecting batch data from ${steps.length} steps`);
862
-
859
+
863
860
  // Requirement 6.1: Gather all collect fields from all steps in the batch
864
861
  const allCollectFields = new Set<string>();
865
862
  for (const step of steps) {
@@ -869,9 +866,9 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
869
866
  }
870
867
  }
871
868
  }
872
-
869
+
873
870
  logger.debug(`[BatchExecutor] Collect fields to extract: ${Array.from(allCollectFields).join(', ')}`);
874
-
871
+
875
872
  // If no fields to collect, return early with unchanged session
876
873
  if (allCollectFields.size === 0) {
877
874
  logger.debug(`[BatchExecutor] No collect fields defined, skipping data collection`);
@@ -882,12 +879,12 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
882
879
  fieldsCollected: [],
883
880
  };
884
881
  }
885
-
882
+
886
883
  // Extract data from LLM response for all collect fields
887
884
  const collectedData: Partial<TData> = {};
888
885
  const fieldsCollected: string[] = [];
889
886
  const fieldsMissing: string[] = [];
890
-
887
+
891
888
  for (const field of allCollectFields) {
892
889
  // Check if the field exists in the LLM response
893
890
  if (field in llmResponse && llmResponse[field] !== undefined) {
@@ -899,20 +896,20 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
899
896
  logger.debug(`[BatchExecutor] Field '${field}' not found in LLM response`);
900
897
  }
901
898
  }
902
-
899
+
903
900
  // Requirement 6.2: Validate collected data against the agent schema
904
901
  const validationErrors: ValidationError[] = [];
905
-
902
+
906
903
  if (schema && Object.keys(collectedData).length > 0) {
907
904
  logger.debug(`[BatchExecutor] Validating collected data against schema`);
908
-
905
+
909
906
  const validationResult = this.validateAgainstSchema(collectedData, schema);
910
907
  if (!validationResult.valid) {
911
908
  validationErrors.push(...validationResult.errors);
912
909
  logger.warn(`[BatchExecutor] Schema validation found ${validationErrors.length} error(s)`);
913
910
  }
914
911
  }
915
-
912
+
916
913
  // Requirement 6.3: Update session data with all collected values
917
914
  // Only merge valid data (data that passed validation or had no schema to validate against)
918
915
  let updatedSession = session;
@@ -922,11 +919,11 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
922
919
  updatedSession = mergeCollected(session, collectedData);
923
920
  logger.debug(`[BatchExecutor] Updated session with collected data`);
924
921
  }
925
-
922
+
926
923
  const success = validationErrors.length === 0;
927
-
924
+
928
925
  logger.debug(`[BatchExecutor] Data collection complete. Success: ${success}, Fields collected: ${fieldsCollected.length}, Fields missing: ${fieldsMissing.length}`);
929
-
926
+
930
927
  return {
931
928
  success,
932
929
  collectedData,
@@ -956,7 +953,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
956
953
  schema: StructuredSchema
957
954
  ): { valid: boolean; errors: ValidationError[] } {
958
955
  const errors: ValidationError[] = [];
959
-
956
+
960
957
  // Check if provided fields exist in schema
961
958
  if (schema.properties) {
962
959
  for (const [key, value] of Object.entries(data)) {
@@ -977,7 +974,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
977
974
  }
978
975
  }
979
976
  }
980
-
977
+
981
978
  return {
982
979
  valid: errors.length === 0,
983
980
  errors,
@@ -997,18 +994,18 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
997
994
  // Null/undefined values are handled separately (required field check)
998
995
  return null;
999
996
  }
1000
-
997
+
1001
998
  const expectedType = fieldSchema.type;
1002
999
  if (!expectedType) {
1003
1000
  // No type specified, consider valid
1004
1001
  return null;
1005
1002
  }
1006
-
1003
+
1007
1004
  const actualType = Array.isArray(value) ? 'array' : typeof value;
1008
-
1005
+
1009
1006
  // Handle array of types
1010
1007
  const allowedTypes = Array.isArray(expectedType) ? expectedType : [expectedType];
1011
-
1008
+
1012
1009
  // Map JavaScript types to JSON Schema types
1013
1010
  const typeMapping: Record<string, string> = {
1014
1011
  'string': 'string',
@@ -1017,9 +1014,9 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
1017
1014
  'object': 'object',
1018
1015
  'array': 'array',
1019
1016
  };
1020
-
1017
+
1021
1018
  const mappedActualType = typeMapping[actualType] || actualType;
1022
-
1019
+
1023
1020
  // Check if actual type matches any allowed type
1024
1021
  // Also handle 'integer' as a valid number type
1025
1022
  const isValidType = allowedTypes.some(t => {
@@ -1028,7 +1025,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
1028
1025
  }
1029
1026
  return t === mappedActualType;
1030
1027
  });
1031
-
1028
+
1032
1029
  if (!isValidType) {
1033
1030
  return {
1034
1031
  field,
@@ -1037,7 +1034,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
1037
1034
  schemaPath: `properties.${field}.type`,
1038
1035
  };
1039
1036
  }
1040
-
1037
+
1041
1038
  return null;
1042
1039
  }
1043
1040
  }
@@ -1068,7 +1065,7 @@ export interface ExecuteHooksParams<TContext, TData> {
1068
1065
  /**
1069
1066
  * Type for the hook execution function
1070
1067
  */
1071
- export type HookFunction<TContext, TData> =
1068
+ export type HookFunction<TContext, TData> =
1072
1069
  | string
1073
1070
  | Tool<TContext, TData>
1074
1071
  | ((context: TContext, data?: Partial<TData>) => void | Promise<void>);