@agent-native/dispatch 0.20.3 → 0.21.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.
Files changed (66) hide show
  1. package/dist/actions/provider-api-register.d.ts +6 -6
  2. package/dist/actions/start-workspace-app-creation.js +1 -1
  3. package/dist/actions/start-workspace-app-creation.js.map +1 -1
  4. package/dist/components/automation-run-history-dialog.d.ts.map +1 -1
  5. package/dist/components/automation-run-history-dialog.js +1 -0
  6. package/dist/components/automation-run-history-dialog.js.map +1 -1
  7. package/dist/components/create-app-popover.d.ts.map +1 -1
  8. package/dist/components/create-app-popover.js +5 -71
  9. package/dist/components/create-app-popover.js.map +1 -1
  10. package/dist/components/layout/Layout.d.ts +23 -1
  11. package/dist/components/layout/Layout.d.ts.map +1 -1
  12. package/dist/components/layout/Layout.js +702 -15
  13. package/dist/components/layout/Layout.js.map +1 -1
  14. package/dist/lib/automation-display.d.ts +6 -0
  15. package/dist/lib/automation-display.d.ts.map +1 -1
  16. package/dist/lib/automation-display.js +8 -0
  17. package/dist/lib/automation-display.js.map +1 -1
  18. package/dist/lib/automations.d.ts +1 -0
  19. package/dist/lib/automations.d.ts.map +1 -1
  20. package/dist/lib/automations.js.map +1 -1
  21. package/dist/lib/workspace-apps.d.ts +6 -0
  22. package/dist/lib/workspace-apps.d.ts.map +1 -1
  23. package/dist/lib/workspace-apps.js +21 -0
  24. package/dist/lib/workspace-apps.js.map +1 -1
  25. package/dist/routes/pages/automations.d.ts.map +1 -1
  26. package/dist/routes/pages/automations.js +16 -6
  27. package/dist/routes/pages/automations.js.map +1 -1
  28. package/dist/routes/pages/settings.d.ts.map +1 -1
  29. package/dist/routes/pages/settings.js +21 -1
  30. package/dist/routes/pages/settings.js.map +1 -1
  31. package/dist/server/index.d.ts +1 -1
  32. package/dist/server/index.d.ts.map +1 -1
  33. package/dist/server/index.js +1 -1
  34. package/dist/server/index.js.map +1 -1
  35. package/dist/server/lib/app-creation-store.d.ts.map +1 -1
  36. package/dist/server/lib/app-creation-store.js +13 -0
  37. package/dist/server/lib/app-creation-store.js.map +1 -1
  38. package/dist/server/plugins/agent-chat.js +1 -0
  39. package/dist/server/plugins/agent-chat.js.map +1 -1
  40. package/dist/server/plugins/db.d.ts +1 -0
  41. package/dist/server/plugins/db.d.ts.map +1 -1
  42. package/dist/server/plugins/db.js +1 -1
  43. package/dist/server/plugins/db.js.map +1 -1
  44. package/dist/server/plugins/integrations.js +1 -1
  45. package/dist/server/plugins/integrations.js.map +1 -1
  46. package/package.json +3 -3
  47. package/src/actions/start-workspace-app-creation.ts +1 -1
  48. package/src/components/automation-run-history-dialog.tsx +1 -0
  49. package/src/components/create-app-popover.spec.tsx +22 -0
  50. package/src/components/create-app-popover.tsx +30 -94
  51. package/src/components/layout/Layout.spec.tsx +16 -1
  52. package/src/components/layout/Layout.tsx +1356 -34
  53. package/src/lib/automation-display.spec.ts +7 -0
  54. package/src/lib/automation-display.ts +11 -0
  55. package/src/lib/automations.ts +1 -0
  56. package/src/lib/workspace-apps.ts +24 -0
  57. package/src/routes/pages/automations.tsx +45 -6
  58. package/src/routes/pages/settings.tsx +72 -0
  59. package/src/server/index.ts +4 -1
  60. package/src/server/lib/app-creation-store.spec.ts +13 -0
  61. package/src/server/lib/app-creation-store.ts +13 -0
  62. package/src/server/lib/mcp-gateway.spec.ts +18 -0
  63. package/src/server/plugins/agent-chat.ts +1 -0
  64. package/src/server/plugins/db.ts +1 -1
  65. package/src/server/plugins/integrations.ts +1 -1
  66. package/src/styles/dispatch.css +29 -0
@@ -1,16 +1,24 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { AgentSidebar, focusAgentChat, navigateWithAgentChatViewTransition, useAgentChatHomeHandoff, useAgentChatHomeHandoffLinks, useChatThreads, } from "@agent-native/core/client/agent-chat";
2
+ import { CHAT_FIRST_MODE_CHANGED_EVENT, AgentChatSurface, chatFirstSurfaceTabId, AgentSidebar, ChatFirstSurfacePanelToggle, closeChatFirstSessionWatch, emitChatFirstSessionWatch, getChatFirstSurfaceTabsStore, focusAgentChat, navigateWithAgentChatViewTransition, readChatFirstAppLayout, readChatFirstMode, resolveChatFirstBrowserTarget, resolveChatFirstAppTarget, subscribeChatFirstOpenBrowser, subscribeChatFirstOpenApp, useChatFirstSessionWatch, useChatFirstSurfacePanel, useChatFirstSurfaceResize, useChatFirstSurfaceTabs, useAgentChatHomeHandoff, useAgentChatHomeHandoffLinks, useChatThreads, } from "@agent-native/core/client/agent-chat";
3
3
  import { appBasePath, appPath } from "@agent-native/core/client/api-path";
4
+ import { readClientAppState, writeClientAppState, } from "@agent-native/core/client/application-state";
5
+ import { ChatFirstAgentsPane, ChatFirstAppPane, ChatFirstAppsRail, ChatFirstBrowserPane, ChatFirstChatHistory, ChatFirstPrimaryNavigation, ChatFirstSessionWatchPane, ChatFirstSurfacePanel, ChatFirstSurfaceContent, ChatFirstSurfaceTabs, defaultChatFirstCopy, } from "@agent-native/core/client/chat-first";
6
+ import { writeClipboardText } from "@agent-native/core/client/clipboard";
7
+ import { useActionMutation, useActionQuery, } from "@agent-native/core/client/hooks";
4
8
  import { useT } from "@agent-native/core/client/i18n";
5
9
  import { openCommandMenu } from "@agent-native/core/client/navigation";
6
10
  import { InvitationBanner, OrgSwitcher } from "@agent-native/core/client/org";
7
11
  import { FeedbackButton } from "@agent-native/core/client/ui";
8
12
  import { SidebarFooterActions } from "@agent-native/toolkit/app-shell";
9
13
  import { ChatHistoryRail, } from "@agent-native/toolkit/chat-history";
10
- import { IconApps, IconBrandSlack, IconBrandTelegram, IconMessageQuestion, IconBroadcast, IconLayoutSidebarLeftCollapse, IconLayoutSidebarLeftExpand, IconSettings, IconShield, IconSearch, IconWorld, IconDeviceDesktop, } from "@tabler/icons-react";
11
- import { createContext, useEffect, useMemo, useRef, useState, useContext, } from "react";
14
+ import { IconApps, IconBrandSlack, IconBrandTelegram, IconCopy, IconEye, IconMessageQuestion, IconBroadcast, IconLayoutSidebarLeftCollapse, IconLayoutSidebarLeftExpand, IconSettings, IconShield, IconSearch, IconWorld, IconDeviceDesktop, IconPlus, } from "@tabler/icons-react";
15
+ import { createContext, useEffect, useMemo, useRef, useState, useContext, useCallback, } from "react";
12
16
  import { Link, useLocation, useNavigate } from "react-router";
17
+ import { toast } from "sonner";
13
18
  import { cn } from "../../lib/utils.js";
19
+ import { mergeChatFirstWorkspaceApps, } from "../../lib/workspace-apps.js";
20
+ import { AppIcon } from "../app-icon.js";
21
+ import { CreateAppPopover } from "../create-app-popover.js";
14
22
  import { Sheet, SheetContent, SheetDescription, SheetTitle } from "../ui/sheet.js";
15
23
  import { Skeleton } from "../ui/skeleton.js";
16
24
  import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip.js";
@@ -59,6 +67,10 @@ const DISPATCH_SIDEBAR_LABEL = "Dispatch";
59
67
  const CHROMELESS_PATHS = ["/approval", "/browser-chat", "/browser-connect"];
60
68
  const SIDEBAR_COLLAPSE_KEY = "dispatch.sidebar.collapsed";
61
69
  const CHAT_HISTORY_SOURCE_KEY = "dispatch.chat-history.source";
70
+ const CHAT_FIRST_PANE_STATE_KEY = "chat-first-pane";
71
+ export function buildChatFirstEmbedSessionInput(appId, path) {
72
+ return { app: appId, path, chrome: "minimal" };
73
+ }
62
74
  const DispatchExtensionsContext = createContext(undefined);
