@agentforge-io/core 2.3.1 → 3.0.0
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.
|
@@ -142,6 +142,11 @@ class OrchestratorService {
|
|
|
142
142
|
return;
|
|
143
143
|
}
|
|
144
144
|
this.logger.debug(`Streaming orchestrator "${agentId}" with subagents: ${agent.subAgents.join(', ')}`);
|
|
145
|
+
// Pre-resolve all sub-agents so `buildDelegationTools` sees their
|
|
146
|
+
// name/description/slug — without this, the agentsMap only has the
|
|
147
|
+
// orchestrator and the tool-name builder falls back to the UUID
|
|
148
|
+
// form, defeating the slug→name match the model relies on.
|
|
149
|
+
await Promise.all(agent.subAgents.map((id) => this.resolveAgentDynamic(id)));
|
|
145
150
|
const delegationTools = this.buildDelegationTools(agent);
|
|
146
151
|
const model = agent.model ?? this.anthropicConfig.defaultModel ?? 'claude-opus-4-6';
|
|
147
152
|
const maxTokens = agent.maxTokens ?? this.anthropicConfig.defaultMaxTokens ?? 4096;
|
|
@@ -213,8 +218,14 @@ class OrchestratorService {
|
|
|
213
218
|
if (block.type !== 'tool_use')
|
|
214
219
|
continue;
|
|
215
220
|
const delegateTool = delegationTools.find((t) => t.name === block.name);
|
|
216
|
-
if (!delegateTool)
|
|
221
|
+
if (!delegateTool) {
|
|
222
|
+
// The model called a tool name we didn't expose. Log loudly
|
|
223
|
+
// so we can spot drift between the prompt's slug references
|
|
224
|
+
// and the actual tool surface.
|
|
225
|
+
this.logger.warn(`Orchestrator "${agent.id}" called unknown tool "${block.name}". ` +
|
|
226
|
+
`Available tools: ${delegationTools.map((t) => t.name).join(', ')}`);
|
|
217
227
|
continue;
|
|
228
|
+
}
|
|
218
229
|
const { task } = block.input;
|
|
219
230
|
const { subAgentId } = delegateTool;
|
|
220
231
|
const subAgent = await this.resolveAgentDynamic(subAgentId);
|
|
@@ -333,6 +344,9 @@ class OrchestratorService {
|
|
|
333
344
|
}
|
|
334
345
|
async runOrchestratorLoop(orchestrator, messages, context) {
|
|
335
346
|
const delegations = [];
|
|
347
|
+
// Pre-resolve subagents so buildDelegationTools sees their
|
|
348
|
+
// slug/name/description (same fix as the stream path).
|
|
349
|
+
await Promise.all((orchestrator.subAgents ?? []).map((id) => this.resolveAgentDynamic(id)));
|
|
336
350
|
const delegationTools = this.buildDelegationTools(orchestrator);
|
|
337
351
|
const model = orchestrator.model ?? this.anthropicConfig.defaultModel ?? 'claude-opus-4-6';
|
|
338
352
|
const maxTokens = orchestrator.maxTokens ?? this.anthropicConfig.defaultMaxTokens ?? 4096;
|
|
@@ -439,13 +453,43 @@ class OrchestratorService {
|
|
|
439
453
|
}
|
|
440
454
|
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
441
455
|
buildDelegationTools(orchestrator) {
|
|
456
|
+
// First pass: collect the slug/uuid-name candidates so we can
|
|
457
|
+
// detect collisions across the team. Anthropic requires `tool.name`
|
|
458
|
+
// to be unique within a single request; two subagents with the
|
|
459
|
+
// same slug (rare but legal at the platform layer in different
|
|
460
|
+
// tenant scopes) must not collapse into one tool.
|
|
461
|
+
const namesUsed = new Set();
|
|
442
462
|
return (orchestrator.subAgents ?? []).map((subAgentId) => {
|
|
443
463
|
const sub = this.agentsMap.get(subAgentId);
|
|
464
|
+
const subSlug = sub?.slug;
|
|
465
|
+
// Tool name strategy: prefer `delegate_to_<slug>` because the model
|
|
466
|
+
// already SEES `@<slug>` in its system prompt; matching it to the
|
|
467
|
+
// tool name eliminates the two-namespace gap that caused
|
|
468
|
+
// wrong-tool calls. Fall back to a sanitized UUID-derived name
|
|
469
|
+
// when no slug is available or when the slug name is already
|
|
470
|
+
// taken by an earlier subagent in this build pass.
|
|
471
|
+
const slugName = subSlug
|
|
472
|
+
? `delegate_to_${subSlug.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 50)}`
|
|
473
|
+
: null;
|
|
474
|
+
const uuidName = `delegate_to_${subAgentId.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
|
475
|
+
let name;
|
|
476
|
+
if (slugName && !namesUsed.has(slugName)) {
|
|
477
|
+
name = slugName;
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
name = uuidName;
|
|
481
|
+
}
|
|
482
|
+
namesUsed.add(name);
|
|
483
|
+
// Description still calls out the slug explicitly — belt and
|
|
484
|
+
// suspenders against models that pattern-match on text more
|
|
485
|
+
// than on tool names.
|
|
444
486
|
const description = sub
|
|
445
|
-
? `Delegate a task to the "${sub.name}" specialist agent
|
|
487
|
+
? `Delegate a task to the "${sub.name}" specialist agent` +
|
|
488
|
+
(subSlug ? ` (mention slug: @${subSlug})` : '') +
|
|
489
|
+
`. ${sub.description ?? ''}`
|
|
446
490
|
: `Delegate a task to subagent "${subAgentId}"`;
|
|
447
491
|
return {
|
|
448
|
-
name
|
|
492
|
+
name,
|
|
449
493
|
description,
|
|
450
494
|
subAgentId,
|
|
451
495
|
inputSchema: {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentforge-io/core",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Framework-free AI runtime SDK. Owns: agent loop (
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Framework-free AI runtime SDK. Owns: agent loop (Anthropic), conversations, tools, streaming, agent-job queue, SdkHooks. Identity, billing, infra (email/uploads/secrets) live in the host's modules \u2014 not here.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -33,4 +33,4 @@
|
|
|
33
33
|
"tsx": "^4.19.0",
|
|
34
34
|
"typescript": "^5.0.0"
|
|
35
35
|
}
|
|
36
|
-
}
|
|
36
|
+
}
|