@agent-native/dispatch 0.27.6 → 0.27.7

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.
@@ -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;AAoC9C,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;AA8CD,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,mDA2NxB;AAED,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,mDA2J7D;AAID,wBAAgB,qBAAqB,CAAC,EACpC,WAAW,GACZ,EAAE;IACD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,mDAwCA"}
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;AA+B9C,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;AAqBD,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,mDAuLxB;AAED,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,mDA2J7D;AAID,wBAAgB,qBAAqB,CAAC,EACpC,WAAW,GACZ,EAAE;IACD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,mDAwCA"}
@@ -5,7 +5,7 @@ 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, IconArrowUpRight, IconClockHour4, IconLock, } from "@tabler/icons-react";
8
+ import { IconArrowLeft, IconClockHour4 } from "@tabler/icons-react";
9
9
  import { useTheme } from "next-themes";
10
10
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
11
11
  import { Link } from "react-router";
@@ -16,26 +16,6 @@ import { ActionQueryError } from "./action-query-error.js";
16
16
  import { Badge } from "./ui/badge.js";
17
17
  import { Button } from "./ui/button.js";
18
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
19
  function buildWorkspaceAppThemeUpdate(theme) {
40
20
  return {
41
21
  type: "agent-native-theme-update",
@@ -58,7 +38,6 @@ export function WorkspaceAppFrame({ app, embedPath, chatSidebar = false, copy =
58
38
  const [embedError, setEmbedError] = useState(null);
59
39
  const [isDirectFallback, setIsDirectFallback] = useState(false);
60
40
  const [embedAttempt, setEmbedAttempt] = useState(0);
61
- const [authState, setAuthState] = useState("unknown");
62
41
  const embedFrameRef = useRef(null);
63
42
  const postThemeToFrame = useCallback(() => {
64
43
  embedFrameRef.current?.contentWindow?.postMessage(buildWorkspaceAppThemeUpdate(theme), "*");
@@ -67,19 +46,7 @@ export function WorkspaceAppFrame({ app, embedPath, chatSidebar = false, copy =
67
46
  postThemeToFrame();
68
47
  if (isDirectFallback)
69
48
  setEmbedError(null);
70
- const frame = embedFrameRef.current;
71
- let frameUrl = embedUrl;
72
- try {
73
- frameUrl = frame?.contentWindow?.location.href ?? frameUrl;
74
- // coercion-ok: Cross-origin frames cannot expose location; their auth state arrives by message.
75
- }
76
- catch {
77
- // Cross-origin app frames report their auth state through postMessage.
78
- }
79
- const nextAuthState = resolveWorkspaceAppAuthState(frameUrl);
80
- if (nextAuthState !== "unknown")
81
- setAuthState(nextAuthState);
82
- }, [embedUrl, isDirectFallback, postThemeToFrame]);
49
+ }, [isDirectFallback, postThemeToFrame]);
83
50
  const workspaceSsoEnabled = useFeatureFlag(DISPATCH_WORKSPACE_SSO_FLAG.key);
84
51
  const createEmbedSession = useActionMutation("create_embed_session", {
85
52
  skipActionQueryInvalidation: true,
@@ -112,7 +79,6 @@ export function WorkspaceAppFrame({ app, embedPath, chatSidebar = false, copy =
112
79
  setEmbedUrl(null);
113
80
  setEmbedError(null);
114
81
  setIsDirectFallback(false);
115
- setAuthState("unknown");
116
82
  const createSession = workspaceSsoEnabled
117
83
  ? createWorkspaceSsoEmbedSession
118
84
  : createEmbedSession;
@@ -125,9 +91,20 @@ export function WorkspaceAppFrame({ app, embedPath, chatSidebar = false, copy =
125
91
  .catch((cause) => {
126
92
  if (cancelled)
127
93
  return;
94
+ const error = cause instanceof Error ? cause : new Error(String(cause));
95
+ if (workspaceSsoEnabled) {
96
+ // An SSO-enabled pane must never fall back to the child app's
97
+ // unauthenticated shell. Keep the parent-owned retry surface in
98
+ // place so a transient exchange failure cannot expose another
99
+ // login form.
100
+ setIsDirectFallback(false);
101
+ setEmbedUrl(null);
102
+ setEmbedError(error);
103
+ return;
104
+ }
128
105
  setIsDirectFallback(true);
129
106
  setEmbedUrl(workspaceAppDirectHref({ path: app.path ?? "", url: app.url }, embedPath ?? "/"));
130
- setEmbedError(cause instanceof Error ? cause : new Error(String(cause)));
107
+ setEmbedError(error);
131
108
  });
132
109
  return () => {
133
110
  cancelled = true;
@@ -153,21 +130,6 @@ export function WorkspaceAppFrame({ app, embedPath, chatSidebar = false, copy =
153
130
  window.addEventListener("message", handleEmbedSessionExpired);
154
131
  return () => window.removeEventListener("message", handleEmbedSessionExpired);
155
132
  }, [embedUrl]);
156
- useEffect(() => {
157
- const handleAuthState = (event) => {
158
- const frame = embedFrameRef.current;
159
- if (!frame || event.source !== frame.contentWindow)
160
- return;
161
- if (event.data?.type !== "agentNative.authState")
162
- return;
163
- const status = event.data.data?.status;
164
- if (status === "authenticated" || status === "unauthenticated") {
165
- setAuthState(status);
166
- }
167
- };
168
- window.addEventListener("message", handleAuthState);
169
- return () => window.removeEventListener("message", handleAuthState);
170
- }, []);
171
133
  useEffect(() => {
172
134
  postThemeToFrame();
173
135
  }, [embedUrl, postThemeToFrame]);
@@ -185,7 +147,7 @@ export function WorkspaceAppFrame({ app, embedPath, chatSidebar = false, copy =
185
147
  id: app.id,
186
148
  label: app.name,
187
149
  contextKey: `workspace-app:${app.id}`,
188
- }, 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 }));
150
+ }, agentChatSurface: "app", showTabBar: true, suppressInlineOpenApp: true, dynamicSuggestions: false, suggestions: [], emptyStateText: `Ask about ${app.name}`, children: appPane }));
189
151
  }
190
152
  export function WorkspaceAppHost({ appId }) {
191
153
  const t = useT();
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-app-host.js","sourceRoot":"","sources":["../../src/components/workspace-app-host.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EACL,gBAAgB,EAChB,oBAAoB,GAErB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EACL,iBAAiB,EACjB,cAAc,GACf,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AACtD,OAAO,EAAE,4BAA4B,EAAE,MAAM,iDAAiD,CAAC;AAC/F,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,QAAQ,GACT,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEpC,OAAO,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAC;AAC7E,OAAO,EACL,2BAA2B,EAC3B,sBAAsB,EACtB,uBAAuB,EACvB,gBAAgB,GAEjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AA0BzC,SAAS,4BAA4B,CACnC,MAAqB;IAErB,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,QAAQ;aACjE,KAAK,CAAC,GAAG,CAAC;aACV,MAAM,CAAC,OAAO,CAAC;aACf,EAAE,CAAC,CAAC,CAAC,CAAC;YACP,EAAE,WAAW,EAAE,CAAC;QAClB,IACE,WAAW,KAAK,SAAS;YACzB,WAAW,KAAK,OAAO;YACvB,WAAW,KAAK,QAAQ,EACxB,CAAC;YACD,OAAO,iBAAiB,CAAC;QAC3B,CAAC;QACD,OAAO,eAAe,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,4BAA4B,CAAC,KAAwB;IAC5D,OAAO;QACL,IAAI,EAAE,2BAAoC;QAC1C,KAAK;QACL,MAAM,EAAE,KAAK,KAAK,MAAM;KACzB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,+BAA+B,CAC7C,KAAa,EACb,IAAY;IAEZ,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AACjD,CAAC;AAkBD,MAAM,UAAU,iBAAiB,CAAC,EAChC,GAAG,EACH,SAAS,EACT,WAAW,GAAG,KAAK,EACnB,IAAI,GAAG,oBAAoB,GACJ;IACvB,MAAM,EAAE,aAAa,EAAE,GAAG,QAAQ,EAAE,CAAC;IACrC,MAAM,KAAK,GACT,aAAa,KAAK,MAAM,IAAI,aAAa,KAAK,OAAO;QACnD,CAAC,CAAC,aAAa;QACf,CAAC,CAAC,OAAO,QAAQ,KAAK,WAAW;YAC7B,QAAQ,CAAC,eAAe,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC;YACrD,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,OAAO,CAAC;IAChB,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAC9D,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IACjE,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAChE,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IACpD,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAwB,SAAS,CAAC,CAAC;IAC7E,MAAM,aAAa,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IACtD,MAAM,gBAAgB,GAAG,WAAW,CAAC,GAAG,EAAE;QACxC,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE,WAAW,CAC/C,4BAA4B,CAAC,KAAK,CAAC,EACnC,GAAG,CACJ,CAAC;IACJ,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACZ,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE;QACvC,gBAAgB,EAAE,CAAC;QACnB,IAAI,gBAAgB;YAAE,aAAa,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC;QACpC,IAAI,QAAQ,GAAG,QAAQ,CAAC;QACxB,IAAI,CAAC;YACH,QAAQ,GAAG,KAAK,EAAE,aAAa,EAAE,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC;YAC3D,gGAAgG;QAClG,CAAC;QAAC,MAAM,CAAC;YACP,uEAAuE;QACzE,CAAC;QACD,MAAM,aAAa,GAAG,4BAA4B,CAAC,QAAQ,CAAC,CAAC;QAC7D,IAAI,aAAa,KAAK,SAAS;YAAE,YAAY,CAAC,aAAa,CAAC,CAAC;IAC/D,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,EAAE,gBAAgB,CAAC,CAAC,CAAC;IACnD,MAAM,mBAAmB,GAAG,cAAc,CAAC,2BAA2B,CAAC,GAAG,CAAC,CAAC;IAC5E,MAAM,kBAAkB,GAAG,iBAAiB,CAG1C,sBAAsB,EAAE;QACxB,2BAA2B,EAAE,IAAI;KAClC,CAAC,CAAC;IACH,MAAM,8BAA8B,GAAG,iBAAiB,CAGtD,oCAAoC,EAAE;QACtC,2BAA2B,EAAE,IAAI;KAClC,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,gBAAgB,CAAC;QAC/B,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE;QACpB,GAAG,EAAE,GAAG,CAAC,GAAG;KACb,CAAC,CAAC;IACH,MAAM,UAAU,GAAG,OAAO,CAA2B,GAAG,EAAE;QACxD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,OAAO,+BAA+B,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;QAC5D,CAAC;QACD,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC1B,OAAO;YACL,GAAG,EAAE,GAAG,CAAC,EAAE;YACX,GAAG,uBAAuB,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YAClE,MAAM,EAAE,SAAS;SAClB,CAAC;IACJ,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;IAEpD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,UAAU;YAAE,OAAO;QACxB,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,WAAW,CAAC,IAAI,CAAC,CAAC;QAClB,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,mBAAmB,CAAC,KAAK,CAAC,CAAC;QAC3B,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,MAAM,aAAa,GAAG,mBAAmB;YACvC,CAAC,CAAC,8BAA8B;YAChC,CAAC,CAAC,kBAAkB,CAAC;QACvB,KAAK,aAAa;aACf,WAAW,CAAC,UAAU,CAAC;aACvB,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YACf,IAAI,CAAC,SAAS;gBAAE,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC/C,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACxB,IAAI,SAAS;gBAAE,OAAO;YACtB,mBAAmB,CAAC,IAAI,CAAC,CAAC;YAC1B,WAAW,CACT,sBAAsB,CACpB,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,EACtC,SAAS,IAAI,GAAG,CACjB,CACF,CAAC;YACF,aAAa,CACX,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAC1D,CAAC;QACJ,CAAC,CAAC,CAAC;QACL,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC,CAAC;IACJ,CAAC,EAAE;QACD,GAAG,CAAC,EAAE;QACN,GAAG,CAAC,IAAI;QACR,GAAG,CAAC,GAAG;QACP,kBAAkB,CAAC,WAAW;QAC9B,8BAA8B,CAAC,WAAW;QAC1C,UAAU;QACV,SAAS;QACT,YAAY;QACZ,mBAAmB;KACpB,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,yBAAyB,GAAG,CAAC,KAAmB,EAAE,EAAE;YACxD,IACE,CAAC,4BAA4B,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,EACrE,CAAC;gBACD,OAAO;YACT,CAAC;YACD,eAAe,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;QAC5C,CAAC,CAAC;QAEF,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC;QAC9D,OAAO,GAAG,EAAE,CACV,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC;IACrE,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,eAAe,GAAG,CAAC,KAAmB,EAAE,EAAE;YAC9C,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC;YACpC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,aAAa;gBAAE,OAAO;YAC3D,IAAI,KAAK,CAAC,IAAI,EAAE,IAAI,KAAK,uBAAuB;gBAAE,OAAO;YACzD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC;YACvC,IAAI,MAAM,KAAK,eAAe,IAAI,MAAM,KAAK,iBAAiB,EAAE,CAAC;gBAC/D,YAAY,CAAC,MAAM,CAAC,CAAC;YACvB,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;QACpD,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;IACtE,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,SAAS,CAAC,GAAG,EAAE;QACb,gBAAgB,EAAE,CAAC;IACrB,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAEjC,MAAM,OAAO,GAAG,CACd,KAAC,gBAAgB,IACf,GAAG,EAAE,GAAG,EACR,MAAM,EACJ,QAAQ;YACN,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,UAAU;gBACV,CAAC,CAAC,OAAO;gBACT,CAAC,CAAC,UAAU;oBACV,CAAC,CAAC,SAAS;oBACX,CAAC,CAAC,YAAY,EAEtB,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,UAAU,EAAE,OAAO,EACjC,OAAO,EACL,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,EAE1E,WAAW,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAC/B,4DAGE,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,KAAK,IAAI,GAAG,CAAC,IAAI,EACxB,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,eAAe,EACvB,cAAc,EAAC,aAAa,EAC5B,KAAK,EAAC,iCAAiC,EACvC,SAAS,EAAC,sCAAsC,IAR3C,GAAG,GAAG,GAAG,GAAG,YAAY,CAS7B,CACH,EACD,IAAI,EAAE,IAAI,GACV,CACH,CAAC;IAEF,IAAI,CAAC,WAAW;QAAE,OAAO,OAAO,CAAC;IAEjC,OAAO,CACL,KAAC,YAAY,IACX,QAAQ,EAAC,MAAM,EACf,WAAW,QACX,cAAc,EAAC,mBAAmB,EAClC,UAAU,EAAE,qBAAqB,GAAG,CAAC,EAAE,EAAE,EACzC,KAAK,EAAE;YACL,IAAI,EAAE,eAAe;YACrB,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,KAAK,EAAE,GAAG,CAAC,IAAI;YACf,UAAU,EAAE,iBAAiB,GAAG,CAAC,EAAE,EAAE;SACtC,EACD,gBAAgB,EAAC,KAAK,EACtB,UAAU,QACV,qBAAqB,QACrB,kBAAkB,EAAE,KAAK,EACzB,WAAW,EAAE,EAAE,EACf,cAAc,EAAE,aAAa,GAAG,CAAC,IAAI,EAAE,EACvC,YAAY,EACV,SAAS,KAAK,iBAAiB,CAAC,CAAC,CAAC,CAChC,cAAK,SAAS,EAAC,sCAAsC,YACnD,kBACE,IAAI,EAAC,QAAQ,mDAED,cAAc,GAAG,CAAC,IAAI,eAAe,EACjD,KAAK,EAAE,cAAc,GAAG,CAAC,IAAI,eAAe,EAC5C,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE,EAC7C,SAAS,EAAC,8MAA8M,aAExN,KAAC,QAAQ,IAAC,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,GAAI,EACnC,kDAAiC,EACjC,KAAC,gBAAgB,IAAC,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,GAAI,IACpC,GACL,CACP,CAAC,CAAC,CAAC,IAAI,YAGT,OAAO,GACK,CAChB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAE,KAAK,EAAsB;IAC5D,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,kBAAkB,GAAG,cAAc,CACvC,qBAAqB,EACrB,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAC7B,CAAC;IACF,MAAM,gBAAgB,GAAG,cAAc,CACrC,WAAW,EACX,EAAE,CACH,CAAC;IACF,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE;QACxB,MAAM,MAAM,GAAG,IAAI,GAAG,EAA+B,CAAC;QAEtD,KAAK,MAAM,GAAG,IAAI,2BAA2B,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;YACvE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,CAAC;QAC/C,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,gBAAgB,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC;YACpD,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;YACzB,IAAI,CAAC,EAAE,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC;gBAAE,SAAS;YAClD,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,EAAE;gBAC3B,EAAE;gBACF,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE;gBAC3B,IAAI,EAAE,EAAE;gBACR,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,IAAI;gBAC5B,MAAM,EAAE,OAAO;aAChB,CAAC,CAAC;QACL,CAAC;QAED,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9B,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3D,MAAM,GAAG,GAAG,OAAO,CACjB,GAAG,EAAE,CACH,IAAI,CAAC,IAAI,CACP,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CACvE,IAAI,IAAI,EACX,CAAC,KAAK,EAAE,IAAI,CAAC,CACd,CAAC;IACF,MAAM,SAAS,GAAG,kBAAkB,CAAC,SAAS,IAAI,gBAAgB,CAAC,SAAS,CAAC;IAC7E,MAAM,UAAU,GAAG,kBAAkB,CAAC,OAAO;QAC3C,CAAC,CAAC,kBAAkB,CAAC,KAAK;QAC1B,CAAC,CAAC,gBAAgB,CAAC,OAAO;YACxB,CAAC,CAAC,gBAAgB,CAAC,KAAK;YACxB,CAAC,CAAC,IAAI,CAAC;IAEX,IAAI,UAAU,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,OAAO,CACL,cAAK,SAAS,EAAC,qDAAqD,YAClE,cAAK,SAAS,EAAC,kBAAkB,YAC/B,KAAC,gBAAgB,IACf,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,GAAG,EAAE;wBACZ,KAAK,kBAAkB,CAAC,OAAO,EAAE,CAAC;wBAClC,KAAK,gBAAgB,CAAC,OAAO,EAAE,CAAC;oBAClC,CAAC,GACD,GACE,GACF,CACP,CAAC;IACJ,CAAC;IAED,IAAI,SAAS,IAAI,CAAC,GAAG,EAAE,CAAC;QACtB,OAAO,CACL,cAAK,SAAS,EAAC,qDAAqD,YAClE,eAAK,SAAS,EAAC,0DAA0D,aACvE,KAAC,QAAQ,IAAC,SAAS,EAAC,UAAU,GAAG,EACjC,KAAC,QAAQ,IAAC,SAAS,EAAC,YAAY,GAAG,EACnC,KAAC,QAAQ,IAAC,SAAS,EAAC,WAAW,GAAG,IAC9B,GACF,CACP,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,CACL,cAAK,SAAS,EAAC,qDAAqD,YAClE,eAAK,SAAS,EAAC,gDAAgD,aAC7D,KAAC,MAAM,IAAC,OAAO,QAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,OAAO,EAAC,SAAS,EAAC,YAAY,YAC9D,MAAC,IAAI,IAAC,EAAE,EAAC,OAAO,aACd,KAAC,aAAa,IAAC,IAAI,EAAE,EAAE,EAAE,SAAS,EAAC,QAAQ,GAAG,EAC7C,CAAC,CAAC,mBAAmB,CAAC,IAClB,GACA,EACT,eAAK,SAAS,EAAC,WAAW,aACxB,aAAI,SAAS,EAAC,yCAAyC,YACpD,CAAC,CAAC,4BAA4B,CAAC,GAC7B,EACL,YAAG,SAAS,EAAC,+BAA+B,YACzC,CAAC,CAAC,wCAAwC,CAAC,GAC1C,IACA,IACF,GACF,CACP,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,CACL,cAAK,SAAS,EAAC,qDAAqD,YAClE,eAAK,SAAS,EAAC,gDAAgD,aAC7D,KAAC,MAAM,IAAC,OAAO,QAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,OAAO,EAAC,SAAS,EAAC,YAAY,YAC9D,MAAC,IAAI,IAAC,EAAE,EAAC,OAAO,aACd,KAAC,aAAa,IAAC,IAAI,EAAE,EAAE,EAAE,SAAS,EAAC,QAAQ,GAAG,EAC7C,CAAC,CAAC,mBAAmB,CAAC,IAClB,GACA,EACT,eAAK,SAAS,EAAC,WAAW,aACxB,eAAK,SAAS,EAAC,mCAAmC,aAChD,aAAI,SAAS,EAAC,yCAAyC,YACpD,GAAG,CAAC,IAAI,GACN,EACL,MAAC,KAAK,IACJ,OAAO,EAAC,SAAS,EACjB,SAAS,EAAC,8EAA8E,aAExF,KAAC,cAAc,IAAC,IAAI,EAAE,EAAE,GAAI,EAC3B,CAAC,CAAC,yBAAyB,CAAC,IACvB,IACJ,EACN,aAAG,SAAS,EAAC,+BAA+B,aACzC,CAAC,CAAC,kCAAkC,CAAC,EAAE,GAAG,EAC3C,eAAM,SAAS,EAAC,2BAA2B,YAAE,GAAG,CAAC,IAAI,GAAQ,EAAC,GAAG,EAChE,CAAC,CAAC,kCAAkC,CAAC,IACpC,EACH,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAChB,KAAC,MAAM,IAAC,OAAO,kBACb,YACE,IAAI,EAAE,4BAA4B,CAAC,GAAG,CAAC,UAAU,EAAE;wCACjD,QAAQ,EAAE,SAAS;wCACnB,OAAO,EAAE,iBAAiB;qCAC3B,CAAC,EACF,MAAM,EAAC,QAAQ,EACf,GAAG,EAAC,YAAY,YAEf,CAAC,CAAC,kCAAkC,EAAE;wCACrC,YAAY,EAAE,iBAAiB;qCAChC,CAAC,GACA,GACG,CACV,CAAC,CAAC,CAAC,IAAI,IACJ,IACF,GACF,CACP,CAAC;IACJ,CAAC;IAED,OAAO,CACL,wDAEE,SAAS,EAAC,4CAA4C,YAEtD,cAAK,SAAS,EAAC,4BAA4B,YACzC,KAAC,iBAAiB,IAAC,GAAG,EAAE,GAAG,GAAI,GAC3B,GACF,CACP,CAAC;AACJ,CAAC;AAED,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAE9B,MAAM,UAAU,qBAAqB,CAAC,EACpC,WAAW,GAGZ;IACC,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAW,GAAG,EAAE,CAChE,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CACjC,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,WAAW;YAAE,OAAO;QACzB,gBAAgB,CAAC,CAAC,OAAO,EAAE,EAAE,CAC3B,CAAC,WAAW,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,KAAK,CACtE,CAAC,EACD,mBAAmB,CACpB,CACF,CAAC;IACJ,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAElB,MAAM,cAAc,GAClB,WAAW,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,WAAW,CAAC;QACjD,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,aAAa,CAAC;QACjC,CAAC,CAAC,aAAa,CAAC;IAEpB,OAAO,CACL,yDAEE,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,kCAAkC,CAAC,CAAC,CAAC,QAAQ,YAErE,cAAc,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YAC5B,MAAM,MAAM,GAAG,KAAK,KAAK,WAAW,CAAC;YACrC,OAAO,CACL,yDAE2C,KAAK,iBACjC,CAAC,MAAM,EACpB,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,QAAQ,YAE/C,KAAC,gBAAgB,IAAC,KAAK,EAAE,KAAK,GAAI,IAL7B,KAAK,CAMN,CACP,CAAC;QACJ,CAAC,CAAC,GACE,CACP,CAAC;AACJ,CAAC","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 mergeChatFirstWorkspaceApps,\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\ninterface GrantedWorkspaceAppSummary {\n id: string;\n name: string;\n url?: string | null;\n}\n\ninterface GrantedWorkspaceAppsResult {\n apps: GrantedWorkspaceAppSummary[];\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 [isDirectFallback, setIsDirectFallback] = useState(false);\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 if (isDirectFallback) setEmbedError(null);\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, isDirectFallback, 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 setIsDirectFallback(false);\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 setIsDirectFallback(true);\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 workspaceAppsQuery = useActionQuery<WorkspaceAppSummary[]>(\n \"list-workspace-apps\",\n { includeAgentCards: false },\n );\n const grantedAppsQuery = useActionQuery<GrantedWorkspaceAppsResult>(\n \"list_apps\",\n {},\n );\n const apps = useMemo(() => {\n const merged = new Map<string, WorkspaceAppSummary>();\n\n for (const app of mergeChatFirstWorkspaceApps(workspaceAppsQuery.data)) {\n merged.set(app.id.trim().toLowerCase(), app);\n }\n for (const app of grantedAppsQuery.data?.apps ?? []) {\n const id = app.id.trim();\n if (!id || merged.has(id.toLowerCase())) continue;\n merged.set(id.toLowerCase(), {\n id,\n name: app.name.trim() || id,\n path: \"\",\n url: app.url?.trim() || null,\n status: \"ready\",\n });\n }\n\n return [...merged.values()];\n }, [grantedAppsQuery.data?.apps, workspaceAppsQuery.data]);\n const app = useMemo(\n () =>\n apps.find(\n (item) => item.id.trim().toLowerCase() === appId?.trim().toLowerCase(),\n ) ?? null,\n [appId, apps],\n );\n const isLoading = workspaceAppsQuery.isLoading || grantedAppsQuery.isLoading;\n const queryError = workspaceAppsQuery.isError\n ? workspaceAppsQuery.error\n : grantedAppsQuery.isError\n ? grantedAppsQuery.error\n : null;\n\n if (queryError && !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\">\n <ActionQueryError\n error={queryError}\n onRetry={() => {\n void workspaceAppsQuery.refetch();\n void grantedAppsQuery.refetch();\n }}\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":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EACL,gBAAgB,EAChB,oBAAoB,GAErB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EACL,iBAAiB,EACjB,cAAc,GACf,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AACtD,OAAO,EAAE,4BAA4B,EAAE,MAAM,iDAAiD,CAAC;AAC/F,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEpC,OAAO,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAC;AAC7E,OAAO,EACL,2BAA2B,EAC3B,sBAAsB,EACtB,uBAAuB,EACvB,gBAAgB,GAEjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAwBzC,SAAS,4BAA4B,CAAC,KAAwB;IAC5D,OAAO;QACL,IAAI,EAAE,2BAAoC;QAC1C,KAAK;QACL,MAAM,EAAE,KAAK,KAAK,MAAM;KACzB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,+BAA+B,CAC7C,KAAa,EACb,IAAY;IAEZ,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AACjD,CAAC;AAkBD,MAAM,UAAU,iBAAiB,CAAC,EAChC,GAAG,EACH,SAAS,EACT,WAAW,GAAG,KAAK,EACnB,IAAI,GAAG,oBAAoB,GACJ;IACvB,MAAM,EAAE,aAAa,EAAE,GAAG,QAAQ,EAAE,CAAC;IACrC,MAAM,KAAK,GACT,aAAa,KAAK,MAAM,IAAI,aAAa,KAAK,OAAO;QACnD,CAAC,CAAC,aAAa;QACf,CAAC,CAAC,OAAO,QAAQ,KAAK,WAAW;YAC7B,QAAQ,CAAC,eAAe,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC;YACrD,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,OAAO,CAAC;IAChB,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAC9D,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IACjE,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAChE,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IACpD,MAAM,aAAa,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IACtD,MAAM,gBAAgB,GAAG,WAAW,CAAC,GAAG,EAAE;QACxC,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE,WAAW,CAC/C,4BAA4B,CAAC,KAAK,CAAC,EACnC,GAAG,CACJ,CAAC;IACJ,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACZ,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE;QACvC,gBAAgB,EAAE,CAAC;QACnB,IAAI,gBAAgB;YAAE,aAAa,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC,EAAE,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC,CAAC;IACzC,MAAM,mBAAmB,GAAG,cAAc,CAAC,2BAA2B,CAAC,GAAG,CAAC,CAAC;IAC5E,MAAM,kBAAkB,GAAG,iBAAiB,CAG1C,sBAAsB,EAAE;QACxB,2BAA2B,EAAE,IAAI;KAClC,CAAC,CAAC;IACH,MAAM,8BAA8B,GAAG,iBAAiB,CAGtD,oCAAoC,EAAE;QACtC,2BAA2B,EAAE,IAAI;KAClC,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,gBAAgB,CAAC;QAC/B,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE;QACpB,GAAG,EAAE,GAAG,CAAC,GAAG;KACb,CAAC,CAAC;IACH,MAAM,UAAU,GAAG,OAAO,CAA2B,GAAG,EAAE;QACxD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,OAAO,+BAA+B,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;QAC5D,CAAC;QACD,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC1B,OAAO;YACL,GAAG,EAAE,GAAG,CAAC,EAAE;YACX,GAAG,uBAAuB,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YAClE,MAAM,EAAE,SAAS;SAClB,CAAC;IACJ,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;IAEpD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,UAAU;YAAE,OAAO;QACxB,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,WAAW,CAAC,IAAI,CAAC,CAAC;QAClB,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,mBAAmB,CAAC,KAAK,CAAC,CAAC;QAC3B,MAAM,aAAa,GAAG,mBAAmB;YACvC,CAAC,CAAC,8BAA8B;YAChC,CAAC,CAAC,kBAAkB,CAAC;QACvB,KAAK,aAAa;aACf,WAAW,CAAC,UAAU,CAAC;aACvB,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YACf,IAAI,CAAC,SAAS;gBAAE,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC/C,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACxB,IAAI,SAAS;gBAAE,OAAO;YACtB,MAAM,KAAK,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACxE,IAAI,mBAAmB,EAAE,CAAC;gBACxB,8DAA8D;gBAC9D,gEAAgE;gBAChE,8DAA8D;gBAC9D,cAAc;gBACd,mBAAmB,CAAC,KAAK,CAAC,CAAC;gBAC3B,WAAW,CAAC,IAAI,CAAC,CAAC;gBAClB,aAAa,CAAC,KAAK,CAAC,CAAC;gBACrB,OAAO;YACT,CAAC;YACD,mBAAmB,CAAC,IAAI,CAAC,CAAC;YAC1B,WAAW,CACT,sBAAsB,CACpB,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,EACtC,SAAS,IAAI,GAAG,CACjB,CACF,CAAC;YACF,aAAa,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC,CAAC,CAAC;QACL,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC,CAAC;IACJ,CAAC,EAAE;QACD,GAAG,CAAC,EAAE;QACN,GAAG,CAAC,IAAI;QACR,GAAG,CAAC,GAAG;QACP,kBAAkB,CAAC,WAAW;QAC9B,8BAA8B,CAAC,WAAW;QAC1C,UAAU;QACV,SAAS;QACT,YAAY;QACZ,mBAAmB;KACpB,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,yBAAyB,GAAG,CAAC,KAAmB,EAAE,EAAE;YACxD,IACE,CAAC,4BAA4B,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,EACrE,CAAC;gBACD,OAAO;YACT,CAAC;YACD,eAAe,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;QAC5C,CAAC,CAAC;QAEF,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC;QAC9D,OAAO,GAAG,EAAE,CACV,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC;IACrE,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf,SAAS,CAAC,GAAG,EAAE;QACb,gBAAgB,EAAE,CAAC;IACrB,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAEjC,MAAM,OAAO,GAAG,CACd,KAAC,gBAAgB,IACf,GAAG,EAAE,GAAG,EACR,MAAM,EACJ,QAAQ;YACN,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,UAAU;gBACV,CAAC,CAAC,OAAO;gBACT,CAAC,CAAC,UAAU;oBACV,CAAC,CAAC,SAAS;oBACX,CAAC,CAAC,YAAY,EAEtB,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,UAAU,EAAE,OAAO,EACjC,OAAO,EACL,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,EAE1E,WAAW,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAC/B,4DAGE,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,KAAK,IAAI,GAAG,CAAC,IAAI,EACxB,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,eAAe,EACvB,cAAc,EAAC,aAAa,EAC5B,KAAK,EAAC,iCAAiC,EACvC,SAAS,EAAC,sCAAsC,IAR3C,GAAG,GAAG,GAAG,GAAG,YAAY,CAS7B,CACH,EACD,IAAI,EAAE,IAAI,GACV,CACH,CAAC;IAEF,IAAI,CAAC,WAAW;QAAE,OAAO,OAAO,CAAC;IAEjC,OAAO,CACL,KAAC,YAAY,IACX,QAAQ,EAAC,MAAM,EACf,WAAW,QACX,cAAc,EAAC,mBAAmB,EAClC,UAAU,EAAE,qBAAqB,GAAG,CAAC,EAAE,EAAE,EACzC,KAAK,EAAE;YACL,IAAI,EAAE,eAAe;YACrB,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,KAAK,EAAE,GAAG,CAAC,IAAI;YACf,UAAU,EAAE,iBAAiB,GAAG,CAAC,EAAE,EAAE;SACtC,EACD,gBAAgB,EAAC,KAAK,EACtB,UAAU,QACV,qBAAqB,QACrB,kBAAkB,EAAE,KAAK,EACzB,WAAW,EAAE,EAAE,EACf,cAAc,EAAE,aAAa,GAAG,CAAC,IAAI,EAAE,YAEtC,OAAO,GACK,CAChB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAE,KAAK,EAAsB;IAC5D,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,kBAAkB,GAAG,cAAc,CACvC,qBAAqB,EACrB,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAC7B,CAAC;IACF,MAAM,gBAAgB,GAAG,cAAc,CACrC,WAAW,EACX,EAAE,CACH,CAAC;IACF,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE;QACxB,MAAM,MAAM,GAAG,IAAI,GAAG,EAA+B,CAAC;QAEtD,KAAK,MAAM,GAAG,IAAI,2BAA2B,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;YACvE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,CAAC;QAC/C,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,gBAAgB,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC;YACpD,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;YACzB,IAAI,CAAC,EAAE,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC;gBAAE,SAAS;YAClD,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,EAAE;gBAC3B,EAAE;gBACF,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE;gBAC3B,IAAI,EAAE,EAAE;gBACR,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,IAAI;gBAC5B,MAAM,EAAE,OAAO;aAChB,CAAC,CAAC;QACL,CAAC;QAED,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9B,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3D,MAAM,GAAG,GAAG,OAAO,CACjB,GAAG,EAAE,CACH,IAAI,CAAC,IAAI,CACP,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CACvE,IAAI,IAAI,EACX,CAAC,KAAK,EAAE,IAAI,CAAC,CACd,CAAC;IACF,MAAM,SAAS,GAAG,kBAAkB,CAAC,SAAS,IAAI,gBAAgB,CAAC,SAAS,CAAC;IAC7E,MAAM,UAAU,GAAG,kBAAkB,CAAC,OAAO;QAC3C,CAAC,CAAC,kBAAkB,CAAC,KAAK;QAC1B,CAAC,CAAC,gBAAgB,CAAC,OAAO;YACxB,CAAC,CAAC,gBAAgB,CAAC,KAAK;YACxB,CAAC,CAAC,IAAI,CAAC;IAEX,IAAI,UAAU,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,OAAO,CACL,cAAK,SAAS,EAAC,qDAAqD,YAClE,cAAK,SAAS,EAAC,kBAAkB,YAC/B,KAAC,gBAAgB,IACf,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,GAAG,EAAE;wBACZ,KAAK,kBAAkB,CAAC,OAAO,EAAE,CAAC;wBAClC,KAAK,gBAAgB,CAAC,OAAO,EAAE,CAAC;oBAClC,CAAC,GACD,GACE,GACF,CACP,CAAC;IACJ,CAAC;IAED,IAAI,SAAS,IAAI,CAAC,GAAG,EAAE,CAAC;QACtB,OAAO,CACL,cAAK,SAAS,EAAC,qDAAqD,YAClE,eAAK,SAAS,EAAC,0DAA0D,aACvE,KAAC,QAAQ,IAAC,SAAS,EAAC,UAAU,GAAG,EACjC,KAAC,QAAQ,IAAC,SAAS,EAAC,YAAY,GAAG,EACnC,KAAC,QAAQ,IAAC,SAAS,EAAC,WAAW,GAAG,IAC9B,GACF,CACP,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,CACL,cAAK,SAAS,EAAC,qDAAqD,YAClE,eAAK,SAAS,EAAC,gDAAgD,aAC7D,KAAC,MAAM,IAAC,OAAO,QAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,OAAO,EAAC,SAAS,EAAC,YAAY,YAC9D,MAAC,IAAI,IAAC,EAAE,EAAC,OAAO,aACd,KAAC,aAAa,IAAC,IAAI,EAAE,EAAE,EAAE,SAAS,EAAC,QAAQ,GAAG,EAC7C,CAAC,CAAC,mBAAmB,CAAC,IAClB,GACA,EACT,eAAK,SAAS,EAAC,WAAW,aACxB,aAAI,SAAS,EAAC,yCAAyC,YACpD,CAAC,CAAC,4BAA4B,CAAC,GAC7B,EACL,YAAG,SAAS,EAAC,+BAA+B,YACzC,CAAC,CAAC,wCAAwC,CAAC,GAC1C,IACA,IACF,GACF,CACP,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,CACL,cAAK,SAAS,EAAC,qDAAqD,YAClE,eAAK,SAAS,EAAC,gDAAgD,aAC7D,KAAC,MAAM,IAAC,OAAO,QAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,OAAO,EAAC,SAAS,EAAC,YAAY,YAC9D,MAAC,IAAI,IAAC,EAAE,EAAC,OAAO,aACd,KAAC,aAAa,IAAC,IAAI,EAAE,EAAE,EAAE,SAAS,EAAC,QAAQ,GAAG,EAC7C,CAAC,CAAC,mBAAmB,CAAC,IAClB,GACA,EACT,eAAK,SAAS,EAAC,WAAW,aACxB,eAAK,SAAS,EAAC,mCAAmC,aAChD,aAAI,SAAS,EAAC,yCAAyC,YACpD,GAAG,CAAC,IAAI,GACN,EACL,MAAC,KAAK,IACJ,OAAO,EAAC,SAAS,EACjB,SAAS,EAAC,8EAA8E,aAExF,KAAC,cAAc,IAAC,IAAI,EAAE,EAAE,GAAI,EAC3B,CAAC,CAAC,yBAAyB,CAAC,IACvB,IACJ,EACN,aAAG,SAAS,EAAC,+BAA+B,aACzC,CAAC,CAAC,kCAAkC,CAAC,EAAE,GAAG,EAC3C,eAAM,SAAS,EAAC,2BAA2B,YAAE,GAAG,CAAC,IAAI,GAAQ,EAAC,GAAG,EAChE,CAAC,CAAC,kCAAkC,CAAC,IACpC,EACH,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAChB,KAAC,MAAM,IAAC,OAAO,kBACb,YACE,IAAI,EAAE,4BAA4B,CAAC,GAAG,CAAC,UAAU,EAAE;wCACjD,QAAQ,EAAE,SAAS;wCACnB,OAAO,EAAE,iBAAiB;qCAC3B,CAAC,EACF,MAAM,EAAC,QAAQ,EACf,GAAG,EAAC,YAAY,YAEf,CAAC,CAAC,kCAAkC,EAAE;wCACrC,YAAY,EAAE,iBAAiB;qCAChC,CAAC,GACA,GACG,CACV,CAAC,CAAC,CAAC,IAAI,IACJ,IACF,GACF,CACP,CAAC;IACJ,CAAC;IAED,OAAO,CACL,wDAEE,SAAS,EAAC,4CAA4C,YAEtD,cAAK,SAAS,EAAC,4BAA4B,YACzC,KAAC,iBAAiB,IAAC,GAAG,EAAE,GAAG,GAAI,GAC3B,GACF,CACP,CAAC;AACJ,CAAC;AAED,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAE9B,MAAM,UAAU,qBAAqB,CAAC,EACpC,WAAW,GAGZ;IACC,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAW,GAAG,EAAE,CAChE,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CACjC,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,WAAW;YAAE,OAAO;QACzB,gBAAgB,CAAC,CAAC,OAAO,EAAE,EAAE,CAC3B,CAAC,WAAW,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,KAAK,CACtE,CAAC,EACD,mBAAmB,CACpB,CACF,CAAC;IACJ,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAElB,MAAM,cAAc,GAClB,WAAW,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,WAAW,CAAC;QACjD,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,aAAa,CAAC;QACjC,CAAC,CAAC,aAAa,CAAC;IAEpB,OAAO,CACL,yDAEE,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,kCAAkC,CAAC,CAAC,CAAC,QAAQ,YAErE,cAAc,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YAC5B,MAAM,MAAM,GAAG,KAAK,KAAK,WAAW,CAAC;YACrC,OAAO,CACL,yDAE2C,KAAK,iBACjC,CAAC,MAAM,EACpB,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,QAAQ,YAE/C,KAAC,gBAAgB,IAAC,KAAK,EAAE,KAAK,GAAI,IAL7B,KAAK,CAMN,CACP,CAAC;QACJ,CAAC,CAAC,GACE,CACP,CAAC;AACJ,CAAC","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 { 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 mergeChatFirstWorkspaceApps,\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\ninterface GrantedWorkspaceAppSummary {\n id: string;\n name: string;\n url?: string | null;\n}\n\ninterface GrantedWorkspaceAppsResult {\n apps: GrantedWorkspaceAppSummary[];\n}\n\ntype WorkspaceAppTheme = \"light\" | \"dark\";\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 [isDirectFallback, setIsDirectFallback] = useState(false);\n const [embedAttempt, setEmbedAttempt] = useState(0);\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 if (isDirectFallback) setEmbedError(null);\n }, [isDirectFallback, 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 setIsDirectFallback(false);\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 const error = cause instanceof Error ? cause : new Error(String(cause));\n if (workspaceSsoEnabled) {\n // An SSO-enabled pane must never fall back to the child app's\n // unauthenticated shell. Keep the parent-owned retry surface in\n // place so a transient exchange failure cannot expose another\n // login form.\n setIsDirectFallback(false);\n setEmbedUrl(null);\n setEmbedError(error);\n return;\n }\n setIsDirectFallback(true);\n setEmbedUrl(\n workspaceAppDirectHref(\n { path: app.path ?? \"\", url: app.url },\n embedPath ?? \"/\",\n ),\n );\n setEmbedError(error);\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 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 >\n {appPane}\n </AgentSidebar>\n );\n}\n\nexport function WorkspaceAppHost({ appId }: { appId?: string }) {\n const t = useT();\n const workspaceAppsQuery = useActionQuery<WorkspaceAppSummary[]>(\n \"list-workspace-apps\",\n { includeAgentCards: false },\n );\n const grantedAppsQuery = useActionQuery<GrantedWorkspaceAppsResult>(\n \"list_apps\",\n {},\n );\n const apps = useMemo(() => {\n const merged = new Map<string, WorkspaceAppSummary>();\n\n for (const app of mergeChatFirstWorkspaceApps(workspaceAppsQuery.data)) {\n merged.set(app.id.trim().toLowerCase(), app);\n }\n for (const app of grantedAppsQuery.data?.apps ?? []) {\n const id = app.id.trim();\n if (!id || merged.has(id.toLowerCase())) continue;\n merged.set(id.toLowerCase(), {\n id,\n name: app.name.trim() || id,\n path: \"\",\n url: app.url?.trim() || null,\n status: \"ready\",\n });\n }\n\n return [...merged.values()];\n }, [grantedAppsQuery.data?.apps, workspaceAppsQuery.data]);\n const app = useMemo(\n () =>\n apps.find(\n (item) => item.id.trim().toLowerCase() === appId?.trim().toLowerCase(),\n ) ?? null,\n [appId, apps],\n );\n const isLoading = workspaceAppsQuery.isLoading || grantedAppsQuery.isLoading;\n const queryError = workspaceAppsQuery.isError\n ? workspaceAppsQuery.error\n : grantedAppsQuery.isError\n ? grantedAppsQuery.error\n : null;\n\n if (queryError && !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\">\n <ActionQueryError\n error={queryError}\n onRetry={() => {\n void workspaceAppsQuery.refetch();\n void grantedAppsQuery.refetch();\n }}\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 +1 @@
1
- {"version":3,"file":"workspace-app-action-auth.d.ts","sourceRoot":"","sources":["../../../src/server/lib/workspace-app-action-auth.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,sBAAsB,EAEvB,MAAM,2BAA2B,CAAC;AAEnC,eAAO,MAAM,0BAA0B,+CACO,CAAC;AAgC/C;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,EAAE,sBAuDzC,CAAC"}
1
+ {"version":3,"file":"workspace-app-action-auth.d.ts","sourceRoot":"","sources":["../../../src/server/lib/workspace-app-action-auth.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,sBAAsB,EAEvB,MAAM,2BAA2B,CAAC;AAGnC,eAAO,MAAM,0BAA0B,+CACO,CAAC;AAgC/C;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,EAAE,sBAuEzC,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { verifyA2AToken } from "@agent-native/core/a2a";
2
2
  import { isOrgMember, resolveOrgByDomain, resolveOrgIdForEmail, } from "@agent-native/core/org";
3
+ import { getSession } from "@agent-native/core/server";
3
4
  export const WORKSPACE_APPS_ACTION_PATH = "/_agent-native/actions/list-workspace-apps";
4
5
  function isWorkspaceAppsActionPath(event) {
5
6
  const rawUrl = (typeof event?.context?._mountedPathname === "string" &&
@@ -38,6 +39,20 @@ export const workspaceAppActionRouteAuth = {
38
39
  if (!authorization.startsWith("Bearer ")) {
39
40
  throw new Error("Invalid workspace registry authorization");
40
41
  }
42
+ // Native clients use their normal persisted session bearer for the same
43
+ // read-only action. Let the framework resolve that credential before
44
+ // treating an otherwise opaque bearer as an A2A token.
45
+ const hasRequestUrl = typeof event?.node?.req?.url === "string" ||
46
+ typeof event?.req?.url === "string" ||
47
+ typeof event?.url === "string";
48
+ const session = hasRequestUrl ? await getSession(event) : null;
49
+ if (session?.email) {
50
+ return {
51
+ owner: session.email,
52
+ anonymous: false,
53
+ ...(session.orgId ? { orgId: session.orgId } : {}),
54
+ };
55
+ }
41
56
  const token = authorization.slice("Bearer ".length).trim();
42
57
  if (!token)
43
58
  throw new Error("Invalid workspace registry authorization");
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-app-action-auth.js","sourceRoot":"","sources":["../../../src/server/lib/workspace-app-action-auth.ts"],"names":[],"mappings":"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","sourcesContent":["import { verifyA2AToken } from \"@agent-native/core/a2a\";\nimport {\n isOrgMember,\n resolveOrgByDomain,\n resolveOrgIdForEmail,\n} from \"@agent-native/core/org\";\nimport type {\n ActionRouteAuthAdapter,\n ActionRouteResolvedCaller,\n} from \"@agent-native/core/server\";\n\nexport const WORKSPACE_APPS_ACTION_PATH =\n \"/_agent-native/actions/list-workspace-apps\";\n\nfunction isWorkspaceAppsActionPath(event: any): boolean {\n const rawUrl =\n (typeof event?.context?._mountedPathname === \"string\" &&\n event.context._mountedPathname) ||\n (typeof event?.path === \"string\" && event.path) ||\n (typeof event?.url?.pathname === \"string\" && event.url.pathname) ||\n (typeof event?.node?.req?.url === \"string\" && event.node.req.url) ||\n (typeof event?.req?.url === \"string\" && event.req.url) ||\n \"/\";\n const requestPath =\n String(rawUrl).split(\"?\", 1)[0].replace(/\\/+$/, \"\") || \"/\";\n const appBasePath = process.env.APP_BASE_PATH?.trim().replace(/\\/+$/, \"\");\n return (\n requestPath === WORKSPACE_APPS_ACTION_PATH ||\n Boolean(\n appBasePath &&\n `${appBasePath}${WORKSPACE_APPS_ACTION_PATH}` === requestPath,\n )\n );\n}\n\nfunction readAuthorizationHeader(event: any): string | undefined {\n const headers = event?.node?.req?.headers ?? event?.req?.headers;\n if (typeof headers?.get === \"function\") {\n return headers.get(\"authorization\") ?? undefined;\n }\n const value = headers?.authorization ?? headers?.Authorization;\n return Array.isArray(value) ? value[0] : value;\n}\n\n/**\n * The hosted workspace registry is a read-only action that accepts a caller\n * JWT from Dispatch. Keep this resolver path-scoped so the shared secret never\n * becomes a bearer credential for unrelated Dispatch actions.\n */\nexport const workspaceAppActionRouteAuth: ActionRouteAuthAdapter = {\n resolveCaller: async (event): Promise<ActionRouteResolvedCaller | null> => {\n if (!isWorkspaceAppsActionPath(event)) return null;\n\n const authorization = readAuthorizationHeader(event);\n if (!authorization) return null;\n if (!authorization.startsWith(\"Bearer \")) {\n throw new Error(\"Invalid workspace registry authorization\");\n }\n\n const token = authorization.slice(\"Bearer \".length).trim();\n if (!token) throw new Error(\"Invalid workspace registry authorization\");\n\n const identity = await verifyA2AToken(token, event);\n if (!identity.email) {\n throw new Error(\"Invalid workspace registry authorization\");\n }\n\n const orgDomain = identity.orgDomain?.trim().toLowerCase();\n const claimedOrgId = identity.orgId?.trim();\n let orgId: string | null;\n if (claimedOrgId) {\n // A signed org_id is authoritative when the sender had an org-scoped\n // request but could not resolve a domain. If both claims exist, reject\n // a mismatch rather than allowing either claim to widen scope.\n if (orgDomain) {\n const org = await resolveOrgByDomain(orgDomain);\n if (org && org.orgId !== claimedOrgId) {\n throw new Error(\"Invalid workspace registry authorization\");\n }\n }\n if (!(await isOrgMember(claimedOrgId, identity.email))) {\n throw new Error(\"Invalid workspace registry authorization\");\n }\n orgId = claimedOrgId;\n } else if (orgDomain) {\n // A verified domain claim identifies the caller's intended org. Resolve\n // it locally instead of falling back to the receiver's active-org or\n // first-membership selection, which can be wrong for multi-org users.\n const org = await resolveOrgByDomain(orgDomain);\n if (!org || !(await isOrgMember(org.orgId, identity.email))) {\n throw new Error(\"Invalid workspace registry authorization\");\n }\n orgId = org.orgId;\n } else {\n // Preserve compatibility with legacy tokens that predate org_domain.\n orgId = await resolveOrgIdForEmail(identity.email);\n }\n\n return {\n owner: identity.email,\n anonymous: false,\n orgId,\n };\n },\n};\n"]}
1
+ {"version":3,"file":"workspace-app-action-auth.js","sourceRoot":"","sources":["../../../src/server/lib/workspace-app-action-auth.ts"],"names":[],"mappings":"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","sourcesContent":["import { verifyA2AToken } from \"@agent-native/core/a2a\";\nimport {\n isOrgMember,\n resolveOrgByDomain,\n resolveOrgIdForEmail,\n} from \"@agent-native/core/org\";\nimport type {\n ActionRouteAuthAdapter,\n ActionRouteResolvedCaller,\n} from \"@agent-native/core/server\";\nimport { getSession } from \"@agent-native/core/server\";\n\nexport const WORKSPACE_APPS_ACTION_PATH =\n \"/_agent-native/actions/list-workspace-apps\";\n\nfunction isWorkspaceAppsActionPath(event: any): boolean {\n const rawUrl =\n (typeof event?.context?._mountedPathname === \"string\" &&\n event.context._mountedPathname) ||\n (typeof event?.path === \"string\" && event.path) ||\n (typeof event?.url?.pathname === \"string\" && event.url.pathname) ||\n (typeof event?.node?.req?.url === \"string\" && event.node.req.url) ||\n (typeof event?.req?.url === \"string\" && event.req.url) ||\n \"/\";\n const requestPath =\n String(rawUrl).split(\"?\", 1)[0].replace(/\\/+$/, \"\") || \"/\";\n const appBasePath = process.env.APP_BASE_PATH?.trim().replace(/\\/+$/, \"\");\n return (\n requestPath === WORKSPACE_APPS_ACTION_PATH ||\n Boolean(\n appBasePath &&\n `${appBasePath}${WORKSPACE_APPS_ACTION_PATH}` === requestPath,\n )\n );\n}\n\nfunction readAuthorizationHeader(event: any): string | undefined {\n const headers = event?.node?.req?.headers ?? event?.req?.headers;\n if (typeof headers?.get === \"function\") {\n return headers.get(\"authorization\") ?? undefined;\n }\n const value = headers?.authorization ?? headers?.Authorization;\n return Array.isArray(value) ? value[0] : value;\n}\n\n/**\n * The hosted workspace registry is a read-only action that accepts a caller\n * JWT from Dispatch. Keep this resolver path-scoped so the shared secret never\n * becomes a bearer credential for unrelated Dispatch actions.\n */\nexport const workspaceAppActionRouteAuth: ActionRouteAuthAdapter = {\n resolveCaller: async (event): Promise<ActionRouteResolvedCaller | null> => {\n if (!isWorkspaceAppsActionPath(event)) return null;\n\n const authorization = readAuthorizationHeader(event);\n if (!authorization) return null;\n if (!authorization.startsWith(\"Bearer \")) {\n throw new Error(\"Invalid workspace registry authorization\");\n }\n\n // Native clients use their normal persisted session bearer for the same\n // read-only action. Let the framework resolve that credential before\n // treating an otherwise opaque bearer as an A2A token.\n const hasRequestUrl =\n typeof event?.node?.req?.url === \"string\" ||\n typeof event?.req?.url === \"string\" ||\n typeof event?.url === \"string\";\n const session = hasRequestUrl ? await getSession(event) : null;\n if (session?.email) {\n return {\n owner: session.email,\n anonymous: false,\n ...(session.orgId ? { orgId: session.orgId } : {}),\n };\n }\n\n const token = authorization.slice(\"Bearer \".length).trim();\n if (!token) throw new Error(\"Invalid workspace registry authorization\");\n\n const identity = await verifyA2AToken(token, event);\n if (!identity.email) {\n throw new Error(\"Invalid workspace registry authorization\");\n }\n\n const orgDomain = identity.orgDomain?.trim().toLowerCase();\n const claimedOrgId = identity.orgId?.trim();\n let orgId: string | null;\n if (claimedOrgId) {\n // A signed org_id is authoritative when the sender had an org-scoped\n // request but could not resolve a domain. If both claims exist, reject\n // a mismatch rather than allowing either claim to widen scope.\n if (orgDomain) {\n const org = await resolveOrgByDomain(orgDomain);\n if (org && org.orgId !== claimedOrgId) {\n throw new Error(\"Invalid workspace registry authorization\");\n }\n }\n if (!(await isOrgMember(claimedOrgId, identity.email))) {\n throw new Error(\"Invalid workspace registry authorization\");\n }\n orgId = claimedOrgId;\n } else if (orgDomain) {\n // A verified domain claim identifies the caller's intended org. Resolve\n // it locally instead of falling back to the receiver's active-org or\n // first-membership selection, which can be wrong for multi-org users.\n const org = await resolveOrgByDomain(orgDomain);\n if (!org || !(await isOrgMember(org.orgId, identity.email))) {\n throw new Error(\"Invalid workspace registry authorization\");\n }\n orgId = org.orgId;\n } else {\n // Preserve compatibility with legacy tokens that predate org_domain.\n orgId = await resolveOrgIdForEmail(identity.email);\n }\n\n return {\n owner: identity.email,\n anonymous: false,\n orgId,\n };\n },\n};\n"]}
@@ -4,4 +4,5 @@
4
4
  * path and keeps the legacy embed route available while the flag is off.
5
5
  */
6
6
  export declare const DISPATCH_WORKSPACE_SSO_FLAG: import("@agent-native/core/feature-flags").FeatureFlagDefinition;
7
+ export declare const DISPATCH_WORKSPACE_APP_LIST_FLAG: import("@agent-native/core/feature-flags").FeatureFlagDefinition;
7
8
  //# sourceMappingURL=feature-flags.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"feature-flags.d.ts","sourceRoot":"","sources":["../../src/shared/feature-flags.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,kEAKtC,CAAC"}
1
+ {"version":3,"file":"feature-flags.d.ts","sourceRoot":"","sources":["../../src/shared/feature-flags.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,kEAKtC,CAAC;AAEH,eAAO,MAAM,gCAAgC,kEAK3C,CAAC"}
@@ -9,4 +9,9 @@ export const DISPATCH_WORKSPACE_SSO_FLAG = defineFeatureFlag({
9
9
  displayName: "Dispatch workspace sign-in",
10
10
  description: "Let Dispatch use the signed-in workspace identity for exact registered app panes.",
11
11
  });
12
+ export const DISPATCH_WORKSPACE_APP_LIST_FLAG = defineFeatureFlag({
13
+ key: "dispatch.workspace-app-list",
14
+ displayName: "Workspace app list",
15
+ description: "Show apps from the signed-in workspace in native desktop and mobile app lists.",
16
+ });
12
17
  //# sourceMappingURL=feature-flags.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"feature-flags.js","sourceRoot":"","sources":["../../src/shared/feature-flags.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAErE;;;;GAIG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,iBAAiB,CAAC;IAC3D,GAAG,EAAE,wBAAwB;IAC7B,WAAW,EAAE,4BAA4B;IACzC,WAAW,EACT,mFAAmF;CACtF,CAAC,CAAC","sourcesContent":["import { defineFeatureFlag } from \"@agent-native/core/feature-flags\";\n\n/**\n * Browser-only rollout for app-scoped sessions in Dispatch panes. The server\n * action checks this flag too; the client hook only selects the presentation\n * path and keeps the legacy embed route available while the flag is off.\n */\nexport const DISPATCH_WORKSPACE_SSO_FLAG = defineFeatureFlag({\n key: \"dispatch.workspace-sso\",\n displayName: \"Dispatch workspace sign-in\",\n description:\n \"Let Dispatch use the signed-in workspace identity for exact registered app panes.\",\n});\n"]}
1
+ {"version":3,"file":"feature-flags.js","sourceRoot":"","sources":["../../src/shared/feature-flags.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAErE;;;;GAIG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,iBAAiB,CAAC;IAC3D,GAAG,EAAE,wBAAwB;IAC7B,WAAW,EAAE,4BAA4B;IACzC,WAAW,EACT,mFAAmF;CACtF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,gCAAgC,GAAG,iBAAiB,CAAC;IAChE,GAAG,EAAE,6BAA6B;IAClC,WAAW,EAAE,oBAAoB;IACjC,WAAW,EACT,gFAAgF;CACnF,CAAC,CAAC","sourcesContent":["import { defineFeatureFlag } from \"@agent-native/core/feature-flags\";\n\n/**\n * Browser-only rollout for app-scoped sessions in Dispatch panes. The server\n * action checks this flag too; the client hook only selects the presentation\n * path and keeps the legacy embed route available while the flag is off.\n */\nexport const DISPATCH_WORKSPACE_SSO_FLAG = defineFeatureFlag({\n key: \"dispatch.workspace-sso\",\n displayName: \"Dispatch workspace sign-in\",\n description:\n \"Let Dispatch use the signed-in workspace identity for exact registered app panes.\",\n});\n\nexport const DISPATCH_WORKSPACE_APP_LIST_FLAG = defineFeatureFlag({\n key: \"dispatch.workspace-app-list\",\n displayName: \"Workspace app list\",\n description:\n \"Show apps from the signed-in workspace in native desktop and mobile app lists.\",\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/dispatch",
3
- "version": "0.27.6",
3
+ "version": "0.27.7",
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": {
@@ -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.17"
104
+ "@agent-native/core": "0.157.18"
105
105
  },
106
106
  "peerDependencies": {
107
107
  "@agent-native/core": ">=0.8.0",
@@ -332,6 +332,28 @@ describe("WorkspaceAppKeepAlive", () => {
332
332
  ).toBe("");
333
333
  });
334
334
 
335
+ it("does not expose a child login page when the workspace SSO exchange fails", async () => {
336
+ clientState.workspaceSsoEnabled = true;
337
+ clientState.workspaceSsoMutateAsync.mockRejectedValue(
338
+ new Error("Workspace SSO is temporarily unavailable"),
339
+ );
340
+
341
+ await act(async () => {
342
+ root.render(
343
+ <WorkspaceAppFrame app={{ id: "mail", name: "Mail", path: "/mail" }} />,
344
+ );
345
+ await Promise.resolve();
346
+ await Promise.resolve();
347
+ });
348
+
349
+ expect(container.querySelector("iframe")).toBeNull();
350
+ expect(
351
+ container
352
+ .querySelector("[data-chat-first-app-error]")
353
+ ?.getAttribute("data-chat-first-app-error"),
354
+ ).toBe("Workspace SSO is temporarily unavailable");
355
+ });
356
+
335
357
  it("evicts the oldest inactive app after reaching the keep-alive limit", async () => {
336
358
  await act(async () => {
337
359
  root.render(<WorkspaceAppKeepAlive activeAppId="mail" />);
@@ -11,12 +11,7 @@ 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
- IconArrowLeft,
16
- IconArrowUpRight,
17
- IconClockHour4,
18
- IconLock,
19
- } from "@tabler/icons-react";
14
+ import { IconArrowLeft, IconClockHour4 } from "@tabler/icons-react";
20
15
  import { useTheme } from "next-themes";
21
16
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
22
17
  import { Link } from "react-router";
@@ -57,31 +52,6 @@ interface GrantedWorkspaceAppsResult {
57
52
  }
58
53
 
59
54
  type WorkspaceAppTheme = "light" | "dark";
60
- type WorkspaceAppAuthState = "unknown" | "authenticated" | "unauthenticated";
61
-
62
- function resolveWorkspaceAppAuthState(
63
- rawUrl: string | null,
64
- ): WorkspaceAppAuthState {
65
- if (!rawUrl) return "unknown";
66
- try {
67
- const lastSegment = new URL(rawUrl, window.location.origin).pathname
68
- .split("/")
69
- .filter(Boolean)
70
- .at(-1)
71
- ?.toLowerCase();
72
- if (
73
- lastSegment === "sign-in" ||
74
- lastSegment === "login" ||
75
- lastSegment === "signup"
76
- ) {
77
- return "unauthenticated";
78
- }
79
- return "authenticated";
80
- } catch {
81
- return "unknown";
82
- }
83
- }
84
-
85
55
  function buildWorkspaceAppThemeUpdate(theme: WorkspaceAppTheme) {
86
56
  return {
87
57
  type: "agent-native-theme-update" as const,
@@ -131,7 +101,6 @@ export function WorkspaceAppFrame({
131
101
  const [embedError, setEmbedError] = useState<Error | null>(null);
132
102
  const [isDirectFallback, setIsDirectFallback] = useState(false);
133
103
  const [embedAttempt, setEmbedAttempt] = useState(0);
134
- const [authState, setAuthState] = useState<WorkspaceAppAuthState>("unknown");
135
104
  const embedFrameRef = useRef<HTMLIFrameElement>(null);
136
105
  const postThemeToFrame = useCallback(() => {
137
106
  embedFrameRef.current?.contentWindow?.postMessage(
@@ -142,17 +111,7 @@ export function WorkspaceAppFrame({
142
111
  const handleFrameLoad = useCallback(() => {
143
112
  postThemeToFrame();
144
113
  if (isDirectFallback) setEmbedError(null);
145
- const frame = embedFrameRef.current;
146
- let frameUrl = embedUrl;
147
- try {
148
- frameUrl = frame?.contentWindow?.location.href ?? frameUrl;
149
- // coercion-ok: Cross-origin frames cannot expose location; their auth state arrives by message.
150
- } catch {
151
- // Cross-origin app frames report their auth state through postMessage.
152
- }
153
- const nextAuthState = resolveWorkspaceAppAuthState(frameUrl);
154
- if (nextAuthState !== "unknown") setAuthState(nextAuthState);
155
- }, [embedUrl, isDirectFallback, postThemeToFrame]);
114
+ }, [isDirectFallback, postThemeToFrame]);
156
115
  const workspaceSsoEnabled = useFeatureFlag(DISPATCH_WORKSPACE_SSO_FLAG.key);
157
116
  const createEmbedSession = useActionMutation<
158
117
  EmbedSessionResult,
@@ -190,7 +149,6 @@ export function WorkspaceAppFrame({
190
149
  setEmbedUrl(null);
191
150
  setEmbedError(null);
192
151
  setIsDirectFallback(false);
193
- setAuthState("unknown");
194
152
  const createSession = workspaceSsoEnabled
195
153
  ? createWorkspaceSsoEmbedSession
196
154
  : createEmbedSession;
@@ -201,6 +159,17 @@ export function WorkspaceAppFrame({
201
159
  })
202
160
  .catch((cause: unknown) => {
203
161
  if (cancelled) return;
162
+ const error = cause instanceof Error ? cause : new Error(String(cause));
163
+ if (workspaceSsoEnabled) {
164
+ // An SSO-enabled pane must never fall back to the child app's
165
+ // unauthenticated shell. Keep the parent-owned retry surface in
166
+ // place so a transient exchange failure cannot expose another
167
+ // login form.
168
+ setIsDirectFallback(false);
169
+ setEmbedUrl(null);
170
+ setEmbedError(error);
171
+ return;
172
+ }
204
173
  setIsDirectFallback(true);
205
174
  setEmbedUrl(
206
175
  workspaceAppDirectHref(
@@ -208,9 +177,7 @@ export function WorkspaceAppFrame({
208
177
  embedPath ?? "/",
209
178
  ),
210
179
  );
211
- setEmbedError(
212
- cause instanceof Error ? cause : new Error(String(cause)),
213
- );
180
+ setEmbedError(error);
214
181
  });
215
182
  return () => {
216
183
  cancelled = true;
@@ -242,21 +209,6 @@ export function WorkspaceAppFrame({
242
209
  window.removeEventListener("message", handleEmbedSessionExpired);
243
210
  }, [embedUrl]);
244
211
 
245
- useEffect(() => {
246
- const handleAuthState = (event: MessageEvent) => {
247
- const frame = embedFrameRef.current;
248
- if (!frame || event.source !== frame.contentWindow) return;
249
- if (event.data?.type !== "agentNative.authState") return;
250
- const status = event.data.data?.status;
251
- if (status === "authenticated" || status === "unauthenticated") {
252
- setAuthState(status);
253
- }
254
- };
255
-
256
- window.addEventListener("message", handleAuthState);
257
- return () => window.removeEventListener("message", handleAuthState);
258
- }, []);
259
-
260
212
  useEffect(() => {
261
213
  postThemeToFrame();
262
214
  }, [embedUrl, postThemeToFrame]);
@@ -315,24 +267,6 @@ export function WorkspaceAppFrame({
315
267
  dynamicSuggestions={false}
316
268
  suggestions={[]}
317
269
  emptyStateText={`Ask about ${app.name}`}
318
- composerSlot={
319
- authState === "unauthenticated" ? (
320
- <div className="flex shrink-0 items-center px-3 pb-1">
321
- <button
322
- type="button"
323
- data-dispatch-app-sign-in
324
- aria-label={`Sign in to ${app.name} on the right`}
325
- title={`Sign in to ${app.name} on the right`}
326
- onClick={() => embedFrameRef.current?.focus()}
327
- 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"
328
- >
329
- <IconLock size={12} stroke={1.8} />
330
- <span>Sign in on the right</span>
331
- <IconArrowUpRight size={12} stroke={1.8} />
332
- </button>
333
- </div>
334
- ) : null
335
- }
336
270
  >
337
271
  {appPane}
338
272
  </AgentSidebar>
@@ -8,6 +8,7 @@ import type {
8
8
  ActionRouteAuthAdapter,
9
9
  ActionRouteResolvedCaller,
10
10
  } from "@agent-native/core/server";
11
+ import { getSession } from "@agent-native/core/server";
11
12
 
12
13
  export const WORKSPACE_APPS_ACTION_PATH =
13
14
  "/_agent-native/actions/list-workspace-apps";
@@ -57,6 +58,22 @@ export const workspaceAppActionRouteAuth: ActionRouteAuthAdapter = {
57
58
  throw new Error("Invalid workspace registry authorization");
58
59
  }
59
60
 
61
+ // Native clients use their normal persisted session bearer for the same
62
+ // read-only action. Let the framework resolve that credential before
63
+ // treating an otherwise opaque bearer as an A2A token.
64
+ const hasRequestUrl =
65
+ typeof event?.node?.req?.url === "string" ||
66
+ typeof event?.req?.url === "string" ||
67
+ typeof event?.url === "string";
68
+ const session = hasRequestUrl ? await getSession(event) : null;
69
+ if (session?.email) {
70
+ return {
71
+ owner: session.email,
72
+ anonymous: false,
73
+ ...(session.orgId ? { orgId: session.orgId } : {}),
74
+ };
75
+ }
76
+
60
77
  const token = authorization.slice("Bearer ".length).trim();
61
78
  if (!token) throw new Error("Invalid workspace registry authorization");
62
79
 
@@ -11,3 +11,10 @@ export const DISPATCH_WORKSPACE_SSO_FLAG = defineFeatureFlag({
11
11
  description:
12
12
  "Let Dispatch use the signed-in workspace identity for exact registered app panes.",
13
13
  });
14
+
15
+ export const DISPATCH_WORKSPACE_APP_LIST_FLAG = defineFeatureFlag({
16
+ key: "dispatch.workspace-app-list",
17
+ displayName: "Workspace app list",
18
+ description:
19
+ "Show apps from the signed-in workspace in native desktop and mobile app lists.",
20
+ });