63
75
  export function useDispatchExtensions() {
64
76
  return useContext(DispatchExtensionsContext);
@@ -120,8 +132,147 @@ function dispatchNavLinkTarget(path) {
120
132
  const routerHasBasename = pathname === basePath || pathname.startsWith(`${basePath}/`);
121
133
  return routerHasBasename ? path : appPath(path);
122
134
  }
135
+ function chatFirstResolutionMessage(reason) {
136
+ switch (reason) {
137
+ case "empty-detail":
138
+ return "The agent did not provide an app target to open.";
139
+ case "invalid-url":
140
+ return "The requested app route was not registered for this workspace app.";
141
+ case "unknown-app":
142
+ return "That app is not available in this workspace.";
143
+ }
144
+ return "The requested app could not be opened.";
145
+ }
146
+ function chatFirstBrowserResolutionMessage(reason) {
147
+ return reason === "empty-detail"
148
+ ? "The agent did not provide a browser URL to open."
149
+ : "The requested browser URL is not a safe HTTP(S) address.";
150
+ }
151
+ const DISPATCH_CHAT_FIRST_COPY_KEYS = {
152
+ workspaceApps: "chatFirstWorkspaceApps",
153
+ createWorkspaceApp: "chatFirstCreateWorkspaceApp",
154
+ openApp: "chatFirstOpenApp",
155
+ appsLoadError: "chatFirstAppsLoadError",
156
+ noWorkspaceApps: "chatFirstNoWorkspaceApps",
157
+ createApp: "chatFirstCreateApp",
158
+ retry: "chatFirstRetry",
159
+ dismiss: "chatFirstDismiss",
160
+ unpinApp: "chatFirstUnpinApp",
161
+ pinApp: "chatFirstPinApp",
162
+ removePinned: "chatFirstRemovePinned",
163
+ pinTop: "chatFirstPinTop",
164
+ openSideSurfaces: "chatFirstOpenSideSurfaces",
165
+ closeTab: "chatFirstCloseTab",
166
+ close: "chatFirstClose",
167
+ closeOthers: "chatFirstCloseOthers",
168
+ closeToRight: "chatFirstCloseToRight",
169
+ closeAll: "chatFirstCloseAll",
170
+ unavailable: "chatFirstUnavailable",
171
+ openActivity: "chatFirstOpenActivity",
172
+ deferred: "chatFirstDeferred",
173
+ browserBack: "chatFirstBrowserBack",
174
+ browserForward: "chatFirstBrowserForward",
175
+ browserReload: "chatFirstBrowserReload",
176
+ browserAddress: "chatFirstBrowserAddress",
177
+ browserOpenExternal: "chatFirstBrowserOpenExternal",
178
+ browserClose: "chatFirstBrowserClose",
179
+ browserPage: "chatFirstBrowserPage",
180
+ browserInvalidUrl: "chatFirstBrowserInvalidUrl",
181
+ browserPreviewStarting: "chatFirstBrowserPreviewStarting",
182
+ browserPreviewError: "chatFirstBrowserPreviewError",
183
+ appUnavailable: "chatFirstAppUnavailable",
184
+ appLoading: "chatFirstLoadingApp",
185
+ agentActivityEyebrow: "chatFirstAgentActivityEyebrow",
186
+ agentActivityTitle: "chatFirstAgentActivityTitle",
187
+ agentActivityDescription: "chatFirstAgentActivityDescription",
188
+ refreshAgentActivity: "chatFirstRefreshAgentActivity",
189
+ noAgentSessions: "chatFirstNoAgentSessions",
190
+ startAgentSession: "chatFirstStartAgentSession",
191
+ watchSession: "chatFirstWatchSession",
192
+ copySessionId: "chatFirstCopySessionId",
193
+ copySessionIdFor: "chatFirstCopySessionIdFor",
194
+ sessionIdCopied: "chatFirstSessionIdCopied",
195
+ sessionIdShort: "chatFirstSessionIdShort",
196
+ copied: "chatFirstCopied",
197
+ agentActivityStatusQueued: "chatFirstAgentActivityStatusQueued",
198
+ agentActivityStatusRunning: "chatFirstAgentActivityStatusRunning",
199
+ agentActivityStatusPaused: "chatFirstAgentActivityStatusPaused",
200
+ agentActivityStatusNeedsApproval: "chatFirstAgentActivityStatusNeedsApproval",
201
+ agentActivityStatusCompleted: "chatFirstAgentActivityStatusCompleted",
202
+ agentActivityStatusErrored: "chatFirstAgentActivityStatusErrored",
203
+ agentActivityStatusRecent: "chatFirstAgentActivityStatusRecent",
204
+ agentActivityStatusUnknown: "chatFirstAgentActivityStatusUnknown",
205
+ watchingSession: "chatFirstWatchingSession",
206
+ session: "chatFirstSession",
207
+ stopWatchingSession: "chatFirstStopWatchingSession",
208
+ stopWatching: "chatFirstStopWatching",
209
+ watchedSession: "chatFirstWatchedSession",
210
+ agentsTitle: "chatFirstAgentsTitle",
211
+ };
212
+ const DISPATCH_CHAT_FIRST_SURFACE_COPY_KEYS = {
213
+ browser: {
214
+ label: "chatFirstSurfaceBrowserLabel",
215
+ reason: "chatFirstSurfaceBrowserReason",
216
+ },
217
+ terminal: {
218
+ label: "chatFirstSurfaceTerminalLabel",
219
+ reason: "chatFirstSurfaceTerminalReason",
220
+ },
221
+ files: {
222
+ label: "chatFirstSurfaceFilesLabel",
223
+ reason: "chatFirstSurfaceFilesReason",
224
+ },
225
+ diff: {
226
+ label: "chatFirstSurfaceDiffLabel",
227
+ reason: "chatFirstSurfaceDiffReason",
228
+ },
229
+ "side-chat": {
230
+ label: "chatFirstSurfaceSideChatLabel",
231
+ reason: "chatFirstSurfaceSideChatReason",
232
+ },
233
+ agents: {
234
+ label: "chatFirstSurfaceAgentsLabel",
235
+ reason: "chatFirstSurfaceAgentsReason",
236
+ },
237
+ };
238
+ function createDispatchChatFirstCopy(t) {
239
+ return (key, values) => {
240
+ if (key.startsWith("surface.")) {
241
+ const [, kind, field] = key.split(".");
242
+ const surface = DISPATCH_CHAT_FIRST_SURFACE_COPY_KEYS[kind];
243
+ const translationKey = surface && (field === "label" || field === "reason")
244
+ ? surface[field]
245
+ : undefined;
246
+ if (translationKey) {
247
+ return t(`dispatch.pages.${translationKey}`, {
248
+ ...(values ?? {}),
249
+ defaultValue: defaultChatFirstCopy(key, values),
250
+ });
251
+ }
252
+ }
253
+ const translationKey = DISPATCH_CHAT_FIRST_COPY_KEYS[key];
254
+ if (!translationKey)
255
+ return defaultChatFirstCopy(key, values);
256
+ return t(`dispatch.pages.${translationKey}`, {
257
+ ...(values ?? {}),
258
+ defaultValue: defaultChatFirstCopy(key, values),
259
+ });
260
+ };
261
+ }
123
262
  function chatThreadPath(threadId) {
124
- return `/chat/${encodeURIComponent(threadId)}`;
263
+ return threadId ? `/chat/${encodeURIComponent(threadId)}` : "/chat";
264
+ }
265
+ function persistedChatFirstPane(value) {
266
+ if (!value || typeof value !== "object")
267
+ return null;
268
+ const record = value;
269
+ if (typeof record.appId !== "string" || !record.appId.trim())
270
+ return null;
271
+ return {
272
+ appId: record.appId,
273
+ ...(typeof record.path === "string" ? { path: record.path } : {}),
274
+ ...(typeof record.view === "string" ? { view: record.view } : {}),
275
+ };
125
276
  }
126
277
  function threadIdFromPath(pathname) {
127
278
  const match = pathname.match(/^\/chat\/([^/]+)/);
@@ -185,7 +336,7 @@ function threadUpdatedAt(thread) {
185
336
  ? thread.createdAt
186
337
  : 0;
187
338
  }
188
- function DispatchChatsSection({ onNavigate }) {
339
+ function DispatchChatsSection({ onNavigate, showNewChat = false, prelude, chatFirstMode = false, chatFirstEmbedded = false, }) {
189
340
  const t = useT();
190
341
  const navigate = useNavigate();
191
342
  const location = useLocation();
@@ -266,7 +417,7 @@ function DispatchChatsSection({ onNavigate }) {
266
417
  if (threadId)
267
418
  openThread(threadId, { isNew: true });
268
419
  }
269
- return (_jsxs("div", { className: "ms-4 min-w-0 space-y-0.5", children: [_jsx("div", { className: "flex justify-end px-2 pt-0.5", children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "data-dispatch-chat-source-toggle": true, "aria-pressed": includeExternal, "aria-label": includeExternal
420
+ return (_jsxs("div", { className: cn("ms-4 min-w-0 space-y-0.5", showNewChat && "dispatch-chat-first-chats"), children: [showNewChat ? (_jsxs("button", { type: "button", className: "flex h-7 w-[calc(100%-0.5rem)] items-center gap-2 rounded-md px-2 text-sm text-sidebar-foreground/80 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", onClick: () => void handleNewChat(), children: [_jsx(IconPlus, { size: 16, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: "New chat" })] })) : null, prelude, _jsx("div", { className: "flex justify-end px-2 pt-0.5", children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "data-dispatch-chat-source-toggle": true, "aria-pressed": includeExternal, "aria-label": includeExternal
270
421
  ? t("dispatch.sidebar.showLocalChats", {
271
422
  defaultValue: "Show local chats",
272
423
  })
@@ -278,9 +429,33 @@ function DispatchChatsSection({ onNavigate }) {
278
429
  })
279
430
  : t("dispatch.sidebar.showAllChats", {
280
431
  defaultValue: "Show all chats",
281
- }) })] }) }), chatsLoading &&
432
+ }) })] }) }), !chatFirstMode &&
433
+ chatsLoading &&
282
434
  visibleThreads.length === 0 &&
