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