@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/actions/bigquery-table-info.ts +3 -2
- package/corpus/templates/analytics/actions/search-bigquery-schema.ts +130 -4
- package/corpus/templates/analytics/agent-native.config.ts +1 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +36 -23
- package/corpus/templates/analytics/app/lib/dashboard-visibility.ts +41 -0
- package/corpus/templates/analytics/app/pages/DashboardOverview.tsx +34 -9
- package/corpus/templates/analytics/changelog/2026-08-21-dashboard-filters-keep-other-users-dashboards-out-of-mine.md +6 -0
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +14 -3
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +33 -3
- package/corpus/templates/brain/server/lib/brain.ts +27 -2
- package/corpus/templates/clips/actions/stop-meeting-recording.ts +87 -6
- package/corpus/templates/clips/app/components/library/search-bar.tsx +1 -1
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +3 -4
- package/corpus/templates/design/app/components/design/EditPanel.tsx +15 -47
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +3 -3
- package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +0 -7
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +2 -4
- package/corpus/templates/design/app/i18n-data.ts +10 -10
- package/corpus/templates/design/app/pages/DesignEditor.tsx +22 -22
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +39 -8
- package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -1
- package/corpus/templates/design/app/pages/Index.tsx +1 -1
- package/corpus/templates/design/app/pages/design-editor/preview-navigation.ts +25 -0
- package/corpus/templates/mail/app/hooks/use-emails.ts +13 -3
- package/dist/agent/engine/credential-errors.d.ts +1 -1
- package/dist/agent/engine/credential-errors.js +2 -2
- package/dist/client/AssistantChat.js +47 -15
- package/dist/client/ConnectBuilderCard.d.ts +3 -1
- package/dist/client/ConnectBuilderCard.js +6 -3
- package/dist/client/chat/message-components.d.ts +7 -0
- package/dist/client/chat/message-components.js +1 -1
- package/dist/client/chat/run-recovery.js +5 -2
- package/dist/client/chat/tool-call-display.js +9 -3
- package/dist/client/error-format.js +12 -0
- package/dist/client/navigation/index.d.ts +1 -0
- package/dist/client/navigation/index.js +1 -0
- package/dist/client/route-state.js +6 -0
- package/dist/client/workspace-app-navigation.d.ts +10 -0
- package/dist/client/workspace-app-navigation.js +38 -0
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +13 -0
- package/dist/deploy/build.js +51 -0
- package/dist/localization/core-messages/ar-SA.js +1 -1
- package/dist/localization/core-messages/de-DE.js +1 -1
- package/dist/localization/core-messages/en-US.d.ts +1 -1
- package/dist/localization/core-messages/en-US.js +1 -1
- package/dist/localization/core-messages/es-ES.js +1 -1
- package/dist/localization/core-messages/fr-FR.js +1 -1
- package/dist/localization/core-messages/hi-IN.js +1 -1
- package/dist/localization/core-messages/ja-JP.js +1 -1
- package/dist/localization/core-messages/ko-KR.js +1 -1
- package/dist/localization/core-messages/pt-BR.js +1 -1
- package/dist/localization/core-messages/zh-CN.js +1 -1
- package/dist/localization/core-messages/zh-TW.js +1 -1
- package/dist/localization/core-messages.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
- package/dist/provider-api/actions/provider-api.d.ts +13 -13
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/db/check-scoping.js +9 -6
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/builder-browser.d.ts +4 -0
- package/dist/server/builder-browser.js +23 -1
- package/dist/server/core-routes-plugin.js +13 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +3 -3
- /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
|
-
<
|
|
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-
|
|
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-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
329
|
+
const runLooksClear = !info?.active ||
|
|
327
330
|
info?.status !== "running" ||
|
|
328
|
-
activeRunLooksStale(info)
|
|
329
|
-
|
|
330
|
-
|
|
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
|
|
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({
|
|
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 =
|
|
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
|
|
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 (
|
|
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
|
+
}
|