@agent-native/dispatch 0.40.0 → 0.40.3

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 (128) hide show
  1. package/CHANGELOG.md +48 -19
  2. package/dist/actions/provider-api-register.d.ts +7 -7
  3. package/dist/components/app-open-actions.d.ts +1 -1
  4. package/dist/components/app-open-actions.d.ts.map +1 -1
  5. package/dist/components/app-open-actions.js +1 -1
  6. package/dist/components/app-open-actions.js.map +1 -1
  7. package/dist/components/app-row-settings.js +1 -1
  8. package/dist/components/app-row-settings.js.map +1 -1
  9. package/dist/components/automation-create-dialog.js +2 -2
  10. package/dist/components/automation-create-dialog.js.map +1 -1
  11. package/dist/components/automation-details-panel.js +1 -1
  12. package/dist/components/automation-details-panel.js.map +1 -1
  13. package/dist/components/chat-home-apps.js +1 -1
  14. package/dist/components/chat-home-apps.js.map +1 -1
  15. package/dist/components/create-app-popover.js +3 -3
  16. package/dist/components/create-app-popover.js.map +1 -1
  17. package/dist/components/dispatch-control-plane.d.ts.map +1 -1
  18. package/dist/components/dispatch-control-plane.js +9 -30
  19. package/dist/components/dispatch-control-plane.js.map +1 -1
  20. package/dist/components/layout/Header.js +2 -2
  21. package/dist/components/layout/Header.js.map +1 -1
  22. package/dist/components/layout/Layout.d.ts +6 -5
  23. package/dist/components/layout/Layout.d.ts.map +1 -1
  24. package/dist/components/layout/Layout.js +73 -113
  25. package/dist/components/layout/Layout.js.map +1 -1
  26. package/dist/components/layout/workspace-apps-rail.d.ts.map +1 -1
  27. package/dist/components/layout/workspace-apps-rail.js +7 -19
  28. package/dist/components/layout/workspace-apps-rail.js.map +1 -1
  29. package/dist/components/messaging-setup-panel.js +2 -1
  30. package/dist/components/messaging-setup-panel.js.map +1 -1
  31. package/dist/components/workspace-app-card.js +2 -2
  32. package/dist/components/workspace-app-card.js.map +1 -1
  33. package/dist/components/workspace-app-host.d.ts +1 -1
  34. package/dist/components/workspace-app-host.d.ts.map +1 -1
  35. package/dist/components/workspace-app-host.js +7 -74
  36. package/dist/components/workspace-app-host.js.map +1 -1
  37. package/dist/hooks/use-navigation-state.js +1 -1
  38. package/dist/hooks/use-navigation-state.js.map +1 -1
  39. package/dist/lib/workspace-apps.js +1 -1
  40. package/dist/lib/workspace-apps.js.map +1 -1
  41. package/dist/routes/pages/$appId.js +1 -1
  42. package/dist/routes/pages/$appId.js.map +1 -1
  43. package/dist/routes/pages/_index.js +1 -1
  44. package/dist/routes/pages/_index.js.map +1 -1
  45. package/dist/routes/pages/admin._index.js +1 -1
  46. package/dist/routes/pages/admin._index.js.map +1 -1
  47. package/dist/routes/pages/agent-integrations.js +1 -1
  48. package/dist/routes/pages/agent-integrations.js.map +1 -1
  49. package/dist/routes/pages/apps.d.ts.map +1 -1
  50. package/dist/routes/pages/apps.js +3 -43
  51. package/dist/routes/pages/apps.js.map +1 -1
  52. package/dist/routes/pages/browser-chat.js +1 -1
  53. package/dist/routes/pages/browser-chat.js.map +1 -1
  54. package/dist/routes/pages/chat.d.ts.map +1 -1
  55. package/dist/routes/pages/chat.js +24 -43
  56. package/dist/routes/pages/chat.js.map +1 -1
  57. package/dist/routes/pages/extensions.$id.js +1 -1
  58. package/dist/routes/pages/extensions.$id.js.map +1 -1
  59. package/dist/routes/pages/extensions._index.js +1 -1
  60. package/dist/routes/pages/extensions._index.js.map +1 -1
  61. package/dist/routes/pages/integrations.js +1 -1
  62. package/dist/routes/pages/integrations.js.map +1 -1
  63. package/dist/routes/pages/new-app.js +1 -1
  64. package/dist/routes/pages/new-app.js.map +1 -1
  65. package/dist/routes/pages/operations.js +2 -2
  66. package/dist/routes/pages/operations.js.map +1 -1
  67. package/dist/routes/pages/overview.d.ts +2 -2
  68. package/dist/routes/pages/settings-page.d.ts +10 -0
  69. package/dist/routes/pages/settings-page.d.ts.map +1 -0
  70. package/dist/routes/pages/settings-page.js +104 -0
  71. package/dist/routes/pages/settings-page.js.map +1 -0
  72. package/dist/routes/pages/settings.d.ts +4 -12
  73. package/dist/routes/pages/settings.d.ts.map +1 -1
  74. package/dist/routes/pages/settings.js +4 -88
  75. package/dist/routes/pages/settings.js.map +1 -1
  76. package/dist/routes/pages/tools.$id.js +1 -1
  77. package/dist/routes/pages/tools.$id.js.map +1 -1
  78. package/dist/routes/pages/tools._index.js +1 -1
  79. package/dist/routes/pages/tools._index.js.map +1 -1
  80. package/dist/routes/pages/vault.d.ts +1 -1
  81. package/dist/routes/pages/vault.js +1 -1
  82. package/dist/routes/pages/vault.js.map +1 -1
  83. package/dist/shared/app-roles.js.map +1 -1
  84. package/package.json +3 -3
  85. package/src/components/app-open-actions.tsx +1 -1
  86. package/src/components/app-row-settings.tsx +1 -1
  87. package/src/components/automation-create-dialog.tsx +7 -7
  88. package/src/components/automation-details-panel.tsx +1 -1
  89. package/src/components/chat-home-apps.tsx +1 -1
  90. package/src/components/create-app-popover.spec.tsx +5 -7
  91. package/src/components/create-app-popover.tsx +3 -3
  92. package/src/components/dispatch-control-plane.spec.tsx +12 -19
  93. package/src/components/dispatch-control-plane.tsx +10 -84
  94. package/src/components/layout/Header.tsx +2 -2
  95. package/src/components/layout/Layout.app-chat.spec.ts +1 -0
  96. package/src/components/layout/Layout.spec.tsx +49 -30
  97. package/src/components/layout/Layout.tsx +153 -205
  98. package/src/components/layout/workspace-apps-rail.spec.tsx +79 -0
  99. package/src/components/layout/workspace-apps-rail.tsx +6 -30
  100. package/src/components/messaging-setup-panel.tsx +1 -1
  101. package/src/components/workspace-app-card.tsx +2 -2
  102. package/src/components/workspace-app-chat-rail.spec.tsx +2 -2
  103. package/src/components/workspace-app-host.spec.tsx +19 -19
  104. package/src/components/workspace-app-host.tsx +12 -129
  105. package/src/hooks/use-navigation-state.ts +1 -1
  106. package/src/lib/workspace-apps.ts +1 -1
  107. package/src/routes/pages/$appId.tsx +1 -1
  108. package/src/routes/pages/_index.tsx +1 -1
  109. package/src/routes/pages/admin._index.tsx +1 -1
  110. package/src/routes/pages/agent-integrations.tsx +1 -1
  111. package/src/routes/pages/apps.tsx +2 -86
  112. package/src/routes/pages/automations.spec.tsx +1 -1
  113. package/src/routes/pages/browser-chat.spec.tsx +1 -1
  114. package/src/routes/pages/browser-chat.tsx +1 -1
  115. package/src/routes/pages/chat.spec.tsx +23 -46
  116. package/src/routes/pages/chat.tsx +20 -57
  117. package/src/routes/pages/extensions.$id.tsx +1 -1
  118. package/src/routes/pages/extensions._index.tsx +1 -1
  119. package/src/routes/pages/integrations.tsx +1 -1
  120. package/src/routes/pages/new-app.tsx +1 -1
  121. package/src/routes/pages/operations.tsx +2 -2
  122. package/src/routes/pages/settings-page.tsx +250 -0
  123. package/src/routes/pages/settings.spec.tsx +30 -8
  124. package/src/routes/pages/settings.tsx +4 -184
  125. package/src/routes/pages/tools.$id.tsx +1 -1
  126. package/src/routes/pages/tools._index.tsx +1 -1
  127. package/src/routes/pages/vault.tsx +1 -1
  128. package/src/shared/app-roles.ts +1 -1
