@defai.digital/agent-domain 13.0.3

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 (77) hide show
  1. package/LICENSE +214 -0
  2. package/dist/enhanced-executor.d.ts +170 -0
  3. package/dist/enhanced-executor.d.ts.map +1 -0
  4. package/dist/enhanced-executor.js +1072 -0
  5. package/dist/enhanced-executor.js.map +1 -0
  6. package/dist/executor.d.ts +120 -0
  7. package/dist/executor.d.ts.map +1 -0
  8. package/dist/executor.js +929 -0
  9. package/dist/executor.js.map +1 -0
  10. package/dist/index.d.ts +25 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +34 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/loader.d.ts +50 -0
  15. package/dist/loader.d.ts.map +1 -0
  16. package/dist/loader.js +160 -0
  17. package/dist/loader.js.map +1 -0
  18. package/dist/persistent-registry.d.ts +105 -0
  19. package/dist/persistent-registry.d.ts.map +1 -0
  20. package/dist/persistent-registry.js +183 -0
  21. package/dist/persistent-registry.js.map +1 -0
  22. package/dist/production-factories.d.ts +70 -0
  23. package/dist/production-factories.d.ts.map +1 -0
  24. package/dist/production-factories.js +434 -0
  25. package/dist/production-factories.js.map +1 -0
  26. package/dist/prompt-executor.d.ts +119 -0
  27. package/dist/prompt-executor.d.ts.map +1 -0
  28. package/dist/prompt-executor.js +211 -0
  29. package/dist/prompt-executor.js.map +1 -0
  30. package/dist/registry.d.ts +57 -0
  31. package/dist/registry.d.ts.map +1 -0
  32. package/dist/registry.js +123 -0
  33. package/dist/registry.js.map +1 -0
  34. package/dist/selection-service.d.ts +74 -0
  35. package/dist/selection-service.d.ts.map +1 -0
  36. package/dist/selection-service.js +322 -0
  37. package/dist/selection-service.js.map +1 -0
  38. package/dist/selector.d.ts +51 -0
  39. package/dist/selector.d.ts.map +1 -0
  40. package/dist/selector.js +249 -0
  41. package/dist/selector.js.map +1 -0
  42. package/dist/stub-checkpoint.d.ts +23 -0
  43. package/dist/stub-checkpoint.d.ts.map +1 -0
  44. package/dist/stub-checkpoint.js +137 -0
  45. package/dist/stub-checkpoint.js.map +1 -0
  46. package/dist/stub-delegation-tracker.d.ts +25 -0
  47. package/dist/stub-delegation-tracker.d.ts.map +1 -0
  48. package/dist/stub-delegation-tracker.js +118 -0
  49. package/dist/stub-delegation-tracker.js.map +1 -0
  50. package/dist/stub-parallel-executor.d.ts +19 -0
  51. package/dist/stub-parallel-executor.d.ts.map +1 -0
  52. package/dist/stub-parallel-executor.js +176 -0
  53. package/dist/stub-parallel-executor.js.map +1 -0
  54. package/dist/types.d.ts +614 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +15 -0
  57. package/dist/types.js.map +1 -0
  58. package/dist/workflow-templates.d.ts +117 -0
  59. package/dist/workflow-templates.d.ts.map +1 -0
  60. package/dist/workflow-templates.js +342 -0
  61. package/dist/workflow-templates.js.map +1 -0
  62. package/package.json +51 -0
  63. package/src/enhanced-executor.ts +1395 -0
  64. package/src/executor.ts +1153 -0
  65. package/src/index.ts +172 -0
  66. package/src/loader.ts +191 -0
  67. package/src/persistent-registry.ts +235 -0
  68. package/src/production-factories.ts +613 -0
  69. package/src/prompt-executor.ts +310 -0
  70. package/src/registry.ts +167 -0
  71. package/src/selection-service.ts +411 -0
  72. package/src/selector.ts +299 -0
  73. package/src/stub-checkpoint.ts +187 -0
  74. package/src/stub-delegation-tracker.ts +161 -0
  75. package/src/stub-parallel-executor.ts +224 -0
  76. package/src/types.ts +784 -0
  77. package/src/workflow-templates.ts +393 -0
