@agent-native/core 0.169.0 → 0.169.1

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 (71) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/actions/bigquery-table-info.ts +3 -2
  3. package/corpus/templates/analytics/actions/search-bigquery-schema.ts +130 -4
  4. package/corpus/templates/analytics/agent-native.config.ts +1 -0
  5. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +36 -23
  6. package/corpus/templates/analytics/app/lib/dashboard-visibility.ts +41 -0
  7. package/corpus/templates/analytics/app/pages/DashboardOverview.tsx +34 -9
  8. package/corpus/templates/analytics/changelog/2026-08-21-dashboard-filters-keep-other-users-dashboards-out-of-mine.md +6 -0
  9. package/corpus/templates/analytics/server/lib/real-data-actions.ts +14 -3
  10. package/corpus/templates/analytics/server/plugins/agent-chat.ts +33 -3
  11. package/corpus/templates/brain/server/lib/brain.ts +27 -2
  12. package/corpus/templates/clips/actions/stop-meeting-recording.ts +87 -6
  13. package/corpus/templates/clips/app/components/library/search-bar.tsx +1 -1
  14. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +3 -4
  15. package/corpus/templates/design/app/components/design/EditPanel.tsx +15 -47
  16. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +3 -3
  17. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +0 -7
  18. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +2 -4
  19. package/corpus/templates/design/app/i18n-data.ts +10 -10
  20. package/corpus/templates/design/app/pages/DesignEditor.tsx +22 -22
  21. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +39 -8
  22. package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -1
  23. package/corpus/templates/design/app/pages/Index.tsx +1 -1
  24. package/corpus/templates/design/app/pages/design-editor/preview-navigation.ts +25 -0
  25. package/corpus/templates/mail/app/hooks/use-emails.ts +13 -3
  26. package/dist/agent/engine/credential-errors.d.ts +1 -1
  27. package/dist/agent/engine/credential-errors.js +2 -2
  28. package/dist/client/AssistantChat.js +47 -15
  29. package/dist/client/ConnectBuilderCard.d.ts +3 -1
  30. package/dist/client/ConnectBuilderCard.js +6 -3
  31. package/dist/client/chat/message-components.d.ts +7 -0
  32. package/dist/client/chat/message-components.js +1 -1
  33. package/dist/client/chat/run-recovery.js +5 -2
  34. package/dist/client/chat/tool-call-display.js +9 -3
  35. package/dist/client/error-format.js +12 -0
  36. package/dist/client/navigation/index.d.ts +1 -0
  37. package/dist/client/navigation/index.js +1 -0
  38. package/dist/client/route-state.js +6 -0
  39. package/dist/client/workspace-app-navigation.d.ts +10 -0
  40. package/dist/client/workspace-app-navigation.js +38 -0
  41. package/dist/collab/struct-routes.d.ts +1 -1
  42. package/dist/deploy/build.d.ts +13 -0
  43. package/dist/deploy/build.js +51 -0
  44. package/dist/localization/core-messages/ar-SA.js +1 -1
  45. package/dist/localization/core-messages/de-DE.js +1 -1
  46. package/dist/localization/core-messages/en-US.d.ts +1 -1
  47. package/dist/localization/core-messages/en-US.js +1 -1
  48. package/dist/localization/core-messages/es-ES.js +1 -1
  49. package/dist/localization/core-messages/fr-FR.js +1 -1
  50. package/dist/localization/core-messages/hi-IN.js +1 -1
  51. package/dist/localization/core-messages/ja-JP.js +1 -1
  52. package/dist/localization/core-messages/ko-KR.js +1 -1
  53. package/dist/localization/core-messages/pt-BR.js +1 -1
  54. package/dist/localization/core-messages/zh-CN.js +1 -1
  55. package/dist/localization/core-messages/zh-TW.js +1 -1
  56. package/dist/localization/core-messages.d.ts +1 -1
  57. package/dist/notifications/routes.d.ts +1 -1
  58. package/dist/observability/routes.d.ts +3 -3
  59. package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
  60. package/dist/provider-api/actions/provider-api.d.ts +13 -13
  61. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  62. package/dist/resources/handlers.d.ts +1 -1
  63. package/dist/scripts/db/check-scoping.js +9 -6
  64. package/dist/secrets/routes.d.ts +6 -6
  65. package/dist/server/builder-browser.d.ts +4 -0
  66. package/dist/server/builder-browser.js +23 -1
  67. package/dist/server/core-routes-plugin.js +13 -1
  68. package/dist/server/realtime-token.d.ts +1 -1
  69. package/dist/server/transcribe-voice.d.ts +1 -1
  70. package/package.json +3 -3
  71. /package/corpus/templates/dispatch/app/routes/{apps.$appId.tsx → apps.$appId.$.tsx} +0 -0
@@ -61,6 +61,8 @@ type OtherSource =
61
61
  | "existing"
62
62
  | "notes";
63
63
 
