@botbotgo/agent-harness 0.0.420 → 0.0.422

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.
@@ -41,31 +41,43 @@ function hasDelegatedExecutionToolEvidence(result) {
41
41
  && !isPlanToolName(toolResult.toolName)));
42
42
  }
43
43
  function buildDelegatedPlanEvidenceBlocker(agentId) {
44
- return [
45
- "Status: blocked",
46
- "Summary:",
47
- `- Delegated agent ${agentId} ended before producing the required TODO plan evidence.`,
48
- "",
49
- "Blockers:",
50
- "- The delegated run did not expose a valid planning trace, so the framework cannot treat the task as complete.",
51
- "",
52
- "Next Actions:",
53
- "- Retry with the same request or inspect the delegated agent configuration and model/tool-call behavior.",
54
- ].join("\n");
44
+ return JSON.stringify({
45
+ status: "blocked",
46
+ routing: [`delegated agent ${agentId}`],
47
+ plan: ["delegate to specialist", "require visible TODO planning evidence", "return blocker when planning evidence is absent"],
48
+ execution: [`task delegated to ${agentId}`, `delegated agent ${agentId} ended before producing required TODO plan evidence`],
49
+ todoTrace: ["TODO evidence missing"],
50
+ stepResults: ["delegated planning evidence was not observed"],
51
+ summary: [`Delegated agent ${agentId} ended before producing the required TODO plan evidence.`],
52
+ findings: ["The delegated run did not expose a valid planning trace, so the framework cannot treat the task as complete."],
53
+ blockers: ["missing delegated TODO planning evidence"],
54
+ nextActions: ["Retry with the same request or inspect the delegated agent configuration and model/tool-call behavior."],
55
+ report: `routing delegated to ${agentId}; todoTrace missing; stepResults blocked; summary missing planning evidence; findings require retry; blockers missing TODO planning evidence; nextActions inspect delegated model/tool behavior; report task delegated to ${agentId}.`,
56
+ });
55
57
  }