@@ -0,0 +1,1395 @@
1
+ /**
2
+ * Enhanced Agent Executor Implementation
3
+ *
4
+ * Full-featured executor with:
5
+ * - Checkpoint support (INV-CP-001, INV-CP-002)
6
+ * - Parallel execution (INV-PE-001, INV-PE-002, INV-PE-003)
7
+ * - Delegation tracking (INV-DT-001, INV-DT-002)
8
+ * - Resumable workflows
9
+ * - Event emission
10
+ *
11
+ * All external dependencies are injected via config (dependency inversion).
12
+ */
13
+
14
+ import {
15
+ type AgentResult,
16
+ type AgentRunOptions,
17
+ type AgentError,
18
+ type AgentWorkflowStep,
19
+ type AgentProfile,
20
+ AgentErrorCode,
21
+ AgentRunOptionsSchema,
22
+ createDefaultCheckpointConfig,
23
+ createDefaultParallelExecutionConfig,
24
+ type CheckpointConfig,
25
+ type ParallelExecutionConfig,
26
+ LIMIT_ABILITY_TOKENS_AGENT,
27
+ } from '@defai.digital/contracts';
28
+ import type {
29
+ AgentExecutor,
30
+ AgentRegistry,
31
+ ExecutionStatus,
32
+ StepExecutionContext,
33
+ StepExecutionResult,
34
+ StepExecutorFn,
35
+ AgentDomainConfig,
36
+ PromptExecutor,
37
+ PromptStepConfig,
38
+ AbilityManagerLike,
39
+ ToolExecutor,
40
+ DelegationTrackerFactory,
41
+ CheckpointStoragePort,
42
+ CheckpointManagerPort,
43
+ CheckpointStorageFactory,
44
+ CheckpointManagerFactory,
45
+ ParallelExecutorPort,
46
+ ParallelExecutorFactory,
47
+ } from './types.js';
48
+ import { createStubPromptExecutor } from './prompt-executor.js';
49
+ import { stubDelegationTrackerFactory } from './stub-delegation-tracker.js';
50
+ import { stubCheckpointStorageFactory, stubCheckpointManagerFactory } from './stub-checkpoint.js';
51
+ import { stubParallelExecutorFactory } from './stub-parallel-executor.js';
52
+
53
+ /**
54
+ * Enhanced configuration with checkpoint and parallel settings
55
+ *
56
+ * All external dependencies are injected via factory functions.
57
+ * If not provided, stub implementations are used (with warnings).
58
+ */
59
+ export interface EnhancedAgentDomainConfig extends AgentDomainConfig {
60
+ checkpointConfig?: Partial<CheckpointConfig>;
61
+ parallelConfig?: Partial<ParallelExecutionConfig>;
62
+ /** Factory for creating checkpoint storage (default: in-memory stub) */
63
+ checkpointStorageFactory?: CheckpointStorageFactory;
64
+ /** Factory for creating checkpoint managers */
65
+ checkpointManagerFactory?: CheckpointManagerFactory;
66
+ /** Factory for creating parallel executors (default: sequential stub) */
67
+ parallelExecutorFactory?: ParallelExecutorFactory;
68
+ }
69
+
70
+ /**
71
+ * Execution state with checkpoint support
72
+ */
73
+ interface ExecutionState {
74
+ executionId: string;
75
+ agentId: string;
76
+ status: 'running' | 'completed' | 'failed' | 'cancelled';
77
+ startedAt: string;
78
+ completedAt?: string;
79
+ currentStep?: string;
80
+ stepResults: StepResult[];
81
+ previousOutputs: Record<string, unknown>;
82
+ checkpointId?: string;
83
+ }
84
+
85
+ /**
86
+ * Step result
87
+ */
88
+ interface StepResult {
89
+ stepId: string;
90
+ success: boolean;
91
+ output?: unknown;
92
+ durationMs: number;
93
+ retryCount: number;
94
+ skipped: boolean;
95
+ error: AgentError | undefined;
96
+ }
97
+
98
+ /**
99
+ * Enhanced agent executor with full feature support
100
+ *
101
+ * Now includes ability injection (INV-AGT-ABL-001, INV-AGT-ABL-002, INV-AGT-ABL-003)
102
+ * All external dependencies are injected via config factories.
103
+ */
104
+ export class EnhancedAgentExecutor implements AgentExecutor {
105
+ private readonly executions = new Map<string, ExecutionState>();
106
+ private readonly stepExecutors = new Map<string, StepExecutorFn>();
107
+ private readonly promptExecutor: PromptExecutor;
108
+ private readonly toolExecutor: ToolExecutor | undefined;
109
+ private readonly checkpointStorage: CheckpointStoragePort;
110
+ private readonly parallelExecutor: ParallelExecutorPort;
111
+ private readonly checkpointConfig: CheckpointConfig;
112
+ private readonly parallelConfig: ParallelExecutionConfig;
113
+ private readonly abilityManager: AbilityManagerLike | undefined;
114
+ private readonly enableAbilityInjection: boolean;
115
+ private readonly maxAbilityTokens: number;
116
+ private readonly delegationTrackerFactory: DelegationTrackerFactory;
117
+ private readonly checkpointManagerFactory: CheckpointManagerFactory;
118
+
119
+ constructor(
120
+ private readonly registry: AgentRegistry,
121
+ private readonly config: EnhancedAgentDomainConfig
122
+ ) {
123
+ // Initialize prompt executor
124
+ this.promptExecutor = config.promptExecutor ?? createStubPromptExecutor(
125
+ config.defaultProvider ?? 'claude'
126
+ );
127
+
128
+ // Initialize tool executor (INV-TOOL-001, INV-TOOL-002, INV-TOOL-003)
129
+ this.toolExecutor = config.toolExecutor;
130
+
131
+ // Initialize ability manager (INV-AGT-ABL-001)
132
+ this.abilityManager = config.abilityManager;
133
+ this.enableAbilityInjection = config.enableAbilityInjection ?? (config.abilityManager !== undefined);
134
+ this.maxAbilityTokens = config.maxAbilityTokens ?? LIMIT_ABILITY_TOKENS_AGENT;
135
+
136
+ // Initialize delegation tracker factory (use stub if not provided)
137
+ this.delegationTrackerFactory = config.delegationTrackerFactory ?? stubDelegationTrackerFactory;
138
+
139
+ // Initialize checkpoint storage using injected factory (or stub)
140
+ this.checkpointConfig = {
141
+ ...createDefaultCheckpointConfig(),
142
+ ...config.checkpointConfig,
143
+ };
144
+ const checkpointStorageFactory = config.checkpointStorageFactory ?? stubCheckpointStorageFactory;
145
+ this.checkpointStorage = checkpointStorageFactory();
146
+
147
+ // Initialize checkpoint manager factory
148
+ this.checkpointManagerFactory = config.checkpointManagerFactory ?? stubCheckpointManagerFactory;
149
+
150
+ // Initialize parallel executor using injected factory (or stub)
151
+ this.parallelConfig = {
152
+ ...createDefaultParallelExecutionConfig(),
153
+ ...config.parallelConfig,
154
+ };
155
+ const parallelExecutorFactory = config.parallelExecutorFactory ?? stubParallelExecutorFactory;
156
+ this.parallelExecutor = parallelExecutorFactory(this.parallelConfig);
157
+
158
+ // Register step executors
159
+ this.registerDefaultExecutors();
160
+ }
161
+
162
+ /**
163
+ * Create a checkpoint manager for a specific execution
164
+ */
165
+ private createCheckpointManagerForExecution(
166
+ agentId: string,
167
+ sessionId: string | undefined
168
+ ): CheckpointManagerPort {
169
+ return this.checkpointManagerFactory(
170
+ agentId,
171
+ sessionId,
172
+ this.checkpointStorage,
173
+ this.checkpointConfig
174
+ );
175
+ }
176
+
177
+ /**
178
+ * Execute an agent with full feature support
179
+ */
180
+ async execute(
181
+ agentId: string,
182
+ input: unknown,
183
+ options?: AgentRunOptions
184
+ ): Promise<AgentResult> {
185
+ const startTime = Date.now();
186
+ const executionId = crypto.randomUUID();
187
+
188
+ // Validate options
189
+ let validatedOptions: AgentRunOptions | undefined;
190
+ if (options !== undefined) {
191
+ try {
192
+ validatedOptions = AgentRunOptionsSchema.parse(options);
193
+ } catch (error) {
194
+ return this.createErrorResult(
195
+ agentId,
196
+ startTime,
197
+ AgentErrorCode.AGENT_VALIDATION_ERROR,
198
+ `Invalid run options: ${error instanceof Error ? error.message : 'Unknown error'}`
199
+ );
200
+ }
201
+ }
202
+
203
+ // Get agent profile
204
+ const agent = await this.registry.get(agentId);
205
+ if (agent === undefined) {
206
+ return this.createErrorResult(
207
+ agentId,
208
+ startTime,
209
+ AgentErrorCode.AGENT_NOT_FOUND,
210
+ `Agent "${agentId}" not found`
211
+ );
212
+ }
213
+
214
+ // Check if agent is enabled
215
+ if (!agent.enabled) {
216
+ return this.createErrorResult(
217
+ agentId,
218
+ startTime,
219
+ AgentErrorCode.AGENT_PERMISSION_DENIED,
220
+ `Agent "${agentId}" is disabled`
221
+ );
222
+ }
223
+
224
+ // Create checkpoint manager for this execution
225
+ const checkpointManager = this.createCheckpointManagerForExecution(
226
+ agentId,
227
+ validatedOptions?.sessionId
228
+ );
229
+
230
+ // Check for resume from checkpoint
231
+ let startFromStep = 0;
232
+ let previousOutputs: Record<string, unknown> = {};
233
+
234
+ if (validatedOptions?.resumable && validatedOptions.checkpoint) {
235
+ const latestCheckpoint = await checkpointManager.getLatestCheckpoint();
236
+ if (latestCheckpoint !== null) {
237
+ const resumeContext = await checkpointManager.getResumeContext(latestCheckpoint.checkpointId);
238
+ if (resumeContext !== null) {
239
+ // INV-CP-002: Resume from step after checkpoint
240
+ startFromStep = resumeContext.startFromStep;
241
+ previousOutputs = resumeContext.previousOutputs;
242
+ }
243
+ }
244
+ }
245
+
246
+ // Initialize execution state
247
+ const state: ExecutionState = {
248
+ executionId,
249
+ agentId,
250
+ status: 'running',
251
+ startedAt: new Date().toISOString(),
252
+ stepResults: [],
253
+ previousOutputs,
254
+ };
255
+ this.executions.set(executionId, state);
256
+
257
+ try {
258
+ const workflow = agent.workflow ?? [];
259
+ const stepResults: StepResult[] = [];
260
+
261
+ // Handle agents with no workflow
262
+ if (workflow.length === 0) {
263
+ return this.handleNoWorkflow(agent, input, validatedOptions, startTime);
264
+ }
265
+
266
+ // Check if parallel execution is enabled
267
+ const useParallel = validatedOptions?.parallel ?? this.parallelConfig.enabled;
268
+
269
+ if (useParallel) {
270
+ // Execute with parallel executor
271
+ return await this.executeParallel(
272
+ agent,
273
+ workflow,
274
+ input,
275
+ state,
276
+ validatedOptions,
277
+ startTime,
278
+ startFromStep
279
+ );
280
+ }
281
+
282
+ // Sequential execution with checkpoint support
283
+ for (let i = startFromStep; i < workflow.length; i++) {
284
+ const step = workflow[i];
285
+ if (step === undefined) continue;
286
+
287
+ if (state.status === 'cancelled') break;
288
+
289
+ state.currentStep = step.stepId;
290
+
291
+ // Check dependencies
292
+ if (!this.checkDependencies(step, stepResults)) {
293
+ stepResults.push(this.createSkippedResult(step.stepId, 'Dependencies not met'));
294
+ continue;
295
+ }
296
+
297
+ // Check condition
298
+ if (step.condition !== undefined && !this.evaluateCondition(step.condition, state.previousOutputs)) {
299
+ stepResults.push(this.createSkippedResult(step.stepId));
300
+ continue;
301
+ }
302
+
303
+ // Execute step
304
+ const result = await this.executeStep(
305
+ step,
306
+ this.createStepContext(agentId, executionId, input, state.previousOutputs, validatedOptions),
307
+ step.retryPolicy?.maxAttempts ?? 1
308
+ );
309
+
310
+ stepResults.push({
311
+ stepId: step.stepId,
312
+ success: result.success,
313
+ output: result.output,
314
+ durationMs: result.durationMs,
315
+ retryCount: result.retryCount,
316
+ skipped: false,
317
+ error: result.error,
318
+ });
319
+
320
+ // Store output
321
+ if (result.success && result.output !== undefined) {
322
+ state.previousOutputs[step.stepId] = result.output;
323
+ }
324
+
325
+ // Create checkpoint if enabled
326
+ // INV-CP-001: Checkpoint contains all data needed to resume
327
+ if (this.checkpointConfig.enabled && checkpointManager.shouldCheckpoint(i)) {
328
+ await checkpointManager.createCheckpoint(
329
+ i,
330
+ step.stepId,
331
+ state.previousOutputs,
332
+ { input }
333
+ );
334
+ }
335
+
336
+ // Stop on failure (unless marked as parallel)
337
+ if (!result.success && !step.parallel) {
338
+ break;
339
+ }
340
+ }
341
+
342
+ return this.buildResult(agentId, stepResults, state, validatedOptions, startTime);
343
+ } catch (error) {
344
+ state.status = 'failed';
345
+ state.completedAt = new Date().toISOString();
346
+ return this.createErrorResult(
347
+ agentId,
348
+ startTime,
349
+ AgentErrorCode.AGENT_STAGE_FAILED,
350
+ error instanceof Error ? error.message : 'Unknown error'
351
+ );
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Execute workflow with parallel executor
357
+ * INV-PE-001: Independent steps execute concurrently
358
+ * INV-PE-002: Dependencies honored (DAG ordering)
359
+ * INV-PE-003: Concurrency limit respected
360
+ */
361
+ private async executeParallel(
362
+ agent: AgentProfile,
363
+ workflow: AgentWorkflowStep[],
364
+ input: unknown,
365
+ state: ExecutionState,
366
+ options: AgentRunOptions | undefined,
367
+ startTime: number,
368
+ _startFromStep: number
369
+ ): Promise<AgentResult> {
370
+ const context = this.createStepContext(
371
+ agent.agentId,
372
+ state.executionId,
373
+ input,
374
+ state.previousOutputs,
375
+ options
376
+ );
377
+
378
+ // Build executor function for parallel executor
379
+ const stepExecutor = async (
380
+ step: AgentWorkflowStep,
381
+ outputs: Record<string, unknown>
382
+ ): Promise<unknown> => {
383
+ const stepContext = {
384
+ ...context,
385
+ previousOutputs: outputs,
386
+ };
387
+
388
+ const result = await this.executeStep(
389
+ step,
390
+ stepContext,
391
+ step.retryPolicy?.maxAttempts ?? 1
392
+ );
393
+
394
+ if (!result.success) {
395
+ throw new Error(result.error?.message ?? 'Step failed');
396
+ }
397
+
398
+ return result.output;
399
+ };
400
+
401
+ // Execute with parallel executor
402
+ const groupResult = await this.parallelExecutor.executeGroup(
403
+ workflow,
404
+ stepExecutor,
405
+ state.previousOutputs
406
+ );
407
+
408
+ // Convert parallel results to step results
409
+ const stepResults: StepResult[] = groupResult.stepResults.map((r) => ({
410
+ stepId: r.stepId,
411
+ success: r.success,
412
+ output: r.output,
413
+ durationMs: r.durationMs,
414
+ retryCount: 0,
415
+ skipped: r.cancelled ?? false,
416
+ error: r.error ? { code: AgentErrorCode.AGENT_STAGE_FAILED, message: r.error } : undefined,
417
+ }));
418
+
419
+ // Update state
420
+ state.stepResults = stepResults;
421
+ state.status = groupResult.allSucceeded ? 'completed' : 'failed';
422
+ state.completedAt = new Date().toISOString();
423
+
424
+ return {
425
+ success: groupResult.allSucceeded,
426
+ agentId: agent.agentId,
427
+ sessionId: options?.sessionId,
428
+ output: this.collectOutputs(stepResults, state.previousOutputs),
429
+ stepResults,
430
+ totalDurationMs: Date.now() - startTime,
431
+ error: groupResult.allSucceeded
432
+ ? undefined
433
+ : stepResults.find((r) => r.error !== undefined)?.error,
434
+ };
435
+ }
436
+
437
+ /**
438
+ * Handle agents with no workflow defined
439
+ */
440
+ private handleNoWorkflow(
441
+ agent: AgentProfile,
442
+ input: unknown,
443
+ options: AgentRunOptions | undefined,
444
+ startTime: number
445
+ ): AgentResult {
446
+ // If agent has systemPrompt, create a single prompt step on the fly
447
+ if (agent.systemPrompt) {
448
+ return {
449
+ success: true,
450
+ agentId: agent.agentId,
451
+ sessionId: options?.sessionId,
452
+ output: {
453
+ message: 'Agent has no workflow but has a system prompt. Use agent_run to execute prompts directly.',
454
+ agent: {
455
+ agentId: agent.agentId,
456
+ description: agent.description,
457
+ hasSystemPrompt: true,
458
+ },
459
+ suggestion: 'Register the agent with workflow steps for structured execution.',
460
+ },
461
+ stepResults: [],
462
+ totalDurationMs: Date.now() - startTime,
463
+ };
464
+ }
465
+
466
+ return {
467
+ success: true,
468
+ agentId: agent.agentId,
469
+ sessionId: options?.sessionId,
470
+ output: {
471
+ message: `Agent "${agent.agentId}" has no workflow steps. Register with workflow to enable execution.`,
472
+ input,
473
+ },
474
+ stepResults: [],
475
+ totalDurationMs: Date.now() - startTime,
476
+ };
477
+ }
478
+
479
+ /**
480
+ * Cancel execution
481
+ */
482
+ async cancel(executionId: string): Promise<void> {
483
+ const state = this.executions.get(executionId);
484
+ if (state?.status === 'running') {
485
+ state.status = 'cancelled';
486
+ state.completedAt = new Date().toISOString();
487
+ this.parallelExecutor.cancel();
488
+ }
489
+ }
490
+
491
+ /**
492
+ * Get execution status
493
+ */
494
+ async getStatus(executionId: string): Promise<ExecutionStatus | undefined> {
495
+ const state = this.executions.get(executionId);
496
+ if (state === undefined) return undefined;
497
+
498
+ const completedSteps = state.stepResults.filter((r) => r.success || r.skipped).length;
499
+ const totalSteps = state.stepResults.length;
500
+
501
+ return {
502
+ executionId: state.executionId,
503
+ agentId: state.agentId,
504
+ status: state.status,
505
+ currentStep: state.currentStep,
506
+ startedAt: state.startedAt,
507
+ completedAt: state.completedAt,
508
+ progress: {
509
+ totalSteps,
510
+ completedSteps,
511
+ currentStepName: state.currentStep,
512
+ percentComplete: totalSteps > 0 ? Math.round((completedSteps / totalSteps) * 100) : 0,
513
+ },
514
+ };
515
+ }
516
+
517
+ /**
518
+ * Register custom step executor
519
+ */
520
+ registerStepExecutor(type: string, executor: StepExecutorFn): void {
521
+ this.stepExecutors.set(type, executor);
522
+ }
523
+
524
+ /**
525
+ * Create step execution context
526
+ */
527
+ private createStepContext(
528
+ agentId: string,
529
+ executionId: string,
530
+ input: unknown,
531
+ previousOutputs: Record<string, unknown>,
532
+ options?: AgentRunOptions
533
+ ): StepExecutionContext {
534
+ return {
535
+ agentId,
536
+ executionId,
537
+ sessionId: options?.sessionId,
538
+ input,
539
+ previousOutputs,
540
+ memory: undefined,
541
+ provider: options?.provider,
542
+ model: options?.model,
543
+ delegationContext: options?.delegationContext,
544
+ };
545
+ }
546
+
547
+ /**
548
+ * Execute a single step with retry
549
+ */
550
+ private async executeStep(
551
+ step: AgentWorkflowStep,
552
+ context: StepExecutionContext,
553
+ maxAttempts: number
554
+ ): Promise<StepExecutionResult> {
555
+ const executor = this.stepExecutors.get(step.type);
556
+ if (executor === undefined) {
557
+ return {
558
+ success: false,
559
+ error: {
560
+ code: AgentErrorCode.AGENT_STAGE_FAILED,
561
+ message: `No executor for step type: ${step.type}`,
562
+ stepId: step.stepId,
563
+ },
564
+ durationMs: 0,
565
+ retryCount: 0,
566
+ };
567
+ }
568
+
569
+ let lastError: AgentError | undefined;
570
+ let retryCount = 0;
571
+
572
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
573
+ const timeout = step.timeoutMs ?? this.config.defaultTimeout;
574
+ const { promise: timeoutPromise, cancel: cancelTimeout } = this.createTimeout(timeout, step.stepId);
575
+
576
+ try {
577
+ const result = await Promise.race([
578
+ executor(step, context),
579
+ timeoutPromise,
580
+ ]);
581
+ cancelTimeout(); // Clean up the timer
582
+
583
+ if (result.success) {
584
+ return { ...result, retryCount };
585
+ }
586
+
587
+ lastError = result.error;
588
+ retryCount++;
589
+
590
+ // Backoff
591
+ if (attempt < maxAttempts - 1 && step.retryPolicy !== undefined) {
592
+ const backoff = step.retryPolicy.backoffMs *
593
+ Math.pow(step.retryPolicy.backoffMultiplier, attempt);
594
+ await this.sleep(backoff);
595
+ }
596
+ } catch (error) {
597
+ cancelTimeout(); // Clean up the timer on error too
598
+ lastError = {
599
+ code: AgentErrorCode.AGENT_STAGE_FAILED,
600
+ message: error instanceof Error ? error.message : 'Unknown error',
601
+ stepId: step.stepId,
602
+ };
603
+ retryCount++;
604
+ }
605
+ }
606
+
607
+ return { success: false, error: lastError, durationMs: 0, retryCount };
608
+ }
609
+
610
+ /**
611
+ * Check step dependencies
612
+ */
613
+ private checkDependencies(step: AgentWorkflowStep, results: StepResult[]): boolean {
614
+ if (step.dependencies === undefined || step.dependencies.length === 0) {
615
+ return true;
616
+ }
617
+ return step.dependencies.every((depId) => {
618
+ const depResult = results.find((r) => r.stepId === depId);
619
+ return depResult !== undefined && depResult.success;
620
+ });
621
+ }
622
+
623
+ /**
624
+ * Evaluate a condition expression safely without using eval/Function
625
+ *
626
+ * Supports patterns:
627
+ * - ${variable} === value
628
+ * - ${variable} !== value
629
+ * - ${variable} > value
630
+ * - ${variable} < value
631
+ * - ${variable} >= value
632
+ * - ${variable} <= value
633
+ * - ${variable} (truthy check)
634
+ * - !${variable} (falsy check)
635
+ * - condition && condition
636
+ * - condition || condition
637
+ */
638
+ private evaluateCondition(condition: string, outputs: Record<string, unknown>): boolean {
639
+ try {
640
+ // Handle logical operators by recursively evaluating sub-conditions
641
+ // Check for || first (lower precedence)
642
+ const orParts = this.splitByOperator(condition, '||');
643
+ if (orParts.length > 1) {
644
+ return orParts.some((part) => this.evaluateCondition(part.trim(), outputs));
645
+ }
646
+
647
+ // Check for && (higher precedence than ||)
648
+ const andParts = this.splitByOperator(condition, '&&');
649
+ if (andParts.length > 1) {
650
+ return andParts.every((part) => this.evaluateCondition(part.trim(), outputs));
651
+ }
652
+
653
+ // Handle negation
654
+ const trimmed = condition.trim();
655
+ if (trimmed.startsWith('!')) {
656
+ return !this.evaluateCondition(trimmed.slice(1).trim(), outputs);
657
+ }
658
+
659
+ // Handle parentheses
660
+ if (trimmed.startsWith('(') && trimmed.endsWith(')')) {
661
+ return this.evaluateCondition(trimmed.slice(1, -1), outputs);
662
+ }
663
+
664
+ // Parse comparison expression: ${variable} op value
665
+ const comparisonMatch = /^\$\{(\w+(?:\.\w+)*)\}\s*(===|!==|==|!=|>=|<=|>|<)\s*(.+)$/.exec(trimmed);
666
+ if (comparisonMatch) {
667
+ const [, keyPath, op, rawValue] = comparisonMatch;
668
+ const actualValue = this.getConditionValue(outputs, keyPath!);
669
+ const expectedValue = this.parseConditionValue(rawValue!.trim());
670
+ return this.compareConditionValues(actualValue, expectedValue, op!);
671
+ }
672
+
673
+ // Handle simple variable reference (truthy check): ${variable}
674
+ const varMatch = /^\$\{(\w+(?:\.\w+)*)\}$/.exec(trimmed);
675
+ if (varMatch) {
676
+ const value = this.getConditionValue(outputs, varMatch[1]!);
677
+ return Boolean(value);
678
+ }
679
+
680
+ // Handle literal boolean
681
+ if (trimmed === 'true') return true;
682
+ if (trimmed === 'false') return false;
683
+
684
+ // Unknown pattern - fail safely
685
+ console.warn(`[evaluateCondition] Unknown condition pattern: ${condition}`);
686
+ return false;
687
+ } catch (error) {
688
+ console.warn(`[evaluateCondition] Error evaluating condition: ${condition}`, error);
689
+ return false;
690
+ }
691
+ }
692
+
693
+ /**
694
+ * Split condition by operator, respecting parentheses
695
+ */
696
+ private splitByOperator(condition: string, operator: string): string[] {
697
+ const parts: string[] = [];
698
+ let current = '';
699
+ let depth = 0;
700
+ let i = 0;
701
+
702
+ while (i < condition.length) {
703
+ const char = condition[i];
704
+
705
+ if (char === '(') {
706
+ depth++;
707
+ current += char;
708
+ } else if (char === ')') {
709
+ depth--;
710
+ current += char;
711
+ } else if (depth === 0 && condition.slice(i, i + operator.length) === operator) {
712
+ parts.push(current);
713
+ current = '';
714
+ i += operator.length;
715
+ continue;
716
+ } else {
717
+ current += char;
718
+ }
719
+ i++;
720
+ }
721
+
722
+ if (current) {
723
+ parts.push(current);
724
+ }
725
+
726
+ return parts;
727
+ }
728
+
729
+ /**
730
+ * Get nested value from object using dot notation (for condition evaluation)
731
+ */
732
+ private getConditionValue(obj: Record<string, unknown>, path: string): unknown {
733
+ const parts = path.split('.');
734
+ let current: unknown = obj;
735
+
736
+ for (const part of parts) {
737
+ if (current === null || current === undefined) {
738
+ return undefined;
739
+ }
740
+ current = (current as Record<string, unknown>)[part];
741
+ }
742
+
743
+ return current;
744
+ }
745
+
746
+ /**
747
+ * Parse a value string into its actual type for condition evaluation
748
+ */
749
+ private parseConditionValue(value: string): unknown {
750
+ // String literal
751
+ if ((value.startsWith('"') && value.endsWith('"')) ||
752
+ (value.startsWith("'") && value.endsWith("'"))) {
753
+ return value.slice(1, -1);
754
+ }
755
+
756
+ // Null/undefined
757
+ if (value === 'null') return null;
758
+ if (value === 'undefined') return undefined;
759
+
760
+ // Boolean
761
+ if (value === 'true') return true;
762
+ if (value === 'false') return false;
763
+
764
+ // Number
765
+ const num = Number(value);
766
+ if (!isNaN(num)) return num;
767
+
768
+ // Default to string
769
+ return value;
770
+ }
771
+
772
+ /**
773
+ * Compare two values using the specified operator
774
+ */
775
+ private compareConditionValues(actual: unknown, expected: unknown, op: string): boolean {
776
+ switch (op) {
777
+ case '===':
778
+ return actual === expected;
779
+ case '!==':
780
+ return actual !== expected;
781
+ case '==':
782
+ return actual == expected;
783
+ case '!=':
784
+ return actual != expected;
785
+ case '>':
786
+ return (actual as number) > (expected as number);
787
+ case '<':
788
+ return (actual as number) < (expected as number);
789
+ case '>=':
790
+ return (actual as number) >= (expected as number);
791
+ case '<=':
792
+ return (actual as number) <= (expected as number);
793
+ default:
794
+ return false;
795
+ }
796
+ }
797
+
798
+ /**
799
+ * Collect outputs from results
800
+ */
801
+ private collectOutputs(
802
+ results: StepResult[],
803
+ _previousOutputs: Record<string, unknown>
804
+ ): Record<string, unknown> {
805
+ const output: Record<string, unknown> = {};
806
+ for (const result of results) {
807
+ if (result.success && result.output !== undefined) {
808
+ output[result.stepId] = result.output;
809
+ }
810
+ }
811
+ return output;
812
+ }
813
+
814
+ /**
815
+ * Build final result
816
+ */
817
+ private buildResult(
818
+ agentId: string,
819
+ stepResults: StepResult[],
820
+ state: ExecutionState,
821
+ options: AgentRunOptions | undefined,
822
+ startTime: number
823
+ ): AgentResult {
824
+ const allSuccess = stepResults.every((r) => r.success || r.skipped);
825
+ state.status = allSuccess ? 'completed' : 'failed';
826
+ state.completedAt = new Date().toISOString();
827
+ state.stepResults = stepResults;
828
+
829
+ return {
830
+ success: allSuccess,
831
+ agentId,
832
+ sessionId: options?.sessionId,
833
+ output: this.collectOutputs(stepResults, state.previousOutputs),
834
+ stepResults,
835
+ totalDurationMs: Date.now() - startTime,
836
+ error: allSuccess ? undefined : stepResults.find((r) => r.error !== undefined)?.error,
837
+ };
838
+ }
839
+
840
+ /**
841
+ * Create error result
842
+ */
843
+ private createErrorResult(
844
+ agentId: string,
845
+ startTime: number,
846
+ code: string,
847
+ message: string
848
+ ): AgentResult {
849
+ return {
850
+ success: false,
851
+ agentId,
852
+ error: { code, message },
853
+ totalDurationMs: Date.now() - startTime,
854
+ };
855
+ }
856
+
857
+ /**
858
+ * Create skipped result
859
+ */
860
+ private createSkippedResult(stepId: string, reason?: string): StepResult {
861
+ return {
862
+ stepId,
863
+ success: reason === undefined,
864
+ durationMs: 0,
865
+ retryCount: 0,
866
+ skipped: true,
867
+ error: reason ? { code: AgentErrorCode.AGENT_DEPENDENCY_FAILED, message: reason } : undefined,
868
+ };
869
+ }
870
+
871
+ /**
872
+ * Create timeout promise
873
+ */
874
+ private createTimeout(ms: number, stepId: string): {
875
+ promise: Promise<StepExecutionResult>;
876
+ cancel: () => void;
877
+ } {
878
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
879
+
880
+ const promise = new Promise<StepExecutionResult>((_, reject) => {
881
+ timeoutId = setTimeout(() => { reject({
882
+ success: false,
883
+ error: {
884
+ code: AgentErrorCode.AGENT_STAGE_FAILED,
885
+ message: `Step "${stepId}" timed out after ${ms}ms`,
886
+ stepId,
887
+ retryable: true,
888
+ },
889
+ durationMs: ms,
890
+ retryCount: 0,
891
+ }); }, ms);
892
+ });
893
+
894
+ const cancel = (): void => {
895
+ if (timeoutId !== undefined) {
896
+ clearTimeout(timeoutId);
897
+ }
898
+ };
899
+
900
+ return { promise, cancel };
901
+ }
902
+
903
+ /**
904
+ * Sleep utility
905
+ */
906
+ private sleep(ms: number): Promise<void> {
907
+ return new Promise((resolve) => setTimeout(resolve, ms));
908
+ }
909
+
910
+ /**
911
+ * Substitute variables in template
912
+ */
913
+ private substituteVariables(
914
+ template: string,
915
+ context: StepExecutionContext,
916
+ agentProfile: { description?: string; systemPrompt?: string; agentId: string }
917
+ ): string {
918
+ return template.replace(/\$\{([^}]+)\}/g, (match, path) => {
919
+ const parts = path.split('.');
920
+ let value: unknown;
921
+
922
+ switch (parts[0]) {
923
+ case 'input':
924
+ if (parts.length === 1) {
925
+ value = typeof context.input === 'string'
926
+ ? context.input
927
+ : JSON.stringify(context.input, null, 2);
928
+ } else {
929
+ value = this.getNestedValue(context.input, parts.slice(1));
930
+ }
931
+ break;
932
+ case 'previousOutputs':
933
+ if (parts.length > 1) {
934
+ value = this.getNestedValue(context.previousOutputs, parts.slice(1));
935
+ }
936
+ break;
937
+ case 'agent':
938
+ if (parts.length > 1) {
939
+ value = this.getNestedValue(agentProfile, parts.slice(1));
940
+ }
941
+ break;
942
+ default:
943
+ value = this.getNestedValue(context.input, parts);
944
+ }
945
+
946
+ if (value === undefined) return match;
947
+ return typeof value === 'string' ? value : JSON.stringify(value);
948
+ });
949
+ }
950
+
951
+ /**
952
+ * Get nested value from object
953
+ */
954
+ private getNestedValue(obj: unknown, path: string[]): unknown {
955
+ let current: unknown = obj;
956
+ for (const key of path) {
957
+ if (current === null || current === undefined) return undefined;
958
+ if (typeof current !== 'object') return undefined;
959
+ current = (current as Record<string, unknown>)[key];
960
+ }
961
+ return current;
962
+ }
963
+
964
+ /**
965
+ * Register all default step executors
966
+ */
967
+ private registerDefaultExecutors(): void {
968
+ // Prompt step executor with ability injection
969
+ // INV-AGT-ABL-001: Abilities injected before prompt execution
970
+ // INV-AGT-ABL-002: Core abilities from agent profile included
971
+ // INV-AGT-ABL-003: Token limits respected
972
+ this.stepExecutors.set('prompt', async (step, context) => {
973
+ const startTime = Date.now();
974
+ const config = (step.config ?? {}) as PromptStepConfig;
975
+
976
+ const agent = await this.registry.get(context.agentId);
977
+ const agentProfile: { description?: string; systemPrompt?: string; agentId: string } = {
978
+ agentId: context.agentId,
979
+ };
980
+ if (agent?.description !== undefined) agentProfile.description = agent.description;
981
+ if (agent?.systemPrompt !== undefined) agentProfile.systemPrompt = agent.systemPrompt;
982
+
983
+ // INV-AGT-ABL-001: Inject abilities if ability manager is available
984
+ let abilityContent = '';
985
+ if (this.enableAbilityInjection && this.abilityManager !== undefined) {
986
+ try {
987
+ // INV-AGT-ABL-002: Core abilities from agent profile
988
+ const coreAbilities = agent?.abilities?.core ?? [];
989
+
990
+ // Determine task from input for relevance matching
991
+ const task = typeof context.input === 'string'
992
+ ? context.input
993
+ : typeof context.input === 'object' && context.input !== null && 'task' in context.input
994
+ ? String((context.input as { task: unknown }).task)
995
+ : typeof context.input === 'object' && context.input !== null && 'prompt' in context.input
996
+ ? String((context.input as { prompt: unknown }).prompt)
997
+ : agent?.description ?? '';
998
+
999
+ // INV-AGT-ABL-003: Token limits respected
1000
+ const injectionResult = await this.abilityManager.injectAbilities(
1001
+ context.agentId,
1002
+ task,
1003
+ coreAbilities,
1004
+ {
1005
+ maxTokens: this.maxAbilityTokens,
1006
+ includeMetadata: true,
1007
+ }
1008
+ );
1009
+
1010
+ if (injectionResult.combinedContent.length > 0) {
1011
+ abilityContent = `\n\n## Relevant Knowledge & Abilities\n\n${injectionResult.combinedContent}\n\n---\n\n`;
1012
+ }
1013
+ } catch {
1014
+ // Ability injection failure should not block execution
1015
+ }
1016
+ }
1017
+
1018
+ // Build prompt with keyQuestions support
1019
+ let promptText: string;
1020
+ if (config.prompt) {
1021
+ promptText = this.substituteVariables(config.prompt, context, agentProfile);
1022
+ } else if (typeof context.input === 'string') {
1023
+ promptText = context.input;
1024
+ } else if (context.input && typeof context.input === 'object' && 'prompt' in context.input) {
1025
+ promptText = (context.input as { prompt: string }).prompt;
1026
+ } else {
1027
+ promptText = JSON.stringify(context.input, null, 2);
1028
+ }
1029
+
1030
+ // Append keyQuestions if defined
1031
+ if (step.keyQuestions && step.keyQuestions.length > 0) {
1032
+ promptText += '\n\nKey Questions to Address:\n' +
1033
+ step.keyQuestions.map((q, i) => `${i + 1}. ${q}`).join('\n');
1034
+ }
1035
+
1036
+ // Build system prompt with injected abilities
1037
+ const baseSystemPrompt = config.systemPrompt ?? agent?.systemPrompt ?? '';
1038
+ const systemPrompt = abilityContent
1039
+ ? `${baseSystemPrompt}${abilityContent}`
1040
+ : baseSystemPrompt || undefined;
1041
+
1042
+ const response = await this.promptExecutor.execute({
1043
+ prompt: promptText,
1044
+ systemPrompt: systemPrompt || undefined,
1045
+ provider: config.provider ?? context.provider,
1046
+ model: config.model ?? context.model,
1047
+ maxTokens: config.maxTokens,
1048
+ temperature: config.temperature,
1049
+ timeout: config.timeout ?? step.timeoutMs,
1050
+ });
1051
+
1052
+ if (response.success) {
1053
+ return {
1054
+ success: true,
1055
+ output: {
1056
+ content: response.content,
1057
+ provider: response.provider,
1058
+ model: response.model,
1059
+ usage: response.usage,
1060
+ },
1061
+ error: undefined,
1062
+ durationMs: Date.now() - startTime,
1063
+ retryCount: 0,
1064
+ };
1065
+ }
1066
+
1067
+ return {
1068
+ success: false,
1069
+ error: {
1070
+ code: response.errorCode ?? AgentErrorCode.AGENT_STAGE_FAILED,
1071
+ message: response.error ?? 'Prompt execution failed',
1072
+ stepId: step.stepId,
1073
+ retryable: true,
1074
+ },
1075
+ durationMs: Date.now() - startTime,
1076
+ retryCount: 0,
1077
+ };
1078
+ });
1079
+
1080
+ // Tool step executor (INV-TOOL-001, INV-TOOL-002, INV-TOOL-003)
1081
+ this.stepExecutors.set('tool', async (step, context) => {
1082
+ const startTime = Date.now();
1083
+ const config = step.config as {
1084
+ toolName?: string;
1085
+ toolInput?: Record<string, unknown>;
1086
+ } | undefined;
1087
+
1088
+ const toolName = config?.toolName;
1089
+ const toolInput = (config?.toolInput ?? context.input ?? {}) as Record<string, unknown>;
1090
+
1091
+ // If no tool executor is configured, return placeholder result
1092
+ if (this.toolExecutor === undefined) {
1093
+ return {
1094
+ success: true,
1095
+ output: {
1096
+ step: step.stepId,
1097
+ type: 'tool',
1098
+ toolName: toolName ?? 'unknown',
1099
+ toolInput,
1100
+ status: 'no_executor',
1101
+ message: 'Tool execution requires a ToolExecutor. Configure AgentDomainConfig.toolExecutor.',
1102
+ },
1103
+ error: undefined,
1104
+ durationMs: Date.now() - startTime,
1105
+ retryCount: 0,
1106
+ };
1107
+ }
1108
+
1109
+ // Check if tool name is provided
1110
+ if (toolName === undefined || toolName.trim() === '') {
1111
+ return {
1112
+ success: false,
1113
+ output: undefined,
1114
+ error: {
1115
+ code: 'TOOL_CONFIG_ERROR',
1116
+ message: `Tool step "${step.stepId}" requires toolName in config`,
1117
+ stepId: step.stepId,
1118
+ },
1119
+ durationMs: Date.now() - startTime,
1120
+ retryCount: 0,
1121
+ };
1122
+ }
1123
+
1124
+ // INV-TOOL-001: Check if tool is available
1125
+ if (!this.toolExecutor.isToolAvailable(toolName)) {
1126
+ return {
1127
+ success: false,
1128
+ output: undefined,
1129
+ error: {
1130
+ code: 'TOOL_NOT_FOUND',
1131
+ message: `Tool "${toolName}" is not available`,
1132
+ stepId: step.stepId,
1133
+ details: {
1134
+ availableTools: this.toolExecutor.getAvailableTools().slice(0, 10),
1135
+ },
1136
+ },
1137
+ durationMs: Date.now() - startTime,
1138
+ retryCount: 0,
1139
+ };
1140
+ }
1141
+
1142
+ // Execute the tool
1143
+ try {
1144
+ const result = await this.toolExecutor.execute(toolName, toolInput);
1145
+
1146
+ // INV-TOOL-002: Result is already frozen by executor
1147
+ return {
1148
+ success: result.success,
1149
+ output: {
1150
+ step: step.stepId,
1151
+ type: 'tool',
1152
+ toolName,
1153
+ toolOutput: result.output,
1154
+ durationMs: result.durationMs,
1155
+ },
1156
+ error: result.success ? undefined : {
1157
+ code: result.errorCode ?? 'TOOL_EXECUTION_ERROR',
1158
+ message: result.error ?? 'Tool execution failed',
1159
+ stepId: step.stepId,
1160
+ retryable: result.retryable ?? false,
1161
+ },
1162
+ durationMs: Date.now() - startTime,
1163
+ retryCount: 0,
1164
+ };
1165
+ } catch (error) {
1166
+ // INV-TOOL-003: Handle unexpected errors gracefully
1167
+ return {
1168
+ success: false,
1169
+ output: undefined,
1170
+ error: {
1171
+ code: 'TOOL_EXECUTION_ERROR',
1172
+ message: error instanceof Error ? error.message : 'Unknown tool execution error',
1173
+ stepId: step.stepId,
1174
+ retryable: true,
1175
+ },
1176
+ durationMs: Date.now() - startTime,
1177
+ retryCount: 0,
1178
+ };
1179
+ }
1180
+ });
1181
+
1182
+ // Conditional step executor
1183
+ this.stepExecutors.set('conditional', async (step, context) => {
1184
+ const startTime = Date.now();
1185
+ const config = step.config as {
1186
+ condition?: string;
1187
+ thenSteps?: string[];
1188
+ elseSteps?: string[];
1189
+ } | undefined;
1190
+
1191
+ const conditionResult = config?.condition
1192
+ ? this.evaluateCondition(config.condition, context.previousOutputs)
1193
+ : true;
1194
+
1195
+ return {
1196
+ success: true,
1197
+ output: {
1198
+ step: step.stepId,
1199
+ type: 'conditional',
1200
+ conditionMet: conditionResult,
1201
+ branch: conditionResult ? 'then' : 'else',
1202
+ nextSteps: conditionResult ? config?.thenSteps : config?.elseSteps,
1203
+ },
1204
+ error: undefined,
1205
+ durationMs: Date.now() - startTime,
1206
+ retryCount: 0,
1207
+ };
1208
+ });
1209
+
1210
+ // Loop step executor
1211
+ this.stepExecutors.set('loop', async (step, context) => {
1212
+ const startTime = Date.now();
1213
+ const config = step.config as {
1214
+ items?: unknown[];
1215
+ itemsPath?: string;
1216
+ maxIterations?: number;
1217
+ bodySteps?: string[];
1218
+ } | undefined;
1219
+
1220
+ // Get items to iterate over
1221
+ let items: unknown[] = config?.items ?? [];
1222
+ if (config?.itemsPath) {
1223
+ const pathParts = config.itemsPath.split('.');
1224
+ let value: unknown = context.previousOutputs;
1225
+ for (const part of pathParts) {
1226
+ if (value === null || value === undefined) break;
1227
+ value = (value as Record<string, unknown>)[part];
1228
+ }
1229
+ if (Array.isArray(value)) items = value;
1230
+ }
1231
+
1232
+ // Apply max iterations limit
1233
+ const maxIterations = config?.maxIterations ?? 100;
1234
+ const limitedItems = items.slice(0, maxIterations);
1235
+
1236
+ return {
1237
+ success: true,
1238
+ output: {
1239
+ step: step.stepId,
1240
+ type: 'loop',
1241
+ itemCount: limitedItems.length,
1242
+ items: limitedItems,
1243
+ bodySteps: config?.bodySteps,
1244
+ message: 'Loop step - items available for iteration',
1245
+ },
1246
+ error: undefined,
1247
+ durationMs: Date.now() - startTime,
1248
+ retryCount: 0,
1249
+ };
1250
+ });
1251
+
1252
+ // Parallel step executor (marker for parallel group)
1253
+ this.stepExecutors.set('parallel', async (step, _context) => {
1254
+ const startTime = Date.now();
1255
+ return {
1256
+ success: true,
1257
+ output: {
1258
+ step: step.stepId,
1259
+ type: 'parallel',
1260
+ message: 'Parallel step marker - child steps executed concurrently',
1261
+ },
1262
+ error: undefined,
1263
+ durationMs: Date.now() - startTime,
1264
+ retryCount: 0,
1265
+ };
1266
+ });
1267
+
1268
+ // Delegate step executor with full delegation tracking
1269
+ this.stepExecutors.set('delegate', async (step, context) => {
1270
+ const startTime = Date.now();
1271
+ const config = step.config as {
1272
+ targetAgentId?: string;
1273
+ task?: string;
1274
+ input?: unknown;
1275
+ } | undefined;
1276
+
1277
+ if (!config?.targetAgentId) {
1278
+ return {
1279
+ success: false,
1280
+ error: {
1281
+ code: AgentErrorCode.AGENT_STAGE_FAILED,
1282
+ message: 'Delegate step requires targetAgentId in config',
1283
+ stepId: step.stepId,
1284
+ },
1285
+ durationMs: Date.now() - startTime,
1286
+ retryCount: 0,
1287
+ };
1288
+ }
1289
+
1290
+ // Create delegation tracker using injected factory
1291
+ const delegationTracker = this.delegationTrackerFactory(
1292
+ context.agentId,
1293
+ context.delegationContext,
1294
+ this.config.maxDelegationDepth
1295
+ );
1296
+
1297
+ // Check delegation allowed (INV-DT-001, INV-DT-002)
1298
+ const checkResult = delegationTracker.canDelegate(config.targetAgentId);
1299
+ if (!checkResult.allowed) {
1300
+ return {
1301
+ success: false,
1302
+ error: {
1303
+ code: checkResult.reason === 'MAX_DEPTH_EXCEEDED'
1304
+ ? AgentErrorCode.AGENT_DELEGATION_DEPTH_EXCEEDED
1305
+ : AgentErrorCode.AGENT_STAGE_FAILED,
1306
+ message: checkResult.message ?? `Cannot delegate to ${config.targetAgentId}`,
1307
+ stepId: step.stepId,
1308
+ retryable: false,
1309
+ },
1310
+ durationMs: Date.now() - startTime,
1311
+ retryCount: 0,
1312
+ };
1313
+ }
1314
+
1315
+ // Check target exists
1316
+ const targetAgent = await this.registry.get(config.targetAgentId);
1317
+ if (targetAgent === undefined) {
1318
+ return {
1319
+ success: false,
1320
+ error: {
1321
+ code: AgentErrorCode.AGENT_NOT_FOUND,
1322
+ message: `Delegation target "${config.targetAgentId}" not found`,
1323
+ stepId: step.stepId,
1324
+ },
1325
+ durationMs: Date.now() - startTime,
1326
+ retryCount: 0,
1327
+ };
1328
+ }
1329
+
1330
+ // Create child context and execute
1331
+ const childContext = delegationTracker.createChildContext(config.targetAgentId);
1332
+
1333
+ try {
1334
+ const delegateResult = await this.execute(
1335
+ config.targetAgentId,
1336
+ config.input ?? context.input,
1337
+ {
1338
+ sessionId: context.sessionId,
1339
+ provider: context.provider,
1340
+ model: context.model,
1341
+ delegationContext: childContext,
1342
+ }
1343
+ );
1344
+
1345
+ delegationTracker.recordResult({
1346
+ success: delegateResult.success,
1347
+ handledBy: config.targetAgentId,
1348
+ result: delegateResult.output,
1349
+ durationMs: delegateResult.totalDurationMs,
1350
+ finalDepth: childContext.currentDepth,
1351
+ error: delegateResult.error ? {
1352
+ code: delegateResult.error.code,
1353
+ message: delegateResult.error.message,
1354
+ retryable: false,
1355
+ } : undefined,
1356
+ });
1357
+
1358
+ return {
1359
+ success: delegateResult.success,
1360
+ output: {
1361
+ step: step.stepId,
1362
+ type: 'delegate',
1363
+ delegatedTo: config.targetAgentId,
1364
+ result: delegateResult.output,
1365
+ delegationDepth: childContext.currentDepth,
1366
+ },
1367
+ error: delegateResult.error,
1368
+ durationMs: Date.now() - startTime,
1369
+ retryCount: 0,
1370
+ };
1371
+ } catch (error) {
1372
+ return {
1373
+ success: false,
1374
+ error: {
1375
+ code: AgentErrorCode.AGENT_STAGE_FAILED,
1376
+ message: error instanceof Error ? error.message : 'Delegation failed',
1377
+ stepId: step.stepId,
1378
+ },
1379
+ durationMs: Date.now() - startTime,
1380
+ retryCount: 0,
1381
+ };
1382
+ }
1383
+ });
1384
+ }
1385
+ }
1386
+
1387
+ /**
1388
+ * Creates an enhanced agent executor with full feature support
1389
+ */
1390
+ export function createEnhancedAgentExecutor(
1391
+ registry: AgentRegistry,
1392
+ config: EnhancedAgentDomainConfig
1393
+ ): AgentExecutor {
1394
+ return new EnhancedAgentExecutor(registry, config);
1395
+ }