@perkos/perkos-a2a 0.8.23 → 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,60 +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);
27571
+ }
27572
+ function cleanNestedResult(text, finalMarker) {
27573
+ const escaped = finalMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
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));
27453
27585
  }
27454
27586
  function buildDelegatedMessage(input) {
27455
- const { originalText, selfName, target, marker } = input;
27587
+ const { originalText, selfName, target, route, marker, notify } = input;
27456
27588
  const depth = delegationDepth(originalText) + 1;
27457
27589
  const clean = stripDelegationNoise(originalText);
27458
- const header = `A2A-DELEGATION origin=${selfName} target=${target} depth=${depth}`;
27459
- if (hasA2ARelayExplainTask(clean)) {
27460
- if (target === "Perkos-Hermes-Tester") {
27461
- return [
27462
- header,
27463
- "Complete this task directly. Briefly explain what an A2A relay is and why agents use it.",
27464
- "Do not delegate this request further unless explicitly required by a new instruction.",
27465
- "End with DELEGATED_A2A_DONE."
27466
- ].join("\n");
27467
- }
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)) {
27468
27592
  return [
27469
27593
  header,
27470
- "Use A2A to ask Perkos-Hermes-Tester to briefly explain what an A2A relay is and why agents use it.",
27471
- "Return Hermes' useful result in your response.",
27472
- `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."
27473
27597
  ].join("\n");
27474
27598
  }
27475
27599
  return [
27476
27600
  header,
27477
- "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.",
27478
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.",
27479
27604
  `End exactly with ${marker}.`
27480
27605
  ].join("\n");
27481
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
+ }
27482
27643
  async function tryHandleAgenticTask(input) {
27483
- 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);
27484
27647
  const cleanText = stripDelegationNoise(text);
27485
- const wantsDelegation = includesAny(cleanText, ["usa comunicaci\xF3n a2a", "usar comunicaci\xF3n a2a", "p\xEDdele", "dile a", "delegate", "delegar", "tell", "ask"]);
27486
- const wantsEthResearch = includesAny(text, ["precio de eth", "eth price", "ethereum price", "precio actual de eth"]);
27487
- const wantsReply = includesAny(text, ["responda a", "env\xEDe", "envia", "send", "reply", "morpheus"]);
27488
- if (includesAny(text, ["A2A_COMM_TEST", "communication test", "ping test"])) {
27489
- 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) {
27490
27656
  completeTaskWithReply(task, [
27491
27657
  `${selfName} received and completed the A2A communication test.`,
27492
- marker
27658
+ finalMarker
27493
27659
  ].join("\n"));
27494
27660
  return true;
27495
27661
  }
27496
- if (hasA2ARelayExplainTask(cleanText) && (!wantsDelegation || cleanText.toLowerCase().startsWith("complete this task directly"))) {
27497
- const marker = requestedMarker(text, "DELEGATED_A2A_DONE");
27498
- completeTaskWithReply(task, [answerA2ARelay(selfName), "", marker].join("\n"));
27662
+ if (hasA2ARelayExplainTask(cleanText) && !wantsDelegation && !envelope.route.length) {
27663
+ completeTaskWithReply(task, [answerA2ARelay(selfName), "", finalMarker].join("\n"));
27499
27664
  return true;
27500
27665
  }
27501
- if (wantsEthResearch && (!wantsDelegation || text.trim().toLowerCase().startsWith("delegated research request"))) {
27666
+ if (wantsEthResearch && !wantsDelegation && !envelope.route.length) {
27502
27667
  const research = await researchEthPrice();
27503
- const replyTarget2 = wantsReply ? detectReplyTarget(text, selfName) : void 0;
27668
+ const replyTarget2 = detectReplyTarget(text, selfName, directory);
27504
27669
  let replyResultText2 = "";
27505
27670
  if (replyTarget2) {
27506
27671
  const replyResult = await server.sendTask(replyTarget2, [
@@ -27512,21 +27677,18 @@ async function tryHandleAgenticTask(input) {
27512
27677
  ].join("\n"));
27513
27678
  replyResultText2 = extractTaskFinalText(replyResult);
27514
27679
  }
27515
- const marker = requestedMarker(text, "ETH_RESEARCH_DONE");
27516
27680
  completeTaskWithReply(task, [
27517
27681
  research,
27518
27682
  "",
27519
27683
  replyTarget2 ? `Forwarded to ${replyTarget2}.` : void 0,
27520
27684
  replyResultText2 ? `Reply delivery result:
27521
27685
  ${replyResultText2}` : void 0,
27522
- marker
27686
+ finalMarker
27523
27687
  ].filter(Boolean).join("\n"));
27524
27688
  return true;
27525
27689
  }
27526
- if (!wantsDelegation) return false;
27527
- const target = detectTarget(text, selfName);
27528
- if (!target) return false;
27529
- if (delegationDepth(text) >= 4) {
27690
+ if (!wantsDelegation && !envelope.route.length) return false;
27691
+ if (delegationDepth(text) >= Number(process.env.A2A_MAX_DELEGATION_DEPTH || 8)) {
27530
27692
  completeTaskWithReply(task, [
27531
27693
  `A2A delegation stopped by ${selfName}: max delegation depth reached.`,
27532
27694
  "This prevents routing loops across heterogeneous agents.",
@@ -27534,22 +27696,32 @@ ${replyResultText2}` : void 0,
27534
27696
  ].join("\n"));
