@agent-native/core 0.84.22 → 0.84.24

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 (97) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +14 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/model-config.ts +1 -1
  5. package/corpus/core/src/cli/skills.ts +17 -3
  6. package/corpus/core/src/client/AssistantChat.tsx +45 -12
  7. package/corpus/core/src/client/active-run-state.ts +22 -0
  8. package/corpus/core/src/client/chat/message-components.tsx +2 -1
  9. package/corpus/core/src/client/chat/tool-call-display.tsx +18 -2
  10. package/corpus/core/src/client/sse-event-processor.ts +157 -40
  11. package/corpus/core/src/notifications/index.ts +1 -0
  12. package/corpus/core/src/notifications/registry.ts +13 -1
  13. package/corpus/core/src/templates/default/.agents/skills/frontend-design/SKILL.md +2 -0
  14. package/corpus/core/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +2 -0
  15. package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +2 -0
  16. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +15 -13
  17. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +18 -1
  18. package/corpus/templates/analytics/server/db/schema.ts +1 -1
  19. package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +4 -1
  20. package/corpus/templates/analytics/server/lib/analytics-alerts.ts +76 -7
  21. package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +2 -0
  22. package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +2 -0
  23. package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +2 -0
  24. package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +2 -0
  25. package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +2 -0
  26. package/corpus/templates/clips/.agents/skills/recording/SKILL.md +30 -1
  27. package/corpus/templates/clips/actions/finalize-recording.ts +84 -4
  28. package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +256 -0
  29. package/corpus/templates/clips/actions/reprocess-recording.ts +160 -0
  30. package/corpus/templates/clips/app/components/player/share-dialog.tsx +2 -2
  31. package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
  32. package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
  33. package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
  34. package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
  35. package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
  36. package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
  37. package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
  38. package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
  39. package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
  40. package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
  41. package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
  42. package/corpus/templates/clips/app/routes/embed.$shareId.tsx +31 -3
  43. package/corpus/templates/clips/changelog/2026-07-01-clip-embeds-now-render-as-a-clean-video-only-player-without-.md +6 -0
  44. package/corpus/templates/clips/changelog/2026-07-01-shared-clips-now-start-playing-and-scrub-instantly-instead-o.md +6 -0
  45. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  46. package/corpus/templates/clips/chrome-extension/src/github-preview-content.ts +2 -2
  47. package/corpus/templates/clips/chrome-extension/src/github-preview.ts +41 -31
  48. package/corpus/templates/clips/server/lib/video-remux.ts +222 -0
  49. package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +2 -0
  50. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +75 -19
  51. package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +2 -0
  52. package/corpus/templates/design/app/pages/DesignEditor.tsx +11 -1
  53. package/corpus/templates/design/changelog/2026-07-01-design-chat-no-longer-shows-an-empty-state-selection-chip-in-the-composer.md +6 -0
  54. package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +2 -0
  55. package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +2 -0
  56. package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +2 -0
  57. package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +2 -0
  58. package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +2 -0
  59. package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +2 -0
  60. package/corpus/templates/videos/.agents/skills/frontend-design/SKILL.md +2 -0
  61. package/dist/agent/model-config.js +1 -1
  62. package/dist/agent/model-config.js.map +1 -1
  63. package/dist/cli/skills.d.ts.map +1 -1
  64. package/dist/cli/skills.js +17 -3
  65. package/dist/cli/skills.js.map +1 -1
  66. package/dist/client/AssistantChat.d.ts +2 -1
  67. package/dist/client/AssistantChat.d.ts.map +1 -1
  68. package/dist/client/AssistantChat.js +37 -15
  69. package/dist/client/AssistantChat.js.map +1 -1
  70. package/dist/client/active-run-state.d.ts +2 -0
  71. package/dist/client/active-run-state.d.ts.map +1 -1
  72. package/dist/client/active-run-state.js +17 -0
  73. package/dist/client/active-run-state.js.map +1 -1
  74. package/dist/client/chat/message-components.d.ts.map +1 -1
  75. package/dist/client/chat/message-components.js +3 -1
  76. package/dist/client/chat/message-components.js.map +1 -1
  77. package/dist/client/chat/tool-call-display.d.ts +3 -1
  78. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  79. package/dist/client/chat/tool-call-display.js +8 -8
  80. package/dist/client/chat/tool-call-display.js.map +1 -1
  81. package/dist/client/sse-event-processor.d.ts +1 -0
  82. package/dist/client/sse-event-processor.d.ts.map +1 -1
  83. package/dist/client/sse-event-processor.js +136 -35
  84. package/dist/client/sse-event-processor.js.map +1 -1
  85. package/dist/notifications/index.d.ts +1 -1
  86. package/dist/notifications/index.d.ts.map +1 -1
  87. package/dist/notifications/index.js +1 -1
  88. package/dist/notifications/index.js.map +1 -1
  89. package/dist/notifications/registry.d.ts +5 -0
  90. package/dist/notifications/registry.d.ts.map +1 -1
  91. package/dist/notifications/registry.js +4 -1
  92. package/dist/notifications/registry.js.map +1 -1
  93. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +2 -0
  94. package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +2 -0
  95. package/package.json +1 -1
  96. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +2 -0
  97. package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +2 -0