@@ -1,14 +1,9 @@
1
1
  import {
2
2
  CHAT_FIRST_MODE_CHANGED_EVENT,
3
- AgentChatSurface,
4
- AgentToggleButton,
5
3
  chatFirstSurfaceTabId,
6
- AgentSidebar,
7
- ChatFirstSurfacePanelToggle,
8
4
  closeChatFirstSessionWatch,
9
5
  emitChatFirstSessionWatch,
10
6
  getChatFirstSurfaceTabsStore,
11
- focusAgentChat,
12
7
  navigateWithAgentChatViewTransition,
13
8
  readChatFirstAppLayout,
14
9
  readChatFirstMode,
@@ -39,6 +34,20 @@ import {
39
34
  readClientAppState,
40
35
  writeClientAppState,
41
36
  } from "@agent-native/core/client/application-state";
37
+ import {
38
+ useFeatureFlagState,
39
+ type FeatureFlagState,
40
+ } from "@agent-native/core/client/feature-flags";
41
+ import { useActionQuery } from "@agent-native/core/client/hooks";
42
+ import { useT } from "@agent-native/core/client/i18n";
43
+ import { SETTINGS_REDESIGN_FLAG } from "@agent-native/core/feature-flags/registry";
44
+ import {
45
+ AgentChatSurface,
46
+ AgentToggleButton,
47
+ AgentSidebar,
48
+ focusAgentChat,
49
+ } from "@agent-native/toolkit/app/chat";
50
+ import { ChatFirstSurfacePanelToggle } from "@agent-native/toolkit/app/chat/chat-first";
42
51
  import {
43
52
  ChatFirstAgentsPane,
44
53
  ChatFirstAppPane,
@@ -56,23 +65,21 @@ import {
56
65
  type ChatFirstCopy,
57
66
  type ChatFirstEmbedTarget,
58
67
  type ChatFirstPrimaryTab,
59
- } from "@agent-native/core/client/chat-first";
60
- import { writeClipboardText } from "@agent-native/core/client/clipboard";
61
- import { useActionQuery } from "@agent-native/core/client/hooks";
62
- import { useT } from "@agent-native/core/client/i18n";
63
- import { openCommandMenu } from "@agent-native/core/client/navigation";
64
- import { InvitationBanner, OrgSwitcher } from "@agent-native/core/client/org";
65
- import { RunsTray } from "@agent-native/core/client/progress";
66
- import { isSettingsPathname } from "@agent-native/core/client/settings";
68
+ } from "@agent-native/toolkit/app/chat/chat-first";
69
+ import { FeedbackButton } from "@agent-native/toolkit/app/feedback";
70
+ import { InvitationBanner, OrgSwitcher } from "@agent-native/toolkit/app/org";
71
+ import { RunsTray } from "@agent-native/toolkit/app/progress";
72
+ import { isSettingsPathname } from "@agent-native/toolkit/app/settings";
67
73
  import {
68
74
  AppSidebarFooter,
69
75
  AppSidebarHeader,
70
- FeedbackButton,
71
- } from "@agent-native/core/client/ui";
76
+ openCommandMenu,
77
+ } from "@agent-native/toolkit/app/shared";
72
78
  import {
73
79
  ChatHistoryRail,
74
80
  type ChatHistoryItem,
75
81
  } from "@agent-native/toolkit/chat-history";
