@agent-native/dispatch 0.1.1 → 0.2.2

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 (141) hide show
  1. package/dist/actions/index.d.ts.map +1 -1
  2. package/dist/actions/index.js +2 -0
  3. package/dist/actions/index.js.map +1 -1
  4. package/dist/actions/list-dispatch-usage-metrics.d.ts +3 -0
  5. package/dist/actions/list-dispatch-usage-metrics.d.ts.map +1 -0
  6. package/dist/actions/list-dispatch-usage-metrics.js +18 -0
  7. package/dist/actions/list-dispatch-usage-metrics.js.map +1 -0
  8. package/dist/actions/navigate.d.ts +1 -0
  9. package/dist/actions/navigate.d.ts.map +1 -1
  10. package/dist/actions/navigate.js +3 -17
  11. package/dist/actions/navigate.js.map +1 -1
  12. package/dist/actions/view-screen.d.ts.map +1 -1
  13. package/dist/actions/view-screen.js +19 -0
  14. package/dist/actions/view-screen.js.map +1 -1
  15. package/dist/components/agents-panel.js +3 -3
  16. package/dist/components/app-keys-popover.js +2 -2
  17. package/dist/components/create-app-popover.js +2 -2
  18. package/dist/components/dispatch-shell.js +2 -2
  19. package/dist/components/index.d.ts +1 -0
  20. package/dist/components/index.d.ts.map +1 -1
  21. package/dist/components/index.js.map +1 -1
  22. package/dist/components/layout/Header.js +4 -4
  23. package/dist/components/layout/Header.js.map +1 -1
  24. package/dist/components/layout/Layout.d.ts +28 -3
  25. package/dist/components/layout/Layout.d.ts.map +1 -1
  26. package/dist/components/layout/Layout.js +137 -26
  27. package/dist/components/layout/Layout.js.map +1 -1
  28. package/dist/components/messaging-setup-panel.js +4 -4
  29. package/dist/components/ui/accordion.js +1 -1
  30. package/dist/components/ui/alert-dialog.js +2 -2
  31. package/dist/components/ui/alert.js +1 -1
  32. package/dist/components/ui/avatar.js +1 -1
  33. package/dist/components/ui/badge.js +1 -1
  34. package/dist/components/ui/breadcrumb.js +1 -1
  35. package/dist/components/ui/button.js +1 -1
  36. package/dist/components/ui/calendar.js +2 -2
  37. package/dist/components/ui/card.js +1 -1
  38. package/dist/components/ui/carousel.d.ts +2 -2
  39. package/dist/components/ui/carousel.js +2 -2
  40. package/dist/components/ui/chart.js +1 -1
  41. package/dist/components/ui/checkbox.js +1 -1
  42. package/dist/components/ui/command.js +2 -2
  43. package/dist/components/ui/context-menu.js +1 -1
  44. package/dist/components/ui/dialog.js +1 -1
  45. package/dist/components/ui/drawer.js +1 -1
  46. package/dist/components/ui/dropdown-menu.js +1 -1
  47. package/dist/components/ui/form.js +2 -2
  48. package/dist/components/ui/hover-card.js +1 -1
  49. package/dist/components/ui/input-otp.js +1 -1
  50. package/dist/components/ui/input.js +1 -1
  51. package/dist/components/ui/label.js +1 -1
  52. package/dist/components/ui/menubar.js +1 -1
  53. package/dist/components/ui/navigation-menu.js +1 -1
  54. package/dist/components/ui/pagination.d.ts +1 -1
  55. package/dist/components/ui/pagination.js +2 -2
  56. package/dist/components/ui/popover.js +1 -1
  57. package/dist/components/ui/progress.js +1 -1
  58. package/dist/components/ui/radio-group.js +1 -1
  59. package/dist/components/ui/resizable.js +1 -1
  60. package/dist/components/ui/scroll-area.js +1 -1
  61. package/dist/components/ui/select.js +1 -1
  62. package/dist/components/ui/separator.js +1 -1
  63. package/dist/components/ui/sheet.js +1 -1
  64. package/dist/components/ui/sidebar.d.ts +2 -2
  65. package/dist/components/ui/sidebar.js +8 -8
  66. package/dist/components/ui/skeleton.js +1 -1
  67. package/dist/components/ui/slider.js +1 -1
  68. package/dist/components/ui/sonner.js +1 -1
  69. package/dist/components/ui/spinner.js +1 -1
  70. package/dist/components/ui/switch.js +1 -1
  71. package/dist/components/ui/table.js +1 -1
  72. package/dist/components/ui/tabs.js +1 -1
  73. package/dist/components/ui/textarea.js +1 -1
  74. package/dist/components/ui/toast.js +1 -1
  75. package/dist/components/ui/toaster.js +2 -2
  76. package/dist/components/ui/toggle-group.js +2 -2
  77. package/dist/components/ui/toggle.js +1 -1
  78. package/dist/components/ui/tooltip.js +1 -1
  79. package/dist/components/ui/use-toast.d.ts +1 -1
  80. package/dist/components/ui/use-toast.js +1 -1
  81. package/dist/hooks/use-navigation-state.d.ts +2 -1
  82. package/dist/hooks/use-navigation-state.d.ts.map +1 -1
  83. package/dist/hooks/use-navigation-state.js +36 -8
  84. package/dist/hooks/use-navigation-state.js.map +1 -1
  85. package/dist/hooks/use-toast.d.ts +1 -1
  86. package/dist/routes/index.d.ts.map +1 -1
  87. package/dist/routes/index.js +3 -2
  88. package/dist/routes/index.js.map +1 -1
  89. package/dist/routes/pages/_index.js +1 -1
  90. package/dist/routes/pages/agents.js +2 -2
  91. package/dist/routes/pages/approval.js +2 -2
  92. package/dist/routes/pages/approvals.js +4 -4
  93. package/dist/routes/pages/apps.$appId.js +3 -3
  94. package/dist/routes/pages/apps.js +5 -5
  95. package/dist/routes/pages/audit.js +1 -1
  96. package/dist/routes/pages/destinations.js +6 -6
  97. package/dist/routes/pages/extensions.$id.d.ts +2 -0
  98. package/dist/routes/pages/extensions.$id.d.ts.map +1 -0
  99. package/dist/routes/pages/extensions.$id.js +6 -0
  100. package/dist/routes/pages/extensions.$id.js.map +1 -0
  101. package/dist/routes/pages/extensions._index.d.ts +2 -0
  102. package/dist/routes/pages/extensions._index.d.ts.map +1 -0
  103. package/dist/routes/pages/extensions._index.js +6 -0
  104. package/dist/routes/pages/extensions._index.js.map +1 -0
  105. package/dist/routes/pages/identities.js +2 -2
  106. package/dist/routes/pages/integrations.js +4 -4
  107. package/dist/routes/pages/messaging.js +2 -2
  108. package/dist/routes/pages/metrics.d.ts +5 -0
  109. package/dist/routes/pages/metrics.d.ts.map +1 -0
  110. package/dist/routes/pages/metrics.js +135 -0
  111. package/dist/routes/pages/metrics.js.map +1 -0
  112. package/dist/routes/pages/new-app.js +1 -1
  113. package/dist/routes/pages/overview.d.ts.map +1 -1
  114. package/dist/routes/pages/overview.js +9 -17
  115. package/dist/routes/pages/overview.js.map +1 -1
  116. package/dist/routes/pages/team.js +1 -1
  117. package/dist/routes/pages/vault.js +10 -10
  118. package/dist/routes/pages/workspace.js +10 -10
  119. package/dist/server/lib/pre-auth-routing.d.ts.map +1 -1
  120. package/dist/server/lib/pre-auth-routing.js +9 -2
  121. package/dist/server/lib/pre-auth-routing.js.map +1 -1
  122. package/dist/server/lib/usage-metrics-store.d.ts +93 -0
  123. package/dist/server/lib/usage-metrics-store.d.ts.map +1 -0
  124. package/dist/server/lib/usage-metrics-store.js +386 -0
  125. package/dist/server/lib/usage-metrics-store.js.map +1 -0
  126. package/package.json +9 -5
  127. package/src/actions/index.ts +2 -0
  128. package/src/actions/list-dispatch-usage-metrics.ts +19 -0
  129. package/src/actions/navigate.ts +5 -17
  130. package/src/actions/view-screen.ts +18 -0
  131. package/src/components/index.ts +6 -0
  132. package/src/components/layout/Header.tsx +1 -1
  133. package/src/components/layout/Layout.tsx +194 -37
  134. package/src/hooks/use-navigation-state.ts +57 -8
  135. package/src/routes/index.ts +3 -2
  136. package/src/routes/pages/extensions.$id.tsx +5 -0
  137. package/src/routes/pages/extensions._index.tsx +5 -0
  138. package/src/routes/pages/metrics.tsx +667 -0
  139. package/src/routes/pages/overview.tsx +0 -10
  140. package/src/server/lib/pre-auth-routing.ts +10 -2
  141. package/src/server/lib/usage-metrics-store.ts +605 -0