283
- Array.from({ length: 3 }).map((_, index) => (_jsxs("div", { className: "flex items-center gap-2 px-3 py-1", children: [_jsx(Skeleton, { className: "size-3.5 shrink-0 rounded-sm" }), _jsx(Skeleton, { className: "h-3 w-3/4 rounded" })] }, `chat-skeleton-${index}`))), _jsx(ChatHistoryRail, { items: chatItems, activeId: displayedActiveThreadId, onSelect: (threadId) => openThread(threadId), onNewChat: () => void handleNewChat(), railLabels: {
435
+ Array.from({ length: 3 }).map((_, index) => (_jsxs("div", { className: "flex items-center gap-2 px-3 py-1", children: [_jsx(Skeleton, { className: "size-3.5 shrink-0 rounded-sm" }), _jsx(Skeleton, { className: "h-3 w-3/4 rounded" })] }, `chat-skeleton-${index}`))), chatFirstMode ? (_jsx(ChatFirstChatHistory, { items: chatItems, activeId: displayedActiveThreadId, loading: chatsLoading && visibleThreads.length === 0, loadingLabel: _jsx("div", { className: "space-y-1 px-2 py-1", children: [0, 1, 2].map((index) => (_jsxs("div", { className: "flex items-center gap-2 px-1 py-1", children: [_jsx(Skeleton, { className: "size-3.5 shrink-0 rounded-sm" }), _jsx(Skeleton, { className: "h-3 w-3/4 rounded" })] }, index))) }), label: t("dispatch.sidebar.chats", { defaultValue: "Chats" }), onSelect: (threadId) => openThread(threadId), renameMaxLength: 160, onRename: (threadId, title) => void renameThread(threadId, title), labels: {
436
+ options: (item) => t("dispatch.sidebar.chatOptions", {
437
+ title: item.titleText ?? "",
438
+ }),
439
+ renameInput: (item) => t("dispatch.sidebar.renameThread", {
440
+ title: item.titleText ?? "",
441
+ }),
442
+ rename: t("dispatch.sidebar.renameChat"),
443
+ }, renderAdditionalRowActions: (item, closeMenu) => (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", role: "menuitem", className: "an-chat-history-row__menu-item", onClick: () => {
444
+ closeMenu();
445
+ void writeClipboardText(item.id).then((copied) => {
446
+ toast[copied ? "success" : "error"](copied
447
+ ? "Session ID copied"
448
+ : "Could not copy the session ID");
449
+ });
450
+ }, children: [_jsx(IconCopy, { size: 13, strokeWidth: 1.8 }), _jsx("span", { children: "Copy session ID" })] }), !chatFirstEmbedded ? (_jsxs("button", { type: "button", role: "menuitem", className: "an-chat-history-row__menu-item", onClick: () => {
451
+ closeMenu();
452
+ emitChatFirstSessionWatch({
453
+ sessionId: item.id,
454
+ title: item.titleText,
455
+ kind: "agent-chat",
456
+ sourceSessionId: displayedActiveThreadId ?? undefined,
457
+ });
458
+ }, children: [_jsx(IconEye, { size: 13, strokeWidth: 1.8 }), _jsx("span", { children: "Watch and message session" })] })) : null] })), className: "min-w-0" })) : (_jsx(ChatHistoryRail, { items: chatItems, activeId: displayedActiveThreadId, onSelect: (threadId) => openThread(threadId), onNewChat: () => void handleNewChat(), railLabels: {
284
459
  newChat: t("dispatch.sidebar.newChat"),
285
460
  showMore: t("dispatch.sidebar.chats"),
286
461
  showLess: t("dispatch.sidebar.chats"),
@@ -292,10 +467,26 @@ function DispatchChatsSection({ onNavigate }) {
292
467
  title: item.titleText ?? "",
293
468
  }),
294
469
  rename: t("dispatch.sidebar.renameChat"),
295
- }, className: "min-w-0" })] }));
470
+ }, renderAdditionalRowActions: (item, closeMenu) => (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", role: "menuitem", className: "an-chat-history-row__menu-item", onClick: () => {
471
+ closeMenu();
472
+ void writeClipboardText(item.id).then((copied) => {
473
+ toast[copied ? "success" : "error"](copied
474
+ ? "Session ID copied"
475
+ : "Could not copy the session ID");
476
+ });
477
+ }, children: [_jsx(IconCopy, { size: 13, strokeWidth: 1.8 }), _jsx("span", { children: "Copy session ID" })] }), chatFirstMode && !chatFirstEmbedded ? (_jsxs("button", { type: "button", role: "menuitem", className: "an-chat-history-row__menu-item", onClick: () => {
478
+ closeMenu();
479
+ emitChatFirstSessionWatch({
480
+ sessionId: item.id,
481
+ title: item.titleText,
482
+ kind: "agent-chat",
483
+ sourceSessionId: displayedActiveThreadId ?? undefined,
484
+ });
485
+ }, children: [_jsx(IconEye, { size: 13, strokeWidth: 1.8 }), _jsx("span", { children: "Watch and message session" })] })) : null] })), className: "min-w-0" }))] }));
296
486
  }
