@agentforge/patterns 0.16.0 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +425 -287
- package/dist/index.d.cts +103 -109
- package/dist/index.d.ts +103 -109
- package/dist/index.js +425 -287
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -790,7 +790,7 @@ var PlanStepSchema = z4.object({
|
|
|
790
790
|
/**
|
|
791
791
|
* Optional arguments for the tool
|
|
792
792
|
*/
|
|
793
|
-
args: z4.record(z4.
|
|
793
|
+
args: z4.record(z4.string(), z4.unknown()).optional().describe("Arguments to pass to the tool")
|
|
794
794
|
});
|
|
795
795
|
var CompletedStepSchema = z4.object({
|
|
796
796
|
/**
|
|
@@ -800,7 +800,7 @@ var CompletedStepSchema = z4.object({
|
|
|
800
800
|
/**
|
|
801
801
|
* The result of executing the step
|
|
802
802
|
*/
|
|
803
|
-
result: z4.
|
|
803
|
+
result: z4.unknown().describe("The result of executing the step"),
|
|
804
804
|
/**
|
|
805
805
|
* Whether the step succeeded
|
|
806
806
|
*/
|
|
@@ -977,6 +977,9 @@ var REMAINING_STEP_TEMPLATE = `Step {stepNumber}: {description}
|
|
|
977
977
|
var plannerLogger = createPatternLogger("agentforge:patterns:plan-execute:planner");
|
|
978
978
|
var executorLogger = createPatternLogger("agentforge:patterns:plan-execute:executor");
|
|
979
979
|
var replannerLogger = createPatternLogger("agentforge:patterns:plan-execute:replanner");
|
|
980
|
+
function invokePlanExecuteTool(tool, args) {
|
|
981
|
+
return tool.invoke.call(tool, args);
|
|
982
|
+
}
|
|
980
983
|
function createPlannerNode(config) {
|
|
981
984
|
const {
|
|
982
985
|
model,
|
|
@@ -1044,10 +1047,18 @@ function createExecutorNode(config) {
|
|
|
1044
1047
|
const {
|
|
1045
1048
|
tools,
|
|
1046
1049
|
model,
|
|
1047
|
-
parallel
|
|
1050
|
+
parallel,
|
|
1048
1051
|
stepTimeout = 3e4,
|
|
1049
1052
|
enableDeduplication = true
|
|
1050
1053
|
} = config;
|
|
1054
|
+
if (typeof model !== "undefined") {
|
|
1055
|
+
executorLogger.warn("ExecutorConfig.model is currently unsupported and will be ignored");
|
|
1056
|
+
}
|
|
1057
|
+
if (typeof parallel !== "undefined") {
|
|
1058
|
+
executorLogger.warn("ExecutorConfig.parallel is currently unsupported and will be ignored", {
|
|
1059
|
+
parallel
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1051
1062
|
return async (state) => {
|
|
1052
1063
|
const { plan, currentStepIndex = 0, pastSteps = [], iteration = 0 } = state;
|
|
1053
1064
|
try {
|
|
@@ -1124,13 +1135,20 @@ function createExecutorNode(config) {
|
|
|
1124
1135
|
throw new Error(`Tool not found: ${currentStep.tool}`);
|
|
1125
1136
|
}
|
|
1126
1137
|
const startTime = Date.now();
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1138
|
+
let timeoutId;
|
|
1139
|
+
try {
|
|
1140
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1141
|
+
timeoutId = setTimeout(() => reject(new Error("Step execution timeout")), stepTimeout);
|
|
1142
|
+
});
|
|
1143
|
+
result = await Promise.race([
|
|
1144
|
+
invokePlanExecuteTool(tool, currentStep.args || {}),
|
|
1145
|
+
timeoutPromise
|
|
1146
|
+
]);
|
|
1147
|
+
} finally {
|
|
1148
|
+
if (timeoutId !== void 0) {
|
|
1149
|
+
clearTimeout(timeoutId);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1134
1152
|
const executionTime = Date.now() - startTime;
|
|
1135
1153
|
executorLogger.debug("Step executed successfully", {
|
|
1136
1154
|
stepId: currentStep.id,
|
|
@@ -1187,9 +1205,14 @@ function createExecutorNode(config) {
|
|
|
1187
1205
|
function createReplannerNode(config) {
|
|
1188
1206
|
const {
|
|
1189
1207
|
model,
|
|
1190
|
-
replanThreshold
|
|
1208
|
+
replanThreshold,
|
|
1191
1209
|
systemPrompt = DEFAULT_REPLANNER_SYSTEM_PROMPT
|
|
1192
1210
|
} = config;
|
|
1211
|
+
if (typeof replanThreshold !== "undefined") {
|
|
1212
|
+
replannerLogger.warn("ReplannerConfig.replanThreshold is currently unsupported and will be ignored", {
|
|
1213
|
+
replanThreshold
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1193
1216
|
return async (state) => {
|
|
1194
1217
|
const startTime = Date.now();
|
|
1195
1218
|
try {
|
|
@@ -2558,16 +2581,82 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2558
2581
|
};
|
|
2559
2582
|
}
|
|
2560
2583
|
|
|
2561
|
-
// src/multi-agent/nodes.ts
|
|
2584
|
+
// src/multi-agent/nodes/shared.ts
|
|
2562
2585
|
import { HumanMessage as HumanMessage5, SystemMessage as SystemMessage5 } from "@langchain/core/messages";
|
|
2563
2586
|
import { toLangChainTools as toLangChainTools2 } from "@agentforge/core";
|
|
2564
2587
|
var logger2 = createPatternLogger("agentforge:patterns:multi-agent:nodes");
|
|
2588
|
+
function createGeneratedId(prefix) {
|
|
2589
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
2590
|
+
}
|
|
2591
|
+
function createTaskAssignmentId() {
|
|
2592
|
+
return `task_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
2593
|
+
}
|
|
2594
|
+
function getLatestTaskContent(state) {
|
|
2595
|
+
return state.messages[state.messages.length - 1]?.content || state.input;
|
|
2596
|
+
}
|
|
2597
|
+
function createTaskAssignments(workerIds, task) {
|
|
2598
|
+
return workerIds.map((workerId) => ({
|
|
2599
|
+
id: createTaskAssignmentId(),
|
|
2600
|
+
workerId,
|
|
2601
|
+
task,
|
|
2602
|
+
priority: 5,
|
|
2603
|
+
assignedAt: Date.now()
|
|
2604
|
+
}));
|
|
2605
|
+
}
|
|
2606
|
+
function createAssignmentMessages(assignments) {
|
|
2607
|
+
return assignments.map((assignment) => ({
|
|
2608
|
+
id: createGeneratedId("msg"),
|
|
2609
|
+
from: "supervisor",
|
|
2610
|
+
to: [assignment.workerId],
|
|
2611
|
+
type: "task_assignment",
|
|
2612
|
+
content: assignment.task,
|
|
2613
|
+
timestamp: Date.now(),
|
|
2614
|
+
metadata: {
|
|
2615
|
+
assignmentId: assignment.id,
|
|
2616
|
+
priority: assignment.priority
|
|
2617
|
+
}
|
|
2618
|
+
}));
|
|
2619
|
+
}
|
|
2620
|
+
function findCurrentAssignment(state, workerId) {
|
|
2621
|
+
return state.activeAssignments.find(
|
|
2622
|
+
(assignment) => assignment.workerId === workerId && !state.completedTasks.some((task) => task.assignmentId === assignment.id)
|
|
2623
|
+
);
|
|
2624
|
+
}
|
|
2565
2625
|
function convertWorkerToolsForLangChain(tools) {
|
|
2566
2626
|
const safeTools = tools ?? [];
|
|
2567
|
-
return toLangChainTools2(
|
|
2568
|
-
safeTools
|
|
2569
|
-
);
|
|
2627
|
+
return toLangChainTools2(safeTools);
|
|
2570
2628
|
}
|
|
2629
|
+
function serializeModelContent(content) {
|
|
2630
|
+
if (typeof content === "string") {
|
|
2631
|
+
return content;
|
|
2632
|
+
}
|
|
2633
|
+
try {
|
|
2634
|
+
const serialized = JSON.stringify(content);
|
|
2635
|
+
if (typeof serialized !== "string") {
|
|
2636
|
+
const error = new Error(
|
|
2637
|
+
"Failed to serialize model content: JSON.stringify returned undefined"
|
|
2638
|
+
);
|
|
2639
|
+
logger2.error("Model content serialization failed", {
|
|
2640
|
+
errorMessage: error.message,
|
|
2641
|
+
contentType: content === null ? "null" : typeof content
|
|
2642
|
+
});
|
|
2643
|
+
throw error;
|
|
2644
|
+
}
|
|
2645
|
+
return serialized;
|
|
2646
|
+
} catch (error) {
|
|
2647
|
+
const normalizedError = error instanceof Error ? error : new Error("Unknown error during model content serialization");
|
|
2648
|
+
logger2.error("Model content serialization threw an error", {
|
|
2649
|
+
errorMessage: normalizedError.message,
|
|
2650
|
+
contentType: content === null ? "null" : typeof content
|
|
2651
|
+
});
|
|
2652
|
+
throw normalizedError;
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
function createPromptMessages(systemPrompt, task) {
|
|
2656
|
+
return [new SystemMessage5(systemPrompt), new HumanMessage5(task)];
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
// src/multi-agent/nodes/aggregator.ts
|
|
2571
2660
|
var DEFAULT_AGGREGATOR_SYSTEM_PROMPT = `You are an aggregator agent responsible for combining results from multiple worker agents.
|
|
2572
2661
|
|
|
2573
2662
|
Your job is to:
|
|
@@ -2577,12 +2666,124 @@ Your job is to:
|
|
|
2577
2666
|
4. Provide a clear, comprehensive final answer
|
|
2578
2667
|
|
|
2579
2668
|
Be concise but thorough in your aggregation.`;
|
|
2580
|
-
function
|
|
2669
|
+
function createAggregationPrompt(state) {
|
|
2670
|
+
const taskResults = state.completedTasks.map((task, idx) => {
|
|
2671
|
+
const status = task.success ? "\u2713" : "\u2717";
|
|
2672
|
+
const result = task.success ? task.result : `Error: ${task.error}`;
|
|
2673
|
+
return `${idx + 1}. [${status}] Worker ${task.workerId}:
|
|
2674
|
+
${result}`;
|
|
2675
|
+
}).join("\n\n");
|
|
2676
|
+
return `Original query: ${state.input}
|
|
2677
|
+
|
|
2678
|
+
Worker results:
|
|
2679
|
+
${taskResults}
|
|
2680
|
+
|
|
2681
|
+
Please synthesize these results into a comprehensive response that addresses the original query.`;
|
|
2682
|
+
}
|
|
2683
|
+
function createAggregatorNode(config = {}) {
|
|
2581
2684
|
const {
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2685
|
+
model,
|
|
2686
|
+
systemPrompt = DEFAULT_AGGREGATOR_SYSTEM_PROMPT,
|
|
2687
|
+
aggregateFn
|
|
2585
2688
|
} = config;
|
|
2689
|
+
return async (state) => {
|
|
2690
|
+
try {
|
|
2691
|
+
logger2.info("Aggregator node executing", {
|
|
2692
|
+
completedTasks: state.completedTasks.length,
|
|
2693
|
+
successfulTasks: state.completedTasks.filter((task) => task.success).length,
|
|
2694
|
+
failedTasks: state.completedTasks.filter((task) => !task.success).length
|
|
2695
|
+
});
|
|
2696
|
+
logger2.debug("Combining results from workers");
|
|
2697
|
+
if (aggregateFn) {
|
|
2698
|
+
logger2.debug("Using custom aggregation function");
|
|
2699
|
+
const response2 = await aggregateFn(state);
|
|
2700
|
+
logger2.info("Custom aggregation complete", {
|
|
2701
|
+
responseLength: response2.length
|
|
2702
|
+
});
|
|
2703
|
+
return {
|
|
2704
|
+
response: response2,
|
|
2705
|
+
status: "completed"
|
|
2706
|
+
};
|
|
2707
|
+
}
|
|
2708
|
+
if (state.completedTasks.length === 0) {
|
|
2709
|
+
logger2.warn("No completed tasks to aggregate");
|
|
2710
|
+
return {
|
|
2711
|
+
response: "No tasks were completed.",
|
|
2712
|
+
status: "completed"
|
|
2713
|
+
};
|
|
2714
|
+
}
|
|
2715
|
+
if (!model) {
|
|
2716
|
+
logger2.debug("No model provided, concatenating results");
|
|
2717
|
+
const combinedResults = state.completedTasks.filter((task) => task.success).map((task) => task.result).join("\n\n");
|
|
2718
|
+
logger2.info("Simple concatenation complete", {
|
|
2719
|
+
resultLength: combinedResults.length
|
|
2720
|
+
});
|
|
2721
|
+
return {
|
|
2722
|
+
response: combinedResults || "No successful results to aggregate.",
|
|
2723
|
+
status: "completed"
|
|
2724
|
+
};
|
|
2725
|
+
}
|
|
2726
|
+
logger2.debug("Using LLM for intelligent aggregation", {
|
|
2727
|
+
taskCount: state.completedTasks.length
|
|
2728
|
+
});
|
|
2729
|
+
const messages = createPromptMessages(systemPrompt, createAggregationPrompt(state));
|
|
2730
|
+
logger2.debug("Invoking aggregation LLM");
|
|
2731
|
+
const response = await model.invoke(messages);
|
|
2732
|
+
const aggregatedResponse = serializeModelContent(response.content);
|
|
2733
|
+
logger2.info("Aggregation complete", {
|
|
2734
|
+
responseLength: aggregatedResponse.length
|
|
2735
|
+
});
|
|
2736
|
+
logger2.debug("Aggregation response details", {
|
|
2737
|
+
responseLength: aggregatedResponse.length
|
|
2738
|
+
});
|
|
2739
|
+
logger2.debug("Aggregation complete");
|
|
2740
|
+
return {
|
|
2741
|
+
response: aggregatedResponse,
|
|
2742
|
+
status: "completed"
|
|
2743
|
+
};
|
|
2744
|
+
} catch (error) {
|
|
2745
|
+
const errorMessage = handleNodeError(error, "aggregator", false);
|
|
2746
|
+
logger2.error("Aggregator node error", {
|
|
2747
|
+
error: errorMessage,
|
|
2748
|
+
...error instanceof Error && error.stack ? { stack: error.stack } : {},
|
|
2749
|
+
completedTasks: state.completedTasks.length
|
|
2750
|
+
});
|
|
2751
|
+
return {
|
|
2752
|
+
status: "failed",
|
|
2753
|
+
error: errorMessage
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
};
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
// src/multi-agent/nodes/supervisor.ts
|
|
2760
|
+
function allAssignmentsCompleted(state) {
|
|
2761
|
+
return state.activeAssignments.every(
|
|
2762
|
+
(assignment) => state.completedTasks.some((task) => task.assignmentId === assignment.id)
|
|
2763
|
+
);
|
|
2764
|
+
}
|
|
2765
|
+
function incrementAssignedWorkerLoads(state, assignments) {
|
|
2766
|
+
const updatedWorkers = { ...state.workers };
|
|
2767
|
+
for (const assignment of assignments) {
|
|
2768
|
+
const worker = updatedWorkers[assignment.workerId];
|
|
2769
|
+
if (!worker) {
|
|
2770
|
+
logger2.error("Worker not found in state", {
|
|
2771
|
+
workerId: assignment.workerId,
|
|
2772
|
+
availableWorkers: Object.keys(updatedWorkers)
|
|
2773
|
+
});
|
|
2774
|
+
throw new Error(
|
|
2775
|
+
`Worker ${assignment.workerId} not found in state.workers. Available workers: ${Object.keys(updatedWorkers).join(", ")}`
|
|
2776
|
+
);
|
|
2777
|
+
}
|
|
2778
|
+
updatedWorkers[assignment.workerId] = {
|
|
2779
|
+
...worker,
|
|
2780
|
+
currentWorkload: worker.currentWorkload + 1
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
return updatedWorkers;
|
|
2784
|
+
}
|
|
2785
|
+
function createSupervisorNode(config) {
|
|
2786
|
+
const { strategy, maxIterations = 10 } = config;
|
|
2586
2787
|
return async (state) => {
|
|
2587
2788
|
try {
|
|
2588
2789
|
logger2.info("Supervisor node executing", {
|
|
@@ -2603,9 +2804,7 @@ function createSupervisorNode(config) {
|
|
|
2603
2804
|
currentAgent: "aggregator"
|
|
2604
2805
|
};
|
|
2605
2806
|
}
|
|
2606
|
-
const allCompleted = state
|
|
2607
|
-
(assignment) => state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2608
|
-
);
|
|
2807
|
+
const allCompleted = allAssignmentsCompleted(state);
|
|
2609
2808
|
logger2.debug("Checking task completion", {
|
|
2610
2809
|
activeAssignments: state.activeAssignments.length,
|
|
2611
2810
|
completedTasks: state.completedTasks.length,
|
|
@@ -2643,6 +2842,7 @@ function createSupervisorNode(config) {
|
|
|
2643
2842
|
reasoning: decision.reasoning,
|
|
2644
2843
|
confidence: decision.confidence
|
|
2645
2844
|
});
|
|
2845
|
+
logger2.debug(`Routing to ${targetAgents[0]}: ${decision.reasoning}`);
|
|
2646
2846
|
} else {
|
|
2647
2847
|
logger2.info("Routing to multiple agents in parallel", {
|
|
2648
2848
|
targetAgents,
|
|
@@ -2650,57 +2850,22 @@ function createSupervisorNode(config) {
|
|
|
2650
2850
|
reasoning: decision.reasoning,
|
|
2651
2851
|
confidence: decision.confidence
|
|
2652
2852
|
});
|
|
2853
|
+
logger2.debug(
|
|
2854
|
+
`Routing to ${targetAgents.length} agents in parallel [${targetAgents.join(", ")}]: ${decision.reasoning}`
|
|
2855
|
+
);
|
|
2653
2856
|
}
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
} else {
|
|
2657
|
-
logger2.debug(`Routing to ${targetAgents.length} agents in parallel [${targetAgents.join(", ")}]: ${decision.reasoning}`);
|
|
2658
|
-
}
|
|
2659
|
-
const task = state.messages[state.messages.length - 1]?.content || state.input;
|
|
2660
|
-
const assignments = targetAgents.map((workerId) => ({
|
|
2661
|
-
id: `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
2662
|
-
workerId,
|
|
2663
|
-
task,
|
|
2664
|
-
priority: 5,
|
|
2665
|
-
assignedAt: Date.now()
|
|
2666
|
-
}));
|
|
2857
|
+
const task = getLatestTaskContent(state);
|
|
2858
|
+
const assignments = createTaskAssignments(targetAgents, task);
|
|
2667
2859
|
logger2.debug("Created task assignments", {
|
|
2668
2860
|
assignmentCount: assignments.length,
|
|
2669
|
-
assignments: assignments.map((
|
|
2670
|
-
id:
|
|
2671
|
-
workerId:
|
|
2672
|
-
taskLength:
|
|
2861
|
+
assignments: assignments.map((assignment) => ({
|
|
2862
|
+
id: assignment.id,
|
|
2863
|
+
workerId: assignment.workerId,
|
|
2864
|
+
taskLength: assignment.task.length
|
|
2673
2865
|
}))
|
|
2674
2866
|
});
|
|
2675
|
-
const messages = assignments
|
|
2676
|
-
|
|
2677
|
-
from: "supervisor",
|
|
2678
|
-
to: [assignment.workerId],
|
|
2679
|
-
type: "task_assignment",
|
|
2680
|
-
content: assignment.task,
|
|
2681
|
-
timestamp: Date.now(),
|
|
2682
|
-
metadata: {
|
|
2683
|
-
assignmentId: assignment.id,
|
|
2684
|
-
priority: assignment.priority
|
|
2685
|
-
}
|
|
2686
|
-
}));
|
|
2687
|
-
const updatedWorkers = { ...state.workers };
|
|
2688
|
-
for (const assignment of assignments) {
|
|
2689
|
-
const worker = updatedWorkers[assignment.workerId];
|
|
2690
|
-
if (!worker) {
|
|
2691
|
-
logger2.error("Worker not found in state", {
|
|
2692
|
-
workerId: assignment.workerId,
|
|
2693
|
-
availableWorkers: Object.keys(updatedWorkers)
|
|
2694
|
-
});
|
|
2695
|
-
throw new Error(
|
|
2696
|
-
`Worker ${assignment.workerId} not found in state.workers. Available workers: ${Object.keys(updatedWorkers).join(", ")}`
|
|
2697
|
-
);
|
|
2698
|
-
}
|
|
2699
|
-
updatedWorkers[assignment.workerId] = {
|
|
2700
|
-
...worker,
|
|
2701
|
-
currentWorkload: worker.currentWorkload + 1
|
|
2702
|
-
};
|
|
2703
|
-
}
|
|
2867
|
+
const messages = createAssignmentMessages(assignments);
|
|
2868
|
+
const updatedWorkers = incrementAssignedWorkerLoads(state, assignments);
|
|
2704
2869
|
logger2.info("Supervisor routing complete", {
|
|
2705
2870
|
currentAgent: targetAgents.join(","),
|
|
2706
2871
|
status: "executing",
|
|
@@ -2710,41 +2875,187 @@ function createSupervisorNode(config) {
|
|
|
2710
2875
|
});
|
|
2711
2876
|
return {
|
|
2712
2877
|
currentAgent: targetAgents.join(","),
|
|
2713
|
-
// Store all agents (for backward compat)
|
|
2714
2878
|
status: "executing",
|
|
2715
2879
|
routingHistory: [decision],
|
|
2716
2880
|
activeAssignments: assignments,
|
|
2717
|
-
// Multiple assignments for parallel execution!
|
|
2718
2881
|
messages,
|
|
2719
2882
|
workers: updatedWorkers,
|
|
2720
|
-
// Include updated workload
|
|
2721
|
-
// Add 1 to iteration counter (uses additive reducer)
|
|
2722
2883
|
iteration: 1
|
|
2723
2884
|
};
|
|
2724
2885
|
} catch (error) {
|
|
2886
|
+
const errorMessage = handleNodeError(error, "supervisor", false);
|
|
2725
2887
|
logger2.error("Supervisor node error", {
|
|
2726
|
-
error:
|
|
2888
|
+
error: errorMessage,
|
|
2727
2889
|
...error instanceof Error && error.stack ? { stack: error.stack } : {},
|
|
2728
2890
|
iteration: state.iteration
|
|
2729
2891
|
});
|
|
2730
2892
|
return {
|
|
2731
2893
|
status: "failed",
|
|
2732
|
-
error:
|
|
2894
|
+
error: errorMessage
|
|
2733
2895
|
};
|
|
2734
2896
|
}
|
|
2735
2897
|
};
|
|
2736
2898
|
}
|
|
2899
|
+
|
|
2900
|
+
// src/multi-agent/nodes/worker.ts
|
|
2901
|
+
function buildDefaultSystemPrompt(config) {
|
|
2902
|
+
return `You are a specialized worker agent with the following capabilities:
|
|
2903
|
+
Skills: ${config.capabilities.skills.join(", ")}
|
|
2904
|
+
Tools: ${config.capabilities.tools.join(", ")}
|
|
2905
|
+
|
|
2906
|
+
Execute the assigned task using your skills and tools. Provide a clear, actionable result.`;
|
|
2907
|
+
}
|
|
2908
|
+
async function invokeWorkerModel(model, config, assignment) {
|
|
2909
|
+
const messages = createPromptMessages(
|
|
2910
|
+
config.systemPrompt || buildDefaultSystemPrompt(config),
|
|
2911
|
+
assignment.task
|
|
2912
|
+
);
|
|
2913
|
+
let modelToUse = model;
|
|
2914
|
+
if (config.tools && config.tools.length > 0 && model.bindTools) {
|
|
2915
|
+
logger2.debug("Binding tools to model", {
|
|
2916
|
+
workerId: config.id,
|
|
2917
|
+
toolCount: config.tools.length,
|
|
2918
|
+
toolNames: config.tools.map((tool) => tool.metadata.name)
|
|
2919
|
+
});
|
|
2920
|
+
modelToUse = model.bindTools(
|
|
2921
|
+
convertWorkerToolsForLangChain(config.tools)
|
|
2922
|
+
);
|
|
2923
|
+
}
|
|
2924
|
+
logger2.debug("Invoking LLM", { workerId: config.id });
|
|
2925
|
+
const response = await modelToUse.invoke(messages);
|
|
2926
|
+
const result = serializeModelContent(response.content);
|
|
2927
|
+
logger2.info("Worker task completed", {
|
|
2928
|
+
workerId: config.id,
|
|
2929
|
+
assignmentId: assignment.id,
|
|
2930
|
+
resultLength: result.length
|
|
2931
|
+
});
|
|
2932
|
+
logger2.debug("Worker result details", {
|
|
2933
|
+
workerId: config.id,
|
|
2934
|
+
assignmentId: assignment.id,
|
|
2935
|
+
resultLength: result.length
|
|
2936
|
+
});
|
|
2937
|
+
const taskResult = {
|
|
2938
|
+
assignmentId: assignment.id,
|
|
2939
|
+
workerId: config.id,
|
|
2940
|
+
success: true,
|
|
2941
|
+
result,
|
|
2942
|
+
completedAt: Date.now(),
|
|
2943
|
+
metadata: {
|
|
2944
|
+
skills_used: config.capabilities.skills
|
|
2945
|
+
}
|
|
2946
|
+
};
|
|
2947
|
+
const message = {
|
|
2948
|
+
id: createGeneratedId("msg"),
|
|
2949
|
+
from: config.id,
|
|
2950
|
+
to: ["supervisor"],
|
|
2951
|
+
type: "task_result",
|
|
2952
|
+
content: result,
|
|
2953
|
+
timestamp: Date.now(),
|
|
2954
|
+
metadata: {
|
|
2955
|
+
assignmentId: assignment.id,
|
|
2956
|
+
success: true
|
|
2957
|
+
}
|
|
2958
|
+
};
|
|
2959
|
+
return {
|
|
2960
|
+
completedTasks: [taskResult],
|
|
2961
|
+
messages: [message]
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
2964
|
+
function getStateWorkerOrThrow(state, workerId) {
|
|
2965
|
+
const currentWorker = state.workers[workerId];
|
|
2966
|
+
if (!currentWorker) {
|
|
2967
|
+
logger2.error("Attempted to decrement workload for unknown worker", {
|
|
2968
|
+
workerId,
|
|
2969
|
+
availableWorkers: Object.keys(state.workers)
|
|
2970
|
+
});
|
|
2971
|
+
throw new Error(`Worker "${workerId}" not found when decrementing workload.`);
|
|
2972
|
+
}
|
|
2973
|
+
return currentWorker;
|
|
2974
|
+
}
|
|
2975
|
+
function resolvePreviousWorkload(workerId, currentWorker, workerFromExecution) {
|
|
2976
|
+
const workloadFromWorker = workerFromExecution?.currentWorkload;
|
|
2977
|
+
if (typeof workloadFromWorker === "number" && Number.isFinite(workloadFromWorker)) {
|
|
2978
|
+
return workloadFromWorker;
|
|
2979
|
+
}
|
|
2980
|
+
if (typeof currentWorker.currentWorkload === "number" && Number.isFinite(currentWorker.currentWorkload)) {
|
|
2981
|
+
return currentWorker.currentWorkload;
|
|
2982
|
+
}
|
|
2983
|
+
logger2.error("Worker workload is not a valid number; cannot decrement", {
|
|
2984
|
+
workerId,
|
|
2985
|
+
workloadFromState: currentWorker.currentWorkload,
|
|
2986
|
+
...typeof workloadFromWorker === "number" ? { workloadFromWorker } : {}
|
|
2987
|
+
});
|
|
2988
|
+
throw new Error(
|
|
2989
|
+
`Worker "${workerId}" does not have a valid numeric currentWorkload to decrement.`
|
|
2990
|
+
);
|
|
2991
|
+
}
|
|
2992
|
+
function mergeWorkersWithDecrement(state, workerId, executionResult) {
|
|
2993
|
+
const currentWorker = getStateWorkerOrThrow(state, workerId);
|
|
2994
|
+
const executionResultWorkers = executionResult.workers || {};
|
|
2995
|
+
const baseWorkers = {
|
|
2996
|
+
...state.workers,
|
|
2997
|
+
...executionResultWorkers
|
|
2998
|
+
};
|
|
2999
|
+
const workerFromExecution = executionResultWorkers[workerId];
|
|
3000
|
+
const previousWorkload = resolvePreviousWorkload(
|
|
3001
|
+
workerId,
|
|
3002
|
+
currentWorker,
|
|
3003
|
+
workerFromExecution
|
|
3004
|
+
);
|
|
3005
|
+
const hasWorkerOverride = typeof workerFromExecution === "object" && workerFromExecution !== null;
|
|
3006
|
+
const updatedWorker = hasWorkerOverride ? {
|
|
3007
|
+
...currentWorker,
|
|
3008
|
+
...workerFromExecution,
|
|
3009
|
+
currentWorkload: Math.max(0, previousWorkload - 1)
|
|
3010
|
+
} : {
|
|
3011
|
+
...currentWorker,
|
|
3012
|
+
currentWorkload: Math.max(0, previousWorkload - 1)
|
|
3013
|
+
};
|
|
3014
|
+
const updatedWorkers = {
|
|
3015
|
+
...baseWorkers,
|
|
3016
|
+
[workerId]: updatedWorker
|
|
3017
|
+
};
|
|
3018
|
+
logger2.debug("Worker workload decremented", {
|
|
3019
|
+
workerId,
|
|
3020
|
+
previousWorkload,
|
|
3021
|
+
newWorkload: updatedWorkers[workerId].currentWorkload,
|
|
3022
|
+
hadExecutionResultWorkers: !!executionResult.workers
|
|
3023
|
+
});
|
|
3024
|
+
return updatedWorkers;
|
|
3025
|
+
}
|
|
3026
|
+
function decrementWorkerOnError(state, workerId) {
|
|
3027
|
+
const currentWorker = getStateWorkerOrThrow(state, workerId);
|
|
3028
|
+
const previousWorkload = resolvePreviousWorkload(workerId, currentWorker);
|
|
3029
|
+
const updatedWorkers = {
|
|
3030
|
+
...state.workers,
|
|
3031
|
+
[workerId]: {
|
|
3032
|
+
...currentWorker,
|
|
3033
|
+
currentWorkload: Math.max(0, previousWorkload - 1)
|
|
3034
|
+
}
|
|
3035
|
+
};
|
|
3036
|
+
logger2.debug("Worker workload decremented (error path)", {
|
|
3037
|
+
workerId,
|
|
3038
|
+
previousWorkload,
|
|
3039
|
+
newWorkload: updatedWorkers[workerId].currentWorkload
|
|
3040
|
+
});
|
|
3041
|
+
return updatedWorkers;
|
|
3042
|
+
}
|
|
3043
|
+
function createErrorTaskResult(assignment, workerId, errorMessage) {
|
|
3044
|
+
logger2.warn("Creating error result for assignment", {
|
|
3045
|
+
workerId,
|
|
3046
|
+
assignmentId: assignment.id
|
|
3047
|
+
});
|
|
3048
|
+
return {
|
|
3049
|
+
assignmentId: assignment.id,
|
|
3050
|
+
workerId,
|
|
3051
|
+
success: false,
|
|
3052
|
+
result: "",
|
|
3053
|
+
error: errorMessage,
|
|
3054
|
+
completedAt: Date.now()
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
2737
3057
|
function createWorkerNode(config) {
|
|
2738
|
-
const {
|
|
2739
|
-
id,
|
|
2740
|
-
capabilities,
|
|
2741
|
-
model,
|
|
2742
|
-
tools = [],
|
|
2743
|
-
systemPrompt,
|
|
2744
|
-
verbose = false,
|
|
2745
|
-
executeFn,
|
|
2746
|
-
agent
|
|
2747
|
-
} = config;
|
|
3058
|
+
const { id, model, executeFn, agent } = config;
|
|
2748
3059
|
return async (state, runConfig) => {
|
|
2749
3060
|
try {
|
|
2750
3061
|
logger2.info("Worker node executing", {
|
|
@@ -2752,9 +3063,7 @@ function createWorkerNode(config) {
|
|
|
2752
3063
|
iteration: state.iteration,
|
|
2753
3064
|
activeAssignments: state.activeAssignments.length
|
|
2754
3065
|
});
|
|
2755
|
-
const currentAssignment = state
|
|
2756
|
-
(assignment) => assignment.workerId === id && !state.completedTasks.some((task) => task.assignmentId === assignment.id)
|
|
2757
|
-
);
|
|
3066
|
+
const currentAssignment = findCurrentAssignment(state, id);
|
|
2758
3067
|
if (!currentAssignment) {
|
|
2759
3068
|
logger2.debug("No active assignment found for worker", {
|
|
2760
3069
|
workerId: id,
|
|
@@ -2766,105 +3075,35 @@ function createWorkerNode(config) {
|
|
|
2766
3075
|
logger2.info("Worker processing assignment", {
|
|
2767
3076
|
workerId: id,
|
|
2768
3077
|
assignmentId: currentAssignment.id,
|
|
2769
|
-
taskLength: currentAssignment.task.length
|
|
2770
|
-
|
|
3078
|
+
taskLength: currentAssignment.task.length
|
|
3079
|
+
});
|
|
3080
|
+
logger2.debug("Worker assignment details", {
|
|
3081
|
+
workerId: id,
|
|
3082
|
+
assignmentId: currentAssignment.id,
|
|
3083
|
+
taskLength: currentAssignment.task.length
|
|
2771
3084
|
});
|
|
2772
|
-
async function executeWithLLM() {
|
|
2773
|
-
logger2.debug("Using default LLM execution", {
|
|
2774
|
-
workerId: id,
|
|
2775
|
-
hasTools: tools.length > 0,
|
|
2776
|
-
toolCount: tools.length
|
|
2777
|
-
});
|
|
2778
|
-
const defaultSystemPrompt = `You are a specialized worker agent with the following capabilities:
|
|
2779
|
-
Skills: ${capabilities.skills.join(", ")}
|
|
2780
|
-
Tools: ${capabilities.tools.join(", ")}
|
|
2781
|
-
|
|
2782
|
-
Execute the assigned task using your skills and tools. Provide a clear, actionable result.`;
|
|
2783
|
-
const messages = [
|
|
2784
|
-
new SystemMessage5(systemPrompt || defaultSystemPrompt),
|
|
2785
|
-
new HumanMessage5(currentAssignment.task)
|
|
2786
|
-
];
|
|
2787
|
-
let modelToUse = model;
|
|
2788
|
-
if (tools.length > 0 && model.bindTools) {
|
|
2789
|
-
logger2.debug("Binding tools to model", {
|
|
2790
|
-
workerId: id,
|
|
2791
|
-
toolCount: tools.length,
|
|
2792
|
-
toolNames: tools.map((t) => t.metadata.name)
|
|
2793
|
-
});
|
|
2794
|
-
const langchainTools = convertWorkerToolsForLangChain(tools);
|
|
2795
|
-
modelToUse = model.bindTools(langchainTools);
|
|
2796
|
-
}
|
|
2797
|
-
logger2.debug("Invoking LLM", { workerId: id });
|
|
2798
|
-
const response = await modelToUse.invoke(messages);
|
|
2799
|
-
const result = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
2800
|
-
logger2.info("Worker task completed", {
|
|
2801
|
-
workerId: id,
|
|
2802
|
-
assignmentId: currentAssignment.id,
|
|
2803
|
-
resultLength: result.length,
|
|
2804
|
-
resultPreview: result.substring(0, 100)
|
|
2805
|
-
});
|
|
2806
|
-
const taskResult = {
|
|
2807
|
-
assignmentId: currentAssignment.id,
|
|
2808
|
-
workerId: id,
|
|
2809
|
-
success: true,
|
|
2810
|
-
result,
|
|
2811
|
-
completedAt: Date.now(),
|
|
2812
|
-
metadata: {
|
|
2813
|
-
skills_used: capabilities.skills
|
|
2814
|
-
}
|
|
2815
|
-
};
|
|
2816
|
-
const message = {
|
|
2817
|
-
id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
2818
|
-
from: id,
|
|
2819
|
-
to: ["supervisor"],
|
|
2820
|
-
type: "task_result",
|
|
2821
|
-
content: result,
|
|
2822
|
-
timestamp: Date.now(),
|
|
2823
|
-
metadata: {
|
|
2824
|
-
assignmentId: currentAssignment.id,
|
|
2825
|
-
success: true
|
|
2826
|
-
}
|
|
2827
|
-
};
|
|
2828
|
-
return {
|
|
2829
|
-
completedTasks: [taskResult],
|
|
2830
|
-
messages: [message]
|
|
2831
|
-
};
|
|
2832
|
-
}
|
|
2833
3085
|
let executionResult;
|
|
2834
3086
|
if (executeFn) {
|
|
2835
3087
|
logger2.debug("Using custom execution function", { workerId: id });
|
|
2836
3088
|
executionResult = await executeFn(state, runConfig);
|
|
2837
3089
|
} else if (agent && isReActAgent(agent)) {
|
|
2838
3090
|
logger2.debug("Using ReAct agent", { workerId: id });
|
|
2839
|
-
const wrappedFn = wrapReActAgent(id, agent, verbose);
|
|
3091
|
+
const wrappedFn = wrapReActAgent(id, agent, config.verbose ?? false);
|
|
2840
3092
|
executionResult = await wrappedFn(state, runConfig);
|
|
2841
3093
|
} else if (model) {
|
|
2842
|
-
|
|
3094
|
+
logger2.debug("Using default LLM execution", {
|
|
3095
|
+
workerId: id,
|
|
3096
|
+
hasTools: (config.tools ?? []).length > 0,
|
|
3097
|
+
toolCount: (config.tools ?? []).length
|
|
3098
|
+
});
|
|
3099
|
+
executionResult = await invokeWorkerModel(model, config, currentAssignment);
|
|
2843
3100
|
} else {
|
|
2844
3101
|
logger2.error("Worker missing required configuration", { workerId: id });
|
|
2845
3102
|
throw new Error(
|
|
2846
3103
|
`Worker ${id} requires either a model, an agent, or a custom execution function. Provide one of: config.model, config.agent, or config.executeFn`
|
|
2847
3104
|
);
|
|
2848
3105
|
}
|
|
2849
|
-
const
|
|
2850
|
-
const baseWorkers = {
|
|
2851
|
-
...state.workers,
|
|
2852
|
-
...executionResult.workers || {}
|
|
2853
|
-
};
|
|
2854
|
-
const workerToUpdate = baseWorkers[id] || currentWorker;
|
|
2855
|
-
const updatedWorkers = {
|
|
2856
|
-
...baseWorkers,
|
|
2857
|
-
[id]: {
|
|
2858
|
-
...workerToUpdate,
|
|
2859
|
-
currentWorkload: Math.max(0, workerToUpdate.currentWorkload - 1)
|
|
2860
|
-
}
|
|
2861
|
-
};
|
|
2862
|
-
logger2.debug("Worker workload decremented", {
|
|
2863
|
-
workerId: id,
|
|
2864
|
-
previousWorkload: workerToUpdate.currentWorkload,
|
|
2865
|
-
newWorkload: updatedWorkers[id].currentWorkload,
|
|
2866
|
-
hadExecutionResultWorkers: !!executionResult.workers
|
|
2867
|
-
});
|
|
3106
|
+
const updatedWorkers = mergeWorkersWithDecrement(state, id, executionResult);
|
|
2868
3107
|
return {
|
|
2869
3108
|
...executionResult,
|
|
2870
3109
|
workers: updatedWorkers
|
|
@@ -2875,41 +3114,29 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2875
3114
|
workerId: id,
|
|
2876
3115
|
error: errorMessage
|
|
2877
3116
|
});
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
3117
|
+
let updatedWorkers;
|
|
3118
|
+
try {
|
|
3119
|
+
updatedWorkers = decrementWorkerOnError(state, id);
|
|
3120
|
+
} catch (workloadError) {
|
|
3121
|
+
const workloadErrorMessage = workloadError instanceof Error ? workloadError.message : String(workloadError);
|
|
3122
|
+
logger2.error("Worker error handling failed", {
|
|
3123
|
+
workerId: id,
|
|
3124
|
+
error: workloadErrorMessage
|
|
3125
|
+
});
|
|
3126
|
+
return {
|
|
3127
|
+
status: "failed",
|
|
3128
|
+
error: `${errorMessage}. ${workloadErrorMessage}`
|
|
3129
|
+
};
|
|
3130
|
+
}
|
|
2891
3131
|
const currentAssignment = state.activeAssignments.find(
|
|
2892
3132
|
(assignment) => assignment.workerId === id
|
|
2893
3133
|
);
|
|
2894
3134
|
if (currentAssignment) {
|
|
2895
|
-
logger2.warn("Creating error result for assignment", {
|
|
2896
|
-
workerId: id,
|
|
2897
|
-
assignmentId: currentAssignment.id
|
|
2898
|
-
});
|
|
2899
|
-
const errorResult = {
|
|
2900
|
-
assignmentId: currentAssignment.id,
|
|
2901
|
-
workerId: id,
|
|
2902
|
-
success: false,
|
|
2903
|
-
result: "",
|
|
2904
|
-
error: errorMessage,
|
|
2905
|
-
completedAt: Date.now()
|
|
2906
|
-
};
|
|
2907
3135
|
return {
|
|
2908
|
-
completedTasks: [
|
|
3136
|
+
completedTasks: [createErrorTaskResult(currentAssignment, id, errorMessage)],
|
|
2909
3137
|
currentAgent: "supervisor",
|
|
2910
3138
|
status: "routing",
|
|
2911
3139
|
workers: updatedWorkers
|
|
2912
|
-
// Include workload update
|
|
2913
3140
|
};
|
|
2914
3141
|
}
|
|
2915
3142
|
logger2.error("No assignment found for error handling", { workerId: id });
|
|
@@ -2917,95 +3144,6 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2917
3144
|
status: "failed",
|
|
2918
3145
|
error: errorMessage,
|
|
2919
3146
|
workers: updatedWorkers
|
|
2920
|
-
// Include workload update even on failure
|
|
2921
|
-
};
|
|
2922
|
-
}
|
|
2923
|
-
};
|
|
2924
|
-
}
|
|
2925
|
-
function createAggregatorNode(config = {}) {
|
|
2926
|
-
const {
|
|
2927
|
-
model,
|
|
2928
|
-
systemPrompt = DEFAULT_AGGREGATOR_SYSTEM_PROMPT,
|
|
2929
|
-
aggregateFn,
|
|
2930
|
-
verbose = false
|
|
2931
|
-
} = config;
|
|
2932
|
-
return async (state) => {
|
|
2933
|
-
try {
|
|
2934
|
-
logger2.info("Aggregator node executing", {
|
|
2935
|
-
completedTasks: state.completedTasks.length,
|
|
2936
|
-
successfulTasks: state.completedTasks.filter((t) => t.success).length,
|
|
2937
|
-
failedTasks: state.completedTasks.filter((t) => !t.success).length
|
|
2938
|
-
});
|
|
2939
|
-
logger2.debug("Combining results from workers");
|
|
2940
|
-
if (aggregateFn) {
|
|
2941
|
-
logger2.debug("Using custom aggregation function");
|
|
2942
|
-
const response2 = await aggregateFn(state);
|
|
2943
|
-
logger2.info("Custom aggregation complete", {
|
|
2944
|
-
responseLength: response2.length
|
|
2945
|
-
});
|
|
2946
|
-
return {
|
|
2947
|
-
response: response2,
|
|
2948
|
-
status: "completed"
|
|
2949
|
-
};
|
|
2950
|
-
}
|
|
2951
|
-
if (state.completedTasks.length === 0) {
|
|
2952
|
-
logger2.warn("No completed tasks to aggregate");
|
|
2953
|
-
return {
|
|
2954
|
-
response: "No tasks were completed.",
|
|
2955
|
-
status: "completed"
|
|
2956
|
-
};
|
|
2957
|
-
}
|
|
2958
|
-
if (!model) {
|
|
2959
|
-
logger2.debug("No model provided, concatenating results");
|
|
2960
|
-
const combinedResults = state.completedTasks.filter((task) => task.success).map((task) => task.result).join("\n\n");
|
|
2961
|
-
logger2.info("Simple concatenation complete", {
|
|
2962
|
-
resultLength: combinedResults.length
|
|
2963
|
-
});
|
|
2964
|
-
return {
|
|
2965
|
-
response: combinedResults || "No successful results to aggregate.",
|
|
2966
|
-
status: "completed"
|
|
2967
|
-
};
|
|
2968
|
-
}
|
|
2969
|
-
logger2.debug("Using LLM for intelligent aggregation", {
|
|
2970
|
-
taskCount: state.completedTasks.length
|
|
2971
|
-
});
|
|
2972
|
-
const taskResults = state.completedTasks.map((task, idx) => {
|
|
2973
|
-
const status = task.success ? "\u2713" : "\u2717";
|
|
2974
|
-
const result = task.success ? task.result : `Error: ${task.error}`;
|
|
2975
|
-
return `${idx + 1}. [${status}] Worker ${task.workerId}:
|
|
2976
|
-
${result}`;
|
|
2977
|
-
}).join("\n\n");
|
|
2978
|
-
const userPrompt = `Original query: ${state.input}
|
|
2979
|
-
|
|
2980
|
-
Worker results:
|
|
2981
|
-
${taskResults}
|
|
2982
|
-
|
|
2983
|
-
Please synthesize these results into a comprehensive response that addresses the original query.`;
|
|
2984
|
-
const messages = [
|
|
2985
|
-
new SystemMessage5(systemPrompt),
|
|
2986
|
-
new HumanMessage5(userPrompt)
|
|
2987
|
-
];
|
|
2988
|
-
logger2.debug("Invoking aggregation LLM");
|
|
2989
|
-
const response = await model.invoke(messages);
|
|
2990
|
-
const aggregatedResponse = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
2991
|
-
logger2.info("Aggregation complete", {
|
|
2992
|
-
responseLength: aggregatedResponse.length,
|
|
2993
|
-
responsePreview: aggregatedResponse.substring(0, 100)
|
|
2994
|
-
});
|
|
2995
|
-
logger2.debug("Aggregation complete");
|
|
2996
|
-
return {
|
|
2997
|
-
response: aggregatedResponse,
|
|
2998
|
-
status: "completed"
|
|
2999
|
-
};
|
|
3000
|
-
} catch (error) {
|
|
3001
|
-
logger2.error("Aggregator node error", {
|
|
3002
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3003
|
-
...error instanceof Error && error.stack ? { stack: error.stack } : {},
|
|
3004
|
-
completedTasks: state.completedTasks.length
|
|
3005
|
-
});
|
|
3006
|
-
return {
|
|
3007
|
-
status: "failed",
|
|
3008
|
-
error: error instanceof Error ? error.message : "Unknown error in aggregator"
|
|
3009
3147
|
};
|
|
3010
3148
|
}
|
|
3011
3149
|
};
|