56
- function buildDelegatedExecutionEvidenceBlocker(agentId) {
57
- return [
58
- "Status: blocked",
59
- "Summary:",
60
- `- Delegated agent ${agentId} did not return any non-planning tool evidence after retry.`,
61
- "",
62
- "Blockers:",
63
- "- The TODO board alone is not execution evidence.",
64
- "- The framework cannot mark the delegated task complete without a non-planning tool result or an explicit blocker from that tool path.",
65
- "",
66
- "Next Actions:",
67
- "- Retry the request or inspect the delegated agent's model/tool-call behavior.",
68
- ].join("\n");
58
+ function buildDelegatedExecutionEvidenceBlocker(agentId, expectedToolNames = []) {
59
+ const expectedTools = expectedToolNames.length > 0 ? expectedToolNames.join(", ") : "configured non-planning tools";
60
+ return JSON.stringify({
61
+ status: "blocked",
62
+ routing: [`delegated agent ${agentId}`],
63
+ plan: ["delegate to specialist", "require non-planning tool evidence", "return blocker when evidence is absent"],
64
+ execution: [
65
+ `task delegated to ${agentId}`,
66
+ `expected evidence tools: ${expectedTools}`,
67
+ `delegated agent ${agentId} did not return any non-planning tool evidence after retry`,
68
+ ],
69
+ todoTrace: ["TODO board observed without completed non-planning evidence"],
70
+ stepResults: ["delegated execution evidence was not observed"],
71
+ summary: [`Delegated agent ${agentId} did not return any non-planning tool evidence after retry.`],
72
+ findings: [
73
+ `Expected evidence tools from configuration: ${expectedTools}.`,
74
+ "The TODO board alone is not execution evidence.",
75
+ "The framework cannot mark the delegated task complete without a non-planning tool result or an explicit blocker from that tool path.",
76
+ ],
77
+ blockers: ["missing delegated non-planning tool evidence"],
78
+ nextActions: ["Retry the request or inspect the delegated agent's model/tool-call behavior."],
79
+ report: `routing delegated to ${agentId}; todoTrace observed planning only; stepResults blocked; summary missing non-planning tool evidence; findings expected evidence tools ${expectedTools}; blockers missing execution evidence; nextActions inspect delegated model/tool behavior; report task delegated to ${agentId}.`,
80
+ });
69
81
  }
70
82
  function normalizePlanToolName(toolName) {
71
83
  return typeof toolName === "string" ? toolName.trim().toLowerCase().replace(/[\s-]+/gu, "_") : "";
@@ -398,15 +410,14 @@ function parseCompactRouterSelection(value, subagentNames) {
398
410
  if (subagentNames.has(trimmed)) {
399
411
  return { subagentType: trimmed };
400
412
  }
401
- const lowered = trimmed.toLowerCase();
402
- const relaxedMatch = Array.from(subagentNames).find((name) => {
403
- const lowerName = name.toLowerCase();
404
- return lowerName.length > 0
405
- && (trimmed === name || lowered.includes(lowerName));
406
- });
407
- if (relaxedMatch) {
408
- return { subagentType: relaxedMatch };
409
- }
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
421
  const parsed = parseFirstJsonObject(trimmed);
411
422
  const toolCall = salvageJsonToolCalls(trimmed).at(0);
412
423
  const payload = typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
@@ -415,7 +426,8 @@ function parseCompactRouterSelection(value, subagentNames) {
415
426
  ? { name: toolCall.name, arguments: toolCall.args }
416
427
  : null;
417
428
  if (!payload) {
418
- return null;
429
+ const relaxedMatch = resolveRelaxedMatch();
430
+ return relaxedMatch ? { subagentType: relaxedMatch } : null;
419
431
  }
420
432
  const args = typeof payload.arguments === "object" && payload.arguments !== null && !Array.isArray(payload.arguments)
421
433
  ? payload.arguments
@@ -430,6 +442,48 @@ function parseCompactRouterSelection(value, subagentNames) {
430
442
  if (subagentNames.has(subagentType)) {
431
443
  return { subagentType };
432
444
  }
445
+ const rawDelegations = Array.isArray(payload.delegations)
446
+ ? payload.delegations
447
+ : Array.isArray(args.delegations)
448
+ ? args.delegations
449
+ : undefined;
450
+ if (rawDelegations) {
451
+ const delegations = rawDelegations
452
+ .map((item) => {
453
+ if (typeof item !== "object" || item === null || Array.isArray(item)) {
454
+ return null;
455
+ }
456
+ const typed = item;
457
+ const rawName = typeof typed.subagent_type === "string"
458
+ ? typed.subagent_type.trim()
459
+ : typeof typed.subagent === "string"
460
+ ? typed.subagent.trim()
461
+ : "";
462
+ const resolvedName = Array.from(subagentNames).find((name) => name.toLowerCase() === rawName.toLowerCase());
463
+ if (!resolvedName) {
464
+ return null;
465
+ }
466
+ const description = typeof typed.description === "string" && typed.description.trim()
467
+ ? typed.description.trim()
468
+ : typeof typed.task === "string" && typed.task.trim()
469
+ ? typed.task.trim()
470
+ : typeof typed.instruction === "string" && typed.instruction.trim()
471
+ ? typed.instruction.trim()
472
+ : "";
473
+ return { subagentType: resolvedName, description };
474
+ })
475
+ .filter((item) => item !== null);
476
+ const deduped = [];
477
+ for (const delegation of delegations) {
478
+ if (deduped.some((item) => item.subagentType === delegation.subagentType)) {
479
+ continue;
480
+ }
481
+ deduped.push(delegation);
482
+ }
483
+ if (deduped.length > 0) {
484
+ return { delegations: deduped };
485
+ }
486
+ }
433
487
  const status = typeof payload.status === "string" ? payload.status.trim().toLowerCase() : "";
434
488
  if (status === "refused") {
435
489
  const reason = typeof payload.reason === "string" && payload.reason.trim()
@@ -437,6 +491,10 @@ function parseCompactRouterSelection(value, subagentNames) {
437
491
  : "No configured subagent can handle the request.";
438
492
  return { refusedReason: reason };
439
493
  }
494
+ const relaxedMatch = resolveRelaxedMatch();
495
+ if (relaxedMatch) {
496
+ return { subagentType: relaxedMatch };
497
+ }
440
498
  return null;
441
499
  }
442
500
  function inferCompactRouterSelectionFromRequest(requestText, subagents) {
@@ -468,6 +526,176 @@ function inferCompactRouterSelectionFromRequest(requestText, subagents) {
468
526
  }
469
527
  return ranked[0].name;
470
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
+ function resolveSingleExplicitOwnerMention(requestText, subagentNames) {
628
+ const numberedClauses = requestText
629
+ .split(/\r?\n/u)
630
+ .map((item) => item.trim())
631
+ .filter((item) => /^\d+\s*[.)、.]\s*/u.test(item));
632
+ if (numberedClauses.length > 1) {
633
+ return null;
634
+ }
635
+ const matches = [];
636
+ for (const subagentName of subagentNames) {
637
+ const escapedName = escapeRegExp(subagentName);
638
+ const pattern = new RegExp(`(?:^|[^\\p{L}\\p{N}_-])\`?${escapedName}\`?(?:$|[^\\p{L}\\p{N}_-])`, "iu");
639
+ if (pattern.test(requestText)) {
640
+ matches.push(subagentName);
641
+ }
642
+ }
643
+ return matches.length === 1 ? matches[0] : null;
644
+ }
645
+ function extractExplicitSubagentTasks(requestText, subagentNames) {
646
+ const lines = requestText.split(/\r?\n/u);
647
+ const tasks = [];
648
+ let current = null;
649
+ const marker = new RegExp(`^\\s*(?:[-*]|\\d+[.)]|\\d+\\s*[、.])?\\s*(${Array.from(subagentNames).map(escapeRegExp).join("|")})\\s*[::]\\s*(.*)$`, "iu");
650
+ for (const line of lines) {
651
+ const match = marker.exec(line);
652
+ if (match?.[1]) {
653
+ if (current) {
654
+ tasks.push({ subagentType: current.subagentType, text: current.parts.join("\n").trim() });
655
+ }
656
+ const subagentType = Array.from(subagentNames).find((name) => name.toLowerCase() === match[1].toLowerCase()) ?? match[1];
657
+ current = { subagentType, parts: [match[2] ?? ""] };
658
+ continue;
659
+ }
660
+ if (current && line.trim()) {
661
+ current.parts.push(line);
662
+ }
663
+ }
664
+ if (current) {
665
+ tasks.push({ subagentType: current.subagentType, text: current.parts.join("\n").trim() });
666
+ }
667
+ const deduped = [];
668
+ for (const task of tasks) {
669
+ if (!subagentNames.has(task.subagentType) || deduped.some((item) => item.subagentType === task.subagentType)) {
670
+ continue;
671
+ }
672
+ deduped.push({ subagentType: task.subagentType, text: task.text || requestText });
673
+ }
674
+ return deduped;
675
+ }
676
+ function buildDelegatedOwnedTaskInstruction(input) {
677
+ const relevantPolicyLines = (input.routingPolicy ?? "")
678
+ .split(/\r?\n/u)
679
+ .map((line) => line.trim())
680
+ .filter((line) => line.includes(`\`${input.subagentType}\``)
681
+ || line.includes(` ${input.subagentType} `)
682
+ || line.toLowerCase().includes(`to ${input.subagentType}`))
683
+ .slice(0, 8);
684
+ return [
685
+ `Delegated owner: ${input.subagentType}.`,
686
+ "Execute only this owner's bounded portion of the larger request.",
687
+ "Do not take over other specialist-owned work; return blockers for missing access instead of inventing success.",
688
+ "Owned subtask:",
689
+ input.taskText.trim() || input.originalRequest,
690
+ relevantPolicyLines.length > 0 ? "Relevant parent routing/delegation policy for this owner:" : "",
691
+ ...relevantPolicyLines,
692
+ "If the owner policy or tool contract requires evidence, call write_todos first and then execute the appropriate non-planning evidence tool before any final answer.",
693
+ "Close every TODO as completed or failed before final output.",
694
+ ].filter(Boolean).join("\n");
695
+ }
696
+ function escapeRegExp(value) {
697
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
698
+ }
471
699
  function extractRouterMatchTokens(value) {
472
700
  const tokens = new Set();
473
701
  for (const match of value.matchAll(/[\p{L}\p{N}_-]+/gu)) {
@@ -478,6 +706,67 @@ function extractRouterMatchTokens(value) {
478
706
  }
479
707
  return tokens;
480
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
+ }
481
770
  function isDelegationOnlyDeepAgentBinding(binding) {
482
771
  return isDeepAgentBinding(binding)
483
772
  && getBindingSubagents(binding).length > 0
@@ -549,6 +838,12 @@ const DELEGATED_PLAN_EVIDENCE_RETRY_INSTRUCTION = [
549
838
  "Before any other tool call or final answer, call write_todos with concrete task steps and statuses.",
550
839
  "Then continue the task to completion, update TODO statuses after evidence steps, and close every TODO as completed or failed before the final answer.",
551
840
  ].join("\n");
841
+ function looksLikeRawCommandTranscript(value) {
842
+ const normalized = value.trim();
843
+ return /^(?:stdout|stderr)\s*:/iu.test(normalized)
844
+ || /(?:^|\n)\s*(?:stdout|stderr)\s*:/iu.test(normalized)
845
+ || /(?:^|\n)\s*exitCode\s*:\s*-?\d+\s*$/iu.test(normalized);
846
+ }
552
847
  function resolveDelegatedResultOutput(result) {
553
848
  const executedToolResults = Array.isArray(result.metadata?.executedToolResults)
554
849
  ? result.metadata.executedToolResults
@@ -927,6 +1222,9 @@ export class AgentRuntimeAdapter {
927
1222
  });
928
1223
  }
929
1224
  catch (error) {
1225
+ if (error instanceof DelegatedExecutionNoToolEvidenceError) {
1226
+ throw new Error(buildDelegatedExecutionEvidenceBlocker(targetBinding.agent.id, getBindingPrimaryTools(targetBinding).map((tool) => tool.name)));
1227
+ }
930
1228
  const message = error instanceof Error && error.message.trim().length > 0
931
1229
  ? error.message.trim()
932
1230
  : "delegated execution failed";
@@ -1360,6 +1658,9 @@ export class AgentRuntimeAdapter {
1360
1658
  selection = parseCompactRouterSelection(previousRawText, subagentNames);
1361
1659
  }
1362
1660
  }
1661
+ if (selection?.delegations && selection.delegations.length > 0) {
1662
+ selection = { subagentType: selection.delegations[0].subagentType };
1663
+ }
1363
1664
  if (selection?.refusedReason) {
1364
1665
  return {
1365
1666
  toolOutput: selection.refusedReason,
@@ -1497,7 +1798,7 @@ export class AgentRuntimeAdapter {
1497
1798
  };
1498
1799
  }
1499
1800
  if (targetRequiresExecutionToolEvidence && !hasDelegatedExecutionToolEvidence(delegatedResult)) {
1500
- const output = buildDelegatedExecutionEvidenceBlocker(selectedBinding.agent.id);
1801
+ const output = buildDelegatedExecutionEvidenceBlocker(selectedBinding.agent.id, getBindingPrimaryTools(selectedBinding).map((tool) => tool.name));
1501
1802
  return {
1502
1803
  toolOutput: output,
1503
1804
  delegatedSubagentType: subagentType,
@@ -1608,6 +1909,9 @@ export class AgentRuntimeAdapter {
1608
1909
  }
1609
1910
  formatCompactDelegationReportForDisplay(report) {
1610
1911
  const reportText = typeof report.report === "string" ? report.report.trim() : "";
1912
+ if (reportText && looksLikeRawCommandTranscript(reportText)) {
1913
+ return JSON.stringify(report);
1914
+ }
1611
1915
  if (reportText) {
1612
1916
  return reportText;
1613
1917
  }
@@ -1630,20 +1934,36 @@ export class AgentRuntimeAdapter {
1630
1934
  }
1631
1935
  const subagents = getBindingSubagents(binding);
1632
1936
  const subagentNames = new Set(subagents.map((subagent) => subagent.name));
1633
- const inferredSubagent = inferCompactRouterSelectionFromRequest(requestText, subagents);
1634
- let selection = inferredSubagent ? { subagentType: inferredSubagent } : null;
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
+ let selection = null;
1635
1952
  const subagentCatalog = subagents
1636
1953
  .map((subagent) => `- ${subagent.name}: ${subagent.description}`)
1637
1954
  .join("\n");
1638
1955
  const routingPolicy = getBindingSystemPrompt(binding);
1639
1956
  const prompt = [
1640
1957
  primaryModel.init?.think === false ? "/no_think" : "",
1641
- "You are selecting a subagent for a delegation-only agent.",
1642
- "Choose exactly one listed subagent when it can responsibly handle the request.",
1958
+ "You are planning delegation for a delegation-only agent.",
1959
+ "Choose exactly one listed subagent when one specialist can responsibly handle the request.",
1960
+ "If the request naturally requires multiple specialist-owned steps, return an ordered delegation plan instead of one subagent.",
1643
1961
  routingPolicy ? "Agent routing policy:" : "",
1644
1962
  routingPolicy ?? "",
1645
- "Return only JSON with this shape:",
1963
+ "For one specialist, return only JSON with this shape:",
1646
1964
  "{\"subagent_type\":\"<listed subagent name>\"}",
1965
+ "For multiple specialist steps, return only JSON with this shape:",
1966
+ "{\"delegations\":[{\"subagent_type\":\"<listed subagent name>\",\"description\":\"<only that specialist's owned portion>\"}]}",
1647
1967
  "If no listed subagent can handle the request, return only:",
1648
1968
  "{\"status\":\"refused\",\"reason\":\"No configured subagent can handle the request.\"}",
1649
1969
  "Available subagents:",
@@ -1675,20 +1995,23 @@ export class AgentRuntimeAdapter {
1675
1995
  ].join("\n\n"),
1676
1996
  [
1677
1997
  primaryModel.init?.think === false ? "/no_think" : "",
1678
- "Select one subagent from this exact list:",
1998
+ "Select one subagent or an ordered delegation plan from this exact list:",
1679
1999
  Array.from(subagentNames).join(", "),
1680
- "Return JSON only:",
2000
+ "Return JSON only. Single-specialist shape:",
1681
2001
  "{\"subagent_type\":\"<one exact listed name>\"}",
2002
+ "Multi-specialist shape:",
2003
+ "{\"delegations\":[{\"subagent_type\":\"<one exact listed name>\",\"description\":\"<owned subtask>\"}]}",
1682
2004
  "User request:",
1683
2005
  requestText,
1684
2006
  ].filter(Boolean).join("\n\n"),
1685
2007
  [
1686
2008
  primaryModel.init?.think === false ? "/no_think" : "",
1687
- "JSON only. Pick a listed subagent or refuse.",
2009
+ "JSON only. Pick a listed subagent, return delegations, or refuse.",
1688
2010
  "Listed subagents:",
1689
2011
  Array.from(subagentNames).join(", "),
1690
2012
  "Allowed outputs:",
1691
2013
  "{\"subagent_type\":\"<listed name>\"}",
2014
+ "{\"delegations\":[{\"subagent_type\":\"<listed name>\",\"description\":\"<owned subtask>\"}]}",
1692
2015
  "{\"status\":\"refused\",\"reason\":\"No configured subagent can handle the request.\"}",
1693
2016
  "Request:",
1694
2017
  requestText,
@@ -1704,6 +2027,286 @@ export class AgentRuntimeAdapter {
1704
2027
  selection = parseCompactRouterSelection(previousRawText, subagentNames);
1705
2028
  }
1706
2029
  }
2030
+ const explicitTasks = extractExplicitSubagentTasks(requestText, subagentNames);
2031
+ const explicitOwner = resolveSingleExplicitOwnerMention(requestText, subagentNames);
2032
+ if (selection?.refusedReason && explicitOwner) {
2033
+ selection = { subagentType: explicitOwner };
2034
+ }
2035
+ const inferredDelegationPlan = inferCompactRouterDelegationPlanFromRequest(requestText, semanticSubagents, routingPolicy);
2036
+ if (inferredDelegationPlan.length > 1
2037
+ && (!selection?.delegations || selection.delegations.length < inferredDelegationPlan.length)) {
2038
+ selection = { delegations: inferredDelegationPlan };
2039
+ }
2040
+ if (!selection) {
2041
+ if (explicitTasks.length > 1) {
2042
+ selection = {
2043
+ delegations: explicitTasks.map((task) => ({
2044
+ subagentType: task.subagentType,
2045
+ description: task.text,
2046
+ })),
2047
+ };
2048
+ }
2049
+ else if (explicitTasks.length === 1) {
2050
+ selection = { subagentType: explicitTasks[0].subagentType };
2051
+ }
2052
+ else {
2053
+ const inferred = inferCompactRouterSelectionFromRequest(requestText, semanticSubagents);
2054
+ if (inferred) {
2055
+ selection = { subagentType: inferred };
2056
+ }
2057
+ }
2058
+ }
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
+ if (selection?.delegations?.length === 1) {
2072
+ const onlyDelegation = selection.delegations[0];
2073
+ selection = { subagentType: onlyDelegation.subagentType };
2074
+ }
2075
+ if (selection?.delegations && selection.delegations.length > 1) {
2076
+ if (explicitOwner) {
2077
+ selection = { subagentType: explicitOwner };
2078
+ }
2079
+ }
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
+ if (selection?.delegations && selection.delegations.length > 1) {
2091
+ const plannedDelegations = selection.delegations;
2092
+ const plannedNames = new Set(plannedDelegations.map((item) => item.subagentType));
2093
+ const planCoversEverySubagent = plannedDelegations.length === subagents.length
2094
+ && plannedNames.size === subagentNames.size
2095
+ && Array.from(subagentNames).every((name) => plannedNames.has(name));
2096
+ const shouldCollapseOverbroadPlan = planCoversEverySubagent && explicitTasks.length === 0 && numberedRequestSteps.length < 3;
2097
+ const executableDelegations = shouldCollapseOverbroadPlan ? plannedDelegations.slice(0, 1) : plannedDelegations;
2098
+ const aggregateToolResults = [];
2099
+ const childReports = [];
2100
+ yield {
2101
+ kind: "commentary",
2102
+ content: `Planned delegation tree: ${executableDelegations.map((item) => item.subagentType).join(" -> ")}.`,
2103
+ agentId: binding.agent.id,
2104
+ };
2105
+ const runPlannedDelegation = async function* (subagentType, text, requestIdSuffix = "") {
2106
+ const selectedBinding = this.options.bindingResolver?.(subagentType);
2107
+ if (!selectedBinding) {
2108
+ const output = `Configured subagent '${subagentType}' could not be resolved.`;
2109
+ return {
2110
+ sessionId,
2111
+ requestId: `${requestId}:${subagentType}${requestIdSuffix}`,
2112
+ agentId: subagentType,
2113
+ state: "failed",
2114
+ output,
2115
+ finalMessageText: output,
2116
+ metadata: { executedToolResults: [] },
2117
+ };
2118
+ }
2119
+ const executedToolResults = [];
2120
+ let output = "";
2121
+ try {
2122
+ for await (const chunk of this.stream(selectedBinding, text, sessionId, [], {
2123
+ context: options.context,
2124
+ state: options.state,
2125
+ files: options.files,
2126
+ requestId: `${requestId}:${subagentType}${requestIdSuffix}`,
2127
+ memoryContext: options.memoryContext,
2128
+ profiling: options.profiling,
2129
+ })) {
2130
+ if (typeof chunk === "string") {
2131
+ output += chunk;
2132
+ continue;
2133
+ }
2134
+ if (chunk.kind === "content") {
2135
+ output += chunk.content ?? "";
2136
+ continue;
2137
+ }
2138
+ if (chunk.kind === "tool-result") {
2139
+ appendUniqueToolEvidence(executedToolResults, {
2140
+ toolName: chunk.toolName,
2141
+ output: chunk.output,
2142
+ ...(chunk.isError !== undefined ? { isError: chunk.isError } : {}),
2143
+ });
2144
+ }
2145
+ if (chunk.kind === "upstream-event") {
2146
+ const streamedEvidence = readUpstreamToolEvidence(chunk.event);
2147
+ if (streamedEvidence) {
2148
+ appendUniqueToolEvidence(executedToolResults, streamedEvidence);
2149
+ }
2150
+ }
2151
+ yield { ...chunk, agentId: chunk.agentId ?? selectedBinding.agent.id };
2152
+ }
2153
+ }
2154
+ catch (error) {
2155
+ output = error instanceof Error ? error.message : String(error);
2156
+ return {
2157
+ sessionId,
2158
+ requestId: `${requestId}:${subagentType}${requestIdSuffix}`,
2159
+ agentId: selectedBinding.agent.id,
2160
+ state: "failed",
2161
+ output,
2162
+ finalMessageText: output,
2163
+ metadata: { executedToolResults },
2164
+ };
2165
+ }
2166
+ return {
2167
+ sessionId,
2168
+ requestId: `${requestId}:${subagentType}${requestIdSuffix}`,
2169
+ agentId: selectedBinding.agent.id,
2170
+ state: "completed",
2171
+ output: sanitizeVisibleText(output),
2172
+ finalMessageText: sanitizeVisibleText(output),
2173
+ metadata: { executedToolResults },
2174
+ };
2175
+ }.bind(this);
2176
+ for (const [index, planned] of executableDelegations.entries()) {
2177
+ const selectedBinding = this.options.bindingResolver?.(planned.subagentType);
2178
+ const delegatedText = buildDelegatedOwnedTaskInstruction({
2179
+ subagentType: planned.subagentType,
2180
+ taskText: planned.description,
2181
+ originalRequest: requestText,
2182
+ routingPolicy,
2183
+ ownerPolicy: selectedBinding ? getBindingSystemPrompt(selectedBinding) : undefined,
2184
+ });
2185
+ yield {
2186
+ kind: "commentary",
2187
+ content: `Delegating to ${planned.subagentType}.`,
2188
+ agentId: binding.agent.id,
2189
+ };
2190
+ yield {
2191
+ kind: "commentary",
2192
+ content: "Starting delegated execution.",
2193
+ agentId: selectedBinding?.agent.id ?? planned.subagentType,
2194
+ };
2195
+ let delegatedResult = yield* runPlannedDelegation(planned.subagentType, delegatedText);
2196
+ if (selectedBinding?.harnessRuntime.executionContract?.requiresPlan === true && !hasDelegatedPlanEvidence(delegatedResult)) {
2197
+ const previousDelegatedResult = delegatedResult;
2198
+ delegatedResult = mergeDelegatedResultToolEvidence(yield* runPlannedDelegation(planned.subagentType, [delegatedText, DELEGATED_PLAN_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":plan-evidence-retry"), previousDelegatedResult);
2199
+ }
2200
+ const targetRequiresExecutionToolEvidence = selectedBinding ? getBindingPrimaryTools(selectedBinding).length > 0 : false;
2201
+ if (targetRequiresExecutionToolEvidence && !hasDelegatedExecutionToolEvidence(delegatedResult)) {
2202
+ const previousDelegatedResult = delegatedResult;
2203
+ delegatedResult = mergeDelegatedResultToolEvidence(yield* runPlannedDelegation(planned.subagentType, [delegatedText, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":tool-evidence-retry"), previousDelegatedResult);
2204
+ }
2205
+ if (selectedBinding?.harnessRuntime.executionContract?.requiresPlan === true && !hasDelegatedPlanEvidence(delegatedResult)) {
2206
+ const output = buildDelegatedPlanEvidenceBlocker(selectedBinding.agent.id);
2207
+ delegatedResult = {
2208
+ ...delegatedResult,
2209
+ state: "failed",
2210
+ output,
2211
+ finalMessageText: output,
2212
+ };
2213
+ }
2214
+ if (targetRequiresExecutionToolEvidence && !hasDelegatedExecutionToolEvidence(delegatedResult)) {
2215
+ const output = buildDelegatedExecutionEvidenceBlocker(selectedBinding.agent.id, getBindingPrimaryTools(selectedBinding).map((tool) => tool.name));
2216
+ delegatedResult = {
2217
+ ...delegatedResult,
2218
+ state: "failed",
2219
+ output,
2220
+ finalMessageText: output,
2221
+ };
2222
+ }
2223
+ const delegatedToolResults = Array.isArray(delegatedResult.metadata?.executedToolResults)
2224
+ ? delegatedResult.metadata.executedToolResults
2225
+ : [];
2226
+ for (const toolResult of delegatedToolResults) {
2227
+ if (typeof toolResult.toolName !== "string") {
2228
+ continue;
2229
+ }
2230
+ appendUniqueToolEvidence(aggregateToolResults, {
2231
+ toolName: toolResult.toolName,
2232
+ output: toolResult.output,
2233
+ ...(toolResult.isError !== undefined ? { isError: Boolean(toolResult.isError) } : {}),
2234
+ });
2235
+ if (isPlanToolName(toolResult.toolName)) {
2236
+ continue;
2237
+ }
2238
+ yield {
2239
+ kind: "commentary",
2240
+ content: `Running tool ${toolResult.toolName}.`,
2241
+ agentId: selectedBinding?.agent.id ?? planned.subagentType,
2242
+ };
2243
+ yield {
2244
+ kind: "commentary",
2245
+ content: `Tool ${toolResult.toolName} ${toolResult.isError === true ? "failed" : "completed"}.`,
2246
+ agentId: selectedBinding?.agent.id ?? planned.subagentType,
2247
+ };
2248
+ }
2249
+ childReports.push({
2250
+ subagentType: planned.subagentType,
2251
+ result: delegatedResult,
2252
+ output: typeof delegatedResult.output === "string" ? delegatedResult.output : String(delegatedResult.output ?? ""),
2253
+ });
2254
+ if (index < executableDelegations.length - 1) {
2255
+ yield {
2256
+ kind: "commentary",
2257
+ content: "Continuing ordered delegation plan.",
2258
+ agentId: binding.agent.id,
2259
+ };
2260
+ }
2261
+ }
2262
+ const failedChildren = childReports.filter((child) => child.result.state === "failed");
2263
+ const uniqueToolNames = [...new Set(aggregateToolResults.map((toolResult) => toolResult.toolName))];
2264
+ const status = failedChildren.length > 0 ? "failed" : "completed";
2265
+ const report = childReports
2266
+ .map((child) => `## ${child.subagentType}\n${child.output.trim() || "No visible output returned."}`)
2267
+ .join("\n\n");
2268
+ const payload = {
2269
+ status,
2270
+ routing: executableDelegations.map((planned, index) => `${index + 1}) 路由判断: ${planned.subagentType} 负责 ${planned.description.trim() || "该专业域子任务"}。`),
2271
+ plan: executableDelegations.map((planned, index) => `${index + 1}) 委托 ${planned.subagentType}: ${planned.description.trim() || "执行其专业域任务"}。`),
2272
+ execution: childReports.map((child, index) => `${index + 1}) ${child.subagentType} ${child.result.state === "failed" ? "failed" : "completed"}。`),
2273
+ todoTrace: childReports.map((child, index) => `${index + 1}) ${child.subagentType}: ${hasDelegatedPlanEvidence(child.result) ? "TODO evidence observed" : "TODO evidence missing"}。`),
2274
+ stepResults: childReports.map((child, index) => `${index + 1}) ${child.subagentType}: delegated result collected; aggregate tools = ${uniqueToolNames.length > 0 ? uniqueToolNames.join(", ") : "none"}。`),
2275
+ summary: [
2276
+ status === "failed"
2277
+ ? `多 specialist 编排完成,但 ${failedChildren.length} 个委托返回 failed。`
2278
+ : "多 specialist 编排已完成,所有委托均返回 completed。",
2279
+ ],
2280
+ findings: childReports.map((child) => `${child.subagentType}: ${child.output.trim().slice(0, 500) || "No finding text returned."}`),
2281
+ blockers: failedChildren.length > 0
2282
+ ? failedChildren.map((child) => `${child.subagentType}: delegated execution failed.`)
2283
+ : ["none"],
2284
+ nextActions: failedChildren.length > 0
2285
+ ? ["查看 failed specialist 的 blocker 并单独重跑该委托。"]
2286
+ : ["可基于各 specialist 结果继续做最终修复或发布决策。"],
2287
+ report,
2288
+ };
2289
+ const output = JSON.stringify(payload);
2290
+ return {
2291
+ toolOutput: output,
2292
+ delegatedSubagentType: "multiple",
2293
+ delegatedResult: {
2294
+ sessionId,
2295
+ requestId,
2296
+ agentId: binding.agent.id,
2297
+ state: status,
2298
+ output,
2299
+ finalMessageText: output,
2300
+ metadata: {
2301
+ executedToolResults: aggregateToolResults,
2302
+ delegations: childReports.map((child) => ({
2303
+ subagentType: child.subagentType,
2304
+ state: child.result.state,
2305
+ })),
2306
+ },
2307
+ },
2308
+ };
2309
+ }
1707
2310
  if (selection?.refusedReason) {
1708
2311
  return {
1709
2312
  toolOutput: selection.refusedReason,
@@ -1754,7 +2357,6 @@ export class AgentRuntimeAdapter {
1754
2357
  requestId: `${childRequestId}${requestIdSuffix}`,
1755
2358
  memoryContext: options.memoryContext,
1756
2359
  profiling: options.profiling,
1757
- toolRuntimeContext: options.toolRuntimeContext,
1758
2360
  })) {
1759
2361
  if (typeof chunk === "string") {
1760
2362
  output += chunk;
@@ -1802,16 +2404,23 @@ export class AgentRuntimeAdapter {
1802
2404
  metadata: { executedToolResults },
1803
2405
  };
1804
2406
  }.bind(this);
1805
- let delegatedResult = yield* runDelegatedStreamAttempt(requestText);
2407
+ const delegatedText = buildDelegatedOwnedTaskInstruction({
2408
+ subagentType,
2409
+ taskText: requestText,
2410
+ originalRequest: requestText,
2411
+ routingPolicy,
2412
+ ownerPolicy: getBindingSystemPrompt(selectedBinding),
2413
+ });
2414
+ let delegatedResult = yield* runDelegatedStreamAttempt(delegatedText);
1806
2415
  const targetRequiresExecutionToolEvidence = getBindingPrimaryTools(selectedBinding).length > 0;
1807
2416
  if (selectedBinding.harnessRuntime.executionContract?.requiresPlan === true
1808
2417
  && !hasDelegatedPlanEvidence(delegatedResult)) {
1809
2418
  const previousDelegatedResult = delegatedResult;
1810
- delegatedResult = mergeDelegatedResultToolEvidence(yield* runDelegatedStreamAttempt([requestText, DELEGATED_PLAN_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":plan-evidence-retry"), previousDelegatedResult);
2419
+ delegatedResult = mergeDelegatedResultToolEvidence(yield* runDelegatedStreamAttempt([delegatedText, DELEGATED_PLAN_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":plan-evidence-retry"), previousDelegatedResult);
1811
2420
  }
1812
2421
  if (targetRequiresExecutionToolEvidence && !hasDelegatedExecutionToolEvidence(delegatedResult)) {
1813
2422
  const previousDelegatedResult = delegatedResult;
1814
- delegatedResult = mergeDelegatedResultToolEvidence(yield* runDelegatedStreamAttempt([requestText, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":tool-evidence-retry"), previousDelegatedResult);
2423
+ delegatedResult = mergeDelegatedResultToolEvidence(yield* runDelegatedStreamAttempt([delegatedText, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":tool-evidence-retry"), previousDelegatedResult);
1815
2424
  }
1816
2425
  if (selectedBinding.harnessRuntime.executionContract?.requiresPlan === true
1817
2426
  && !hasDelegatedPlanEvidence(delegatedResult)) {
@@ -1824,7 +2433,7 @@ export class AgentRuntimeAdapter {
1824
2433
  };
1825
2434
  }
1826
2435
  if (targetRequiresExecutionToolEvidence && !hasDelegatedExecutionToolEvidence(delegatedResult)) {
1827
- const output = buildDelegatedExecutionEvidenceBlocker(selectedBinding.agent.id);
2436
+ const output = buildDelegatedExecutionEvidenceBlocker(selectedBinding.agent.id, getBindingPrimaryTools(selectedBinding).map((tool) => tool.name));
1828
2437
  delegatedResult = {
1829
2438
  ...delegatedResult,
1830
2439
  state: "failed",