297
- export function NavContent({ onNavigate, extensions, collapsed = false, collapsible = false, onCollapsedChange, }) {
487
+ export function NavContent({ onNavigate, extensions, chatFirstMode = false, chatFirstEmbedded = false, collapsed = false, collapsible = false, onCollapsedChange, chatFirstAppLayout, onChatFirstAppLayoutChange, chatFirstApps = [], chatFirstAppsLoading = false, chatFirstAppsError, chatFirstActiveAppId, onChatFirstAppOpen, onChatFirstAppsRetry, }) {
298
488
  const t = useT();
489
+ const chatFirstCopy = useMemo(() => createDispatchChatFirstCopy(t), [t]);
299
490
  const location = useLocation();
300
491
  const navigate = useNavigate();
301
492
  const extensionNavItems = extensions?.navItems ?? EMPTY_NAV_ITEMS;
@@ -317,6 +508,18 @@ export function NavContent({ onNavigate, extensions, collapsed = false, collapsi
317
508
  : t("sidebar.collapseSidebar"), className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-sidebar-foreground/65 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", children: collapsed ? (_jsx(IconLayoutSidebarLeftExpand, { className: "h-4 w-4 rtl:-scale-x-100" })) : (_jsx(IconLayoutSidebarLeftCollapse, { className: "h-4 w-4 rtl:-scale-x-100" })) }) }), _jsx(TooltipContent, { side: "right", children: collapsed ? t("sidebar.expandSidebar") : t("sidebar.collapseSidebar") })] })) : null;
318
509
  const searchButton = (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", onClick: openCommandMenu, "aria-label": t("sidebar.search"), className: "flex h-8 w-8 items-center justify-center rounded-md text-sidebar-foreground/65 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", children: _jsx(IconSearch, { className: "h-4 w-4" }) }) }), _jsx(TooltipContent, { side: "right", children: t("sidebar.search") })] }));
319
510
  const feedbackButton = (_jsx(FeedbackButton, { variant: collapsed ? "icon" : "sidebar", side: "right", className: collapsed ? "size-8" : "min-w-0" }));
511
+ const chatFirstCreateAppTrigger = (_jsx(CreateAppPopover, { align: "start", trigger: _jsx("button", { type: "button", className: "flex size-6 items-center justify-center rounded text-sidebar-foreground/55 hover:bg-sidebar-accent hover:text-sidebar-foreground", "aria-label": chatFirstCopy("createWorkspaceApp"), title: chatFirstCopy("createWorkspaceApp"), children: _jsx(IconPlus, { size: 14, "aria-hidden": "true" }) }) }));
512
+ const chatFirstPrimaryNavigation = (_jsx(ChatFirstPrimaryNavigation, { copy: chatFirstCopy, onOpenIntegrations: () => {
513
+ navigate(dispatchNavLinkTarget("/admin/integrations"));
514
+ onNavigate?.();
515
+ }, onOpenScheduled: () => {
516
+ navigate(dispatchNavLinkTarget("/admin/automations"));
517
+ onNavigate?.();
518
+ } }));
519
+ const chatFirstAppsRail = (_jsx(ChatFirstAppsRail, { apps: chatFirstApps, activeAppId: chatFirstActiveAppId, collapsed: collapsed, loading: chatFirstAppsLoading, error: chatFirstAppsError, layout: chatFirstAppLayout, onLayoutChange: onChatFirstAppLayoutChange, onRetry: onChatFirstAppsRetry, onOpenApp: (app) => {
520
+ onChatFirstAppOpen?.(app);
521
+ onNavigate?.();
522
+ }, createAppTrigger: chatFirstCreateAppTrigger, renderIcon: (app) => (_jsx(AppIcon, { id: app.id, name: app.name, size: "sm", className: "size-5 rounded-md" })), copy: chatFirstCopy }));
320
523
  const renderNavItem = (item) => {
321
524
  const Icon = item.icon;
322
525
  const itemMatchesLocalPath = navItemMatchesPath(item, localPathname);
@@ -351,9 +554,9 @@ export function NavContent({ onNavigate, extensions, collapsed = false, collapsi
351
554
  onNavigate?.();
352
555
  }, className: cn("flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm", itemMatchesLocalPath
353
556
  ? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
354
- : "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), children: [Icon ? (_jsx(Icon, { size: 16, className: "shrink-0" })) : (_jsx("span", { className: "h-4 w-4 shrink-0", "aria-hidden": "true" })), _jsx("span", { className: "truncate", children: label })] })), !collapsed && item.id === "chat" && itemMatchesLocalPath ? (_jsx(DispatchChatsSection, { onNavigate: onNavigate })) : null] }, item.id));
557
+ : "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), children: [Icon ? (_jsx(Icon, { size: 16, className: "shrink-0" })) : (_jsx("span", { className: "h-4 w-4 shrink-0", "aria-hidden": "true" })), _jsx("span", { className: "truncate", children: label })] })), !collapsed && item.id === "chat" && itemMatchesLocalPath ? (_jsx(DispatchChatsSection, { onNavigate: onNavigate, chatFirstMode: chatFirstMode, chatFirstEmbedded: chatFirstEmbedded })) : null] }, item.id));
355
558
  };
356
- return (_jsxs(_Fragment, { children: [_jsx("div", { className: cn("flex h-12 shrink-0 items-center border-b border-sidebar-border", collapsed ? "justify-center px-0" : "px-4"), children: _jsxs("div", { className: cn("flex items-center", collapsed ? "justify-center" : "gap-2"), children: [_jsx("img", { src: appPath("/agent-native-icon-light.svg"), alt: "", "aria-hidden": "true", width: 35, height: 20, className: cn("block shrink-0 object-contain object-center dark:hidden", collapsed ? "h-4 w-7" : "h-5 w-[35px]") }), _jsx("img", { src: appPath("/agent-native-icon-dark.svg"), alt: "", "aria-hidden": "true", width: 35, height: 20, className: cn("hidden shrink-0 object-contain object-center dark:block", collapsed ? "h-4 w-7" : "h-5 w-[35px]") }), !collapsed && (_jsx("div", { className: "min-w-0 flex-1", children: _jsx("div", { "data-dispatch-sidebar-label": true, className: "truncate text-lg font-bold tracking-tight text-foreground", children: DISPATCH_SIDEBAR_LABEL }) }))] }) }), _jsxs("div", { className: "flex min-h-0 flex-1 flex-col overflow-y-auto", children: [_jsxs("nav", { className: cn("py-2", collapsed ? "px-1.5" : "px-2"), children: [_jsx("ul", { className: cn(collapsed ? "flex flex-col items-center gap-1" : "space-y-0.5"), children: primaryNavItems.map(renderNavItem) }), _jsx(WorkspaceAppsRail, { collapsed: collapsed, onNavigate: onNavigate })] }), _jsxs("div", { className: "mt-auto shrink-0", children: [_jsx("nav", { className: cn("py-1", collapsed ? "px-1.5" : "px-2"), children: _jsx("ul", { className: cn(collapsed ? "flex flex-col items-center gap-1" : "space-y-0.5"), children: BOTTOM_NAV_ITEMS.map(renderNavItem) }) }), _jsx("div", { className: cn("py-2", collapsed ? "flex justify-center px-1" : "px-3"), children: _jsx(OrgSwitcher, { compact: collapsed, reserveSpace: true }) })] }), _jsx(SidebarFooterActions, { collapsed: collapsed, feedback: feedbackButton, search: searchButton, collapse: collapseButton })] })] }));
559
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: cn("flex h-12 shrink-0 items-center border-b border-sidebar-border", collapsed ? "justify-center px-0" : "px-4"), children: _jsxs("div", { className: cn("flex items-center", collapsed ? "justify-center" : "gap-2"), children: [_jsx("img", { src: appPath("/agent-native-icon-light.svg"), alt: "", "aria-hidden": "true", width: 35, height: 20, className: cn("block shrink-0 object-contain object-center dark:hidden", collapsed ? "h-4 w-7" : "h-5 w-[35px]") }), _jsx("img", { src: appPath("/agent-native-icon-dark.svg"), alt: "", "aria-hidden": "true", width: 35, height: 20, className: cn("hidden shrink-0 object-contain object-center dark:block", collapsed ? "h-4 w-7" : "h-5 w-[35px]") }), !collapsed && (_jsx("div", { className: "min-w-0 flex-1", children: _jsx("div", { "data-dispatch-sidebar-label": true, className: "truncate text-lg font-bold tracking-tight text-foreground", children: DISPATCH_SIDEBAR_LABEL }) }))] }) }), chatFirstMode ? (_jsx("div", { className: "flex min-h-0 flex-1 flex-col overflow-y-auto", children: !collapsed ? (_jsx(DispatchChatsSection, { onNavigate: onNavigate, showNewChat: true, chatFirstMode: true, prelude: _jsxs(_Fragment, { children: [_jsx("nav", { className: "space-y-0.5 px-2 py-2", children: chatFirstPrimaryNavigation }), chatFirstAppsRail] }) })) : (chatFirstAppsRail) })) : null, _jsxs("div", { className: cn("flex min-h-0 flex-1 flex-col overflow-y-auto", chatFirstMode && "hidden"), children: [_jsxs("nav", { className: cn("py-2", collapsed ? "px-1.5" : "px-2"), children: [_jsx("ul", { className: cn(collapsed ? "flex flex-col items-center gap-1" : "space-y-0.5"), children: primaryNavItems.map(renderNavItem) }), _jsx(WorkspaceAppsRail, { collapsed: collapsed, onNavigate: onNavigate })] }), _jsxs("div", { className: "mt-auto shrink-0", children: [_jsx("nav", { className: cn("py-1", collapsed ? "px-1.5" : "px-2"), children: _jsx("ul", { className: cn(collapsed ? "flex flex-col items-center gap-1" : "space-y-0.5"), children: BOTTOM_NAV_ITEMS.map(renderNavItem) }) }), _jsx("div", { className: cn("py-2", collapsed ? "flex justify-center px-1" : "px-3"), children: _jsx(OrgSwitcher, { compact: collapsed, reserveSpace: true }) })] }), _jsx(SidebarFooterActions, { collapsed: collapsed, feedback: feedbackButton, search: searchButton, collapse: collapseButton })] }), chatFirstMode && collapsed ? (_jsx("div", { className: "mt-auto shrink-0", children: _jsx(SidebarFooterActions, { collapsed: true, feedback: feedbackButton, search: searchButton, collapse: collapseButton }) })) : null] }));
357
560
  }
358
561
  export function Layout({ children, extensions, agentPageHref, }) {
359
562
  const t = useT();
@@ -372,7 +575,181 @@ export function Layout({ children, extensions, agentPageHref, }) {
372
575
  });
373
576
  const localPathname = localDispatchPath(location.pathname);
374
577
  const isChatRoute = localPathname === "/chat" || localPathname.startsWith("/chat/");
578
+ const chatFirstSurfaceScope = threadIdFromPath(localPathname) ?? "new";
375
579
  const isWorkspaceAppHostRoute = localPathname.startsWith("/apps/");
