@botbotgo/agent-harness 0.0.423 → 0.0.425

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.
@@ -1,2 +1,2 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.423";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.425";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-05-03";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.423";
1
+ export const AGENT_HARNESS_VERSION = "0.0.425";
2
2
  export const AGENT_HARNESS_RELEASE_DATE = "2026-05-03";
@@ -235,10 +235,7 @@ function resolveCommittedPlanEvidenceTools(primaryTools, planToolOutput) {
235
235
  const seen = new Set();
236
236
  for (const content of todoContents) {
237
237
  const todoText = content.toLowerCase();
238
- const matches = availableToolNames.filter((name) => todoText.includes(name.toLowerCase()));
239
- const selectedNames = matches.length === 1
240
- ? [matches[0]]
241
- : resolveBestScoredToolNames(availableToolNames, toolsByName, todoText);
238
+ const selectedNames = availableToolNames.filter((name) => hasExplicitToolNameReference(todoText, name));
242
239
  for (const selectedName of selectedNames) {
243
240
  if (seen.has(selectedName)) {
244
241
  continue;
@@ -255,44 +252,13 @@ function resolveCommittedPlanEvidenceTools(primaryTools, planToolOutput) {
255
252
  }
256
253
  return resolved;
257
254
  }
258
- function extractSelectionTokens(value) {
259
- const tokens = new Set();
260
- for (const match of value.matchAll(/[\p{L}\p{N}_-]+/gu)) {
261
- const token = match[0].toLowerCase();
262
- if (token.length >= 2) {
263
- tokens.add(token);
264
- }
265
- for (const part of token.split(/[_-]+/u)) {
266
- if (part.length >= 2) {
267
- tokens.add(part);
268
- }
269
- }
255
+ function hasExplicitToolNameReference(todoText, toolName) {
256
+ const normalizedToolName = toolName.trim().toLowerCase();
257
+ if (!normalizedToolName) {
258
+ return false;
270
259
  }
271
- return tokens;
272
- }
273
- function resolveBestScoredToolNames(availableToolNames, toolsByName, todoText) {
274
- const requestTokens = extractSelectionTokens(todoText);
275
- const scored = availableToolNames
276
- .map((name) => {
277
- const tool = toolsByName.get(name);
278
- const toolTokens = extractSelectionTokens(`${name} ${tool?.description ?? ""}`);
279
- const toolNameTokens = extractSelectionTokens(name);
280
- let score = 0;
281
- for (const token of requestTokens) {
282
- if (toolNameTokens.has(token)) {
283
- score += 10;
284
- continue;
285
- }
286
- if (toolTokens.has(token)) {
287
- score += token.length > 3 ? 2 : 1;
288
- }
289
- }
290
- return { name, score };
291
- })
292
- .filter((item) => item.score > 0)
293
- .sort((left, right) => right.score - left.score);
294
- const topScore = scored[0]?.score ?? 0;
295
- return topScore > 0 ? scored.filter((item) => item.score === topScore).map((item) => item.name) : [];
260
+ const escapedName = normalizedToolName.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
261
+ return new RegExp(`(?:^|[^\\p{L}\\p{N}_-])${escapedName}(?:$|[^\\p{L}\\p{N}_-])`, "iu").test(todoText);
296
262
  }
297
263
  function buildCommittedPlanEvidenceToolArgs(tool, todoText) {
298
264
  const properties = typeof tool?.modelSchema === "object" && tool.modelSchema !== null
@@ -407,17 +407,11 @@ function parseFirstJsonObject(value) {
407
407
  }
408
408
  function parseCompactRouterSelection(value, subagentNames) {
409
409
  const trimmed = value.trim();
410
- if (subagentNames.has(trimmed)) {
411
- return { subagentType: trimmed };
412
- }
413
- const resolveRelaxedMatch = () => {
414
- const lowered = trimmed.toLowerCase();
415
- return Array.from(subagentNames).find((name) => {
416
- const lowerName = name.toLowerCase();
417
- return lowerName.length > 0
418
- && (trimmed === name || lowered.includes(lowerName));
419
- });
420
- };
410
+ const resolveExactName = (candidate) => Array.from(subagentNames).find((name) => name.toLowerCase() === candidate.trim().toLowerCase());
411
+ const exactTrimmedName = resolveExactName(trimmed);
412
+ if (exactTrimmedName) {
413
+ return { subagentType: exactTrimmedName };
414
+ }
421
415
  const parsed = parseFirstJsonObject(trimmed);
422
416
  const toolCall = salvageJsonToolCalls(trimmed).at(0);
423
417
  const payload = typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
@@ -426,8 +420,7 @@ function parseCompactRouterSelection(value, subagentNames) {
426
420
  ? { name: toolCall.name, arguments: toolCall.args }
427
421
  : null;
428
422
  if (!payload) {
429
- const relaxedMatch = resolveRelaxedMatch();
430
- return relaxedMatch ? { subagentType: relaxedMatch } : null;
423
+ return null;
431
424
  }
432
425
  const args = typeof payload.arguments === "object" && payload.arguments !== null && !Array.isArray(payload.arguments)
433
426
  ? payload.arguments
@@ -439,8 +432,9 @@ function parseCompactRouterSelection(value, subagentNames) {
439
432
  : typeof args.subagent_type === "string"
440
433
  ? args.subagent_type.trim()
441
434
  : "";
442
- if (subagentNames.has(subagentType)) {
443
- return { subagentType };
435
+ const resolvedSubagentType = resolveExactName(subagentType);
436
+ if (resolvedSubagentType) {
437
+ return { subagentType: resolvedSubagentType };
444
438
  }
445
439
  const rawDelegations = Array.isArray(payload.delegations)
446
440
  ? payload.delegations
@@ -491,139 +485,8 @@ function parseCompactRouterSelection(value, subagentNames) {
491
485
  : "No configured subagent can handle the request.";
492
486
  return { refusedReason: reason };
493
487
  }
494
- const relaxedMatch = resolveRelaxedMatch();
495
- if (relaxedMatch) {
496
- return { subagentType: relaxedMatch };
497
- }
498
488
  return null;
499
489
  }
500
- function inferCompactRouterSelectionFromRequest(requestText, subagents) {
501
- const normalized = requestText.toLowerCase();
502
- const requestTokens = extractRouterMatchTokens(normalized);
503
- const score = (subagent) => {
504
- const name = subagent.name.toLowerCase();
505
- const description = (subagent.description ?? "").toLowerCase();
506
- const descriptionTokens = extractRouterMatchTokens(`${name} ${description}`);
507
- let value = 0;
508
- if (normalized.includes(name))
509
- value += 4;
510
- for (const token of requestTokens) {
511
- if (token === name) {
512
- value += 4;
513
- }
514
- else if (descriptionTokens.has(token)) {
515
- value += token.length > 2 ? 2 : 1;
516
- }
517
- }
518
- return value;
519
- };
520
- const ranked = subagents
521
- .map((subagent) => ({ name: subagent.name, score: score(subagent) }))
522
- .filter((item) => item.score > 0)
523
- .sort((left, right) => right.score - left.score);
524
- if (ranked.length === 0 || (ranked[1] && ranked[1].score === ranked[0].score)) {
525
- return null;
526
- }
527
- return ranked[0].name;
528
- }
529
- function inferCompactRouterDelegationPlanFromRequest(requestText, subagents, routingPolicy) {
530
- const normalizedRequest = requestText.toLowerCase();
531
- const numberedClauses = requestText
532
- .split(/\r?\n/u)
533
- .map((item) => item.trim())
534
- .filter((item) => /^\d+\s*[.)、.]\s*/u.test(item));
535
- const clauses = (numberedClauses.length > 1 ? numberedClauses : requestText
536
- .split(/\r?\n|[。;;]\s*/u))
537
- .map((item) => item.trim())
538
- .filter((item) => item.length > 0);
539
- if (clauses.length <= 1) {
540
- return [];
541
- }
542
- for (const subagent of subagents) {
543
- const escapedName = escapeRegExp(subagent.name);
544
- const ownerPerspectivePattern = new RegExp(`(?:从\\s*\`?${escapedName}\`?\\s*角度|as\\s+(?:the\\s+)?\`?${escapedName}\`?|from\\s+(?:the\\s+)?\`?${escapedName}\`?\\s+perspective)`, "iu");
545
- if (ownerPerspectivePattern.test(requestText)) {
546
- return [];
547
- }
548
- }
549
- const policyLineFor = (subagentName) => {
550
- if (!routingPolicy) {
551
- return "";
552
- }
553
- const escapedName = escapeRegExp(subagentName);
554
- const match = new RegExp(`(?:^|\\n)\\s*[-*]\\s*\`?${escapedName}\`?\\s*[::]\\s*([^\\n]+)`, "iu").exec(routingPolicy);
555
- return match?.[1] ?? "";
556
- };
557
- const scoreText = (text, signals, subagentName) => {
558
- const normalized = text.toLowerCase();
559
- let value = normalized.includes(subagentName.toLowerCase()) ? 10 : 0;
560
- for (const signal of signals) {
561
- if (normalized.includes(signal)) {
562
- value += /[\p{Script=Han}]/u.test(signal)
563
- ? Math.min(6, Math.max(2, signal.length))
564
- : Math.min(5, Math.max(2, Math.floor(signal.length / 2)));
565
- }
566
- }
567
- return value;
568
- };
569
- const subagentSignals = subagents.map((subagent) => {
570
- const signalText = [
571
- subagent.name,
572
- subagent.description ?? "",
573
- policyLineFor(subagent.name),
574
- ].join(" ");
575
- return {
576
- name: subagent.name,
577
- signals: extractRouterMatchSignals(signalText),
578
- globalScore: 0,
579
- };
580
- });
581
- for (const item of subagentSignals) {
582
- item.globalScore = scoreText(normalizedRequest, item.signals, item.name);
583
- }
584
- const ordered = [];
585
- const pushUnique = (subagentType, description) => {
586
- if (ordered.some((item) => item.subagentType === subagentType)) {
587
- return;
588
- }
589
- ordered.push({ subagentType, description: description.trim() || requestText });
590
- };
591
- for (const clause of clauses) {
592
- const ranked = subagentSignals
593
- .map((item) => ({ name: item.name, score: scoreText(clause, item.signals, item.name) }))
594
- .filter((item) => item.score >= 4)
595
- .sort((left, right) => right.score - left.score);
596
- const top = ranked[0];
597
- if (top) {
598
- pushUnique(top.name, clause);
599
- }
600
- }
601
- if (ordered.length <= 1 && numberedClauses.length > 1) {
602
- ordered.length = 0;
603
- for (const clause of numberedClauses) {
604
- const ranked = subagentSignals
605
- .map((item) => ({ name: item.name, score: scoreText(clause, item.signals, item.name) }))
606
- .filter((item) => item.score > 0)
607
- .sort((left, right) => right.score - left.score);
608
- const top = ranked[0];
609
- if (top) {
610
- pushUnique(top.name, clause);
611
- }
612
- }
613
- }
614
- for (const item of subagentSignals
615
- .filter((candidate) => candidate.globalScore >= 6)
616
- .sort((left, right) => right.globalScore - left.globalScore)) {
617
- pushUnique(item.name, requestText);
618
- }
619
- if (ordered.length <= 1 && numberedClauses.length >= 3 && subagents.length > 1) {
620
- return subagents.map((subagent, index) => ({
621
- subagentType: subagent.name,
622
- description: numberedClauses[index] ?? requestText,
623
- }));
624
- }
625
- return ordered.length > 1 ? ordered : [];
626
- }
627
490
  function resolveSingleExplicitOwnerMention(requestText, subagentNames) {
628
491
  const numberedClauses = requestText
629
492
  .split(/\r?\n/u)
@@ -673,6 +536,55 @@ function extractExplicitSubagentTasks(requestText, subagentNames) {
673
536
  }
674
537
  return deduped;
675
538
  }
539
+ function buildCompactRouterPolicy(routingPolicy, subagentNames) {
540
+ if (!routingPolicy) {
541
+ return "";
542
+ }
543
+ const lines = routingPolicy
544
+ .split(/\r?\n/u)
545
+ .map((line) => line.trim())
546
+ .filter((line) => line.length > 0);
547
+ const selected = [];
548
+ let inRoutingBlock = false;
549
+ let inDelegationBlock = false;
550
+ const subagentPatterns = Array.from(subagentNames).map((name) => {
551
+ const escaped = escapeRegExp(name);
552
+ return new RegExp(`(?:^|[^\\p{L}\\p{N}_-])\`?${escaped}\`?(?:$|[^\\p{L}\\p{N}_-])`, "iu");
553
+ });
554
+ for (const line of lines) {
555
+ const normalized = line.toLowerCase();
556
+ if (/^route by meaning\b/iu.test(line)) {
557
+ inRoutingBlock = true;
558
+ inDelegationBlock = false;
559
+ selected.push(line);
560
+ continue;
561
+ }
562
+ if (/^delegation rules\b/iu.test(line)) {
563
+ inRoutingBlock = false;
564
+ inDelegationBlock = true;
565
+ selected.push(line);
566
+ continue;
567
+ }
568
+ if (/^(output|role|rules|working style|tool rules)\b/iu.test(line)) {
569
+ inRoutingBlock = false;
570
+ inDelegationBlock = false;
571
+ }
572
+ const mentionsSubagent = subagentPatterns.some((pattern) => pattern.test(line));
573
+ const mentionsDelegationPrimitive = normalized.includes("task")
574
+ || normalized.includes("delegate")
575
+ || normalized.includes("delegating")
576
+ || normalized.includes("委托")
577
+ || normalized.includes("路由");
578
+ if (inRoutingBlock && mentionsSubagent) {
579
+ selected.push(line);
580
+ continue;
581
+ }
582
+ if (inDelegationBlock && (mentionsSubagent || mentionsDelegationPrimitive)) {
583
+ selected.push(line);
584
+ }
585
+ }
586
+ return selected.slice(0, 80).join("\n");
587
+ }
676
588
  function buildDelegatedOwnedTaskInstruction(input) {
677
589
  const relevantPolicyLines = (input.routingPolicy ?? "")
678
590
  .split(/\r?\n/u)
@@ -696,77 +608,6 @@ function buildDelegatedOwnedTaskInstruction(input) {
696
608
  function escapeRegExp(value) {
697
609
  return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
698
610
  }
699
- function extractRouterMatchTokens(value) {
700
- const tokens = new Set();
701
- for (const match of value.matchAll(/[\p{L}\p{N}_-]+/gu)) {
702
- const token = match[0].toLowerCase();
703
- if (token.length >= 2) {
704
- tokens.add(token);
705
- }
706
- }
707
- return tokens;
708
- }
709
- function extractRouterMatchSignals(value) {
710
- const signals = extractRouterMatchTokens(value);
711
- const stopSignals = new Set([
712
- "the",
713
- "and",
714
- "for",
715
- "with",
716
- "owner",
717
- "primary",
718
- "route",
719
- "agent",
720
- "tool",
721
- "tools",
722
- "todo",
723
- "json",
724
- "status",
725
- "report",
726
- "final",
727
- "output",
728
- "failed",
729
- "blocked",
730
- "work",
731
- "request",
732
- "user",
733
- "负责",
734
- "不是",
735
- "用户",
736
- "请求",
737
- "当前",
738
- "输出",
739
- "证据",
740
- "使用",
741
- "必须",
742
- "一个",
743
- "如果",
744
- "需要",
745
- "工作",
746
- "总结",
747
- "分析",
748
- "最终",
749
- "相关",
750
- "工具",
751
- "执行",
752
- "结果",
753
- ]);
754
- for (const stopSignal of stopSignals) {
755
- signals.delete(stopSignal);
756
- }
757
- for (const match of value.matchAll(/[\p{Script=Han}]{2,}/gu)) {
758
- const sequence = match[0];
759
- for (let size = 2; size <= Math.min(4, sequence.length); size += 1) {
760
- for (let index = 0; index <= sequence.length - size; index += 1) {
761
- const signal = sequence.slice(index, index + size);
762
- if (!stopSignals.has(signal)) {
763
- signals.add(signal);
764
- }
765
- }
766
- }
767
- }
768
- return signals;
769
- }
770
611
  function isDelegationOnlyDeepAgentBinding(binding) {
771
612
  return isDeepAgentBinding(binding)
772
613
  && getBindingSubagents(binding).length > 0
@@ -1584,18 +1425,21 @@ export class AgentRuntimeAdapter {
1584
1425
  }
1585
1426
  const subagents = getBindingSubagents(binding);
1586
1427
  const subagentNames = new Set(subagents.map((subagent) => subagent.name));
1587
- const inferredSubagent = inferCompactRouterSelectionFromRequest(requestText, subagents);
1588
- let selection = inferredSubagent ? { subagentType: inferredSubagent } : null;
1428
+ let selection = null;
1589
1429
  const subagentCatalog = subagents
1590
1430
  .map((subagent) => `- ${subagent.name}: ${subagent.description}`)
1591
1431
  .join("\n");
1592
1432
  const routingPolicy = getBindingSystemPrompt(binding);
1433
+ const compactRoutingPolicy = buildCompactRouterPolicy(routingPolicy, subagentNames);
1593
1434
  const prompt = [
1594
1435
  primaryModel.init?.think === false ? "/no_think" : "",
1595
1436
  "You are selecting a subagent for a delegation-only agent.",
1596
1437
  "Choose exactly one listed subagent when it can responsibly handle the request.",
1597
- routingPolicy ? "Agent routing policy:" : "",
1598
- routingPolicy ?? "",
1438
+ "Use the agent routing policy as authoritative routing configuration when it maps the request meaning to a listed subagent.",
1439
+ "Do not refuse while any listed subagent can own the request after applying the routing policy.",
1440
+ "Refuse only when the request is outside every listed subagent's configured responsibility.",
1441
+ compactRoutingPolicy ? "Agent routing policy:" : "",
1442
+ compactRoutingPolicy,
1599
1443
  "Return only JSON with this shape:",
1600
1444
  "{\"subagent_type\":\"<listed subagent name>\"}",
1601
1445
  "If no listed subagent can handle the request, return only:",
@@ -1605,12 +1449,12 @@ export class AgentRuntimeAdapter {
1605
1449
  "User request:",
1606
1450
  requestText,
1607
1451
  ].filter(Boolean).join("\n\n");
1608
- if (!selection) {
1609
- const model = await this.resolveModel(primaryModel);
1610
- if (typeof model.invoke !== "function") {
1452
+ let model = null;
1453
+ const invokeRouter = async (activePrompt, operationName) => {
1454
+ if (!model || typeof model.invoke !== "function") {
1611
1455
  return null;
1612
1456
  }
1613
- const invokeRouter = async (activePrompt, operationName) => this.invokeWithProviderRetry(binding, () => this.withTimeout(() => model.invoke(activePrompt, resolveLangChainInvocationConfig(binding, {
1457
+ return this.invokeWithProviderRetry(binding, () => this.withTimeout(() => model.invoke(activePrompt, resolveLangChainInvocationConfig(binding, {
1614
1458
  sessionId,
1615
1459
  requestId,
1616
1460
  context: options.context,
@@ -1620,6 +1464,12 @@ export class AgentRuntimeAdapter {
1620
1464
  requestId,
1621
1465
  }),
1622
1466
  })), resolveBindingTimeout(binding), operationName, "invoke"));
1467
+ };
1468
+ if (!selection) {
1469
+ model = await this.resolveModel(primaryModel);
1470
+ if (typeof model.invoke !== "function") {
1471
+ return null;
1472
+ }
1623
1473
  const routerPrompts = [
1624
1474
  prompt,
1625
1475
  [
@@ -1654,10 +1504,20 @@ export class AgentRuntimeAdapter {
1654
1504
  ? routerPrompts[index]
1655
1505
  : [routerPrompts[index], "Previous output:", previousRawText].join("\n\n");
1656
1506
  const raw = await invokeRouter(activePrompt, index === 0 ? "delegation router invoke" : `delegation router retry invoke ${index}`);
1507
+ if (raw === null) {
1508
+ break;
1509
+ }
1657
1510
  previousRawText = readModelText(raw);
1658
- selection = parseCompactRouterSelection(previousRawText, subagentNames);
1511
+ const parsedSelection = parseCompactRouterSelection(previousRawText, subagentNames);
1512
+ selection = parsedSelection?.refusedReason && index < routerPrompts.length - 1
1513
+ ? null
1514
+ : parsedSelection;
1659
1515
  }
1660
1516
  }
1517
+ const explicitOwner = resolveSingleExplicitOwnerMention(requestText, subagentNames);
1518
+ if ((selection?.refusedReason || !selection) && explicitOwner) {
1519
+ selection = { subagentType: explicitOwner };
1520
+ }
1661
1521
  if (selection?.delegations && selection.delegations.length > 0) {
1662
1522
  selection = { subagentType: selection.delegations[0].subagentType };
1663
1523
  }
@@ -1676,15 +1536,6 @@ export class AgentRuntimeAdapter {
1676
1536
  };
1677
1537
  }
1678
1538
  let subagentType = selection?.subagentType ?? "";
1679
- if (!subagentNames.has(subagentType)) {
1680
- const fallbackSubagent = subagentNames.values().next().value;
1681
- if (typeof fallbackSubagent === "string" && fallbackSubagent) {
1682
- subagentType = fallbackSubagent;
1683
- }
1684
- else {
1685
- return null;
1686
- }
1687
- }
1688
1539
  if (!subagentNames.has(subagentType)) {
1689
1540
  return null;
1690
1541
  }
@@ -1884,7 +1735,7 @@ export class AgentRuntimeAdapter {
1884
1735
  return {
1885
1736
  status: state,
1886
1737
  routing: [
1887
- `1) 路由判断: 已选择 sub-agent = ${delegatedSubagentType}(基于请求语义)。`,
1738
+ `1) 路由判断: 已选择 sub-agent = ${delegatedSubagentType}。`,
1888
1739
  ],
1889
1740
  plan: [
1890
1741
  "1) 选择具备匹配能力的子代理并创建委托任务。",
@@ -1934,32 +1785,23 @@ export class AgentRuntimeAdapter {
1934
1785
  }
1935
1786
  const subagents = getBindingSubagents(binding);
1936
1787
  const subagentNames = new Set(subagents.map((subagent) => subagent.name));
1937
- const semanticSubagents = subagents.map((subagent) => {
1938
- const resolvedBinding = this.options.bindingResolver?.(subagent.name);
1939
- const toolNames = resolvedBinding
1940
- ? getBindingPrimaryTools(resolvedBinding).map((tool) => tool.name).filter(Boolean).join(" ")
1941
- : "";
1942
- return {
1943
- name: subagent.name,
1944
- description: [
1945
- subagent.description ?? "",
1946
- toolNames,
1947
- resolvedBinding ? getBindingSystemPrompt(resolvedBinding) : "",
1948
- ].filter(Boolean).join("\n"),
1949
- };
1950
- });
1951
1788
  let selection = null;
1952
1789
  const subagentCatalog = subagents
1953
1790
  .map((subagent) => `- ${subagent.name}: ${subagent.description}`)
1954
1791
  .join("\n");
1955
1792
  const routingPolicy = getBindingSystemPrompt(binding);
1793
+ const compactRoutingPolicy = buildCompactRouterPolicy(routingPolicy, subagentNames);
1956
1794
  const prompt = [
1957
1795
  primaryModel.init?.think === false ? "/no_think" : "",
1958
1796
  "You are planning delegation for a delegation-only agent.",
1959
1797
  "Choose exactly one listed subagent when one specialist can responsibly handle the request.",
1960
1798
  "If the request naturally requires multiple specialist-owned steps, return an ordered delegation plan instead of one subagent.",
1961
- routingPolicy ? "Agent routing policy:" : "",
1962
- routingPolicy ?? "",
1799
+ "Use the agent routing policy as authoritative routing configuration when it maps request meaning to listed subagents.",
1800
+ "A broad request can still be handleable when multiple listed subagents each own part of it; return a delegation plan in that case.",
1801
+ "Do not refuse while any listed subagent can own the request or a subtask after applying the routing policy.",
1802
+ "Refuse only when the request is outside every listed subagent's configured responsibility.",
1803
+ compactRoutingPolicy ? "Agent routing policy:" : "",
1804
+ compactRoutingPolicy,
1963
1805
  "For one specialist, return only JSON with this shape:",
1964
1806
  "{\"subagent_type\":\"<listed subagent name>\"}",
1965
1807
  "For multiple specialist steps, return only JSON with this shape:",
@@ -1971,12 +1813,12 @@ export class AgentRuntimeAdapter {
1971
1813
  "User request:",
1972
1814
  requestText,
1973
1815
  ].filter(Boolean).join("\n\n");
1974
- if (!selection) {
1975
- const model = await this.resolveModel(primaryModel);
1976
- if (typeof model.invoke !== "function") {
1816
+ let model = null;
1817
+ const invokeRouter = async (activePrompt, operationName) => {
1818
+ if (!model || typeof model.invoke !== "function") {
1977
1819
  return null;
1978
1820
  }
1979
- const invokeRouter = async (activePrompt, operationName) => this.invokeWithProviderRetry(binding, () => this.withTimeout(() => model.invoke(activePrompt, resolveLangChainInvocationConfig(binding, {
1821
+ return this.invokeWithProviderRetry(binding, () => this.withTimeout(() => model.invoke(activePrompt, resolveLangChainInvocationConfig(binding, {
1980
1822
  sessionId,
1981
1823
  requestId,
1982
1824
  context: options.context,
@@ -1986,6 +1828,12 @@ export class AgentRuntimeAdapter {
1986
1828
  requestId,
1987
1829
  }),
1988
1830
  })), resolveBindingTimeout(binding), operationName, "invoke"));
1831
+ };
1832
+ if (!selection) {
1833
+ model = await this.resolveModel(primaryModel);
1834
+ if (typeof model.invoke !== "function") {
1835
+ return null;
1836
+ }
1989
1837
  const routerPrompts = [
1990
1838
  prompt,
1991
1839
  [
@@ -2023,8 +1871,14 @@ export class AgentRuntimeAdapter {
2023
1871
  ? routerPrompts[index]
2024
1872
  : [routerPrompts[index], "Previous output:", previousRawText].join("\n\n");
2025
1873
  const raw = await invokeRouter(activePrompt, index === 0 ? "delegation router invoke" : `delegation router retry invoke ${index}`);
1874
+ if (raw === null) {
1875
+ break;
1876
+ }
2026
1877
  previousRawText = readModelText(raw);
2027
- selection = parseCompactRouterSelection(previousRawText, subagentNames);
1878
+ const parsedSelection = parseCompactRouterSelection(previousRawText, subagentNames);
1879
+ selection = parsedSelection?.refusedReason && index < routerPrompts.length - 1
1880
+ ? null
1881
+ : parsedSelection;
2028
1882
  }
2029
1883
  }
2030
1884
  const explicitTasks = extractExplicitSubagentTasks(requestText, subagentNames);
@@ -2032,10 +1886,47 @@ export class AgentRuntimeAdapter {
2032
1886
  if (selection?.refusedReason && explicitOwner) {
2033
1887
  selection = { subagentType: explicitOwner };
2034
1888
  }
2035
- const inferredDelegationPlan = inferCompactRouterDelegationPlanFromRequest(requestText, semanticSubagents, routingPolicy);
2036
- if (inferredDelegationPlan.length > 1
2037
- && (!selection?.delegations || selection.delegations.length < inferredDelegationPlan.length)) {
2038
- selection = { delegations: inferredDelegationPlan };
1889
+ const numberedRequestSteps = requestText
1890
+ .split(/\r?\n/u)
1891
+ .map((item) => item.trim())
1892
+ .filter((item) => /^\d+\s*[.)、.]\s*/u.test(item));
1893
+ if ((!selection || selection.refusedReason) && !explicitOwner && explicitTasks.length === 0 && model) {
1894
+ const routeClauses = numberedRequestSteps.length > 1 ? numberedRequestSteps : [requestText];
1895
+ const routedDelegations = [];
1896
+ for (const [index, clause] of routeClauses.entries()) {
1897
+ const clausePrompt = [
1898
+ primaryModel.init?.think === false ? "/no_think" : "",
1899
+ "You are a strict JSON router for one bounded task.",
1900
+ "Choose exactly one owner from this list:",
1901
+ Array.from(subagentNames).join(", "),
1902
+ "Return only JSON with this shape:",
1903
+ "{\"subagent_type\":\"<one listed subagent name>\"}",
1904
+ compactRoutingPolicy ? "Agent routing policy:" : "",
1905
+ compactRoutingPolicy,
1906
+ "Available subagents:",
1907
+ subagentCatalog,
1908
+ "Task:",
1909
+ clause,
1910
+ ].filter(Boolean).join("\n\n");
1911
+ const raw = await invokeRouter(clausePrompt, routeClauses.length > 1
1912
+ ? `delegation router clause invoke ${index + 1}`
1913
+ : "delegation router single-clause invoke");
1914
+ const routed = raw === null ? null : parseCompactRouterSelection(readModelText(raw), subagentNames);
1915
+ if (!routed?.subagentType || routed.refusedReason) {
1916
+ routedDelegations.length = 0;
1917
+ break;
1918
+ }
1919
+ if (routedDelegations.some((item) => item.subagentType === routed.subagentType)) {
1920
+ continue;
1921
+ }
1922
+ routedDelegations.push({ subagentType: routed.subagentType, description: clause });
1923
+ }
1924
+ if (routedDelegations.length > 1) {
1925
+ selection = { delegations: routedDelegations };
1926
+ }
1927
+ else if (routedDelegations.length === 1) {
1928
+ selection = { subagentType: routedDelegations[0].subagentType };
1929
+ }
2039
1930
  }
2040
1931
  if (!selection) {
2041
1932
  if (explicitTasks.length > 1) {
@@ -2049,25 +1940,10 @@ export class AgentRuntimeAdapter {
2049
1940
  else if (explicitTasks.length === 1) {
2050
1941
  selection = { subagentType: explicitTasks[0].subagentType };
2051
1942
  }
2052
- else {
2053
- const inferred = inferCompactRouterSelectionFromRequest(requestText, semanticSubagents);
2054
- if (inferred) {
2055
- selection = { subagentType: inferred };
2056
- }
1943
+ else if (explicitOwner) {
1944
+ selection = { subagentType: explicitOwner };
2057
1945
  }
2058
1946
  }
2059
- const numberedRequestSteps = requestText
2060
- .split(/\r?\n/u)
2061
- .map((item) => item.trim())
2062
- .filter((item) => /^\d+\s*[.)、.]\s*/u.test(item));
2063
- if (selection?.subagentType && numberedRequestSteps.length >= 3 && subagents.length > 1) {
2064
- selection = {
2065
- delegations: subagents.map((subagent, index) => ({
2066
- subagentType: subagent.name,
2067
- description: numberedRequestSteps[index] ?? requestText,
2068
- })),
2069
- };
2070
- }
2071
1947
  if (selection?.delegations?.length === 1) {
2072
1948
  const onlyDelegation = selection.delegations[0];
2073
1949
  selection = { subagentType: onlyDelegation.subagentType };
@@ -2077,16 +1953,6 @@ export class AgentRuntimeAdapter {
2077
1953
  selection = { subagentType: explicitOwner };
2078
1954
  }
2079
1955
  }
2080
- if (selection?.delegations
2081
- && selection.delegations.length > 1
2082
- && inferredDelegationPlan.length <= 1
2083
- && numberedRequestSteps.length < 3) {
2084
- const inferredSingleOwner = inferCompactRouterSelectionFromRequest(requestText, subagents)
2085
- ?? inferCompactRouterSelectionFromRequest(requestText, semanticSubagents);
2086
- if (inferredSingleOwner) {
2087
- selection = { subagentType: inferredSingleOwner };
2088
- }
2089
- }
2090
1956
  if (selection?.delegations && selection.delegations.length > 1) {
2091
1957
  const plannedDelegations = selection.delegations;
2092
1958
  const plannedNames = new Set(plannedDelegations.map((item) => item.subagentType));
@@ -2323,13 +2189,7 @@ export class AgentRuntimeAdapter {
2323
2189
  }
2324
2190
  let subagentType = selection?.subagentType ?? "";
2325
2191
  if (!subagentNames.has(subagentType)) {
2326
- const fallbackSubagent = subagentNames.values().next().value;
2327
- if (typeof fallbackSubagent === "string" && fallbackSubagent) {
2328
- subagentType = fallbackSubagent;
2329
- }
2330
- else {
2331
- return null;
2332
- }
2192
+ return null;
2333
2193
  }
2334
2194
  const selectedBinding = this.options.bindingResolver(subagentType);
2335
2195
  if (!selectedBinding) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.423",
3
+ "version": "0.0.425",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",