@perkos/perkos-a2a 0.8.24 → 0.8.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -27341,6 +27341,12 @@ var A2AServer = class {
27341
27341
  import { randomUUID as randomUUID5 } from "crypto";
27342
27342
 
27343
27343
  // src/agentic-actions.ts
27344
+ var FALLBACK_AGENT_ALIASES = [
27345
+ ["Perkos-Claw-Tester", ["perkos-claw-tester", "openclaw", "open claw", "claw"]],
27346
+ ["Perkos-Hermes-Tester", ["perkos-hermes-tester", "hermes"]],
27347
+ ["Morpheus-A2A-Receiver", ["morpheus-a2a-receiver", "morpheus"]],
27348
+ ["Apollo", ["apollo"]]
27349
+ ];
27344
27350
  function includesAny(text, terms) {
27345
27351
  const lower = text.toLowerCase();
27346
27352
  return terms.some((term) => lower.includes(term.toLowerCase()));
@@ -27360,29 +27366,124 @@ function requestedMarker(text, fallback) {
27360
27366
  function stripDelegationNoise(text) {
27361
27367
  return text.replace(/^Delegated by .*?\.\s*/i, "").replace(/^Delegated request:\s*/i, "").replace(/^A2A-DELEGATION[^\n]*\n?/gim, "").replace(/End with DELEGATED_A2A_DONE\.?/gi, "").trim();
27362
27368
  }
27363
- function detectTarget(text, selfName) {
27364
- const routingText = stripDelegationNoise(text);
27365
- const candidates = [
27366
- ["Perkos-Claw-Tester", ["perkos-claw-tester", "openclaw", "open claw", "claw"]],
27367
- ["Perkos-Hermes-Tester", ["perkos-hermes-tester", "hermes"]],
27368
- ["Morpheus-A2A-Receiver", ["morpheus-a2a-receiver", "morpheus"]]
27369
- ];
27370
- for (const [name, terms] of candidates) {
27371
- if (name === selfName) continue;
27372
- if (includesAny(routingText, terms)) return name;
27369
+ function normalizeKey(value) {
27370
+ return value.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, " ").trim().replace(/\s+/g, " ");
27371
+ }
27372
+ function compactKey(value) {
27373
+ return normalizeKey(value).replace(/\s+/g, "");
27374
+ }
27375
+ function addAlias(aliases, alias, canonical) {
27376
+ const normalized = normalizeKey(alias);
27377
+ if (normalized) aliases.set(normalized, canonical);
27378
+ const compact = compactKey(alias);
27379
+ if (compact) aliases.set(compact, canonical);
27380
+ }
27381
+ function aliasesForName(name) {
27382
+ const base = normalizeKey(name);
27383
+ const compact = compactKey(name);
27384
+ const withoutTester = base.replace(/\btester\b/g, "").replace(/\s+/g, " ").trim();
27385
+ const parts = base.split(" ").filter(Boolean);
27386
+ return Array.from(new Set([
27387
+ name,
27388
+ base,
27389
+ compact,
27390
+ withoutTester,
27391
+ parts.at(-1) || "",
27392
+ parts.at(0) || ""
27393
+ ].filter(Boolean)));
27394
+ }
27395
+ async function buildAgentDirectory(server, selfName) {
27396
+ const names = /* @__PURE__ */ new Set();
27397
+ const aliases = /* @__PURE__ */ new Map();
27398
+ names.add(selfName);
27399
+ for (const [name, extraAliases] of FALLBACK_AGENT_ALIASES) {
27400
+ names.add(name);
27401
+ for (const alias of [name, ...extraAliases]) addAlias(aliases, alias, name);
27402
+ }
27403
+ if (server.discoverPeers) {
27404
+ try {
27405
+ const peers = await server.discoverPeers();
27406
+ for (const [name, entry] of Object.entries(peers)) {
27407
+ const canonical = entry.card?.name || name;
27408
+ if (!canonical || canonical === selfName) continue;
27409
+ names.add(canonical);
27410
+ for (const alias of aliasesForName(canonical)) addAlias(aliases, alias, canonical);
27411
+ if (name !== canonical) for (const alias of aliasesForName(name)) addAlias(aliases, alias, canonical);
27412
+ }
27413
+ } catch {
27414
+ }
27415
+ }
27416
+ for (const name of names) for (const alias of aliasesForName(name)) addAlias(aliases, alias, name);
27417
+ return { names: Array.from(names), aliases };
27418
+ }
27419
+ function resolveAgentName(raw, directory, selfName, allowSelf = false) {
27420
+ const cleaned = raw.replace(/^@/, "").trim();
27421
+ if (!cleaned) return void 0;
27422
+ const normalized = normalizeKey(cleaned);
27423
+ const compact = compactKey(cleaned);
27424
+ for (const name of directory.names) {
27425
+ if (!allowSelf && name === selfName) continue;
27426
+ if (normalizeKey(name) === normalized || compactKey(name) === compact) return name;
27427
+ }
27428
+ const exact = directory.aliases.get(normalized) || directory.aliases.get(compact);
27429
+ if (exact && (allowSelf || exact !== selfName)) return exact;
27430
+ const entries = Array.from(directory.aliases.entries()).sort((a, b) => b[0].length - a[0].length);
27431
+ for (const [alias, canonical] of entries) {
27432
+ if (!allowSelf && canonical === selfName) continue;
27433
+ if (alias.length < 4) continue;
27434
+ if (normalized.includes(alias) || compact.includes(alias.replace(/\s+/g, ""))) return canonical;
27373
27435
  }
27374
27436
  return void 0;
27375
27437
  }
27376
- function detectReplyTarget(text, selfName) {
27377
- const routingText = stripDelegationNoise(text);
27378
- if (selfName !== "Morpheus-A2A-Receiver" && includesAny(routingText, ["morpheus-a2a-receiver", "morpheus"])) {
27379
- return "Morpheus-A2A-Receiver";
27438
+ function findMentionedAgents(text, directory, selfName) {
27439
+ const normalized = normalizeKey(stripDelegationNoise(text));
27440
+ const compact = compactKey(stripDelegationNoise(text));
27441
+ const hits = [];
27442
+ const seen = /* @__PURE__ */ new Set();
27443
+ const entries = Array.from(directory.aliases.entries()).sort((a, b) => b[0].length - a[0].length);
27444
+ for (const [alias, canonical] of entries) {
27445
+ if (canonical === selfName || alias.length < 3 || seen.has(canonical)) continue;
27446
+ const idx = normalized.indexOf(alias);
27447
+ const compactIdx = compact.indexOf(alias.replace(/\s+/g, ""));
27448
+ const index = idx >= 0 ? idx : compactIdx;
27449
+ if (index >= 0) {
27450
+ hits.push({ name: canonical, index });
27451
+ seen.add(canonical);
27452
+ }
27380
27453
  }
27381
- if (selfName !== "Perkos-Claw-Tester" && includesAny(routingText, ["perkos-claw-tester", "openclaw", "open claw"])) {
27382
- return "Perkos-Claw-Tester";
27454
+ return hits.sort((a, b) => a.index - b.index).map((hit) => hit.name);
27455
+ }
27456
+ function parseArrowRoute(text, directory, selfName) {
27457
+ const clean = stripDelegationNoise(text);
27458
+ const routeLine = clean.split(/\n/).find((line) => /(?:->|→|=>|➡)/.test(line));
27459
+ if (!routeLine) return [];
27460
+ const route = routeLine.split(/(?:->|→|=>|➡)/).map((part) => resolveAgentName(part, directory, selfName) || (normalizeKey(part).includes(normalizeKey(selfName)) ? selfName : void 0)).filter(Boolean);
27461
+ return Array.from(new Set(route));
27462
+ }
27463
+ function detectTarget(text, selfName, directory) {
27464
+ const route = parseDelegationEnvelope(text).route;
27465
+ if (route.length) return nextRouteTarget(route, selfName);
27466
+ const arrowRoute = parseArrowRoute(text, directory, selfName);
27467
+ if (arrowRoute.length) return nextRouteTarget(arrowRoute, selfName) || arrowRoute.find((name) => name !== selfName);
27468
+ return findMentionedAgents(text, directory, selfName)[0];
27469
+ }
27470
+ function detectReplyTarget(text, selfName, directory) {
27471
+ const envelope = parseDelegationEnvelope(text);
27472
+ if (envelope.notify && envelope.notify !== selfName) return envelope.notify;
27473
+ const clean = stripDelegationNoise(text);
27474
+ const patterns = [
27475
+ /(?:reply|respond|notify|send|return|env[ií]a|enviar|responde|notifica)(?:\s+the\s+result)?\s+(?:to|a)\s+([A-Za-z0-9_. -]{2,80}?)(?:[.;,\n]|$)/i,
27476
+ /(?:notify|notifica|reply|responde)\s+([A-Za-z0-9_. -]{2,80}?)(?:[.;,\n]|$)/i,
27477
+ /(?:final|ultimo|último).*?(?:notify|notifica|responde|reply).*?(?:to|a)?\s+([A-Za-z0-9_. -]{2,80}?)(?:[.;,\n]|$)/i
27478
+ ];
27479
+ for (const pattern of patterns) {
27480
+ const match = clean.match(pattern);
27481
+ const resolved = match?.[1] ? resolveAgentName(match[1], directory, selfName, true) : void 0;
27482
+ if (resolved) return resolved;
27383
27483
  }
27384
- if (selfName !== "Perkos-Hermes-Tester" && includesAny(routingText, ["perkos-hermes-tester", "hermes"])) {
27385
- return "Perkos-Hermes-Tester";
27484
+ if (includesAny(clean, ["responda a", "env\xEDe", "envia", "send", "reply", "notify", "notifica", "morpheus"])) {
27485
+ const mentioned = findMentionedAgents(clean, directory, selfName);
27486
+ return mentioned.at(-1);
27386
27487
  }
27387
27488
  return void 0;
27388
27489
  }
@@ -27447,64 +27548,124 @@ function answerA2ARelay(selfName) {
27447
27548
  "It handles registration, discovery, message routing, heartbeats, and delivery fallback while each agent keeps its own runtime and local permissions."
27448
27549
  ].join("\n");
27449
27550
  }
27551
+ function parseDelegationEnvelope(text) {
27552
+ const line = text.match(/^A2A-DELEGATION[^\n]*/im)?.[0] || "";
27553
+ const routeRaw = line.match(/\broute=([^\s]+)/i)?.[1];
27554
+ const notifyRaw = line.match(/\bnotify=([^\s]+)/i)?.[1];
27555
+ const markerRaw = line.match(/\bmarker=([A-Z0-9_]{6,})/i)?.[1];
27556
+ const depthRaw = line.match(/\bdepth=(\d+)/i)?.[1];
27557
+ return {
27558
+ route: routeRaw ? routeRaw.split(/[|,]/).map((x) => x.trim()).filter(Boolean) : [],
27559
+ notify: notifyRaw && notifyRaw !== "none" ? notifyRaw : void 0,
27560
+ marker: markerRaw,
27561
+ depth: depthRaw ? Number(depthRaw) : 0
27562
+ };
27563
+ }
27450
27564
  function delegationDepth(text) {
27451
- const match = text.match(/A2A-DELEGATION[^\n]*depth=(\d+)/i);
27452
- return match?.[1] ? Number(match[1]) : 0;
27565
+ return parseDelegationEnvelope(text).depth;
27566
+ }
27567
+ function nextRouteTarget(route, selfName) {
27568
+ const selfIndex = route.findIndex((name) => name === selfName);
27569
+ if (selfIndex >= 0) return route.slice(selfIndex + 1).find((name) => name !== selfName);
27570
+ return route.find((name) => name !== selfName);
27453
27571
  }
27454
27572
  function cleanNestedResult(text, finalMarker) {
27455
27573
  const escaped = finalMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
27456
- return text.replace(new RegExp(`\\n?${escaped}\\s*$`, "g"), "").replace(/\n?DELEGATED_A2A_DONE\s*$/g, "").trim();
27574
+ return text.replace(new RegExp(`
27575
+ ?${escaped}s*$`, "g"), "").replace(/\n?DELEGATED_A2A_DONE\s*$/g, "").trim();
27576
+ }
27577
+ function routeForDelegation(selfName, target, text, directory) {
27578
+ const envelopeRoute = parseDelegationEnvelope(text).route;
27579
+ if (envelopeRoute.length) return Array.from(new Set(envelopeRoute));
27580
+ const arrowRoute = parseArrowRoute(text, directory, selfName);
27581
+ if (arrowRoute.length) return Array.from(new Set(arrowRoute.includes(selfName) ? arrowRoute : [selfName, ...arrowRoute]));
27582
+ const mentions = findMentionedAgents(text, directory, selfName).filter((name) => name !== selfName);
27583
+ const route = [selfName, target, ...mentions.filter((name) => name !== target)];
27584
+ return Array.from(new Set(route));
27457
27585
  }
27458
27586
  function buildDelegatedMessage(input) {
27459
- const { originalText, selfName, target, marker } = input;
27587
+ const { originalText, selfName, target, route, marker, notify } = input;
27460
27588
  const depth = delegationDepth(originalText) + 1;
27461
27589
  const clean = stripDelegationNoise(originalText);
27462
- const header = `A2A-DELEGATION origin=${selfName} target=${target} depth=${depth}`;
27463
- if (hasA2ARelayExplainTask(clean)) {
27464
- if (target === "Perkos-Hermes-Tester") {
27465
- return [
27466
- header,
27467
- "Complete this task directly. Briefly explain what an A2A relay is and why agents use it.",
27468
- "Do not delegate this request further unless explicitly required by a new instruction.",
27469
- "End with DELEGATED_A2A_DONE."
27470
- ].join("\n");
27471
- }
27590
+ const header = `A2A-DELEGATION origin=${selfName} target=${target} route=${route.join("|")} notify=${notify || "none"} depth=${depth} marker=${marker}`;
27591
+ if (hasA2ARelayExplainTask(clean) && !nextRouteTarget(route, target)) {
27472
27592
  return [
27473
27593
  header,
27474
- "Use A2A to ask Perkos-Hermes-Tester to briefly explain what an A2A relay is and why agents use it.",
27475
- "Return Hermes' useful result in your response.",
27476
- `End exactly with ${marker}.`
27594
+ "Complete this task directly. Briefly explain what an A2A relay is and why agents use it.",
27595
+ "Do not delegate this request further unless explicitly required by a new instruction.",
27596
+ "End with DELEGATED_A2A_DONE."
27477
27597
  ].join("\n");
27478
27598
  }
27479
27599
  return [
27480
27600
  header,
27481
- "Complete or delegate the following task once. Do not echo this request back to agents already named in the route.",
27601
+ "Complete or delegate the following task once. Follow the A2A-DELEGATION route exactly. Do not echo this request back to agents already named in the route.",
27482
27602
  clean,
27603
+ notify ? `When the route is complete, send or return the final useful result to ${notify}.` : "Return the final useful result to the caller.",
27483
27604
  `End exactly with ${marker}.`
27484
27605
  ].join("\n");
27485
27606
  }
27607
+ async function completeTerminalTask(input) {
27608
+ const { selfName, server, task, text, marker, replyTarget, runtimeResponder } = input;
27609
+ const cleanText = stripDelegationNoise(text);
27610
+ const wantsEthResearch = includesAny(cleanText, ["precio de eth", "eth price", "ethereum price", "precio actual de eth"]);
27611
+ let finalText;
27612
+ if (includesAny(cleanText, ["A2A_COMM_TEST", "communication test", "ping test"])) {
27613
+ finalText = [`${selfName} received and completed the A2A communication test.`, marker].join("\n");
27614
+ } else if (hasA2ARelayExplainTask(cleanText)) {
27615
+ finalText = [answerA2ARelay(selfName), "", marker].join("\n");
27616
+ } else if (wantsEthResearch) {
27617
+ finalText = [await researchEthPrice(), "", marker].join("\n");
27618
+ } else if (runtimeResponder) {
27619
+ const reply = await runtimeResponder(cleanText, marker);
27620
+ finalText = [reply, "", marker].join("\n");
27621
+ } else {
27622
+ finalText = [`${selfName} completed the delegated A2A task.`, cleanText, marker].join("\n");
27623
+ }
27624
+ if (replyTarget && replyTarget !== selfName) {
27625
+ const replyResult = await server.sendTask(replyTarget, [
27626
+ `Final A2A result from ${selfName}:`,
27627
+ "",
27628
+ cleanNestedResult(finalText, marker),
27629
+ "",
27630
+ "A2A_FINAL_NOTIFICATION_OK"
27631
+ ].join("\n"));
27632
+ finalText = [
27633
+ cleanNestedResult(finalText, marker),
27634
+ "",
27635
+ `Forwarded final result to ${replyTarget}.`,
27636
+ extractTaskFinalText(replyResult),
27637
+ "",
27638
+ marker
27639
+ ].join("\n");
27640
+ }
27641
+ completeTaskWithReply(task, finalText);
27642
+ }
27486
27643
  async function tryHandleAgenticTask(input) {
27487
- const { selfName, server, task, text, logger } = input;
27644
+ const { selfName, server, task, text, logger, runtimeResponder } = input;
27645
+ const directory = await buildAgentDirectory(server, selfName);
27646
+ const envelope = parseDelegationEnvelope(text);
27488
27647
  const cleanText = stripDelegationNoise(text);
27489
- const wantsDelegation = includesAny(cleanText, ["usa comunicaci\xF3n a2a", "usar comunicaci\xF3n a2a", "p\xEDdele", "dile a", "delegate", "delegar", "tell", "ask"]);
27490
- const wantsEthResearch = includesAny(text, ["precio de eth", "eth price", "ethereum price", "precio actual de eth"]);
27491
- const wantsReply = includesAny(text, ["responda a", "env\xEDe", "envia", "send", "reply", "morpheus"]);
27492
- if (includesAny(text, ["A2A_COMM_TEST", "communication test", "ping test"])) {
27493
- const marker = requestedMarker(text, "A2A_COMM_OK");
27648
+ const wantsDelegation = includesAny(cleanText, ["usa comunicaci\xF3n a2a", "usar comunicaci\xF3n a2a", "p\xEDdele", "pidele", "dile a", "delegate", "delegar", "tell", "ask", "route", "chain", "handoff", "notify", "notifica"]);
27649
+ const wantsEthResearch = includesAny(cleanText, ["precio de eth", "eth price", "ethereum price", "precio actual de eth"]);
27650
+ const finalMarker = envelope.marker || requestedMarker(text, envelope.route.length ? "DELEGATED_A2A_DONE" : "TRI_HERMES_DONE");
27651
+ if (/^(Final A2A result|Delegated result from|Research result from)\b/i.test(cleanText) && !envelope.route.length) {
27652
+ completeTaskWithReply(task, [`${selfName} received the final A2A notification.`, "", cleanText, "", requestedMarker(text, "A2A_NOTIFICATION_RECEIVED")].join("\n"));
27653
+ return true;
27654
+ }
27655
+ if (includesAny(cleanText, ["A2A_COMM_TEST", "communication test", "ping test"]) && !wantsDelegation && !envelope.route.length) {
27494
27656
  completeTaskWithReply(task, [
27495
27657
  `${selfName} received and completed the A2A communication test.`,
27496
- marker
27658
+ finalMarker
27497
27659
  ].join("\n"));
27498
27660
  return true;
27499
27661
  }
27500
- if (hasA2ARelayExplainTask(cleanText) && (!wantsDelegation || cleanText.toLowerCase().startsWith("complete this task directly"))) {
27501
- const marker = requestedMarker(text, "DELEGATED_A2A_DONE");
27502
- completeTaskWithReply(task, [answerA2ARelay(selfName), "", marker].join("\n"));
27662
+ if (hasA2ARelayExplainTask(cleanText) && !wantsDelegation && !envelope.route.length) {
27663
+ completeTaskWithReply(task, [answerA2ARelay(selfName), "", finalMarker].join("\n"));
27503
27664
  return true;
27504
27665
  }
27505
- if (wantsEthResearch && (!wantsDelegation || text.trim().toLowerCase().startsWith("delegated research request"))) {
27666
+ if (wantsEthResearch && !wantsDelegation && !envelope.route.length) {
27506
27667
  const research = await researchEthPrice();
27507
- const replyTarget2 = wantsReply ? detectReplyTarget(text, selfName) : void 0;
27668
+ const replyTarget2 = detectReplyTarget(text, selfName, directory);
27508
27669
  let replyResultText2 = "";
27509
27670
  if (replyTarget2) {
27510
27671
  const replyResult = await server.sendTask(replyTarget2, [
@@ -27516,21 +27677,18 @@ async function tryHandleAgenticTask(input) {
27516
27677
  ].join("\n"));
27517
27678
  replyResultText2 = extractTaskFinalText(replyResult);
27518
27679
  }
27519
- const marker = requestedMarker(text, "ETH_RESEARCH_DONE");
27520
27680
  completeTaskWithReply(task, [
27521
27681
  research,
27522
27682
  "",
27523
27683
  replyTarget2 ? `Forwarded to ${replyTarget2}.` : void 0,
27524
27684
  replyResultText2 ? `Reply delivery result:
27525
27685
  ${replyResultText2}` : void 0,
27526
- marker
27686
+ finalMarker
27527
27687
  ].filter(Boolean).join("\n"));
27528
27688
  return true;
27529
27689
  }
27530
- if (!wantsDelegation) return false;
27531
- const target = detectTarget(text, selfName);
27532
- if (!target) return false;
27533
- if (delegationDepth(text) >= 4) {
27690
+ if (!wantsDelegation && !envelope.route.length) return false;
27691
+ if (delegationDepth(text) >= Number(process.env.A2A_MAX_DELEGATION_DEPTH || 8)) {
27534
27692
  completeTaskWithReply(task, [
27535
27693
  `A2A delegation stopped by ${selfName}: max delegation depth reached.`,
27536
27694
  "This prevents routing loops across heterogeneous agents.",
@@ -27538,22 +27696,32 @@ ${replyResultText2}` : void 0,
27538
27696
  ].join("\n"));
27539
27697
  return true;
27540
27698
  }
27541
- const replyTarget = wantsReply ? detectReplyTarget(text, selfName) : void 0;
27542
- const finalMarker = requestedMarker(text, "TRI_HERMES_DONE");
27699
+ const target = detectTarget(text, selfName, directory);
27700
+ const replyTarget = detectReplyTarget(text, selfName, directory);
27701
+ if (!target) {
27702
+ await completeTerminalTask({ selfName, server, task, text, marker: finalMarker, replyTarget, runtimeResponder });
27703
+ return true;
27704
+ }
27705
+ const route = routeForDelegation(selfName, target, text, directory);
27706
+ const next = nextRouteTarget(route, selfName) || target;
27707
+ if (!next || next === selfName) {
27708
+ await completeTerminalTask({ selfName, server, task, text, marker: finalMarker, replyTarget, runtimeResponder });
27709
+ return true;
27710
+ }
27543
27711
  const delegatedMessage = wantsEthResearch ? [
27544
- `A2A-DELEGATION origin=${selfName} target=${target} depth=${delegationDepth(text) + 1}`,
27712
+ `A2A-DELEGATION origin=${selfName} target=${next} route=${route.join("|")} notify=${replyTarget || "none"} depth=${delegationDepth(text) + 1} marker=${finalMarker}`,
27545
27713
  "Delegated research request. Research the current ETH price using available live/public APIs.",
27546
27714
  "Return concise findings with source names, timestamp if available, and limitations.",
27547
- replyTarget ? `If possible, send the final result to ${replyTarget} via A2A.` : "",
27715
+ replyTarget ? `If possible, send the final result to ${replyTarget} via A2A.` : "Return the final useful result to the caller.",
27548
27716
  "End with DELEGATED_ETH_RESEARCH_DONE."
27549
- ].filter(Boolean).join("\n") : buildDelegatedMessage({ originalText: text, selfName, target, marker: finalMarker });
27550
- logger.info(`[perkos-a2a] Agentic delegation: ${selfName} -> ${target}${replyTarget ? ` -> ${replyTarget}` : ""}`);
27551
- const delegatedResult = await server.sendTask(target, delegatedMessage);
27717
+ ].filter(Boolean).join("\n") : buildDelegatedMessage({ originalText: text, selfName, target: next, route, marker: finalMarker, notify: replyTarget });
27718
+ logger.info(`[perkos-a2a] Agentic delegation: ${selfName} -> ${next}${replyTarget ? ` -> notify ${replyTarget}` : ""}; route=${route.join(" -> ")}`);
27719
+ const delegatedResult = await server.sendTask(next, delegatedMessage);
27552
27720
  const delegatedText = cleanNestedResult(extractTaskFinalText(delegatedResult), finalMarker);
27553
27721
  let replyResultText = "";
27554
- if (replyTarget && replyTarget !== target) {
27722
+ if (replyTarget && replyTarget !== next && !delegatedText.includes("Forwarded final result to")) {
27555
27723
  const replyMessage = [
27556
- `Delegated result from ${target}, forwarded by ${selfName}:`,
27724
+ `Delegated result from ${next}, forwarded by ${selfName}:`,
27557
27725
  "",
27558
27726
  delegatedText,
27559
27727
  "",
@@ -27564,7 +27732,8 @@ ${replyResultText2}` : void 0,
27564
27732
  }
27565
27733
  completeTaskWithReply(task, [
27566
27734
  `Agentic A2A delegation completed by ${selfName}.`,
27567
- `Delegated target: ${target}`,
27735
+ `Delegated target: ${next}`,
27736
+ `Route: ${route.join(" -> ")}`,
27568
27737
  replyTarget ? `Reply target: ${replyTarget}` : void 0,
27569
27738
  "",
27570
27739
  "Delegated result:",
@@ -27671,7 +27840,8 @@ function register(api) {
27671
27840
  server,
27672
27841
  task,
27673
27842
  text,
27674
- logger
27843
+ logger,
27844
+ runtimeResponder: (prompt, resultMarker) => generateRuntimeReply(pluginConfig, prompt, resultMarker)
27675
27845
  }).catch((err) => {
27676
27846
  const msg = err instanceof Error ? err.message : String(err);
27677
27847
  logger.error(`[perkos-a2a] Agentic task handler failed: ${msg}`);
@@ -27809,7 +27979,7 @@ function register(api) {
27809
27979
  });
27810
27980
  api.registerTool({
27811
27981
  name: "perkos_a2a_send",
27812
- description: "Send a task to another agent in the council via A2A protocol. Use this to delegate work to a peer agent.",
27982
+ description: "Send a task to another agent via PerkOS A2A. Use this to delegate work, ask a peer to research, or route a chain/circular workflow. The message may name additional agents and a final notification target; the bridge preserves the route and prevents loops.",
27813
27983
  parameters: {
27814
27984
  type: "object",
27815
27985
  properties: {
@@ -27819,7 +27989,7 @@ function register(api) {
27819
27989
  },
27820
27990
  message: {
27821
27991
  type: "string",
27822
- description: "The task message to send to the agent"
27992
+ description: "The task message to send. You can include routes like 'Apollo -> OpenClaw -> Hermes -> Morpheus' or natural language such as 'ask Hermes to research X, then notify Morpheus'."
27823
27993
  }
27824
27994
  },
27825
27995
  required: ["target", "message"]