@agent-native/core 0.118.1 → 0.119.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +11 -0
  3. package/corpus/core/docs/content/a2a-protocol.mdx +15 -0
  4. package/corpus/core/docs/content/template-chat.mdx +1 -1
  5. package/corpus/core/docs/content/template-plan.mdx +3 -3
  6. package/corpus/core/package.json +1 -1
  7. package/corpus/core/src/a2a/activity.ts +371 -0
  8. package/corpus/core/src/a2a/client.ts +3 -1
  9. package/corpus/core/src/a2a/index.ts +21 -0
  10. package/corpus/core/src/a2a/types.ts +8 -0
  11. package/corpus/core/src/agent/model-config.ts +19 -23
  12. package/corpus/core/src/agent/production-agent.ts +167 -100
  13. package/corpus/core/src/agent/types.ts +16 -1
  14. package/corpus/core/src/client/chat/tool-call-display.tsx +190 -35
  15. package/corpus/core/src/client/chat-model-groups.ts +67 -27
  16. package/corpus/core/src/client/sharing/ShareButton.tsx +1 -27
  17. package/corpus/core/src/client/sharing/ShareDialog.tsx +0 -16
  18. package/corpus/core/src/client/sharing/useShareButtonController.ts +0 -7
  19. package/corpus/core/src/client/sharing/useShareDialogController.ts +0 -2
  20. package/corpus/core/src/client/sse-event-processor.ts +85 -1
  21. package/corpus/core/src/localization/default-messages.ts +6 -0
  22. package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +1 -18
  23. package/corpus/core/src/scripts/call-agent.ts +49 -6
  24. package/corpus/core/src/server/agent-chat-plugin.ts +50 -10
  25. package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
  26. package/corpus/templates/clips/changelog/2026-07-23-fixed-recordings-failing-to-save-with-a-cancelled-or-cleanup.md +6 -0
  27. package/corpus/templates/clips/server/plugins/db.ts +9 -1
  28. package/dist/a2a/activity.d.ts +50 -0
  29. package/dist/a2a/activity.d.ts.map +1 -0
  30. package/dist/a2a/activity.js +236 -0
  31. package/dist/a2a/activity.js.map +1 -0
  32. package/dist/a2a/client.d.ts +2 -0
  33. package/dist/a2a/client.d.ts.map +1 -1
  34. package/dist/a2a/client.js +1 -1
  35. package/dist/a2a/client.js.map +1 -1
  36. package/dist/a2a/index.d.ts +2 -1
  37. package/dist/a2a/index.d.ts.map +1 -1
  38. package/dist/a2a/index.js +1 -0
  39. package/dist/a2a/index.js.map +1 -1
  40. package/dist/a2a/types.d.ts +1 -0
  41. package/dist/a2a/types.d.ts.map +1 -1
  42. package/dist/a2a/types.js.map +1 -1
  43. package/dist/agent/engine/anthropic-engine.d.ts +1 -1
  44. package/dist/agent/engine/builder-engine.d.ts +2 -2
  45. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  46. package/dist/agent/model-config.d.ts +16 -16
  47. package/dist/agent/model-config.d.ts.map +1 -1
  48. package/dist/agent/model-config.js +19 -23
  49. package/dist/agent/model-config.js.map +1 -1
  50. package/dist/agent/production-agent.d.ts +26 -0
  51. package/dist/agent/production-agent.d.ts.map +1 -1
  52. package/dist/agent/production-agent.js +137 -80
  53. package/dist/agent/production-agent.js.map +1 -1
  54. package/dist/agent/types.d.ts +10 -0
  55. package/dist/agent/types.d.ts.map +1 -1
  56. package/dist/agent/types.js.map +1 -1
  57. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  58. package/dist/client/chat/tool-call-display.js +63 -14
  59. package/dist/client/chat/tool-call-display.js.map +1 -1
  60. package/dist/client/chat-model-groups.d.ts.map +1 -1
  61. package/dist/client/chat-model-groups.js +44 -21
  62. package/dist/client/chat-model-groups.js.map +1 -1
  63. package/dist/client/sharing/ShareButton.d.ts +1 -1
  64. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  65. package/dist/client/sharing/ShareButton.js +3 -4
  66. package/dist/client/sharing/ShareButton.js.map +1 -1
  67. package/dist/client/sharing/ShareDialog.d.ts.map +1 -1
  68. package/dist/client/sharing/ShareDialog.js +1 -2
  69. package/dist/client/sharing/ShareDialog.js.map +1 -1
  70. package/dist/client/sharing/useShareButtonController.d.ts +0 -1
  71. package/dist/client/sharing/useShareButtonController.d.ts.map +1 -1
  72. package/dist/client/sharing/useShareButtonController.js +0 -6
  73. package/dist/client/sharing/useShareButtonController.js.map +1 -1
  74. package/dist/client/sharing/useShareDialogController.d.ts +0 -1
  75. package/dist/client/sharing/useShareDialogController.d.ts.map +1 -1
  76. package/dist/client/sharing/useShareDialogController.js +0 -1
  77. package/dist/client/sharing/useShareDialogController.js.map +1 -1
  78. package/dist/client/sse-event-processor.d.ts +12 -0
  79. package/dist/client/sse-event-processor.d.ts.map +1 -1
  80. package/dist/client/sse-event-processor.js +63 -1
  81. package/dist/client/sse-event-processor.js.map +1 -1
  82. package/dist/collab/awareness.d.ts +2 -2
  83. package/dist/collab/awareness.d.ts.map +1 -1
  84. package/dist/collab/routes.d.ts +1 -1
  85. package/dist/collab/struct-routes.d.ts +1 -1
  86. package/dist/localization/default-messages.d.ts +6 -0
  87. package/dist/localization/default-messages.d.ts.map +1 -1
  88. package/dist/localization/default-messages.js +6 -0
  89. package/dist/localization/default-messages.js.map +1 -1
  90. package/dist/notifications/routes.d.ts +2 -2
  91. package/dist/observability/routes.d.ts +1 -1
  92. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  93. package/dist/scripts/agent-engines/list-agent-engines.js +1 -18
  94. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  95. package/dist/scripts/call-agent.d.ts.map +1 -1
  96. package/dist/scripts/call-agent.js +49 -6
  97. package/dist/scripts/call-agent.js.map +1 -1
  98. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  99. package/dist/server/agent-chat-plugin.js +38 -10
  100. package/dist/server/agent-chat-plugin.js.map +1 -1
  101. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  102. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
  103. package/docs/content/a2a-protocol.mdx +15 -0
  104. package/docs/content/template-chat.mdx +1 -1
  105. package/docs/content/template-plan.mdx +3 -3
  106. package/package.json +1 -1
  107. package/src/a2a/activity.ts +371 -0
  108. package/src/a2a/client.ts +3 -1
  109. package/src/a2a/index.ts +21 -0
  110. package/src/a2a/types.ts +8 -0
  111. package/src/agent/model-config.ts +19 -23
  112. package/src/agent/production-agent.ts +167 -100
  113. package/src/agent/types.ts +16 -1
  114. package/src/client/chat/tool-call-display.tsx +190 -35
  115. package/src/client/chat-model-groups.ts +67 -27
  116. package/src/client/sharing/ShareButton.tsx +1 -27
  117. package/src/client/sharing/ShareDialog.tsx +0 -16
  118. package/src/client/sharing/useShareButtonController.ts +0 -7
  119. package/src/client/sharing/useShareDialogController.ts +0 -2
  120. package/src/client/sse-event-processor.ts +85 -1
  121. package/src/localization/default-messages.ts +6 -0
  122. package/src/scripts/agent-engines/list-agent-engines.ts +1 -18
  123. package/src/scripts/call-agent.ts +49 -6
  124. package/src/server/agent-chat-plugin.ts +50 -10
  125. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
  126. package/corpus/core/src/observability/hosted-model-experiment.ts +0 -118
  127. package/dist/observability/hosted-model-experiment.d.ts +0 -39
  128. package/dist/observability/hosted-model-experiment.d.ts.map +0 -1
  129. package/dist/observability/hosted-model-experiment.js +0 -90
  130. package/dist/observability/hosted-model-experiment.js.map +0 -1
  131. package/src/observability/hosted-model-experiment.ts +0 -118
