@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.
- package/LICENSE +214 -0
- package/dist/enhanced-executor.d.ts +170 -0
- package/dist/enhanced-executor.d.ts.map +1 -0
- package/dist/enhanced-executor.js +1072 -0
- package/dist/enhanced-executor.js.map +1 -0
- package/dist/executor.d.ts +120 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +929 -0
- package/dist/executor.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +50 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +160 -0
- package/dist/loader.js.map +1 -0
- package/dist/persistent-registry.d.ts +105 -0
- package/dist/persistent-registry.d.ts.map +1 -0
- package/dist/persistent-registry.js +183 -0
- package/dist/persistent-registry.js.map +1 -0
- package/dist/production-factories.d.ts +70 -0
- package/dist/production-factories.d.ts.map +1 -0
- package/dist/production-factories.js +434 -0
- package/dist/production-factories.js.map +1 -0
- package/dist/prompt-executor.d.ts +119 -0
- package/dist/prompt-executor.d.ts.map +1 -0
- package/dist/prompt-executor.js +211 -0
- package/dist/prompt-executor.js.map +1 -0
- package/dist/registry.d.ts +57 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +123 -0
- package/dist/registry.js.map +1 -0
- package/dist/selection-service.d.ts +74 -0
- package/dist/selection-service.d.ts.map +1 -0
- package/dist/selection-service.js +322 -0
- package/dist/selection-service.js.map +1 -0
- package/dist/selector.d.ts +51 -0
- package/dist/selector.d.ts.map +1 -0
- package/dist/selector.js +249 -0
- package/dist/selector.js.map +1 -0
- package/dist/stub-checkpoint.d.ts +23 -0
- package/dist/stub-checkpoint.d.ts.map +1 -0
- package/dist/stub-checkpoint.js +137 -0
- package/dist/stub-checkpoint.js.map +1 -0
- package/dist/stub-delegation-tracker.d.ts +25 -0
- package/dist/stub-delegation-tracker.d.ts.map +1 -0
- package/dist/stub-delegation-tracker.js +118 -0
- package/dist/stub-delegation-tracker.js.map +1 -0
- package/dist/stub-parallel-executor.d.ts +19 -0
- package/dist/stub-parallel-executor.d.ts.map +1 -0
- package/dist/stub-parallel-executor.js +176 -0
- package/dist/stub-parallel-executor.js.map +1 -0
- package/dist/types.d.ts +614 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/dist/workflow-templates.d.ts +117 -0
- package/dist/workflow-templates.d.ts.map +1 -0
- package/dist/workflow-templates.js +342 -0
- package/dist/workflow-templates.js.map +1 -0
- package/package.json +51 -0
- package/src/enhanced-executor.ts +1395 -0
- package/src/executor.ts +1153 -0
- package/src/index.ts +172 -0
- package/src/loader.ts +191 -0
- package/src/persistent-registry.ts +235 -0
- package/src/production-factories.ts +613 -0
- package/src/prompt-executor.ts +310 -0
- package/src/registry.ts +167 -0
- package/src/selection-service.ts +411 -0
- package/src/selector.ts +299 -0
- package/src/stub-checkpoint.ts +187 -0
- package/src/stub-delegation-tracker.ts +161 -0
- package/src/stub-parallel-executor.ts +224 -0
- package/src/types.ts +784 -0
- 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
|