580
+ const [chatFirstPreference, setChatFirstPreference] = useState(() => readChatFirstMode());
581
+ const chatFirstEmbedded = typeof window !== "undefined" &&
582
+ new URLSearchParams(window.location.search).get("chatFirst") === "1";
583
+ const chatFirstMode = extensions?.chatFirst === true || chatFirstPreference || chatFirstEmbedded;
584
+ const [chatFirstAppLayout, setChatFirstAppLayout] = useState(() => readChatFirstAppLayout());
585
+ const chatFirstAppLayoutHydratedRef = useRef(false);
586
+ const chatFirstAppsQuery = useActionQuery("list-workspace-apps", { includeAgentCards: false }, { enabled: chatFirstMode && isChatRoute });
587
+ const chatFirstWorkspaceApps = useMemo(() => mergeChatFirstWorkspaceApps(chatFirstAppsQuery.data), [chatFirstAppsQuery.data]);
588
+ const chatFirstAppRegistrations = useMemo(() => chatFirstWorkspaceApps.map((app) => ({
589
+ id: app.id,
590
+ name: app.name,
591
+ path: app.path,
592
+ url: app.url,
593
+ enabled: app.status !== "pending" && app.archived !== true,
594
+ })), [chatFirstWorkspaceApps]);
595
+ const chatFirstAppItems = useMemo(() => chatFirstWorkspaceApps.map((app) => ({
596
+ id: app.id,
597
+ name: app.name,
598
+ })), [chatFirstWorkspaceApps]);
599
+ const chatFirstCopy = useMemo(() => createDispatchChatFirstCopy(t), [t]);
600
+ const createChatFirstEmbedSession = useActionMutation("create_embed_session", { skipActionQueryInvalidation: true });
601
+ const [chatFirstPane, setChatFirstPane] = useState(null);
602
+ const [chatFirstEmbedUrl, setChatFirstEmbedUrl] = useState(null);
603
+ const [chatFirstEmbedError, setChatFirstEmbedError] = useState(null);
604
+ const [chatFirstEmbedAttempt, setChatFirstEmbedAttempt] = useState(0);
605
+ const [chatFirstNotice, setChatFirstNotice] = useState(null);
606
+ const chatFirstSessionWatch = useChatFirstSessionWatch();
607
+ const chatFirstSurfaceTabs = useChatFirstSurfaceTabs(chatFirstSurfaceScope);
608
+ const chatFirstSurfaceTabsStore = getChatFirstSurfaceTabsStore(chatFirstSurfaceScope);
609
+ const chatFirstAgentsQuery = useActionQuery("search-agent-threads", { sourceId: "current", limit: 12 }, {
610
+ enabled: chatFirstMode &&
611
+ isChatRoute &&
612
+ chatFirstSurfaceTabs.activeTabId?.startsWith("agents:") === true,
613
+ });
614
+ const chatFirstSurfacePanel = useChatFirstSurfacePanel(chatFirstSurfaceScope);
615
+ const { setOpen: setChatFirstSurfacePanelOpen } = chatFirstSurfacePanel;
616
+ const chatFirstSurfaceResize = useChatFirstSurfaceResize(chatFirstSurfaceScope);
617
+ const chatFirstPaneHydratedRef = useRef(false);
618
+ const pendingChatFirstOpenAppRef = useRef(null);
619
+ const previousChatFirstSurfaceScopeRef = useRef(chatFirstSurfaceScope);
620
+ const previousChatFirstSurfaceTabCountRef = useRef(null);
621
+ const activeChatFirstSurfaceTab = useMemo(() => chatFirstSurfaceTabs.tabs.find((tab) => tab.id === chatFirstSurfaceTabs.activeTabId) ?? null, [chatFirstSurfaceTabs]);
622
+ const activeChatFirstApp = useMemo(() => activeChatFirstSurfaceTab?.kind === "app" &&
623
+ activeChatFirstSurfaceTab.appId
624
+ ? (chatFirstWorkspaceApps.find((app) => app.id === activeChatFirstSurfaceTab.appId) ?? null)
625
+ : null, [activeChatFirstSurfaceTab, chatFirstWorkspaceApps]);
626
+ const chatFirstAgentActivities = useMemo(() => (chatFirstAgentsQuery.data?.threads ?? []).map((thread) => ({
627
+ sessionId: thread.id,
628
+ title: thread.title || chatFirstCopy("agentsTitle", { name: thread.id }),
629
+ subtitle: thread.snippet || thread.preview || chatFirstCopy("session"),
630
+ status: "recent",
631
+ ...(thread.updatedAt ? { updatedAt: thread.updatedAt } : {}),
632
+ })), [chatFirstAgentsQuery.data, chatFirstCopy]);
633
+ const chatFirstEmbedPath = activeChatFirstSurfaceTab?.kind === "app"
634
+ ? (activeChatFirstSurfaceTab.path ??
635
+ chatFirstPane?.path ??
636
+ activeChatFirstApp?.path)
637
+ : null;
638
+ useEffect(() => {
639
+ if (!chatFirstMode || !isChatRoute || !activeChatFirstApp) {
640
+ setChatFirstEmbedUrl(null);
641
+ setChatFirstEmbedError(null);
642
+ return;
643
+ }
644
+ if (!chatFirstEmbedPath?.startsWith("/")) {
645
+ setChatFirstEmbedUrl(null);
646
+ setChatFirstEmbedError(chatFirstCopy("appUnavailable"));
647
+ return;
648
+ }
649
+ let cancelled = false;
650
+ setChatFirstEmbedUrl(null);
651
+ setChatFirstEmbedError(null);
652
+ void createChatFirstEmbedSession
653
+ .mutateAsync(buildChatFirstEmbedSessionInput(activeChatFirstApp.id, chatFirstEmbedPath))
654
+ .then((result) => {
655
+ if (!cancelled)
656
+ setChatFirstEmbedUrl(result.startUrl);
657
+ })
658
+ .catch((cause) => {
659
+ if (!cancelled) {
660
+ setChatFirstEmbedError(cause instanceof Error
661
+ ? cause.message
662
+ : chatFirstCopy("appUnavailable"));
663
+ }
664
+ });
665
+ return () => {
666
+ cancelled = true;
667
+ };
668
+ }, [
669
+ activeChatFirstApp,
670
+ chatFirstCopy,
671
+ chatFirstEmbedAttempt,
672
+ chatFirstEmbedPath,
673
+ chatFirstMode,
674
+ createChatFirstEmbedSession.mutateAsync,
675
+ isChatRoute,
676
+ ]);
677
+ const persistChatFirstPane = useCallback((pane) => {
678
+ setChatFirstPane(pane);
679
+ void writeClientAppState(CHAT_FIRST_PANE_STATE_KEY, pane).catch(() => {
680
+ setChatFirstNotice("The app pane opened locally, but its focus could not be synced.");
681
+ });
682
+ }, []);
683
+ const openChatFirstPane = useCallback((pane) => {
684
+ setChatFirstNotice(null);
685
+ closeChatFirstSessionWatch();
686
+ const app = chatFirstAppRegistrations.find((candidate) => candidate.id === pane.appId);
687
+ chatFirstSurfaceTabsStore.open({
688
+ id: chatFirstSurfaceTabId("app", `${pane.appId}:${pane.path ?? pane.view ?? "/"}`),
689
+ kind: "app",
690
+ title: app?.name ?? pane.appId,
691
+ appId: pane.appId,
692
+ ...(pane.path ? { path: pane.path } : {}),
693
+ ...(pane.view ? { view: pane.view } : {}),
694
+ });
695
+ persistChatFirstPane(pane);
696
+ }, [
697
+ chatFirstAppRegistrations,
698
+ chatFirstSurfaceTabsStore,
699
+ persistChatFirstPane,
700
+ ]);
701
+ const openChatFirstSurface = useCallback((kind) => {
702
+ if (kind !== "agents")
703
+ return;
704
+ persistChatFirstPane(null);
705
+ closeChatFirstSessionWatch();
706
+ chatFirstSurfaceTabsStore.open({
707
+ id: chatFirstSurfaceTabId("agents", "activity"),
708
+ kind: "agents",
709
+ title: "Agents",
710
+ });
711
+ }, [chatFirstSurfaceTabsStore, persistChatFirstPane]);
712
+ const resolveChatFirstOpenBrowser = useCallback((detail) => {
713
+ const resolution = resolveChatFirstBrowserTarget(detail);
714
+ if (resolution.status === "unresolved") {
715
+ setChatFirstNotice(chatFirstBrowserResolutionMessage(resolution.reason));
716
+ return;
717
+ }
718
+ persistChatFirstPane(null);
719
+ closeChatFirstSessionWatch();
720
+ setChatFirstNotice(null);
721
+ chatFirstSurfaceTabsStore.open({
722
+ id: chatFirstSurfaceTabId("browser", resolution.target.url),
723
+ kind: "browser",
724
+ title: resolution.target.title ?? "Browser",
725
+ url: resolution.target.url,
726
+ });
727
+ }, [chatFirstSurfaceTabsStore, persistChatFirstPane]);
728
+ const resolveChatFirstOpenApp = useCallback((detail) => {
729
+ if (chatFirstAppsQuery.isLoading) {
730
+ pendingChatFirstOpenAppRef.current = detail;
731
+ return;
732
+ }
733
+ if (chatFirstAppsQuery.isError) {
734
+ pendingChatFirstOpenAppRef.current = null;
735
+ setChatFirstNotice("Workspace apps could not be loaded, so the requested app was not opened.");
736
+ return;
737
+ }
738
+ const resolution = resolveChatFirstAppTarget(detail, chatFirstAppRegistrations, {
739
+ currentOrigin: typeof window === "undefined" ? undefined : window.location.origin,
740
+ });
741
+ pendingChatFirstOpenAppRef.current = null;
742
+ if (resolution.status === "unresolved") {
743
+ setChatFirstNotice(chatFirstResolutionMessage(resolution.reason));
744
+ return;
745
+ }
746
+ openChatFirstPane(resolution.target);
747
+ }, [
748
+ chatFirstAppRegistrations,
749
+ chatFirstAppsQuery.isError,
750
+ chatFirstAppsQuery.isLoading,
751
+ openChatFirstPane,
752
+ ]);
376
753
  const sidebarBeforeAppRef = useRef(null);
