@agentforge/patterns 0.15.3 → 0.15.5
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 +130 -41
- package/dist/index.d.cts +19 -10
- package/dist/index.d.ts +19 -10
- package/dist/index.js +130 -41
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -302,8 +302,8 @@ function generateToolCallCacheKey(toolName, args) {
|
|
|
302
302
|
return `${toolName}:${sortedArgs}`;
|
|
303
303
|
}
|
|
304
304
|
function createPatternLogger(name, defaultLevel = "info") {
|
|
305
|
-
const
|
|
306
|
-
return (0, import_core2.createLogger)(name, { level:
|
|
305
|
+
const logLevel = process.env.LOG_LEVEL?.toLowerCase() || defaultLevel;
|
|
306
|
+
return (0, import_core2.createLogger)(name, { level: logLevel });
|
|
307
307
|
}
|
|
308
308
|
function calculateDeduplicationSavings(duplicatesSkipped, toolsExecuted) {
|
|
309
309
|
if (duplicatesSkipped === 0) {
|
|
@@ -2403,17 +2403,87 @@ function getRoutingStrategy(name) {
|
|
|
2403
2403
|
}
|
|
2404
2404
|
|
|
2405
2405
|
// src/multi-agent/utils.ts
|
|
2406
|
-
var
|
|
2407
|
-
|
|
2408
|
-
|
|
2406
|
+
var logger = createPatternLogger("agentforge:patterns:multi-agent:utils");
|
|
2407
|
+
function isRecord(value) {
|
|
2408
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2409
|
+
}
|
|
2410
|
+
function toRunnableConfig(config) {
|
|
2411
|
+
if (!isRecord(config)) {
|
|
2412
|
+
return void 0;
|
|
2413
|
+
}
|
|
2414
|
+
return config;
|
|
2415
|
+
}
|
|
2416
|
+
function getReActResultShape(value) {
|
|
2417
|
+
if (!isRecord(value)) {
|
|
2418
|
+
return {};
|
|
2419
|
+
}
|
|
2420
|
+
const messages = Array.isArray(value.messages) ? value.messages.filter((message) => isRecord(message)) : void 0;
|
|
2421
|
+
const actions = Array.isArray(value.actions) ? value.actions.filter((action) => isRecord(action)) : void 0;
|
|
2422
|
+
return {
|
|
2423
|
+
messages,
|
|
2424
|
+
actions,
|
|
2425
|
+
iteration: value.iteration
|
|
2426
|
+
};
|
|
2427
|
+
}
|
|
2428
|
+
function safeSerializeContent(content) {
|
|
2429
|
+
if (content === null || content === void 0) {
|
|
2430
|
+
return void 0;
|
|
2431
|
+
}
|
|
2432
|
+
if (typeof content === "string") {
|
|
2433
|
+
return content.length > 0 ? content : void 0;
|
|
2434
|
+
}
|
|
2435
|
+
if (Array.isArray(content)) {
|
|
2436
|
+
const parts = content.map((part) => {
|
|
2437
|
+
if (typeof part === "string") {
|
|
2438
|
+
return part;
|
|
2439
|
+
}
|
|
2440
|
+
if (isRecord(part) && typeof part.text === "string" && part.text.length > 0) {
|
|
2441
|
+
return part.text;
|
|
2442
|
+
}
|
|
2443
|
+
try {
|
|
2444
|
+
return JSON.stringify(part);
|
|
2445
|
+
} catch {
|
|
2446
|
+
return String(part);
|
|
2447
|
+
}
|
|
2448
|
+
}).filter((part) => part.length > 0);
|
|
2449
|
+
return parts.length > 0 ? parts.join("\n") : void 0;
|
|
2450
|
+
}
|
|
2451
|
+
try {
|
|
2452
|
+
const serialized = JSON.stringify(content);
|
|
2453
|
+
if (typeof serialized === "string" && serialized.length > 0 && serialized !== "null") {
|
|
2454
|
+
return serialized;
|
|
2455
|
+
}
|
|
2456
|
+
} catch {
|
|
2457
|
+
}
|
|
2458
|
+
const fallback = String(content);
|
|
2459
|
+
return fallback.length > 0 ? fallback : void 0;
|
|
2460
|
+
}
|
|
2461
|
+
function extractResponse(resultShape) {
|
|
2462
|
+
const { messages } = resultShape;
|
|
2463
|
+
const lastMessage = messages?.[messages.length - 1];
|
|
2464
|
+
const serialized = safeSerializeContent(lastMessage?.content);
|
|
2465
|
+
return serialized ?? "No response";
|
|
2466
|
+
}
|
|
2467
|
+
function extractToolsUsed(resultShape) {
|
|
2468
|
+
const { actions } = resultShape;
|
|
2469
|
+
const names = actions?.map((action) => action.name).filter((name) => typeof name === "string" && name.length > 0) || [];
|
|
2470
|
+
return [...new Set(names)];
|
|
2471
|
+
}
|
|
2472
|
+
function extractIteration(resultShape) {
|
|
2473
|
+
const { iteration } = resultShape;
|
|
2474
|
+
return typeof iteration === "number" && Number.isFinite(iteration) ? iteration : 0;
|
|
2475
|
+
}
|
|
2409
2476
|
function isReActAgent(obj) {
|
|
2410
|
-
return
|
|
2411
|
-
|
|
2477
|
+
return Boolean(
|
|
2478
|
+
obj && typeof obj === "object" && "invoke" in obj && typeof obj.invoke === "function" && "stream" in obj && typeof obj.stream === "function" && // Additional check to ensure it's not just any object with invoke/stream
|
|
2479
|
+
(obj.constructor?.name === "CompiledGraph" || obj.constructor?.name === "CompiledStateGraph")
|
|
2480
|
+
);
|
|
2412
2481
|
}
|
|
2413
2482
|
function wrapReActAgent(workerId, agent, verbose = false) {
|
|
2414
2483
|
return async (state, config) => {
|
|
2415
2484
|
try {
|
|
2416
2485
|
logger.debug("Wrapping ReAct agent execution", { workerId });
|
|
2486
|
+
const runnableConfig = toRunnableConfig(config);
|
|
2417
2487
|
const currentAssignment = state.activeAssignments.find(
|
|
2418
2488
|
(assignment) => assignment.workerId === workerId && !state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2419
2489
|
);
|
|
@@ -2427,17 +2497,17 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2427
2497
|
assignmentId: currentAssignment.id,
|
|
2428
2498
|
taskPreview: task.substring(0, 100) + (task.length > 100 ? "..." : "")
|
|
2429
2499
|
});
|
|
2430
|
-
const workerThreadId =
|
|
2500
|
+
const workerThreadId = runnableConfig?.configurable?.thread_id ? `${runnableConfig.configurable.thread_id}:worker:${workerId}` : void 0;
|
|
2431
2501
|
const workerConfig = workerThreadId ? {
|
|
2432
|
-
...
|
|
2502
|
+
...runnableConfig,
|
|
2433
2503
|
configurable: {
|
|
2434
|
-
...
|
|
2504
|
+
...runnableConfig?.configurable ?? {},
|
|
2435
2505
|
thread_id: workerThreadId
|
|
2436
2506
|
}
|
|
2437
|
-
} :
|
|
2507
|
+
} : runnableConfig;
|
|
2438
2508
|
logger.debug("Invoking ReAct agent with worker-specific config", {
|
|
2439
2509
|
workerId,
|
|
2440
|
-
parentThreadId:
|
|
2510
|
+
parentThreadId: runnableConfig?.configurable?.thread_id,
|
|
2441
2511
|
workerThreadId,
|
|
2442
2512
|
hasConfig: !!workerConfig
|
|
2443
2513
|
});
|
|
@@ -2448,13 +2518,13 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2448
2518
|
workerConfig
|
|
2449
2519
|
// Worker-specific config with unique thread_id
|
|
2450
2520
|
);
|
|
2451
|
-
const
|
|
2521
|
+
const resultShape = getReActResultShape(result);
|
|
2522
|
+
const response = extractResponse(resultShape);
|
|
2452
2523
|
logger.debug("Received response from ReAct agent", {
|
|
2453
2524
|
workerId,
|
|
2454
2525
|
responsePreview: response.substring(0, 100) + (response.length > 100 ? "..." : "")
|
|
2455
2526
|
});
|
|
2456
|
-
const
|
|
2457
|
-
const uniqueTools = [...new Set(toolsUsed)];
|
|
2527
|
+
const uniqueTools = extractToolsUsed(resultShape);
|
|
2458
2528
|
if (uniqueTools.length > 0) {
|
|
2459
2529
|
logger.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
|
|
2460
2530
|
}
|
|
@@ -2466,7 +2536,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2466
2536
|
success: true,
|
|
2467
2537
|
metadata: {
|
|
2468
2538
|
agent_type: "react",
|
|
2469
|
-
iterations:
|
|
2539
|
+
iterations: extractIteration(resultShape),
|
|
2470
2540
|
tools_used: uniqueTools
|
|
2471
2541
|
}
|
|
2472
2542
|
};
|
|
@@ -2474,7 +2544,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2474
2544
|
completedTasks: [taskResult]
|
|
2475
2545
|
};
|
|
2476
2546
|
} catch (error) {
|
|
2477
|
-
const errorMessage = handleNodeError(error, `react-agent:${workerId}`,
|
|
2547
|
+
const errorMessage = handleNodeError(error, `react-agent:${workerId}`, verbose);
|
|
2478
2548
|
logger.error("Error in ReAct agent execution", {
|
|
2479
2549
|
workerId,
|
|
2480
2550
|
error: errorMessage
|
|
@@ -2507,9 +2577,14 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2507
2577
|
|
|
2508
2578
|
// src/multi-agent/nodes.ts
|
|
2509
2579
|
var import_messages5 = require("@langchain/core/messages");
|
|
2510
|
-
var
|
|
2511
|
-
var
|
|
2512
|
-
|
|
2580
|
+
var import_core8 = require("@agentforge/core");
|
|
2581
|
+
var logger2 = createPatternLogger("agentforge:patterns:multi-agent:nodes");
|
|
2582
|
+
function convertWorkerToolsForLangChain(tools) {
|
|
2583
|
+
const safeTools = tools ?? [];
|
|
2584
|
+
return (0, import_core8.toLangChainTools)(
|
|
2585
|
+
safeTools
|
|
2586
|
+
);
|
|
2587
|
+
}
|
|
2513
2588
|
var DEFAULT_AGGREGATOR_SYSTEM_PROMPT = `You are an aggregator agent responsible for combining results from multiple worker agents.
|
|
2514
2589
|
|
|
2515
2590
|
Your job is to:
|
|
@@ -2733,7 +2808,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2733
2808
|
toolCount: tools.length,
|
|
2734
2809
|
toolNames: tools.map((t) => t.metadata.name)
|
|
2735
2810
|
});
|
|
2736
|
-
const langchainTools = (
|
|
2811
|
+
const langchainTools = convertWorkerToolsForLangChain(tools);
|
|
2737
2812
|
modelToUse = model.bindTools(langchainTools);
|
|
2738
2813
|
}
|
|
2739
2814
|
logger2.debug("Invoking LLM", { workerId: id });
|
|
@@ -2955,15 +3030,17 @@ Please synthesize these results into a comprehensive response that addresses the
|
|
|
2955
3030
|
|
|
2956
3031
|
// src/multi-agent/agent.ts
|
|
2957
3032
|
var import_langgraph4 = require("@langchain/langgraph");
|
|
2958
|
-
var
|
|
2959
|
-
var logLevel3 = process.env.LOG_LEVEL?.toLowerCase() || import_core10.LogLevel.INFO;
|
|
2960
|
-
var logger3 = (0, import_core10.createLogger)("multi-agent:system", { level: logLevel3 });
|
|
3033
|
+
var logger3 = createPatternLogger("agentforge:patterns:multi-agent:system");
|
|
2961
3034
|
function getToolName(tool) {
|
|
2962
|
-
if (tool
|
|
2963
|
-
return
|
|
3035
|
+
if (!tool || typeof tool !== "object") {
|
|
3036
|
+
return "unknown";
|
|
2964
3037
|
}
|
|
2965
|
-
|
|
2966
|
-
|
|
3038
|
+
const candidate = tool;
|
|
3039
|
+
if (typeof candidate.metadata?.name === "string") {
|
|
3040
|
+
return candidate.metadata.name;
|
|
3041
|
+
}
|
|
3042
|
+
if (typeof candidate.name === "string") {
|
|
3043
|
+
return candidate.name;
|
|
2967
3044
|
}
|
|
2968
3045
|
return "unknown";
|
|
2969
3046
|
}
|
|
@@ -3061,7 +3138,7 @@ function createMultiAgentSystem(config) {
|
|
|
3061
3138
|
workflow.addConditionalEdges("aggregator", aggregatorRouter, [import_langgraph4.END]);
|
|
3062
3139
|
const compiled = workflow.compile(checkpointer ? { checkpointer } : void 0);
|
|
3063
3140
|
const originalInvoke = compiled.invoke.bind(compiled);
|
|
3064
|
-
compiled.invoke = async function(input, config2) {
|
|
3141
|
+
compiled.invoke = (async function(input, config2) {
|
|
3065
3142
|
const mergedInput = {
|
|
3066
3143
|
...input,
|
|
3067
3144
|
workers: {
|
|
@@ -3069,10 +3146,13 @@ function createMultiAgentSystem(config) {
|
|
|
3069
3146
|
...input.workers || {}
|
|
3070
3147
|
}
|
|
3071
3148
|
};
|
|
3072
|
-
return originalInvoke(
|
|
3073
|
-
|
|
3149
|
+
return originalInvoke(
|
|
3150
|
+
mergedInput,
|
|
3151
|
+
config2
|
|
3152
|
+
);
|
|
3153
|
+
});
|
|
3074
3154
|
const originalStream = compiled.stream.bind(compiled);
|
|
3075
|
-
compiled.stream = async function(input, config2) {
|
|
3155
|
+
compiled.stream = (async function(input, config2) {
|
|
3076
3156
|
const mergedInput = {
|
|
3077
3157
|
...input,
|
|
3078
3158
|
workers: {
|
|
@@ -3080,8 +3160,11 @@ function createMultiAgentSystem(config) {
|
|
|
3080
3160
|
...input.workers || {}
|
|
3081
3161
|
}
|
|
3082
3162
|
};
|
|
3083
|
-
return originalStream(
|
|
3084
|
-
|
|
3163
|
+
return originalStream(
|
|
3164
|
+
mergedInput,
|
|
3165
|
+
config2
|
|
3166
|
+
);
|
|
3167
|
+
});
|
|
3085
3168
|
return compiled;
|
|
3086
3169
|
}
|
|
3087
3170
|
var MultiAgentSystemBuilder = class {
|
|
@@ -3160,7 +3243,7 @@ var MultiAgentSystemBuilder = class {
|
|
|
3160
3243
|
};
|
|
3161
3244
|
function registerWorkers(system, workers) {
|
|
3162
3245
|
console.warn(
|
|
3163
|
-
"[AgentForge] registerWorkers() on a compiled system only updates worker capabilities in state.\nIt does NOT add worker nodes to the graph. Use MultiAgentSystemBuilder for proper worker registration.\nSee: https://github.com/
|
|
3246
|
+
"[AgentForge] registerWorkers() on a compiled system only updates worker capabilities in state.\nIt does NOT add worker nodes to the graph. Use MultiAgentSystemBuilder for proper worker registration.\nSee: https://github.com/TVScoundrel/agentforge/blob/main/packages/patterns/docs/multi-agent-pattern.md"
|
|
3164
3247
|
);
|
|
3165
3248
|
if (!system._workerRegistry) {
|
|
3166
3249
|
system._workerRegistry = {};
|
|
@@ -3175,7 +3258,7 @@ function registerWorkers(system, workers) {
|
|
|
3175
3258
|
}
|
|
3176
3259
|
if (!system._originalInvoke) {
|
|
3177
3260
|
system._originalInvoke = system.invoke.bind(system);
|
|
3178
|
-
system.invoke = async function(input, config) {
|
|
3261
|
+
system.invoke = (async function(input, config) {
|
|
3179
3262
|
const mergedInput = {
|
|
3180
3263
|
...input,
|
|
3181
3264
|
workers: {
|
|
@@ -3183,12 +3266,15 @@ function registerWorkers(system, workers) {
|
|
|
3183
3266
|
...input.workers || {}
|
|
3184
3267
|
}
|
|
3185
3268
|
};
|
|
3186
|
-
return system._originalInvoke(
|
|
3187
|
-
|
|
3269
|
+
return system._originalInvoke(
|
|
3270
|
+
mergedInput,
|
|
3271
|
+
config
|
|
3272
|
+
);
|
|
3273
|
+
});
|
|
3188
3274
|
}
|
|
3189
3275
|
if (!system._originalStream) {
|
|
3190
3276
|
system._originalStream = system.stream.bind(system);
|
|
3191
|
-
system.stream = async function(input, config) {
|
|
3277
|
+
system.stream = (async function(input, config) {
|
|
3192
3278
|
const mergedInput = {
|
|
3193
3279
|
...input,
|
|
3194
3280
|
workers: {
|
|
@@ -3196,8 +3282,11 @@ function registerWorkers(system, workers) {
|
|
|
3196
3282
|
...input.workers || {}
|
|
3197
3283
|
}
|
|
3198
3284
|
};
|
|
3199
|
-
return system._originalStream(
|
|
3200
|
-
|
|
3285
|
+
return system._originalStream(
|
|
3286
|
+
mergedInput,
|
|
3287
|
+
config
|
|
3288
|
+
);
|
|
3289
|
+
});
|
|
3201
3290
|
}
|
|
3202
3291
|
}
|
|
3203
3292
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/index.d.cts
CHANGED
|
@@ -4,6 +4,7 @@ import { BaseCheckpointSaver, CompiledStateGraph } from '@langchain/langgraph';
|
|
|
4
4
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
5
5
|
import * as _agentforge_core from '@agentforge/core';
|
|
6
6
|
import { ToolRegistry, Tool, LogLevel } from '@agentforge/core';
|
|
7
|
+
import { RunnableConfig } from '@langchain/core/runnables';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Zod schemas for ReAct pattern state
|
|
@@ -2526,6 +2527,14 @@ type MultiAgentStateType = {
|
|
|
2526
2527
|
* @module patterns/multi-agent/types
|
|
2527
2528
|
*/
|
|
2528
2529
|
|
|
2530
|
+
type WorkerTool = Tool<never, unknown>;
|
|
2531
|
+
/**
|
|
2532
|
+
* Runtime config passed to worker execution functions.
|
|
2533
|
+
*
|
|
2534
|
+
* Includes LangGraph's RunnableConfig while remaining open to caller-defined
|
|
2535
|
+
* keys used by integrations.
|
|
2536
|
+
*/
|
|
2537
|
+
type WorkerExecutionConfig = RunnableConfig | Record<string, unknown>;
|
|
2529
2538
|
/**
|
|
2530
2539
|
* Configuration for the supervisor node
|
|
2531
2540
|
*/
|
|
@@ -2582,7 +2591,7 @@ interface WorkerConfig {
|
|
|
2582
2591
|
/**
|
|
2583
2592
|
* Available tools for this worker
|
|
2584
2593
|
*/
|
|
2585
|
-
tools?:
|
|
2594
|
+
tools?: WorkerTool[];
|
|
2586
2595
|
/**
|
|
2587
2596
|
* System prompt for the worker
|
|
2588
2597
|
*/
|
|
@@ -2600,7 +2609,7 @@ interface WorkerConfig {
|
|
|
2600
2609
|
* The config parameter contains LangGraph runtime configuration including
|
|
2601
2610
|
* thread_id for checkpointing, which is required for interrupt functionality.
|
|
2602
2611
|
*/
|
|
2603
|
-
executeFn?: (state: MultiAgentStateType, config?:
|
|
2612
|
+
executeFn?: (state: MultiAgentStateType, config?: WorkerExecutionConfig) => Promise<Partial<MultiAgentStateType>>;
|
|
2604
2613
|
/**
|
|
2605
2614
|
* ReAct agent instance
|
|
2606
2615
|
*
|
|
@@ -2623,7 +2632,7 @@ interface WorkerConfig {
|
|
|
2623
2632
|
* });
|
|
2624
2633
|
* ```
|
|
2625
2634
|
*/
|
|
2626
|
-
agent?: CompiledStateGraph<
|
|
2635
|
+
agent?: CompiledStateGraph<unknown, unknown>;
|
|
2627
2636
|
}
|
|
2628
2637
|
/**
|
|
2629
2638
|
* Configuration for the aggregator node
|
|
@@ -2737,7 +2746,7 @@ type MultiAgentNode = 'supervisor' | 'aggregator' | string;
|
|
|
2737
2746
|
/**
|
|
2738
2747
|
* Route type for multi-agent graph
|
|
2739
2748
|
*/
|
|
2740
|
-
type MultiAgentRoute = 'continue' | 'aggregate' | 'end' | string;
|
|
2749
|
+
type MultiAgentRoute = 'continue' | 'aggregate' | 'end' | string | string[];
|
|
2741
2750
|
/**
|
|
2742
2751
|
* Router function type
|
|
2743
2752
|
*/
|
|
@@ -2985,9 +2994,9 @@ declare class MultiAgentSystemBuilder {
|
|
|
2985
2994
|
name: string;
|
|
2986
2995
|
description?: string;
|
|
2987
2996
|
capabilities: string[];
|
|
2988
|
-
tools?:
|
|
2997
|
+
tools?: WorkerConfig['tools'];
|
|
2989
2998
|
systemPrompt?: string;
|
|
2990
|
-
model?:
|
|
2999
|
+
model?: WorkerConfig['model'];
|
|
2991
3000
|
}>): this;
|
|
2992
3001
|
/**
|
|
2993
3002
|
* Build and compile the multi-agent system
|
|
@@ -2999,10 +3008,10 @@ declare class MultiAgentSystemBuilder {
|
|
|
2999
3008
|
/**
|
|
3000
3009
|
* Extended multi-agent system with worker registration support
|
|
3001
3010
|
*/
|
|
3002
|
-
interface MultiAgentSystemWithRegistry extends CompiledStateGraph<
|
|
3011
|
+
interface MultiAgentSystemWithRegistry extends CompiledStateGraph<MultiAgentStateType, unknown> {
|
|
3003
3012
|
_workerRegistry?: Record<string, WorkerCapabilities>;
|
|
3004
|
-
_originalInvoke?:
|
|
3005
|
-
_originalStream?:
|
|
3013
|
+
_originalInvoke?: MultiAgentSystemWithRegistry['invoke'];
|
|
3014
|
+
_originalStream?: MultiAgentSystemWithRegistry['stream'];
|
|
3006
3015
|
}
|
|
3007
3016
|
/**
|
|
3008
3017
|
* Register workers with a compiled multi-agent system
|
|
@@ -3035,7 +3044,7 @@ declare function registerWorkers(system: MultiAgentSystemWithRegistry, workers:
|
|
|
3035
3044
|
name: string;
|
|
3036
3045
|
description?: string;
|
|
3037
3046
|
capabilities: string[];
|
|
3038
|
-
tools?:
|
|
3047
|
+
tools?: WorkerConfig['tools'];
|
|
3039
3048
|
systemPrompt?: string;
|
|
3040
3049
|
}>): void;
|
|
3041
3050
|
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { BaseCheckpointSaver, CompiledStateGraph } from '@langchain/langgraph';
|
|
|
4
4
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
5
5
|
import * as _agentforge_core from '@agentforge/core';
|
|
6
6
|
import { ToolRegistry, Tool, LogLevel } from '@agentforge/core';
|
|
7
|
+
import { RunnableConfig } from '@langchain/core/runnables';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Zod schemas for ReAct pattern state
|
|
@@ -2526,6 +2527,14 @@ type MultiAgentStateType = {
|
|
|
2526
2527
|
* @module patterns/multi-agent/types
|
|
2527
2528
|
*/
|
|
2528
2529
|
|
|
2530
|
+
type WorkerTool = Tool<never, unknown>;
|
|
2531
|
+
/**
|
|
2532
|
+
* Runtime config passed to worker execution functions.
|
|
2533
|
+
*
|
|
2534
|
+
* Includes LangGraph's RunnableConfig while remaining open to caller-defined
|
|
2535
|
+
* keys used by integrations.
|
|
2536
|
+
*/
|
|
2537
|
+
type WorkerExecutionConfig = RunnableConfig | Record<string, unknown>;
|
|
2529
2538
|
/**
|
|
2530
2539
|
* Configuration for the supervisor node
|
|
2531
2540
|
*/
|
|
@@ -2582,7 +2591,7 @@ interface WorkerConfig {
|
|
|
2582
2591
|
/**
|
|
2583
2592
|
* Available tools for this worker
|
|
2584
2593
|
*/
|
|
2585
|
-
tools?:
|
|
2594
|
+
tools?: WorkerTool[];
|
|
2586
2595
|
/**
|
|
2587
2596
|
* System prompt for the worker
|
|
2588
2597
|
*/
|
|
@@ -2600,7 +2609,7 @@ interface WorkerConfig {
|
|
|
2600
2609
|
* The config parameter contains LangGraph runtime configuration including
|
|
2601
2610
|
* thread_id for checkpointing, which is required for interrupt functionality.
|
|
2602
2611
|
*/
|
|
2603
|
-
executeFn?: (state: MultiAgentStateType, config?:
|
|
2612
|
+
executeFn?: (state: MultiAgentStateType, config?: WorkerExecutionConfig) => Promise<Partial<MultiAgentStateType>>;
|
|
2604
2613
|
/**
|
|
2605
2614
|
* ReAct agent instance
|
|
2606
2615
|
*
|
|
@@ -2623,7 +2632,7 @@ interface WorkerConfig {
|
|
|
2623
2632
|
* });
|
|
2624
2633
|
* ```
|
|
2625
2634
|
*/
|
|
2626
|
-
agent?: CompiledStateGraph<
|
|
2635
|
+
agent?: CompiledStateGraph<unknown, unknown>;
|
|
2627
2636
|
}
|
|
2628
2637
|
/**
|
|
2629
2638
|
* Configuration for the aggregator node
|
|
@@ -2737,7 +2746,7 @@ type MultiAgentNode = 'supervisor' | 'aggregator' | string;
|
|
|
2737
2746
|
/**
|
|
2738
2747
|
* Route type for multi-agent graph
|
|
2739
2748
|
*/
|
|
2740
|
-
type MultiAgentRoute = 'continue' | 'aggregate' | 'end' | string;
|
|
2749
|
+
type MultiAgentRoute = 'continue' | 'aggregate' | 'end' | string | string[];
|
|
2741
2750
|
/**
|
|
2742
2751
|
* Router function type
|
|
2743
2752
|
*/
|
|
@@ -2985,9 +2994,9 @@ declare class MultiAgentSystemBuilder {
|
|
|
2985
2994
|
name: string;
|
|
2986
2995
|
description?: string;
|
|
2987
2996
|
capabilities: string[];
|
|
2988
|
-
tools?:
|
|
2997
|
+
tools?: WorkerConfig['tools'];
|
|
2989
2998
|
systemPrompt?: string;
|
|
2990
|
-
model?:
|
|
2999
|
+
model?: WorkerConfig['model'];
|
|
2991
3000
|
}>): this;
|
|
2992
3001
|
/**
|
|
2993
3002
|
* Build and compile the multi-agent system
|
|
@@ -2999,10 +3008,10 @@ declare class MultiAgentSystemBuilder {
|
|
|
2999
3008
|
/**
|
|
3000
3009
|
* Extended multi-agent system with worker registration support
|
|
3001
3010
|
*/
|
|
3002
|
-
interface MultiAgentSystemWithRegistry extends CompiledStateGraph<
|
|
3011
|
+
interface MultiAgentSystemWithRegistry extends CompiledStateGraph<MultiAgentStateType, unknown> {
|
|
3003
3012
|
_workerRegistry?: Record<string, WorkerCapabilities>;
|
|
3004
|
-
_originalInvoke?:
|
|
3005
|
-
_originalStream?:
|
|
3013
|
+
_originalInvoke?: MultiAgentSystemWithRegistry['invoke'];
|
|
3014
|
+
_originalStream?: MultiAgentSystemWithRegistry['stream'];
|
|
3006
3015
|
}
|
|
3007
3016
|
/**
|
|
3008
3017
|
* Register workers with a compiled multi-agent system
|
|
@@ -3035,7 +3044,7 @@ declare function registerWorkers(system: MultiAgentSystemWithRegistry, workers:
|
|
|
3035
3044
|
name: string;
|
|
3036
3045
|
description?: string;
|
|
3037
3046
|
capabilities: string[];
|
|
3038
|
-
tools?:
|
|
3047
|
+
tools?: WorkerConfig['tools'];
|
|
3039
3048
|
systemPrompt?: string;
|
|
3040
3049
|
}>): void;
|
|
3041
3050
|
|
package/dist/index.js
CHANGED
|
@@ -199,8 +199,8 @@ function generateToolCallCacheKey(toolName, args) {
|
|
|
199
199
|
return `${toolName}:${sortedArgs}`;
|
|
200
200
|
}
|
|
201
201
|
function createPatternLogger(name, defaultLevel = "info") {
|
|
202
|
-
const
|
|
203
|
-
return createLogger(name, { level:
|
|
202
|
+
const logLevel = process.env.LOG_LEVEL?.toLowerCase() || defaultLevel;
|
|
203
|
+
return createLogger(name, { level: logLevel });
|
|
204
204
|
}
|
|
205
205
|
function calculateDeduplicationSavings(duplicatesSkipped, toolsExecuted) {
|
|
206
206
|
if (duplicatesSkipped === 0) {
|
|
@@ -2300,17 +2300,87 @@ function getRoutingStrategy(name) {
|
|
|
2300
2300
|
}
|
|
2301
2301
|
|
|
2302
2302
|
// src/multi-agent/utils.ts
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2303
|
+
var logger = createPatternLogger("agentforge:patterns:multi-agent:utils");
|
|
2304
|
+
function isRecord(value) {
|
|
2305
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2306
|
+
}
|
|
2307
|
+
function toRunnableConfig(config) {
|
|
2308
|
+
if (!isRecord(config)) {
|
|
2309
|
+
return void 0;
|
|
2310
|
+
}
|
|
2311
|
+
return config;
|
|
2312
|
+
}
|
|
2313
|
+
function getReActResultShape(value) {
|
|
2314
|
+
if (!isRecord(value)) {
|
|
2315
|
+
return {};
|
|
2316
|
+
}
|
|
2317
|
+
const messages = Array.isArray(value.messages) ? value.messages.filter((message) => isRecord(message)) : void 0;
|
|
2318
|
+
const actions = Array.isArray(value.actions) ? value.actions.filter((action) => isRecord(action)) : void 0;
|
|
2319
|
+
return {
|
|
2320
|
+
messages,
|
|
2321
|
+
actions,
|
|
2322
|
+
iteration: value.iteration
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
function safeSerializeContent(content) {
|
|
2326
|
+
if (content === null || content === void 0) {
|
|
2327
|
+
return void 0;
|
|
2328
|
+
}
|
|
2329
|
+
if (typeof content === "string") {
|
|
2330
|
+
return content.length > 0 ? content : void 0;
|
|
2331
|
+
}
|
|
2332
|
+
if (Array.isArray(content)) {
|
|
2333
|
+
const parts = content.map((part) => {
|
|
2334
|
+
if (typeof part === "string") {
|
|
2335
|
+
return part;
|
|
2336
|
+
}
|
|
2337
|
+
if (isRecord(part) && typeof part.text === "string" && part.text.length > 0) {
|
|
2338
|
+
return part.text;
|
|
2339
|
+
}
|
|
2340
|
+
try {
|
|
2341
|
+
return JSON.stringify(part);
|
|
2342
|
+
} catch {
|
|
2343
|
+
return String(part);
|
|
2344
|
+
}
|
|
2345
|
+
}).filter((part) => part.length > 0);
|
|
2346
|
+
return parts.length > 0 ? parts.join("\n") : void 0;
|
|
2347
|
+
}
|
|
2348
|
+
try {
|
|
2349
|
+
const serialized = JSON.stringify(content);
|
|
2350
|
+
if (typeof serialized === "string" && serialized.length > 0 && serialized !== "null") {
|
|
2351
|
+
return serialized;
|
|
2352
|
+
}
|
|
2353
|
+
} catch {
|
|
2354
|
+
}
|
|
2355
|
+
const fallback = String(content);
|
|
2356
|
+
return fallback.length > 0 ? fallback : void 0;
|
|
2357
|
+
}
|
|
2358
|
+
function extractResponse(resultShape) {
|
|
2359
|
+
const { messages } = resultShape;
|
|
2360
|
+
const lastMessage = messages?.[messages.length - 1];
|
|
2361
|
+
const serialized = safeSerializeContent(lastMessage?.content);
|
|
2362
|
+
return serialized ?? "No response";
|
|
2363
|
+
}
|
|
2364
|
+
function extractToolsUsed(resultShape) {
|
|
2365
|
+
const { actions } = resultShape;
|
|
2366
|
+
const names = actions?.map((action) => action.name).filter((name) => typeof name === "string" && name.length > 0) || [];
|
|
2367
|
+
return [...new Set(names)];
|
|
2368
|
+
}
|
|
2369
|
+
function extractIteration(resultShape) {
|
|
2370
|
+
const { iteration } = resultShape;
|
|
2371
|
+
return typeof iteration === "number" && Number.isFinite(iteration) ? iteration : 0;
|
|
2372
|
+
}
|
|
2306
2373
|
function isReActAgent(obj) {
|
|
2307
|
-
return
|
|
2308
|
-
|
|
2374
|
+
return Boolean(
|
|
2375
|
+
obj && typeof obj === "object" && "invoke" in obj && typeof obj.invoke === "function" && "stream" in obj && typeof obj.stream === "function" && // Additional check to ensure it's not just any object with invoke/stream
|
|
2376
|
+
(obj.constructor?.name === "CompiledGraph" || obj.constructor?.name === "CompiledStateGraph")
|
|
2377
|
+
);
|
|
2309
2378
|
}
|
|
2310
2379
|
function wrapReActAgent(workerId, agent, verbose = false) {
|
|
2311
2380
|
return async (state, config) => {
|
|
2312
2381
|
try {
|
|
2313
2382
|
logger.debug("Wrapping ReAct agent execution", { workerId });
|
|
2383
|
+
const runnableConfig = toRunnableConfig(config);
|
|
2314
2384
|
const currentAssignment = state.activeAssignments.find(
|
|
2315
2385
|
(assignment) => assignment.workerId === workerId && !state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2316
2386
|
);
|
|
@@ -2324,17 +2394,17 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2324
2394
|
assignmentId: currentAssignment.id,
|
|
2325
2395
|
taskPreview: task.substring(0, 100) + (task.length > 100 ? "..." : "")
|
|
2326
2396
|
});
|
|
2327
|
-
const workerThreadId =
|
|
2397
|
+
const workerThreadId = runnableConfig?.configurable?.thread_id ? `${runnableConfig.configurable.thread_id}:worker:${workerId}` : void 0;
|
|
2328
2398
|
const workerConfig = workerThreadId ? {
|
|
2329
|
-
...
|
|
2399
|
+
...runnableConfig,
|
|
2330
2400
|
configurable: {
|
|
2331
|
-
...
|
|
2401
|
+
...runnableConfig?.configurable ?? {},
|
|
2332
2402
|
thread_id: workerThreadId
|
|
2333
2403
|
}
|
|
2334
|
-
} :
|
|
2404
|
+
} : runnableConfig;
|
|
2335
2405
|
logger.debug("Invoking ReAct agent with worker-specific config", {
|
|
2336
2406
|
workerId,
|
|
2337
|
-
parentThreadId:
|
|
2407
|
+
parentThreadId: runnableConfig?.configurable?.thread_id,
|
|
2338
2408
|
workerThreadId,
|
|
2339
2409
|
hasConfig: !!workerConfig
|
|
2340
2410
|
});
|
|
@@ -2345,13 +2415,13 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2345
2415
|
workerConfig
|
|
2346
2416
|
// Worker-specific config with unique thread_id
|
|
2347
2417
|
);
|
|
2348
|
-
const
|
|
2418
|
+
const resultShape = getReActResultShape(result);
|
|
2419
|
+
const response = extractResponse(resultShape);
|
|
2349
2420
|
logger.debug("Received response from ReAct agent", {
|
|
2350
2421
|
workerId,
|
|
2351
2422
|
responsePreview: response.substring(0, 100) + (response.length > 100 ? "..." : "")
|
|
2352
2423
|
});
|
|
2353
|
-
const
|
|
2354
|
-
const uniqueTools = [...new Set(toolsUsed)];
|
|
2424
|
+
const uniqueTools = extractToolsUsed(resultShape);
|
|
2355
2425
|
if (uniqueTools.length > 0) {
|
|
2356
2426
|
logger.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
|
|
2357
2427
|
}
|
|
@@ -2363,7 +2433,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2363
2433
|
success: true,
|
|
2364
2434
|
metadata: {
|
|
2365
2435
|
agent_type: "react",
|
|
2366
|
-
iterations:
|
|
2436
|
+
iterations: extractIteration(resultShape),
|
|
2367
2437
|
tools_used: uniqueTools
|
|
2368
2438
|
}
|
|
2369
2439
|
};
|
|
@@ -2371,7 +2441,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2371
2441
|
completedTasks: [taskResult]
|
|
2372
2442
|
};
|
|
2373
2443
|
} catch (error) {
|
|
2374
|
-
const errorMessage = handleNodeError(error, `react-agent:${workerId}`,
|
|
2444
|
+
const errorMessage = handleNodeError(error, `react-agent:${workerId}`, verbose);
|
|
2375
2445
|
logger.error("Error in ReAct agent execution", {
|
|
2376
2446
|
workerId,
|
|
2377
2447
|
error: errorMessage
|
|
@@ -2404,9 +2474,14 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2404
2474
|
|
|
2405
2475
|
// src/multi-agent/nodes.ts
|
|
2406
2476
|
import { HumanMessage as HumanMessage5, SystemMessage as SystemMessage5 } from "@langchain/core/messages";
|
|
2407
|
-
import { toLangChainTools as toLangChainTools2
|
|
2408
|
-
var
|
|
2409
|
-
|
|
2477
|
+
import { toLangChainTools as toLangChainTools2 } from "@agentforge/core";
|
|
2478
|
+
var logger2 = createPatternLogger("agentforge:patterns:multi-agent:nodes");
|
|
2479
|
+
function convertWorkerToolsForLangChain(tools) {
|
|
2480
|
+
const safeTools = tools ?? [];
|
|
2481
|
+
return toLangChainTools2(
|
|
2482
|
+
safeTools
|
|
2483
|
+
);
|
|
2484
|
+
}
|
|
2410
2485
|
var DEFAULT_AGGREGATOR_SYSTEM_PROMPT = `You are an aggregator agent responsible for combining results from multiple worker agents.
|
|
2411
2486
|
|
|
2412
2487
|
Your job is to:
|
|
@@ -2630,7 +2705,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2630
2705
|
toolCount: tools.length,
|
|
2631
2706
|
toolNames: tools.map((t) => t.metadata.name)
|
|
2632
2707
|
});
|
|
2633
|
-
const langchainTools =
|
|
2708
|
+
const langchainTools = convertWorkerToolsForLangChain(tools);
|
|
2634
2709
|
modelToUse = model.bindTools(langchainTools);
|
|
2635
2710
|
}
|
|
2636
2711
|
logger2.debug("Invoking LLM", { workerId: id });
|
|
@@ -2852,15 +2927,17 @@ Please synthesize these results into a comprehensive response that addresses the
|
|
|
2852
2927
|
|
|
2853
2928
|
// src/multi-agent/agent.ts
|
|
2854
2929
|
import { StateGraph as StateGraph4, END as END4 } from "@langchain/langgraph";
|
|
2855
|
-
|
|
2856
|
-
var logLevel3 = process.env.LOG_LEVEL?.toLowerCase() || LogLevel3.INFO;
|
|
2857
|
-
var logger3 = createLogger4("multi-agent:system", { level: logLevel3 });
|
|
2930
|
+
var logger3 = createPatternLogger("agentforge:patterns:multi-agent:system");
|
|
2858
2931
|
function getToolName(tool) {
|
|
2859
|
-
if (tool
|
|
2860
|
-
return
|
|
2932
|
+
if (!tool || typeof tool !== "object") {
|
|
2933
|
+
return "unknown";
|
|
2934
|
+
}
|
|
2935
|
+
const candidate = tool;
|
|
2936
|
+
if (typeof candidate.metadata?.name === "string") {
|
|
2937
|
+
return candidate.metadata.name;
|
|
2861
2938
|
}
|
|
2862
|
-
if (
|
|
2863
|
-
return
|
|
2939
|
+
if (typeof candidate.name === "string") {
|
|
2940
|
+
return candidate.name;
|
|
2864
2941
|
}
|
|
2865
2942
|
return "unknown";
|
|
2866
2943
|
}
|
|
@@ -2958,7 +3035,7 @@ function createMultiAgentSystem(config) {
|
|
|
2958
3035
|
workflow.addConditionalEdges("aggregator", aggregatorRouter, [END4]);
|
|
2959
3036
|
const compiled = workflow.compile(checkpointer ? { checkpointer } : void 0);
|
|
2960
3037
|
const originalInvoke = compiled.invoke.bind(compiled);
|
|
2961
|
-
compiled.invoke = async function(input, config2) {
|
|
3038
|
+
compiled.invoke = (async function(input, config2) {
|
|
2962
3039
|
const mergedInput = {
|
|
2963
3040
|
...input,
|
|
2964
3041
|
workers: {
|
|
@@ -2966,10 +3043,13 @@ function createMultiAgentSystem(config) {
|
|
|
2966
3043
|
...input.workers || {}
|
|
2967
3044
|
}
|
|
2968
3045
|
};
|
|
2969
|
-
return originalInvoke(
|
|
2970
|
-
|
|
3046
|
+
return originalInvoke(
|
|
3047
|
+
mergedInput,
|
|
3048
|
+
config2
|
|
3049
|
+
);
|
|
3050
|
+
});
|
|
2971
3051
|
const originalStream = compiled.stream.bind(compiled);
|
|
2972
|
-
compiled.stream = async function(input, config2) {
|
|
3052
|
+
compiled.stream = (async function(input, config2) {
|
|
2973
3053
|
const mergedInput = {
|
|
2974
3054
|
...input,
|
|
2975
3055
|
workers: {
|
|
@@ -2977,8 +3057,11 @@ function createMultiAgentSystem(config) {
|
|
|
2977
3057
|
...input.workers || {}
|
|
2978
3058
|
}
|
|
2979
3059
|
};
|
|
2980
|
-
return originalStream(
|
|
2981
|
-
|
|
3060
|
+
return originalStream(
|
|
3061
|
+
mergedInput,
|
|
3062
|
+
config2
|
|
3063
|
+
);
|
|
3064
|
+
});
|
|
2982
3065
|
return compiled;
|
|
2983
3066
|
}
|
|
2984
3067
|
var MultiAgentSystemBuilder = class {
|
|
@@ -3057,7 +3140,7 @@ var MultiAgentSystemBuilder = class {
|
|
|
3057
3140
|
};
|
|
3058
3141
|
function registerWorkers(system, workers) {
|
|
3059
3142
|
console.warn(
|
|
3060
|
-
"[AgentForge] registerWorkers() on a compiled system only updates worker capabilities in state.\nIt does NOT add worker nodes to the graph. Use MultiAgentSystemBuilder for proper worker registration.\nSee: https://github.com/
|
|
3143
|
+
"[AgentForge] registerWorkers() on a compiled system only updates worker capabilities in state.\nIt does NOT add worker nodes to the graph. Use MultiAgentSystemBuilder for proper worker registration.\nSee: https://github.com/TVScoundrel/agentforge/blob/main/packages/patterns/docs/multi-agent-pattern.md"
|
|
3061
3144
|
);
|
|
3062
3145
|
if (!system._workerRegistry) {
|
|
3063
3146
|
system._workerRegistry = {};
|
|
@@ -3072,7 +3155,7 @@ function registerWorkers(system, workers) {
|
|
|
3072
3155
|
}
|
|
3073
3156
|
if (!system._originalInvoke) {
|
|
3074
3157
|
system._originalInvoke = system.invoke.bind(system);
|
|
3075
|
-
system.invoke = async function(input, config) {
|
|
3158
|
+
system.invoke = (async function(input, config) {
|
|
3076
3159
|
const mergedInput = {
|
|
3077
3160
|
...input,
|
|
3078
3161
|
workers: {
|
|
@@ -3080,12 +3163,15 @@ function registerWorkers(system, workers) {
|
|
|
3080
3163
|
...input.workers || {}
|
|
3081
3164
|
}
|
|
3082
3165
|
};
|
|
3083
|
-
return system._originalInvoke(
|
|
3084
|
-
|
|
3166
|
+
return system._originalInvoke(
|
|
3167
|
+
mergedInput,
|
|
3168
|
+
config
|
|
3169
|
+
);
|
|
3170
|
+
});
|
|
3085
3171
|
}
|
|
3086
3172
|
if (!system._originalStream) {
|
|
3087
3173
|
system._originalStream = system.stream.bind(system);
|
|
3088
|
-
system.stream = async function(input, config) {
|
|
3174
|
+
system.stream = (async function(input, config) {
|
|
3089
3175
|
const mergedInput = {
|
|
3090
3176
|
...input,
|
|
3091
3177
|
workers: {
|
|
@@ -3093,8 +3179,11 @@ function registerWorkers(system, workers) {
|
|
|
3093
3179
|
...input.workers || {}
|
|
3094
3180
|
}
|
|
3095
3181
|
};
|
|
3096
|
-
return system._originalStream(
|
|
3097
|
-
|
|
3182
|
+
return system._originalStream(
|
|
3183
|
+
mergedInput,
|
|
3184
|
+
config
|
|
3185
|
+
);
|
|
3186
|
+
});
|
|
3098
3187
|
}
|
|
3099
3188
|
}
|
|
3100
3189
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentforge/patterns",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.5",
|
|
4
4
|
"description": "Production-ready agent workflow patterns for TypeScript including ReAct and Planner-Executor, built on LangGraph.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"url": "https://github.com/TVScoundrel/agentforge/issues"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@agentforge/core": "0.15.
|
|
44
|
+
"@agentforge/core": "0.15.5",
|
|
45
45
|
"@langchain/core": "^1.1.17",
|
|
46
46
|
"@langchain/langgraph": "^1.1.2",
|
|
47
47
|
"zod": "^3.23.8"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@agentforge/testing": "0.15.
|
|
50
|
+
"@agentforge/testing": "0.15.5",
|
|
51
51
|
"@eslint/js": "^9.17.0",
|
|
52
52
|
"@types/node": "^22.10.2",
|
|
53
53
|
"eslint": "^9.17.0",
|