@@ -1,3 +1,5 @@
1
+ import { randomUUID } from "node:crypto";
2
+
1
3
  import Ajv, { type ValidateFunction } from "ajv";
2
4
  import {
3
5
  defineEventHandler,
@@ -8,6 +10,8 @@ import {
8
10
  } from "h3";
9
11
  import type { EventHandler as H3EventHandler } from "h3";
10
12
 
13
+ import { parseA2AAgentActivityPart } from "../a2a/activity.js";
14
+ import type { Task } from "../a2a/types.js";
11
15
  import {
12
16
  isAgentActionStopError,
13
17
  type ActionAutomationContext,
@@ -1725,6 +1729,159 @@ export async function resolveSkillReferenceContent(
1725
1729
  }
1726
1730
  }
1727
1731
 
1732
+ export function createConnectedAgentReferenceEventRelay(input: {
1733
+ agent: string;
1734
+ send: (event: AgentChatEvent) => void;
1735
+ agentCallId?: string;
1736
+ now?: () => number;
1737
+ }) {
1738
+ const agentCallId = input.agentCallId ?? randomUUID();
1739
+ const now = input.now ?? Date.now;
1740
+ const startedAt = now();
1741
+ let lastActivitySequence = -1;
1742
+ let hasRichActivity = false;
1743
+
1744
+ const emitResponseText = (text: string) => {
1745
+ if (text) {
1746
+ input.send({
1747
+ type: "agent_call_text",
1748
+ agent: input.agent,
1749
+ text,
1750
+ agentCallId,
1751
+ });
1752
+ }
1753
+ };
1754
+ const observeActivity = (task: Task) => {
1755
+ const parts = task.status?.message?.parts;
1756
+ const snapshot = Array.isArray(parts)
1757
+ ? parts.map(parseA2AAgentActivityPart).find((value) => value !== null)
1758
+ : undefined;
1759
+ if (snapshot) {
1760
+ hasRichActivity = true;
1761
+ }
1762
+ if (snapshot && snapshot.sequence > lastActivitySequence) {
1763
+ lastActivitySequence = snapshot.sequence;
1764
+ input.send({
1765
+ type: "agent_call_activity",
1766
+ agent: input.agent,
1767
+ agentCallId,
1768
+ snapshot,
1769
+ });
1770
+ }
1771
+ };
1772
+ const observePollUpdate = (task: Task) => {
1773
+ observeActivity(task);
1774
+ if (hasRichActivity) return;
1775
+
1776
+ const state = task.status?.state;
1777
+ if (
1778
+ state !== "submitted" &&
1779
+ state !== "working" &&
1780
+ state !== "processing"
1781
+ ) {
1782
+ return;
1783
+ }
1784
+ const currentTime = now();
1785
+ const detail = extractConnectedAgentProgressDetail(task);
1786
+ input.send({
1787
+ type: "agent_call_progress",
1788
+ agent: input.agent,
1789
+ agentCallId,
1790
+ state,
1791
+ elapsedSeconds: Math.max(0, Math.round((currentTime - startedAt) / 1000)),
1792
+ ...(detail ? { detail } : {}),
1793
+ });
1794
+ };
1795
+
1796
+ return {
1797
+ agentCallId,
1798
+ start() {
1799
+ input.send({
1800
+ type: "agent_call",
1801
+ agent: input.agent,
1802
+ status: "start",
1803
+ agentCallId,
1804
+ });
1805
+ },
1806
+ observeActivity,
1807
+ observePollUpdate,
1808
+ emitResponseText,
1809
+ finish(status: "done" | "error") {
1810
+ input.send({
1811
+ type: "agent_call",
1812
+ agent: input.agent,
1813
+ status,
1814
+ agentCallId,
1815
+ durationMs: Math.max(0, now() - startedAt),
1816
+ });
1817
+ },
1818
+ };
1819
+ }
1820
+
1821
+ type ConnectedAgentCall = typeof import("../a2a/client.js").callAgent;
1822
+ type ResolveConnectedAgentCallerAuth =
1823
+ typeof import("../a2a/caller-auth.js").resolveA2ACallerAuth;
1824
+
1825
+ export async function callConnectedAgentReference(input: {
1826
+ agent: string;
1827
+ path: string;
1828
+ message: string;
1829
+ send: (event: AgentChatEvent) => void;
1830
+ callAgent: ConnectedAgentCall;
1831
+ resolveCallerAuth: ResolveConnectedAgentCallerAuth;
1832
+ agentCallId?: string;
1833
+ now?: () => number;
1834
+ }): Promise<string> {
1835
+ const relay = createConnectedAgentReferenceEventRelay({
1836
+ agent: input.agent,
1837
+ send: input.send,
1838
+ agentCallId: input.agentCallId,
1839
+ now: input.now,
1840
+ });
1841
+ relay.start();
1842
+ try {
1843
+ const callerAuth = await input.resolveCallerAuth({
1844
+ includeGoogleToken: true,
1845
+ });
1846
+ const response = await input.callAgent(input.path, input.message, {
1847
+ async: true,
1848
+ apiKey: callerAuth.apiKey,
1849
+ metadata: callerAuth.metadata,
1850
+ userEmail: callerAuth.userEmail,
1851
+ orgDomain: callerAuth.orgDomain,
1852
+ orgSecret: callerAuth.orgSecret,
1853
+ onUpdate: relay.observePollUpdate,
1854
+ });
1855
+ const responseText =
1856
+ userFacingLlmCredentialError(response, { agentName: input.agent }) ??
1857
+ response;
1858
+ relay.emitResponseText(responseText);
1859
+ relay.finish("done");
1860
+ return responseText;
1861
+ } catch (error) {
1862
+ relay.finish("error");
1863
+ throw error;
1864
+ }
1865
+ }
1866
+
1867
+ const MAX_CONNECTED_AGENT_PROGRESS_DETAIL_CHARS = 200;
1868
+ function extractConnectedAgentProgressDetail(task: Task): string | undefined {
1869
+ const parts = task.status?.message?.parts;
1870
+ if (!Array.isArray(parts)) return undefined;
1871
+ const text = parts
1872
+ .filter(
1873
+ (part): part is { type: "text"; text: string } => part.type === "text",
1874
+ )
1875
+ .map((part) => part.text)
1876
+ .join(" ")
1877
+ .replace(/\s+/g, " ")
1878
+ .trim();
1879
+ if (!text) return undefined;
1880
+ return text.length > MAX_CONNECTED_AGENT_PROGRESS_DETAIL_CHARS
1881
+ ? `${text.slice(0, MAX_CONNECTED_AGENT_PROGRESS_DETAIL_CHARS - 1)}…`
1882
+ : text;
1883
+ }
1884
+
1728
1885
  async function enrichMessage(
1729
1886
  message: string,
1730
1887
  references: AgentChatReference[],
@@ -6700,22 +6857,6 @@ export function createProductionAgentHandler(
6700
6857
  modelSelectionSource = "experiment";
6701
6858
  }
6702
6859
  }
6703
-
6704
- if (modelSelectionSource === "default") {
6705
- const { resolveHostedDefaultModelExperiment } =
6706
- await import("../observability/hosted-model-experiment.js");
6707
- const hostedExperiment = resolveHostedDefaultModelExperiment({
6708
- userId: ownerEmail,
6709
- engineName: engine.name,
6710
- isDefaultModelSelection: true,
6711
- supportedModels: engine.supportedModels,
6712
- });
6713
- if (hostedExperiment) {
6714
- effectiveModel = hostedExperiment.model;
6715
- experimentAssignments.push(hostedExperiment.assignment);
6716
- modelSelectionSource = "experiment";
6717
- }
6718
- }
6719
6860
  }
