@agent-native/dispatch 0.40.13 → 0.40.14-nightly-20261006033736
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/actions/provider-api-register.d.ts +10 -10
- package/dist/components/connected-app-card.d.ts.map +1 -1
- package/dist/components/connected-app-card.js +23 -4
- package/dist/components/connected-app-card.js.map +1 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +116 -60
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/layout/workspace-apps-rail.d.ts.map +1 -1
- package/dist/components/layout/workspace-apps-rail.js +35 -9
- package/dist/components/layout/workspace-apps-rail.js.map +1 -1
- package/dist/lib/other-apps.d.ts +1 -0
- package/dist/lib/other-apps.d.ts.map +1 -1
- package/dist/lib/other-apps.js +29 -1
- package/dist/lib/other-apps.js.map +1 -1
- package/dist/lib/workspace-apps.d.ts +2 -1
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js +15 -5
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +17 -4
- package/dist/routes/pages/apps.js.map +1 -1
- package/dist/server/plugins/auth.js +2 -2
- package/dist/server/plugins/auth.js.map +1 -1
- package/package.json +4 -4
- package/src/components/connected-app-card.spec.tsx +100 -0
- package/src/components/connected-app-card.tsx +38 -9
- package/src/components/layout/Layout.spec.tsx +397 -6
- package/src/components/layout/Layout.tsx +173 -85
- package/src/components/layout/workspace-apps-rail.spec.tsx +69 -7
- package/src/components/layout/workspace-apps-rail.tsx +49 -7
- package/src/lib/other-apps.spec.ts +84 -2
- package/src/lib/other-apps.ts +39 -1
- package/src/lib/workspace-apps.spec.ts +53 -0
- package/src/lib/workspace-apps.ts +15 -7
- package/src/routes/pages/apps.spec.tsx +132 -0
- package/src/routes/pages/apps.tsx +46 -10
- package/src/server/plugins/auth.ts +2 -2
|
@@ -101,6 +101,10 @@ import { Link, useLocation, useNavigate } from "react-router";
|
|
|
101
101
|
import { toast } from "sonner";
|
|
102
102
|
|
|
103
103
|
import { useIsMobile } from "../../hooks/use-mobile";
|
|
104
|
+
import {
|
|
105
|
+
filterBuiltInApps,
|
|
106
|
+
type ConnectedAppSummary,
|
|
107
|
+
} from "../../lib/other-apps";
|
|
104
108
|
import { cn } from "../../lib/utils";
|
|
105
109
|
import { normalizeWorkspaceAppLayout } from "../../lib/workspace-app-layout";
|
|
106
110
|
import {
|
|
@@ -108,6 +112,7 @@ import {
|
|
|
108
112
|
isPathMountedWorkspaceApp,
|
|
109
113
|
isWorkspaceAppVisibleInDefaultLaunchers,
|
|
110
114
|
isWorkspaceSsoApp,
|
|
115
|
+
mergeChatFirstWorkspaceApps,
|
|
111
116
|
navigateToWorkspaceApp,
|
|
112
117
|
shouldOpenWorkspaceAppInTopWindow,
|
|
113
118
|
workspaceAppIdFromRoute,
|
|
@@ -1458,29 +1463,65 @@ function DispatchLayout({
|
|
|
1458
1463
|
workspaceAppRouteActive,
|
|
1459
1464
|
},
|
|
1460
1465
|
);
|
|
1461
|
-
const
|
|
1466
|
+
const chatFirstConnectedAppsQuery = useActionQuery<ConnectedAppSummary[]>(
|
|
1467
|
+
"list-connected-agents",
|
|
1468
|
+
{},
|
|
1469
|
+
{ enabled: shouldQueryChatFirstApps(isChatRoute, chatFirstMode) },
|
|
1470
|
+
);
|
|
1471
|
+
const chatFirstBuiltinApps = useMemo(
|
|
1462
1472
|
() =>
|
|
1463
|
-
(
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
app.archived !== true &&
|
|
1467
|
-
isWorkspaceAppVisibleInDefaultLaunchers(app),
|
|
1473
|
+
filterBuiltInApps(
|
|
1474
|
+
chatFirstConnectedAppsQuery.data ?? [],
|
|
1475
|
+
chatFirstAppsQuery.data ?? [],
|
|
1468
1476
|
),
|
|
1469
|
-
[chatFirstAppsQuery.data],
|
|
1477
|
+
[chatFirstAppsQuery.data, chatFirstConnectedAppsQuery.data],
|
|
1478
|
+
);
|
|
1479
|
+
const chatFirstEnabledBuiltinAppIds = useMemo(
|
|
1480
|
+
() => chatFirstBuiltinApps.map((app) => app.id),
|
|
1481
|
+
[chatFirstBuiltinApps],
|
|
1482
|
+
);
|
|
1483
|
+
const chatFirstWorkspaceAppsLoading = chatFirstAppsQuery.isLoading;
|
|
1484
|
+
const chatFirstConnectedAppsLoading = chatFirstConnectedAppsQuery.isLoading;
|
|
1485
|
+
const chatFirstAppsLoading =
|
|
1486
|
+
chatFirstWorkspaceAppsLoading || chatFirstConnectedAppsLoading;
|
|
1487
|
+
const chatFirstAppsFailed = chatFirstAppsQuery.isError;
|
|
1488
|
+
const chatFirstAppsError =
|
|
1489
|
+
chatFirstAppsQuery.error ?? chatFirstConnectedAppsQuery.error;
|
|
1490
|
+
const chatFirstResolvableApps = useMemo(
|
|
1491
|
+
() =>
|
|
1492
|
+
mergeChatFirstWorkspaceApps(
|
|
1493
|
+
chatFirstAppsQuery.data,
|
|
1494
|
+
chatFirstEnabledBuiltinAppIds,
|
|
1495
|
+
chatFirstBuiltinApps.map((app) => ({
|
|
1496
|
+
id: app.id,
|
|
1497
|
+
name: app.name,
|
|
1498
|
+
description: app.description,
|
|
1499
|
+
url: app.homeUrl?.trim() || app.url,
|
|
1500
|
+
source: "builtin" as const,
|
|
1501
|
+
})),
|
|
1502
|
+
).filter((app) => app.status !== "pending" && app.archived !== true),
|
|
1503
|
+
[
|
|
1504
|
+
chatFirstAppsQuery.data,
|
|
1505
|
+
chatFirstBuiltinApps,
|
|
1506
|
+
chatFirstEnabledBuiltinAppIds,
|
|
1507
|
+
],
|
|
1508
|
+
);
|
|
1509
|
+
const chatFirstWorkspaceApps = useMemo(
|
|
1510
|
+
() =>
|
|
1511
|
+
chatFirstResolvableApps.filter(isWorkspaceAppVisibleInDefaultLaunchers),
|
|
1512
|
+
[chatFirstResolvableApps],
|
|
1470
1513
|
);
|
|
1471
1514
|
const chatFirstAppRegistrations = useMemo<ChatFirstAppRegistration[]>(() => {
|
|
1472
|
-
return (
|
|
1473
|
-
|
|
1474
|
-
.
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
}));
|
|
1483
|
-
}, [chatFirstAppsQuery.data]);
|
|
1515
|
+
return chatFirstResolvableApps.map((app) => ({
|
|
1516
|
+
id: app.id,
|
|
1517
|
+
name: app.name,
|
|
1518
|
+
source: app.source ?? "workspace",
|
|
1519
|
+
path: app.path,
|
|
1520
|
+
url: app.url,
|
|
1521
|
+
homePath: app.homePath,
|
|
1522
|
+
enabled: true,
|
|
1523
|
+
}));
|
|
1524
|
+
}, [chatFirstResolvableApps]);
|
|
1484
1525
|
const chatFirstAppItems = useMemo<ChatFirstAppItem[]>(
|
|
1485
1526
|
() =>
|
|
1486
1527
|
chatFirstWorkspaceApps.map((app) => ({
|
|
@@ -1496,6 +1537,14 @@ function DispatchLayout({
|
|
|
1496
1537
|
const registration = chatFirstAppRegistrations.find(
|
|
1497
1538
|
(candidate) => candidate.id.toLowerCase() === app.id.toLowerCase(),
|
|
1498
1539
|
);
|
|
1540
|
+
if (registration?.source === "builtin") {
|
|
1541
|
+
const directHref = workspaceAppDirectHref(
|
|
1542
|
+
registration,
|
|
1543
|
+
workspaceAppTargetPath(registration),
|
|
1544
|
+
);
|
|
1545
|
+
if (directHref) navigateToWorkspaceApp(directHref);
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1499
1548
|
const hasWorkspaceRoute = Boolean(registration?.path?.trim());
|
|
1500
1549
|
if (!hasWorkspaceRoute) {
|
|
1501
1550
|
if (registration) {
|
|
@@ -1523,16 +1572,19 @@ function DispatchLayout({
|
|
|
1523
1572
|
const chatHomeAppLauncher = useMemo<DispatchWorkspaceAppLauncher>(
|
|
1524
1573
|
() => ({
|
|
1525
1574
|
workspaceApps: chatFirstWorkspaceApps,
|
|
1526
|
-
isLoading:
|
|
1527
|
-
error:
|
|
1575
|
+
isLoading: chatFirstAppsLoading,
|
|
1576
|
+
error: chatFirstAppsError,
|
|
1528
1577
|
openApp: openChatFirstApp,
|
|
1529
|
-
retry: () =>
|
|
1578
|
+
retry: () => {
|
|
1579
|
+
void chatFirstAppsQuery.refetch();
|
|
1580
|
+
void chatFirstConnectedAppsQuery.refetch();
|
|
1581
|
+
},
|
|
1530
1582
|
}),
|
|
1531
1583
|
[
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
chatFirstAppsQuery.isLoading,
|
|
1584
|
+
chatFirstAppsError,
|
|
1585
|
+
chatFirstAppsLoading,
|
|
1535
1586
|
chatFirstAppsQuery.refetch,
|
|
1587
|
+
chatFirstConnectedAppsQuery.refetch,
|
|
1536
1588
|
chatFirstWorkspaceApps,
|
|
1537
1589
|
openChatFirstApp,
|
|
1538
1590
|
],
|
|
@@ -1540,6 +1592,8 @@ function DispatchLayout({
|
|
|
1540
1592
|
const chatFirstCopy = useMemo(() => createDispatchChatFirstCopy(t), [t]);
|
|
1541
1593
|
const [chatFirstPane, setChatFirstPane] =
|
|
1542
1594
|
useState<DispatchChatFirstPane | null>(null);
|
|
1595
|
+
const [pendingChatFirstPaneRestore, setPendingChatFirstPaneRestore] =
|
|
1596
|
+
useState<DispatchChatFirstPane | null>(null);
|
|
1543
1597
|
const [chatFirstNotice, setChatFirstNotice] = useState<string | null>(null);
|
|
1544
1598
|
const chatFirstSessionWatch = useChatFirstSessionWatch();
|
|
1545
1599
|
const chatFirstSurfaceTabs = useChatFirstSurfaceTabs(chatFirstSurfaceScope);
|
|
@@ -1731,18 +1785,6 @@ function DispatchLayout({
|
|
|
1731
1785
|
);
|
|
1732
1786
|
const resolveChatFirstOpenApp = useCallback(
|
|
1733
1787
|
(detail: ChatFirstOpenAppDetail) => {
|
|
1734
|
-
if (chatFirstAppsQuery.isLoading) {
|
|
1735
|
-
pendingChatFirstOpenAppRef.current = detail;
|
|
1736
|
-
return;
|
|
1737
|
-
}
|
|
1738
|
-
if (chatFirstAppsQuery.isError) {
|
|
1739
|
-
pendingChatFirstOpenAppRef.current = null;
|
|
1740
|
-
setChatFirstNotice(
|
|
1741
|
-
"Workspace apps could not be loaded, so the requested app was not opened.",
|
|
1742
|
-
);
|
|
1743
|
-
return;
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
1788
|
const resolution = resolveChatFirstAppTarget(
|
|
1747
1789
|
detail,
|
|
1748
1790
|
chatFirstAppRegistrations,
|
|
@@ -1751,11 +1793,29 @@ function DispatchLayout({
|
|
|
1751
1793
|
typeof window === "undefined" ? undefined : window.location.origin,
|
|
1752
1794
|
},
|
|
1753
1795
|
);
|
|
1754
|
-
pendingChatFirstOpenAppRef.current = null;
|
|
1755
1796
|
if (resolution.status === "unresolved") {
|
|
1797
|
+
if (
|
|
1798
|
+
chatFirstWorkspaceAppsLoading ||
|
|
1799
|
+
(resolution.reason === "unknown-app" && chatFirstConnectedAppsLoading)
|
|
1800
|
+
) {
|
|
1801
|
+
pendingChatFirstOpenAppRef.current = detail;
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
if (chatFirstConnectedAppsQuery.isError) {
|
|
1805
|
+
pendingChatFirstOpenAppRef.current = detail;
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
pendingChatFirstOpenAppRef.current = null;
|
|
1809
|
+
if (chatFirstAppsFailed) {
|
|
1810
|
+
setChatFirstNotice(
|
|
1811
|
+
"Workspace apps could not be loaded, so the requested app was not opened.",
|
|
1812
|
+
);
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1756
1815
|
setChatFirstNotice(chatFirstResolutionMessage(resolution.reason));
|
|
1757
1816
|
return;
|
|
1758
1817
|
}
|
|
1818
|
+
pendingChatFirstOpenAppRef.current = null;
|
|
1759
1819
|
if (isDispatchWorkspaceAppId(resolution.target.appId)) {
|
|
1760
1820
|
closeChatFirstSessionWatch();
|
|
1761
1821
|
chatFirstSurfaceTabsStore.closeAll();
|
|
@@ -1773,8 +1833,10 @@ function DispatchLayout({
|
|
|
1773
1833
|
},
|
|
1774
1834
|
[
|
|
1775
1835
|
chatFirstAppRegistrations,
|
|
1776
|
-
|
|
1777
|
-
|
|
1836
|
+
chatFirstAppsFailed,
|
|
1837
|
+
chatFirstWorkspaceAppsLoading,
|
|
1838
|
+
chatFirstConnectedAppsLoading,
|
|
1839
|
+
chatFirstConnectedAppsQuery.isError,
|
|
1778
1840
|
chatFirstSurfaceTabsStore,
|
|
1779
1841
|
openChatFirstPane,
|
|
1780
1842
|
navigate,
|
|
@@ -1850,6 +1912,7 @@ function DispatchLayout({
|
|
|
1850
1912
|
chatFirstSurfaceTabsStore.closeAll();
|
|
1851
1913
|
setChatFirstNotice(null);
|
|
1852
1914
|
pendingChatFirstOpenAppRef.current = null;
|
|
1915
|
+
setPendingChatFirstPaneRestore(null);
|
|
1853
1916
|
chatFirstPaneHydratedRef.current = false;
|
|
1854
1917
|
return;
|
|
1855
1918
|
}
|
|
@@ -1870,13 +1933,7 @@ function DispatchLayout({
|
|
|
1870
1933
|
]);
|
|
1871
1934
|
|
|
1872
1935
|
useEffect(() => {
|
|
1873
|
-
if (
|
|
1874
|
-
!chatFirstMode ||
|
|
1875
|
-
!isChatRoute ||
|
|
1876
|
-
chatFirstPaneHydratedRef.current ||
|
|
1877
|
-
chatFirstAppsQuery.isLoading ||
|
|
1878
|
-
chatFirstAppsQuery.isError
|
|
1879
|
-
) {
|
|
1936
|
+
if (!chatFirstMode || !isChatRoute || chatFirstPaneHydratedRef.current) {
|
|
1880
1937
|
return;
|
|
1881
1938
|
}
|
|
1882
1939
|
chatFirstPaneHydratedRef.current = true;
|
|
@@ -1885,25 +1942,7 @@ function DispatchLayout({
|
|
|
1885
1942
|
void readClientAppState<unknown>(CHAT_FIRST_PANE_STATE_KEY)
|
|
1886
1943
|
.then((value) => {
|
|
1887
1944
|
if (!active) return;
|
|
1888
|
-
|
|
1889
|
-
if (!persisted) return;
|
|
1890
|
-
const resolution = resolveChatFirstAppTarget(
|
|
1891
|
-
{
|
|
1892
|
-
app: persisted.appId,
|
|
1893
|
-
path: persisted.path,
|
|
1894
|
-
view: persisted.view,
|
|
1895
|
-
},
|
|
1896
|
-
chatFirstAppRegistrations,
|
|
1897
|
-
{
|
|
1898
|
-
currentOrigin:
|
|
1899
|
-
typeof window === "undefined"
|
|
1900
|
-
? undefined
|
|
1901
|
-
: window.location.origin,
|
|
1902
|
-
},
|
|
1903
|
-
);
|
|
1904
|
-
if (resolution.status === "ready") {
|
|
1905
|
-
openChatFirstPane(resolution.target, persisted.placement);
|
|
1906
|
-
}
|
|
1945
|
+
setPendingChatFirstPaneRestore(persistedChatFirstPane(value));
|
|
1907
1946
|
})
|
|
1908
1947
|
.catch(() => {
|
|
1909
1948
|
if (active) {
|
|
@@ -1915,14 +1954,56 @@ function DispatchLayout({
|
|
|
1915
1954
|
return () => {
|
|
1916
1955
|
active = false;
|
|
1917
1956
|
};
|
|
1957
|
+
}, [chatFirstMode, chatFirstPane, chatFirstSurfaceScope, isChatRoute]);
|
|
1958
|
+
|
|
1959
|
+
useEffect(() => {
|
|
1960
|
+
if (!chatFirstMode || !isChatRoute || !pendingChatFirstPaneRestore) return;
|
|
1961
|
+
if (chatFirstPane) {
|
|
1962
|
+
setPendingChatFirstPaneRestore(null);
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
const resolution = resolveChatFirstAppTarget(
|
|
1966
|
+
{
|
|
1967
|
+
app: pendingChatFirstPaneRestore.appId,
|
|
1968
|
+
path: pendingChatFirstPaneRestore.path,
|
|
1969
|
+
view: pendingChatFirstPaneRestore.view,
|
|
1970
|
+
},
|
|
1971
|
+
chatFirstAppRegistrations,
|
|
1972
|
+
{
|
|
1973
|
+
currentOrigin:
|
|
1974
|
+
typeof window === "undefined" ? undefined : window.location.origin,
|
|
1975
|
+
},
|
|
1976
|
+
);
|
|
1977
|
+
if (resolution.status === "ready") {
|
|
1978
|
+
setPendingChatFirstPaneRestore(null);
|
|
1979
|
+
openChatFirstPane(
|
|
1980
|
+
resolution.target,
|
|
1981
|
+
pendingChatFirstPaneRestore.placement,
|
|
1982
|
+
);
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
if (
|
|
1986
|
+
chatFirstWorkspaceAppsLoading ||
|
|
1987
|
+
(resolution.status === "unresolved" &&
|
|
1988
|
+
resolution.reason === "unknown-app" &&
|
|
1989
|
+
chatFirstConnectedAppsLoading) ||
|
|
1990
|
+
chatFirstAppsFailed ||
|
|
1991
|
+
chatFirstConnectedAppsQuery.isError
|
|
1992
|
+
) {
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
setPendingChatFirstPaneRestore(null);
|
|
1918
1996
|
}, [
|
|
1919
|
-
|
|
1920
|
-
chatFirstAppsQuery.isLoading,
|
|
1997
|
+
chatFirstAppsFailed,
|
|
1921
1998
|
chatFirstAppRegistrations,
|
|
1999
|
+
chatFirstWorkspaceAppsLoading,
|
|
2000
|
+
chatFirstConnectedAppsLoading,
|
|
2001
|
+
chatFirstConnectedAppsQuery.isError,
|
|
1922
2002
|
chatFirstMode,
|
|
1923
2003
|
chatFirstPane,
|
|
1924
2004
|
isChatRoute,
|
|
1925
2005
|
openChatFirstPane,
|
|
2006
|
+
pendingChatFirstPaneRestore,
|
|
1926
2007
|
]);
|
|
1927
2008
|
|
|
1928
2009
|
useEffect(() => {
|
|
@@ -1933,6 +2014,7 @@ function DispatchLayout({
|
|
|
1933
2014
|
persistChatFirstPane(null);
|
|
1934
2015
|
setChatFirstNotice(null);
|
|
1935
2016
|
pendingChatFirstOpenAppRef.current = null;
|
|
2017
|
+
setPendingChatFirstPaneRestore(null);
|
|
1936
2018
|
chatFirstPaneHydratedRef.current = true;
|
|
1937
2019
|
chatFirstSurfaceTabsStore.closeAll();
|
|
1938
2020
|
setChatFirstSurfacePanelOpen(false);
|
|
@@ -1983,12 +2065,15 @@ function DispatchLayout({
|
|
|
1983
2065
|
|
|
1984
2066
|
useEffect(() => {
|
|
1985
2067
|
const pending = pendingChatFirstOpenAppRef.current;
|
|
1986
|
-
if (!pending
|
|
2068
|
+
if (!pending) return;
|
|
1987
2069
|
resolveChatFirstOpenApp(pending);
|
|
1988
2070
|
}, [
|
|
1989
2071
|
chatFirstAppsQuery.data,
|
|
1990
|
-
|
|
1991
|
-
|
|
2072
|
+
chatFirstAppsFailed,
|
|
2073
|
+
chatFirstWorkspaceAppsLoading,
|
|
2074
|
+
chatFirstConnectedAppsLoading,
|
|
2075
|
+
chatFirstConnectedAppsQuery.data,
|
|
2076
|
+
chatFirstConnectedAppsQuery.isError,
|
|
1992
2077
|
resolveChatFirstOpenApp,
|
|
1993
2078
|
]);
|
|
1994
2079
|
|
|
@@ -2165,7 +2250,9 @@ function DispatchLayout({
|
|
|
2165
2250
|
return renderChatFirstAppSurfaceTab({
|
|
2166
2251
|
registration,
|
|
2167
2252
|
embedPath: tab.path ?? registration?.path ?? "/",
|
|
2168
|
-
loading:
|
|
2253
|
+
loading:
|
|
2254
|
+
!registration &&
|
|
2255
|
+
(chatFirstWorkspaceAppsLoading || chatFirstConnectedAppsLoading),
|
|
2169
2256
|
isMobileSurface,
|
|
2170
2257
|
copy: chatFirstCopy,
|
|
2171
2258
|
});
|
|
@@ -2235,7 +2322,8 @@ function DispatchLayout({
|
|
|
2235
2322
|
chatFirstAgentsQuery.error,
|
|
2236
2323
|
chatFirstAgentsQuery.isError,
|
|
2237
2324
|
chatFirstAgentsQuery.isLoading,
|
|
2238
|
-
|
|
2325
|
+
chatFirstWorkspaceAppsLoading,
|
|
2326
|
+
chatFirstConnectedAppsLoading,
|
|
2239
2327
|
chatFirstCopy,
|
|
2240
2328
|
chatFirstSessionWatch.target,
|
|
2241
2329
|
chatFirstSurfaceTabs.activeTabId,
|
|
@@ -2553,11 +2641,9 @@ function DispatchLayout({
|
|
|
2553
2641
|
chatFirstAppLayout={chatFirstAppLayout}
|
|
2554
2642
|
onChatFirstAppLayoutChange={persistChatFirstAppLayout}
|
|
2555
2643
|
chatFirstApps={chatFirstAppItems}
|
|
2556
|
-
chatFirstAppsLoading={
|
|
2644
|
+
chatFirstAppsLoading={chatFirstAppsLoading}
|
|
2557
2645
|
chatFirstAppsError={
|
|
2558
|
-
|
|
2559
|
-
? chatFirstCopy("appsLoadError")
|
|
2560
|
-
: null
|
|
2646
|
+
chatFirstAppsError ? chatFirstCopy("appsLoadError") : null
|
|
2561
2647
|
}
|
|
2562
2648
|
chatFirstActiveAppId={chatFirstActiveAppId}
|
|
2563
2649
|
chatFirstActivePrimaryTab={chatFirstActivePrimaryTab}
|
|
@@ -2571,7 +2657,10 @@ function DispatchLayout({
|
|
|
2571
2657
|
setSidebarCollapsed(true);
|
|
2572
2658
|
openChatFirstApp(app);
|
|
2573
2659
|
}}
|
|
2574
|
-
onChatFirstAppsRetry={() =>
|
|
2660
|
+
onChatFirstAppsRetry={() => {
|
|
2661
|
+
void chatFirstAppsQuery.refetch();
|
|
2662
|
+
void chatFirstConnectedAppsQuery.refetch();
|
|
2663
|
+
}}
|
|
2575
2664
|
collapsible
|
|
2576
2665
|
onCollapsedChange={setSidebarCollapsed}
|
|
2577
2666
|
/>
|
|
@@ -2600,11 +2689,9 @@ function DispatchLayout({
|
|
|
2600
2689
|
chatFirstAppLayout={chatFirstAppLayout}
|
|
2601
2690
|
onChatFirstAppLayoutChange={persistChatFirstAppLayout}
|
|
2602
2691
|
chatFirstApps={chatFirstAppItems}
|
|
2603
|
-
chatFirstAppsLoading={
|
|
2692
|
+
chatFirstAppsLoading={chatFirstAppsLoading}
|
|
2604
2693
|
chatFirstAppsError={
|
|
2605
|
-
|
|
2606
|
-
? chatFirstCopy("appsLoadError")
|
|
2607
|
-
: null
|
|
2694
|
+
chatFirstAppsError ? chatFirstCopy("appsLoadError") : null
|
|
2608
2695
|
}
|
|
2609
2696
|
chatFirstActiveAppId={chatFirstActiveAppId}
|
|
2610
2697
|
chatFirstActivePrimaryTab={chatFirstActivePrimaryTab}
|
|
@@ -2614,9 +2701,10 @@ function DispatchLayout({
|
|
|
2614
2701
|
setChatFirstSurfacePanelOpen(false);
|
|
2615
2702
|
}}
|
|
2616
2703
|
onChatFirstAppOpen={openChatFirstApp}
|
|
2617
|
-
onChatFirstAppsRetry={() =>
|
|
2618
|
-
void chatFirstAppsQuery.refetch()
|
|
2619
|
-
|
|
2704
|
+
onChatFirstAppsRetry={() => {
|
|
2705
|
+
void chatFirstAppsQuery.refetch();
|
|
2706
|
+
void chatFirstConnectedAppsQuery.refetch();
|
|
2707
|
+
}}
|
|
2620
2708
|
onNavigate={() => setMobileOpen(false)}
|
|
2621
2709
|
/>
|
|
2622
2710
|
</div>
|
|
@@ -7,6 +7,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
7
7
|
const clientState = vi.hoisted(() => ({
|
|
8
8
|
actions: [] as string[],
|
|
9
9
|
}));
|
|
10
|
+
const navigation = vi.hoisted(() => ({
|
|
11
|
+
navigate: vi.fn(() => true),
|
|
12
|
+
shouldOpenInTopWindow: vi.fn(() => false),
|
|
13
|
+
}));
|
|
10
14
|
|
|
11
15
|
vi.mock("@agent-native/core/client/hooks", () => ({
|
|
12
16
|
useActionQuery: (action: string) => {
|
|
@@ -25,12 +29,18 @@ vi.mock("@agent-native/core/client/hooks", () => ({
|
|
|
25
29
|
]
|
|
26
30
|
: [
|
|
27
31
|
{
|
|
28
|
-
id: "
|
|
29
|
-
name: "
|
|
30
|
-
url: "https://
|
|
31
|
-
homeUrl: "https://
|
|
32
|
+
id: "clips",
|
|
33
|
+
name: "Clips",
|
|
34
|
+
url: "https://clips.agent-native.com",
|
|
35
|
+
homeUrl: "https://clips.agent-native.com",
|
|
32
36
|
source: "builtin",
|
|
33
37
|
},
|
|
38
|
+
{
|
|
39
|
+
id: "remote-tool",
|
|
40
|
+
name: "Remote tool",
|
|
41
|
+
url: "https://remote.example.test",
|
|
42
|
+
source: "custom",
|
|
43
|
+
},
|
|
34
44
|
],
|
|
35
45
|
isError: false,
|
|
36
46
|
};
|
|
@@ -42,6 +52,12 @@ vi.mock("@agent-native/core/client/i18n", () => ({
|
|
|
42
52
|
values?.defaultValue ?? "Workspace apps",
|
|
43
53
|
}));
|
|
44
54
|
|
|
55
|
+
vi.mock("../../lib/workspace-apps", async (importOriginal) => ({
|
|
56
|
+
...(await importOriginal<typeof import("../../lib/workspace-apps")>()),
|
|
57
|
+
navigateToWorkspaceApp: navigation.navigate,
|
|
58
|
+
shouldOpenWorkspaceAppInTopWindow: navigation.shouldOpenInTopWindow,
|
|
59
|
+
}));
|
|
60
|
+
|
|
45
61
|
import { WorkspaceAppsRail } from "./workspace-apps-rail";
|
|
46
62
|
|
|
47
63
|
describe("WorkspaceAppsRail", () => {
|
|
@@ -51,6 +67,8 @@ describe("WorkspaceAppsRail", () => {
|
|
|
51
67
|
beforeEach(() => {
|
|
52
68
|
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
53
69
|
clientState.actions = [];
|
|
70
|
+
navigation.navigate.mockClear();
|
|
71
|
+
navigation.shouldOpenInTopWindow.mockReturnValue(false);
|
|
54
72
|
container = document.createElement("div");
|
|
55
73
|
document.body.appendChild(container);
|
|
56
74
|
root = createRoot(container);
|
|
@@ -62,7 +80,7 @@ describe("WorkspaceAppsRail", () => {
|
|
|
62
80
|
vi.unstubAllGlobals();
|
|
63
81
|
});
|
|
64
82
|
|
|
65
|
-
it("lists mounted
|
|
83
|
+
it("lists mounted apps and configured first-party apps, not custom agents", async () => {
|
|
66
84
|
await act(async () => {
|
|
67
85
|
root.render(
|
|
68
86
|
<MemoryRouter initialEntries={["/overview"]}>
|
|
@@ -72,8 +90,52 @@ describe("WorkspaceAppsRail", () => {
|
|
|
72
90
|
});
|
|
73
91
|
|
|
74
92
|
expect(container.textContent).toContain("Reports");
|
|
75
|
-
expect(container.textContent).
|
|
93
|
+
expect(container.textContent).toContain("Clips");
|
|
94
|
+
expect(container.textContent).not.toContain("Remote tool");
|
|
76
95
|
expect(container.querySelector('a[href="/apps/reports"]')).not.toBeNull();
|
|
77
|
-
expect(
|
|
96
|
+
expect(
|
|
97
|
+
[...container.querySelectorAll("a")].map((link) =>
|
|
98
|
+
link.getAttribute("href"),
|
|
99
|
+
),
|
|
100
|
+
).toEqual(["https://clips.agent-native.com/home", "/apps/reports"]);
|
|
101
|
+
expect(clientState.actions).toEqual([
|
|
102
|
+
"list-workspace-apps",
|
|
103
|
+
"list-connected-agents",
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("routes built-in apps through the host window on unmodified clicks", async () => {
|
|
108
|
+
navigation.shouldOpenInTopWindow.mockReturnValue(true);
|
|
109
|
+
await act(async () => {
|
|
110
|
+
root.render(
|
|
111
|
+
<MemoryRouter initialEntries={["/overview"]}>
|
|
112
|
+
<WorkspaceAppsRail />
|
|
113
|
+
</MemoryRouter>,
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const link = container.querySelector<HTMLAnchorElement>(
|
|
118
|
+
'a[href^="https://clips.agent-native.com"]',
|
|
119
|
+
);
|
|
120
|
+
expect(link?.getAttribute("href")).toBe(
|
|
121
|
+
"https://clips.agent-native.com/home",
|
|
122
|
+
);
|
|
123
|
+
const click = new MouseEvent("click", { bubbles: true, cancelable: true });
|
|
124
|
+
link?.dispatchEvent(click);
|
|
125
|
+
|
|
126
|
+
expect(navigation.navigate).toHaveBeenCalledWith(
|
|
127
|
+
"https://clips.agent-native.com/home",
|
|
128
|
+
);
|
|
129
|
+
expect(click.defaultPrevented).toBe(true);
|
|
130
|
+
|
|
131
|
+
const modifiedClick = new MouseEvent("click", {
|
|
132
|
+
bubbles: true,
|
|
133
|
+
cancelable: true,
|
|
134
|
+
ctrlKey: true,
|
|
135
|
+
});
|
|
136
|
+
link?.dispatchEvent(modifiedClick);
|
|
137
|
+
|
|
138
|
+
expect(navigation.navigate).toHaveBeenCalledTimes(1);
|
|
139
|
+
expect(modifiedClick.defaultPrevented).toBe(false);
|
|
78
140
|
});
|
|
79
141
|
});
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
2
2
|
import { useT } from "@agent-native/core/client/i18n";
|
|
3
3
|
import { IconApps } from "@tabler/icons-react";
|
|
4
|
+
import type { MouseEvent } from "react";
|
|
4
5
|
import { Link, useLocation } from "react-router";
|
|
5
6
|
|
|
7
|
+
import {
|
|
8
|
+
filterBuiltInApps,
|
|
9
|
+
type ConnectedAppSummary,
|
|
10
|
+
} from "../../lib/other-apps";
|
|
6
11
|
import { cn } from "../../lib/utils";
|
|
7
12
|
import {
|
|
8
13
|
isWorkspaceAppVisibleInDefaultLaunchers,
|
|
14
|
+
navigateToWorkspaceApp,
|
|
15
|
+
shouldOpenWorkspaceAppInTopWindow,
|
|
9
16
|
workspaceAppIdFromRoute,
|
|
10
17
|
workspaceAppRoute,
|
|
11
18
|
workspaceAppHref,
|
|
@@ -22,7 +29,7 @@ function appLabel(app: WorkspaceAppSummary): string {
|
|
|
22
29
|
return app.name.trim() || app.id;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
|
-
type RailApp = WorkspaceAppSummary;
|
|
32
|
+
type RailApp = WorkspaceAppSummary & { external?: boolean };
|
|
26
33
|
|
|
27
34
|
export function WorkspaceAppsRail({
|
|
28
35
|
collapsed = false,
|
|
@@ -39,17 +46,34 @@ export function WorkspaceAppsRail({
|
|
|
39
46
|
includeAgentCards: false,
|
|
40
47
|
},
|
|
41
48
|
);
|
|
49
|
+
const connectedAppsQuery = useActionQuery<ConnectedAppSummary[]>(
|
|
50
|
+
"list-connected-agents",
|
|
51
|
+
{},
|
|
52
|
+
);
|
|
42
53
|
if (appsQuery.isError || !appsQuery.data) return null;
|
|
43
54
|
|
|
44
|
-
const
|
|
45
|
-
|
|
55
|
+
const workspaceApps = appsQuery.data;
|
|
56
|
+
const apps: RailApp[] = [
|
|
57
|
+
...workspaceApps.filter(
|
|
46
58
|
(app) =>
|
|
47
59
|
isWorkspaceAppVisibleInDefaultLaunchers(app) &&
|
|
48
60
|
!app.archived &&
|
|
49
61
|
app.status !== "pending" &&
|
|
50
62
|
!!workspaceAppHref(app),
|
|
51
|
-
)
|
|
52
|
-
.
|
|
63
|
+
),
|
|
64
|
+
...filterBuiltInApps(connectedAppsQuery.data ?? [], workspaceApps).map(
|
|
65
|
+
(app) => ({
|
|
66
|
+
id: app.id,
|
|
67
|
+
name: app.name,
|
|
68
|
+
description: app.description,
|
|
69
|
+
path: "",
|
|
70
|
+
url: app.homeUrl?.trim() || app.url,
|
|
71
|
+
status: "ready" as const,
|
|
72
|
+
source: "builtin" as const,
|
|
73
|
+
external: true,
|
|
74
|
+
}),
|
|
75
|
+
),
|
|
76
|
+
].sort((a, b) => appLabel(a).localeCompare(appLabel(b)));
|
|
53
77
|
|
|
54
78
|
if (apps.length === 0) return null;
|
|
55
79
|
|
|
@@ -62,7 +86,21 @@ export function WorkspaceAppsRail({
|
|
|
62
86
|
const linkProps = {
|
|
63
87
|
"aria-current": active ? ("page" as const) : undefined,
|
|
64
88
|
"aria-label": collapsed ? label : undefined,
|
|
65
|
-
onClick:
|
|
89
|
+
onClick: (event: MouseEvent<HTMLAnchorElement>) => {
|
|
90
|
+
onNavigate?.();
|
|
91
|
+
if (
|
|
92
|
+
!app.external ||
|
|
93
|
+
event.button !== 0 ||
|
|
94
|
+
event.metaKey ||
|
|
95
|
+
event.ctrlKey ||
|
|
96
|
+
event.shiftKey ||
|
|
97
|
+
event.altKey ||
|
|
98
|
+
!shouldOpenWorkspaceAppInTopWindow()
|
|
99
|
+
) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (navigateToWorkspaceApp(href)) event.preventDefault();
|
|
103
|
+
},
|
|
66
104
|
className: cn(
|
|
67
105
|
"flex h-9 items-center rounded-md text-sm transition-colors",
|
|
68
106
|
collapsed ? "w-9 justify-center" : "w-full gap-2 px-2 text-start",
|
|
@@ -82,7 +120,11 @@ export function WorkspaceAppsRail({
|
|
|
82
120
|
{!collapsed ? <span className="truncate">{label}</span> : null}
|
|
83
121
|
</>
|
|
84
122
|
);
|
|
85
|
-
const link = (
|
|
123
|
+
const link = app.external ? (
|
|
124
|
+
<a href={href} {...linkProps}>
|
|
125
|
+
{linkContent}
|
|
126
|
+
</a>
|
|
127
|
+
) : (
|
|
86
128
|
<Link to={workspaceAppRoute(app.id)} {...linkProps}>
|
|
87
129
|
{linkContent}
|
|
88
130
|
</Link>
|