package/corpus/README.md CHANGED
@@ -28,4 +28,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
  ## Generated Counts
29
29
 
30
30
  - core files: 2043
31
- - template files: 4962
31
+ - template files: 4968
@@ -1,5 +1,19 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.84.24
4
+
5
+ ### Patch Changes
6
+
7
+ - d88a57f: Fix chat tool-call rows shrinking, keep running chats visibly thinking, stream reconnect content incrementally, and collapse exact repeated tool rows.
8
+
9
+ ## 0.84.23
10
+
11
+ ### Patch Changes
12
+
13
+ - fde28eb: Expose notification delivery channel results so callers can distinguish custom-channel delivery from inbox persistence.
14
+ - fde28eb: Re-enable Claude Sonnet 5 as the visible and default Sonnet model now that the Builder gateway supports it.
15
+ - fde28eb: Sharpen the exported Design skill's quality bar to defeat "AI slop": frame the variant flow as separating taste/exploration/spec/code, pair every banned default with a concrete alternative, warn about second-order convergence, and add explicit guidance for building on an existing design system or codebase.
16
+
3
17
  ## 0.84.22
4
18
 
5
19
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.84.22",
3
+ "version": "0.84.24",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -120,7 +120,7 @@ export function getContextWindowForModel(modelId: string): number {
120
120
  return DEFAULT_CONTEXT_WINDOW;
121
121
  }
122
122
 
123
- const ENABLE_CLAUDE_SONNET_5 = false;
123
+ const ENABLE_CLAUDE_SONNET_5 = true;
124
124
 
125
125
  export const CLAUDE_SONNET_MODEL_ID = ENABLE_CLAUDE_SONNET_5
126
126
  ? "claude-sonnet-5"
@@ -363,14 +363,28 @@ iteration, or a human-in-the-loop choice among design directions.
363
363
 
364
364
  ## Design Quality Bar
365
365
 
366
+ Generic "AI slop" comes from letting one prompt set taste, explore, and emit code
367
+ at once — so the model returns the training-average (Inter, an indigo/violet
368
+ gradient, a centered hero, three rounded cards). The variant flow above exists to
369
+ separate those jobs; use it, and hold this bar:
370
+
366
371
  - Before generating, name the concrete audience, the screen's primary job, and
367
372
  the visual thesis. If the brief is vague, make a reasonable choice and state
368
373
  it instead of producing a generic dashboard/landing-page default.
