@falai/agent 1.1.0 → 1.1.2

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 (122) 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/Agent.d.ts +202 -67
  40. package/dist/cjs/core/Agent.d.ts.map +1 -1
  41. package/dist/cjs/core/Agent.js +366 -158
  42. package/dist/cjs/core/Agent.js.map +1 -1
  43. package/dist/cjs/core/BatchExecutor.js +5 -6
  44. package/dist/cjs/core/BatchExecutor.js.map +1 -1
  45. package/dist/cjs/core/BatchPromptBuilder.d.ts.map +1 -1
  46. package/dist/cjs/core/BatchPromptBuilder.js +5 -2
  47. package/dist/cjs/core/BatchPromptBuilder.js.map +1 -1
  48. package/dist/cjs/core/ResponseEngine.d.ts.map +1 -1
  49. package/dist/cjs/core/ResponseEngine.js +6 -3
  50. package/dist/cjs/core/ResponseEngine.js.map +1 -1
  51. package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
  52. package/dist/cjs/core/ResponseModal.js +18 -17
  53. package/dist/cjs/core/ResponseModal.js.map +1 -1
  54. package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
  55. package/dist/cjs/core/RoutingEngine.js +8 -73
  56. package/dist/cjs/core/RoutingEngine.js.map +1 -1
  57. package/dist/cjs/core/SessionManager.d.ts.map +1 -1
  58. package/dist/cjs/core/SessionManager.js +4 -11
  59. package/dist/cjs/core/SessionManager.js.map +1 -1
  60. package/dist/cjs/index.d.ts +1 -1
  61. package/dist/cjs/index.d.ts.map +1 -1
  62. package/dist/cjs/index.js +3 -2
  63. package/dist/cjs/index.js.map +1 -1
  64. package/dist/cjs/utils/session.d.ts +6 -0
  65. package/dist/cjs/utils/session.d.ts.map +1 -1
  66. package/dist/cjs/utils/session.js +26 -8
  67. package/dist/cjs/utils/session.js.map +1 -1
  68. package/dist/core/Agent.d.ts +202 -67
  69. package/dist/core/Agent.d.ts.map +1 -1
  70. package/dist/core/Agent.js +366 -158
  71. package/dist/core/Agent.js.map +1 -1
  72. package/dist/core/BatchExecutor.js +6 -7
  73. package/dist/core/BatchExecutor.js.map +1 -1
  74. package/dist/core/BatchPromptBuilder.d.ts.map +1 -1
  75. package/dist/core/BatchPromptBuilder.js +5 -2
  76. package/dist/core/BatchPromptBuilder.js.map +1 -1
  77. package/dist/core/ResponseEngine.d.ts.map +1 -1
  78. package/dist/core/ResponseEngine.js +6 -3
  79. package/dist/core/ResponseEngine.js.map +1 -1
  80. package/dist/core/ResponseModal.d.ts.map +1 -1
  81. package/dist/core/ResponseModal.js +18 -17
  82. package/dist/core/ResponseModal.js.map +1 -1
  83. package/dist/core/RoutingEngine.d.ts.map +1 -1
  84. package/dist/core/RoutingEngine.js +8 -73
  85. package/dist/core/RoutingEngine.js.map +1 -1
  86. package/dist/core/SessionManager.d.ts.map +1 -1
  87. package/dist/core/SessionManager.js +4 -11
  88. package/dist/core/SessionManager.js.map +1 -1
  89. package/dist/index.d.ts +1 -1
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +1 -1
  92. package/dist/index.js.map +1 -1
  93. package/dist/utils/session.d.ts +6 -0
  94. package/dist/utils/session.d.ts.map +1 -1
  95. package/dist/utils/session.js +26 -8
  96. package/dist/utils/session.js.map +1 -1
  97. package/docs/api/README.md +33 -4
  98. package/docs/api/overview.md +9 -5
  99. package/docs/architecture/data-extraction-flow.md +17 -19
  100. package/docs/core/conversation-flows/data-collection.md +9 -3
  101. package/docs/core/conversation-flows/route-dsl.md +8 -1
  102. package/docs/core/error-handling.md +3 -4
  103. package/docs/core/persistence/session-storage.md +12 -0
  104. package/examples/integrations/database-integration.ts +10 -9
  105. package/examples/persistence/custom-adapter.ts +12 -15
  106. package/package.json +2 -2
  107. package/src/adapters/MemoryAdapter.ts +6 -8
  108. package/src/adapters/MongoAdapter.ts +6 -8
  109. package/src/adapters/OpenSearchAdapter.ts +6 -8
  110. package/src/adapters/PostgreSQLAdapter.ts +6 -8
  111. package/src/adapters/PrismaAdapter.ts +4 -6
  112. package/src/adapters/RedisAdapter.ts +4 -7
  113. package/src/adapters/SQLiteAdapter.ts +6 -9
  114. package/src/core/Agent.ts +427 -195
  115. package/src/core/BatchExecutor.ts +129 -129
  116. package/src/core/BatchPromptBuilder.ts +41 -38
  117. package/src/core/ResponseEngine.ts +56 -53
  118. package/src/core/ResponseModal.ts +79 -81
  119. package/src/core/RoutingEngine.ts +67 -149
  120. package/src/core/SessionManager.ts +4 -13
  121. package/src/index.ts +11 -11
  122. 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
