@ugm/desiagent 0.1.21
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/README.md +251 -0
- package/dist/__tests__/test-utils.d.ts +24 -0
- package/dist/__tests__/test-utils.d.ts.map +1 -0
- package/dist/__tests__/test-utils.js +32 -0
- package/dist/__tests__/test-utils.js.map +1 -0
- package/dist/core/execution/agents.d.ts +60 -0
- package/dist/core/execution/agents.d.ts.map +1 -0
- package/dist/core/execution/agents.js +249 -0
- package/dist/core/execution/agents.js.map +1 -0
- package/dist/core/execution/artifacts.d.ts +27 -0
- package/dist/core/execution/artifacts.d.ts.map +1 -0
- package/dist/core/execution/artifacts.js +93 -0
- package/dist/core/execution/artifacts.js.map +1 -0
- package/dist/core/execution/costs.d.ts +160 -0
- package/dist/core/execution/costs.d.ts.map +1 -0
- package/dist/core/execution/costs.js +196 -0
- package/dist/core/execution/costs.js.map +1 -0
- package/dist/core/execution/dagExecutor.d.ts +112 -0
- package/dist/core/execution/dagExecutor.d.ts.map +1 -0
- package/dist/core/execution/dagExecutor.js +647 -0
- package/dist/core/execution/dagExecutor.js.map +1 -0
- package/dist/core/execution/dags.d.ts +167 -0
- package/dist/core/execution/dags.d.ts.map +1 -0
- package/dist/core/execution/dags.js +713 -0
- package/dist/core/execution/dags.js.map +1 -0
- package/dist/core/execution/executions.d.ts +158 -0
- package/dist/core/execution/executions.d.ts.map +1 -0
- package/dist/core/execution/executions.js +258 -0
- package/dist/core/execution/executions.js.map +1 -0
- package/dist/core/execution/goals.d.ts +62 -0
- package/dist/core/execution/goals.d.ts.map +1 -0
- package/dist/core/execution/goals.js +245 -0
- package/dist/core/execution/goals.js.map +1 -0
- package/dist/core/execution/runs.d.ts +65 -0
- package/dist/core/execution/runs.d.ts.map +1 -0
- package/dist/core/execution/runs.js +219 -0
- package/dist/core/execution/runs.js.map +1 -0
- package/dist/core/execution/tools.d.ts +24 -0
- package/dist/core/execution/tools.d.ts.map +1 -0
- package/dist/core/execution/tools.js +33 -0
- package/dist/core/execution/tools.js.map +1 -0
- package/dist/core/orchestration/index.d.ts +7 -0
- package/dist/core/orchestration/index.d.ts.map +1 -0
- package/dist/core/orchestration/index.js +7 -0
- package/dist/core/orchestration/index.js.map +1 -0
- package/dist/core/orchestration/orchestrator.d.ts +39 -0
- package/dist/core/orchestration/orchestrator.d.ts.map +1 -0
- package/dist/core/orchestration/orchestrator.js +141 -0
- package/dist/core/orchestration/orchestrator.js.map +1 -0
- package/dist/core/orchestration/planner.d.ts +63 -0
- package/dist/core/orchestration/planner.d.ts.map +1 -0
- package/dist/core/orchestration/planner.js +99 -0
- package/dist/core/orchestration/planner.js.map +1 -0
- package/dist/core/providers/factory.d.ts +25 -0
- package/dist/core/providers/factory.d.ts.map +1 -0
- package/dist/core/providers/factory.js +86 -0
- package/dist/core/providers/factory.js.map +1 -0
- package/dist/core/providers/index.d.ts +11 -0
- package/dist/core/providers/index.d.ts.map +1 -0
- package/dist/core/providers/index.js +10 -0
- package/dist/core/providers/index.js.map +1 -0
- package/dist/core/providers/ollama.d.ts +36 -0
- package/dist/core/providers/ollama.d.ts.map +1 -0
- package/dist/core/providers/ollama.js +123 -0
- package/dist/core/providers/ollama.js.map +1 -0
- package/dist/core/providers/openai.d.ts +33 -0
- package/dist/core/providers/openai.d.ts.map +1 -0
- package/dist/core/providers/openai.js +114 -0
- package/dist/core/providers/openai.js.map +1 -0
- package/dist/core/providers/openrouter.d.ts +38 -0
- package/dist/core/providers/openrouter.d.ts.map +1 -0
- package/dist/core/providers/openrouter.js +269 -0
- package/dist/core/providers/openrouter.js.map +1 -0
- package/dist/core/providers/types.d.ts +101 -0
- package/dist/core/providers/types.d.ts.map +1 -0
- package/dist/core/providers/types.js +7 -0
- package/dist/core/providers/types.js.map +1 -0
- package/dist/core/tools/base.d.ts +56 -0
- package/dist/core/tools/base.d.ts.map +1 -0
- package/dist/core/tools/base.js +60 -0
- package/dist/core/tools/base.js.map +1 -0
- package/dist/core/tools/bash.d.ts +50 -0
- package/dist/core/tools/bash.d.ts.map +1 -0
- package/dist/core/tools/bash.js +179 -0
- package/dist/core/tools/bash.js.map +1 -0
- package/dist/core/tools/edit.d.ts +39 -0
- package/dist/core/tools/edit.d.ts.map +1 -0
- package/dist/core/tools/edit.js +67 -0
- package/dist/core/tools/edit.js.map +1 -0
- package/dist/core/tools/executor.d.ts +33 -0
- package/dist/core/tools/executor.d.ts.map +1 -0
- package/dist/core/tools/executor.js +105 -0
- package/dist/core/tools/executor.js.map +1 -0
- package/dist/core/tools/fetchPage.d.ts +46 -0
- package/dist/core/tools/fetchPage.d.ts.map +1 -0
- package/dist/core/tools/fetchPage.js +87 -0
- package/dist/core/tools/fetchPage.js.map +1 -0
- package/dist/core/tools/fetchURLs.d.ts +39 -0
- package/dist/core/tools/fetchURLs.d.ts.map +1 -0
- package/dist/core/tools/fetchURLs.js +67 -0
- package/dist/core/tools/fetchURLs.js.map +1 -0
- package/dist/core/tools/glob.d.ts +36 -0
- package/dist/core/tools/glob.d.ts.map +1 -0
- package/dist/core/tools/glob.js +78 -0
- package/dist/core/tools/glob.js.map +1 -0
- package/dist/core/tools/grep.d.ts +51 -0
- package/dist/core/tools/grep.d.ts.map +1 -0
- package/dist/core/tools/grep.js +152 -0
- package/dist/core/tools/grep.js.map +1 -0
- package/dist/core/tools/index.d.ts +22 -0
- package/dist/core/tools/index.d.ts.map +1 -0
- package/dist/core/tools/index.js +22 -0
- package/dist/core/tools/index.js.map +1 -0
- package/dist/core/tools/llmExecute.d.ts +153 -0
- package/dist/core/tools/llmExecute.d.ts.map +1 -0
- package/dist/core/tools/llmExecute.js +105 -0
- package/dist/core/tools/llmExecute.js.map +1 -0
- package/dist/core/tools/readEmail.d.ts +68 -0
- package/dist/core/tools/readEmail.d.ts.map +1 -0
- package/dist/core/tools/readEmail.js +182 -0
- package/dist/core/tools/readEmail.js.map +1 -0
- package/dist/core/tools/readFile.d.ts +42 -0
- package/dist/core/tools/readFile.d.ts.map +1 -0
- package/dist/core/tools/readFile.js +79 -0
- package/dist/core/tools/readFile.js.map +1 -0
- package/dist/core/tools/registry.d.ts +53 -0
- package/dist/core/tools/registry.d.ts.map +1 -0
- package/dist/core/tools/registry.js +112 -0
- package/dist/core/tools/registry.js.map +1 -0
- package/dist/core/tools/sendEmail.d.ts +83 -0
- package/dist/core/tools/sendEmail.d.ts.map +1 -0
- package/dist/core/tools/sendEmail.js +132 -0
- package/dist/core/tools/sendEmail.js.map +1 -0
- package/dist/core/tools/sendWebhook.d.ts +48 -0
- package/dist/core/tools/sendWebhook.d.ts.map +1 -0
- package/dist/core/tools/sendWebhook.js +61 -0
- package/dist/core/tools/sendWebhook.js.map +1 -0
- package/dist/core/tools/webSearch.d.ts +34 -0
- package/dist/core/tools/webSearch.d.ts.map +1 -0
- package/dist/core/tools/webSearch.js +104 -0
- package/dist/core/tools/webSearch.js.map +1 -0
- package/dist/core/tools/writeFile.d.ts +49 -0
- package/dist/core/tools/writeFile.d.ts.map +1 -0
- package/dist/core/tools/writeFile.js +120 -0
- package/dist/core/tools/writeFile.js.map +1 -0
- package/dist/db/client.d.ts +22 -0
- package/dist/db/client.d.ts.map +1 -0
- package/dist/db/client.js +178 -0
- package/dist/db/client.js.map +1 -0
- package/dist/db/schema.d.ts +1846 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +175 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/errors/index.d.ts +80 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +135 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +172 -0
- package/dist/index.js.map +1 -0
- package/dist/services/initDB.d.ts +17 -0
- package/dist/services/initDB.d.ts.map +1 -0
- package/dist/services/initDB.js +212 -0
- package/dist/services/initDB.js.map +1 -0
- package/dist/types/agent.d.ts +354 -0
- package/dist/types/agent.d.ts.map +1 -0
- package/dist/types/agent.js +63 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/client.d.ts +309 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/client.js +2 -0
- package/dist/types/client.js.map +1 -0
- package/dist/types/config.d.ts +96 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +33 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/dag.d.ts +253 -0
- package/dist/types/dag.d.ts.map +1 -0
- package/dist/types/dag.js +46 -0
- package/dist/types/dag.js.map +1 -0
- package/dist/types/execution.d.ts +171 -0
- package/dist/types/execution.d.ts.map +1 -0
- package/dist/types/execution.js +41 -0
- package/dist/types/execution.js.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +11 -0
- package/dist/types/index.js.map +1 -0
- package/dist/util/cron-validator.d.ts +16 -0
- package/dist/util/cron-validator.d.ts.map +1 -0
- package/dist/util/cron-validator.js +31 -0
- package/dist/util/cron-validator.js.map +1 -0
- package/dist/util/dag-utils.d.ts +54 -0
- package/dist/util/dag-utils.d.ts.map +1 -0
- package/dist/util/dag-utils.js +167 -0
- package/dist/util/dag-utils.js.map +1 -0
- package/dist/util/logger.d.ts +21 -0
- package/dist/util/logger.d.ts.map +1 -0
- package/dist/util/logger.js +87 -0
- package/dist/util/logger.js.map +1 -0
- package/package.json +82 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DAG Executor
|
|
3
|
+
*
|
|
4
|
+
* Executes DAG (Directed Acyclic Graph) workflows by running tasks
|
|
5
|
+
* in dependency order with parallel execution where possible.
|
|
6
|
+
*/
|
|
7
|
+
import { eq, and } from 'drizzle-orm';
|
|
8
|
+
import { nanoid } from 'nanoid';
|
|
9
|
+
import { dagExecutions, dagSubSteps, agents } from '../../db/schema.js';
|
|
10
|
+
import { LlmExecuteTool } from '../tools/llmExecute.js';
|
|
11
|
+
import { ExecutionsService } from './executions.js';
|
|
12
|
+
import { ExecutionEventType } from '../../types/execution.js';
|
|
13
|
+
import { getLogger } from '../../util/logger.js';
|
|
14
|
+
function generateSubStepId() {
|
|
15
|
+
return `substep_${nanoid(21)}`;
|
|
16
|
+
}
|
|
17
|
+
export class DAGExecutor {
|
|
18
|
+
db;
|
|
19
|
+
llmProvider;
|
|
20
|
+
toolRegistry;
|
|
21
|
+
artifactsDir;
|
|
22
|
+
logger = getLogger();
|
|
23
|
+
constructor(config) {
|
|
24
|
+
this.db = config.db;
|
|
25
|
+
this.llmProvider = config.llmProvider;
|
|
26
|
+
this.toolRegistry = config.toolRegistry;
|
|
27
|
+
this.artifactsDir = config.artifactsDir || process.env.ARTIFACTS_DIR || './artifacts';
|
|
28
|
+
this.logger.debug({
|
|
29
|
+
provider: this.llmProvider.name,
|
|
30
|
+
}, 'DAGExecutor created');
|
|
31
|
+
}
|
|
32
|
+
extractUrls(text) {
|
|
33
|
+
const urlRegex = /(https?:\/\/)?(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi;
|
|
34
|
+
const matches = text.match(urlRegex) || [];
|
|
35
|
+
return matches.map(url => {
|
|
36
|
+
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
37
|
+
return 'https://' + url;
|
|
38
|
+
}
|
|
39
|
+
return url;
|
|
40
|
+
}).filter(url => url.length > 0);
|
|
41
|
+
}
|
|
42
|
+
buildGlobalContext(job) {
|
|
43
|
+
const entitiesStr = job.entities.length > 0
|
|
44
|
+
? job.entities.map(e => `• ${e.entity} (${e.type}): ${e.grounded_value}`).join('\n')
|
|
45
|
+
: 'None';
|
|
46
|
+
const formatted = `# Global Context
|
|
47
|
+
**Request:** ${job.original_request}
|
|
48
|
+
**Primary Intent:** ${job.intent.primary}
|
|
49
|
+
**Sub-intents:** ${job.intent.sub_intents.join('; ') || 'None'}
|
|
50
|
+
**Entities:**
|
|
51
|
+
${entitiesStr}
|
|
52
|
+
**Synthesis Goal:** ${job.synthesis_plan}`;
|
|
53
|
+
return { formatted, totalTasks: job.sub_tasks.length };
|
|
54
|
+
}
|
|
55
|
+
buildInferencePrompt(task, globalContext, taskResults) {
|
|
56
|
+
const MAX_DEP_LENGTH = 2000;
|
|
57
|
+
const depsStr = task.dependencies
|
|
58
|
+
.filter(id => id !== 'none' && taskResults.has(id))
|
|
59
|
+
.map(id => {
|
|
60
|
+
const result = taskResults.get(id);
|
|
61
|
+
const str = typeof result === 'string' ? result : JSON.stringify(result);
|
|
62
|
+
return `[Task ${id}]: ${str.length > MAX_DEP_LENGTH ? str.slice(0, MAX_DEP_LENGTH) + '...' : str}`;
|
|
63
|
+
})
|
|
64
|
+
.join('\n\n') || 'None';
|
|
65
|
+
return `You are an expert assistant executing a sub-task within a larger workflow.
|
|
66
|
+
|
|
67
|
+
${globalContext.formatted}
|
|
68
|
+
|
|
69
|
+
# Current Task [${task.id}/${globalContext.totalTasks}]
|
|
70
|
+
**Description:** ${task.description}
|
|
71
|
+
**Reasoning:** ${task.thought}
|
|
72
|
+
**Expected Output:** ${task.expected_output}
|
|
73
|
+
|
|
74
|
+
# Dependencies
|
|
75
|
+
${depsStr}
|
|
76
|
+
|
|
77
|
+
# Instruction
|
|
78
|
+
${task.tool_or_prompt.params?.prompt || task.description}
|
|
79
|
+
|
|
80
|
+
Respond with ONLY the expected output format. Build upon dependencies for coherence and align with the global context.`;
|
|
81
|
+
}
|
|
82
|
+
resolveDependencies(task, taskResults) {
|
|
83
|
+
const params = task.tool_or_prompt.params || {};
|
|
84
|
+
const resolvedParams = { ...params };
|
|
85
|
+
let singleDependency = null;
|
|
86
|
+
for (const [key, value] of Object.entries(resolvedParams)) {
|
|
87
|
+
this.handleMultipleMatches(value, key, task, taskResults, resolvedParams);
|
|
88
|
+
}
|
|
89
|
+
return { resolvedParams, singleDependency };
|
|
90
|
+
}
|
|
91
|
+
handleMultipleMatches(value, key, task, taskResults, resolvedParams) {
|
|
92
|
+
const tool = task.tool_or_prompt.name;
|
|
93
|
+
const DEPENDENCY_PATTERN = /<Results? (?:from|of) Task (\d+)>/g;
|
|
94
|
+
const matches = [...String(value).matchAll(DEPENDENCY_PATTERN)];
|
|
95
|
+
if (tool === 'fetchURLs') {
|
|
96
|
+
resolvedParams[key] = this.resolveFetchURLs(task, key, taskResults);
|
|
97
|
+
}
|
|
98
|
+
else if (tool === 'writeFile' && key === 'content') {
|
|
99
|
+
resolvedParams[key] = this.resolveWriteFileContent(task, taskResults);
|
|
100
|
+
}
|
|
101
|
+
else if (tool === 'sendEmail' && key === 'attachments') {
|
|
102
|
+
if (Array.isArray(resolvedParams[key]) && resolvedParams[key].length > 0) {
|
|
103
|
+
resolvedParams[key][0]['content'] = this.resolveEmailContent(task, taskResults);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
resolvedParams[key] = this.resolveStringReplacements(value, matches, key, taskResults);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
resolveEmailContent(task, taskResults) {
|
|
111
|
+
const contentArray = [];
|
|
112
|
+
for (const deps of task.dependencies) {
|
|
113
|
+
const depResult = taskResults.get(deps);
|
|
114
|
+
if (typeof depResult === 'string') {
|
|
115
|
+
contentArray.push(depResult);
|
|
116
|
+
}
|
|
117
|
+
else if (typeof depResult === 'object' && depResult?.content) {
|
|
118
|
+
contentArray.push(depResult.content);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return contentArray.join('\n');
|
|
122
|
+
}
|
|
123
|
+
resolveWriteFileContent(task, taskResults) {
|
|
124
|
+
const contentArray = [];
|
|
125
|
+
for (const deps of task.dependencies) {
|
|
126
|
+
const depResult = taskResults.get(deps);
|
|
127
|
+
if (typeof depResult === 'string') {
|
|
128
|
+
this.logger.debug(`╰─dependency reference in: Task ${deps} - content`);
|
|
129
|
+
contentArray.push(depResult);
|
|
130
|
+
}
|
|
131
|
+
else if (typeof depResult === 'object' && depResult?.content) {
|
|
132
|
+
contentArray.push(depResult.content);
|
|
133
|
+
this.logger.debug(`╰─dependency reference in: Task ${deps} - content`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return contentArray.join('\n');
|
|
137
|
+
}
|
|
138
|
+
resolveFetchURLs(task, key, taskResults) {
|
|
139
|
+
const urlArray = [];
|
|
140
|
+
for (const deps of task.dependencies) {
|
|
141
|
+
const depResult = taskResults.get(deps);
|
|
142
|
+
const urls = Array.isArray(depResult)
|
|
143
|
+
? depResult.map((obj) => obj.url).filter(Boolean)
|
|
144
|
+
: typeof depResult === 'string'
|
|
145
|
+
? this.extractUrls(depResult)
|
|
146
|
+
: [];
|
|
147
|
+
if (urls.length) {
|
|
148
|
+
urlArray.push(...urls);
|
|
149
|
+
}
|
|
150
|
+
this.logger.debug(`╰─dependency reference in '${key}': Task ${deps} result has - ${urls.length} URLs`);
|
|
151
|
+
}
|
|
152
|
+
this.logger.debug(`╰─Total URLs resolved for ${key}: ${urlArray.length} sample ${urlArray[0]}`);
|
|
153
|
+
return urlArray;
|
|
154
|
+
}
|
|
155
|
+
resolveStringReplacements(value, matches, key, taskResults) {
|
|
156
|
+
if (typeof value !== 'string') {
|
|
157
|
+
return value;
|
|
158
|
+
}
|
|
159
|
+
let resolvedValue = value;
|
|
160
|
+
for (const match of matches) {
|
|
161
|
+
const depTaskId = match[1];
|
|
162
|
+
const depResult = taskResults.get(depTaskId);
|
|
163
|
+
if (depResult !== undefined) {
|
|
164
|
+
const replacementValue = typeof depResult === 'string' ? depResult : JSON.stringify(depResult);
|
|
165
|
+
resolvedValue = resolvedValue.replace(match[0], replacementValue);
|
|
166
|
+
this.logger.info(`╰─dependency reference in '${key}': Task ${depTaskId} - string replacements`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return resolvedValue;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Pre-fetch all agents needed for inference tasks
|
|
173
|
+
* Reduces DB queries during execution from O(n) to O(1) per agent
|
|
174
|
+
*/
|
|
175
|
+
async prefetchAgents(job) {
|
|
176
|
+
const agentNames = new Set();
|
|
177
|
+
for (const task of job.sub_tasks) {
|
|
178
|
+
if (task.action_type === 'inference' || task.tool_or_prompt.name === 'inference') {
|
|
179
|
+
agentNames.add(task.tool_or_prompt.name);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const agentMap = new Map();
|
|
183
|
+
if (agentNames.size === 0) {
|
|
184
|
+
return agentMap;
|
|
185
|
+
}
|
|
186
|
+
this.logger.debug({ agentNames: [...agentNames] }, 'Pre-fetching agents for inference tasks');
|
|
187
|
+
const fetchPromises = [...agentNames].map(async (name) => {
|
|
188
|
+
const agent = await this.db.query.agents.findFirst({
|
|
189
|
+
where: eq(agents.name, name),
|
|
190
|
+
});
|
|
191
|
+
if (agent && agent.provider && agent.model) {
|
|
192
|
+
agentMap.set(name, {
|
|
193
|
+
name: agent.name,
|
|
194
|
+
provider: agent.provider,
|
|
195
|
+
model: agent.model,
|
|
196
|
+
promptTemplate: agent.promptTemplate,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
await Promise.all(fetchPromises);
|
|
201
|
+
this.logger.debug({ cachedAgents: agentMap.size }, 'Agents pre-fetched');
|
|
202
|
+
return agentMap;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Conditionally emit event based on config
|
|
206
|
+
*/
|
|
207
|
+
emitEventIfEnabled(config, event) {
|
|
208
|
+
if (!config.skipEvents) {
|
|
209
|
+
ExecutionsService.emitEvent(event);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async execute(job, executionId, dagId, originalRequest, config = {}) {
|
|
213
|
+
const effectiveOriginalRequest = originalRequest || job.original_request;
|
|
214
|
+
const execConfig = {
|
|
215
|
+
skipEvents: config.skipEvents ?? false,
|
|
216
|
+
batchDbUpdates: config.batchDbUpdates ?? true,
|
|
217
|
+
};
|
|
218
|
+
if (job.clarification_needed) {
|
|
219
|
+
throw new Error(`Clarification needed: ${job.clarification_query}`);
|
|
220
|
+
}
|
|
221
|
+
const execId = executionId;
|
|
222
|
+
const startTime = Date.now();
|
|
223
|
+
this.logger.debug({
|
|
224
|
+
executionId: execId,
|
|
225
|
+
dagId,
|
|
226
|
+
totalTasks: job.sub_tasks.length,
|
|
227
|
+
primaryIntent: job.intent.primary,
|
|
228
|
+
config: execConfig,
|
|
229
|
+
}, 'Starting DAG execution');
|
|
230
|
+
try {
|
|
231
|
+
// Pre-fetch all agents needed for inference tasks
|
|
232
|
+
const agentCache = await this.prefetchAgents(job);
|
|
233
|
+
// Update execution status to running
|
|
234
|
+
await this.db.update(dagExecutions)
|
|
235
|
+
.set({ status: 'running', startedAt: new Date() })
|
|
236
|
+
.where(eq(dagExecutions.id, execId));
|
|
237
|
+
this.emitEventIfEnabled(execConfig, {
|
|
238
|
+
type: ExecutionEventType.Started,
|
|
239
|
+
executionId: execId,
|
|
240
|
+
timestamp: new Date(),
|
|
241
|
+
data: {
|
|
242
|
+
totalTasks: job.sub_tasks.length,
|
|
243
|
+
originalRequest: effectiveOriginalRequest,
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
const taskResults = new Map();
|
|
247
|
+
const executedTasks = new Set();
|
|
248
|
+
const globalContext = this.buildGlobalContext(job);
|
|
249
|
+
const canExecute = (task) => {
|
|
250
|
+
if (task.dependencies.length === 0 || task.dependencies.includes('none')) {
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
return task.dependencies.every(dep => executedTasks.has(dep));
|
|
254
|
+
};
|
|
255
|
+
const executeTask = async (task) => {
|
|
256
|
+
const symbols = {
|
|
257
|
+
writeFile: '📄',
|
|
258
|
+
readFile: '📖',
|
|
259
|
+
inference: '✨',
|
|
260
|
+
webSearch: '🔎',
|
|
261
|
+
fetchURLs: '🌐',
|
|
262
|
+
readEmail: '📧',
|
|
263
|
+
sendEmail: '✉️',
|
|
264
|
+
};
|
|
265
|
+
const displaySym = symbols[task.tool_or_prompt.name] || '⚙️';
|
|
266
|
+
this.logger.info(`${displaySym} Executing sub-task ${task.id} ${task.description.slice(0, 50)}...`);
|
|
267
|
+
// Skip individual DB update if batching enabled - will batch at wave end
|
|
268
|
+
if (!execConfig.batchDbUpdates) {
|
|
269
|
+
await this.db.update(dagSubSteps)
|
|
270
|
+
.set({ status: 'running', startedAt: new Date() })
|
|
271
|
+
.where(and(eq(dagSubSteps.taskId, task.id), eq(dagSubSteps.executionId, execId)));
|
|
272
|
+
}
|
|
273
|
+
this.emitEventIfEnabled(execConfig, {
|
|
274
|
+
type: ExecutionEventType.StepCompleted,
|
|
275
|
+
executionId: execId,
|
|
276
|
+
timestamp: new Date(),
|
|
277
|
+
data: {
|
|
278
|
+
subStepId: task.id,
|
|
279
|
+
status: 'started',
|
|
280
|
+
description: task.description,
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
const toolCtx = {
|
|
284
|
+
logger: this.logger,
|
|
285
|
+
db: this.db,
|
|
286
|
+
runId: `dag-${Date.now()}`,
|
|
287
|
+
abortSignal: new AbortController().signal,
|
|
288
|
+
executionId: execId,
|
|
289
|
+
subStepId: task.id,
|
|
290
|
+
artifactsDir: this.artifactsDir,
|
|
291
|
+
emitEvent: {
|
|
292
|
+
progress: (message) => {
|
|
293
|
+
this.emitEventIfEnabled(execConfig, {
|
|
294
|
+
type: ExecutionEventType.ToolCalled,
|
|
295
|
+
executionId: execId,
|
|
296
|
+
timestamp: new Date(),
|
|
297
|
+
data: { message, subStepId: task.id },
|
|
298
|
+
});
|
|
299
|
+
},
|
|
300
|
+
completed: (message) => {
|
|
301
|
+
this.emitEventIfEnabled(execConfig, {
|
|
302
|
+
type: ExecutionEventType.ToolCompleted,
|
|
303
|
+
executionId: execId,
|
|
304
|
+
timestamp: new Date(),
|
|
305
|
+
data: { message, subStepId: task.id },
|
|
306
|
+
});
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
if (task.action_type === 'tool' && task.tool_or_prompt.name !== 'inference') {
|
|
311
|
+
const tool = this.toolRegistry.get(task.tool_or_prompt.name);
|
|
312
|
+
if (!tool) {
|
|
313
|
+
throw new Error(`Tool not found: ${task.tool_or_prompt.name}`);
|
|
314
|
+
}
|
|
315
|
+
const { resolvedParams } = this.resolveDependencies(task, taskResults);
|
|
316
|
+
this.logger.debug(`Executing tool ${task.tool_or_prompt.name} with params: ${JSON.stringify(resolvedParams)} before validation`);
|
|
317
|
+
const validatedInput = tool.inputSchema.parse(resolvedParams);
|
|
318
|
+
const result = await tool.execute(validatedInput, toolCtx);
|
|
319
|
+
return { content: result };
|
|
320
|
+
}
|
|
321
|
+
else if (task.action_type === 'inference' || task.tool_or_prompt.name === 'inference') {
|
|
322
|
+
const fullPrompt = this.buildInferencePrompt(task, globalContext, taskResults);
|
|
323
|
+
const agentName = task.tool_or_prompt.name;
|
|
324
|
+
// Use pre-fetched agent from cache instead of DB query
|
|
325
|
+
const agent = agentCache.get(agentName);
|
|
326
|
+
if (!agent) {
|
|
327
|
+
throw new Error(`No agent found with name: ${agentName} (not in pre-fetch cache)`);
|
|
328
|
+
}
|
|
329
|
+
const llmExecuteTool = new LlmExecuteTool();
|
|
330
|
+
const result = await llmExecuteTool.execute({
|
|
331
|
+
provider: agent.provider,
|
|
332
|
+
model: agent.model,
|
|
333
|
+
task: agent.promptTemplate,
|
|
334
|
+
prompt: fullPrompt,
|
|
335
|
+
}, toolCtx);
|
|
336
|
+
return {
|
|
337
|
+
content: result.content,
|
|
338
|
+
usage: result.usage,
|
|
339
|
+
costUsd: result.costUsd,
|
|
340
|
+
generationStats: result.generationStats,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
throw new Error(`Unknown action type: ${task.action_type}`);
|
|
344
|
+
};
|
|
345
|
+
// Execute tasks in dependency order with wave-based batching
|
|
346
|
+
while (executedTasks.size < job.sub_tasks.length) {
|
|
347
|
+
const readyTasks = job.sub_tasks.filter(task => !executedTasks.has(task.id) && canExecute(task));
|
|
348
|
+
if (readyTasks.length === 0) {
|
|
349
|
+
const remaining = job.sub_tasks.filter(task => !executedTasks.has(task.id));
|
|
350
|
+
throw new Error(`DAG execution deadlock. Remaining tasks: ${remaining.map(t => t.id).join(', ')}`);
|
|
351
|
+
}
|
|
352
|
+
// Collect wave results for batch DB update
|
|
353
|
+
const waveResults = [];
|
|
354
|
+
const waveStartTime = Date.now();
|
|
355
|
+
// Batch update all tasks in wave to 'running' status if batching enabled
|
|
356
|
+
if (execConfig.batchDbUpdates && readyTasks.length > 0) {
|
|
357
|
+
const updatePromises = readyTasks.map(task => this.db.update(dagSubSteps)
|
|
358
|
+
.set({ status: 'running', startedAt: new Date() })
|
|
359
|
+
.where(and(eq(dagSubSteps.taskId, task.id), eq(dagSubSteps.executionId, execId))));
|
|
360
|
+
await Promise.all(updatePromises);
|
|
361
|
+
this.logger.debug({ waveSize: readyTasks.length }, 'Batch updated wave tasks to running');
|
|
362
|
+
}
|
|
363
|
+
await Promise.all(readyTasks.map(async (task) => {
|
|
364
|
+
const taskExecStartTime = Date.now();
|
|
365
|
+
const waveResult = { taskId: task.id, startTime: taskExecStartTime };
|
|
366
|
+
waveResults.push(waveResult);
|
|
367
|
+
try {
|
|
368
|
+
const execResult = await executeTask(task);
|
|
369
|
+
taskResults.set(task.id, execResult.content);
|
|
370
|
+
executedTasks.add(task.id);
|
|
371
|
+
waveResult.result = execResult;
|
|
372
|
+
const serializedResult = typeof execResult.content === 'string'
|
|
373
|
+
? execResult.content
|
|
374
|
+
: JSON.stringify(execResult.content);
|
|
375
|
+
this.logger.debug({ taskId: task.id, result: serializedResult }, `╰─task ${task.id} result after executeTask():`);
|
|
376
|
+
// Skip individual DB update if batching - will batch at wave end
|
|
377
|
+
if (!execConfig.batchDbUpdates) {
|
|
378
|
+
await this.db.update(dagSubSteps)
|
|
379
|
+
.set({
|
|
380
|
+
status: 'completed',
|
|
381
|
+
result: serializedResult,
|
|
382
|
+
completedAt: new Date(),
|
|
383
|
+
durationMs: Date.now() - taskExecStartTime,
|
|
384
|
+
usage: execResult.usage,
|
|
385
|
+
costUsd: execResult.costUsd?.toString(),
|
|
386
|
+
generationStats: execResult.generationStats,
|
|
387
|
+
})
|
|
388
|
+
.where(and(eq(dagSubSteps.taskId, task.id), eq(dagSubSteps.executionId, execId)));
|
|
389
|
+
}
|
|
390
|
+
this.emitEventIfEnabled(execConfig, {
|
|
391
|
+
type: ExecutionEventType.StepCompleted,
|
|
392
|
+
executionId: execId,
|
|
393
|
+
timestamp: new Date(),
|
|
394
|
+
data: {
|
|
395
|
+
subStepId: task.id,
|
|
396
|
+
status: 'completed',
|
|
397
|
+
durationMs: Date.now() - taskExecStartTime,
|
|
398
|
+
usage: execResult.usage,
|
|
399
|
+
costUsd: execResult.costUsd?.toString(),
|
|
400
|
+
generationStats: execResult.generationStats,
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
406
|
+
this.logger.error({ err: errorMessage, taskId: task.id }, `Task ${task.id} failed`);
|
|
407
|
+
waveResult.error = errorMessage;
|
|
408
|
+
// Immediate update for failures (important for debugging)
|
|
409
|
+
await this.db.update(dagSubSteps)
|
|
410
|
+
.set({
|
|
411
|
+
status: 'failed',
|
|
412
|
+
error: errorMessage,
|
|
413
|
+
completedAt: new Date(),
|
|
414
|
+
durationMs: Date.now() - taskExecStartTime,
|
|
415
|
+
})
|
|
416
|
+
.where(and(eq(dagSubSteps.taskId, task.id), eq(dagSubSteps.executionId, execId)));
|
|
417
|
+
this.emitEventIfEnabled(execConfig, {
|
|
418
|
+
type: ExecutionEventType.StepFailed,
|
|
419
|
+
executionId: execId,
|
|
420
|
+
timestamp: new Date(),
|
|
421
|
+
data: {
|
|
422
|
+
subStepId: task.id,
|
|
423
|
+
},
|
|
424
|
+
error: {
|
|
425
|
+
message: errorMessage,
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
throw error;
|
|
429
|
+
}
|
|
430
|
+
}));
|
|
431
|
+
// Batch update completed tasks at wave end
|
|
432
|
+
if (execConfig.batchDbUpdates) {
|
|
433
|
+
const completedResults = waveResults.filter(r => r.result && !r.error);
|
|
434
|
+
if (completedResults.length > 0) {
|
|
435
|
+
const batchUpdatePromises = completedResults.map(wr => {
|
|
436
|
+
const serializedResult = typeof wr.result.content === 'string'
|
|
437
|
+
? wr.result.content
|
|
438
|
+
: JSON.stringify(wr.result.content);
|
|
439
|
+
return this.db.update(dagSubSteps)
|
|
440
|
+
.set({
|
|
441
|
+
status: 'completed',
|
|
442
|
+
result: serializedResult,
|
|
443
|
+
completedAt: new Date(),
|
|
444
|
+
durationMs: Date.now() - wr.startTime,
|
|
445
|
+
usage: wr.result.usage,
|
|
446
|
+
costUsd: wr.result.costUsd?.toString(),
|
|
447
|
+
generationStats: wr.result.generationStats,
|
|
448
|
+
})
|
|
449
|
+
.where(and(eq(dagSubSteps.taskId, wr.taskId), eq(dagSubSteps.executionId, execId)));
|
|
450
|
+
});
|
|
451
|
+
await Promise.all(batchUpdatePromises);
|
|
452
|
+
this.logger.debug({
|
|
453
|
+
waveSize: completedResults.length,
|
|
454
|
+
waveDurationMs: Date.now() - waveStartTime,
|
|
455
|
+
}, 'Batch updated wave completed tasks');
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
this.logger.info('All tasks completed, running synthesis');
|
|
460
|
+
const synthesisResult = await this.synthesize(job.synthesis_plan, taskResults, execId);
|
|
461
|
+
this.logger.info('╰─Synthesis completed, running validation');
|
|
462
|
+
const validatedResult = await this.validate(synthesisResult.content);
|
|
463
|
+
const allSubSteps = await this.db.query.dagSubSteps.findMany({
|
|
464
|
+
where: eq(dagSubSteps.executionId, execId),
|
|
465
|
+
});
|
|
466
|
+
const statusData = this.deriveExecutionStatus(allSubSteps);
|
|
467
|
+
const totalUsage = this.aggregateUsage(allSubSteps);
|
|
468
|
+
const totalCostUsd = this.aggregateCost(allSubSteps);
|
|
469
|
+
await this.db.update(dagExecutions)
|
|
470
|
+
.set({
|
|
471
|
+
status: statusData.status,
|
|
472
|
+
completedTasks: statusData.completedTasks,
|
|
473
|
+
failedTasks: statusData.failedTasks,
|
|
474
|
+
waitingTasks: statusData.waitingTasks,
|
|
475
|
+
finalResult: validatedResult,
|
|
476
|
+
synthesisResult: synthesisResult.content,
|
|
477
|
+
completedAt: new Date(),
|
|
478
|
+
durationMs: Date.now() - startTime,
|
|
479
|
+
totalUsage,
|
|
480
|
+
totalCostUsd: totalCostUsd?.toString(),
|
|
481
|
+
})
|
|
482
|
+
.where(eq(dagExecutions.id, execId));
|
|
483
|
+
if (statusData.status === 'completed' || statusData.status === 'partial') {
|
|
484
|
+
this.emitEventIfEnabled(execConfig, {
|
|
485
|
+
type: ExecutionEventType.Completed,
|
|
486
|
+
executionId: execId,
|
|
487
|
+
timestamp: new Date(),
|
|
488
|
+
data: {
|
|
489
|
+
status: statusData.status,
|
|
490
|
+
completedTasks: statusData.completedTasks,
|
|
491
|
+
failedTasks: statusData.failedTasks,
|
|
492
|
+
durationMs: Date.now() - startTime,
|
|
493
|
+
finalResult: validatedResult,
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
else if (statusData.status === 'failed') {
|
|
498
|
+
this.emitEventIfEnabled(execConfig, {
|
|
499
|
+
type: ExecutionEventType.Failed,
|
|
500
|
+
executionId: execId,
|
|
501
|
+
timestamp: new Date(),
|
|
502
|
+
error: {
|
|
503
|
+
message: 'Execution failed',
|
|
504
|
+
},
|
|
505
|
+
data: {
|
|
506
|
+
completedTasks: statusData.completedTasks,
|
|
507
|
+
failedTasks: statusData.failedTasks,
|
|
508
|
+
},
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
this.logger.info({ executionId: execId, status: statusData.status }, 'DAG execution completed');
|
|
512
|
+
return validatedResult;
|
|
513
|
+
}
|
|
514
|
+
catch (error) {
|
|
515
|
+
await this.suspendExecution(execId, error);
|
|
516
|
+
throw error;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
async suspendExecution(executionId, error) {
|
|
520
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
521
|
+
this.logger.error({ executionId, err: error }, 'Suspending execution due to error');
|
|
522
|
+
await this.db.update(dagExecutions)
|
|
523
|
+
.set({
|
|
524
|
+
status: 'suspended',
|
|
525
|
+
suspendedReason: errorMessage,
|
|
526
|
+
suspendedAt: new Date(),
|
|
527
|
+
})
|
|
528
|
+
.where(eq(dagExecutions.id, executionId));
|
|
529
|
+
ExecutionsService.emitEvent({
|
|
530
|
+
type: ExecutionEventType.Failed,
|
|
531
|
+
executionId,
|
|
532
|
+
timestamp: new Date(),
|
|
533
|
+
error: {
|
|
534
|
+
message: errorMessage,
|
|
535
|
+
},
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
deriveExecutionStatus(subSteps) {
|
|
539
|
+
const completed = subSteps.filter(s => s.status === 'completed').length;
|
|
540
|
+
const failed = subSteps.filter(s => s.status === 'failed').length;
|
|
541
|
+
const running = subSteps.filter(s => s.status === 'running').length;
|
|
542
|
+
const waiting = subSteps.filter(s => s.status === 'waiting').length;
|
|
543
|
+
const total = subSteps.length;
|
|
544
|
+
let status;
|
|
545
|
+
if (waiting > 0) {
|
|
546
|
+
status = 'waiting';
|
|
547
|
+
}
|
|
548
|
+
else if (failed > 0 && completed + failed === total) {
|
|
549
|
+
status = failed === total ? 'failed' : 'partial';
|
|
550
|
+
}
|
|
551
|
+
else if (completed === total) {
|
|
552
|
+
status = 'completed';
|
|
553
|
+
}
|
|
554
|
+
else if (running > 0 || completed > 0) {
|
|
555
|
+
status = 'running';
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
status = 'pending';
|
|
559
|
+
}
|
|
560
|
+
return { status, completedTasks: completed, failedTasks: failed, waitingTasks: waiting };
|
|
561
|
+
}
|
|
562
|
+
async synthesize(plan, taskResults, executionId) {
|
|
563
|
+
const context = Array.from(taskResults.entries())
|
|
564
|
+
.map(([taskId, result]) => {
|
|
565
|
+
const resultStr = typeof result === 'string'
|
|
566
|
+
? result
|
|
567
|
+
: JSON.stringify(result, null, 2);
|
|
568
|
+
return `Task ${taskId} result:\n${resultStr}`;
|
|
569
|
+
})
|
|
570
|
+
.join('\n\n');
|
|
571
|
+
const synthesisPrompt = `${plan}
|
|
572
|
+
|
|
573
|
+
Available task results:
|
|
574
|
+
${context}
|
|
575
|
+
|
|
576
|
+
Generate the final report in Markdown format as specified in the synthesis plan.`;
|
|
577
|
+
const startTime = Date.now();
|
|
578
|
+
const response = await this.llmProvider.chat({
|
|
579
|
+
messages: [
|
|
580
|
+
{
|
|
581
|
+
role: 'system',
|
|
582
|
+
content: 'You are a helpful assistant that synthesizes information into well-formatted Markdown reports.',
|
|
583
|
+
},
|
|
584
|
+
{ role: 'user', content: synthesisPrompt },
|
|
585
|
+
],
|
|
586
|
+
temperature: 0.5,
|
|
587
|
+
});
|
|
588
|
+
const synthesisSubStepId = generateSubStepId();
|
|
589
|
+
await this.db.insert(dagSubSteps).values({
|
|
590
|
+
id: synthesisSubStepId,
|
|
591
|
+
executionId,
|
|
592
|
+
taskId: '__SYNTHESIS__',
|
|
593
|
+
description: 'Final synthesis of all task results',
|
|
594
|
+
thought: 'Aggregating results into final output',
|
|
595
|
+
actionType: 'inference',
|
|
596
|
+
toolOrPromptName: '__synthesis__',
|
|
597
|
+
toolOrPromptParams: { taskCount: taskResults.size },
|
|
598
|
+
dependencies: Array.from(taskResults.keys()),
|
|
599
|
+
status: 'completed',
|
|
600
|
+
startedAt: new Date(startTime),
|
|
601
|
+
completedAt: new Date(),
|
|
602
|
+
durationMs: Date.now() - startTime,
|
|
603
|
+
usage: response.usage,
|
|
604
|
+
costUsd: response.costUsd?.toString(),
|
|
605
|
+
generationStats: response.generationStats,
|
|
606
|
+
result: response.content,
|
|
607
|
+
});
|
|
608
|
+
this.logger.debug({ synthesisSubStepId, usage: response.usage }, 'Synthesis sub-step created');
|
|
609
|
+
return {
|
|
610
|
+
content: response.content,
|
|
611
|
+
usage: response.usage,
|
|
612
|
+
costUsd: response.costUsd,
|
|
613
|
+
generationStats: response.generationStats,
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
async validate(output) {
|
|
617
|
+
this.logger.info('Validation step (pass-through)');
|
|
618
|
+
return output;
|
|
619
|
+
}
|
|
620
|
+
aggregateUsage(allSubSteps) {
|
|
621
|
+
let hasUsage = false;
|
|
622
|
+
let promptTokens = 0;
|
|
623
|
+
let completionTokens = 0;
|
|
624
|
+
let totalTokens = 0;
|
|
625
|
+
for (const step of allSubSteps) {
|
|
626
|
+
if (step.usage) {
|
|
627
|
+
hasUsage = true;
|
|
628
|
+
promptTokens += step.usage.promptTokens ?? 0;
|
|
629
|
+
completionTokens += step.usage.completionTokens ?? 0;
|
|
630
|
+
totalTokens += step.usage.totalTokens ?? 0;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return hasUsage ? { promptTokens, completionTokens, totalTokens } : null;
|
|
634
|
+
}
|
|
635
|
+
aggregateCost(allSubSteps) {
|
|
636
|
+
let totalCost = 0;
|
|
637
|
+
let hasCost = false;
|
|
638
|
+
for (const step of allSubSteps) {
|
|
639
|
+
if (step.costUsd) {
|
|
640
|
+
hasCost = true;
|
|
641
|
+
totalCost += parseFloat(step.costUsd);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return hasCost ? totalCost : null;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
//# sourceMappingURL=dagExecutor.js.map
|