@agent-native/dispatch 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/create-workspace-resource.d.ts +13 -13
- package/dist/actions/delete-workspace-resource.d.ts +13 -13
- package/dist/actions/grant-vault-secrets-to-app.js +1 -1
- package/dist/actions/grant-vault-secrets-to-app.js.map +1 -1
- package/dist/actions/list-vault-audit.js +1 -1
- package/dist/actions/list-vault-audit.js.map +1 -1
- package/dist/actions/list-vault-secret-options.js +2 -9
- package/dist/actions/list-vault-secret-options.js.map +1 -1
- package/dist/actions/list-vault-secrets.js +1 -1
- package/dist/actions/list-vault-secrets.js.map +1 -1
- package/dist/actions/provider-api-register.d.ts +7 -7
- package/dist/actions/set-app-creation-settings.js +1 -1
- package/dist/actions/set-app-creation-settings.js.map +1 -1
- package/dist/actions/set-vault-access-settings.js +1 -1
- package/dist/actions/set-vault-access-settings.js.map +1 -1
- package/dist/actions/sync-vault-to-app.js +1 -1
- package/dist/actions/sync-vault-to-app.js.map +1 -1
- package/dist/actions/view-screen.js +4 -3
- package/dist/actions/view-screen.js.map +1 -1
- package/dist/components/admin-navigation.d.ts +15 -0
- package/dist/components/admin-navigation.d.ts.map +1 -0
- package/dist/components/admin-navigation.js +174 -0
- package/dist/components/admin-navigation.js.map +1 -0
- package/dist/components/automation-run-history-dialog.js +1 -1
- package/dist/components/automation-run-history-dialog.js.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/layout/Header.d.ts.map +1 -1
- package/dist/components/layout/Header.js +9 -0
- package/dist/components/layout/Header.js.map +1 -1
- package/dist/components/layout/Layout.d.ts +4 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +14 -117
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/layout/workspace-apps-rail.d.ts +5 -0
- package/dist/components/layout/workspace-apps-rail.d.ts.map +1 -0
- package/dist/components/layout/workspace-apps-rail.js +76 -0
- package/dist/components/layout/workspace-apps-rail.js.map +1 -0
- package/dist/hooks/use-navigation-state.js +39 -16
- package/dist/hooks/use-navigation-state.js.map +1 -1
- package/dist/lib/automation-display.js +1 -1
- package/dist/lib/automation-display.js.map +1 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +22 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/pages/admin._index.d.ts +5 -0
- package/dist/routes/pages/admin._index.d.ts.map +1 -0
- package/dist/routes/pages/admin._index.js +19 -0
- package/dist/routes/pages/admin._index.js.map +1 -0
- package/dist/routes/pages/admin.d.ts +5 -0
- package/dist/routes/pages/admin.d.ts.map +1 -0
- package/dist/routes/pages/admin.js +10 -0
- package/dist/routes/pages/admin.js.map +1 -0
- package/dist/routes/pages/approval.js +2 -2
- package/dist/routes/pages/approval.js.map +1 -1
- package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
- package/dist/routes/pages/apps.$appId.js +3 -2
- package/dist/routes/pages/apps.$appId.js.map +1 -1
- package/dist/routes/pages/integrations.d.ts.map +1 -1
- package/dist/routes/pages/integrations.js +2 -1
- package/dist/routes/pages/integrations.js.map +1 -1
- package/dist/routes/pages/operations.js +3 -3
- package/dist/routes/pages/operations.js.map +1 -1
- package/dist/routes/pages/settings.d.ts.map +1 -1
- package/dist/routes/pages/settings.js +1 -1
- package/dist/routes/pages/settings.js.map +1 -1
- package/dist/routes/pages/team.js +1 -1
- package/dist/routes/pages/team.js.map +1 -1
- package/dist/routes/pages/transactional-email.$appId.$id.d.ts.map +1 -1
- package/dist/routes/pages/transactional-email.$appId.$id.js +5 -2
- package/dist/routes/pages/transactional-email.$appId.$id.js.map +1 -1
- package/dist/routes/pages/transactional-email.d.ts.map +1 -1
- package/dist/routes/pages/transactional-email.js +6 -2
- package/dist/routes/pages/transactional-email.js.map +1 -1
- package/dist/routes/pages/vault.d.ts.map +1 -1
- package/dist/routes/pages/vault.js +20 -16
- package/dist/routes/pages/vault.js.map +1 -1
- package/dist/server/lib/app-creation-store.js +2 -2
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/thread-debug-store.d.ts.map +1 -1
- package/dist/server/lib/thread-debug-store.js +2 -1
- package/dist/server/lib/thread-debug-store.js.map +1 -1
- package/dist/server/lib/vault-store.d.ts +15 -0
- package/dist/server/lib/vault-store.d.ts.map +1 -1
- package/dist/server/lib/vault-store.js +110 -17
- package/dist/server/lib/vault-store.js.map +1 -1
- package/package.json +3 -3
- package/src/actions/grant-vault-secrets-to-app.ts +1 -1
- package/src/actions/list-vault-audit.ts +1 -1
- package/src/actions/list-vault-secret-options.ts +2 -9
- package/src/actions/list-vault-secrets.ts +1 -1
- package/src/actions/set-app-creation-settings.ts +1 -1
- package/src/actions/set-vault-access-settings.ts +1 -1
- package/src/actions/sync-vault-to-app.ts +1 -1
- package/src/actions/view-screen.spec.ts +1 -0
- package/src/actions/view-screen.ts +5 -3
- package/src/components/admin-navigation.tsx +278 -0
- package/src/components/automation-run-history-dialog.tsx +1 -1
- package/src/components/index.ts +1 -0
- package/src/components/layout/Header.tsx +8 -0
- package/src/components/layout/Layout.spec.tsx +85 -6
- package/src/components/layout/Layout.tsx +62 -206
- package/src/components/layout/workspace-apps-rail.tsx +154 -0
- package/src/hooks/use-navigation-state.spec.ts +11 -0
- package/src/hooks/use-navigation-state.ts +37 -16
- package/src/lib/automation-display.spec.ts +1 -1
- package/src/lib/automation-display.ts +1 -1
- package/src/routes/index.spec.ts +8 -0
- package/src/routes/index.ts +25 -0
- package/src/routes/pages/admin._index.tsx +39 -0
- package/src/routes/pages/admin.tsx +15 -0
- package/src/routes/pages/approval.tsx +2 -2
- package/src/routes/pages/apps.$appId.tsx +7 -2
- package/src/routes/pages/integrations.tsx +3 -2
- package/src/routes/pages/operations.tsx +3 -3
- package/src/routes/pages/settings.tsx +7 -3
- package/src/routes/pages/team.tsx +1 -1
- package/src/routes/pages/transactional-email.$appId.$id.tsx +9 -2
- package/src/routes/pages/transactional-email.tsx +6 -2
- package/src/routes/pages/vault.tsx +152 -89
- package/src/server/lib/app-creation-store.ts +2 -2
- package/src/server/lib/approval-fencing.spec.ts +69 -0
- package/src/server/lib/thread-debug-store.spec.ts +7 -3
- package/src/server/lib/thread-debug-store.ts +2 -1
- package/src/server/lib/vault-store.spec.ts +59 -0
- package/src/server/lib/vault-store.ts +123 -17
|
@@ -18,38 +18,26 @@ import {
|
|
|
18
18
|
type ChatHistoryItem,
|
|
19
19
|
} from "@agent-native/toolkit/chat-history";
|
|
20
20
|
import {
|
|
21
|
-
IconActivity,
|
|
22
|
-
IconArrowUpRight,
|
|
23
21
|
IconApps,
|
|
24
|
-
IconBrain,
|
|
25
22
|
IconBrandSlack,
|
|
26
|
-
IconChartBar,
|
|
27
23
|
IconBrandTelegram,
|
|
28
|
-
IconKey,
|
|
29
|
-
IconChevronDown,
|
|
30
|
-
IconLayersSubtract,
|
|
31
|
-
IconMail,
|
|
32
24
|
IconMessageQuestion,
|
|
33
|
-
IconMessages,
|
|
34
|
-
IconPlugConnected,
|
|
35
25
|
IconBroadcast,
|
|
36
|
-
IconFingerprint,
|
|
37
|
-
IconHistory,
|
|
38
26
|
IconLayoutSidebarLeftCollapse,
|
|
39
27
|
IconLayoutSidebarLeftExpand,
|
|
40
|
-
IconPuzzle,
|
|
41
28
|
IconSettings,
|
|
42
|
-
|
|
43
|
-
IconShieldCheck,
|
|
29
|
+
IconShield,
|
|
44
30
|
IconSearch,
|
|
45
31
|
IconWorld,
|
|
46
32
|
IconDeviceDesktop,
|
|
47
33
|
} from "@tabler/icons-react";
|
|
48
34
|
import {
|
|
35
|
+
createContext,
|
|
49
36
|
useEffect,
|
|
50
37
|
useMemo,
|
|
51
38
|
useRef,
|
|
52
39
|
useState,
|
|
40
|
+
useContext,
|
|
53
41
|
type ComponentType,
|
|
54
42
|
type ReactNode,
|
|
55
43
|
} from "react";
|
|
@@ -67,6 +55,7 @@ import { Skeleton } from "../ui/skeleton";
|
|
|
67
55
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
|
68
56
|
import { Header } from "./Header";
|
|
69
57
|
import { HeaderActionsProvider } from "./HeaderActions";
|
|
58
|
+
import { WorkspaceAppsRail } from "./workspace-apps-rail";
|
|
70
59
|
|
|
71
60
|
export type DispatchNavSection = "primary" | "operations";
|
|
72
61
|
|
|
@@ -82,10 +71,12 @@ export interface DispatchNavItem {
|
|
|
82
71
|
to: string;
|
|
83
72
|
label: string;
|
|
84
73
|
icon?: DispatchNavIcon;
|
|
85
|
-
/** Defaults to "operations", which
|
|
74
|
+
/** Defaults to "operations", which renders under the Admin control plane. */
|
|
86
75
|
section?: DispatchNavSection;
|
|
87
76
|
/** Override active matching for nested or multi-route tools. */
|
|
88
77
|
match?: (pathname: string) => boolean;
|
|
78
|
+
/** Canonical path inside the Admin shell for management tabs. */
|
|
79
|
+
adminTo?: string;
|
|
89
80
|
}
|
|
90
81
|
|
|
91
82
|
export interface DispatchExtensionConfig {
|
|
@@ -119,80 +110,13 @@ const PRIMARY_NAV_ITEMS = [
|
|
|
119
110
|
},
|
|
120
111
|
] as const satisfies readonly DispatchNavItem[];
|
|
121
112
|
|
|
122
|
-
const
|
|
123
|
-
{
|
|
124
|
-
id: "operations",
|
|
125
|
-
to: "/operations",
|
|
126
|
-
label: "Operations",
|
|
127
|
-
icon: IconActivity,
|
|
128
|
-
section: "operations",
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
id: "metrics",
|
|
132
|
-
to: "/metrics",
|
|
133
|
-
label: "Metrics",
|
|
134
|
-
icon: IconChartBar,
|
|
135
|
-
section: "operations",
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
id: "automations",
|
|
139
|
-
to: "/automations",
|
|
140
|
-
label: "Automations",
|
|
141
|
-
icon: IconSettingsAutomation,
|
|
142
|
-
section: "operations",
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
id: "approvals",
|
|
146
|
-
to: "/approvals",
|
|
147
|
-
label: "Approvals",
|
|
148
|
-
icon: IconShieldCheck,
|
|
149
|
-
section: "operations",
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
id: "destinations",
|
|
153
|
-
to: "/destinations",
|
|
154
|
-
label: "Destinations",
|
|
155
|
-
icon: IconArrowUpRight,
|
|
156
|
-
section: "operations",
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
id: "integrations",
|
|
160
|
-
to: "/integrations",
|
|
161
|
-
label: "Integrations",
|
|
162
|
-
icon: IconPuzzle,
|
|
163
|
-
section: "operations",
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
id: "transactional-email",
|
|
167
|
-
to: "/transactional-email",
|
|
168
|
-
label: "Transactional email",
|
|
169
|
-
icon: IconMail,
|
|
170
|
-
section: "operations",
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
id: "vault",
|
|
174
|
-
to: "/vault",
|
|
175
|
-
label: "Vault",
|
|
176
|
-
icon: IconKey,
|
|
177
|
-
section: "operations",
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
id: "agents",
|
|
181
|
-
to: "/agents",
|
|
182
|
-
label: "Agents",
|
|
183
|
-
icon: IconPlugConnected,
|
|
184
|
-
section: "operations",
|
|
185
|
-
},
|
|
113
|
+
const BOTTOM_NAV_ITEMS = [
|
|
186
114
|
{
|
|
187
|
-
id: "
|
|
188
|
-
to: "/
|
|
189
|
-
label: "
|
|
190
|
-
icon:
|
|
191
|
-
section: "operations",
|
|
115
|
+
id: "admin",
|
|
116
|
+
to: "/admin",
|
|
117
|
+
label: "Admin",
|
|
118
|
+
icon: IconShield,
|
|
192
119
|
},
|
|
193
|
-
] as const satisfies readonly DispatchNavItem[];
|
|
194
|
-
|
|
195
|
-
const BOTTOM_NAV_ITEMS = [
|
|
196
120
|
{
|
|
197
121
|
id: "settings",
|
|
198
122
|
to: "/settings",
|
|
@@ -201,44 +125,6 @@ const BOTTOM_NAV_ITEMS = [
|
|
|
201
125
|
},
|
|
202
126
|
] as const satisfies readonly DispatchNavItem[];
|
|
203
127
|
|
|
204
|
-
const ADVANCED_NAV_ITEMS = [
|
|
205
|
-
{
|
|
206
|
-
id: "messaging",
|
|
207
|
-
to: "/messaging",
|
|
208
|
-
label: "Messaging",
|
|
209
|
-
icon: IconBrandTelegram,
|
|
210
|
-
section: "operations",
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
id: "identities",
|
|
214
|
-
to: "/identities",
|
|
215
|
-
label: "Identities",
|
|
216
|
-
icon: IconFingerprint,
|
|
217
|
-
section: "operations",
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
id: "audit",
|
|
221
|
-
to: "/audit",
|
|
222
|
-
label: "Audit",
|
|
223
|
-
icon: IconHistory,
|
|
224
|
-
section: "operations",
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
id: "dreams",
|
|
228
|
-
to: "/dreams",
|
|
229
|
-
label: "Dreams",
|
|
230
|
-
icon: IconBrain,
|
|
231
|
-
section: "operations",
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
id: "thread-debug",
|
|
235
|
-
to: "/thread-debug",
|
|
236
|
-
label: "Thread Debug",
|
|
237
|
-
icon: IconMessages,
|
|
238
|
-
section: "operations",
|
|
239
|
-
},
|
|
240
|
-
] as const satisfies readonly DispatchNavItem[];
|
|
241
|
-
|
|
242
128
|
const EMPTY_NAV_ITEMS: readonly DispatchNavItem[] = [];
|
|
243
129
|
const DISPATCH_SIDEBAR_LABEL = "Dispatch";
|
|
244
130
|
|
|
@@ -246,6 +132,14 @@ const CHROMELESS_PATHS = ["/approval", "/browser-chat", "/browser-connect"];
|
|
|
246
132
|
const SIDEBAR_COLLAPSE_KEY = "dispatch.sidebar.collapsed";
|
|
247
133
|
const CHAT_HISTORY_SOURCE_KEY = "dispatch.chat-history.source";
|
|
248
134
|
|
|
135
|
+
const DispatchExtensionsContext = createContext<
|
|
136
|
+
DispatchExtensionConfig | undefined
|
|
137
|
+
>(undefined);
|
|
138
|
+
|
|
139
|
+
export function useDispatchExtensions(): DispatchExtensionConfig | undefined {
|
|
140
|
+
return useContext(DispatchExtensionsContext);
|
|
141
|
+
}
|
|
142
|
+
|
|
249
143
|
// Routes whose page renders its own toolbar.
|
|
250
144
|
// Layout still mounts the sidebar + AgentSidebar, but skips its own Header so
|
|
251
145
|
// there's no double-header.
|
|
@@ -594,14 +488,7 @@ export function NavContent({
|
|
|
594
488
|
...PRIMARY_NAV_ITEMS,
|
|
595
489
|
...navItemsForSection(extensionNavItems, "primary"),
|
|
596
490
|
];
|
|
597
|
-
const operationsNavItems = [
|
|
598
|
-
...OPERATIONS_NAV_ITEMS,
|
|
599
|
-
...navItemsForSection(extensionNavItems, "operations"),
|
|
600
|
-
];
|
|
601
491
|
const localPathname = localDispatchPath(location.pathname);
|
|
602
|
-
const advancedOpen = ADVANCED_NAV_ITEMS.some((item) =>
|
|
603
|
-
navItemMatchesPath(item, localPathname),
|
|
604
|
-
);
|
|
605
492
|
const navLabel = (item: DispatchNavItem) => {
|
|
606
493
|
const key =
|
|
607
494
|
item.id === "thread-debug"
|
|
@@ -807,40 +694,7 @@ export function NavContent({
|
|
|
807
694
|
{primaryNavItems.map(renderNavItem)}
|
|
808
695
|
</ul>
|
|
809
696
|
|
|
810
|
-
{collapsed
|
|
811
|
-
<>
|
|
812
|
-
<ul className="mt-5 flex flex-col items-center gap-1">
|
|
813
|
-
{operationsNavItems.map(renderNavItem)}
|
|
814
|
-
</ul>
|
|
815
|
-
<ul className="mt-3 flex flex-col items-center gap-1">
|
|
816
|
-
{ADVANCED_NAV_ITEMS.map(renderNavItem)}
|
|
817
|
-
</ul>
|
|
818
|
-
</>
|
|
819
|
-
) : (
|
|
820
|
-
<div className="mt-5">
|
|
821
|
-
<p className="px-2 pb-1 text-[11px] font-medium uppercase tracking-wide text-sidebar-foreground/45">
|
|
822
|
-
{t("dispatch.nav.operate", { defaultValue: "Operate" })}
|
|
823
|
-
</p>
|
|
824
|
-
<ul className="space-y-0.5">
|
|
825
|
-
{operationsNavItems.map(renderNavItem)}
|
|
826
|
-
</ul>
|
|
827
|
-
|
|
828
|
-
<details className="group mt-3" open={advancedOpen}>
|
|
829
|
-
<summary className="flex h-8 cursor-pointer list-none items-center justify-between rounded-md px-2 text-xs font-medium text-sidebar-foreground/60 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground [&::-webkit-details-marker]:hidden">
|
|
830
|
-
<span>
|
|
831
|
-
{t("dispatch.nav.advanced", { defaultValue: "Advanced" })}
|
|
832
|
-
</span>
|
|
833
|
-
<IconChevronDown
|
|
834
|
-
size={14}
|
|
835
|
-
className="transition-transform group-open:rotate-180"
|
|
836
|
-
/>
|
|
837
|
-
</summary>
|
|
838
|
-
<ul className="mt-1 space-y-0.5">
|
|
839
|
-
{ADVANCED_NAV_ITEMS.map(renderNavItem)}
|
|
840
|
-
</ul>
|
|
841
|
-
</details>
|
|
842
|
-
</div>
|
|
843
|
-
)}
|
|
697
|
+
<WorkspaceAppsRail collapsed={collapsed} onNavigate={onNavigate} />
|
|
844
698
|
</nav>
|
|
845
699
|
|
|
846
700
|
<div className="mt-auto shrink-0">
|
|
@@ -980,46 +834,48 @@ export function Layout({
|
|
|
980
834
|
);
|
|
981
835
|
|
|
982
836
|
return (
|
|
983
|
-
<
|
|
984
|
-
<
|
|
985
|
-
<
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
<NavContent
|
|
993
|
-
extensions={extensions}
|
|
994
|
-
collapsed={sidebarCollapsed}
|
|
995
|
-
collapsible
|
|
996
|
-
onCollapsedChange={setSidebarCollapsed}
|
|
997
|
-
/>
|
|
998
|
-
</aside>
|
|
999
|
-
|
|
1000
|
-
<Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
|
|
1001
|
-
<SheetContent
|
|
1002
|
-
side="left"
|
|
1003
|
-
className="w-72 p-0 bg-sidebar text-sidebar-foreground [&>button]:hidden"
|
|
837
|
+
<DispatchExtensionsContext.Provider value={extensions}>
|
|
838
|
+
<HeaderActionsProvider>
|
|
839
|
+
<div className="agent-layout-shell flex h-screen w-full overflow-hidden bg-background">
|
|
840
|
+
<aside
|
|
841
|
+
data-collapsed={sidebarCollapsed ? "true" : "false"}
|
|
842
|
+
className={cn(
|
|
843
|
+
"agent-layout-left-drawer hidden shrink-0 flex-col border-e bg-sidebar text-sidebar-foreground transition-[width] duration-200 ease-out lg:flex",
|
|
844
|
+
sidebarCollapsed ? "w-14" : "w-56",
|
|
845
|
+
)}
|
|
1004
846
|
>
|
|
1005
|
-
<
|
|
1006
|
-
{
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
{
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
847
|
+
<NavContent
|
|
848
|
+
extensions={extensions}
|
|
849
|
+
collapsed={sidebarCollapsed}
|
|
850
|
+
collapsible
|
|
851
|
+
onCollapsedChange={setSidebarCollapsed}
|
|
852
|
+
/>
|
|
853
|
+
</aside>
|
|
854
|
+
|
|
855
|
+
<Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
|
|
856
|
+
<SheetContent
|
|
857
|
+
side="left"
|
|
858
|
+
className="w-72 p-0 bg-sidebar text-sidebar-foreground [&>button]:hidden"
|
|
859
|
+
>
|
|
860
|
+
<SheetTitle className="sr-only">
|
|
861
|
+
{t("dispatch.nav.navigation")}
|
|
862
|
+
</SheetTitle>
|
|
863
|
+
<SheetDescription className="sr-only">
|
|
864
|
+
{t("dispatch.nav.navigationDescription")}
|
|
865
|
+
</SheetDescription>
|
|
866
|
+
<div className="flex h-full w-full flex-col">
|
|
867
|
+
<NavContent
|
|
868
|
+
extensions={extensions}
|
|
869
|
+
collapsed={false}
|
|
870
|
+
onNavigate={() => setMobileOpen(false)}
|
|
871
|
+
/>
|
|
872
|
+
</div>
|
|
873
|
+
</SheetContent>
|
|
874
|
+
</Sheet>
|
|
1020
875
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
876
|
+
{content}
|
|
877
|
+
</div>
|
|
878
|
+
</HeaderActionsProvider>
|
|
879
|
+
</DispatchExtensionsContext.Provider>
|
|
1024
880
|
);
|
|
1025
881
|
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
2
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
3
|
+
import { IconApps } from "@tabler/icons-react";
|
|
4
|
+
import { useLocation } from "react-router";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils";
|
|
7
|
+
import {
|
|
8
|
+
workspaceAppHref,
|
|
9
|
+
type WorkspaceAppSummary,
|
|
10
|
+
} from "../../lib/workspace-apps";
|
|
11
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
|
12
|
+
|
|
13
|
+
function pathFromValue(value: string | null | undefined): string | null {
|
|
14
|
+
const trimmed = value?.trim();
|
|
15
|
+
if (!trimmed) return null;
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const pathname = new URL(trimmed, "https://dispatch.local").pathname;
|
|
19
|
+
return pathname.replace(/\/+$/, "") || "/";
|
|
20
|
+
} catch {
|
|
21
|
+
// coercion-ok: malformed app URLs cannot match a workspace route.
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function appMatchesPath(app: WorkspaceAppSummary, pathname: string): boolean {
|
|
27
|
+
const candidatePaths = [app.path, app.url]
|
|
28
|
+
.map(pathFromValue)
|
|
29
|
+
.filter((path): path is string => !!path);
|
|
30
|
+
|
|
31
|
+
return candidatePaths.some(
|
|
32
|
+
(path) => pathname === path || pathname.startsWith(`${path}/`),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function appInitial(name: string): string {
|
|
37
|
+
return name.trim().charAt(0).toUpperCase() || "A";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function appLabel(app: WorkspaceAppSummary): string {
|
|
41
|
+
return app.name.trim() || app.id;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function WorkspaceAppsRail({
|
|
45
|
+
collapsed = false,
|
|
46
|
+
onNavigate,
|
|
47
|
+
}: {
|
|
48
|
+
collapsed?: boolean;
|
|
49
|
+
onNavigate?: () => void;
|
|
50
|
+
}) {
|
|
51
|
+
const t = useT();
|
|
52
|
+
const location = useLocation();
|
|
53
|
+
const appsQuery = useActionQuery<WorkspaceAppSummary[]>(
|
|
54
|
+
"list-workspace-apps",
|
|
55
|
+
{
|
|
56
|
+
includeAgentCards: false,
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
if (appsQuery.isError || !appsQuery.data) return null;
|
|
60
|
+
|
|
61
|
+
const apps = appsQuery.data
|
|
62
|
+
.filter(
|
|
63
|
+
(app) =>
|
|
64
|
+
!app.isDispatch &&
|
|
65
|
+
!app.archived &&
|
|
66
|
+
app.status !== "pending" &&
|
|
67
|
+
!!workspaceAppHref(app),
|
|
68
|
+
)
|
|
69
|
+
.sort((a, b) => appLabel(a).localeCompare(appLabel(b)));
|
|
70
|
+
|
|
71
|
+
if (apps.length === 0) return null;
|
|
72
|
+
|
|
73
|
+
const renderAppLink = (app: WorkspaceAppSummary) => {
|
|
74
|
+
const href = workspaceAppHref(app);
|
|
75
|
+
if (!href) return null;
|
|
76
|
+
|
|
77
|
+
const label = appLabel(app);
|
|
78
|
+
const active = appMatchesPath(app, location.pathname);
|
|
79
|
+
const link = (
|
|
80
|
+
<a
|
|
81
|
+
href={href}
|
|
82
|
+
aria-current={active ? "page" : undefined}
|
|
83
|
+
aria-label={collapsed ? label : undefined}
|
|
84
|
+
onClick={onNavigate}
|
|
85
|
+
className={cn(
|
|
86
|
+
"flex h-8 items-center rounded-md text-sm transition-colors",
|
|
87
|
+
collapsed ? "w-8 justify-center" : "w-full gap-2 px-2 text-start",
|
|
88
|
+
active
|
|
89
|
+
? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
|
|
90
|
+
: "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
91
|
+
)}
|
|
92
|
+
>
|
|
93
|
+
<span
|
|
94
|
+
className={cn(
|
|
95
|
+
"flex size-5 shrink-0 items-center justify-center rounded text-[10px] font-semibold",
|
|
96
|
+
active
|
|
97
|
+
? "bg-sidebar-primary text-sidebar-primary-foreground"
|
|
98
|
+
: "bg-sidebar-accent text-sidebar-foreground/70",
|
|
99
|
+
)}
|
|
100
|
+
>
|
|
101
|
+
{appInitial(label)}
|
|
102
|
+
</span>
|
|
103
|
+
{!collapsed ? <span className="truncate">{label}</span> : null}
|
|
104
|
+
</a>
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (!collapsed) return link;
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<Tooltip>
|
|
111
|
+
<TooltipTrigger asChild>{link}</TooltipTrigger>
|
|
112
|
+
<TooltipContent side="right">{label}</TooltipContent>
|
|
113
|
+
</Tooltip>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div
|
|
119
|
+
className={cn(
|
|
120
|
+
"mt-4 border-t border-sidebar-border pt-3",
|
|
121
|
+
collapsed ? "px-1" : "px-2",
|
|
122
|
+
)}
|
|
123
|
+
data-dispatch-apps-rail
|
|
124
|
+
>
|
|
125
|
+
{!collapsed ? (
|
|
126
|
+
<div className="mb-1 flex items-center gap-1.5 px-2 text-[11px] font-medium uppercase tracking-wide text-sidebar-foreground/45">
|
|
127
|
+
<IconApps size={13} />
|
|
128
|
+
<span>
|
|
129
|
+
{t("dispatch.pages.workspaceApps", {
|
|
130
|
+
defaultValue: "Workspace apps",
|
|
131
|
+
})}
|
|
132
|
+
</span>
|
|
133
|
+
</div>
|
|
134
|
+
) : (
|
|
135
|
+
<span className="sr-only">
|
|
136
|
+
{t("dispatch.pages.workspaceApps", {
|
|
137
|
+
defaultValue: "Workspace apps",
|
|
138
|
+
})}
|
|
139
|
+
</span>
|
|
140
|
+
)}
|
|
141
|
+
<ul
|
|
142
|
+
className={cn(
|
|
143
|
+
collapsed ? "flex flex-col items-center gap-1" : "space-y-0.5",
|
|
144
|
+
)}
|
|
145
|
+
>
|
|
146
|
+
{apps.map((app) => {
|
|
147
|
+
const href = workspaceAppHref(app);
|
|
148
|
+
if (!href) return null;
|
|
149
|
+
return <li key={app.id}>{renderAppLink(app)}</li>;
|
|
150
|
+
})}
|
|
151
|
+
</ul>
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
@@ -50,6 +50,17 @@ describe("buildDispatchNavigationState", () => {
|
|
|
50
50
|
});
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
+
it("recognizes Admin routes without losing the underlying view", () => {
|
|
54
|
+
expect(buildDispatchNavigationState("/admin/metrics")).toEqual({
|
|
55
|
+
view: "metrics",
|
|
56
|
+
path: "/admin/metrics",
|
|
57
|
+
});
|
|
58
|
+
expect(buildDispatchNavigationState("/admin")).toEqual({
|
|
59
|
+
view: "admin",
|
|
60
|
+
path: "/admin",
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
53
64
|
it("preserves thread debug filters and selection", () => {
|
|
54
65
|
expect(
|
|
55
66
|
buildDispatchNavigationState(
|
|
@@ -224,7 +224,8 @@ function resolveExtensionPath(
|
|
|
224
224
|
extensions?: DispatchExtensionConfig,
|
|
225
225
|
): string | undefined {
|
|
226
226
|
if (!view) return undefined;
|
|
227
|
-
|
|
227
|
+
const item = extensions?.navItems?.find((candidate) => candidate.id === view);
|
|
228
|
+
return item?.adminTo ?? item?.to;
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
function resolveView(
|
|
@@ -236,9 +237,15 @@ function resolveView(
|
|
|
236
237
|
if (pathname === "/extensions" || pathname.startsWith("/extensions/")) {
|
|
237
238
|
return "extensions";
|
|
238
239
|
}
|
|
240
|
+
if (pathname === "/admin") return "admin";
|
|
241
|
+
if (pathname.startsWith("/admin/")) {
|
|
242
|
+
const adminView = resolveView(pathname.slice("/admin".length), extensions);
|
|
243
|
+
return adminView === "overview" ? "admin" : adminView;
|
|
244
|
+
}
|
|
239
245
|
if (pathname.startsWith("/browser-chat")) return "browser-chat";
|
|
240
246
|
if (pathname.startsWith("/chat")) return "chat";
|
|
241
247
|
if (pathname.startsWith("/apps")) return "apps";
|
|
248
|
+
if (pathname.startsWith("/operations")) return "operations";
|
|
242
249
|
if (pathname.startsWith("/metrics")) return "metrics";
|
|
243
250
|
if (pathname.startsWith("/new-app")) return "new-app";
|
|
244
251
|
if (pathname.startsWith("/vault")) return "vault";
|
|
@@ -250,6 +257,9 @@ function resolveView(
|
|
|
250
257
|
if (pathname.startsWith("/identities")) return "identities";
|
|
251
258
|
if (pathname.startsWith("/approvals")) return "approvals";
|
|
252
259
|
if (pathname.startsWith("/automations")) return "automations";
|
|
260
|
+
if (pathname.startsWith("/transactional-email")) {
|
|
261
|
+
return "transactional-email";
|
|
262
|
+
}
|
|
253
263
|
if (pathname.startsWith("/audit")) return "audit";
|
|
254
264
|
if (pathname.startsWith("/dreams")) return "dreams";
|
|
255
265
|
if (pathname.startsWith("/thread-debug")) return "thread-debug";
|
|
@@ -263,6 +273,8 @@ function resolvePath(
|
|
|
263
273
|
command?: Pick<NavigationState, "extensionId" | "threadId">,
|
|
264
274
|
): string | undefined {
|
|
265
275
|
switch (view) {
|
|
276
|
+
case "admin":
|
|
277
|
+
return "/admin";
|
|
266
278
|
case "chat":
|
|
267
279
|
case "ask":
|
|
268
280
|
return command?.threadId && command.threadId.trim()
|
|
@@ -272,43 +284,52 @@ function resolvePath(
|
|
|
272
284
|
return "/overview";
|
|
273
285
|
case "apps":
|
|
274
286
|
return "/apps";
|
|
287
|
+
case "operations":
|
|
288
|
+
case "monitoring":
|
|
289
|
+
case "observability":
|
|
290
|
+
case "database":
|
|
291
|
+
return view === "database"
|
|
292
|
+
? "/admin/operations?view=database"
|
|
293
|
+
: "/admin/operations";
|
|
275
294
|
case "metrics":
|
|
276
295
|
case "usage":
|
|
277
|
-
return "/metrics";
|
|
296
|
+
return "/admin/metrics";
|
|
278
297
|
case "new-app":
|
|
279
298
|
case "create-app":
|
|
280
|
-
return "/new-app";
|
|
299
|
+
return "/admin/new-app";
|
|
281
300
|
case "vault":
|
|
282
301
|
case "secrets":
|
|
283
|
-
return "/vault";
|
|
302
|
+
return "/admin/vault";
|
|
284
303
|
case "integrations":
|
|
285
|
-
return "/integrations";
|
|
304
|
+
return "/admin/integrations";
|
|
286
305
|
case "workspace":
|
|
287
306
|
case "resources":
|
|
288
|
-
return "/workspace";
|
|
307
|
+
return "/admin/workspace";
|
|
289
308
|
case "agents":
|
|
290
|
-
return "/agents";
|
|
309
|
+
return "/admin/agents";
|
|
291
310
|
case "messaging":
|
|
292
|
-
return "/messaging";
|
|
311
|
+
return "/admin/messaging";
|
|
293
312
|
case "destinations":
|
|
294
313
|
case "routes":
|
|
295
|
-
return "/destinations";
|
|
314
|
+
return "/admin/destinations";
|
|
296
315
|
case "identities":
|
|
297
|
-
return "/identities";
|
|
316
|
+
return "/admin/identities";
|
|
298
317
|
case "approvals":
|
|
299
|
-
return "/approvals";
|
|
318
|
+
return "/admin/approvals";
|
|
300
319
|
case "automations":
|
|
301
320
|
case "jobs":
|
|
302
|
-
return "/automations";
|
|
321
|
+
return "/admin/automations";
|
|
322
|
+
case "transactional-email":
|
|
323
|
+
return "/admin/transactional-email";
|
|
303
324
|
case "audit":
|
|
304
|
-
return "/audit";
|
|
325
|
+
return "/admin/audit";
|
|
305
326
|
case "dreams":
|
|
306
|
-
return "/dreams";
|
|
327
|
+
return "/admin/dreams";
|
|
307
328
|
case "thread-debug":
|
|
308
329
|
case "threads":
|
|
309
|
-
return "/thread-debug";
|
|
330
|
+
return "/admin/thread-debug";
|
|
310
331
|
case "team":
|
|
311
|
-
return "/settings
|
|
332
|
+
return "/settings/organization";
|
|
312
333
|
case "extensions":
|
|
313
334
|
return command?.extensionId
|
|
314
335
|
? `/extensions/${encodeURIComponent(command.extensionId)}`
|
|
@@ -31,7 +31,7 @@ describe("automation-display", () => {
|
|
|
31
31
|
it("prepares a thread-debug search for an automation", () => {
|
|
32
32
|
expect(
|
|
33
33
|
automationTroubleshootPath({ name: "coach onboarding reminder" }),
|
|
34
|
-
).toBe("/thread-debug?query=coach+onboarding+reminder");
|
|
34
|
+
).toBe("/admin/thread-debug?query=coach+onboarding+reminder");
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
it("prefers event names, then schedule descriptions", () => {
|
|
@@ -17,7 +17,7 @@ export function automationTroubleshootPath(
|
|
|
17
17
|
item: Pick<DispatchAutomationItem, "name">,
|
|
18
18
|
): string {
|
|
19
19
|
const params = new URLSearchParams({ query: item.name });
|
|
20
|
-
return `/thread-debug?${params.toString()}`;
|
|
20
|
+
return `/admin/thread-debug?${params.toString()}`;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export function automationTarget(item: DispatchAutomationItem): string {
|
package/src/routes/index.spec.ts
CHANGED
|
@@ -8,6 +8,7 @@ describe("Dispatch route registration", () => {
|
|
|
8
8
|
path?: string;
|
|
9
9
|
file?: string;
|
|
10
10
|
index?: boolean;
|
|
11
|
+
children?: Array<{ path?: string; index?: boolean }>;
|
|
11
12
|
}>;
|
|
12
13
|
const paths = routes.map((route) => route.path);
|
|
13
14
|
|
|
@@ -16,6 +17,7 @@ describe("Dispatch route registration", () => {
|
|
|
16
17
|
expect(paths).toContain("browser-chat");
|
|
17
18
|
expect(paths).toContain("browser-connect");
|
|
18
19
|
expect(paths).toContain("operations");
|
|
20
|
+
expect(paths).toContain("admin");
|
|
19
21
|
expect(paths.indexOf("chat")).toBeLessThan(paths.indexOf(":appId"));
|
|
20
22
|
expect(paths.indexOf("chat/:threadId")).toBeLessThan(
|
|
21
23
|
paths.indexOf(":appId"),
|
|
@@ -25,5 +27,11 @@ describe("Dispatch route registration", () => {
|
|
|
25
27
|
paths.indexOf(":appId"),
|
|
26
28
|
);
|
|
27
29
|
expect(paths.indexOf("operations")).toBeLessThan(paths.indexOf(":appId"));
|
|
30
|
+
|
|
31
|
+
const admin = routes.find((route) => route.path === "admin");
|
|
32
|
+
const adminPaths = admin?.children?.map((route) => route.path) ?? [];
|
|
33
|
+
expect(adminPaths).toContain("operations");
|
|
34
|
+
expect(adminPaths).toContain("apps/:appId");
|
|
35
|
+
expect(adminPaths.indexOf("operations")).toBeGreaterThanOrEqual(0);
|
|
28
36
|
});
|
|
29
37
|
});
|