@@ -338,12 +335,15 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
338
335
  const warning = `[Agent] Step "${step.description || step.id}" requires data [${missingRequires.join(', ')}] that was not collected by previous steps. ` +
339
336
  `Ensure earlier steps collect these fields before this step can proceed.`;
340
337
  logger.warn(warning);
341
- // Also log to console for developer visibility
342
- console.warn(warning);
338
+
339
+ // Also log to console for developer visibility (but silence in test suites to prevent fast-check spam)
340
+ if (process.env.NODE_ENV !== 'test') {
341
+ console.warn(warning);
342
+ }
343
343
  }
344
344
 
345
345
  logger.debug(`[BatchExecutor] Step ${step.id} needs input, stopping batch. Missing requires: [${missingRequires.join(', ')}], Collect fields: [${collectFields.join(', ')}]`);
346
-
346
+
347
347
  // Emit batch_stop event (Requirement 11.3)
348
348
  this.emitBatchEvent('batch_stop', {
349
349
  stepId: step.id,
@@ -353,26 +353,26 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
353
353
  });
354
354
  break;
355
355
  }
356
-
356
+
357
357
  // Requirement 1.4: Step doesn't need input, include in batch
358
358
  batchSteps.push(stepOptions);
359
-
359
+
360
360
  // Log step inclusion with reason (Requirement 11.1)
361
361
  logger.debug(`[BatchExecutor] Including step ${step.id} in batch (all requirements satisfied)`);
362
-
362
+
363
363
  // Emit step_included event (Requirement 11.3)
364
364
  this.emitBatchEvent('step_included', {
365
365
  stepId: step.id,
366
366
  reason: 'All requirements satisfied, no input needed',
367
367
  batchSize: batchSteps.length,
368
368
  });
369
-
369
+
370
370
  // Check if we've reached the max steps limit
371
371
  if (batchSteps.length >= maxSteps) {
372
372
  stoppedReason = 'max_steps_reached';
373
-
373
+
374
374
  logger.debug(`[BatchExecutor] Reached maxStepsPerBatch limit (${maxSteps}), stopping batch`);
375
-
375
+
376
376
  this.emitBatchEvent('batch_stop', {
377
377
  stepId: step.id,
378
378
  reason: `Reached maxStepsPerBatch limit (${maxSteps})`,
@@ -381,16 +381,16 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
381
381
  });
382
382
  break;
383
383
  }
384
-
384
+
385
385
  // Move to next step in the sequence
386
386
  const transitions = step.getTransitions();
387
387
  if (transitions.length === 0) {
388
388
  // No more transitions, route is complete
389
389
  stoppedReason = 'route_complete';
390
-
390
+
391
391
  // Log stopping reason (Requirement 11.2)
392
392
  logger.debug(`[BatchExecutor] No more transitions from step ${step.id}, route complete`);
393
-
393
+
394
394
  // Emit batch_stop event (Requirement 11.3)
395
395
  this.emitBatchEvent('batch_stop', {
396
396
  stepId: step.id,
@@ -400,7 +400,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
400
400
  });
401
401
  break;
402
402
  }