369
- - For existing products, inspect the current screen, design system, tokens,
370
- component language, or codebase context before inventing a new direction.
374
+ - Refuse the defaults, and pair every "don't" with a "do" (banning Inter alone
375
+ just makes you reach for Roboto). Avoid Inter/Roboto/system fonts, the
376
+ indigo/violet slop palette (\`#6366F1\`/\`#8B5CF6\`/\`#A855F7\`) and purple-on-white
377
+ gradients, and centered-hero + three-icon-card layouts; instead pick a
378
+ distinctive font pairing, one non-default palette family with a single decisive
379
+ accent, and an asymmetric layout with a clear focal point.
371
380
  - Make each direction distinct in structure and behavior, not just palette.
372
381
  Give every variant one memorable signature choice, then keep the surrounding
373
- chrome disciplined.
382
+ chrome disciplined. Even your creative picks converge (Space Grotesk
383
+ everywhere) — vary deliberately so two directions never share a fingerprint.
384
+ - For existing products, inspect the current screen, design system, tokens, and
385
+ component language before inventing a new direction. Treat any drift back to a
386
+ default as a missing token to pin, and vary layout per screen so on-brand does
387
+ not become same-in-your-colors.
374
388
  - Treat copy, data, and imagery as design material. Use realistic domain
375
389
  content and first-party/generated assets when images matter; avoid lorem
376
390
  ipsum, vague SaaS filler, and decorative placeholder boxes.
@@ -37,10 +37,13 @@ import React, {
37
37
  import { LLM_MISSING_CREDENTIALS_MESSAGE } from "../agent/engine/credential-errors.js";
38
38
  import type { ReasoningEffort } from "../shared/reasoning-effort.js";
39
39
  import {
40
+ ACTIVE_RUN_STATE_EVENT,
41
+ clearActiveRunIfMatches,
40
42
  getActiveRunActivityTool,
41
43
  getActiveRun,
42
44
  resolveReconnectAfterSeq,
43
45
  setActiveRun,
46
+ type ActiveRunState,
44
47
  updateActiveRunActivity,
45
48
  updateActiveRunSeq,
46
49
  } from "./active-run-state.js";
@@ -282,6 +285,13 @@ export function reconnectProgressTimedOut(args: {
282
285
  return args.now - args.lastProgressAt >= threshold;
283
286
  }
284
287
 
288
+ function activeRunMatchesThread(
289
+ state: ActiveRunState | null,
290
+ threadId: string | undefined,
291
+ ): boolean {
292
+ return Boolean(threadId && state?.threadId === threadId && state.runId);
293
+ }
294
+
285
295
  function isAssistantUiDuplicateMessageIdError(error: unknown): boolean {
286
296
  const message = error instanceof Error ? error.message : String(error ?? "");
287
297
  return (
@@ -627,15 +637,21 @@ export function resolveAssistantChatRunningState({
627
637
  isReconnecting,
628
638
  optimisticRunning,
629
639
  isAutoResuming,
640
+ hasActiveServerRun,
630
641
  }: {
631
642
  forceStopped: boolean;
632
643
  isRuntimeRunning: boolean;
633
644
  isReconnecting: boolean;
634
645
  optimisticRunning: boolean;
635
646
  isAutoResuming: boolean;
647
+ hasActiveServerRun?: boolean;
636
648
  }): { isRunning: boolean; showRunningInUI: boolean } {
637
649
  const isRunning =
638
- !forceStopped && (isRuntimeRunning || isReconnecting || optimisticRunning);
650
+ !forceStopped &&
651
+ (isRuntimeRunning ||
652
+ isReconnecting ||
653
+ optimisticRunning ||
654
+ Boolean(hasActiveServerRun));
639
655
  return {
640
656
  isRunning,
641
657
  // During auto-continuation, assistant-ui can briefly mark the message done
@@ -1440,13 +1456,33 @@ const AssistantChatInner = forwardRef<
1440
1456
  // When stop is clicked during reconnect, keep content visible (don't wipe it)
1441
1457
  const [reconnectFrozen, setReconnectFrozen] = useState(false);
1442
1458
  const reconnectRunIdRef = useRef<string | null>(null);
1443
- const [reconnectAfterSeq, setReconnectAfterSeq] = useState(0);
1444
1459
  const reconnectAbortRef = useRef<AbortController | null>(null);
1445
1460
  // Nuclear stop: user clicked stop. Clears the stop button/indicator AND
1446
1461
  // lets new submissions go through immediately — prevents the "stuck
1447
1462
  // queueing forever" state where isReconnecting or isRuntimeRunning gets
1448
1463
  // wedged (e.g. after a tab refresh + stop during reconnect).
1449
1464
  const [forceStopped, setForceStopped] = useState(false);
1465
+ const [hasActiveServerRun, setHasActiveServerRun] = useState(() =>
1466
+ activeRunMatchesThread(getActiveRun(), threadId),
1467
+ );
1468
+ useEffect(() => {
1469
+ const syncActiveRun = (state = getActiveRun()) => {
1470
+ setHasActiveServerRun(activeRunMatchesThread(state, threadId));
1471
+ };
1472
+ syncActiveRun();
1473
+ const handler = (event: Event) => {
1474
+ const state = (event as CustomEvent<{ state?: ActiveRunState | null }>)
1475
+ .detail?.state;
1476
+ syncActiveRun(state ?? null);
1477
+ };
1478
+ const storageHandler = () => syncActiveRun();
1479
+ window.addEventListener(ACTIVE_RUN_STATE_EVENT, handler);
1480
+ window.addEventListener("storage", storageHandler);
1481
+ return () => {
1482
+ window.removeEventListener(ACTIVE_RUN_STATE_EVENT, handler);
1483
+ window.removeEventListener("storage", storageHandler);
1484
+ };
1485
+ }, [threadId]);
1450
1486
  // Real running state drives submission/queue gating; UI running also covers
1451
1487
  // short auto-continuation gaps so the latest assistant message does not flash
1452
1488
  // into a done state while the agent is still working.
@@ -1456,14 +1492,14 @@ const AssistantChatInner = forwardRef<
1456
1492
  isReconnecting,
1457
1493
  optimisticRunning,
1458
1494
  isAutoResuming,
1495
+ hasActiveServerRun,
1459
1496
  });
1460
1497
  const textStreaming = showRunningInUI || externalStreaming;
1461
1498
  // A revealed activity label wins; otherwise stay a steady "Thinking" by
1462
1499
  // default. We only surface "Reconnecting" while we are actively replaying
1463
1500
  // recovered content (reconnectContent populated). A bare reconnect with no
1464
- // replayed content e.g. a tail-resume after the serverless wall, where
1465
- // `scheduleUpdate` intentionally leaves reconnectContent empty — is normal
1466
- // ongoing work, so it must read as "Thinking", never a perpetual "Working".
1501
+ // replayed content is normal ongoing work, so it must read as "Thinking",
1502
+ // never a perpetual "Working".
1467
1503
  const runningStatusLabel = runningActivityLabel
1468
1504
  ? runningActivityLabel
1469
1505
  : isAutoResuming
@@ -1740,7 +1776,6 @@ const AssistantChatInner = forwardRef<
1740
1776
  const afterSeq = resolveReconnectAfterSeq(threadId, runId);
1741
1777
  const storedActivityTool = getActiveRunActivityTool(threadId, runId);
1742
1778
  setRunningActivityTool(storedActivityTool);
1743
- setReconnectAfterSeq(afterSeq);
1744
1779
  setActiveRun({
1745
1780
  threadId,
1746
1781
  runId,
@@ -1830,7 +1865,6 @@ const AssistantChatInner = forwardRef<
1830
1865
  let rafPending = false;
1831
1866
  let latestSnapshot: ContentPart[] = [];
1832
1867
  const scheduleUpdate = (snapshot: ContentPart[]) => {
1833
- if (afterSeq > 0) return;
1834
1868
  latestSnapshot = snapshot;
1835
1869
  if (rafPending) return;
1836
1870
  rafPending = true;
@@ -1918,10 +1952,10 @@ const AssistantChatInner = forwardRef<
1918
1952
  runId,
1919
1953
  });
1920
1954
  setDismissedRunErrorKey(null);
1955
+ clearActiveRunIfMatches(threadId, runId);
1921
1956
  reconnectAbortRef.current = null;
1922
1957
  setIsReconnecting(false);
1923
1958
  reconnectRunIdRef.current = null;
1924
- setReconnectAfterSeq(0);
1925
1959
  window.dispatchEvent(
1926
1960
  new CustomEvent("agentNative.chatRunning", {
1927
1961
  detail: { isRunning: false, tabId: tabId || threadId },
@@ -1945,10 +1979,10 @@ const AssistantChatInner = forwardRef<
1945
1979
  }
1946
1980
 
1947
1981
  if (reconnectRunIdRef.current === runId) {
1982
+ clearActiveRunIfMatches(threadId, runId);
1948
1983
  reconnectAbortRef.current = null;
1949
1984
  setIsReconnecting(false);
1950
1985
  reconnectRunIdRef.current = null;
1951
- setReconnectAfterSeq(0);
1952
1986
  window.dispatchEvent(
1953
1987
  new CustomEvent("agentNative.chatRunning", {
1954
1988
  detail: { isRunning: false, tabId: tabId || threadId },
@@ -2748,6 +2782,7 @@ const AssistantChatInner = forwardRef<
2748
2782
  setRunErrorInfo(null);
2749
2783
  setDismissedRunErrorKey(null);
2750
2784
  if (runIdToAbort) {
2785
+ if (threadId) clearActiveRunIfMatches(threadId, runIdToAbort);
2751
2786
  fetch(`${apiUrl}/runs/${encodeURIComponent(runIdToAbort)}/abort`, {
2752
2787
  method: "POST",
2753
2788
  }).catch(() => {});
@@ -2756,7 +2791,6 @@ const AssistantChatInner = forwardRef<
2756
2791
  reconnectAbortRef.current?.abort();
2757
2792
  reconnectAbortRef.current = null;
2758
2793
  reconnectRunIdRef.current = null;
2759
- setReconnectAfterSeq(0);
2760
2794
  setIsReconnecting(false);
2761
2795
  setReconnectFrozen(reconnectContent.length > 0);
2762
2796
  }
@@ -3610,12 +3644,11 @@ const AssistantChatInner = forwardRef<
3610
3644
  />
3611
3645
  )}
3612
3646
  {(isReconnecting || reconnectFrozen) &&
3613
- reconnectAfterSeq === 0 &&
3614
3647
  reconnectContent.length > 0 && (
3615
3648
  <ReconnectStreamMessage content={reconnectContent} />
3616
3649
  )}
3617
3650
  {(isReconnecting || reconnectFrozen) &&
3618
- reconnectAfterSeq > 0 &&
3651
+ reconnectContent.length === 0 &&
3619
3652
  reconnectActivityContent.length > 0 && (
3620
3653
  <ReconnectStreamMessage
3621
3654
  content={reconnectActivityContent}
@@ -1,4 +1,5 @@
1
1
  const STORAGE_KEY = "agent-chat-active-run";
2
+ export const ACTIVE_RUN_STATE_EVENT = "agent-chat:active-run-state-change";
2
3
 
3
4
  export interface ActiveRunState {
4
5
  threadId: string;
@@ -7,6 +8,19 @@ export interface ActiveRunState {
7
8
  activityTool?: string | null;
8
9
  }
9
10
 
11
+ function notifyActiveRunStateChanged(state: ActiveRunState | null): void {
12
+ if (
13
+ typeof window === "undefined" ||
14
+ typeof window.dispatchEvent !== "function" ||
15
+ typeof CustomEvent === "undefined"
16
+ ) {
17
+ return;
18
+ }
19
+ window.dispatchEvent(
20
+ new CustomEvent(ACTIVE_RUN_STATE_EVENT, { detail: { state } }),
21
+ );
22
+ }
23
+
10
24
  function normalizeActivityTool(toolName: unknown): string | null {
11
25
  if (typeof toolName !== "string") return null;
12
26
  const tool = toolName.trim();
@@ -17,6 +31,7 @@ export function setActiveRun(state: ActiveRunState): void {
17
31
  try {
18
32
  sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state));
19
33
  } catch {}
34
+ notifyActiveRunStateChanged(state);
20
35
  }
21
36
 
22
37
  export function getActiveRun(): ActiveRunState | null {
@@ -64,6 +79,13 @@ export function clearActiveRun(): void {
64
79
  try {
65
80
  sessionStorage.removeItem(STORAGE_KEY);
66
81
  } catch {}
82
+ notifyActiveRunStateChanged(null);
83
+ }
84
+
85
+ export function clearActiveRunIfMatches(threadId: string, runId: string): void {
86
+ const state = getActiveRun();
87
+ if (state?.threadId !== threadId || state.runId !== runId) return;
88
+ clearActiveRun();
67
89
  }
68
90
 
69
91
  /** Resume reconnect SSE after the last seen event (0 = replay from the start). */
@@ -807,6 +807,7 @@ export function shouldShowAssistantMessageFooter({
807
807
  hasUnresolvedTool?: boolean;
808
808
  }): boolean {
809
809
  if (!hasRenderableContent) return false;
810
+ if (chatRunning) return false;
810
811
  if (!isLast) return true;
811
812
  if (hasUnresolvedTool) return false;
812
813
  return !chatRunning && statusIsTerminal;
@@ -909,7 +910,7 @@ export function AssistantMessage() {
909
910
  className="group relative"
910
911
  style={{ contentVisibility: isComplete ? "auto" : "visible" }}
911
912
  >
912
- <div className="max-w-[95%] text-sm leading-relaxed text-foreground">
913
+ <div className="w-full max-w-[95%] text-sm leading-relaxed text-foreground">
913
914
  <MessagePrimitive.Parts
914
915
  components={{
915
916
  Text: MarkdownText,
@@ -418,6 +418,7 @@ export function ToolCallDisplay({
418
418
  isRunning,
419
419
  structuredMeta,
420
420
  approval,
421
+ repeatCount,
421
422
  }: {
422
423
  toolName: string;
423
424
  argsText?: string;
@@ -428,6 +429,7 @@ export function ToolCallDisplay({
428
429
  isRunning: boolean;
429
430
  structuredMeta?: Record<string, unknown>;
430
431
  approval?: { approvalKey: string; dismissed?: boolean };
432
+ repeatCount?: number;
431
433
  }) {
432
434
  // Delegate to bespoke cells when structured metadata is present.
433
435
  // These must be separate components so hook order in ToolCallDisplayGeneric
@@ -474,6 +476,7 @@ export function ToolCallDisplay({
474
476
  chatUI={chatUI}
475
477
  isRunning={isRunning}
476
478
  approval={approval}
479
+ repeatCount={repeatCount}
477
480
  />
478
481
  );
479
482
  }
@@ -487,6 +490,7 @@ function ToolCallDisplayGeneric({
487
490
  chatUI,
488
491
  isRunning,
489
492
  approval,
493
+ repeatCount,
490
494
  }: {
491
495
  toolName: string;
492
496
  argsText?: string;
@@ -496,6 +500,7 @@ function ToolCallDisplayGeneric({
496
500
  chatUI?: ActionChatUIConfig;
497
501
  isRunning: boolean;
498
502
  approval?: { approvalKey: string; dismissed?: boolean };
503
+ repeatCount?: number;
499
504
  }) {
500
505
  const streamRef = useRef<HTMLDivElement>(null);
501
506
 
@@ -614,7 +619,7 @@ function ToolCallDisplayGeneric({
614
619
  const isExpanded = isAgentCall ? hasStreamText && expanded : expanded;
615
620
 
616
621
  return (
617
- <div className="my-1 overflow-hidden">
622
+ <div className="my-1 w-full overflow-hidden">
618
623
  {mcpApp && <McpAppRenderer app={mcpApp} className="mb-1.5" />}
619
624
  <button
620
625
  onClick={() => canExpand && setExpanded(!isExpanded)}
@@ -640,6 +645,14 @@ function ToolCallDisplayGeneric({
640
645
  <span className="truncate min-w-0">
641
646
  <span className="font-medium">{displayName}</span>
642
647
  </span>
648
+ {repeatCount && repeatCount > 1 && (
649
+ <span
650
+ className="shrink-0 rounded border border-border/60 px-1.5 py-0.5 text-[10px] leading-none text-muted-foreground"
651
+ title={`Repeated ${repeatCount} times`}
652
+ >
653
+ {repeatCount}x
654
+ </span>
655
+ )}
643
656
  {canExpand && (
644
657
  <IconChevronDown
645
658
  className={cn(
@@ -694,6 +707,7 @@ export function ToolCallFallback({
694
707
  structuredMeta?: Record<string, unknown>;
695
708
  activity?: boolean;
696
709
  approval?: { approvalKey: string; dismissed?: boolean };
710
+ repeatCount?: number;
697
711
  }) {
698
712
  const chatRunning = React.useContext(ChatRunningContext);
699
713
  const isRunning =
@@ -715,6 +729,7 @@ export function ToolCallFallback({
715
729
  structuredMeta={rest.structuredMeta}
716
730
  isRunning={isRunning}
717
731
  approval={rest.approval}
732
+ repeatCount={rest.repeatCount}
718
733
  />
719
734
  );
720
735
  }
@@ -734,7 +749,7 @@ export function ReconnectStreamMessage({
734
749
 
735
750
  return (
736
751
  <div className="flex justify-start">
737
- <div className="max-w-[95%] text-sm leading-relaxed text-foreground space-y-1">
752
+ <div className="w-full max-w-[95%] text-sm leading-relaxed text-foreground space-y-1">
738
753
  {content.map((part, i) => {
739
754
  if (part.type === "text") {
740
755
  const partStreaming = chatRunning && i === streamingTextPartIndex;
@@ -764,6 +779,7 @@ export function ReconnectStreamMessage({
764
779
  (chatRunning || part.activity === true)
765
780
  }
766
781
  approval={part.approval}
782
+ repeatCount={part.repeatCount}
767
783
  />
768
784
  );
769
785
  }