6720
6861
  } catch {
6721
6862
  // Experiments are best-effort. Model resolution must keep working if the
@@ -8015,97 +8156,23 @@ export function createProductionAgentHandler(
8015
8156
 
8016
8157
  // Resolve connected agent @-mentions via A2A calls.
8017
8158
  if (agentRefs.length > 0 && requestMode !== "plan") {
8018
- const [{ A2AClient, callAgent }, { resolveA2ACallerAuth }] =
8019
- await Promise.all([
8020
- import("../a2a/client.js"),
8021
- import("../a2a/caller-auth.js"),
8022
- ]);
8159
+ const [{ callAgent }, { resolveA2ACallerAuth }] = await Promise.all([
8160
+ import("../a2a/client.js"),
8161
+ import("../a2a/caller-auth.js"),
8162
+ ]);
8023
8163
  const results = await Promise.allSettled(
8024
8164
  agentRefs.map(async (ref) => {
8025
- send({
8026
- type: "agent_call",
8027
- agent: ref.name,
8028
- status: "start",
8029
- });
8030
8165
  try {
8031
- const callerAuth = await resolveA2ACallerAuth({
8032
- includeGoogleToken: true,
8033
- });
8034
- const a2aClient = new A2AClient(ref.path, callerAuth.apiKey, {
8035
- ...(callerAuth.apiKeyFallbacks
8036
- ? { fallbackApiKeys: callerAuth.apiKeyFallbacks }
8037
- : {}),
8038
- });
8039
- const a2aMetadata = callerAuth.metadata;
8040
-
8041
- let responseText = "";
8042
- let lastSentLength = 0;
8043
-
8044
- try {
8045
- for await (const task of a2aClient.stream(
8046
- {
8047
- role: "user",
8048
- parts: [
8049
- {
8050
- type: "text",
8051
- text: enrichedMessage + screenContext,
8052
- },
8053
- ],
8054
- },
8055
- Object.keys(a2aMetadata).length > 0
8056
- ? { metadata: a2aMetadata }
8057
- : undefined,
8058
- )) {
8059
- const newText =
8060
- task.status?.message?.parts
8061
- ?.filter(
8062
- (p): p is { type: "text"; text: string } =>
8063
- p.type === "text",
8064
- )
8065
- ?.map((p) => p.text)
8066
- ?.join("") ?? "";
8067
-
8068
- if (newText.length > lastSentLength) {
8069
- send({
8070
- type: "agent_call_text",
8071
- agent: ref.name,
8072
- text: newText.slice(lastSentLength),
8073
- });
8074
- lastSentLength = newText.length;
8075
- }
8076
- responseText = newText;
8077
- }
8078
- } catch {
8079
- if (!responseText) {
8080
- responseText = await callAgent(
8081
- ref.path,
8082
- enrichedMessage + screenContext,
8083
- {
8084
- apiKey: callerAuth.apiKey,
8085
- userEmail: callerAuth.userEmail,
8086
- orgDomain: callerAuth.orgDomain,
8087
- orgSecret: callerAuth.orgSecret,
8088
- },
8089
- );
8090
- }
8091
- }
8092
- responseText =
8093
- userFacingLlmCredentialError(responseText, {
8094
- agentName: ref.name,
8095
- }) ?? responseText;
8096
-
8097
- send({
8098
- type: "agent_call",
8166
+ const responseText = await callConnectedAgentReference({
8099
8167
  agent: ref.name,
8100
- status: "done",
8168
+ path: ref.path,
8169
+ message: enrichedMessage + screenContext,
8170
+ send,
8171
+ callAgent,
8172
+ resolveCallerAuth: resolveA2ACallerAuth,
8101
8173
  });
8102
8174
  return `<agent-response name="${ref.name}" id="${ref.refId}">\n${responseText}\n</agent-response>`;
8103
8175
  } catch (err: any) {
8104
- send({
8105
- type: "agent_call",
8106
- agent: ref.name,
8107
- status: "error",
8108
- });
8109
8176
  const message =
8110
8177
  userFacingLlmCredentialError(err, {
8111
8178
  agentName: ref.name,
@@ -1,3 +1,4 @@
1
+ import type { A2AAgentActivitySnapshot } from "../a2a/activity.js";
1
2
  import type { ActionChatUIConfig } from "../action-ui.js";
2
3
  import type { AgentMcpAppPayload } from "../mcp-client/app-result.js";
3
4
  import type { ReasoningEffort } from "../shared/reasoning-effort.js";
@@ -273,6 +274,8 @@ export type AgentChatEvent =
273
274
  type: "agent_call";
274
275
  agent: string;
275
276
  status: "start" | "done" | "error";
277
+ agentCallId?: string;
278
+ durationMs?: number;
276
279
  }
277
280
  | {
278
281
  /**
@@ -296,8 +299,20 @@ export type AgentChatEvent =
296
299
  elapsedSeconds: number;
297
300
  /** Optional short text surfaced from the remote poll, when present. */
298
301
  detail?: string;
302
+ agentCallId?: string;
303
+ }
304
+ | {
305
+ type: "agent_call_text";
306
+ agent: string;
307
+ text: string;
308
+ agentCallId?: string;
309
+ }
310
+ | {
311
+ type: "agent_call_activity";
312
+ agent: string;
313
+ snapshot: A2AAgentActivitySnapshot;
314
+ agentCallId?: string;
299
315
  }
300
- | { type: "agent_call_text"; agent: string; text: string }
301
316
  | {
302
317
  type: "agent_task";
303
318
  taskId: string;
@@ -25,6 +25,7 @@ import React, {
25
25
  useRef,
26
26
  } from "react";
27
27
 
28
+ import type { A2AAgentActivitySnapshot } from "../../a2a/activity.js";
28
29
  import type { ActionChatUIConfig } from "../../action-ui.js";
29
30
  import type { AgentMcpAppPayload } from "../../mcp-client/app-result.js";
30
31
  import { AgentTaskCard } from "../AgentTaskCard.js";
@@ -35,8 +36,9 @@ import {
35
36
  PopoverTrigger,
36
37
  } from "../components/ui/popover.js";
37
38
  import { ConnectBuilderCard } from "../ConnectBuilderCard.js";
39
+ import { useT } from "../i18n.js";
38
40
  import { McpAppRenderer } from "../mcp-apps/McpAppRenderer.js";
39
- import type { ContentPart } from "../sse-event-processor.js";
41
+ import type { AgentCallProgress, ContentPart } from "../sse-event-processor.js";
40
42
  import {
41
43
  BashCell,
42
44
  EditCell,
@@ -52,10 +54,6 @@ import { ActionChatUiSurface } from "./action-chat-ui-surface.js";
52
54
  import {
53
55
  SmoothMarkdownText,
54
56
  HighlightedCodeBlock,
55
- markdownComponents,
56
- markdownModule,
57
- remarkGfmFn,
58
- markdownUrlTransform,
59
57
  useSmoothStreamingText,
60
58
  } from "./markdown-renderer.js";
61
59
  import { resolveToolRenderer } from "./tool-render-registry.js";
@@ -631,6 +629,7 @@ export function ToolCallDisplay({
631
629
  chatUI={chatUI}
632
630
  isRunning={isRunning}
633
631
  isActiveTail={showActiveTail}
632
+ structuredMeta={structuredMeta}
634
633
  approval={approval}
635
634
  repeatCount={repeatCount}
636
635
  />,
@@ -646,6 +645,7 @@ function ToolCallDisplayGeneric({
646
645
  chatUI,
647
646
  isRunning,
648
647
  isActiveTail,
648
+ structuredMeta,
649
649
  approval,
650
650
  repeatCount,
651
651
  }: {
@@ -657,27 +657,34 @@ function ToolCallDisplayGeneric({
657
657
  chatUI?: ActionChatUIConfig;
658
658
  isRunning: boolean;
659
659
  isActiveTail: boolean;
660
+ structuredMeta?: Record<string, unknown>;
660
661
  approval?: { approvalKey: string; dismissed?: boolean };
661
662
  repeatCount?: number;
662
663
  }) {
663
- const streamRef = useRef<HTMLDivElement>(null);
664
-
665
- const isAgentCall = toolName.startsWith("agent:");
664
+ const isRawCallAgent = toolName === "call-agent";
665
+ const isAgentCall = toolName.startsWith("agent:") || isRawCallAgent;
666
666
  const [expanded, setExpanded] = useState(isAgentCall);
667
667
  const [outputOpen, setOutputOpen] = useState(false);
668
- const agentName = isAgentCall ? toolName.slice(6) : null;
668
+ const agentName = toolName.startsWith("agent:")
669
+ ? toolName.slice(6)
670
+ : typeof args.agent === "string"
671
+ ? args.agent
672
+ : null;
669
673
  const isAgentError = isAgentCall && result === "Error calling agent";
670
- const agentStreamText = isAgentCall ? (argsText ?? "") : "";
674
+ const agentStreamText = isRawCallAgent
675
+ ? (result ?? "")
676
+ : isAgentCall
677
+ ? (argsText ?? "")
678
+ : "";
679
+ const agentActivity = structuredMeta?.agentActivity as
680
+ | A2AAgentActivitySnapshot
681
+ | undefined;
682
+ const agentProgress = structuredMeta?.agentProgress as
683
+ | AgentCallProgress
684
+ | undefined;
671
685
  const hasStreamText = agentStreamText.length > 0;
672
686
  const hasArgs = !isAgentCall && Object.keys(args).length > 0;
673
687
 
674
- // NOTE: All hooks must be above any conditional returns
675
- useEffect(() => {
676
- if (isAgentCall && isRunning && streamRef.current) {
677
- streamRef.current.scrollTop = streamRef.current.scrollHeight;
678
- }
679
- }, [agentStreamText, isAgentCall, isRunning]);
680
-
681
688
  // Render connect-builder as ConnectBuilderCard once the result is available
682
689
  if (toolName === "connect-builder" && result) {
683
690
  try {
@@ -789,6 +796,24 @@ function ToolCallDisplayGeneric({
789
796
  const ToolIcon = resolveToolIcon(toolName);
790
797
  const outputTitle = `Raw ${toolName} tool call output`;
791
798
 
799
+ if (isAgentCall) {
800
+ return (
801
+ <AgentCallCell
802
+ agentName={agentName ?? "agent"}
803
+ activity={agentActivity}
804
+ progress={agentProgress}
805
+ responseText={agentStreamText}
806
+ isRunning={isRunning}
807
+ isError={isAgentError}
808
+ durationMs={
809
+ typeof structuredMeta?.agentDurationMs === "number"
810
+ ? structuredMeta.agentDurationMs
811
+ : agentActivity?.durationMs
812
+ }
813
+ />
814
+ );
815
+ }
816
+
792
817
  return (
793
818
  <div className="group/tool my-0.5 w-full overflow-hidden">
794
819
  {mcpApp && <McpAppRenderer app={mcpApp} className="mb-1.5" />}
@@ -843,24 +868,6 @@ function ToolCallDisplayGeneric({
843
868
  </span>
844
869
  )}
845
870
  </button>
846
- <AnimatedCollapse open={isExpanded && isAgentCall && hasStreamText}>
847
- <div
848
- ref={streamRef}
849
- className="mt-1 rounded-md bg-muted/50 px-3 py-2 text-xs text-muted-foreground break-words max-h-48 overflow-y-auto agent-markdown prose prose-sm prose-invert max-w-none"
850
- >
851
- {markdownModule?.default && remarkGfmFn ? (
852
- <markdownModule.default
853
- remarkPlugins={[remarkGfmFn]}
854
- components={markdownComponents}
855
- urlTransform={markdownUrlTransform}
856
- >
857
- {agentStreamText}
858
- </markdownModule.default>
859
- ) : (
860
- <span style={{ whiteSpace: "pre-wrap" }}>{agentStreamText}</span>
861
- )}
862
- </div>
863
- </AnimatedCollapse>
864
871
  <AnimatedCollapse
865
872
  open={isExpanded && !isAgentCall && (hasArgs || result !== undefined)}
866
873
  >
@@ -896,6 +903,154 @@ function ToolCallDisplayGeneric({
896
903
  );
897
904
  }
898
905
 
906
+ function AgentCallCell({
907
+ agentName,
908
+ activity,
909
+ progress,
910
+ responseText,
911
+ isRunning,
912
+ isError,
913
+ durationMs,
914
+ }: {
915
+ agentName: string;
916
+ activity?: A2AAgentActivitySnapshot;
917
+ progress?: AgentCallProgress;
918
+ responseText: string;
919
+ isRunning: boolean;
920
+ isError: boolean;
921
+ durationMs?: number;
922
+ }) {
923
+ const t = useT();
924
+ const [open, setOpen] = useState(true);
925
+ const finalText = isRunning
926
+ ? activity?.responseText || responseText
927
+ : responseText || activity?.responseText;
928
+ const work = activity?.reasoning?.length || activity?.toolCalls?.length;
929
+ const workItemCount = Math.max(
930
+ activity?.reasoning?.length ?? 0,
931
+ activity?.toolCalls?.length ?? 0,
932
+ );
933
+ const label = isRunning
934
+ ? t("agentPanel.delegatedAgent.asking", { name: agentName })
935
+ : isError
936
+ ? t("agentPanel.delegatedAgent.error", { name: agentName })
937
+ : t("agentPanel.delegatedAgent.asked", { name: agentName });
938
+ const workContent = work ? (
939
+ <div className="space-y-1 ps-5">
940
+ {Array.from({ length: workItemCount }, (_, index) => {
941
+ const reasoningText = activity?.reasoning?.[index];
942
+ const tool = activity?.toolCalls?.[index];
943
+ return (
944
+ <React.Fragment key={`activity-${index}`}>
945
+ {reasoningText && (
946
+ <ReasoningCell
947
+ text={reasoningText}
948
+ isStreaming={
949
+ isRunning &&
950
+ activity.activePhase === "reasoning" &&
951
+ index === activity.reasoning.length - 1
952
+ }
953
+ defaultOpen={index === activity.reasoning.length - 1}
954
+ collapseWhenReplaced={index < activity.toolCalls.length}
955
+ />
956
+ )}
957
+ {tool && (
958
+ <ToolCallDisplay
959
+ toolName={tool.name}
960
+ args={{}}
961
+ argsText=""
962
+ isRunning={tool.status === "running"}
963
+ result={
964
+ tool.status === "failed"
965
+ ? "Failed"
966
+ : tool.status === "completed"
967
+ ? "Done"
968
+ : undefined
969
+ }
970
+ isActiveTail={
971
+ isRunning && index === activity.toolCalls.length - 1
972
+ }
973
+ />
974
+ )}
975
+ </React.Fragment>
976
+ );
977
+ })}
978
+ </div>
979
+ ) : null;
980
+ const progressState = progress?.state.replaceAll(/[-_]+/g, " ");
981
+ const progressText =
982
+ isRunning && !activity && progress && progressState
983
+ ? [
984
+ progressState.charAt(0).toUpperCase() + progressState.slice(1),
985
+ t("agentPanel.delegatedAgent.elapsed", {
986
+ duration: formatWorkedDuration(progress.elapsedSeconds * 1000),
987
+ }),
988
+ progress.detail,
989
+ ]
990
+ .filter(Boolean)
991
+ .join(" · ")
992
+ : null;
993
+ return (
994
+ <div className="group/tool my-0.5 w-full">
995
+ <button
996
+ type="button"
997
+ onClick={() => setOpen((value) => !value)}
998
+ aria-expanded={open}
999
+ className="flex w-full items-center gap-1.5 rounded-md py-0.5 text-left text-[13px] text-muted-foreground transition-colors hover:text-foreground"
1000
+ >
1001
+ {isRunning ? (
1002
+ <IconLoader2 className="size-3.5 animate-spin" />
1003
+ ) : isError ? (
1004
+ <IconCircleX className="size-3.5 text-destructive" />
1005
+ ) : (
1006
+ <IconChevronRight
1007
+ className={cn("size-3.5 transition-transform", open && "rotate-90")}
1008
+ />
1009
+ )}
1010
+ <span
1011
+ className={cn(
1012
+ "min-w-0 truncate font-normal",
1013
+ isRunning && "agent-running-shimmer",
1014
+ )}
1015
+ >
1016
+ {label}
1017
+ </span>
1018
+ </button>
1019
+ <AnimatedCollapse open={open}>
1020
+ <div className="ms-1 border-s border-border/50 ps-2 pt-1">
1021
+ {workContent &&
1022
+ (isRunning ? (
1023
+ workContent
1024
+ ) : (
1025
+ <WorkedForSummary durationMs={durationMs}>
1026
+ {workContent}
1027
+ </WorkedForSummary>
1028
+ ))}
1029
+ {progressText && (
1030
+ <p
1031
+ className="ps-5 pb-1 text-xs text-muted-foreground"
1032
+ data-testid="agent-call-progress"
1033
+ aria-live="polite"
1034
+ >
1035
+ {progressText}
1036
+ </p>
1037
+ )}
1038
+ {finalText && (
1039
+ <div className="ps-5 pb-1">
1040
+ <SmoothMarkdownText
1041
+ text={finalText}
1042
+ streaming={isRunning}
1043
+ resetKey={`agent-response-${agentName}`}
1044
+ statusType={isRunning ? "running" : "complete"}
1045
+ />
1046
+ </div>
1047
+ )}
1048
+ </div>
1049
+ </AnimatedCollapse>
1050
+ </div>
1051
+ );
1052
+ }
1053
+
899
1054
  // ─── ToolCallFallback ──────────────────────────────────────────────────────────
900
1055
 
901
1056
  export function ToolCallFallback({