@@ -1,4 +1,4 @@
1
- import { useState, type ReactNode } from "react";
1
+ import { useState, type ComponentType, type ReactNode } from "react";
2
2
  import { NavLink, useLocation } from "react-router";
3
3
  import {
4
4
  AgentSidebar,
@@ -11,6 +11,7 @@ import { ToolsSidebarSection } from "@agent-native/core/client/tools";
11
11
  import {
12
12
  IconArrowUpRight,
13
13
  IconApps,
14
+ IconChartBar,
14
15
  IconBrandTelegram,
15
16
  IconKey,
16
17
  IconChevronDown,
@@ -33,27 +34,131 @@ import {
33
34
  import { Header } from "./Header";
34
35
  import { HeaderActionsProvider } from "./HeaderActions";
35
36
 
37
+ export type DispatchNavSection = "primary" | "operations";
38
+
39
+ export type DispatchNavIcon = ComponentType<{
40
+ size?: number | string;
41
+ className?: string;
42
+ }>;
43
+
44
+ export interface DispatchNavItem {
45
+ /** Stable id used for keys and navigation.view. Avoid built-in ids. */
46
+ id: string;
47
+ /** React Router path for the tab, usually backed by an app/routes/*.tsx file. */
48
+ to: string;
49
+ label: string;
50
+ icon?: DispatchNavIcon;
51
+ /** Defaults to "operations", which is where local management tools usually fit. */
52
+ section?: DispatchNavSection;
53
+ /** Override active matching for nested or multi-route tools. */
54
+ match?: (pathname: string) => boolean;
55
+ }
56
+
57
+ export interface DispatchExtensionConfig {
58
+ /** Extra sidebar tabs supplied by the generated workspace. */
59
+ navItems?: readonly DispatchNavItem[];
60
+ /** Extra React Query keys to invalidate when Dispatch receives DB sync events. */
61
+ queryKeys?: readonly string[];
62
+ }
63
+
36
64
  const PRIMARY_NAV_ITEMS = [
37
- { to: "/overview", label: "Overview", icon: IconBroadcast },
38
- { to: "/apps", label: "Apps", icon: IconApps },
39
- { to: "/vault", label: "Vault", icon: IconKey },
40
- { to: "/integrations", label: "Integrations", icon: IconPuzzle },
41
- { to: "/agents", label: "Agents", icon: IconPlugConnected },
42
- ] as const;
65
+ {
66
+ id: "overview",
67
+ to: "/overview",
68
+ label: "Overview",
69
+ icon: IconBroadcast,
70
+ section: "primary",
71
+ },
72
+ {
73
+ id: "apps",
74
+ to: "/apps",
75
+ label: "Apps",
76
+ icon: IconApps,
77
+ section: "primary",
78
+ },
79
+ {
80
+ id: "metrics",
81
+ to: "/metrics",
82
+ label: "Metrics",
83
+ icon: IconChartBar,
84
+ section: "primary",
85
+ },
86
+ {
87
+ id: "vault",
88
+ to: "/vault",
89
+ label: "Vault",
90
+ icon: IconKey,
91
+ section: "primary",
92
+ },
93
+ {
94
+ id: "integrations",
95
+ to: "/integrations",
96
+ label: "Integrations",
97
+ icon: IconPuzzle,
98
+ section: "primary",
99
+ },
100
+ {
101
+ id: "agents",
102
+ to: "/agents",
103
+ label: "Agents",
104
+ icon: IconPlugConnected,
105
+ section: "primary",
106
+ },
107
+ ] as const satisfies readonly DispatchNavItem[];
43
108
 
44
109
  const OPERATIONS_NAV_ITEMS = [
45
- { to: "/workspace", label: "Resources", icon: IconLayersSubtract },
46
- { to: "/messaging", label: "Messaging", icon: IconBrandTelegram },
47
- { to: "/destinations", label: "Destinations", icon: IconArrowUpRight },
48
- { to: "/identities", label: "Identities", icon: IconFingerprint },
49
- { to: "/approvals", label: "Approvals", icon: IconShieldCheck },
50
- { to: "/audit", label: "Audit", icon: IconHistory },
51
- { to: "/team", label: "Team", icon: IconUsersGroup },
52
- ] as const;
53
-
54
- type NavItem =
55
- | (typeof PRIMARY_NAV_ITEMS)[number]
56
- | (typeof OPERATIONS_NAV_ITEMS)[number];
110
+ {
111
+ id: "workspace",
112
+ to: "/workspace",
113
+ label: "Resources",
114
+ icon: IconLayersSubtract,
115
+ section: "operations",
116
+ },
117
+ {
118
+ id: "messaging",
119
+ to: "/messaging",
120
+ label: "Messaging",
121
+ icon: IconBrandTelegram,
122
+ section: "operations",
123
+ },
124
+ {
125
+ id: "destinations",
126
+ to: "/destinations",
127
+ label: "Destinations",
128
+ icon: IconArrowUpRight,
129
+ section: "operations",
130
+ },
131
+ {
132
+ id: "identities",
133
+ to: "/identities",
134
+ label: "Identities",
135
+ icon: IconFingerprint,
136
+ section: "operations",
137
+ },
138
+ {
139
+ id: "approvals",
140
+ to: "/approvals",
141
+ label: "Approvals",
142
+ icon: IconShieldCheck,
143
+ section: "operations",
144
+ },
145
+ {
146
+ id: "audit",
147
+ to: "/audit",
148
+ label: "Audit",
149
+ icon: IconHistory,
150
+ section: "operations",
151
+ },
152
+ {
153
+ id: "team",
154
+ to: "/team",
155
+ label: "Team",
156
+ icon: IconUsersGroup,
157
+ section: "operations",
158
+ },
159
+ ] as const satisfies readonly DispatchNavItem[];
160
+
161
+ const EMPTY_NAV_ITEMS: readonly DispatchNavItem[] = [];
57
162
 
58
163
  const SIDEBAR_SUGGESTIONS = [
59
164
  "Create a new app",
@@ -68,6 +173,8 @@ const CHROMELESS_PATHS = ["/approval"];
68
173
  // there's no double-header.
69
174
  function pageOwnsToolbar(pathname: string): boolean {
70
175
  if (pathname === "/tools" || pathname.startsWith("/tools/")) return true;
176
+ if (pathname === "/extensions" || pathname.startsWith("/extensions/"))
177
+ return true;
71
178
  return false;
72
179
  }
73
180
 
@@ -77,7 +184,35 @@ interface WorkspaceInfo {
77
184
  appCount: number;
78
185
  }
79
186
 
80
- export function NavContent({ onNavigate }: { onNavigate?: () => void }) {
187
+ function sectionFor(item: DispatchNavItem): DispatchNavSection {
188
+ return item.section ?? "operations";
189
+ }
190
+
191
+ function navItemMatchesPath(item: DispatchNavItem, pathname: string): boolean {
192
+ if (item.match) {
193
+ try {
194
+ if (item.match(pathname)) return true;
195
+ } catch {
196
+ return false;
197
+ }
198
+ }
199
+ return pathname === item.to || pathname.startsWith(`${item.to}/`);
200
+ }
201
+
202
+ function navItemsForSection(
203
+ items: readonly DispatchNavItem[],
204
+ section: DispatchNavSection,
205
+ ): DispatchNavItem[] {
206
+ return items.filter((item) => sectionFor(item) === section);
207
+ }
208
+
209
+ export function NavContent({
210
+ onNavigate,
211
+ extensions,
212
+ }: {
213
+ onNavigate?: () => void;
214
+ extensions?: DispatchExtensionConfig;
215
+ }) {
81
216
  const location = useLocation();
82
217
  const { data: workspace } = useActionQuery(
83
218
  "get-workspace-info",
@@ -86,29 +221,42 @@ export function NavContent({ onNavigate }: { onNavigate?: () => void }) {
86
221
  );
87
222
  const ws = workspace as WorkspaceInfo | undefined;
88
223
  const workspaceLabel = ws?.displayName ?? ws?.name ?? null;
89
- const operationsOpen = OPERATIONS_NAV_ITEMS.some(
90
- (item) =>
91
- location.pathname === item.to ||
92
- location.pathname.startsWith(`${item.to}/`),
224
+ const extensionNavItems = extensions?.navItems ?? EMPTY_NAV_ITEMS;
225
+ const primaryNavItems = [
226
+ ...PRIMARY_NAV_ITEMS,
227
+ ...navItemsForSection(extensionNavItems, "primary"),
228
+ ];
229
+ const operationsNavItems = [
230
+ ...OPERATIONS_NAV_ITEMS,
231
+ ...navItemsForSection(extensionNavItems, "operations"),
232
+ ];
233
+ const operationsOpen = operationsNavItems.some((item) =>
234
+ navItemMatchesPath(item, location.pathname),
93
235
  );
94
236
 
95
- const renderNavItem = (item: NavItem) => {
237
+ const renderNavItem = (item: DispatchNavItem) => {
96
238
  const Icon = item.icon;
97
239
  return (
98
- <li key={item.to}>
240
+ <li key={item.id}>
99
241
  <NavLink
100
242
  to={item.to}
101
243
  onClick={onNavigate}
102
- className={({ isActive }) =>
103
- cn(
244
+ className={({ isActive }) => {
245
+ const active =
246
+ isActive || navItemMatchesPath(item, location.pathname);
247
+ return cn(
104
248
  "flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm",
105
- isActive
249
+ active
106
250
  ? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
107
251
  : "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
108
- )
109
- }
252
+ );
253
+ }}
110
254
  >
111
- <Icon size={16} className="shrink-0" />
255
+ {Icon ? (
256
+ <Icon size={16} className="shrink-0" />
257
+ ) : (
258
+ <span className="h-4 w-4 shrink-0" aria-hidden="true" />
259
+ )}
112
260
  <span className="truncate">{item.label}</span>
113
261
  </NavLink>
114
262
  </li>
@@ -147,7 +295,7 @@ export function NavContent({ onNavigate }: { onNavigate?: () => void }) {
147
295
  </div>
148
296
 
149
297
  <nav className="flex-1 overflow-y-auto px-2 py-3">
150
- <ul className="space-y-0.5">{PRIMARY_NAV_ITEMS.map(renderNavItem)}</ul>
298
+ <ul className="space-y-0.5">{primaryNavItems.map(renderNavItem)}</ul>
151
299
  <details className="group mt-4" open={operationsOpen}>
152
300
  <summary className="flex h-8 cursor-pointer list-none items-center justify-between rounded-md px-2 text-xs font-medium uppercase text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground [&::-webkit-details-marker]:hidden">
153
301
  <span>Operations</span>
@@ -157,7 +305,7 @@ export function NavContent({ onNavigate }: { onNavigate?: () => void }) {
157
305
  />
158
306
  </summary>
159
307
  <ul className="mt-1 space-y-0.5">
160
- {OPERATIONS_NAV_ITEMS.map(renderNavItem)}
308
+ {operationsNavItems.map(renderNavItem)}
161
309
  </ul>
162
310
  </details>
163
311
  </nav>
@@ -170,7 +318,13 @@ export function NavContent({ onNavigate }: { onNavigate?: () => void }) {
170
318
  );
171
319
  }
172
320
 
173
- export function Layout({ children }: { children: ReactNode }) {
321
+ export function Layout({
322
+ children,
323
+ extensions,
324
+ }: {
325
+ children: ReactNode;
326
+ extensions?: DispatchExtensionConfig;
327
+ }) {
174
328
  const location = useLocation();
175
329
  const [mobileOpen, setMobileOpen] = useState(false);
176
330
  const hasEmbeddedAgentChat =
@@ -206,7 +360,7 @@ export function Layout({ children }: { children: ReactNode }) {
206
360
  <HeaderActionsProvider>
207
361
  <div className="flex h-screen w-full overflow-hidden bg-background">
208
362
  <aside className="hidden 2xl:flex w-64 shrink-0 flex-col border-r bg-sidebar text-sidebar-foreground">
209
- <NavContent />
363
+ <NavContent extensions={extensions} />
210
364
  </aside>
211
365
 
212
366
  <Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
@@ -219,7 +373,10 @@ export function Layout({ children }: { children: ReactNode }) {
219
373
  Workspace navigation links
220
374
  </SheetDescription>
221
375
  <div className="flex h-full w-full flex-col">
222
- <NavContent onNavigate={() => setMobileOpen(false)} />
376
+ <NavContent
377
+ extensions={extensions}
378
+ onNavigate={() => setMobileOpen(false)}
379
+ />
223
380
  </div>
224
381
  </SheetContent>
225
382
  </Sheet>
@@ -6,13 +6,17 @@ import {
6
6
  appBasePath,
7
7
  appPath,
8
8
  } from "@agent-native/core/client";
9
+ import type {
10
+ DispatchExtensionConfig,
11
+ DispatchNavItem,
12
+ } from "../components/index.js";
9
13
 
10
14
  export interface NavigationState {
11
15
  view: string;
12
16
  path?: string;
13
17
  }
14
18
 
15
- export function useNavigationState() {
19
+ export function useNavigationState(extensions?: DispatchExtensionConfig) {
16
20
  const location = useLocation();
17
21
  const navigate = useNavigate();
18
22
  const qc = useQueryClient();
@@ -20,7 +24,7 @@ export function useNavigationState() {
20
24
  // Sync current route to application state
21
25
  useEffect(() => {
22
26
  const state: NavigationState = {
23
- view: resolveView(location.pathname),
27
+ view: resolveView(location.pathname, extensions),
24
28
  path: appPath(location.pathname),
25
29
  };
26
30
 
@@ -30,7 +34,7 @@ export function useNavigationState() {
30
34
  headers: { "Content-Type": "application/json" },
31
35
  body: JSON.stringify(state),
32
36
  }).catch(() => {});
33
- }, [location.pathname]);
37
+ }, [extensions, location.pathname]);
34
38
 
35
39
  // Listen for navigate commands from agent
36
40
  const { data: navCommand } = useQuery({
@@ -62,10 +66,12 @@ export function useNavigationState() {
62
66
  const cmd = navCommand as NavigationState;
63
67
 
64
68
  // Navigate to a specific path or resolve view name to path
65
- const path = routerPath(cmd.path || resolvePath(cmd.view) || "/overview");
69
+ const path = routerPath(
70
+ cmd.path || resolvePath(cmd.view, extensions) || "/overview",
71
+ );
66
72
  navigate(path);
67
73
  qc.setQueryData(["navigate-command"], null);
68
- }, [navCommand, navigate, qc]);
74
+ }, [extensions, navCommand, navigate, qc]);
69
75
  }
70
76
 
71
77
  function routerPath(path: string): string {
@@ -78,8 +84,45 @@ function routerPath(path: string): string {
78
84
  return path;
79
85
  }
80
86
 
81
- function resolveView(pathname: string): string {
87
+ function extensionItemMatchesPath(
88
+ item: DispatchNavItem,
89
+ pathname: string,
90
+ ): boolean {
91
+ if (item.match) {
92
+ try {
93
+ if (item.match(pathname)) return true;
94
+ } catch {
95
+ return false;
96
+ }
97
+ }
98
+ return pathname === item.to || pathname.startsWith(`${item.to}/`);
99
+ }
100
+
101
+ function resolveExtensionView(
102
+ pathname: string,
103
+ extensions?: DispatchExtensionConfig,
104
+ ): string | undefined {
105
+ return extensions?.navItems?.find((item) =>
106
+ extensionItemMatchesPath(item, pathname),
107
+ )?.id;
108
+ }
109
+
110
+ function resolveExtensionPath(
111
+ view: string | undefined,
112
+ extensions?: DispatchExtensionConfig,
113
+ ): string | undefined {
114
+ if (!view) return undefined;
115
+ return extensions?.navItems?.find((item) => item.id === view)?.to;
116
+ }
117
+
118
+ function resolveView(
119
+ pathname: string,
120
+ extensions?: DispatchExtensionConfig,
121
+ ): string {
122
+ const extensionView = resolveExtensionView(pathname, extensions);
123
+ if (extensionView) return extensionView;
82
124
  if (pathname.startsWith("/apps")) return "apps";
125
+ if (pathname.startsWith("/metrics")) return "metrics";
83
126
  if (pathname.startsWith("/new-app")) return "new-app";
84
127
  if (pathname.startsWith("/vault")) return "vault";
85
128
  if (pathname.startsWith("/integrations")) return "integrations";
@@ -94,12 +137,18 @@ function resolveView(pathname: string): string {
94
137
  return "overview";
95
138
  }
96
139
 
97
- function resolvePath(view?: string): string | undefined {
140
+ function resolvePath(
141
+ view?: string,
142
+ extensions?: DispatchExtensionConfig,
143
+ ): string | undefined {
98
144
  switch (view) {
99
145
  case "overview":
100
146
  return "/overview";
101
147
  case "apps":
102
148
  return "/apps";
149
+ case "metrics":
150
+ case "usage":
151
+ return "/metrics";
103
152
  case "new-app":
104
153
  case "create-app":
105
154
  return "/new-app";
@@ -127,6 +176,6 @@ function resolvePath(view?: string): string | undefined {
127
176
  case "team":
128
177
  return "/team";
129
178
  default:
130
- return undefined;
179
+ return resolveExtensionPath(view, extensions);
131
180
  }
132
181
  }
@@ -32,6 +32,7 @@ import { type RouteConfig, route, index } from "@react-router/dev/routes";
32
32
  export const dispatchRoutes: RouteConfig = [
33
33
  index("./pages/_index.js"),
34
34
  route("overview", "./pages/overview.js"),
35
+ route("metrics", "./pages/metrics.js"),
35
36
  route("apps", "./pages/apps.js"),
36
37
  route("apps/:appId", "./pages/apps.$appId.js"),
37
38
  route("new-app", "./pages/new-app.js"),
@@ -46,6 +47,6 @@ export const dispatchRoutes: RouteConfig = [
46
47
  route("approvals", "./pages/approvals.js"),
47
48
  route("audit", "./pages/audit.js"),
48
49
  route("team", "./pages/team.js"),
49
- route("tools", "./pages/tools._index.js"),
50
- route("tools/:id", "./pages/tools.$id.js"),
50
+ route("extensions", "./pages/extensions._index.js"),
51
+ route("extensions/:id", "./pages/extensions.$id.js"),
51
52
  ];
@@ -0,0 +1,5 @@
1
+ import { ToolViewerPage } from "@agent-native/core/client/tools";
2
+
3
+ export default function ExtensionViewerRoute() {
4
+ return <ToolViewerPage />;
5
+ }
@@ -0,0 +1,5 @@
1
+ import { ToolsListPage } from "@agent-native/core/client/tools";
2
+
3
+ export default function ExtensionsRoute() {
4
+ return <ToolsListPage />;
5
+ }