403
-
403
+
404
404
  // For linear routes, follow the first transition
405
405
  // For branching routes, we'd need more complex logic
406
406
  const nextStep = transitions[0];
@@ -414,10 +414,10 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
414
414
  // Next step is END_ROUTE
415
415
  stoppedReason = 'end_route';
416
416
  stoppedAtStep = nextStep.toOptions();
417
-
417
+
418
418
  // Log stopping reason (Requirement 11.2)
419
419
  logger.debug(`[BatchExecutor] Next step is END_ROUTE, stopping batch`);
420
-
420
+
421
421
  // Emit batch_stop event (Requirement 11.3)
422
422
  this.emitBatchEvent('batch_stop', {
423
423
  stepId: nextStep.id,
@@ -429,11 +429,11 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
429
429
  }
430
430
  }
431
431
  }
432
-
432
+
433
433
  // Log batch determination complete with timing (Requirement 11.1, 11.2)
434
434
  const determinationTime = Date.now() - startTime;
435
435
  logger.debug(`[BatchExecutor] Batch determination complete. Steps: ${batchSteps.length}, Stopped reason: ${stoppedReason}, Time: ${determinationTime}ms`);
436
-
436
+
437
437
  return {
438
438
  steps: batchSteps,
439
439
  stoppedReason,
@@ -455,13 +455,13 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
455
455
  async executePrepareHooks(params: ExecuteHooksParams<TContext, TData>): Promise<HookExecutionResult> {
456
456
  const { steps, context, data, executeHook } = params;
457
457
  const executedSteps: string[] = [];
458
-
458
+
459
459
  logger.debug(`[BatchExecutor] Executing prepare hooks for ${steps.length} steps`);
460
-
460
+
461
461
  for (const step of steps) {
462
462
  if (step.prepare) {
463
463
  logger.debug(`[BatchExecutor] Executing prepare hook for step: ${step.id}`);
464
-
464
+
465
465
  try {
466
466
  await executeHook(step.prepare, context, data, step);
467
467
  executedSteps.push(step.id || 'unknown');
@@ -470,7 +470,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
470
470
  // Requirement 5.4: Stop on prepare hook failure
471
471
  const errorMessage = error instanceof Error ? error.message : String(error);
472
472
  logger.error(`[BatchExecutor] Prepare hook failed for step ${step.id}: ${errorMessage}`);
473
-
473
+
474
474
  return {
475
475
  success: false,
476
476
  executedSteps,
@@ -484,7 +484,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
484
484
  }
485
485
  }
486
486
  }
487
-
487
+
488
488
  logger.debug(`[BatchExecutor] All prepare hooks completed successfully`);
489
489
  return {
490
490
  success: true,
@@ -508,13 +508,13 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
508
508
  const { steps, context, data, executeHook } = params;
509
509
  const executedSteps: string[] = [];
510
510
  const errors: Array<{ stepId: string; error: BatchExecutionError }> = [];
511
-
511
+
512
512
  logger.debug(`[BatchExecutor] Executing finalize hooks for ${steps.length} steps`);
513
-
513
+
514
514
  for (const step of steps) {
515
515
  if (step.finalize) {
516
516
  logger.debug(`[BatchExecutor] Executing finalize hook for step: ${step.id}`);
517
-
517
+
518
518
  try {
519
519
  await executeHook(step.finalize, context, data, step);
520
520
  executedSteps.push(step.id || 'unknown');
@@ -523,7 +523,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
523
523
  // Requirement 5.5: Log error and continue with remaining hooks
524
524
  const errorMessage = error instanceof Error ? error.message : String(error);
525
525
  logger.error(`[BatchExecutor] Finalize hook failed for step ${step.id}: ${errorMessage}`);
526
-
526
+
527
527
  errors.push({
528
528
  stepId: step.id || 'unknown',
529
529
  error: {
@@ -533,18 +533,18 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
533
533
  details: error,
534
534
  },
535
535
  });
536
-
536
+
537
537
  // Continue to next step despite error
538
538
  }
539
539
  }
540
540
  }
541
-
541
+
542
542
  if (errors.length > 0) {
543
543
  logger.warn(`[BatchExecutor] ${errors.length} finalize hook(s) failed, but execution continued`);
544
544
  } else {
545
545
  logger.debug(`[BatchExecutor] All finalize hooks completed successfully`);
546
546
  }
547
-
547
+
548
548
  // Always return success for finalize hooks (errors are logged but don't stop execution)
549
549
  return {
550
550
  success: true,
@@ -592,36 +592,36 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
592
592
  * **Validates: Requirements 9.1, 9.2, 9.3, 2.4**
593
593
  */
594
594
  async executeBatch(params: ExecuteBatchParams<TContext, TData>): Promise<BatchExecutionResult<TData>> {
595
- const {
596
- batch,
597
- session: initialSession,
598
- context,
599
- executeHook,
600
- generateMessage,
595
+ const {
596
+ batch,
597
+ session: initialSession,
598
+ context,
599
+ executeHook,
600
+ generateMessage,
601
601
  schema,
602
602
  routeId,
603
603
  } = params;
604
-
604
+
605
605
  // Track timing for each phase (Requirement 11.1)
606
606
  const timing: BatchExecutionTiming = {
607
607
  totalMs: 0,
608
608
  };
609
609
  const batchStartTime = Date.now();
610
-
610
+
611
611
  // Track the last successful session state for error recovery
612
612
  let lastSuccessfulSession = initialSession;
613
613
  let currentSession = initialSession;
614
-
614
+
615
615
  // Track executed steps for the response
616
616
  const executedSteps: StepRef[] = [];
617
-
617
+
618
618
  // Log batch execution start with details (Requirement 11.1)
619
619
  logger.debug(`[BatchExecutor] Starting batch execution with ${batch.steps.length} steps, route: ${routeId || 'unknown'}`);
620
-
620
+
621
621
  // If batch is empty, return early with appropriate reason
622
622
  if (batch.steps.length === 0) {
623
623
  logger.debug(`[BatchExecutor] Empty batch, returning with stopped reason: ${batch.stoppedReason}`);
624
-
624
+
625
625
  // Emit batch_complete event for empty batch (Requirement 11.3)
626
626
  timing.totalMs = Date.now() - batchStartTime;
627
627
  this.emitBatchEvent('batch_complete', {
@@ -630,7 +630,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
630
630
  reason: 'Empty batch',
631
631
  timing,
632
632
  });
633
-
633
+
634
634
  return {
635
635
  message: '',
636
636
  session: currentSession,
@@ -639,26 +639,26 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
639
639
  collectedData: {},
640
640
  };
641
641
  }
642
-
642
+
643
643
  // PHASE 1: Execute prepare hooks (Requirement 5.4 - stop on failure)
644
644
  const prepareStartTime = Date.now();
645
645
  logger.debug(`[BatchExecutor] Phase 1: Executing prepare hooks`);
646
-
646
+
647
647
  const prepareResult = await this.executePrepareHooks({
648
648
  steps: batch.steps,
649
649
  context,
650
650
  data: currentSession.data,
651
651
  executeHook,
652
652
  });
653
-
653
+
654
654
  timing.prepareHooksMs = Date.now() - prepareStartTime;
655
655
  logger.debug(`[BatchExecutor] Prepare hooks completed in ${timing.prepareHooksMs}ms`);
656
-
656
+
657
657
  if (!prepareResult.success) {
658
658
  // Requirement 9.3: Preserve partial progress on errors
659
659
  // Requirement 2.4: Stop and include error information
660
660
  logger.error(`[BatchExecutor] Prepare hook failed:`, prepareResult.error);
661
-
661
+
662
662
  // Emit batch_complete event with error (Requirement 11.3)
663
663
  timing.totalMs = Date.now() - batchStartTime;
664
664
  this.emitBatchEvent('batch_complete', {
@@ -667,7 +667,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
667
667
  reason: `Prepare hook failed: ${prepareResult.error?.message}`,
668
668
  timing,
669
669
  });
670
-
670
+
671
671
  return {
672
672
  message: '',
673
673
  session: lastSuccessfulSession, // Return last successful state
@@ -679,22 +679,22 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
679
679
  error: prepareResult.error,
680
680
  };
681
681
  }
682
-
682
+
683
683
  // Update last successful session after prepare hooks complete
684
684
  lastSuccessfulSession = currentSession;
685
-
685
+
686
686
  // PHASE 2: Make LLM call (Requirement 9.1 - preserve session state on failure)
687
687
  const llmStartTime = Date.now();
688
688
  logger.debug(`[BatchExecutor] Phase 2: Making LLM call`);
689
-
689
+
690
690
  let llmResponse: Record<string, unknown>;
691
691
  let message: string;
692
-
692
+
693
693
  try {
694
694
  const result = await generateMessage();
695
695
  llmResponse = result.structured || {};
696
696
  message = result.message || '';
697
-
697
+
698
698
  timing.llmCallMs = Date.now() - llmStartTime;
699
699
  logger.debug(`[BatchExecutor] LLM call successful in ${timing.llmCallMs}ms`);
700
700
  } catch (error) {
@@ -702,7 +702,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
702
702
  const errorMessage = error instanceof Error ? error.message : String(error);
703
703
  timing.llmCallMs = Date.now() - llmStartTime;
704
704
  logger.error(`[BatchExecutor] LLM call failed after ${timing.llmCallMs}ms:`, errorMessage);
705
-
705
+
706
706
  // Emit batch_complete event with error (Requirement 11.3)
707
707
  timing.totalMs = Date.now() - batchStartTime;
708
708
  this.emitBatchEvent('batch_complete', {
@@ -711,7 +711,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
711
711
  reason: `LLM call failed: ${errorMessage}`,
712
712
  timing,
713
713
  });
714
-
714
+
715
715
  return {
716
716
  message: '',
717
717
  session: lastSuccessfulSession, // Preserve session state
@@ -724,50 +724,50 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
724
724
  },
725
725
  };
726
726
  }
727
-
727
+
728
728
  // Update last successful session after LLM call
729
729
  lastSuccessfulSession = currentSession;
730
-
730
+
731
731
  // PHASE 3: Collect and validate data (Requirement 9.2 - include validation errors)
732
732
  const collectStartTime = Date.now();
733
733
  logger.debug(`[BatchExecutor] Phase 3: Collecting and validating data`);
734
-
734
+
735
735
  const collectResult = this.collectBatchData({
736
736
  steps: batch.steps,
737
737
  llmResponse,
738
738
  session: currentSession,
739
739
  schema,
740
740
  });
741
-
741
+
742
742
  timing.dataCollectionMs = Date.now() - collectStartTime;
743
743
  logger.debug(`[BatchExecutor] Data collection completed in ${timing.dataCollectionMs}ms`);
744
-
744
+
745
745
  // Update session with collected data (even if validation failed)
746
746
  currentSession = collectResult.session;
747
-
747
+
748
748
  // Track collected data for response
749
749
  const collectedData = collectResult.collectedData;
750
-
750
+
751
751
  // Check for validation errors
752
752
  let validationError: BatchExecutionError | undefined;
753
753
  if (!collectResult.success && collectResult.validationErrors && collectResult.validationErrors.length > 0) {
754
754
  // Requirement 9.2: Include validation errors in response
755
755
  logger.warn(`[BatchExecutor] Data validation failed:`, collectResult.validationErrors);
756
-
756
+
757
757
  validationError = {
758
758
  type: 'data_validation',
759
759
  message: `Validation failed for ${collectResult.validationErrors.length} field(s): ${collectResult.validationErrors.map(e => e.field).join(', ')}`,
760
760
  details: collectResult.validationErrors,
761
761
  };
762
-
762
+
763
763
  // Note: We continue execution despite validation errors
764
764
  // The error is included in the response for the caller to handle
765
765
  }
766
-
766
+
767
767
  // Update last successful session after data collection
768
768
  // (even with validation errors, we preserve the collected data)
769
769
  lastSuccessfulSession = currentSession;
770
-
770
+
771
771
  // Build executed steps list
772
772
  for (const step of batch.steps) {
773
773
  if (step.id) {
@@ -777,26 +777,26 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
777
777
  });
778
778
  }
779
779
  }
780
-
780
+
781
781
  // PHASE 4: Execute finalize hooks (Requirement 5.5 - continue on failure)
782
782
  const finalizeStartTime = Date.now();
783
783
  logger.debug(`[BatchExecutor] Phase 4: Executing finalize hooks`);
784
-
784
+
785
785
  const finalizeResult = await this.executeFinalizeHooks({
786
786
  steps: batch.steps,
787
787
  context,
788
788
  data: currentSession.data,
789
789
  executeHook,
790
790
  });
791
-
791
+
792
792
  timing.finalizeHooksMs = Date.now() - finalizeStartTime;
793
793
  logger.debug(`[BatchExecutor] Finalize hooks completed in ${timing.finalizeHooksMs}ms`);
794
-
794
+
795
795
  // Log finalize errors but don't fail the batch
796
796
  let finalizeError: BatchExecutionError | undefined;
797
797
  if (finalizeResult.errors && finalizeResult.errors.length > 0) {
798
798
  logger.warn(`[BatchExecutor] Some finalize hooks failed:`, finalizeResult.errors);
799
-
799
+
800
800
  // Create a summary error for finalize failures
801
801
  finalizeError = {
802
802
  type: 'finalize_hook',
@@ -804,12 +804,12 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
804
804
  details: finalizeResult.errors,
805
805
  };
806
806
  }
807
-
807
+
808
808
  // Determine the final stopped reason
809
809
  // Priority: validation_error > finalize_error > batch.stoppedReason
810
810
  let finalStoppedReason: StoppedReason = batch.stoppedReason;
811
811
  let finalError: BatchExecutionError | undefined;
812
-
812
+
813
813
  if (validationError) {
814
814
  finalStoppedReason = 'validation_error';
815
815
  finalError = validationError;
@@ -818,11 +818,11 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
818
818
  // but include the error in the response
819
819
  finalError = finalizeError;
820
820
  }
821
-
821
+
822
822
  // Calculate total time and log completion (Requirement 11.1, 11.2)
823
823
  timing.totalMs = Date.now() - batchStartTime;
824
824
  logger.debug(`[BatchExecutor] Batch execution complete. Stopped reason: ${finalStoppedReason}, Executed steps: ${executedSteps.length}, Total time: ${timing.totalMs}ms`);
825
-
825
+
826
826
  // Emit batch_complete event (Requirement 11.3)
827
827
  this.emitBatchEvent('batch_complete', {
828
828
  batchSize: executedSteps.length,
@@ -830,7 +830,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
830
830
  reason: `Batch completed with ${executedSteps.length} steps`,
831
831
  timing,
832
832
  });
833
-
833
+
834
834
  return {
835
835
  message,
836
836
  session: currentSession,
@@ -857,9 +857,9 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
857
857
  */
858
858
  collectBatchData(params: CollectBatchDataParams<TData>): CollectBatchDataResult<TData> {
859
859
  const { steps, llmResponse, session, schema } = params;
860
-
860
+
861
861
  logger.debug(`[BatchExecutor] Collecting batch data from ${steps.length} steps`);
862
-
862
+
863
863
  // Requirement 6.1: Gather all collect fields from all steps in the batch
864
864
  const allCollectFields = new Set<string>();
865
865
  for (const step of steps) {
@@ -869,9 +869,9 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
869
869
  }
870
870
  }
871
871
  }
872
-
872
+
873
873
  logger.debug(`[BatchExecutor] Collect fields to extract: ${Array.from(allCollectFields).join(', ')}`);
874
-
874
+
875
875
  // If no fields to collect, return early with unchanged session
876
876
  if (allCollectFields.size === 0) {
877
877
  logger.debug(`[BatchExecutor] No collect fields defined, skipping data collection`);
@@ -882,12 +882,12 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
882
882
  fieldsCollected: [],
883
883
  };
884
884
  }
885
-
885
+
886
886
  // Extract data from LLM response for all collect fields
887
887
  const collectedData: Partial<TData> = {};
888
888
  const fieldsCollected: string[] = [];
889
889
  const fieldsMissing: string[] = [];
890
-
890
+
891
891
  for (const field of allCollectFields) {
892
892
  // Check if the field exists in the LLM response
893
893
  if (field in llmResponse && llmResponse[field] !== undefined) {
@@ -899,20 +899,20 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
899
899
  logger.debug(`[BatchExecutor] Field '${field}' not found in LLM response`);
900
900
  }
901
901
  }
902
-
902
+
903
903
  // Requirement 6.2: Validate collected data against the agent schema
904
904
  const validationErrors: ValidationError[] = [];
905
-
905
+
906
906
  if (schema && Object.keys(collectedData).length > 0) {
907
907
  logger.debug(`[BatchExecutor] Validating collected data against schema`);
908
-
908
+
909
909
  const validationResult = this.validateAgainstSchema(collectedData, schema);
910
910
  if (!validationResult.valid) {
911
911
  validationErrors.push(...validationResult.errors);
912
912
  logger.warn(`[BatchExecutor] Schema validation found ${validationErrors.length} error(s)`);
913
913
  }
914
914
  }
915
-
915
+
916
916
  // Requirement 6.3: Update session data with all collected values
917
917
  // Only merge valid data (data that passed validation or had no schema to validate against)
918
918
  let updatedSession = session;
@@ -922,11 +922,11 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
922
922
  updatedSession = mergeCollected(session, collectedData);
923
923
  logger.debug(`[BatchExecutor] Updated session with collected data`);
924
924
  }
925
-
925
+
926
926
  const success = validationErrors.length === 0;
927
-
927
+
928
928
  logger.debug(`[BatchExecutor] Data collection complete. Success: ${success}, Fields collected: ${fieldsCollected.length}, Fields missing: ${fieldsMissing.length}`);
929
-
929
+
930
930
  return {
931
931
  success,
932
932
  collectedData,
@@ -956,7 +956,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
956
956
  schema: StructuredSchema
957
957
  ): { valid: boolean; errors: ValidationError[] } {
958
958
  const errors: ValidationError[] = [];
959
-
959
+
960
960
  // Check if provided fields exist in schema
961
961
  if (schema.properties) {
962
962
  for (const [key, value] of Object.entries(data)) {
@@ -977,7 +977,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
977
977
  }
978
978
  }
979
979
  }
980
-
980
+
981
981
  return {
982
982
  valid: errors.length === 0,
983
983
  errors,
@@ -997,18 +997,18 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
997
997
  // Null/undefined values are handled separately (required field check)
998
998
  return null;
999
999
  }
1000
-
1000
+
1001
1001
  const expectedType = fieldSchema.type;
1002
1002
  if (!expectedType) {
1003
1003
  // No type specified, consider valid
1004
1004
  return null;
1005
1005
  }
1006
-
1006
+
1007
1007
  const actualType = Array.isArray(value) ? 'array' : typeof value;
1008
-
1008
+
1009
1009
  // Handle array of types
1010
1010
  const allowedTypes = Array.isArray(expectedType) ? expectedType : [expectedType];
1011
-
1011
+
1012
1012
  // Map JavaScript types to JSON Schema types
1013
1013
  const typeMapping: Record<string, string> = {
1014
1014
  'string': 'string',
@@ -1017,9 +1017,9 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
1017
1017
  'object': 'object',
1018
1018
  'array': 'array',
1019
1019
  };
1020
-
1020
+
1021
1021
  const mappedActualType = typeMapping[actualType] || actualType;
1022
-
1022
+
1023
1023
  // Check if actual type matches any allowed type
1024
1024
  // Also handle 'integer' as a valid number type
1025
1025
  const isValidType = allowedTypes.some(t => {
@@ -1028,7 +1028,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
1028
1028
  }
1029
1029
  return t === mappedActualType;
1030
1030
  });
1031
-
1031
+
1032
1032
  if (!isValidType) {
1033
1033
  return {
1034
1034
  field,
@@ -1037,7 +1037,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
1037
1037
  schemaPath: `properties.${field}.type`,
1038
1038
  };
1039
1039
  }
1040
-
1040
+
1041
1041
  return null;
1042
1042
  }
1043
1043
  }
@@ -1068,7 +1068,7 @@ export interface ExecuteHooksParams<TContext, TData> {
1068
1068
  /**
1069
1069
  * Type for the hook execution function
1070
1070
  */
1071
- export type HookFunction<TContext, TData> =
1071
+ export type HookFunction<TContext, TData> =
1072
1072
  | string
1073
1073
  | Tool<TContext, TData>
1074
1074
  | ((context: TContext, data?: Partial<TData>) => void | Promise<void>);