27535
27697
  return true;
27536
27698
  }
27537
- const replyTarget = wantsReply ? detectReplyTarget(text, selfName) : void 0;
27538
- 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
+ }
27539
27711
  const delegatedMessage = wantsEthResearch ? [
27540
- `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}`,
27541
27713
  "Delegated research request. Research the current ETH price using available live/public APIs.",
27542
27714
  "Return concise findings with source names, timestamp if available, and limitations.",
27543
- 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.",
27544
27716
  "End with DELEGATED_ETH_RESEARCH_DONE."
27545
- ].filter(Boolean).join("\n") : buildDelegatedMessage({ originalText: text, selfName, target, marker: finalMarker });
27546
- logger.info(`[perkos-a2a] Agentic delegation: ${selfName} -> ${target}${replyTarget ? ` -> ${replyTarget}` : ""}`);
27547
- const delegatedResult = await server.sendTask(target, delegatedMessage);
27548
- const delegatedText = extractTaskFinalText(delegatedResult);
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);
27720
+ const delegatedText = cleanNestedResult(extractTaskFinalText(delegatedResult), finalMarker);
27549
27721
  let replyResultText = "";
27550
- if (replyTarget && replyTarget !== target) {
27722
+ if (replyTarget && replyTarget !== next && !delegatedText.includes("Forwarded final result to")) {
27551
27723
  const replyMessage = [
27552
- `Delegated result from ${target}, forwarded by ${selfName}:`,
27724
+ `Delegated result from ${next}, forwarded by ${selfName}:`,
27553
27725
  "",
27554
27726
  delegatedText,
27555
27727
  "",
@@ -27560,7 +27732,8 @@ ${replyResultText2}` : void 0,
27560
27732
  }
27561
27733
  completeTaskWithReply(task, [
27562
27734
  `Agentic A2A delegation completed by ${selfName}.`,
27563
- `Delegated target: ${target}`,
27735
+ `Delegated target: ${next}`,
27736
+ `Route: ${route.join(" -> ")}`,
27564
27737
  replyTarget ? `Reply target: ${replyTarget}` : void 0,
27565
27738
  "",
27566
27739
  "Delegated result:",
@@ -27667,7 +27840,8 @@ function register(api) {
27667
27840
  server,
27668
27841
  task,
27669
27842
  text,
27670
- logger
27843
+ logger,
27844
+ runtimeResponder: (prompt, resultMarker) => generateRuntimeReply(pluginConfig, prompt, resultMarker)
27671
27845
  }).catch((err) => {
27672
27846
  const msg = err instanceof Error ? err.message : String(err);
27673
27847
  logger.error(`[perkos-a2a] Agentic task handler failed: ${msg}`);
@@ -27805,7 +27979,7 @@ function register(api) {
27805
27979
  });
27806
27980
  api.registerTool({
27807
27981
  name: "perkos_a2a_send",
27808
- 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.",
27809
27983
  parameters: {
27810
27984
  type: "object",
27811
27985
  properties: {
@@ -27815,7 +27989,7 @@ function register(api) {
27815
27989
  },
27816
27990
  message: {
27817
27991
  type: "string",
27818
- 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'."
27819
27993
  }
27820
27994
  },
27821
27995
  required: ["target", "message"]