82
+ import { writeClipboardText } from "@agent-native/toolkit/clipboard";
76
83
  import {
77
84
  IconApps,
78
85
  IconBrandSlack,
@@ -113,8 +120,6 @@ import {
113
120
  isPathMountedWorkspaceApp,
114
121
  isWorkspaceAppVisibleInDefaultLaunchers,
115
122
  isWorkspaceSsoApp,
116
- mergeChatFirstWorkspaceApps,
117
- workspaceAppSourceFromConnected,
118
123
  navigateToWorkspaceApp,
119
124
  shouldOpenWorkspaceAppInTopWindow,
120
125
  workspaceAppIdFromRoute,
@@ -261,15 +266,6 @@ interface DispatchChatFirstPane {
261
266
  view?: string;
262
267
  }
263
268
 
264
- interface ChatFirstConnectedAppSummary {
265
- id: string;
266
- name: string;
267
- description?: string | null;
268
- url?: string | null;
269
- homeUrl?: string | null;
270
- source?: string;
271
- }
272
-
273
269
  interface DispatchAgentThreadSummary {
274
270
  id: string;
275
271
  title: string;
@@ -286,7 +282,6 @@ const DispatchExtensionsContext = createContext<
286
282
  DispatchExtensionConfig | undefined
287
283
  >(undefined);
288
284
  interface DispatchWorkspaceAppLauncher {
289
- apps: readonly ChatFirstAppItem[];
290
285
  workspaceApps: readonly WorkspaceAppSummary[];
291
286
  isLoading: boolean;
292
287
  error?: unknown;
@@ -361,11 +356,18 @@ export function shouldQueryChatFirstApps(
361
356
  }
362
357
 
363
358
  /**
364
- * Settings brings its own navigation, header, and agent toggle, so it renders
365
- * full width.
359
+ * The redesigned Settings brings its own navigation, header, and agent
360
+ * toggle, so it renders full width. While the flag loads it shows the
361
+ * shell's skeleton, which needs the same frame.
366
362
  */
367
- export function isSettingsShellPath(pathname: string): boolean {
368
- return isSettingsPathname(localDispatchPath(pathname));
363
+ export function isRedesignedSettingsPath(
364
+ pathname: string,
365
+ settingsRedesign: FeatureFlagState,
366
+ ): boolean {
367
+ return (
368
+ isSettingsPathname(localDispatchPath(pathname)) &&
369
+ (settingsRedesign.enabled || settingsRedesign.status === "loading")
370
+ );
369
371
  }
370
372
 
371
373
  function chatFirstPrimaryTabForPath(
@@ -1391,7 +1393,10 @@ export function Layout({
1391
1393
  );
1392
1394
  const isChatRoute =
1393
1395
  localPathname === "/chat" || localPathname.startsWith("/chat/");
1394
- const isSettingsShellRoute = isSettingsShellPath(location.pathname);
1396
+ const isRedesignedSettingsRoute = isRedesignedSettingsPath(
1397
+ location.pathname,
1398
+ useFeatureFlagState(SETTINGS_REDESIGN_FLAG.key),
1399
+ );
1395
1400
  const chatFirstSurfaceScope = threadIdFromPath(localPathname) ?? "new";
1396
1401
  const isWorkspaceAppRoute = shouldAutoCollapseDispatchSidebar(
1397
1402
  location.pathname,
@@ -1432,94 +1437,43 @@ export function Layout({
1432
1437
  const chatFirstAppsQuery = useActionQuery<WorkspaceAppSummary[]>(
1433
1438
  "list-workspace-apps",
1434
1439
  { includeAgentCards: false, includeArchived: true },
1435
- { enabled: shouldQueryChatFirstApps(isChatRoute, chatFirstMode) },
1436
- );
1437
- // Not list_apps: MCP app access restricts routing, not what the workspace
1438
- // shows. list-connected-agents already applies the built-in app config.
1439
- const chatFirstConnectedAppsQuery = useActionQuery<
1440
- ChatFirstConnectedAppSummary[]
1441
- >(
1442
- "list-connected-agents",
1443
- {},
1444
- { enabled: shouldQueryChatFirstApps(isChatRoute, chatFirstMode) },
1445
- );
1446
- const chatFirstEnabledBuiltinAppIds = useMemo(
1447
- () =>
1448
- chatFirstConnectedAppsQuery.data
1449
- ?.filter((app) => app.source === "builtin")
1450
- .map((app) => app.id),
1451
- [chatFirstConnectedAppsQuery.data],
1440
+ {
1441
+ enabled:
1442
+ shouldQueryChatFirstApps(isChatRoute, chatFirstMode) ||
1443
+ workspaceAppRouteActive,
1444
+ },
1452
1445
  );
1453
1446
  const chatFirstWorkspaceApps = useMemo(
1454
1447
  () =>
1455
- mergeChatFirstWorkspaceApps(
1456
- chatFirstAppsQuery.data,
1457
- chatFirstEnabledBuiltinAppIds,
1458
- ),
1459
- [chatFirstAppsQuery.data, chatFirstEnabledBuiltinAppIds],
1460
- );
1461
- const chatHomeWorkspaceApps = useMemo(
1462
- () =>
1463
- mergeChatFirstWorkspaceApps(
1464
- chatFirstWorkspaceApps,
1465
- chatFirstEnabledBuiltinAppIds,
1466
- chatFirstConnectedAppsQuery.data?.map((app) => ({
1467
- id: app.id,
1468
- name: app.name,
1469
- description: app.description,
1470
- url: app.homeUrl?.trim() || app.url,
1471
- source: workspaceAppSourceFromConnected(app.source),
1472
- })),
1473
- ).filter(
1448
+ (chatFirstAppsQuery.data ?? []).filter(
1474
1449
  (app) =>
1475
1450
  app.status !== "pending" &&
1476
1451
  app.archived !== true &&
1477
1452
  isWorkspaceAppVisibleInDefaultLaunchers(app),
1478
1453
  ),
1479
- [
1480
- chatFirstConnectedAppsQuery.data,
1481
- chatFirstEnabledBuiltinAppIds,
1482
- chatFirstWorkspaceApps,
1483
- ],
1454
+ [chatFirstAppsQuery.data],
1484
1455
  );
1485
1456
  const chatFirstAppRegistrations = useMemo<ChatFirstAppRegistration[]>(() => {
1486
- const registrations = new Map<string, ChatFirstAppRegistration>();
1487
- for (const app of chatFirstWorkspaceApps) {
1488
- registrations.set(app.id.toLowerCase(), {
1457
+ return (chatFirstAppsQuery.data ?? [])
1458
+ .filter((app) => app.status !== "pending" && app.archived !== true)
1459
+ .map((app) => ({
1489
1460
  id: app.id,
1490
1461
  name: app.name,
1491
- source: app.source,
1462
+ source: app.source ?? "workspace",
1492
1463
  path: app.path,
1493
1464
  url: app.url,
1494
1465
  homePath: app.homePath,
1495
- enabled: app.status !== "pending" && app.archived !== true,
1496
- });
1497
- }
1498
- for (const app of chatFirstConnectedAppsQuery.data ?? []) {
1499
- const id = app.id.trim().toLowerCase();
1500
- if (!id || registrations.has(id)) continue;
1501
- registrations.set(id, {
1502
- id,
1503
- name: app.name,
1504
- source: workspaceAppSourceFromConnected(app.source),
1505
- url: app.homeUrl?.trim() || app.url,
1506
1466
  enabled: true,
1507
- });
1508
- }
1509
- return [...registrations.values()];
1510
- }, [chatFirstConnectedAppsQuery.data, chatFirstWorkspaceApps]);
1467
+ }));
1468
+ }, [chatFirstAppsQuery.data]);
1511
1469
  const chatFirstAppItems = useMemo<ChatFirstAppItem[]>(
1512
1470
  () =>
1513
- chatFirstAppRegistrations
1514
- .filter(
1515
- (app) => app.enabled && isWorkspaceAppVisibleInDefaultLaunchers(app),
1516
- )
1517
- .map((app) => ({
1518
- id: app.id,
1519
- name: app.name ?? app.id,
1520
- source: app.source,
1521
- })),
1522
- [chatFirstAppRegistrations],
1471
+ chatFirstWorkspaceApps.map((app) => ({
1472
+ id: app.id,
1473
+ name: app.name,
1474
+ source: app.source ?? "workspace",
1475
+ })),
1476
+ [chatFirstWorkspaceApps],
1523
1477
  );
1524
1478
  const openChatFirstApp = useCallback(
1525
1479
  (app: ChatFirstAppItem) => {
@@ -1553,32 +1507,18 @@ export function Layout({
1553
1507
  );
1554
1508
  const chatHomeAppLauncher = useMemo<DispatchWorkspaceAppLauncher>(
1555
1509
  () => ({
1556
- apps: chatFirstAppItems,
1557
- workspaceApps: chatHomeWorkspaceApps,
1558
- isLoading:
1559
- chatFirstAppsQuery.isLoading || chatFirstConnectedAppsQuery.isLoading,
1560
- error: chatFirstAppsQuery.isError
1561
- ? chatFirstAppsQuery.error
1562
- : chatFirstConnectedAppsQuery.isError
1563
- ? chatFirstConnectedAppsQuery.error
1564
- : undefined,
1510
+ workspaceApps: chatFirstWorkspaceApps,
1511
+ isLoading: chatFirstAppsQuery.isLoading,
1512
+ error: chatFirstAppsQuery.isError ? chatFirstAppsQuery.error : undefined,
1565
1513
  openApp: openChatFirstApp,
1566
- retry: () => {
1567
- void chatFirstAppsQuery.refetch();
1568
- void chatFirstConnectedAppsQuery.refetch();
1569
- },
1514
+ retry: () => void chatFirstAppsQuery.refetch(),
1570
1515
  }),
1571
1516
  [
1572
1517
  chatFirstAppsQuery.error,
1573
1518
  chatFirstAppsQuery.isError,
1574
1519
  chatFirstAppsQuery.isLoading,
1575
1520
  chatFirstAppsQuery.refetch,
1576
- chatFirstConnectedAppsQuery.error,
1577
- chatFirstConnectedAppsQuery.isError,
1578
- chatFirstConnectedAppsQuery.isLoading,
1579
- chatFirstConnectedAppsQuery.refetch,
1580
- chatFirstAppItems,
1581
- chatHomeWorkspaceApps,
1521
+ chatFirstWorkspaceApps,
1582
1522
  openChatFirstApp,
1583
1523
  ],
1584
1524
  );
@@ -1776,10 +1716,7 @@ export function Layout({
1776
1716
  );
1777
1717
  const resolveChatFirstOpenApp = useCallback(
1778
1718
  (detail: ChatFirstOpenAppDetail) => {
1779
- if (
1780
- chatFirstAppsQuery.isLoading ||
1781
- chatFirstConnectedAppsQuery.isLoading
1782
- ) {
1719
+ if (chatFirstAppsQuery.isLoading) {
1783
1720
  pendingChatFirstOpenAppRef.current = detail;
1784
1721
  return;
1785
1722
  }
@@ -1823,7 +1760,6 @@ export function Layout({
1823
1760
  chatFirstAppRegistrations,
1824
1761
  chatFirstAppsQuery.isError,
1825
1762
  chatFirstAppsQuery.isLoading,
1826
- chatFirstConnectedAppsQuery.isLoading,
1827
1763
  chatFirstSurfaceTabsStore,
1828
1764
  openChatFirstPane,
1829
1765
  navigate,
@@ -2032,19 +1968,12 @@ export function Layout({
2032
1968
 
2033
1969
  useEffect(() => {
2034
1970
  const pending = pendingChatFirstOpenAppRef.current;
2035
- if (
2036
- !pending ||
2037
- chatFirstAppsQuery.isLoading ||
2038
- chatFirstConnectedAppsQuery.isLoading
2039
- )
2040
- return;
1971
+ if (!pending || chatFirstAppsQuery.isLoading) return;
2041
1972
  resolveChatFirstOpenApp(pending);
2042
1973
  }, [
2043
1974
  chatFirstAppsQuery.data,
2044
1975
  chatFirstAppsQuery.isError,
2045
1976
  chatFirstAppsQuery.isLoading,
2046
- chatFirstConnectedAppsQuery.data,
2047
- chatFirstConnectedAppsQuery.isLoading,
2048
1977
  resolveChatFirstOpenApp,
2049
1978
  ]);
2050
1979
 
@@ -2302,6 +2231,51 @@ export function Layout({
2302
2231
  ],
2303
2232
  );
2304
2233
 
2234
+ const chatFirstSurfaceTabsBar =
2235
+ activeChatFirstSurfaceTab?.kind === "app" ? null : (
2236
+ <ChatFirstSurfaceTabs
2237
+ tabs={chatFirstSurfaceTabs.tabs}
2238
+ activeTabId={chatFirstSurfaceTabs.activeTabId}
2239
+ onActivate={activateChatFirstSurfaceTab}
2240
+ onClose={closeChatFirstSurfaceTab}
2241
+ onCloseOthers={(tab) => {
2242
+ activateChatFirstSurfaceTab(tab);
2243
+ chatFirstSurfaceTabsStore.closeOthers(tab.id);
2244
+ }}
2245
+ onCloseToRight={(tab) => {
2246
+ const targetIndex = chatFirstSurfaceTabs.tabs.findIndex(
2247
+ (candidate) => candidate.id === tab.id,
2248
+ );
2249
+ const activeIndex = chatFirstSurfaceTabs.tabs.findIndex(
2250
+ (candidate) => candidate.id === chatFirstSurfaceTabs.activeTabId,
2251
+ );
2252
+ if (activeIndex > targetIndex) activateChatFirstSurfaceTab(tab);
2253
+ chatFirstSurfaceTabsStore.closeToRight(tab.id);
2254
+ }}
2255
+ onCloseAll={closeAllChatFirstSurfaceTabs}
2256
+ onOpenSurface={openChatFirstSurface}
2257
+ apps={chatFirstAppItems}
2258
+ onOpenApp={(app) => openChatFirstPane({ appId: app.id }, "side")}
2259
+ renderAppIcon={(app) => (
2260
+ <AppIcon
2261
+ id={app.id}
2262
+ name={app.name}
2263
+ size="sm"
2264
+ className="size-7 rounded-lg"
2265
+ />
2266
+ )}
2267
+ copy={chatFirstCopy}
2268
+ />
2269
+ );
2270
+ const chatFirstSurfaceContent =
2271
+ chatFirstSurfaceTabs.tabs.length > 0 ? (
2272
+ <ChatFirstSurfaceContent
2273
+ tabs={chatFirstSurfaceTabs.tabs}
2274
+ activeTabId={chatFirstSurfaceTabs.activeTabId}
2275
+ renderTab={renderChatFirstSurfaceTab}
2276
+ />
2277
+ ) : null;
2278
+
2305
2279
  if (CHROMELESS_PATHS.some((path) => localPathname === path)) {
2306
2280
  return <>{children}</>;
2307
2281
  }
@@ -2320,10 +2294,32 @@ export function Layout({
2320
2294
  <div className="relative flex min-w-0 flex-1 flex-col overflow-hidden">
2321
2295
  <Header showAgentToggle={false} />
2322
2296
  <InvitationBanner />
2323
- <main className="flex-1 overflow-y-auto">
2324
- <div className="mx-auto max-w-7xl space-y-10 px-4 py-6 sm:px-6">
2325
- {children}
2297
+ <main className="relative flex min-h-0 flex-1 overflow-hidden">
2298
+ <div className="min-w-0 flex-1 overflow-y-auto">
2299
+ <div className="mx-auto max-w-7xl space-y-10 px-4 py-6 sm:px-6">
2300
+ {children}
2301
+ </div>
2326
2302
  </div>
2303
+ {isChatRoute &&
2304
+ chatFirstMode &&
2305
+ chatFirstHasActiveChat &&
2306
+ chatFirstSurfacePanel.open ? (
2307
+ <ChatFirstSurfacePanel
2308
+ width={chatFirstSurfaceResize.width}
2309
+ onResizePointerDown={chatFirstSurfaceResize.onPointerDown}
2310
+ copy={chatFirstCopy}
2311
+ >
2312
+ {chatFirstSurfaceTabsBar}
2313
+ {chatFirstSurfaceContent}
2314
+ </ChatFirstSurfacePanel>
2315
+ ) : null}
2316
+ {isChatRoute && chatFirstMode && chatFirstHasActiveChat ? (
2317
+ <ChatFirstSurfacePanelToggle
2318
+ open={chatFirstSurfacePanel.open}
2319
+ onToggle={chatFirstSurfacePanel.toggle}
2320
+ className={CHAT_FIRST_SURFACE_PANEL_TOGGLE_CLASS_NAME}
2321
+ />
2322
+ ) : null}
2327
2323
  </main>
2328
2324
  </div>
2329
2325
  </div>
@@ -2349,7 +2345,9 @@ export function Layout({
2349
2345
  }
2350
2346
 
2351
2347
  const showAgentControls =
2352
- !isChatRoute && !isSettingsShellRoute && !pageOwnsToolbar(localPathname);
2348
+ !isChatRoute &&
2349
+ !isRedesignedSettingsRoute &&
2350
+ !pageOwnsToolbar(localPathname);
2353
2351
  function openAskAgentFullscreen() {
2354
2352
  focusAgentChat();
2355
2353
  navigateWithAgentChatViewTransition(
@@ -2382,13 +2380,6 @@ export function Layout({
2382
2380
  const appContent = (
2383
2381
  <div className="relative flex h-full min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
2384
2382
  <InvitationBanner />
2385
- {isChatRoute && chatFirstMode && chatFirstHasActiveChat ? (
2386
- <ChatFirstSurfacePanelToggle
2387
- open={chatFirstSurfacePanel.open}
2388
- onToggle={chatFirstSurfacePanel.toggle}
2389
- className={CHAT_FIRST_SURFACE_PANEL_TOGGLE_CLASS_NAME}
2390
- />
2391
- ) : null}
2392
2383
  {isChatRoute && chatFirstMode && chatFirstNotice ? (
2393
2384
  <div
2394
2385
  className="dispatch-chat-first-notice"
@@ -2450,68 +2441,25 @@ export function Layout({
2450
2441
  </main>
2451
2442
  </div>
2452
2443
  );
2453
- const chatFirstSurfaceTabsBar =
2454
- activeChatFirstSurfaceTab?.kind === "app" ? null : (
2455
- <ChatFirstSurfaceTabs
2456
- tabs={chatFirstSurfaceTabs.tabs}
2457
- activeTabId={chatFirstSurfaceTabs.activeTabId}
2458
- onActivate={activateChatFirstSurfaceTab}
2459
- onClose={closeChatFirstSurfaceTab}
2460
- onCloseOthers={(tab) => {
2461
- activateChatFirstSurfaceTab(tab);
2462
- chatFirstSurfaceTabsStore.closeOthers(tab.id);
2463
- }}
2464
- onCloseToRight={(tab) => {
2465
- const targetIndex = chatFirstSurfaceTabs.tabs.findIndex(
2466
- (candidate) => candidate.id === tab.id,
2467
- );
2468
- const activeIndex = chatFirstSurfaceTabs.tabs.findIndex(
2469
- (candidate) => candidate.id === chatFirstSurfaceTabs.activeTabId,
2470
- );
2471
- if (activeIndex > targetIndex) activateChatFirstSurfaceTab(tab);
2472
- chatFirstSurfaceTabsStore.closeToRight(tab.id);
2473
- }}
2474
- onCloseAll={closeAllChatFirstSurfaceTabs}
2475
- onOpenSurface={openChatFirstSurface}
2476
- apps={chatFirstAppItems}
2477
- onOpenApp={(app) => openChatFirstPane({ appId: app.id }, "side")}
2478
- renderAppIcon={(app) => (
2479
- <AppIcon
2480
- id={app.id}
2481
- name={app.name}
2482
- size="sm"
2483
- className="size-7 rounded-lg"
2484
- />
2485
- )}
2486
- copy={chatFirstCopy}
2487
- />
2488
- );
2489
- const chatFirstSurfaceContent =
2490
- chatFirstSurfaceTabs.tabs.length > 0 ? (
2491
- <ChatFirstSurfaceContent
2492
- tabs={chatFirstSurfaceTabs.tabs}
2493
- activeTabId={chatFirstSurfaceTabs.activeTabId}
2494
- renderTab={renderChatFirstSurfaceTab}
2495
- />
2496
- ) : null;
2497
- const workspaceAppChatName =
2498
- (workspaceAppId
2499
- ? chatFirstAppRegistrations.find(
2500
- (app) => app.id.toLowerCase() === workspaceAppId.toLowerCase(),
2501
- )?.name
2502
- : null) ??
2503
- workspaceAppId ??
2504
- "Workspace app";
2444
+ const workspaceAppChatRegistration = workspaceAppId
2445
+ ? chatFirstAppRegistrations.find(
2446
+ (app) => app.id.toLowerCase() === workspaceAppId.toLowerCase(),
2447
+ )
2448
+ : undefined;
2505
2449
  const workspaceAppContent =
2506
2450
  workspaceAppRouteActive && workspaceAppId ? (
2507
- <WorkspaceAppChatRail
2508
- appId={workspaceAppId}
2509
- appName={workspaceAppChatName}
2510
- agentPageHref={agentPageHref}
2511
- onFullscreenRequest={openAskAgentFullscreen}
2512
- >
2451
+ workspaceAppChatRegistration ? (
2452
+ <WorkspaceAppChatRail
2453
+ appId={workspaceAppId}
2454
+ appName={workspaceAppChatRegistration.name ?? workspaceAppId}
2455
+ agentPageHref={agentPageHref}
2456
+ onFullscreenRequest={openAskAgentFullscreen}
2457
+ >
2458
+ <WorkspaceAppKeepAlive activeAppId={workspaceAppId} />
2459
+ </WorkspaceAppChatRail>
2460
+ ) : (
2513
2461
  <WorkspaceAppKeepAlive activeAppId={workspaceAppId} />
2514
- </WorkspaceAppChatRail>
2462
+ )
2515
2463
  ) : (
2516
2464
  <WorkspaceAppKeepAlive
2517
2465
  activeAppId={workspaceAppRouteActive ? workspaceAppId : null}
@@ -2572,7 +2520,7 @@ export function Layout({
2572
2520
  <DispatchWorkspaceAppLauncherContext.Provider value={chatHomeAppLauncher}>
2573
2521
  <HeaderActionsProvider>
2574
2522
  <div className="agent-layout-shell flex h-screen w-full overflow-hidden bg-background">
2575
- {isSettingsShellRoute ? null : (
2523
+ {isRedesignedSettingsRoute ? null : (
2576
2524
  <aside
2577
2525
  data-collapsed={sidebarCollapsed ? "true" : "false"}
2578
2526
  className={cn(
@@ -0,0 +1,79 @@
1
+ // @vitest-environment happy-dom
2
+ import React, { act } from "react";
3
+ import { createRoot, type Root } from "react-dom/client";
4
+ import { MemoryRouter } from "react-router";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+
7
+ const clientState = vi.hoisted(() => ({
8
+ actions: [] as string[],
9
+ }));
10
+
11
+ vi.mock("@agent-native/core/client/hooks", () => ({
12
+ useActionQuery: (action: string) => {
13
+ clientState.actions.push(action);
14
+ return {
15
+ data:
16
+ action === "list-workspace-apps"
17
+ ? [
18
+ {
19
+ id: "reports",
20
+ name: "Reports",
21
+ path: "/reports",
22
+ url: "/reports",
23
+ status: "ready",
24
+ },
25
+ ]
26
+ : [
27
+ {
28
+ id: "internal-app",
29
+ name: "Internal app",
30
+ url: "https://internal.example.test",
31
+ homeUrl: "https://internal.example.test",
32
+ source: "builtin",
33
+ },
34
+ ],
35
+ isError: false,
36
+ };
37
+ },
38
+ }));
39
+
40
+ vi.mock("@agent-native/core/client/i18n", () => ({
41
+ useT: () => (_key: string, values?: { defaultValue?: string }) =>
42
+ values?.defaultValue ?? "Workspace apps",
43
+ }));
44
+
45
+ import { WorkspaceAppsRail } from "./workspace-apps-rail";
46
+
47
+ describe("WorkspaceAppsRail", () => {
48
+ let container: HTMLDivElement;
49
+ let root: Root;
50
+
51
+ beforeEach(() => {
52
+ vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
53
+ clientState.actions = [];
54
+ container = document.createElement("div");
55
+ document.body.appendChild(container);
56
+ root = createRoot(container);
57
+ });
58
+
59
+ afterEach(() => {
60
+ act(() => root.unmount());
61
+ container.remove();
62
+ vi.unstubAllGlobals();
63
+ });
64
+
65
+ it("lists mounted workspace apps without connected agents", async () => {
66
+ await act(async () => {
67
+ root.render(
68
+ <MemoryRouter initialEntries={["/overview"]}>
69
+ <WorkspaceAppsRail />
70
+ </MemoryRouter>,
71
+ );
72
+ });
73
+
74
+ expect(container.textContent).toContain("Reports");
75
+ expect(container.textContent).not.toContain("Internal app");
76
+ expect(container.querySelector('a[href="/apps/reports"]')).not.toBeNull();
77
+ expect(clientState.actions).toEqual(["list-workspace-apps"]);
78
+ });
79
+ });
@@ -3,10 +3,6 @@ import { useT } from "@agent-native/core/client/i18n";
3
3
  import { IconApps } from "@tabler/icons-react";
4
4
  import { Link, useLocation } from "react-router";
5
5
 
6
- import {
7
- filterOtherApps,
8
- type ConnectedAppSummary,
9
- } from "../../lib/other-apps";
10
6
  import { cn } from "../../lib/utils";
11
7
  import {
12
8
  isWorkspaceAppVisibleInDefaultLaunchers,
@@ -26,7 +22,7 @@ function appLabel(app: WorkspaceAppSummary): string {
26
22
  return app.name.trim() || app.id;
27
23
  }
28
24
 
29
- type RailApp = WorkspaceAppSummary & { external?: boolean };
25
+ type RailApp = WorkspaceAppSummary;
30
26
 
31
27
  export function WorkspaceAppsRail({
32
28
  collapsed = false,
@@ -43,33 +39,17 @@ export function WorkspaceAppsRail({
43
39
  includeAgentCards: false,
44
40
  },
45
41
  );
46
- const connectedAppsQuery = useActionQuery<ConnectedAppSummary[]>(
47
- "list-connected-agents",
48
- {},
49
- );
50
42
  if (appsQuery.isError || !appsQuery.data) return null;
51
43
 
52
- const workspaceApps = appsQuery.data;
53
- const apps: RailApp[] = [
54
- ...workspaceApps.filter(
44
+ const apps: RailApp[] = appsQuery.data
45
+ .filter(
55
46
  (app) =>
56
47
  isWorkspaceAppVisibleInDefaultLaunchers(app) &&
57
48
  !app.archived &&
58
49
  app.status !== "pending" &&
59
50
  !!workspaceAppHref(app),
60
- ),
61
- ...filterOtherApps(connectedAppsQuery.data ?? [], workspaceApps).map(
62
- (app) => ({
63
- id: app.id,
64
- name: app.name,
65
- description: app.description,
66
- path: "",
67
- url: app.homeUrl ?? app.url,
68
- status: "ready" as const,
69
- external: true,
70
- }),
71
- ),
72
- ].sort((a, b) => appLabel(a).localeCompare(appLabel(b)));
51
+ )
52
+ .sort((a, b) => appLabel(a).localeCompare(appLabel(b)));
73
53
 
74
54
  if (apps.length === 0) return null;
75
55
 
@@ -102,11 +82,7 @@ export function WorkspaceAppsRail({
102
82
  {!collapsed ? <span className="truncate">{label}</span> : null}
103
83
  </>
104
84
  );
105
- const link = app.external ? (
106
- <a href={href} {...linkProps}>
107
- {linkContent}
108
- </a>
109
- ) : (
85
+ const link = (
110
86
  <Link to={workspaceAppRoute(app.id)} {...linkProps}>
111
87
  {linkContent}
112
88
  </Link>