64
+ type BuilderIndexInputSource = "figma" | "github" | "design-md";
65
+
64
66
  interface BuilderIndexResult {
65
67
  ok: boolean;
66
68
  source: "builder";
@@ -151,6 +153,8 @@ export default function DesignSystemSetup() {
151
153
  const [builderIndexing, setBuilderIndexing] = useState(false);
152
154
  const [builderIndexResult, setBuilderIndexResult] =
153
155
  useState<BuilderIndexResult | null>(null);
156
+ const [builderIndexInputSource, setBuilderIndexInputSource] =
157
+ useState<BuilderIndexInputSource | null>(null);
154
158
  const [builderIndexError, setBuilderIndexError] = useState<string | null>(
155
159
  null,
156
160
  );
@@ -222,6 +226,7 @@ export default function DesignSystemSetup() {
222
226
  }
223
227
  setBuilderIndexError(null);
224
228
  setBuilderIndexResult(null);
229
+ setBuilderIndexInputSource("figma");
225
230
  stopDecodePolling();
226
231
  setDecodeStatus(null);
227
232
  setBuilderIndexing(true);
@@ -550,7 +555,7 @@ export default function DesignSystemSetup() {
550
555
  if (isGithubOnlySource) {
551
556
  setValidationError(null);
552
557
  try {
553
- await indexSystemMutation.mutateAsync({
558
+ const result = await indexSystemMutation.mutateAsync({
554
559
  projectName: companyInfo.trim() || undefined,
555
560
  description:
556
561
  [notes.trim(), customInstructions.trim()]
@@ -562,8 +567,9 @@ export default function DesignSystemSetup() {
562
567
  ...(link.include?.length ? { include: link.include } : {}),
563
568
  })),
564
569
  });
570
+ setBuilderIndexInputSource("github");
571
+ setBuilderIndexResult(result);
565
572
  toast.success(t("designSystemSetup.githubIndexStarted"));
566
- navigate("/design-systems");
567
573
  } catch (error) {
568
574
  setValidationError(
569
575
  error instanceof Error
@@ -603,7 +609,7 @@ export default function DesignSystemSetup() {
603
609
  if (isDesignMdOnlySource) {
604
610
  setValidationError(null);
605
611
  try {
606
- await indexSystemMutation.mutateAsync({
612
+ const result = await indexSystemMutation.mutateAsync({
607
613
  projectName: companyInfo.trim() || undefined,
608
614
  description:
609
615
  [notes.trim(), customInstructions.trim()]
@@ -611,8 +617,9 @@ export default function DesignSystemSetup() {
611
617
  .join("\n\n") || undefined,
612
618
  designMd: readableDesignMdFiles[0]?.textContent,
613
619
  });
620
+ setBuilderIndexInputSource("design-md");
621
+ setBuilderIndexResult(result);
614
622
  toast.success(t("designSystemSetup.designMdIndexStarted"));
615
- navigate("/design-systems");
616
623
  } catch (error) {
617
624
  setValidationError(
618
625
  error instanceof Error
@@ -861,6 +868,23 @@ export default function DesignSystemSetup() {
861
868
  </div>
862
869
  )}
863
870
 
871
+ {builderIndexResult && builderIndexInputSource !== "figma" ? (
872
+ <div className="mb-6">
873
+ <BuilderIndexPreview
874
+ result={builderIndexResult}
875
+ decodeStatus={null}
876
+ source={builderIndexInputSource ?? "design-md"}
877
+ displayTitle={companyInfo.trim()}
878
+ onReset={() => {
879
+ setDecodeStatus(null);
880
+ setBuilderIndexResult(null);
881
+ setBuilderIndexInputSource(null);
882
+ setBuilderIndexError(null);
883
+ }}
884
+ />
885
+ </div>
886
+ ) : null}
887
+
864
888
  <div className="space-y-5">
865
889
  <section className="rounded-lg border border-border bg-card p-4">
866
890
  <div className="mb-3">
@@ -990,6 +1014,7 @@ export default function DesignSystemSetup() {
990
1014
  stopDecodePolling();
991
1015
  setDecodeStatus(null);
992
1016
  setBuilderIndexResult(null);
1017
+ setBuilderIndexInputSource(null);
993
1018
  setBuilderIndexError(null);
994
1019
  }}
995
1020
  />
@@ -1596,32 +1621,38 @@ function FileList({
1596
1621
  function BuilderIndexPreview({
1597
1622
  result,
1598
1623
  decodeStatus,
1624
+ source = "figma",
1599
1625
  displayTitle,
1600
1626
  onReset,
1601
1627
  }: {
1602
1628
  result: BuilderIndexResult;
1603
1629
  decodeStatus: DecodeJobStatus | null;
1630
+ source?: BuilderIndexInputSource;
1604
1631
  displayTitle?: string;
1605
1632
  onReset: () => void;
1606
1633
  }) {
1607
1634
  const t = useT();
1608
1635
  const decodeError =
1609
1636
  decodeStatus?.status === "error" ? decodeStatus.error : null;
1637
+ const SourceIcon =
1638
+ source === "figma"
1639
+ ? IconBrandFigma
1640
+ : source === "github"
1641
+ ? IconBrandGithub
1642
+ : IconFileDescription;
1610
1643
 
1611
1644
  return (
1612
1645
  <div className="space-y-4 rounded-xl border border-border bg-card p-4">
1613
1646
  <div className="flex items-start gap-3">
1614
1647
  <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-[#609FF8]/10">
1615
- <IconBrandFigma className="h-5 w-5 text-[#609FF8]" />
1648
+ <SourceIcon className="h-5 w-5 text-[var(--design-editor-accent-color)]" />
1616
1649
  </div>
1617
1650
  <div className="min-w-0 flex-1">
1618
1651
  <h3 className="text-sm font-semibold text-foreground">
1619
1652
  {displayTitle || result.suggestedTitle}
1620
1653
  </h3>
1621
1654
  <p className="mt-1 text-xs leading-relaxed text-muted-foreground">
1622
- {
1623
- "Builder is indexing this Figma file into a reusable design system." /* i18n-ignore Builder indexing status */
1624
- }
1655
+ {t("designSystemSetup.figmaParsingDescription")}
1625
1656
  </p>
1626
1657
  </div>
1627
1658
  </div>
@@ -699,7 +699,7 @@ export default function DesignSystems() {
699
699
  </AlertDialogCancel>
700
700
  <AlertDialogAction
701
701
  onClick={bulkDeleteOpen ? handleBulkDelete : handleDelete}
702
- className="bg-red-600 hover:bg-red-700 cursor-pointer"
702
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90 cursor-pointer"
703
703
  >
704
704
  {t("designSystems.actions.delete")}
705
705
  </AlertDialogAction>
@@ -1232,7 +1232,7 @@ export default function Index() {
1232
1232
  </AlertDialogCancel>
1233
1233
  <AlertDialogAction
1234
1234
  onClick={bulkDeleteOpen ? handleBulkDelete : handleDelete}
1235
- className="bg-red-600 hover:bg-red-700 cursor-pointer"
1235
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90 cursor-pointer"
1236
1236
  >
1237
1237
  {t("home.delete")}
1238
1238
  </AlertDialogAction>
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Opens a preview from a user gesture without losing it to popup blocking.
3
+ * The blank window must be created before assigning the real URL; otherwise
4
+ * browsers can treat the delayed navigation as an unsolicited popup.
5
+ */
6
+ export function openPreviewUrl(
7
+ url: string,
8
+ openWindow: (url: string, target: string) => Window | null,
9
+ navigateSameTab: (url: string) => void,
10
+ ): "popup" | "same-tab" {
11
+ const popup = openWindow("", "_blank");
12
+ if (!popup) {
13
+ navigateSameTab(url);
14
+ return "same-tab";
15
+ }
16
+
17
+ try {
18
+ popup.opener = null;
19
+ popup.location.href = url;
20
+ return "popup";
21
+ } catch {
22
+ navigateSameTab(url);
23
+ return "same-tab";
24
+ }
25
+ }
@@ -88,19 +88,27 @@ export function fetchThreadMessages(
88
88
  }
89
89
 
90
90
  let externalRefreshAt = 0;
91
+ let externalRefreshGeneration = 0;
92
+ const externalRefreshConsumers = new Map<string, number>();
91
93
 
92
94
  export function markExternalEmailRefresh() {
93
95
  externalRefreshAt = Date.now();
96
+ externalRefreshGeneration += 1;
94
97
  }
95
98
 
96
- export function consumeExternalEmailRefresh(): number | undefined {
99
+ export function consumeExternalEmailRefresh(
100
+ scope = "default",
101
+ ): number | undefined {
97
102
  const refreshAt = externalRefreshAt;
98
103
  if (!refreshAt) return undefined;
99
104
  if (Date.now() - refreshAt >= 5000) {
100
105
  externalRefreshAt = 0;
106
+ externalRefreshConsumers.clear();
101
107
  return undefined;
102
108
  }
103
- externalRefreshAt = 0;
109
+ if (externalRefreshConsumers.get(scope) === externalRefreshGeneration)
110
+ return undefined;
111
+ externalRefreshConsumers.set(scope, externalRefreshGeneration);
104
112
  return refreshAt;
105
113
  }
106
114
 
@@ -516,7 +524,9 @@ export function useEmails(
516
524
  if (label) params.set("label", label);
517
525
  if (pageParam) params.set("pageToken", pageParam);
518
526
  const forceRefreshAt = !pageParam
519
- ? consumeExternalEmailRefresh()
527
+ ? consumeExternalEmailRefresh(
528
+ JSON.stringify([view, search ?? null, label ?? null]),
529
+ )
520
530
  : undefined;
521
531
  if (forceRefreshAt) {
522
532
  params.set("forceRefresh", String(forceRefreshAt));
@@ -5,7 +5,7 @@ export declare const LLM_MISSING_CREDENTIALS_ERROR_CODE = "missing_credentials";
5
5
  * recognize it without importing server-only code into the browser bundle.
6
6
  */
7
7
  export declare const CREDENTIAL_STORE_UNAVAILABLE_ERROR_CODE = "credential_store_unavailable";
8
- export declare const LLM_MISSING_CREDENTIALS_MESSAGE = "No LLM provider is connected. Open this app's Manage agent > LLM, then connect Builder.io (free tier available) or add a provider key.";
8
+ export declare const LLM_MISSING_CREDENTIALS_MESSAGE = "No LLM provider is connected. Open Settings > Agent > AI providers, then connect Builder.io (free tier available) or add a provider key.";
9
9
  /**
10
10
  * The one line a site visitor sees for every gateway rejection. Quota,
11
11
  * concurrency, a revoked token, a disabled gateway and an unreadable credential
@@ -6,7 +6,7 @@ export const LLM_MISSING_CREDENTIALS_ERROR_CODE = "missing_credentials";
6
6
  * recognize it without importing server-only code into the browser bundle.
7
7
  */
8
8
  export const CREDENTIAL_STORE_UNAVAILABLE_ERROR_CODE = "credential_store_unavailable";
9
- export const LLM_MISSING_CREDENTIALS_MESSAGE = "No LLM provider is connected. Open this app's Manage agent > LLM, then connect Builder.io (free tier available) or add a provider key.";
9
+ export const LLM_MISSING_CREDENTIALS_MESSAGE = "No LLM provider is connected. Open Settings > Agent > AI providers, then connect Builder.io (free tier available) or add a provider key.";
10
10
  /**
11
11
  * The one line a site visitor sees for every gateway rejection. Quota,
12
12
  * concurrency, a revoked token, a disabled gateway and an unreadable credential
@@ -59,7 +59,7 @@ export function formatLlmCredentialErrorMessage(options) {
59
59
  return GATEWAY_UNAVAILABLE_VISITOR_MESSAGE;
60
60
  const agentName = options?.agentName?.trim();
61
61
  if (agentName) {
62
- return `The ${agentName} agent could not finish this request because that app needs an LLM connection. Open ${agentName}'s Manage agent > LLM, then connect Builder.io (free tier available) or add a provider key.`;
62
+ return `The ${agentName} agent could not finish this request because that app needs an LLM connection. Open Settings > Agent > AI providers, then connect Builder.io (free tier available) or add a provider key.`;
63
63
  }
64
64
  return LLM_MISSING_CREDENTIALS_MESSAGE;
65
65
  }
@@ -121,6 +121,8 @@ function getPollAbortMs(interval) {
121
121
  return Math.max(POLL_ABORT_MIN_MS, interval * 4);
122
122
  }
123
123
  const ACTIVE_RUN_CLEAR_TIMEOUT_MS = 5_000;
124
+ const ACTIVE_RUN_CLEAR_STABLE_POLLS = 2;
125
+ const ACTIVE_RUN_CLEAR_RETRY_DELAY_MS = 1_000;
124
126
  const ACTIVE_RUN_STUCK_THRESHOLD_MS = 90_000;
125
127
  const BACKGROUND_ACTIVE_RUN_STUCK_THRESHOLD_MS = 13 * 60_000;
126
128
  const ACTIVE_RUN_POLL_INTERVAL_MS = 150;
@@ -303,6 +305,7 @@ export async function waitForThreadRunToClear(apiUrl, threadId) {
303
305
  return true;
304
306
  const deadline = Date.now() + ACTIVE_RUN_CLEAR_TIMEOUT_MS;
305
307
  let activeRunToResume = null;
308
+ let consecutiveClearPolls = 0;
306
309
  const resumeActiveRun = (info) => {
307
310
  if (!info.runId)
308
311
  return;
@@ -323,11 +326,22 @@ export async function waitForThreadRunToClear(apiUrl, threadId) {
323
326
  const res = await fetch(`${apiUrl}/runs/active?threadId=${encodeURIComponent(threadId)}`);
324
327
  if (res.ok) {
325
328
  const info = (await res.json());
326
- if (!info?.active ||
329
+ const runLooksClear = !info?.active ||
327
330
  info?.status !== "running" ||
328
- activeRunLooksStale(info))
329
- return true;
330
- if (info.runId) {
331
+ activeRunLooksStale(info);
332
+ if (runLooksClear) {
333
+ consecutiveClearPolls += 1;
334
+ // A terminal/no-active snapshot can briefly appear between a
335
+ // foreground run and its server-owned continuation. Require two
336
+ // consecutive clear polls before releasing a queued follow-up.
337
+ if (consecutiveClearPolls >= ACTIVE_RUN_CLEAR_STABLE_POLLS) {
338
+ return true;
339
+ }
340
+ }
341
+ else {
342
+ consecutiveClearPolls = 0;
343
+ }
344
+ if (!runLooksClear && info.runId) {
331
345
  activeRunToResume = info;
332
346
  if (info.awaitingRedispatch === true) {
333
347
  // This is not the brief terminal-write lag the 5s waiter was built
@@ -340,8 +354,12 @@ export async function waitForThreadRunToClear(apiUrl, threadId) {
340
354
  }
341
355
  }
342
356
  }
357
+ else {
358
+ consecutiveClearPolls = 0;
359
+ }
343
360
  }
344
361
  catch {
362
+ consecutiveClearPolls = 0;
345
363
  // Transient poll failure — try again until the short grace period ends.
346
364
  }
347
365
  await new Promise((resolve) => window.setTimeout(resolve, ACTIVE_RUN_POLL_INTERVAL_MS));
@@ -1730,6 +1748,15 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
1730
1748
  tabId,
1731
1749
  onActiveRunChange: setHasActiveServerRun,
1732
1750
  });
1751
+ // Server truth must participate in submit gating, not only in the missing
1752
+ // final-response warning. The local lifecycle can clear during a transport
1753
+ // handoff while the server still owns the turn.
1754
+ const serverRunState = useRunStuckDetection({
1755
+ threadId: threadId ?? null,
1756
+ enabled: isActiveComposer,
1757
+ apiUrl,
1758
+ });
1759
+ const serverRunActive = serverRunState.runId != null && serverRunState.status === "running";
1733
1760
  // Real running state drives submission/queue gating; UI running also covers
1734
1761
  // short auto-continuation gaps so the latest assistant message does not flash
1735
1762
  // into a done state while the agent is still working.
@@ -1739,19 +1766,10 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
1739
1766
  isReconnecting,
1740
1767
  optimisticRunning,
1741
1768
  isAutoResuming,
1742
- hasActiveServerRun,
1769
+ hasActiveServerRun: hasActiveServerRun || serverRunActive,
1743
1770
  hasTerminalRunError: runErrorInfo !== null,
1744
1771
  });
1745
1772
  const textStreaming = showRunningInUI || externalStreaming;
1746
- // Server truth about this thread's run, used to suppress the "agent stopped
1747
- // without sending a final message" notice while the server still has the
1748
- // turn in flight. The poll backs itself off to 15s while nothing is running.
1749
- const serverRunState = useRunStuckDetection({
1750
- threadId: threadId ?? null,
1751
- enabled: isActiveComposer,
1752
- apiUrl,
1753
- });
1754
- const serverRunActive = serverRunState.runId != null && serverRunState.status === "running";
1755
1773
  const storedActiveRun = getActiveRun();
1756
1774
  const activeChatRunId = (serverRunState.status === "running" ? serverRunState.runId : null) ??
1757
1775
  (activeRunMatchesThread(storedActiveRun, threadId)
@@ -3318,6 +3336,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
3318
3336
  dequeueInFlightRef.current = true;
3319
3337
  let cancelled = false;
3320
3338
  let started = false;
3339
+ let retryTimer = null;
3321
3340
  const timer = window.setTimeout(() => {
3322
3341
  started = true;
3323
3342
  void (async () => {
@@ -3329,8 +3348,19 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
3329
3348
  // complete. Starting the queued turn during that window can reconnect
3330
3349
  // to the old run and replay the old answer under the new prompt.
3331
3350
  const runCleared = await waitForThreadRunToClear(apiUrl, threadId);
3332
- if (cancelled || !runCleared)
3351
+ if (cancelled)
3333
3352
  return;
3353
+ if (!runCleared) {
3354
+ // The server still owns this turn (including a deferred durable
3355
+ // successor). Keep the queued message visible and retry after a
3356
+ // short delay so one transient idle snapshot cannot strand it.
3357
+ retryTimer = window.setTimeout(() => {
3358
+ if (!cancelled) {
3359
+ setQueueWakeVersion((version) => version + 1);
3360
+ }
3361
+ }, ACTIVE_RUN_CLEAR_RETRY_DELAY_MS);
3362
+ return;
3363
+ }
3334
3364
  if (queueStopVersionRef.current !== stopVersion ||
3335
3365
  queuedMessagesRef.current[0]?.id !== next.id) {
3336
3366
  return;
@@ -3391,6 +3421,8 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
3391
3421
  return () => {
3392
3422
  cancelled = true;
3393
3423
  window.clearTimeout(timer);
3424
+ if (retryTimer !== null)
3425
+ window.clearTimeout(retryTimer);
3394
3426
  if (!started) {
3395
3427
  dequeueInFlightRef.current = false;
3396
3428
  }
@@ -12,10 +12,12 @@ export interface ConnectBuilderCardProps {
12
12
  /** The user's feature/change request, forwarded to the selected coding
13
13
  * agent when they click Send. Empty for generic "connect Builder" prompts. */
14
14
  prompt?: string;
15
+ /** Formatted staged chat context forwarded with the Builder request. */
16
+ context?: string;
15
17
  }
16
18
  /**
17
19
  * Rich inline card rendered for the `connect-builder` tool call. Shows the
18
20
  * Builder handoff when available, or routes local development requests to the
19
21
  * external coding agent.
20
22
  */
21
- export declare function ConnectBuilderCard({ configured: initialConfigured, builderEnabled: initialBuilderEnabled, connectUrl: initialConnectUrl, orgName: initialOrgName, prompt, }: ConnectBuilderCardProps): React.JSX.Element;
23
+ export declare function ConnectBuilderCard({ configured: initialConfigured, builderEnabled: initialBuilderEnabled, connectUrl: initialConnectUrl, orgName: initialOrgName, prompt, context, }: ConnectBuilderCardProps): React.JSX.Element;
@@ -30,7 +30,7 @@ function hasElectronShellBridge() {
30
30
  * Builder handoff when available, or routes local development requests to the
31
31
  * external coding agent.
32
32
  */
33
- export function ConnectBuilderCard({ configured: initialConfigured, builderEnabled: initialBuilderEnabled = true, connectUrl: initialConnectUrl, orgName: initialOrgName, prompt = "", }) {
33
+ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabled: initialBuilderEnabled = true, connectUrl: initialConnectUrl, orgName: initialOrgName, prompt = "", context = "", }) {
34
34
  // The connect-poll state machine is shared — the tool-call result is
35
35
  // frozen at render time, so the hook's mount-time fetch + focus refresh
36
36
  // is what catches a flow the user completed in another tab.
@@ -89,7 +89,10 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
89
89
  const res = await fetch(new URL(agentNativePath("/_agent-native/builder/run"), origin).href, {
90
90
  method: "POST",
91
91
  headers: { "Content-Type": "application/json" },
92
- body: JSON.stringify({ prompt }),
92
+ body: JSON.stringify({
93
+ prompt,
94
+ ...(context.trim() ? { context } : {}),
95
+ }),
93
96
  });
94
97
  const data = await res.json().catch(() => ({}));
95
98
  if (!res.ok) {
@@ -108,7 +111,7 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
108
111
  setSendErr(e instanceof Error ? e.message : "Send failed");
109
112
  setSending(false);
110
113
  }
111
- }, [prompt]);
114
+ }, [context, prompt]);
112
115
  const handleJoinWaitlist = useCallback(async () => {
113
116
  setJoiningWaitlist(true);
114
117
  setWaitlistErr(null);
@@ -115,6 +115,13 @@ export declare function shouldShowMissingFinalResponse({ isCurrentTurnRunning, s
115
115
  hasActiveTool?: boolean;
116
116
  userStoppedRun?: boolean;
117
117
  }): boolean;
118
+ /**
119
+ * "The agent stopped" is derived from local client state, which dips to
120
+ * not-running at every chunk boundary and transport re-attach while the turn is
121
+ * still alive server-side. Requiring the shape to hold for a beat keeps the
122
+ * notice off the screen for those gaps without hiding a real stop for long.
123
+ */
124
+ export declare const MISSING_FINAL_RESPONSE_SETTLE_MS = 3000;
118
125
  export declare function useSettledFlag(active: boolean, delayMs: number): boolean;
119
126
  export declare function shouldShowAssistantWorkSummary({ isLast, isComplete, hasCollapsibleWork, hasUnresolvedTool, hasActiveTool, chatRunning, }: {
120
127
  isLast: boolean;
@@ -728,7 +728,7 @@ export function shouldShowMissingFinalResponse({ isCurrentTurnRunning, serverRun
728
728
  * still alive server-side. Requiring the shape to hold for a beat keeps the
729
729
  * notice off the screen for those gaps without hiding a real stop for long.
730
730
  */
731
- const MISSING_FINAL_RESPONSE_SETTLE_MS = 250;
731
+ export const MISSING_FINAL_RESPONSE_SETTLE_MS = 3_000;
732
732
  export function useSettledFlag(active, delayMs) {
733
733
  const [settled, setSettled] = useState(false);
734
734
  useEffect(() => {
@@ -120,6 +120,9 @@ function isMissingLlmProviderRunError(info) {
120
120
  ((code === "missing_credentials" || code === "missing_api_key") &&
121
121
  !text.trim()));
122
122
  }
123
+ function isDesktopChatRelayRunError(info) {
124
+ return [info.message, info.details].some((value) => typeof value === "string" && /desktop app chat relay failed/i.test(value));
125
+ }
123
126
  // ─── BuilderConnectCta ────────────────────────────────────────────────────────
124
127
  // Renders a single row with left-aligned copy and a right-aligned action.
125
128
  // Click opens the Builder OAuth popup via the shared
@@ -210,7 +213,7 @@ export function RunErrorRecoveryCard({ info, onContinue, onRetry, onFork, onDism
210
213
  });
211
214
  const canRecover = info.recoverable === true;
212
215
  const shouldShowBuilderReconnect = isBuilderReconnectRunError(info);
213
- const shouldShowMissingProviderSetup = isMissingLlmProviderRunError(info);
216
+ const shouldShowProviderSetup = isMissingLlmProviderRunError(info) || isDesktopChatRelayRunError(info);
214
217
  const isProviderAuthError = isProviderAuthenticationError([info.message, info.details].filter(Boolean).join("\n"), info.errorCode);
215
218
  // Blocked on something the reader goes and fixes elsewhere, then comes back
216
219
  // to. Recoverable runs and email verification keep a retry path; rejected
@@ -292,7 +295,7 @@ export function RunErrorRecoveryCard({ info, onContinue, onRetry, onFork, onDism
292
295
  onDismiss();
293
296
  }
294
297
  }, [builderReconnectResolved, onDismiss]);
295
- if (shouldShowMissingProviderSetup) {
298
+ if (shouldShowProviderSetup) {
296
299
  return (_jsxs("div", { className: "w-full", children: [_jsx(BuilderSetupCard, { fullWidth: true, layout: "sidebar", onConnected: handleMissingProviderConnected }), providerConnected ? (_jsx("div", { className: "flex justify-center px-3 pt-1", children: _jsxs("button", { type: "button", onClick: handleMissingProviderRetry, disabled: retryRequested, className: "inline-flex h-8 items-center gap-1.5 rounded-md bg-foreground px-3 text-xs font-medium text-background hover:opacity-90 disabled:cursor-wait disabled:opacity-60", children: [_jsx(IconRefresh, { size: 13 }), t("agentChat.common.retry")] }) })) : null] }));
297
300
  }
298
301
  return (_jsxs("div", { className: "rounded-lg border border-amber-500/25 bg-amber-500/[0.06] p-3 text-sm", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-amber-500/10 text-amber-700 dark:text-amber-300", children: _jsx(IconAlertTriangle, { size: 14 }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "font-medium text-foreground", children: runErrorHeadline(info, {
@@ -1,6 +1,7 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { IconLoader2, IconAlertTriangle, IconCircleX, IconCheck, IconChevronRight, IconCopy, IconCode, IconBrandSlack, IconTerminal2, IconDatabase, IconSearch, IconFileCode, IconShieldCheck, IconX, } from "@tabler/icons-react";
3
3
  import React, { useState, useEffect, useCallback, useLayoutEffect, useRef, } from "react";
4
+ import { formatAgentChatContextItemsForPrompt } from "../agent-chat.js";
4
5
  import { AgentTaskCard } from "../AgentTaskCard.js";
5
6
  import { writeClipboardText } from "../clipboard.js";
6
7
  import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
@@ -12,6 +13,7 @@ import { findMcpIntegrationForToolName } from "../resources/mcp-integration-cata
12
13
  import { McpIntegrationLogo } from "../resources/McpIntegrationLogo.js";
13
14
  import { BashCell, EditCell, WriteCell, FilesChangedSummary, } from "../tool-cells/index.js";
14
15
  import { humanizeToolName, isCallAgentToolCallShadowed, isToolCallActive, } from "../tool-display.js";
16
+ import { useAgentChatContext } from "../use-agent-chat-context.js";
15
17
  import { cn } from "../utils.js";
16
18
  import { ActionChatUiSurface } from "./action-chat-ui-surface.js";
17
19
  import { SmoothMarkdownText, HighlightedCodeBlock, } from "./markdown-renderer.js";
@@ -280,6 +282,10 @@ function ApprovalAffordance({ toolName, toolCallId, approval, }) {
280
282
  }
281
283
  // ─── ToolCallDisplay ──────────────────────────────────────────────────────────
282
284
  export function ToolCallDisplay({ toolName, toolCallId, argsText, args, result, mcpApp, chatUI, isRunning, outcome, structuredMeta, activity, approval, repeatCount, isLatestRunning = isRunning, isActiveTail, }) {
285
+ const { items: agentChatContextItems } = useAgentChatContext(toolName === "connect-builder");
286
+ const builderContext = toolName === "connect-builder"
287
+ ? formatAgentChatContextItemsForPrompt(agentChatContextItems)
288
+ : "";
283
289
  const isDelegatedAgentCall = toolName === "call-agent" || toolName.startsWith("agent:");
284
290
  const effectiveIsRunning = isRunning ||
285
291
  (isDelegatedAgentCall &&
@@ -306,9 +312,9 @@ export function ToolCallDisplay({ toolName, toolCallId, argsText, args, result,
306
312
  if (toolKind === "write") {
307
313
  return wrapToolDisplay(_jsx(WriteCell, { meta: structuredMeta, isRunning: effectiveIsRunning }));
308
314
  }
309
- return wrapToolDisplay(_jsx(ToolCallDisplayGeneric, { toolName: toolName, toolCallId: toolCallId, argsText: argsText, args: args, result: result, mcpApp: mcpApp, chatUI: chatUI, isRunning: effectiveIsRunning, outcome: outcome, isActiveTail: showActiveTail, structuredMeta: structuredMeta, approval: approval, repeatCount: repeatCount }));
315
+ return wrapToolDisplay(_jsx(ToolCallDisplayGeneric, { toolName: toolName, toolCallId: toolCallId, argsText: argsText, args: args, result: result, mcpApp: mcpApp, chatUI: chatUI, isRunning: effectiveIsRunning, outcome: outcome, isActiveTail: showActiveTail, structuredMeta: structuredMeta, approval: approval, repeatCount: repeatCount, context: builderContext }));
310
316
  }
311
- function ToolCallDisplayGeneric({ toolName, toolCallId, argsText, args, result, mcpApp, chatUI, isRunning, outcome, isActiveTail, structuredMeta, approval, repeatCount, }) {
317
+ function ToolCallDisplayGeneric({ toolName, toolCallId, argsText, args, result, mcpApp, chatUI, isRunning, outcome, isActiveTail, structuredMeta, approval, repeatCount, context, }) {
312
318
  const t = useT();
313
319
  const suppressInlineOpenApp = React.useContext(SuppressInlineOpenAppContext);
314
320
  const isRawCallAgent = toolName === "call-agent";
@@ -339,7 +345,7 @@ function ToolCallDisplayGeneric({ toolName, toolCallId, argsText, args, result,
339
345
  return (_jsx(ConnectBuilderCard, { configured: !!parsed.configured, builderEnabled: parsed.builderEnabled !== false,
340
346
  // Ignore obsolete direct-auth URLs from older tool results. The
341
347
  // card fetches a fresh app-local connect URL on mount and click.
342
- connectUrl: parsed.connectUrl || "", orgName: parsed.orgName ?? null, prompt: typeof parsed.prompt === "string" ? parsed.prompt : "" }));
348
+ connectUrl: parsed.connectUrl || "", orgName: parsed.orgName ?? null, prompt: typeof parsed.prompt === "string" ? parsed.prompt : "", context: context }));
343
349
  }
344
350
  }
345
351
  catch {
@@ -80,6 +80,18 @@ const KNOWN_CHAT_ERROR_KEYS = new Map([
80
80
  "No LLM provider is connected. Open this app's Manage agent > LLM, then connect Builder.io or add a provider key.",
81
81
  "agentChat.errorMessages.noProviderConnected",
82
82
  ],
83
+ [
84
+ "No LLM provider is connected. Open this app's Manage agent > LLM, then connect Builder.io (free tier available) or add a provider key.",
85
+ "agentChat.errorMessages.noProviderConnected",
86
+ ],
87
+ [
88
+ "No LLM provider is connected. Open Settings > Agent > AI providers, then connect Builder.io or add a provider key.",
89
+ "agentChat.errorMessages.noProviderConnected",
90
+ ],
91
+ [
92
+ "No LLM provider is connected. Open Settings > Agent > AI providers, then connect Builder.io (free tier available) or add a provider key.",
93
+ "agentChat.errorMessages.noProviderConnected",
94
+ ],
83
95
  [
84
96
  "The provider behind this model rejected the request. Pick a different model, then retry.",
85
97
  "agentChat.errorMessages.builderModelUnauthorized",
@@ -1,5 +1,6 @@
1
1
  export { agentNativePath, appApiPath, appBasePath, appPath, } from "../api-path.js";
2
2
  export { useAgentRouteState, useSemanticNavigationState, type AgentRouteLocation, type SemanticNavigationCommandEnvelope, type UseAgentRouteStateOptions, type UseAgentRouteStateResult, type UseSemanticNavigationStateOptions, type UseSemanticNavigationStateResult, } from "../route-state.js";
3
+ export { AGENT_NATIVE_WORKSPACE_APP_ROUTE_MESSAGE_TYPE, postAgentNativeWorkspaceAppRoute, type AgentNativeWorkspaceAppRouteMessage, } from "../workspace-app-navigation.js";
3
4
  export { COMMAND_MENU_OPEN_EVENT, CommandMenu, openAgentSidebar, openAgentSettings, openCommandMenu, submitToAgent, useCommandMenuShortcut, type CommandMenuProps, type CommandMenuDoc, type CommandDocsGroupProps, type CommandGroupProps, type CommandItemProps, type CommandShortcutProps, } from "../CommandMenu.js";
4
5
  export { buildOpenRouteLink, buildOpenRoutePath, buildLegacyAgentSettingsRoute, buildResourceRoute, buildSettingsRoute, buildStandardAppRoute, buildTeamRoute, createStandardOpenPathResolver, STANDARD_APP_ROUTES, STANDARD_SETTINGS_TABS, type BuildResourceRouteOptions, type BuildStandardAppRouteOptions, type NavigationLink, type NavigationTarget, type StandardAppRouteId, type StandardOpenPathResolverOptions, type StandardOpenPathRoute, type StandardSettingsTabId, } from "../../navigation/index.js";
5
6
  export { postNavigate, isInAgentEmbed, AGENT_NAVIGATE_MESSAGE_TYPE, type AgentNavigateMessage, } from "../embed.js";
@@ -1,5 +1,6 @@
1
1
  export { agentNativePath, appApiPath, appBasePath, appPath, } from "../api-path.js";
2
2
  export { useAgentRouteState, useSemanticNavigationState, } from "../route-state.js";
3
+ export { AGENT_NATIVE_WORKSPACE_APP_ROUTE_MESSAGE_TYPE, postAgentNativeWorkspaceAppRoute, } from "../workspace-app-navigation.js";
3
4
  export { COMMAND_MENU_OPEN_EVENT, CommandMenu, openAgentSidebar, openAgentSettings, openCommandMenu, submitToAgent, useCommandMenuShortcut, } from "../CommandMenu.js";
4
5
  export { buildOpenRouteLink, buildOpenRoutePath, buildLegacyAgentSettingsRoute, buildResourceRoute, buildSettingsRoute, buildStandardAppRoute, buildTeamRoute, createStandardOpenPathResolver, STANDARD_APP_ROUTES, STANDARD_SETTINGS_TABS, } from "../../navigation/index.js";
5
6
  export { postNavigate, isInAgentEmbed, AGENT_NAVIGATE_MESSAGE_TYPE, } from "../embed.js";
@@ -4,6 +4,7 @@ import { useLocation, useNavigate, } from "react-router";
4
4
  import { isWorkspaceAppPath } from "./api-path.js";
5
5
  import { deleteClientAppState, readClientAppState, setClientAppState, } from "./application-state.js";
6
6
  import { navigateWithAgentChatViewTransition, } from "./chat-view-transition.js";
7
+ import { postAgentNativeWorkspaceAppRoute } from "./workspace-app-navigation.js";
7
8
  const SAFE_BROWSER_TAB_ID_RE = /^[A-Za-z0-9_-]{1,96}$/;
8
9
  function normalizeBrowserTabId(browserTabId) {
9
10
  if (typeof browserTabId !== "string")
@@ -195,6 +196,11 @@ export function useAgentRouteState(options) {
195
196
  const location = useLocation();
196
197
  const navigate = useNavigate();
197
198
  const browserTabId = useMemo(() => normalizeBrowserTabId(options.browserTabId), [options.browserTabId]);
199
+ useEffect(() => {
200
+ if (options.enabled === false)
201
+ return;
202
+ postAgentNativeWorkspaceAppRoute(`${location.pathname}${location.search}${location.hash}`);
203
+ }, [location.hash, location.pathname, location.search, options.enabled]);
198
204
  const navigationKeys = useMemo(() => {
199
205
  const scopedKey = appStateKeyForBrowserTab(navigationKey, browserTabId);
200
206
  const keys = [scopedKey];
@@ -0,0 +1,10 @@
1
+ export declare const AGENT_NATIVE_WORKSPACE_APP_ROUTE_MESSAGE_TYPE: "agent-native:workspace-app-route";
2
+ export interface AgentNativeWorkspaceAppRouteMessage {
3
+ type: typeof AGENT_NATIVE_WORKSPACE_APP_ROUTE_MESSAGE_TYPE;
4
+ path: string;
5
+ }
6
+ /**
7
+ * Report an app-local route to its immediate embedding host. The host checks
8
+ * the iframe source and origin before applying the route to its own URL.
9
+ */
10
+ export declare function postAgentNativeWorkspaceAppRoute(path: string): boolean;
@@ -0,0 +1,38 @@
1
+ export const AGENT_NATIVE_WORKSPACE_APP_ROUTE_MESSAGE_TYPE = "agent-native:workspace-app-route";
2
+ function normalizeWorkspaceAppRoute(path) {
3
+ if (typeof path !== "string" ||
4
+ !path.startsWith("/") ||
5
+ path.startsWith("//")) {
6
+ return null;
7
+ }
8
+ if (/[\u0000-\u001f\u007f]/.test(path))
9
+ return null;
10
+ try {
11
+ const url = new URL(path, "http://agent-native.invalid");
12
+ return `${url.pathname}${url.search}${url.hash}`;
13
+ }
14
+ catch {
15
+ // coercion-ok: malformed route input is not reported to the host.
16
+ return null;
17
+ }
18
+ }
19
+ /**
20
+ * Report an app-local route to its immediate embedding host. The host checks
21
+ * the iframe source and origin before applying the route to its own URL.
22
+ */
23
+ export function postAgentNativeWorkspaceAppRoute(path) {
24
+ if (typeof window === "undefined")
25
+ return false;
26
+ const parentWindow = window.parent;
27
+ if (!parentWindow || parentWindow === window)
28
+ return false;
29
+ const normalizedPath = normalizeWorkspaceAppRoute(path);
30
+ if (!normalizedPath)
31
+ return false;
32
+ const message = {
33
+ type: AGENT_NATIVE_WORKSPACE_APP_ROUTE_MESSAGE_TYPE,
34
+ path: normalizedPath,
35
+ };
36
+ parentWindow.postMessage(message, "*");
37
+ return true;
38
+ }