lynkr 1.0.0 → 2.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.
- package/CITATIONS.bib +6 -0
- package/DEPLOYMENT.md +1001 -0
- package/README.md +215 -71
- package/docs/index.md +55 -2
- package/monitor-agents.sh +31 -0
- package/package.json +7 -3
- package/src/agents/context-manager.js +220 -0
- package/src/agents/definitions/loader.js +563 -0
- package/src/agents/executor.js +412 -0
- package/src/agents/index.js +157 -0
- package/src/agents/parallel-coordinator.js +68 -0
- package/src/agents/reflector.js +321 -0
- package/src/agents/skillbook.js +331 -0
- package/src/agents/store.js +244 -0
- package/src/api/router.js +55 -0
- package/src/clients/databricks.js +214 -17
- package/src/clients/routing.js +15 -7
- package/src/clients/standard-tools.js +341 -0
- package/src/config/index.js +41 -5
- package/src/orchestrator/index.js +254 -37
- package/src/server.js +2 -0
- package/src/tools/agent-task.js +96 -0
- package/test/azure-openai-config.test.js +203 -0
- package/test/azure-openai-error-resilience.test.js +238 -0
- package/test/azure-openai-format-conversion.test.js +354 -0
- package/test/azure-openai-integration.test.js +281 -0
- package/test/azure-openai-routing.test.js +148 -0
- package/test/azure-openai-streaming.test.js +171 -0
- package/test/format-conversion.test.js +578 -0
- package/test/hybrid-routing-integration.test.js +18 -11
- package/test/openrouter-error-resilience.test.js +418 -0
- package/test/passthrough-mode.test.js +385 -0
- package/test/routing.test.js +9 -3
- package/test/web-tools.test.js +3 -0
- package/test-agents-simple.js +43 -0
- package/test-cli-connection.sh +33 -0
- package/test-learning-unit.js +126 -0
- package/test-learning.js +112 -0
- package/test-parallel-agents.sh +124 -0
- package/test-parallel-direct.js +155 -0
- package/test-subagents.sh +117 -0
|
@@ -699,10 +699,10 @@ function toAnthropicResponse(openai, requestedModel, wantsThinking) {
|
|
|
699
699
|
choice?.finish_reason === "stop"
|
|
700
700
|
? "end_turn"
|
|
701
701
|
: choice?.finish_reason === "length"
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
702
|
+
? "max_tokens"
|
|
703
|
+
: choice?.finish_reason === "tool_calls"
|
|
704
|
+
? "tool_use"
|
|
705
|
+
: choice?.finish_reason ?? "end_turn",
|
|
706
706
|
stop_sequence: null,
|
|
707
707
|
usage: {
|
|
708
708
|
input_tokens: usage.prompt_tokens ?? 0,
|
|
@@ -874,9 +874,9 @@ function sanitizePayload(payload) {
|
|
|
874
874
|
tools && tools.length > 0
|
|
875
875
|
? tools
|
|
876
876
|
: DEFAULT_AZURE_TOOLS.map((tool) => ({
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
877
|
+
name: tool.name,
|
|
878
|
+
input_schema: JSON.parse(JSON.stringify(tool.input_schema)),
|
|
879
|
+
}));
|
|
880
880
|
delete clean.tool_choice;
|
|
881
881
|
} else if (providerType === "ollama") {
|
|
882
882
|
// Check if model supports tools
|
|
@@ -1122,7 +1122,7 @@ async function runAgentLoop({
|
|
|
1122
1122
|
"Azure Anthropic request payload structure",
|
|
1123
1123
|
);
|
|
1124
1124
|
}
|
|
1125
|
-
|
|
1125
|
+
|
|
1126
1126
|
const databricksResponse = await invokeModel(cleanPayload);
|
|
1127
1127
|
|
|
1128
1128
|
// Handle streaming responses (pass through without buffering)
|
|
@@ -1195,7 +1195,7 @@ async function runAgentLoop({
|
|
|
1195
1195
|
// Extract message and tool calls based on provider response format
|
|
1196
1196
|
let message = {};
|
|
1197
1197
|
let toolCalls = [];
|
|
1198
|
-
|
|
1198
|
+
|
|
1199
1199
|
if (providerType === "azure-anthropic") {
|
|
1200
1200
|
// Anthropic format: { content: [{ type: "tool_use", ... }], stop_reason: "tool_use" }
|
|
1201
1201
|
message = {
|
|
@@ -1203,8 +1203,8 @@ async function runAgentLoop({
|
|
|
1203
1203
|
stop_reason: databricksResponse.json?.stop_reason,
|
|
1204
1204
|
};
|
|
1205
1205
|
// Extract tool_use blocks from content array
|
|
1206
|
-
const contentArray = Array.isArray(databricksResponse.json?.content)
|
|
1207
|
-
? databricksResponse.json.content
|
|
1206
|
+
const contentArray = Array.isArray(databricksResponse.json?.content)
|
|
1207
|
+
? databricksResponse.json.content
|
|
1208
1208
|
: [];
|
|
1209
1209
|
toolCalls = contentArray
|
|
1210
1210
|
.filter(block => block?.type === "tool_use")
|
|
@@ -1217,7 +1217,7 @@ async function runAgentLoop({
|
|
|
1217
1217
|
// Keep original block for reference
|
|
1218
1218
|
_anthropic_block: block,
|
|
1219
1219
|
}));
|
|
1220
|
-
|
|
1220
|
+
|
|
1221
1221
|
logger.debug(
|
|
1222
1222
|
{
|
|
1223
1223
|
sessionId: session?.id ?? null,
|
|
@@ -1331,23 +1331,51 @@ async function runAgentLoop({
|
|
|
1331
1331
|
|
|
1332
1332
|
// Check if tool execution should happen on client side
|
|
1333
1333
|
const executionMode = config.toolExecutionMode || "server";
|
|
1334
|
-
|
|
1334
|
+
|
|
1335
|
+
// IMPORTANT: Task tools (subagents) and Web Search tools ALWAYS execute server-side, regardless of execution mode to ensure reliability
|
|
1336
|
+
// Separate Server-side tools from Client-side tools
|
|
1337
|
+
const serverSideToolCalls = [];
|
|
1338
|
+
const clientSideToolCalls = [];
|
|
1339
|
+
|
|
1340
|
+
const SERVER_SIDE_TOOLS = new Set(["task", "web_search", "web_fetch", "websearch", "webfetch"]);
|
|
1341
|
+
|
|
1342
|
+
for (const call of toolCalls) {
|
|
1343
|
+
const toolName = (call.function?.name ?? call.name ?? "").toLowerCase();
|
|
1344
|
+
if (SERVER_SIDE_TOOLS.has(toolName)) {
|
|
1345
|
+
serverSideToolCalls.push(call);
|
|
1346
|
+
} else {
|
|
1347
|
+
clientSideToolCalls.push(call);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// If in passthrough/client mode and there are client-side tools, return them to client
|
|
1352
|
+
// Server-side tools (Task, Web) will be executed below
|
|
1353
|
+
if ((executionMode === "passthrough" || executionMode === "client") && clientSideToolCalls.length > 0) {
|
|
1335
1354
|
logger.info(
|
|
1336
1355
|
{
|
|
1337
1356
|
sessionId: session?.id ?? null,
|
|
1338
|
-
|
|
1357
|
+
totalToolCount: toolCalls.length,
|
|
1358
|
+
serverToolCount: serverSideToolCalls.length,
|
|
1359
|
+
clientToolCount: clientSideToolCalls.length,
|
|
1339
1360
|
executionMode,
|
|
1340
|
-
|
|
1361
|
+
clientTools: clientSideToolCalls.map((c) => c.function?.name ?? c.name),
|
|
1341
1362
|
},
|
|
1342
|
-
"
|
|
1363
|
+
"Hybrid mode: returning non-Task tools to client, executing Task tools on server"
|
|
1343
1364
|
);
|
|
1344
1365
|
|
|
1366
|
+
// Filter sessionContent to only include client-side tool_use blocks
|
|
1367
|
+
const clientContent = sessionContent.filter(block => {
|
|
1368
|
+
if (block.type !== "tool_use") return true; // Keep text blocks
|
|
1369
|
+
const toolName = (block.name ?? "").toLowerCase();
|
|
1370
|
+
return !SERVER_SIDE_TOOLS.has(toolName); // Keep client-side tool_use blocks
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1345
1373
|
// Convert OpenRouter response to Anthropic format for CLI
|
|
1346
1374
|
const anthropicResponse = {
|
|
1347
1375
|
id: databricksResponse.json?.id || `msg_${Date.now()}`,
|
|
1348
1376
|
type: "message",
|
|
1349
1377
|
role: "assistant",
|
|
1350
|
-
content:
|
|
1378
|
+
content: clientContent,
|
|
1351
1379
|
model: databricksResponse.json?.model || clean.model,
|
|
1352
1380
|
stop_reason: "tool_use",
|
|
1353
1381
|
usage: databricksResponse.json?.usage || {
|
|
@@ -1356,32 +1384,51 @@ async function runAgentLoop({
|
|
|
1356
1384
|
},
|
|
1357
1385
|
};
|
|
1358
1386
|
|
|
1359
|
-
// Debug: Log the actual content being returned
|
|
1360
1387
|
logger.debug(
|
|
1361
1388
|
{
|
|
1362
1389
|
sessionId: session?.id ?? null,
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
firstBlock: Array.isArray(sessionContent) && sessionContent.length > 0 ? sessionContent[0] : null,
|
|
1366
|
-
responseId: anthropicResponse.id,
|
|
1367
|
-
stopReason: anthropicResponse.stop_reason,
|
|
1390
|
+
clientContentLength: clientContent.length,
|
|
1391
|
+
clientContentTypes: clientContent.map(b => b.type),
|
|
1368
1392
|
},
|
|
1369
|
-
"Passthrough: returning
|
|
1393
|
+
"Passthrough: returning client-side tools to client"
|
|
1370
1394
|
);
|
|
1371
1395
|
|
|
1372
|
-
//
|
|
1373
|
-
//
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1396
|
+
// If there are server-side tools, we need to execute them server-side first
|
|
1397
|
+
// then continue the conversation loop. For now, let's fall through to execute server-side tools.
|
|
1398
|
+
if (serverSideToolCalls.length === 0) {
|
|
1399
|
+
// No server-side tools - pure passthrough
|
|
1400
|
+
return {
|
|
1401
|
+
response: {
|
|
1402
|
+
status: 200,
|
|
1403
|
+
body: anthropicResponse,
|
|
1404
|
+
terminationReason: "tool_use",
|
|
1405
|
+
},
|
|
1406
|
+
steps,
|
|
1407
|
+
durationMs: Date.now() - start,
|
|
1379
1408
|
terminationReason: "tool_use",
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
// Has Server-side tools - we need to execute them and continue
|
|
1413
|
+
// Override toolCalls to only include Server-side tools for server execution
|
|
1414
|
+
toolCalls = serverSideToolCalls;
|
|
1415
|
+
|
|
1416
|
+
logger.info(
|
|
1417
|
+
{
|
|
1418
|
+
sessionId: session?.id ?? null,
|
|
1419
|
+
serverToolCount: serverSideToolCalls.length,
|
|
1380
1420
|
},
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1421
|
+
"Executing server-side tools in hybrid mode"
|
|
1422
|
+
);
|
|
1423
|
+
} else if (executionMode === "passthrough" || executionMode === "client") {
|
|
1424
|
+
// Only Server-side tools, no Client-side tools - execute all server-side
|
|
1425
|
+
logger.info(
|
|
1426
|
+
{
|
|
1427
|
+
sessionId: session?.id ?? null,
|
|
1428
|
+
serverToolCount: serverSideToolCalls.length,
|
|
1429
|
+
},
|
|
1430
|
+
"All tools are server-side tools - executing server-side"
|
|
1431
|
+
);
|
|
1385
1432
|
}
|
|
1386
1433
|
|
|
1387
1434
|
logger.debug(
|
|
@@ -1413,8 +1460,127 @@ async function runAgentLoop({
|
|
|
1413
1460
|
toolCallsWithPolicy.push({ call, decision });
|
|
1414
1461
|
}
|
|
1415
1462
|
|
|
1416
|
-
//
|
|
1417
|
-
|
|
1463
|
+
// Identify Task tool calls for parallel execution
|
|
1464
|
+
const taskCalls = [];
|
|
1465
|
+
const nonTaskCalls = [];
|
|
1466
|
+
|
|
1467
|
+
for (const item of toolCallsWithPolicy) {
|
|
1468
|
+
const toolName = (item.call.function?.name ?? item.call.name ?? "").toLowerCase();
|
|
1469
|
+
if (toolName === "task" && item.decision.allowed) {
|
|
1470
|
+
taskCalls.push(item);
|
|
1471
|
+
} else {
|
|
1472
|
+
nonTaskCalls.push(item);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
// Execute Task tools in parallel if multiple exist
|
|
1477
|
+
if (taskCalls.length > 1) {
|
|
1478
|
+
logger.info({
|
|
1479
|
+
taskCount: taskCalls.length,
|
|
1480
|
+
sessionId: session?.id
|
|
1481
|
+
}, "Executing multiple Task tools in parallel");
|
|
1482
|
+
|
|
1483
|
+
try {
|
|
1484
|
+
// Execute all Task tools in parallel
|
|
1485
|
+
const taskExecutions = await Promise.all(
|
|
1486
|
+
taskCalls.map(({ call }) => executeToolCall(call, {
|
|
1487
|
+
session,
|
|
1488
|
+
requestMessages: cleanPayload.messages,
|
|
1489
|
+
}))
|
|
1490
|
+
);
|
|
1491
|
+
|
|
1492
|
+
// Process results and add to messages
|
|
1493
|
+
taskExecutions.forEach((execution, index) => {
|
|
1494
|
+
const call = taskCalls[index].call;
|
|
1495
|
+
toolCallsExecuted += 1;
|
|
1496
|
+
|
|
1497
|
+
let toolMessage;
|
|
1498
|
+
if (providerType === "azure-anthropic") {
|
|
1499
|
+
const parsedContent = parseExecutionContent(execution.content);
|
|
1500
|
+
const serialisedContent =
|
|
1501
|
+
typeof parsedContent === "string" || parsedContent === null
|
|
1502
|
+
? parsedContent ?? ""
|
|
1503
|
+
: JSON.stringify(parsedContent);
|
|
1504
|
+
|
|
1505
|
+
toolMessage = {
|
|
1506
|
+
role: "user",
|
|
1507
|
+
content: [
|
|
1508
|
+
{
|
|
1509
|
+
type: "tool_result",
|
|
1510
|
+
tool_use_id: call.id ?? execution.id,
|
|
1511
|
+
content: serialisedContent,
|
|
1512
|
+
is_error: execution.ok === false,
|
|
1513
|
+
},
|
|
1514
|
+
],
|
|
1515
|
+
};
|
|
1516
|
+
|
|
1517
|
+
toolCallNames.set(
|
|
1518
|
+
call.id ?? execution.id,
|
|
1519
|
+
normaliseToolIdentifier(
|
|
1520
|
+
call.function?.name ?? call.name ?? execution.name ?? "tool",
|
|
1521
|
+
),
|
|
1522
|
+
);
|
|
1523
|
+
} else {
|
|
1524
|
+
toolMessage = {
|
|
1525
|
+
role: "tool",
|
|
1526
|
+
tool_call_id: execution.id,
|
|
1527
|
+
name: execution.name,
|
|
1528
|
+
content: execution.content,
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
cleanPayload.messages.push(toolMessage);
|
|
1533
|
+
|
|
1534
|
+
// Convert to Anthropic format for session storage
|
|
1535
|
+
let sessionToolResultContent;
|
|
1536
|
+
if (providerType === "azure-anthropic") {
|
|
1537
|
+
sessionToolResultContent = toolMessage.content;
|
|
1538
|
+
} else {
|
|
1539
|
+
sessionToolResultContent = [
|
|
1540
|
+
{
|
|
1541
|
+
type: "tool_result",
|
|
1542
|
+
tool_use_id: toolMessage.tool_call_id,
|
|
1543
|
+
content: toolMessage.content,
|
|
1544
|
+
is_error: execution.ok === false,
|
|
1545
|
+
},
|
|
1546
|
+
];
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
appendTurnToSession(session, {
|
|
1550
|
+
role: "tool",
|
|
1551
|
+
type: "tool_result",
|
|
1552
|
+
status: execution.status,
|
|
1553
|
+
content: sessionToolResultContent,
|
|
1554
|
+
metadata: {
|
|
1555
|
+
tool: execution.name,
|
|
1556
|
+
ok: execution.ok,
|
|
1557
|
+
parallel: true,
|
|
1558
|
+
parallelIndex: index,
|
|
1559
|
+
totalParallel: taskExecutions.length
|
|
1560
|
+
},
|
|
1561
|
+
});
|
|
1562
|
+
});
|
|
1563
|
+
|
|
1564
|
+
logger.info({
|
|
1565
|
+
completedTasks: taskExecutions.length,
|
|
1566
|
+
sessionId: session?.id
|
|
1567
|
+
}, "Completed parallel Task execution");
|
|
1568
|
+
} catch (error) {
|
|
1569
|
+
logger.error({
|
|
1570
|
+
error: error.message,
|
|
1571
|
+
taskCount: taskCalls.length
|
|
1572
|
+
}, "Error in parallel Task execution");
|
|
1573
|
+
|
|
1574
|
+
// Fall back to sequential execution on error
|
|
1575
|
+
taskCalls.forEach(item => nonTaskCalls.push(item));
|
|
1576
|
+
}
|
|
1577
|
+
} else if (taskCalls.length === 1) {
|
|
1578
|
+
// Single Task tool - add back to non-task calls for normal processing
|
|
1579
|
+
nonTaskCalls.push(...taskCalls);
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
// Now process results (sequential for non-Task tools or blocked tools)
|
|
1583
|
+
for (const { call, decision } of nonTaskCalls) {
|
|
1418
1584
|
|
|
1419
1585
|
if (!decision.allowed) {
|
|
1420
1586
|
policy.logPolicyDecision(decision, {
|
|
@@ -1653,6 +1819,57 @@ async function runAgentLoop({
|
|
|
1653
1819
|
databricksResponse.json,
|
|
1654
1820
|
requestedModel,
|
|
1655
1821
|
);
|
|
1822
|
+
anthropicPayload.content = policy.sanitiseContent(anthropicPayload.content);
|
|
1823
|
+
} else if (actualProvider === "azure-openai") {
|
|
1824
|
+
const { convertOpenRouterResponseToAnthropic } = require("../clients/openrouter-utils");
|
|
1825
|
+
|
|
1826
|
+
// Validate Azure OpenAI response has choices array before conversion
|
|
1827
|
+
if (!databricksResponse.json?.choices?.length) {
|
|
1828
|
+
logger.warn({
|
|
1829
|
+
json: databricksResponse.json,
|
|
1830
|
+
status: databricksResponse.status
|
|
1831
|
+
}, "Azure OpenAI response missing choices array");
|
|
1832
|
+
|
|
1833
|
+
appendTurnToSession(session, {
|
|
1834
|
+
role: "assistant",
|
|
1835
|
+
type: "error",
|
|
1836
|
+
status: databricksResponse.status,
|
|
1837
|
+
content: databricksResponse.json,
|
|
1838
|
+
metadata: { termination: "malformed_response" },
|
|
1839
|
+
});
|
|
1840
|
+
|
|
1841
|
+
const response = buildErrorResponse(databricksResponse);
|
|
1842
|
+
return {
|
|
1843
|
+
response,
|
|
1844
|
+
steps,
|
|
1845
|
+
durationMs: Date.now() - start,
|
|
1846
|
+
terminationReason: response.terminationReason,
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
// Log Azure OpenAI raw response
|
|
1851
|
+
logger.info({
|
|
1852
|
+
hasChoices: !!databricksResponse.json?.choices,
|
|
1853
|
+
choiceCount: databricksResponse.json?.choices?.length || 0,
|
|
1854
|
+
firstChoice: databricksResponse.json?.choices?.[0],
|
|
1855
|
+
hasToolCalls: !!databricksResponse.json?.choices?.[0]?.message?.tool_calls,
|
|
1856
|
+
toolCallCount: databricksResponse.json?.choices?.[0]?.message?.tool_calls?.length || 0,
|
|
1857
|
+
finishReason: databricksResponse.json?.choices?.[0]?.finish_reason
|
|
1858
|
+
}, "=== AZURE OPENAI RAW RESPONSE ===");
|
|
1859
|
+
|
|
1860
|
+
// Convert OpenAI format to Anthropic format (reuse OpenRouter utility)
|
|
1861
|
+
anthropicPayload = convertOpenRouterResponseToAnthropic(
|
|
1862
|
+
databricksResponse.json,
|
|
1863
|
+
requestedModel,
|
|
1864
|
+
);
|
|
1865
|
+
|
|
1866
|
+
logger.info({
|
|
1867
|
+
contentBlocks: anthropicPayload.content?.length || 0,
|
|
1868
|
+
contentTypes: anthropicPayload.content?.map(c => c.type) || [],
|
|
1869
|
+
stopReason: anthropicPayload.stop_reason,
|
|
1870
|
+
hasToolUse: anthropicPayload.content?.some(c => c.type === 'tool_use')
|
|
1871
|
+
}, "=== CONVERTED ANTHROPIC RESPONSE ===");
|
|
1872
|
+
|
|
1656
1873
|
anthropicPayload.content = policy.sanitiseContent(anthropicPayload.content);
|
|
1657
1874
|
} else {
|
|
1658
1875
|
anthropicPayload = toAnthropicResponse(
|
package/src/server.js
CHANGED
|
@@ -26,6 +26,7 @@ const { registerGitTools } = require("./tools/git");
|
|
|
26
26
|
const { registerTaskTools } = require("./tools/tasks");
|
|
27
27
|
const { registerTestTools } = require("./tools/tests");
|
|
28
28
|
const { registerMcpTools } = require("./tools/mcp");
|
|
29
|
+
const { registerAgentTaskTool } = require("./tools/agent-task");
|
|
29
30
|
|
|
30
31
|
initialiseMcp();
|
|
31
32
|
registerStubTools();
|
|
@@ -38,6 +39,7 @@ registerGitTools();
|
|
|
38
39
|
registerTaskTools();
|
|
39
40
|
registerTestTools();
|
|
40
41
|
registerMcpTools();
|
|
42
|
+
registerAgentTaskTool();
|
|
41
43
|
|
|
42
44
|
function createApp() {
|
|
43
45
|
const app = express();
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const { registerTool } = require(".");
|
|
2
|
+
const { spawnAgent, autoSelectAgent } = require("../agents");
|
|
3
|
+
const logger = require("../logger");
|
|
4
|
+
|
|
5
|
+
function registerAgentTaskTool() {
|
|
6
|
+
registerTool(
|
|
7
|
+
"Task",
|
|
8
|
+
async ({ args = {} }, context = {}) => {
|
|
9
|
+
let subagentType = args.subagent_type || args.type;
|
|
10
|
+
const prompt = args.prompt;
|
|
11
|
+
const description = args.description || "Agent task";
|
|
12
|
+
|
|
13
|
+
if (!prompt) {
|
|
14
|
+
return {
|
|
15
|
+
ok: false,
|
|
16
|
+
status: 400,
|
|
17
|
+
content: JSON.stringify({
|
|
18
|
+
error: "prompt is required"
|
|
19
|
+
}, null, 2)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Auto-select agent if not specified
|
|
24
|
+
if (!subagentType) {
|
|
25
|
+
const selected = autoSelectAgent(prompt);
|
|
26
|
+
if (selected) {
|
|
27
|
+
subagentType = selected.name;
|
|
28
|
+
logger.info({
|
|
29
|
+
selectedAgent: subagentType,
|
|
30
|
+
prompt: prompt.slice(0, 50)
|
|
31
|
+
}, "Auto-selected subagent");
|
|
32
|
+
} else {
|
|
33
|
+
subagentType = "Explore"; // Default fallback
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
logger.info({
|
|
38
|
+
subagentType,
|
|
39
|
+
prompt: prompt.slice(0, 100),
|
|
40
|
+
sessionId: context.sessionId
|
|
41
|
+
}, "Task tool: spawning subagent");
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const result = await spawnAgent(subagentType, prompt, {
|
|
45
|
+
sessionId: context.sessionId,
|
|
46
|
+
mainContext: context.mainContext // Pass minimal context
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (result.success) {
|
|
50
|
+
return {
|
|
51
|
+
ok: true,
|
|
52
|
+
status: 200,
|
|
53
|
+
content: result.result,
|
|
54
|
+
metadata: {
|
|
55
|
+
agentType: subagentType,
|
|
56
|
+
agentId: result.stats.agentId,
|
|
57
|
+
steps: result.stats.steps,
|
|
58
|
+
durationMs: result.stats.durationMs
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
} else {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
status: 500,
|
|
65
|
+
content: JSON.stringify({
|
|
66
|
+
error: "Subagent execution failed",
|
|
67
|
+
message: result.error
|
|
68
|
+
}, null, 2)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
} catch (error) {
|
|
73
|
+
logger.error({
|
|
74
|
+
error: error.message,
|
|
75
|
+
subagentType
|
|
76
|
+
}, "Task tool: subagent error");
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
ok: false,
|
|
80
|
+
status: 500,
|
|
81
|
+
content: JSON.stringify({
|
|
82
|
+
error: "Subagent error",
|
|
83
|
+
message: error.message
|
|
84
|
+
}, null, 2)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{ category: "agents" }
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
logger.info("Task tool registered");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = {
|
|
95
|
+
registerAgentTaskTool
|
|
96
|
+
};
|