@agent-native/dispatch 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/provider-api-register.d.ts +14 -14
- package/dist/components/workspace-app-host.d.ts.map +1 -1
- package/dist/components/workspace-app-host.js +77 -4
- package/dist/components/workspace-app-host.js.map +1 -1
- package/package.json +3 -3
- package/src/components/workspace-app-host.spec.tsx +57 -0
- package/src/components/workspace-app-host.tsx +109 -2
|
@@ -47,9 +47,9 @@ declare const _default: import("node_modules/@agent-native/core/dist").ActionDef
|
|
|
47
47
|
notes?: string | undefined;
|
|
48
48
|
scope?: "org" | "user" | undefined;
|
|
49
49
|
}, {
|
|
50
|
-
deleted?: undefined;
|
|
51
50
|
id?: undefined;
|
|
52
|
-
|
|
51
|
+
message?: undefined;
|
|
52
|
+
deleted?: undefined;
|
|
53
53
|
providers: {
|
|
54
54
|
id: string;
|
|
55
55
|
label: string;
|
|
@@ -60,45 +60,45 @@ declare const _default: import("node_modules/@agent-native/core/dist").ActionDef
|
|
|
60
60
|
updatedAt: number;
|
|
61
61
|
}[];
|
|
62
62
|
count: number;
|
|
63
|
+
provider?: undefined;
|
|
63
64
|
found?: undefined;
|
|
64
65
|
registered?: undefined;
|
|
65
66
|
label?: undefined;
|
|
66
|
-
message?: undefined;
|
|
67
67
|
} | {
|
|
68
|
-
deleted?: undefined;
|
|
69
68
|
id?: undefined;
|
|
70
|
-
|
|
69
|
+
message?: undefined;
|
|
71
70
|
count?: undefined;
|
|
71
|
+
deleted?: undefined;
|
|
72
|
+
providers?: undefined;
|
|
72
73
|
found: boolean;
|
|
73
74
|
provider: import("node_modules/@agent-native/core/dist/provider-api").CustomProviderConfig;
|
|
74
75
|
registered?: undefined;
|
|
75
76
|
label?: undefined;
|
|
76
|
-
message?: undefined;
|
|
77
77
|
} | {
|
|
78
|
+
message?: undefined;
|
|
79
|
+
count?: undefined;
|
|
78
80
|
deleted?: undefined;
|
|
79
|
-
provider?: undefined;
|
|
80
81
|
providers?: undefined;
|
|
81
|
-
|
|
82
|
+
provider?: undefined;
|
|
82
83
|
found: boolean;
|
|
83
84
|
id: string;
|
|
84
85
|
registered?: undefined;
|
|
85
86
|
label?: undefined;
|
|
86
|
-
message?: undefined;
|
|
87
87
|
} | {
|
|
88
|
-
|
|
89
|
-
providers?: undefined;
|
|
88
|
+
message?: undefined;
|
|
90
89
|
count?: undefined;
|
|
90
|
+
providers?: undefined;
|
|
91
|
+
provider?: undefined;
|
|
91
92
|
found?: undefined;
|
|
92
93
|
deleted: boolean;
|
|
93
94
|
id: string;
|
|
94
95
|
registered?: undefined;
|
|
95
96
|
label?: undefined;
|
|
96
|
-
message?: undefined;
|
|
97
97
|
} | {
|
|
98
|
+
count?: undefined;
|
|
98
99
|
deleted?: undefined;
|
|
99
|
-
provider?: undefined;
|
|
100
100
|
providers?: undefined;
|
|
101
|
-
|
|
101
|
+
provider?: undefined;
|
|
102
102
|
found?: undefined;
|
|
103
103
|
registered: boolean;
|
|
104
104
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-app-host.d.ts","sourceRoot":"","sources":["../../src/components/workspace-app-host.tsx"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,sCAAsC,CAAC;
|
|
1
|
+
{"version":3,"file":"workspace-app-host.d.ts","sourceRoot":"","sources":["../../src/components/workspace-app-host.tsx"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,sCAAsC,CAAC;AAmC9C,UAAU,iBAAiB;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,CAAC;CACnB;AAoCD,wBAAgB,+BAA+B,CAC7C,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GACX,iBAAiB,CAEnB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,UAAU,sBAAsB;IAC9B,GAAG,EAAE,oBAAoB,CAAC;IAC1B,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,aAAa,CAAC;CACtB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,GAAG,EACH,SAAS,EACT,WAAmB,EACnB,IAA2B,GAC5B,EAAE,sBAAsB,mDAuNxB;AAED,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,mDAwH7D;AAID,wBAAgB,qBAAqB,CAAC,EACpC,WAAW,GACZ,EAAE;IACD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,mDAwCA"}
|
|
@@ -5,8 +5,9 @@ import { useFeatureFlag } from "@agent-native/core/client/feature-flags";
|
|
|
5
5
|
import { useActionMutation, useActionQuery, } from "@agent-native/core/client/hooks";
|
|
6
6
|
import { useT } from "@agent-native/core/client/i18n";
|
|
7
7
|
import { withBuilderUtmTrackingParams } from "@agent-native/core/shared/builder-link-tracking";
|
|
8
|
-
import { IconArrowLeft, IconClockHour4 } from "@tabler/icons-react";
|
|
9
|
-
import {
|
|
8
|
+
import { IconArrowLeft, IconArrowUpRight, IconClockHour4, IconLock, } from "@tabler/icons-react";
|
|
9
|
+
import { useTheme } from "next-themes";
|
|
10
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
10
11
|
import { Link } from "react-router";
|
|
11
12
|
import { isEmbedSessionExpiredMessage } from "../lib/embed-session-recovery.js";
|
|
12
13
|
import { workspaceAppDirectHref, workspaceAppEmbedTarget, workspaceAppHref, } from "../lib/workspace-apps.js";
|
|
@@ -15,14 +16,67 @@ import { ActionQueryError } from "./action-query-error.js";
|
|
|
15
16
|
import { Badge } from "./ui/badge.js";
|
|
16
17
|
import { Button } from "./ui/button.js";
|
|
17
18
|
import { Skeleton } from "./ui/skeleton.js";
|
|
19
|
+
function resolveWorkspaceAppAuthState(rawUrl) {
|
|
20
|
+
if (!rawUrl)
|
|
21
|
+
return "unknown";
|
|
22
|
+
try {
|
|
23
|
+
const lastSegment = new URL(rawUrl, window.location.origin).pathname
|
|
24
|
+
.split("/")
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.at(-1)
|
|
27
|
+
?.toLowerCase();
|
|
28
|
+
if (lastSegment === "sign-in" ||
|
|
29
|
+
lastSegment === "login" ||
|
|
30
|
+
lastSegment === "signup") {
|
|
31
|
+
return "unauthenticated";
|
|
32
|
+
}
|
|
33
|
+
return "authenticated";
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return "unknown";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function buildWorkspaceAppThemeUpdate(theme) {
|
|
40
|
+
return {
|
|
41
|
+
type: "agent-native-theme-update",
|
|
42
|
+
theme,
|
|
43
|
+
isDark: theme === "dark",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
18
46
|
export function buildChatFirstEmbedSessionInput(appId, path) {
|
|
19
47
|
return { app: appId, path, chrome: "minimal" };
|
|
20
48
|
}
|
|
21
49
|
export function WorkspaceAppFrame({ app, embedPath, chatSidebar = false, copy = defaultChatFirstCopy, }) {
|
|
50
|
+
const { resolvedTheme } = useTheme();
|
|
51
|
+
const theme = resolvedTheme === "dark" || resolvedTheme === "light"
|
|
52
|
+
? resolvedTheme
|
|
53
|
+
: typeof document !== "undefined" &&
|
|
54
|
+
document.documentElement.classList.contains("dark")
|
|
55
|
+
? "dark"
|
|
56
|
+
: "light";
|
|
22
57
|
const [embedUrl, setEmbedUrl] = useState(null);
|
|
23
58
|
const [embedError, setEmbedError] = useState(null);
|
|
24
59
|
const [embedAttempt, setEmbedAttempt] = useState(0);
|
|
60
|
+
const [authState, setAuthState] = useState("unknown");
|
|
25
61
|
const embedFrameRef = useRef(null);
|
|
62
|
+
const postThemeToFrame = useCallback(() => {
|
|
63
|
+
embedFrameRef.current?.contentWindow?.postMessage(buildWorkspaceAppThemeUpdate(theme), "*");
|
|
64
|
+
}, [theme]);
|
|
65
|
+
const handleFrameLoad = useCallback(() => {
|
|
66
|
+
postThemeToFrame();
|
|
67
|
+
const frame = embedFrameRef.current;
|
|
68
|
+
let frameUrl = embedUrl;
|
|
69
|
+
try {
|
|
70
|
+
frameUrl = frame?.contentWindow?.location.href ?? frameUrl;
|
|
71
|
+
// coercion-ok: Cross-origin frames cannot expose location; their auth state arrives by message.
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Cross-origin app frames report their auth state through postMessage.
|
|
75
|
+
}
|
|
76
|
+
const nextAuthState = resolveWorkspaceAppAuthState(frameUrl);
|
|
77
|
+
if (nextAuthState !== "unknown")
|
|
78
|
+
setAuthState(nextAuthState);
|
|
79
|
+
}, [embedUrl, postThemeToFrame]);
|
|
26
80
|
const workspaceSsoEnabled = useFeatureFlag(DISPATCH_WORKSPACE_SSO_FLAG.key);
|
|
27
81
|
const createEmbedSession = useActionMutation("create_embed_session", {
|
|
28
82
|
skipActionQueryInvalidation: true,
|
|
@@ -54,6 +108,7 @@ export function WorkspaceAppFrame({ app, embedPath, chatSidebar = false, copy =
|
|
|
54
108
|
let cancelled = false;
|
|
55
109
|
setEmbedUrl(null);
|
|
56
110
|
setEmbedError(null);
|
|
111
|
+
setAuthState("unknown");
|
|
57
112
|
const createSession = workspaceSsoEnabled
|
|
58
113
|
? createWorkspaceSsoEmbedSession
|
|
59
114
|
: createEmbedSession;
|
|
@@ -93,13 +148,31 @@ export function WorkspaceAppFrame({ app, embedPath, chatSidebar = false, copy =
|
|
|
93
148
|
window.addEventListener("message", handleEmbedSessionExpired);
|
|
94
149
|
return () => window.removeEventListener("message", handleEmbedSessionExpired);
|
|
95
150
|
}, [embedUrl]);
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
const handleAuthState = (event) => {
|
|
153
|
+
const frame = embedFrameRef.current;
|
|
154
|
+
if (!frame || event.source !== frame.contentWindow)
|
|
155
|
+
return;
|
|
156
|
+
if (event.data?.type !== "agentNative.authState")
|
|
157
|
+
return;
|
|
158
|
+
const status = event.data.data?.status;
|
|
159
|
+
if (status === "authenticated" || status === "unauthenticated") {
|
|
160
|
+
setAuthState(status);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
window.addEventListener("message", handleAuthState);
|
|
164
|
+
return () => window.removeEventListener("message", handleAuthState);
|
|
165
|
+
}, []);
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
postThemeToFrame();
|
|
168
|
+
}, [embedUrl, postThemeToFrame]);
|
|
96
169
|
const appPane = (_jsx(ChatFirstAppPane, { app: app, status: embedUrl
|
|
97
170
|
? "ready"
|
|
98
171
|
: embedError
|
|
99
172
|
? "error"
|
|
100
173
|
: embedInput
|
|
101
174
|
? "loading"
|
|
102
|
-
: "unresolved", embedUrl: embedUrl, errorMessage: embedError?.message, onRetry: embedInput ? () => setEmbedAttempt((attempt) => attempt + 1) : undefined, renderEmbed: ({ url, title }) => (_jsx("iframe", { "data-dispatch-workspace-app-frame": true, src: url, title: title ?? app.name, ref: embedFrameRef, referrerPolicy: "no-referrer", allow: "clipboard-read; clipboard-write", className: "h-full w-full border-0 bg-background" }, url + ":" + embedAttempt)), copy: copy }));
|
|
175
|
+
: "unresolved", embedUrl: embedUrl, errorMessage: embedError?.message, onRetry: embedInput ? () => setEmbedAttempt((attempt) => attempt + 1) : undefined, renderEmbed: ({ url, title }) => (_jsx("iframe", { "data-dispatch-workspace-app-frame": true, src: url, title: title ?? app.name, ref: embedFrameRef, onLoad: handleFrameLoad, referrerPolicy: "no-referrer", allow: "clipboard-read; clipboard-write", className: "h-full w-full border-0 bg-background" }, url + ":" + embedAttempt)), copy: copy }));
|
|
103
176
|
if (!chatSidebar)
|
|
104
177
|
return appPane;
|
|
105
178
|
return (_jsx(AgentSidebar, { position: "left", defaultOpen: true, openStorageKey: "dispatch-app-chat", storageKey: `dispatch-app-chat:${app.id}`, scope: {
|
|
@@ -107,7 +180,7 @@ export function WorkspaceAppFrame({ app, embedPath, chatSidebar = false, copy =
|
|
|
107
180
|
id: app.id,
|
|
108
181
|
label: app.name,
|
|
109
182
|
contextKey: `workspace-app:${app.id}`,
|
|
110
|
-
}, agentChatSurface: "app", showTabBar: true, suppressInlineOpenApp: true, dynamicSuggestions: false, suggestions: [], emptyStateText: `Ask about ${app.name}`, children: appPane }));
|
|
183
|
+
}, agentChatSurface: "app", showTabBar: true, suppressInlineOpenApp: true, dynamicSuggestions: false, suggestions: [], emptyStateText: `Ask about ${app.name}`, composerSlot: authState === "unauthenticated" ? (_jsx("div", { className: "flex shrink-0 items-center px-3 pb-1", children: _jsxs("button", { type: "button", "data-dispatch-app-sign-in": true, "aria-label": `Sign in to ${app.name} on the right`, title: `Sign in to ${app.name} on the right`, onClick: () => embedFrameRef.current?.focus(), className: "inline-flex h-6 shrink-0 items-center gap-1 rounded-full border border-border/70 bg-background/60 px-2 text-[11px] font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground", children: [_jsx(IconLock, { size: 12, stroke: 1.8 }), _jsx("span", { children: "Sign in on the right" }), _jsx(IconArrowUpRight, { size: 12, stroke: 1.8 })] }) })) : null, children: appPane }));
|
|
111
184
|
}
|
|
112
185
|
export function WorkspaceAppHost({ appId }) {
|
|
113
186
|
const t = useT();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-app-host.js","sourceRoot":"","sources":["../../src/components/workspace-app-host.tsx"],"names":[],"mappings":"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","sourcesContent":["import { AgentSidebar } from \"@agent-native/core/client/agent-chat\";\nimport {\n ChatFirstAppPane,\n defaultChatFirstCopy,\n type ChatFirstCopy,\n} from \"@agent-native/core/client/chat-first\";\nimport { useFeatureFlag } from \"@agent-native/core/client/feature-flags\";\nimport {\n useActionMutation,\n useActionQuery,\n} from \"@agent-native/core/client/hooks\";\nimport { useT } from \"@agent-native/core/client/i18n\";\nimport { withBuilderUtmTrackingParams } from \"@agent-native/core/shared/builder-link-tracking\";\nimport { IconArrowLeft, IconClockHour4 } from \"@tabler/icons-react\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { Link } from \"react-router\";\n\nimport { isEmbedSessionExpiredMessage } from \"../lib/embed-session-recovery\";\nimport {\n workspaceAppDirectHref,\n workspaceAppEmbedTarget,\n workspaceAppHref,\n type WorkspaceAppSummary,\n} from \"../lib/workspace-apps\";\nimport { DISPATCH_WORKSPACE_SSO_FLAG } from \"../shared/feature-flags\";\nimport { ActionQueryError } from \"./action-query-error\";\nimport { Badge } from \"./ui/badge\";\nimport { Button } from \"./ui/button\";\nimport { Skeleton } from \"./ui/skeleton\";\n\ninterface EmbedSessionResult {\n startUrl: string;\n}\n\ninterface EmbedSessionInput {\n app?: string;\n path?: string;\n url?: string;\n chrome: \"minimal\";\n}\n\nexport function buildChatFirstEmbedSessionInput(\n appId: string,\n path: string,\n): EmbedSessionInput {\n return { app: appId, path, chrome: \"minimal\" };\n}\n\nexport interface WorkspaceAppFrameApp {\n id: string;\n name: string;\n path?: string | null;\n url?: string | null;\n}\n\ninterface WorkspaceAppFrameProps {\n app: WorkspaceAppFrameApp;\n /** Chat-first app tabs use their own route while standalone hosts use app metadata. */\n embedPath?: string;\n /** Chat-first app surfaces own the parent chat rail around the iframe. */\n chatSidebar?: boolean;\n copy?: ChatFirstCopy;\n}\n\nexport function WorkspaceAppFrame({\n app,\n embedPath,\n chatSidebar = false,\n copy = defaultChatFirstCopy,\n}: WorkspaceAppFrameProps) {\n const [embedUrl, setEmbedUrl] = useState<string | null>(null);\n const [embedError, setEmbedError] = useState<Error | null>(null);\n const [embedAttempt, setEmbedAttempt] = useState(0);\n const embedFrameRef = useRef<HTMLIFrameElement>(null);\n const workspaceSsoEnabled = useFeatureFlag(DISPATCH_WORKSPACE_SSO_FLAG.key);\n const createEmbedSession = useActionMutation<\n EmbedSessionResult,\n EmbedSessionInput\n >(\"create_embed_session\", {\n skipActionQueryInvalidation: true,\n });\n const createWorkspaceSsoEmbedSession = useActionMutation<\n EmbedSessionResult,\n EmbedSessionInput\n >(\"create-workspace-app-embed-session\", {\n skipActionQueryInvalidation: true,\n });\n const appHref = workspaceAppHref({\n id: app.id,\n name: app.name,\n path: app.path ?? \"\",\n url: app.url,\n });\n const embedInput = useMemo<EmbedSessionInput | null>(() => {\n if (embedPath !== undefined) {\n return buildChatFirstEmbedSessionInput(app.id, embedPath);\n }\n if (!appHref) return null;\n return {\n app: app.id,\n ...workspaceAppEmbedTarget({ path: app.path ?? \"\", url: app.url }),\n chrome: \"minimal\",\n };\n }, [app.id, app.path, app.url, appHref, embedPath]);\n\n useEffect(() => {\n if (!embedInput) return;\n let cancelled = false;\n setEmbedUrl(null);\n setEmbedError(null);\n const createSession = workspaceSsoEnabled\n ? createWorkspaceSsoEmbedSession\n : createEmbedSession;\n void createSession\n .mutateAsync(embedInput)\n .then((result) => {\n if (!cancelled) setEmbedUrl(result.startUrl);\n })\n .catch((cause: unknown) => {\n if (cancelled) return;\n setEmbedUrl(\n workspaceAppDirectHref(\n { path: app.path ?? \"\", url: app.url },\n embedPath ?? \"/\",\n ),\n );\n setEmbedError(\n cause instanceof Error ? cause : new Error(String(cause)),\n );\n });\n return () => {\n cancelled = true;\n };\n }, [\n app.id,\n app.path,\n app.url,\n createEmbedSession.mutateAsync,\n createWorkspaceSsoEmbedSession.mutateAsync,\n embedInput,\n embedPath,\n embedAttempt,\n workspaceSsoEnabled,\n ]);\n\n useEffect(() => {\n const handleEmbedSessionExpired = (event: MessageEvent) => {\n if (\n !isEmbedSessionExpiredMessage(event, embedFrameRef.current, embedUrl)\n ) {\n return;\n }\n setEmbedAttempt((attempt) => attempt + 1);\n };\n\n window.addEventListener(\"message\", handleEmbedSessionExpired);\n return () =>\n window.removeEventListener(\"message\", handleEmbedSessionExpired);\n }, [embedUrl]);\n\n const appPane = (\n <ChatFirstAppPane\n app={app}\n status={\n embedUrl\n ? \"ready\"\n : embedError\n ? \"error\"\n : embedInput\n ? \"loading\"\n : \"unresolved\"\n }\n embedUrl={embedUrl}\n errorMessage={embedError?.message}\n onRetry={\n embedInput ? () => setEmbedAttempt((attempt) => attempt + 1) : undefined\n }\n renderEmbed={({ url, title }) => (\n <iframe\n key={url + \":\" + embedAttempt}\n data-dispatch-workspace-app-frame\n src={url}\n title={title ?? app.name}\n ref={embedFrameRef}\n referrerPolicy=\"no-referrer\"\n allow=\"clipboard-read; clipboard-write\"\n className=\"h-full w-full border-0 bg-background\"\n />\n )}\n copy={copy}\n />\n );\n\n if (!chatSidebar) return appPane;\n\n return (\n <AgentSidebar\n position=\"left\"\n defaultOpen\n openStorageKey=\"dispatch-app-chat\"\n storageKey={`dispatch-app-chat:${app.id}`}\n scope={{\n type: \"workspace-app\",\n id: app.id,\n label: app.name,\n contextKey: `workspace-app:${app.id}`,\n }}\n agentChatSurface=\"app\"\n showTabBar\n suppressInlineOpenApp\n dynamicSuggestions={false}\n suggestions={[]}\n emptyStateText={`Ask about ${app.name}`}\n >\n {appPane}\n </AgentSidebar>\n );\n}\n\nexport function WorkspaceAppHost({ appId }: { appId?: string }) {\n const t = useT();\n const appsQuery = useActionQuery(\"list-workspace-apps\", {\n includeAgentCards: false,\n });\n const { data: apps = [], isLoading } = appsQuery;\n const app = useMemo(\n () =>\n (apps as WorkspaceAppSummary[]).find((item) => item.id === appId) ?? null,\n [appId, apps],\n );\n\n if (appsQuery.isError) {\n return (\n <div className=\"flex h-full min-h-0 items-center justify-center p-6\">\n <div className=\"w-full max-w-2xl\">\n <ActionQueryError\n error={appsQuery.error}\n onRetry={() => void appsQuery.refetch()}\n />\n </div>\n </div>\n );\n }\n\n if (isLoading && !app) {\n return (\n <div className=\"flex h-full min-h-0 items-center justify-center p-6\">\n <div className=\"w-full max-w-2xl space-y-3 rounded-xl border bg-card p-6\">\n <Skeleton className=\"h-5 w-48\" />\n <Skeleton className=\"h-4 w-full\" />\n <Skeleton className=\"h-4 w-2/3\" />\n </div>\n </div>\n );\n }\n\n if (!app) {\n return (\n <div className=\"flex h-full min-h-0 items-center justify-center p-6\">\n <div className=\"w-full max-w-2xl rounded-xl border bg-card p-6\">\n <Button asChild size=\"sm\" variant=\"ghost\" className=\"-ml-2 mb-4\">\n <Link to=\"/apps\">\n <IconArrowLeft size={15} className=\"mr-1.5\" />\n {t(\"dispatch.nav.apps\")}\n </Link>\n </Button>\n <div className=\"space-y-3\">\n <h2 className=\"text-base font-semibold text-foreground\">\n {t(\"dispatch.pages.appNotFound\")}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t(\"dispatch.pages.pageNotFoundDescription\")}\n </p>\n </div>\n </div>\n </div>\n );\n }\n\n if (app.status === \"pending\") {\n return (\n <div className=\"flex h-full min-h-0 items-center justify-center p-6\">\n <div className=\"w-full max-w-2xl rounded-xl border bg-card p-6\">\n <Button asChild size=\"sm\" variant=\"ghost\" className=\"-ml-2 mb-4\">\n <Link to=\"/apps\">\n <IconArrowLeft size={15} className=\"mr-1.5\" />\n {t(\"dispatch.nav.apps\")}\n </Link>\n </Button>\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-base font-semibold text-foreground\">\n {app.name}\n </h2>\n <Badge\n variant=\"outline\"\n className=\"gap-1 border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300\"\n >\n <IconClockHour4 size={12} />\n {t(\"dispatch.pages.building\")}\n </Badge>\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {t(\"dispatch.pages.appBuildingPrefix\")}{\" \"}\n <span className=\"font-mono text-foreground\">{app.path}</span>{\" \"}\n {t(\"dispatch.pages.appBuildingSuffix\")}\n </p>\n {app.builderUrl ? (\n <Button asChild>\n <a\n href={withBuilderUtmTrackingParams(app.builderUrl, {\n campaign: \"product\",\n content: \"dispatch_branch\",\n })}\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n {t(\"dispatch.pages.openBuilderBranch\", {\n defaultValue: \"Open in Builder\",\n })}\n </a>\n </Button>\n ) : null}\n </div>\n </div>\n </div>\n );\n }\n\n return (\n <div\n data-dispatch-workspace-app-host\n className=\"flex h-full min-h-0 flex-col bg-background\"\n >\n <div className=\"min-h-0 flex-1 bg-muted/20\">\n <WorkspaceAppFrame app={app} />\n </div>\n </div>\n );\n}\n\nconst MAX_KEEP_ALIVE_APPS = 3;\n\nexport function WorkspaceAppKeepAlive({\n activeAppId,\n}: {\n activeAppId: string | null;\n}) {\n const [visitedAppIds, setVisitedAppIds] = useState<string[]>(() =>\n activeAppId ? [activeAppId] : [],\n );\n\n useEffect(() => {\n if (!activeAppId) return;\n setVisitedAppIds((current) =>\n [activeAppId, ...current.filter((appId) => appId !== activeAppId)].slice(\n 0,\n MAX_KEEP_ALIVE_APPS,\n ),\n );\n }, [activeAppId]);\n\n const renderedAppIds =\n activeAppId && !visitedAppIds.includes(activeAppId)\n ? [activeAppId, ...visitedAppIds]\n : visitedAppIds;\n\n return (\n <div\n data-dispatch-workspace-app-cache\n className={activeAppId ? \"absolute inset-0 overflow-hidden\" : \"hidden\"}\n >\n {renderedAppIds.map((appId) => {\n const active = appId === activeAppId;\n return (\n <div\n key={appId}\n data-dispatch-workspace-app-cache-entry={appId}\n aria-hidden={!active}\n className={active ? \"h-full min-h-0\" : \"hidden\"}\n >\n <WorkspaceAppHost appId={appId} />\n </div>\n );\n })}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"workspace-app-host.js","sourceRoot":"","sources":["../../src/components/workspace-app-host.tsx"],"names":[],"mappings":"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","sourcesContent":["import { AgentSidebar } from \"@agent-native/core/client/agent-chat\";\nimport {\n ChatFirstAppPane,\n defaultChatFirstCopy,\n type ChatFirstCopy,\n} from \"@agent-native/core/client/chat-first\";\nimport { useFeatureFlag } from \"@agent-native/core/client/feature-flags\";\nimport {\n useActionMutation,\n useActionQuery,\n} from \"@agent-native/core/client/hooks\";\nimport { useT } from \"@agent-native/core/client/i18n\";\nimport { withBuilderUtmTrackingParams } from \"@agent-native/core/shared/builder-link-tracking\";\nimport {\n IconArrowLeft,\n IconArrowUpRight,\n IconClockHour4,\n IconLock,\n} from \"@tabler/icons-react\";\nimport { useTheme } from \"next-themes\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { Link } from \"react-router\";\n\nimport { isEmbedSessionExpiredMessage } from \"../lib/embed-session-recovery\";\nimport {\n workspaceAppDirectHref,\n workspaceAppEmbedTarget,\n workspaceAppHref,\n type WorkspaceAppSummary,\n} from \"../lib/workspace-apps\";\nimport { DISPATCH_WORKSPACE_SSO_FLAG } from \"../shared/feature-flags\";\nimport { ActionQueryError } from \"./action-query-error\";\nimport { Badge } from \"./ui/badge\";\nimport { Button } from \"./ui/button\";\nimport { Skeleton } from \"./ui/skeleton\";\n\ninterface EmbedSessionResult {\n startUrl: string;\n}\n\ninterface EmbedSessionInput {\n app?: string;\n path?: string;\n url?: string;\n chrome: \"minimal\";\n}\n\ntype WorkspaceAppTheme = \"light\" | \"dark\";\ntype WorkspaceAppAuthState = \"unknown\" | \"authenticated\" | \"unauthenticated\";\n\nfunction resolveWorkspaceAppAuthState(\n rawUrl: string | null,\n): WorkspaceAppAuthState {\n if (!rawUrl) return \"unknown\";\n try {\n const lastSegment = new URL(rawUrl, window.location.origin).pathname\n .split(\"/\")\n .filter(Boolean)\n .at(-1)\n ?.toLowerCase();\n if (\n lastSegment === \"sign-in\" ||\n lastSegment === \"login\" ||\n lastSegment === \"signup\"\n ) {\n return \"unauthenticated\";\n }\n return \"authenticated\";\n } catch {\n return \"unknown\";\n }\n}\n\nfunction buildWorkspaceAppThemeUpdate(theme: WorkspaceAppTheme) {\n return {\n type: \"agent-native-theme-update\" as const,\n theme,\n isDark: theme === \"dark\",\n };\n}\n\nexport function buildChatFirstEmbedSessionInput(\n appId: string,\n path: string,\n): EmbedSessionInput {\n return { app: appId, path, chrome: \"minimal\" };\n}\n\nexport interface WorkspaceAppFrameApp {\n id: string;\n name: string;\n path?: string | null;\n url?: string | null;\n}\n\ninterface WorkspaceAppFrameProps {\n app: WorkspaceAppFrameApp;\n /** Chat-first app tabs use their own route while standalone hosts use app metadata. */\n embedPath?: string;\n /** Chat-first app surfaces own the parent chat rail around the iframe. */\n chatSidebar?: boolean;\n copy?: ChatFirstCopy;\n}\n\nexport function WorkspaceAppFrame({\n app,\n embedPath,\n chatSidebar = false,\n copy = defaultChatFirstCopy,\n}: WorkspaceAppFrameProps) {\n const { resolvedTheme } = useTheme();\n const theme: WorkspaceAppTheme =\n resolvedTheme === \"dark\" || resolvedTheme === \"light\"\n ? resolvedTheme\n : typeof document !== \"undefined\" &&\n document.documentElement.classList.contains(\"dark\")\n ? \"dark\"\n : \"light\";\n const [embedUrl, setEmbedUrl] = useState<string | null>(null);\n const [embedError, setEmbedError] = useState<Error | null>(null);\n const [embedAttempt, setEmbedAttempt] = useState(0);\n const [authState, setAuthState] = useState<WorkspaceAppAuthState>(\"unknown\");\n const embedFrameRef = useRef<HTMLIFrameElement>(null);\n const postThemeToFrame = useCallback(() => {\n embedFrameRef.current?.contentWindow?.postMessage(\n buildWorkspaceAppThemeUpdate(theme),\n \"*\",\n );\n }, [theme]);\n const handleFrameLoad = useCallback(() => {\n postThemeToFrame();\n const frame = embedFrameRef.current;\n let frameUrl = embedUrl;\n try {\n frameUrl = frame?.contentWindow?.location.href ?? frameUrl;\n // coercion-ok: Cross-origin frames cannot expose location; their auth state arrives by message.\n } catch {\n // Cross-origin app frames report their auth state through postMessage.\n }\n const nextAuthState = resolveWorkspaceAppAuthState(frameUrl);\n if (nextAuthState !== \"unknown\") setAuthState(nextAuthState);\n }, [embedUrl, postThemeToFrame]);\n const workspaceSsoEnabled = useFeatureFlag(DISPATCH_WORKSPACE_SSO_FLAG.key);\n const createEmbedSession = useActionMutation<\n EmbedSessionResult,\n EmbedSessionInput\n >(\"create_embed_session\", {\n skipActionQueryInvalidation: true,\n });\n const createWorkspaceSsoEmbedSession = useActionMutation<\n EmbedSessionResult,\n EmbedSessionInput\n >(\"create-workspace-app-embed-session\", {\n skipActionQueryInvalidation: true,\n });\n const appHref = workspaceAppHref({\n id: app.id,\n name: app.name,\n path: app.path ?? \"\",\n url: app.url,\n });\n const embedInput = useMemo<EmbedSessionInput | null>(() => {\n if (embedPath !== undefined) {\n return buildChatFirstEmbedSessionInput(app.id, embedPath);\n }\n if (!appHref) return null;\n return {\n app: app.id,\n ...workspaceAppEmbedTarget({ path: app.path ?? \"\", url: app.url }),\n chrome: \"minimal\",\n };\n }, [app.id, app.path, app.url, appHref, embedPath]);\n\n useEffect(() => {\n if (!embedInput) return;\n let cancelled = false;\n setEmbedUrl(null);\n setEmbedError(null);\n setAuthState(\"unknown\");\n const createSession = workspaceSsoEnabled\n ? createWorkspaceSsoEmbedSession\n : createEmbedSession;\n void createSession\n .mutateAsync(embedInput)\n .then((result) => {\n if (!cancelled) setEmbedUrl(result.startUrl);\n })\n .catch((cause: unknown) => {\n if (cancelled) return;\n setEmbedUrl(\n workspaceAppDirectHref(\n { path: app.path ?? \"\", url: app.url },\n embedPath ?? \"/\",\n ),\n );\n setEmbedError(\n cause instanceof Error ? cause : new Error(String(cause)),\n );\n });\n return () => {\n cancelled = true;\n };\n }, [\n app.id,\n app.path,\n app.url,\n createEmbedSession.mutateAsync,\n createWorkspaceSsoEmbedSession.mutateAsync,\n embedInput,\n embedPath,\n embedAttempt,\n workspaceSsoEnabled,\n ]);\n\n useEffect(() => {\n const handleEmbedSessionExpired = (event: MessageEvent) => {\n if (\n !isEmbedSessionExpiredMessage(event, embedFrameRef.current, embedUrl)\n ) {\n return;\n }\n setEmbedAttempt((attempt) => attempt + 1);\n };\n\n window.addEventListener(\"message\", handleEmbedSessionExpired);\n return () =>\n window.removeEventListener(\"message\", handleEmbedSessionExpired);\n }, [embedUrl]);\n\n useEffect(() => {\n const handleAuthState = (event: MessageEvent) => {\n const frame = embedFrameRef.current;\n if (!frame || event.source !== frame.contentWindow) return;\n if (event.data?.type !== \"agentNative.authState\") return;\n const status = event.data.data?.status;\n if (status === \"authenticated\" || status === \"unauthenticated\") {\n setAuthState(status);\n }\n };\n\n window.addEventListener(\"message\", handleAuthState);\n return () => window.removeEventListener(\"message\", handleAuthState);\n }, []);\n\n useEffect(() => {\n postThemeToFrame();\n }, [embedUrl, postThemeToFrame]);\n\n const appPane = (\n <ChatFirstAppPane\n app={app}\n status={\n embedUrl\n ? \"ready\"\n : embedError\n ? \"error\"\n : embedInput\n ? \"loading\"\n : \"unresolved\"\n }\n embedUrl={embedUrl}\n errorMessage={embedError?.message}\n onRetry={\n embedInput ? () => setEmbedAttempt((attempt) => attempt + 1) : undefined\n }\n renderEmbed={({ url, title }) => (\n <iframe\n key={url + \":\" + embedAttempt}\n data-dispatch-workspace-app-frame\n src={url}\n title={title ?? app.name}\n ref={embedFrameRef}\n onLoad={handleFrameLoad}\n referrerPolicy=\"no-referrer\"\n allow=\"clipboard-read; clipboard-write\"\n className=\"h-full w-full border-0 bg-background\"\n />\n )}\n copy={copy}\n />\n );\n\n if (!chatSidebar) return appPane;\n\n return (\n <AgentSidebar\n position=\"left\"\n defaultOpen\n openStorageKey=\"dispatch-app-chat\"\n storageKey={`dispatch-app-chat:${app.id}`}\n scope={{\n type: \"workspace-app\",\n id: app.id,\n label: app.name,\n contextKey: `workspace-app:${app.id}`,\n }}\n agentChatSurface=\"app\"\n showTabBar\n suppressInlineOpenApp\n dynamicSuggestions={false}\n suggestions={[]}\n emptyStateText={`Ask about ${app.name}`}\n composerSlot={\n authState === \"unauthenticated\" ? (\n <div className=\"flex shrink-0 items-center px-3 pb-1\">\n <button\n type=\"button\"\n data-dispatch-app-sign-in\n aria-label={`Sign in to ${app.name} on the right`}\n title={`Sign in to ${app.name} on the right`}\n onClick={() => embedFrameRef.current?.focus()}\n className=\"inline-flex h-6 shrink-0 items-center gap-1 rounded-full border border-border/70 bg-background/60 px-2 text-[11px] font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground\"\n >\n <IconLock size={12} stroke={1.8} />\n <span>Sign in on the right</span>\n <IconArrowUpRight size={12} stroke={1.8} />\n </button>\n </div>\n ) : null\n }\n >\n {appPane}\n </AgentSidebar>\n );\n}\n\nexport function WorkspaceAppHost({ appId }: { appId?: string }) {\n const t = useT();\n const appsQuery = useActionQuery(\"list-workspace-apps\", {\n includeAgentCards: false,\n });\n const { data: apps = [], isLoading } = appsQuery;\n const app = useMemo(\n () =>\n (apps as WorkspaceAppSummary[]).find((item) => item.id === appId) ?? null,\n [appId, apps],\n );\n\n if (appsQuery.isError) {\n return (\n <div className=\"flex h-full min-h-0 items-center justify-center p-6\">\n <div className=\"w-full max-w-2xl\">\n <ActionQueryError\n error={appsQuery.error}\n onRetry={() => void appsQuery.refetch()}\n />\n </div>\n </div>\n );\n }\n\n if (isLoading && !app) {\n return (\n <div className=\"flex h-full min-h-0 items-center justify-center p-6\">\n <div className=\"w-full max-w-2xl space-y-3 rounded-xl border bg-card p-6\">\n <Skeleton className=\"h-5 w-48\" />\n <Skeleton className=\"h-4 w-full\" />\n <Skeleton className=\"h-4 w-2/3\" />\n </div>\n </div>\n );\n }\n\n if (!app) {\n return (\n <div className=\"flex h-full min-h-0 items-center justify-center p-6\">\n <div className=\"w-full max-w-2xl rounded-xl border bg-card p-6\">\n <Button asChild size=\"sm\" variant=\"ghost\" className=\"-ml-2 mb-4\">\n <Link to=\"/apps\">\n <IconArrowLeft size={15} className=\"mr-1.5\" />\n {t(\"dispatch.nav.apps\")}\n </Link>\n </Button>\n <div className=\"space-y-3\">\n <h2 className=\"text-base font-semibold text-foreground\">\n {t(\"dispatch.pages.appNotFound\")}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t(\"dispatch.pages.pageNotFoundDescription\")}\n </p>\n </div>\n </div>\n </div>\n );\n }\n\n if (app.status === \"pending\") {\n return (\n <div className=\"flex h-full min-h-0 items-center justify-center p-6\">\n <div className=\"w-full max-w-2xl rounded-xl border bg-card p-6\">\n <Button asChild size=\"sm\" variant=\"ghost\" className=\"-ml-2 mb-4\">\n <Link to=\"/apps\">\n <IconArrowLeft size={15} className=\"mr-1.5\" />\n {t(\"dispatch.nav.apps\")}\n </Link>\n </Button>\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-base font-semibold text-foreground\">\n {app.name}\n </h2>\n <Badge\n variant=\"outline\"\n className=\"gap-1 border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300\"\n >\n <IconClockHour4 size={12} />\n {t(\"dispatch.pages.building\")}\n </Badge>\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {t(\"dispatch.pages.appBuildingPrefix\")}{\" \"}\n <span className=\"font-mono text-foreground\">{app.path}</span>{\" \"}\n {t(\"dispatch.pages.appBuildingSuffix\")}\n </p>\n {app.builderUrl ? (\n <Button asChild>\n <a\n href={withBuilderUtmTrackingParams(app.builderUrl, {\n campaign: \"product\",\n content: \"dispatch_branch\",\n })}\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n {t(\"dispatch.pages.openBuilderBranch\", {\n defaultValue: \"Open in Builder\",\n })}\n </a>\n </Button>\n ) : null}\n </div>\n </div>\n </div>\n );\n }\n\n return (\n <div\n data-dispatch-workspace-app-host\n className=\"flex h-full min-h-0 flex-col bg-background\"\n >\n <div className=\"min-h-0 flex-1 bg-muted/20\">\n <WorkspaceAppFrame app={app} />\n </div>\n </div>\n );\n}\n\nconst MAX_KEEP_ALIVE_APPS = 3;\n\nexport function WorkspaceAppKeepAlive({\n activeAppId,\n}: {\n activeAppId: string | null;\n}) {\n const [visitedAppIds, setVisitedAppIds] = useState<string[]>(() =>\n activeAppId ? [activeAppId] : [],\n );\n\n useEffect(() => {\n if (!activeAppId) return;\n setVisitedAppIds((current) =>\n [activeAppId, ...current.filter((appId) => appId !== activeAppId)].slice(\n 0,\n MAX_KEEP_ALIVE_APPS,\n ),\n );\n }, [activeAppId]);\n\n const renderedAppIds =\n activeAppId && !visitedAppIds.includes(activeAppId)\n ? [activeAppId, ...visitedAppIds]\n : visitedAppIds;\n\n return (\n <div\n data-dispatch-workspace-app-cache\n className={activeAppId ? \"absolute inset-0 overflow-hidden\" : \"hidden\"}\n >\n {renderedAppIds.map((appId) => {\n const active = appId === activeAppId;\n return (\n <div\n key={appId}\n data-dispatch-workspace-app-cache-entry={appId}\n aria-hidden={!active}\n className={active ? \"h-full min-h-0\" : \"hidden\"}\n >\n <WorkspaceAppHost appId={appId} />\n </div>\n );\n })}\n </div>\n );\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/dispatch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Dispatch — workspace control plane for agent-native apps. Vault, integrations, destinations, scheduled jobs, and cross-app delegation, shipped as a single drop-in package.",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"tailwind-merge": "^3.5.0",
|
|
88
88
|
"vaul": "^1.1.2",
|
|
89
89
|
"zod": "^4.3.6",
|
|
90
|
-
"@agent-native/toolkit": "0.
|
|
90
|
+
"@agent-native/toolkit": "0.16.0"
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
|
93
93
|
"@react-router/dev": "^8.1.0",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"typescript-7": "npm:typescript@^7.0.2",
|
|
102
102
|
"vite": "8.1.0",
|
|
103
103
|
"vitest": "^4.1.5",
|
|
104
|
-
"@agent-native/core": "0.157.
|
|
104
|
+
"@agent-native/core": "0.157.4"
|
|
105
105
|
},
|
|
106
106
|
"peerDependencies": {
|
|
107
107
|
"@agent-native/core": ">=0.8.0",
|
|
@@ -15,6 +15,7 @@ const clientState = vi.hoisted(() => {
|
|
|
15
15
|
return {
|
|
16
16
|
actionNames,
|
|
17
17
|
legacyMutateAsync,
|
|
18
|
+
theme: "dark" as "dark" | "light",
|
|
18
19
|
workspaceSsoEnabled: false,
|
|
19
20
|
workspaceSsoMutateAsync,
|
|
20
21
|
};
|
|
@@ -90,6 +91,10 @@ vi.mock("@agent-native/core/client/i18n", () => ({
|
|
|
90
91
|
useT: () => (key: string) => key,
|
|
91
92
|
}));
|
|
92
93
|
|
|
94
|
+
vi.mock("next-themes", () => ({
|
|
95
|
+
useTheme: () => ({ resolvedTheme: clientState.theme }),
|
|
96
|
+
}));
|
|
97
|
+
|
|
93
98
|
import { WorkspaceAppFrame, WorkspaceAppKeepAlive } from "./workspace-app-host";
|
|
94
99
|
|
|
95
100
|
describe("WorkspaceAppKeepAlive", () => {
|
|
@@ -104,6 +109,7 @@ describe("WorkspaceAppKeepAlive", () => {
|
|
|
104
109
|
clientState.actionNames.length = 0;
|
|
105
110
|
clientState.legacyMutateAsync.mockClear();
|
|
106
111
|
clientState.workspaceSsoMutateAsync.mockClear();
|
|
112
|
+
clientState.theme = "dark";
|
|
107
113
|
clientState.workspaceSsoEnabled = false;
|
|
108
114
|
});
|
|
109
115
|
|
|
@@ -163,6 +169,57 @@ describe("WorkspaceAppKeepAlive", () => {
|
|
|
163
169
|
expect(clientState.legacyMutateAsync).not.toHaveBeenCalled();
|
|
164
170
|
});
|
|
165
171
|
|
|
172
|
+
it("sends the parent theme on iframe load and when the parent changes", async () => {
|
|
173
|
+
await act(async () => {
|
|
174
|
+
root.render(
|
|
175
|
+
<WorkspaceAppFrame app={{ id: "mail", name: "Mail", path: "/mail" }} />,
|
|
176
|
+
);
|
|
177
|
+
await Promise.resolve();
|
|
178
|
+
await Promise.resolve();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const iframe = container.querySelector<HTMLIFrameElement>("iframe");
|
|
182
|
+
expect(iframe).not.toBeNull();
|
|
183
|
+
if (!iframe) throw new Error("Workspace app iframe was not rendered");
|
|
184
|
+
|
|
185
|
+
const postMessage = vi.fn();
|
|
186
|
+
Object.defineProperty(iframe, "contentWindow", {
|
|
187
|
+
configurable: true,
|
|
188
|
+
value: { postMessage },
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await act(async () => {
|
|
192
|
+
iframe?.dispatchEvent(new Event("load"));
|
|
193
|
+
await Promise.resolve();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
expect(postMessage).toHaveBeenCalledWith(
|
|
197
|
+
{
|
|
198
|
+
type: "agent-native-theme-update",
|
|
199
|
+
theme: "dark",
|
|
200
|
+
isDark: true,
|
|
201
|
+
},
|
|
202
|
+
"*",
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
clientState.theme = "light";
|
|
206
|
+
await act(async () => {
|
|
207
|
+
root.render(
|
|
208
|
+
<WorkspaceAppFrame app={{ id: "mail", name: "Mail", path: "/mail" }} />,
|
|
209
|
+
);
|
|
210
|
+
await Promise.resolve();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect(postMessage).toHaveBeenLastCalledWith(
|
|
214
|
+
{
|
|
215
|
+
type: "agent-native-theme-update",
|
|
216
|
+
theme: "light",
|
|
217
|
+
isDark: false,
|
|
218
|
+
},
|
|
219
|
+
"*",
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
166
223
|
it("evicts the oldest inactive app after reaching the keep-alive limit", async () => {
|
|
167
224
|
await act(async () => {
|
|
168
225
|
root.render(<WorkspaceAppKeepAlive activeAppId="mail" />);
|
|
@@ -11,8 +11,14 @@ import {
|
|
|
11
11
|
} from "@agent-native/core/client/hooks";
|
|
12
12
|
import { useT } from "@agent-native/core/client/i18n";
|
|
13
13
|
import { withBuilderUtmTrackingParams } from "@agent-native/core/shared/builder-link-tracking";
|
|
14
|
-
import {
|
|
15
|
-
|
|
14
|
+
import {
|
|
15
|
+
IconArrowLeft,
|
|
16
|
+
IconArrowUpRight,
|
|
17
|
+
IconClockHour4,
|
|
18
|
+
IconLock,
|
|
19
|
+
} from "@tabler/icons-react";
|
|
20
|
+
import { useTheme } from "next-themes";
|
|
21
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
16
22
|
import { Link } from "react-router";
|
|
17
23
|
|
|
18
24
|
import { isEmbedSessionExpiredMessage } from "../lib/embed-session-recovery";
|
|
@@ -39,6 +45,40 @@ interface EmbedSessionInput {
|
|
|
39
45
|
chrome: "minimal";
|
|
40
46
|
}
|
|
41
47
|
|
|
48
|
+
type WorkspaceAppTheme = "light" | "dark";
|
|
49
|
+
type WorkspaceAppAuthState = "unknown" | "authenticated" | "unauthenticated";
|
|
50
|
+
|
|
51
|
+
function resolveWorkspaceAppAuthState(
|
|
52
|
+
rawUrl: string | null,
|
|
53
|
+
): WorkspaceAppAuthState {
|
|
54
|
+
if (!rawUrl) return "unknown";
|
|
55
|
+
try {
|
|
56
|
+
const lastSegment = new URL(rawUrl, window.location.origin).pathname
|
|
57
|
+
.split("/")
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.at(-1)
|
|
60
|
+
?.toLowerCase();
|
|
61
|
+
if (
|
|
62
|
+
lastSegment === "sign-in" ||
|
|
63
|
+
lastSegment === "login" ||
|
|
64
|
+
lastSegment === "signup"
|
|
65
|
+
) {
|
|
66
|
+
return "unauthenticated";
|
|
67
|
+
}
|
|
68
|
+
return "authenticated";
|
|
69
|
+
} catch {
|
|
70
|
+
return "unknown";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function buildWorkspaceAppThemeUpdate(theme: WorkspaceAppTheme) {
|
|
75
|
+
return {
|
|
76
|
+
type: "agent-native-theme-update" as const,
|
|
77
|
+
theme,
|
|
78
|
+
isDark: theme === "dark",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
42
82
|
export function buildChatFirstEmbedSessionInput(
|
|
43
83
|
appId: string,
|
|
44
84
|
path: string,
|
|
@@ -68,10 +108,38 @@ export function WorkspaceAppFrame({
|
|
|
68
108
|
chatSidebar = false,
|
|
69
109
|
copy = defaultChatFirstCopy,
|
|
70
110
|
}: WorkspaceAppFrameProps) {
|
|
111
|
+
const { resolvedTheme } = useTheme();
|
|
112
|
+
const theme: WorkspaceAppTheme =
|
|
113
|
+
resolvedTheme === "dark" || resolvedTheme === "light"
|
|
114
|
+
? resolvedTheme
|
|
115
|
+
: typeof document !== "undefined" &&
|
|
116
|
+
document.documentElement.classList.contains("dark")
|
|
117
|
+
? "dark"
|
|
118
|
+
: "light";
|
|
71
119
|
const [embedUrl, setEmbedUrl] = useState<string | null>(null);
|
|
72
120
|
const [embedError, setEmbedError] = useState<Error | null>(null);
|
|
73
121
|
const [embedAttempt, setEmbedAttempt] = useState(0);
|
|
122
|
+
const [authState, setAuthState] = useState<WorkspaceAppAuthState>("unknown");
|
|
74
123
|
const embedFrameRef = useRef<HTMLIFrameElement>(null);
|
|
124
|
+
const postThemeToFrame = useCallback(() => {
|
|
125
|
+
embedFrameRef.current?.contentWindow?.postMessage(
|
|
126
|
+
buildWorkspaceAppThemeUpdate(theme),
|
|
127
|
+
"*",
|
|
128
|
+
);
|
|
129
|
+
}, [theme]);
|
|
130
|
+
const handleFrameLoad = useCallback(() => {
|
|
131
|
+
postThemeToFrame();
|
|
132
|
+
const frame = embedFrameRef.current;
|
|
133
|
+
let frameUrl = embedUrl;
|
|
134
|
+
try {
|
|
135
|
+
frameUrl = frame?.contentWindow?.location.href ?? frameUrl;
|
|
136
|
+
// coercion-ok: Cross-origin frames cannot expose location; their auth state arrives by message.
|
|
137
|
+
} catch {
|
|
138
|
+
// Cross-origin app frames report their auth state through postMessage.
|
|
139
|
+
}
|
|
140
|
+
const nextAuthState = resolveWorkspaceAppAuthState(frameUrl);
|
|
141
|
+
if (nextAuthState !== "unknown") setAuthState(nextAuthState);
|
|
142
|
+
}, [embedUrl, postThemeToFrame]);
|
|
75
143
|
const workspaceSsoEnabled = useFeatureFlag(DISPATCH_WORKSPACE_SSO_FLAG.key);
|
|
76
144
|
const createEmbedSession = useActionMutation<
|
|
77
145
|
EmbedSessionResult,
|
|
@@ -108,6 +176,7 @@ export function WorkspaceAppFrame({
|
|
|
108
176
|
let cancelled = false;
|
|
109
177
|
setEmbedUrl(null);
|
|
110
178
|
setEmbedError(null);
|
|
179
|
+
setAuthState("unknown");
|
|
111
180
|
const createSession = workspaceSsoEnabled
|
|
112
181
|
? createWorkspaceSsoEmbedSession
|
|
113
182
|
: createEmbedSession;
|
|
@@ -158,6 +227,25 @@ export function WorkspaceAppFrame({
|
|
|
158
227
|
window.removeEventListener("message", handleEmbedSessionExpired);
|
|
159
228
|
}, [embedUrl]);
|
|
160
229
|
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
const handleAuthState = (event: MessageEvent) => {
|
|
232
|
+
const frame = embedFrameRef.current;
|
|
233
|
+
if (!frame || event.source !== frame.contentWindow) return;
|
|
234
|
+
if (event.data?.type !== "agentNative.authState") return;
|
|
235
|
+
const status = event.data.data?.status;
|
|
236
|
+
if (status === "authenticated" || status === "unauthenticated") {
|
|
237
|
+
setAuthState(status);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
window.addEventListener("message", handleAuthState);
|
|
242
|
+
return () => window.removeEventListener("message", handleAuthState);
|
|
243
|
+
}, []);
|
|
244
|
+
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
postThemeToFrame();
|
|
247
|
+
}, [embedUrl, postThemeToFrame]);
|
|
248
|
+
|
|
161
249
|
const appPane = (
|
|
162
250
|
<ChatFirstAppPane
|
|
163
251
|
app={app}
|
|
@@ -182,6 +270,7 @@ export function WorkspaceAppFrame({
|
|
|
182
270
|
src={url}
|
|
183
271
|
title={title ?? app.name}
|
|
184
272
|
ref={embedFrameRef}
|
|
273
|
+
onLoad={handleFrameLoad}
|
|
185
274
|
referrerPolicy="no-referrer"
|
|
186
275
|
allow="clipboard-read; clipboard-write"
|
|
187
276
|
className="h-full w-full border-0 bg-background"
|
|
@@ -211,6 +300,24 @@ export function WorkspaceAppFrame({
|
|
|
211
300
|
dynamicSuggestions={false}
|
|
212
301
|
suggestions={[]}
|
|
213
302
|
emptyStateText={`Ask about ${app.name}`}
|
|
303
|
+
composerSlot={
|
|
304
|
+
authState === "unauthenticated" ? (
|
|
305
|
+
<div className="flex shrink-0 items-center px-3 pb-1">
|
|
306
|
+
<button
|
|
307
|
+
type="button"
|
|
308
|
+
data-dispatch-app-sign-in
|
|
309
|
+
aria-label={`Sign in to ${app.name} on the right`}
|
|
310
|
+
title={`Sign in to ${app.name} on the right`}
|
|
311
|
+
onClick={() => embedFrameRef.current?.focus()}
|
|
312
|
+
className="inline-flex h-6 shrink-0 items-center gap-1 rounded-full border border-border/70 bg-background/60 px-2 text-[11px] font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
|
313
|
+
>
|
|
314
|
+
<IconLock size={12} stroke={1.8} />
|
|
315
|
+
<span>Sign in on the right</span>
|
|
316
|
+
<IconArrowUpRight size={12} stroke={1.8} />
|
|
317
|
+
</button>
|
|
318
|
+
</div>
|
|
319
|
+
) : null
|
|
320
|
+
}
|
|
214
321
|
>
|
|
215
322
|
{appPane}
|
|
216
323
|
</AgentSidebar>
|