377
754
  const sidebarAutoCollapsedRef = useRef(false);
378
755
  const chatHomeHandoffActive = useAgentChatHomeHandoff({
@@ -386,6 +763,194 @@ export function Layout({ children, extensions, agentPageHref, }) {
386
763
  requireActiveHandoff: true,
387
764
  };
388
765
  useAgentChatHomeHandoffLinks(chatHandoffLinkOptions);
766
+ useEffect(() => {
767
+ const handleModeChange = (event) => {
768
+ const enabled = event.detail
769
+ ?.enabled;
770
+ setChatFirstPreference(enabled === true);
771
+ };
772
+ window.addEventListener(CHAT_FIRST_MODE_CHANGED_EVENT, handleModeChange);
773
+ return () => window.removeEventListener(CHAT_FIRST_MODE_CHANGED_EVENT, handleModeChange);
774
+ }, []);
775
+ useEffect(() => {
776
+ if (!chatFirstMode ||
777
+ !isChatRoute ||
778
+ chatFirstAppLayoutHydratedRef.current) {
779
+ return;
780
+ }
781
+ chatFirstAppLayoutHydratedRef.current = true;
782
+ void readClientAppState("chat-first-app-layout")
783
+ .then((value) => {
784
+ if (!value || typeof value !== "object")
785
+ return;
786
+ const candidate = value;
787
+ const ids = (input) => Array.isArray(input)
788
+ ? input.filter((id) => typeof id === "string" && id.trim().length > 0)
789
+ : [];
790
+ setChatFirstAppLayout({
791
+ pinnedIds: [...new Set(ids(candidate.pinnedIds))],
792
+ orderedIds: [...new Set(ids(candidate.orderedIds))],
793
+ });
794
+ })
795
+ .catch(() => {
796
+ // Device-local layout remains the fallback when workspace state is unavailable.
797
+ });
798
+ }, [chatFirstMode, isChatRoute]);
799
+ const persistChatFirstAppLayout = useCallback((layout) => {
800
+ setChatFirstAppLayout(layout);
801
+ void writeClientAppState("chat-first-app-layout", layout).catch(() => {
802
+ setChatFirstNotice("App order changed locally, but workspace state could not be synced.");
803
+ });
804
+ }, []);
805
+ useEffect(() => {
806
+ if (!chatFirstMode || !isChatRoute) {
807
+ setChatFirstPane(null);
808
+ closeChatFirstSessionWatch();
809
+ chatFirstSurfaceTabsStore.closeAll();
810
+ setChatFirstNotice(null);
811
+ pendingChatFirstOpenAppRef.current = null;
812
+ chatFirstPaneHydratedRef.current = false;
813
+ return;
814
+ }
815
+ const unsubscribeApp = subscribeChatFirstOpenApp(resolveChatFirstOpenApp);
816
+ const unsubscribeBrowser = subscribeChatFirstOpenBrowser(resolveChatFirstOpenBrowser);
817
+ return () => {
818
+ unsubscribeApp();
819
+ unsubscribeBrowser();
820
+ };
821
+ }, [
822
+ chatFirstMode,
823
+ isChatRoute,
824
+ chatFirstSurfaceTabsStore,
825
+ resolveChatFirstOpenApp,
826
+ resolveChatFirstOpenBrowser,
827
+ ]);
828
+ useEffect(() => {
829
+ if (!chatFirstMode ||
830
+ !isChatRoute ||
831
+ chatFirstPaneHydratedRef.current ||
832
+ chatFirstAppsQuery.isLoading ||
833
+ chatFirstAppsQuery.isError) {
834
+ return;
835
+ }
836
+ chatFirstPaneHydratedRef.current = true;
837
+ if (chatFirstPane)
838
+ return;
839
+ let active = true;
840
+ void readClientAppState(CHAT_FIRST_PANE_STATE_KEY)
841
+ .then((value) => {
842
+ if (!active)
843
+ return;
844
+ const persisted = persistedChatFirstPane(value);
845
+ if (!persisted)
846
+ return;
847
+ const resolution = resolveChatFirstAppTarget({
848
+ app: persisted.appId,
849
+ path: persisted.path,
850
+ view: persisted.view,
851
+ }, chatFirstAppRegistrations, {
852
+ currentOrigin: typeof window === "undefined"
853
+ ? undefined
854
+ : window.location.origin,
855
+ });
856
+ if (resolution.status === "ready")
857
+ openChatFirstPane(resolution.target);
858
+ })
859
+ .catch(() => {
860
+ if (active) {
861
+ setChatFirstNotice("The last app pane could not be restored from workspace state.");
862
+ }
863
+ });
864
+ return () => {
865
+ active = false;
866
+ };
867
+ }, [
868
+ chatFirstAppsQuery.isError,
869
+ chatFirstAppsQuery.isLoading,
870
+ chatFirstAppRegistrations,
871
+ chatFirstMode,
872
+ chatFirstPane,
873
+ isChatRoute,
874
+ openChatFirstPane,
875
+ ]);
876
+ useEffect(() => {
877
+ if (previousChatFirstSurfaceScopeRef.current === chatFirstSurfaceScope) {
878
+ return;
879
+ }
880
+ previousChatFirstSurfaceScopeRef.current = chatFirstSurfaceScope;
881
+ persistChatFirstPane(null);
882
+ setChatFirstEmbedUrl(null);
883
+ setChatFirstEmbedError(null);
884
+ setChatFirstNotice(null);
885
+ pendingChatFirstOpenAppRef.current = null;
886
+ chatFirstPaneHydratedRef.current = true;
887
+ chatFirstSurfaceTabsStore.closeAll();
888
+ setChatFirstSurfacePanelOpen(false);
889
+ closeChatFirstSessionWatch();
890
+ }, [
891
+ chatFirstSurfaceScope,
892
+ chatFirstSurfaceTabsStore,
893
+ persistChatFirstPane,
894
+ setChatFirstSurfacePanelOpen,
895
+ ]);
896
+ useEffect(() => {
897
+ const tabCount = chatFirstSurfaceTabs.tabs.length;
898
+ const previousTabCount = previousChatFirstSurfaceTabCountRef.current;
899
+ if (!chatFirstMode || !isChatRoute) {
900
+ setChatFirstSurfacePanelOpen(false);
901
+ }
902
+ else if (tabCount > 0 &&
903
+ (previousTabCount === null || previousTabCount === 0)) {
904
+ setChatFirstSurfacePanelOpen(true);
905
+ }
906
+ else if (tabCount === 0 &&
907
+ previousTabCount !== null &&
908
+ previousTabCount > 0) {
909
+ setChatFirstSurfacePanelOpen(false);
910
+ }
911
+ previousChatFirstSurfaceTabCountRef.current = tabCount;
912
+ }, [
913
+ chatFirstMode,
914
+ setChatFirstSurfacePanelOpen,
915
+ chatFirstSurfaceTabs.tabs.length,
916
+ isChatRoute,
917
+ ]);
918
+ useEffect(() => {
919
+ const activeTab = activeChatFirstSurfaceTab;
920
+ if (activeTab?.kind === "side-chat" &&
921
+ activeTab.session &&
922
+ !chatFirstSessionWatch.target) {
923
+ emitChatFirstSessionWatch(activeTab.session);
924
+ }
925
+ }, [activeChatFirstSurfaceTab, chatFirstSessionWatch.target]);
926
+ useEffect(() => {
927
+ const pending = pendingChatFirstOpenAppRef.current;
928
+ if (!pending || chatFirstAppsQuery.isLoading)
929
+ return;
930
+ resolveChatFirstOpenApp(pending);
931
+ }, [
932
+ chatFirstAppsQuery.data,
933
+ chatFirstAppsQuery.isError,
934
+ chatFirstAppsQuery.isLoading,
935
+ resolveChatFirstOpenApp,
936
+ ]);
937
+ useEffect(() => {
938
+ const target = chatFirstSessionWatch.target;
939
+ if (!target || !chatFirstMode || !isChatRoute)
940
+ return;
941
+ setChatFirstPane(null);
942
+ chatFirstSurfaceTabsStore.open({
943
+ id: chatFirstSurfaceTabId("side-chat", target.sessionId),
944
+ kind: "side-chat",
945
+ title: target.title ? `Watch · ${target.title}` : "Watched session",
946
+ session: target,
947
+ });
948
+ }, [
949
+ chatFirstMode,
950
+ chatFirstSessionWatch.target,
951
+ chatFirstSurfaceTabsStore,
952
+ isChatRoute,
953
+ ]);
389
954
  useEffect(() => {
390
955
  if (isWorkspaceAppHostRoute) {
391
956
  if (!sidebarAutoCollapsedRef.current) {
@@ -416,9 +981,114 @@ export function Layout({ children, extensions, agentPageHref, }) {
416
981
  // Ignore storage failures; the in-memory preference still works.
417
982
  }
418
983
  }, [isWorkspaceAppHostRoute, sidebarCollapsed]);
984
+ const activateChatFirstSurfaceTab = useCallback((tab) => {
985
+ chatFirstSurfaceTabsStore.activate(tab.id);
986
+ if (tab.kind === "app" && tab.appId) {
987
+ closeChatFirstSessionWatch();
988
+ persistChatFirstPane({
989
+ appId: tab.appId,
990
+ ...(tab.path ? { path: tab.path } : {}),
991
+ ...(tab.view ? { view: tab.view } : {}),
992
+ });
993
+ return;
994
+ }
995
+ if (tab.kind === "browser" && tab.url) {
996
+ persistChatFirstPane(null);
997
+ closeChatFirstSessionWatch();
998
+ return;
999
+ }
1000
+ if (tab.kind === "side-chat" && tab.session) {
1001
+ persistChatFirstPane(null);
1002
+ emitChatFirstSessionWatch(tab.session);
1003
+ return;
1004
+ }
1005
+ if (tab.kind === "agents") {
1006
+ persistChatFirstPane(null);
1007
+ closeChatFirstSessionWatch();
1008
+ }
1009
+ }, [
1010
+ chatFirstSurfaceTabs.tabs,
1011
+ chatFirstSurfaceTabsStore,
1012
+ persistChatFirstPane,
1013
+ ]);
1014
+ const closeChatFirstSurfaceTab = useCallback((tab) => {
1015
+ const isActive = chatFirstSurfaceTabs.activeTabId === tab.id;
1016
+ if (tab.kind === "app" && isActive)
1017
+ persistChatFirstPane(null);
1018
+ if (tab.kind === "side-chat" && isActive) {
1019
+ closeChatFirstSessionWatch();
1020
+ }
1021
+ chatFirstSurfaceTabsStore.close(tab.id);
1022
+ }, [chatFirstSurfaceTabs, chatFirstSurfaceTabsStore, persistChatFirstPane]);
1023
+ const closeAllChatFirstSurfaceTabs = useCallback(() => {
1024
+ persistChatFirstPane(null);
1025
+ closeChatFirstSessionWatch();
1026
+ chatFirstSurfaceTabsStore.closeAll();
1027
+ }, [chatFirstSurfaceTabsStore, persistChatFirstPane]);
1028
+ const renderChatFirstWatchChat = useCallback((target) => (_jsx(AgentChatSurface, { mode: "page", className: "h-full min-h-0 w-full", defaultMode: "chat", storageKey: `dispatch-session-watch:${target.sessionId}`, restoreActiveThread: false, threadUrlSync: {
1029
+ routeThreadId: target.sessionId,
1030
+ getPath: chatThreadPath,
1031
+ navigate: () => undefined,
1032
+ }, showHeader: false, showTabBar: false, showPageNewChatButton: false, chatOnly: true, dynamicSuggestions: false, suggestions: [], emptyStateDisplay: "hidden", composerPlaceholder: t("dispatch.pages.chatFirstSessionMessagePlaceholder") })), [t]);
1033
+ const renderChatFirstSurfaceTab = useCallback((tab) => {
1034
+ if (tab.kind === "app") {
1035
+ if (tab.id !== chatFirstSurfaceTabs.activeTabId)
1036
+ return null;
1037
+ const app = tab.appId
1038
+ ? (chatFirstWorkspaceApps.find((candidate) => candidate.id === tab.appId) ?? null)
1039
+ : null;
1040
+ return (_jsx(ChatFirstAppPane, { app: app, status: chatFirstEmbedError
1041
+ ? "error"
1042
+ : chatFirstEmbedUrl
1043
+ ? "ready"
1044
+ : chatFirstAppsQuery.isLoading
1045
+ ? "loading"
1046
+ : app
1047
+ ? "loading"
1048
+ : "unresolved", embedUrl: chatFirstEmbedUrl, errorMessage: chatFirstEmbedError, onRetry: () => setChatFirstEmbedAttempt((value) => value + 1), renderEmbed: ({ url, title }) => (_jsx("iframe", { "data-dispatch-chat-first-app-frame": true, src: url, title: title ?? chatFirstCopy("appUnavailable"), referrerPolicy: "no-referrer", allow: "clipboard-read; clipboard-write", className: "h-full w-full border-0 bg-background" })), copy: chatFirstCopy }));
1049
+ }
1050
+ if (tab.kind === "browser" && tab.url) {
1051
+ return (_jsx(ChatFirstBrowserPane, { url: tab.url, title: tab.title, onClose: () => closeChatFirstSurfaceTab(tab), renderEmbed: ({ url, title, key }) => (_jsx("iframe", { src: url, title: title ?? chatFirstCopy("browserPage"), referrerPolicy: "no-referrer", allow: "clipboard-read; clipboard-write", className: "h-full w-full border-0 bg-background" }, key)), copy: chatFirstCopy }));
1052
+ }
1053
+ if (tab.kind === "side-chat") {
1054
+ const target = tab.session ??
1055
+ (tab.id === chatFirstSurfaceTabs.activeTabId
1056
+ ? chatFirstSessionWatch.target
1057
+ : null);
1058
+ return target ? (_jsx(ChatFirstSessionWatchPane, { target: target, onClose: () => closeChatFirstSurfaceTab(tab), renderChat: renderChatFirstWatchChat, copy: chatFirstCopy })) : null;
1059
+ }
1060
+ if (tab.kind === "agents") {
1061
+ return (_jsx(ChatFirstAgentsPane, { activities: chatFirstAgentActivities, loading: chatFirstAgentsQuery.isLoading, error: chatFirstAgentsQuery.isError
1062
+ ? chatFirstAgentsQuery.error.message
1063
+ : null, onRefresh: () => void chatFirstAgentsQuery.refetch(), onWatch: (activity) => emitChatFirstSessionWatch({
1064
+ sessionId: activity.sessionId,
1065
+ title: activity.title,
1066
+ kind: "agent-chat",
1067
+ ...(activity.goalId ? { goalId: activity.goalId } : {}),
1068
+ }), copy: chatFirstCopy }));
1069
+ }
1070
+ return null;
1071
+ }, [
1072
+ chatFirstAgentActivities,
1073
+ chatFirstAgentsQuery.error,
1074
+ chatFirstAgentsQuery.isError,
1075
+ chatFirstAgentsQuery.isLoading,
1076
+ chatFirstAppsQuery.isLoading,
1077
+ chatFirstCopy,
1078
+ chatFirstEmbedError,
1079
+ chatFirstEmbedUrl,
1080
+ chatFirstSessionWatch.target,
1081
+ chatFirstSurfaceTabs.activeTabId,
1082
+ chatFirstWorkspaceApps,
1083
+ closeChatFirstSurfaceTab,
1084
+ renderChatFirstWatchChat,
1085
+ ]);
419
1086
  if (CHROMELESS_PATHS.some((path) => localPathname === path)) {
420
1087
  return _jsx(_Fragment, { children: children });
421
1088
  }
1089
+ if (isWorkspaceAppHostRoute) {
1090
+ return (_jsx(DispatchExtensionsContext.Provider, { value: extensions, children: _jsx(HeaderActionsProvider, { children: _jsx("div", { "data-dispatch-workspace-app-chrome-less": true, className: "h-screen w-full overflow-hidden bg-background", children: children }) }) }));
1091
+ }
422
1092
  const showHeader = !isChatRoute && !pageOwnsToolbar(localPathname);
423
1093
  function openAskAgentFullscreen() {
424
1094
  focusAgentChat();
@@ -429,10 +1099,27 @@ export function Layout({ children, extensions, agentPageHref, }) {
429
1099
  t("dispatch.sidebar.suggestionRouteSlack"),
430
1100
  t("dispatch.sidebar.suggestionGrantKey"),
431
1101
  ];
432
- const appContent = (_jsxs("div", { className: "flex h-full min-w-0 flex-1 flex-col overflow-hidden", children: [showHeader ? _jsx(Header, { onOpenMobile: () => setMobileOpen(true) }) : null, _jsx(InvitationBanner, {}), _jsx("main", { className: cn("flex-1", isChatRoute || isWorkspaceAppHostRoute
1102
+ const appContent = (_jsxs("div", { className: "relative flex h-full min-w-0 flex-1 flex-col overflow-hidden", children: [showHeader ? _jsx(Header, { onOpenMobile: () => setMobileOpen(true) }) : null, _jsx(InvitationBanner, {}), isChatRoute && chatFirstMode ? (_jsx(ChatFirstSurfacePanelToggle, { open: chatFirstSurfacePanel.open, onToggle: chatFirstSurfacePanel.toggle, className: "absolute right-3 top-2 z-[4]" })) : null, isChatRoute && chatFirstMode && chatFirstNotice ? (_jsxs("div", { className: "dispatch-chat-first-notice", role: "status", "aria-live": "polite", children: [_jsx("span", { children: chatFirstNotice }), _jsx("button", { type: "button", onClick: () => setChatFirstNotice(null), "aria-label": "Dismiss app notice", children: "Dismiss" })] })) : null, _jsx("main", { className: cn("flex-1", isChatRoute || isWorkspaceAppHostRoute
433
1103
  ? "min-h-0 overflow-hidden"
434
1104
  : "overflow-y-auto"), children: showHeader ? (_jsx("div", { className: "mx-auto max-w-7xl space-y-10 px-4 py-6 sm:px-6", children: children })) : (children) })] }));
435
- const content = isChatRoute ? (_jsx("div", { className: "agent-layout-main-surface flex min-w-0 flex-1 overflow-hidden", children: appContent })) : (_jsx(AgentSidebar, { position: "right", defaultOpen: false, agentPageHref: agentPageHref, chatViewTransition: true, storageKey: "dispatch", openOnChatRunning: chatHomeHandoffActive, onFullscreenRequest: openAskAgentFullscreen, emptyStateText: t("dispatch.sidebar.emptyAgentText"), suggestions: sidebarSuggestions, children: appContent }));
436
- return (_jsx(DispatchExtensionsContext.Provider, { value: extensions, children: _jsx(HeaderActionsProvider, { children: _jsxs("div", { className: "agent-layout-shell flex h-screen w-full overflow-hidden bg-background", children: [_jsx("aside", { "data-collapsed": sidebarCollapsed ? "true" : "false", className: cn("agent-layout-left-drawer hidden shrink-0 flex-col border-e !border-e-sidebar-border bg-sidebar text-sidebar-foreground transition-[width] duration-200 ease-out lg:flex", sidebarCollapsed ? "w-14" : "w-56"), children: _jsx(NavContent, { extensions: extensions, collapsed: sidebarCollapsed, collapsible: true, onCollapsedChange: setSidebarCollapsed }) }), _jsx(Sheet, { open: mobileOpen, onOpenChange: setMobileOpen, children: _jsxs(SheetContent, { side: "left", className: "w-72 p-0 bg-sidebar text-sidebar-foreground [&>button]:hidden", children: [_jsx(SheetTitle, { className: "sr-only", children: t("dispatch.nav.navigation") }), _jsx(SheetDescription, { className: "sr-only", children: t("dispatch.nav.navigationDescription") }), _jsx("div", { className: "flex h-full w-full flex-col", children: _jsx(NavContent, { extensions: extensions, collapsed: false, onNavigate: () => setMobileOpen(false) }) })] }) }), content] }) }) }));
1105
+ const content = isChatRoute ? (_jsxs("div", { className: cn("agent-layout-main-surface flex min-w-0 flex-1 overflow-hidden", chatFirstMode && "dispatch-chat-first-surface"), children: [appContent, chatFirstMode && chatFirstSurfacePanel.open ? (_jsxs(ChatFirstSurfacePanel, { width: chatFirstSurfaceResize.width, onResizePointerDown: chatFirstSurfaceResize.onPointerDown, copy: chatFirstCopy, children: [_jsx(ChatFirstSurfaceTabs, { tabs: chatFirstSurfaceTabs.tabs, activeTabId: chatFirstSurfaceTabs.activeTabId, onActivate: activateChatFirstSurfaceTab, onClose: closeChatFirstSurfaceTab, onCloseOthers: (tab) => {
1106
+ activateChatFirstSurfaceTab(tab);
1107
+ chatFirstSurfaceTabsStore.closeOthers(tab.id);
1108
+ }, onCloseToRight: (tab) => {
1109
+ const targetIndex = chatFirstSurfaceTabs.tabs.findIndex((candidate) => candidate.id === tab.id);
1110
+ const activeIndex = chatFirstSurfaceTabs.tabs.findIndex((candidate) => candidate.id === chatFirstSurfaceTabs.activeTabId);
1111
+ if (activeIndex > targetIndex)
1112
+ activateChatFirstSurfaceTab(tab);
1113
+ chatFirstSurfaceTabsStore.closeToRight(tab.id);
1114
+ }, onCloseAll: closeAllChatFirstSurfaceTabs, onOpenSurface: openChatFirstSurface, copy: chatFirstCopy }), chatFirstSurfaceTabs.tabs.length > 0 ? (_jsx(ChatFirstSurfaceContent, { tabs: chatFirstSurfaceTabs.tabs, activeTabId: chatFirstSurfaceTabs.activeTabId, renderTab: renderChatFirstSurfaceTab })) : null] })) : null] })) : (_jsx(AgentSidebar, { position: "right", defaultOpen: false, agentPageHref: agentPageHref, chatViewTransition: true, storageKey: "dispatch", openOnChatRunning: chatHomeHandoffActive, onFullscreenRequest: openAskAgentFullscreen, emptyStateText: t("dispatch.sidebar.emptyAgentText"), suggestions: sidebarSuggestions, children: appContent }));
1115
+ return (_jsx(DispatchExtensionsContext.Provider, { value: extensions, children: _jsx(HeaderActionsProvider, { children: _jsxs("div", { className: "agent-layout-shell flex h-screen w-full overflow-hidden bg-background", children: [_jsx("aside", { "data-collapsed": sidebarCollapsed ? "true" : "false", className: cn("agent-layout-left-drawer hidden shrink-0 flex-col border-e !border-e-sidebar-border bg-sidebar text-sidebar-foreground transition-[width] duration-200 ease-out lg:flex", sidebarCollapsed ? "w-14" : "w-56"), children: _jsx(NavContent, { extensions: extensions, chatFirstMode: chatFirstMode, chatFirstEmbedded: chatFirstEmbedded, collapsed: sidebarCollapsed, chatFirstAppLayout: chatFirstAppLayout, onChatFirstAppLayoutChange: persistChatFirstAppLayout, chatFirstApps: chatFirstAppItems, chatFirstAppsLoading: chatFirstAppsQuery.isLoading, chatFirstAppsError: chatFirstAppsQuery.isError
1116
+ ? chatFirstCopy("appsLoadError")
1117
+ : null, chatFirstActiveAppId: activeChatFirstSurfaceTab?.kind === "app"
1118
+ ? activeChatFirstSurfaceTab.appId
1119
+ : undefined, onChatFirstAppOpen: (app) => openChatFirstPane({ appId: app.id }), onChatFirstAppsRetry: () => void chatFirstAppsQuery.refetch(), collapsible: true, onCollapsedChange: setSidebarCollapsed }) }), _jsx(Sheet, { open: mobileOpen, onOpenChange: setMobileOpen, children: _jsxs(SheetContent, { side: "left", className: "w-72 p-0 bg-sidebar text-sidebar-foreground [&>button]:hidden", children: [_jsx(SheetTitle, { className: "sr-only", children: t("dispatch.nav.navigation") }), _jsx(SheetDescription, { className: "sr-only", children: t("dispatch.nav.navigationDescription") }), _jsx("div", { className: "flex h-full w-full flex-col", children: _jsx(NavContent, { extensions: extensions, chatFirstMode: chatFirstMode, chatFirstEmbedded: chatFirstEmbedded, collapsed: false, chatFirstAppLayout: chatFirstAppLayout, onChatFirstAppLayoutChange: persistChatFirstAppLayout, chatFirstApps: chatFirstAppItems, chatFirstAppsLoading: chatFirstAppsQuery.isLoading, chatFirstAppsError: chatFirstAppsQuery.isError
1120
+ ? chatFirstCopy("appsLoadError")
1121
+ : null, chatFirstActiveAppId: activeChatFirstSurfaceTab?.kind === "app"
1122
+ ? activeChatFirstSurfaceTab.appId
1123
+ : undefined, onChatFirstAppOpen: (app) => openChatFirstPane({ appId: app.id }), onChatFirstAppsRetry: () => void chatFirstAppsQuery.refetch(), onNavigate: () => setMobileOpen(false) }) })] }) }), content] }) }) }));
437
1124
  }
438
1125
  //# sourceMappingURL=Layout.js.map