@agent-native/dispatch 0.24.0 → 0.24.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 (161) hide show
  1. package/dist/actions/provider-api-register.d.ts +12 -12
  2. package/dist/components/admin-navigation.d.ts.map +1 -1
  3. package/dist/components/admin-navigation.js +21 -5
  4. package/dist/components/admin-navigation.js.map +1 -1
  5. package/dist/components/app-list-row.d.ts +1 -1
  6. package/dist/components/app-list-row.d.ts.map +1 -1
  7. package/dist/components/app-list-row.js +1 -1
  8. package/dist/components/app-list-row.js.map +1 -1
  9. package/dist/components/app-open-actions.d.ts +1 -17
  10. package/dist/components/app-open-actions.d.ts.map +1 -1
  11. package/dist/components/app-open-actions.js +1 -20
  12. package/dist/components/app-open-actions.js.map +1 -1
  13. package/dist/components/app-row-settings.d.ts +6 -0
  14. package/dist/components/app-row-settings.d.ts.map +1 -0
  15. package/dist/components/app-row-settings.js +11 -0
  16. package/dist/components/app-row-settings.js.map +1 -0
  17. package/dist/components/automation-run-history-dialog.d.ts.map +1 -1
  18. package/dist/components/automation-run-history-dialog.js +3 -2
  19. package/dist/components/automation-run-history-dialog.js.map +1 -1
  20. package/dist/components/connected-app-card.d.ts.map +1 -1
  21. package/dist/components/connected-app-card.js +1 -1
  22. package/dist/components/connected-app-card.js.map +1 -1
  23. package/dist/components/create-app-popover.js +2 -2
  24. package/dist/components/create-app-popover.js.map +1 -1
  25. package/dist/components/dispatch-control-plane.d.ts.map +1 -1
  26. package/dist/components/dispatch-control-plane.js +23 -7
  27. package/dist/components/dispatch-control-plane.js.map +1 -1
  28. package/dist/components/dispatch-shell.d.ts.map +1 -1
  29. package/dist/components/dispatch-shell.js +5 -1
  30. package/dist/components/dispatch-shell.js.map +1 -1
  31. package/dist/components/docs-link.d.ts +16 -0
  32. package/dist/components/docs-link.d.ts.map +1 -0
  33. package/dist/components/docs-link.js +34 -0
  34. package/dist/components/docs-link.js.map +1 -0
  35. package/dist/components/layout/Layout.d.ts +1 -0
  36. package/dist/components/layout/Layout.d.ts.map +1 -1
  37. package/dist/components/layout/Layout.js +37 -28
  38. package/dist/components/layout/Layout.js.map +1 -1
  39. package/dist/components/messaging-setup-panel.d.ts.map +1 -1
  40. package/dist/components/messaging-setup-panel.js +3 -2
  41. package/dist/components/messaging-setup-panel.js.map +1 -1
  42. package/dist/components/usage-alerts-panel.d.ts +11 -0
  43. package/dist/components/usage-alerts-panel.d.ts.map +1 -0
  44. package/dist/components/usage-alerts-panel.js +125 -0
  45. package/dist/components/usage-alerts-panel.js.map +1 -0
  46. package/dist/components/workspace-app-card.d.ts +3 -1
  47. package/dist/components/workspace-app-card.d.ts.map +1 -1
  48. package/dist/components/workspace-app-card.js +70 -29
  49. package/dist/components/workspace-app-card.js.map +1 -1
  50. package/dist/components/workspace-app-search.d.ts +10 -0
  51. package/dist/components/workspace-app-search.d.ts.map +1 -0
  52. package/dist/components/workspace-app-search.js +27 -0
  53. package/dist/components/workspace-app-search.js.map +1 -0
  54. package/dist/components/workspace-resource-effective-stack.d.ts.map +1 -1
  55. package/dist/components/workspace-resource-effective-stack.js +3 -2
  56. package/dist/components/workspace-resource-effective-stack.js.map +1 -1
  57. package/dist/components/workspace-template-card.d.ts.map +1 -1
  58. package/dist/components/workspace-template-card.js +2 -1
  59. package/dist/components/workspace-template-card.js.map +1 -1
  60. package/dist/lib/workspace-app-layout.d.ts +19 -0
  61. package/dist/lib/workspace-app-layout.d.ts.map +1 -0
  62. package/dist/lib/workspace-app-layout.js +75 -0
  63. package/dist/lib/workspace-app-layout.js.map +1 -0
  64. package/dist/lib/workspace-apps.d.ts +10 -0
  65. package/dist/lib/workspace-apps.d.ts.map +1 -1
  66. package/dist/lib/workspace-apps.js +59 -0
  67. package/dist/lib/workspace-apps.js.map +1 -1
  68. package/dist/lib/workspace-apps.test.js +18 -1
  69. package/dist/lib/workspace-apps.test.js.map +1 -1
  70. package/dist/routes/pages/$appId.d.ts.map +1 -1
  71. package/dist/routes/pages/$appId.js +4 -2
  72. package/dist/routes/pages/$appId.js.map +1 -1
  73. package/dist/routes/pages/agents.d.ts.map +1 -1
  74. package/dist/routes/pages/agents.js +2 -3
  75. package/dist/routes/pages/agents.js.map +1 -1
  76. package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
  77. package/dist/routes/pages/apps.$appId.js +8 -3
  78. package/dist/routes/pages/apps.$appId.js.map +1 -1
  79. package/dist/routes/pages/apps.d.ts.map +1 -1
  80. package/dist/routes/pages/apps.js +31 -16
  81. package/dist/routes/pages/apps.js.map +1 -1
  82. package/dist/routes/pages/audit.d.ts.map +1 -1
  83. package/dist/routes/pages/audit.js +2 -1
  84. package/dist/routes/pages/audit.js.map +1 -1
  85. package/dist/routes/pages/dreams.d.ts.map +1 -1
  86. package/dist/routes/pages/dreams.js +14 -18
  87. package/dist/routes/pages/dreams.js.map +1 -1
  88. package/dist/routes/pages/integrations.d.ts.map +1 -1
  89. package/dist/routes/pages/integrations.js +51 -14
  90. package/dist/routes/pages/integrations.js.map +1 -1
  91. package/dist/routes/pages/metrics.d.ts.map +1 -1
  92. package/dist/routes/pages/metrics.js +4 -1
  93. package/dist/routes/pages/metrics.js.map +1 -1
  94. package/dist/routes/pages/operations.js +1 -1
  95. package/dist/routes/pages/operations.js.map +1 -1
  96. package/dist/routes/pages/settings.d.ts.map +1 -1
  97. package/dist/routes/pages/settings.js +7 -2
  98. package/dist/routes/pages/settings.js.map +1 -1
  99. package/dist/routes/pages/thread-debug.d.ts.map +1 -1
  100. package/dist/routes/pages/thread-debug.js +5 -8
  101. package/dist/routes/pages/thread-debug.js.map +1 -1
  102. package/dist/routes/pages/vault.d.ts.map +1 -1
  103. package/dist/routes/pages/vault.js +1 -1
  104. package/dist/routes/pages/vault.js.map +1 -1
  105. package/dist/routes/pages/workspace.d.ts.map +1 -1
  106. package/dist/routes/pages/workspace.js +13 -10
  107. package/dist/routes/pages/workspace.js.map +1 -1
  108. package/dist/server/index.d.ts.map +1 -1
  109. package/dist/server/index.js +3 -0
  110. package/dist/server/index.js.map +1 -1
  111. package/dist/server/lib/app-creation-store.d.ts.map +1 -1
  112. package/dist/server/lib/app-creation-store.js +12 -52
  113. package/dist/server/lib/app-creation-store.js.map +1 -1
  114. package/dist/shared/app-roles.d.ts +9 -0
  115. package/dist/shared/app-roles.d.ts.map +1 -0
  116. package/dist/shared/app-roles.js +7 -0
  117. package/dist/shared/app-roles.js.map +1 -0
  118. package/package.json +4 -3
  119. package/src/components/admin-navigation.tsx +57 -11
  120. package/src/components/app-list-row.tsx +1 -2
  121. package/src/components/app-open-actions.tsx +5 -114
  122. package/src/components/app-row-settings.tsx +54 -0
  123. package/src/components/automation-run-history-dialog.tsx +8 -4
  124. package/src/components/connected-app-card.tsx +0 -1
  125. package/src/components/create-app-popover.spec.tsx +1 -1
  126. package/src/components/create-app-popover.tsx +2 -2
  127. package/src/components/dispatch-control-plane.spec.tsx +36 -2
  128. package/src/components/dispatch-control-plane.tsx +83 -27
  129. package/src/components/dispatch-shell.tsx +7 -1
  130. package/src/components/docs-link.tsx +52 -0
  131. package/src/components/layout/Layout.spec.tsx +10 -0
  132. package/src/components/layout/Layout.tsx +50 -23
  133. package/src/components/messaging-setup-panel.tsx +10 -3
  134. package/src/components/usage-alerts-panel.tsx +445 -0
  135. package/src/components/workspace-app-card.spec.tsx +130 -9
  136. package/src/components/workspace-app-card.tsx +128 -71
  137. package/src/components/workspace-app-search.tsx +96 -0
  138. package/src/components/workspace-resource-effective-stack.tsx +10 -5
  139. package/src/components/workspace-template-card.spec.tsx +52 -23
  140. package/src/components/workspace-template-card.tsx +7 -10
  141. package/src/lib/workspace-app-layout.spec.ts +47 -0
  142. package/src/lib/workspace-app-layout.ts +121 -0
  143. package/src/lib/workspace-apps.test.ts +45 -1
  144. package/src/lib/workspace-apps.ts +72 -0
  145. package/src/routes/pages/$appId.tsx +3 -6
  146. package/src/routes/pages/agents.tsx +6 -3
  147. package/src/routes/pages/apps.$appId.tsx +6 -6
  148. package/src/routes/pages/apps.tsx +116 -62
  149. package/src/routes/pages/audit.tsx +28 -8
  150. package/src/routes/pages/dreams.tsx +92 -142
  151. package/src/routes/pages/integrations.tsx +147 -68
  152. package/src/routes/pages/metrics.tsx +8 -0
  153. package/src/routes/pages/operations.tsx +2 -2
  154. package/src/routes/pages/settings.tsx +14 -9
  155. package/src/routes/pages/thread-debug.tsx +18 -36
  156. package/src/routes/pages/vault.tsx +7 -2
  157. package/src/routes/pages/workspace.tsx +92 -56
  158. package/src/server/index.ts +4 -0
  159. package/src/server/lib/app-creation-store.spec.ts +90 -43
  160. package/src/server/lib/app-creation-store.ts +15 -61
  161. package/src/shared/app-roles.ts +8 -0
@@ -0,0 +1,9 @@
1
+ export declare const dispatchAccessDescriptor: {
2
+ appId: string;
3
+ roles: readonly ["admin"];
4
+ roleLabels: {
5
+ admin: string;
6
+ };
7
+ label: string;
8
+ };
9
+ //# sourceMappingURL=app-roles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-roles.d.ts","sourceRoot":"","sources":["../../src/shared/app-roles.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,wBAAwB;;;;QAGrB,KAAK;;;CAEkB,CAAC"}
@@ -0,0 +1,7 @@
1
+ export const dispatchAccessDescriptor = {
2
+ appId: "dispatch",
3
+ roles: ["admin"],
4
+ roleLabels: { admin: "Dispatch admin" },
5
+ label: "Dispatch access",
6
+ };
7
+ //# sourceMappingURL=app-roles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-roles.js","sourceRoot":"","sources":["../../src/shared/app-roles.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,KAAK,EAAE,UAAU;IACjB,KAAK,EAAE,CAAC,OAAO,CAAU;IACzB,UAAU,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE;IACvC,KAAK,EAAE,iBAAiB;CACa,CAAC","sourcesContent":["import type { AppRolesDescriptor } from \"@agent-native/core/client/org\";\n\nexport const dispatchAccessDescriptor = {\n appId: \"dispatch\",\n roles: [\"admin\"] as const,\n roleLabels: { admin: \"Dispatch admin\" },\n label: \"Dispatch access\",\n} satisfies AppRolesDescriptor<\"admin\">;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/dispatch",
3
- "version": "0.24.0",
3
+ "version": "0.24.2",
4
4
  "description": "Dispatch — workspace control plane for agent-native apps. Vault, integrations, destinations, scheduled jobs, and cross-app delegation, shipped as a single drop-in package.",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -28,6 +28,7 @@
28
28
  "./routes": "./dist/routes/index.js",
29
29
  "./routes/pages/*": "./dist/routes/pages/*.js",
30
30
  "./server": "./dist/server/index.js",
31
+ "./shared/app-roles": "./dist/shared/app-roles.js",
31
32
  "./server/lib/thread-link-preview": "./dist/server/lib/thread-link-preview.js",
32
33
  "./actions": "./dist/actions/index.js",
33
34
  "./db": "./dist/db/index.js",
@@ -84,7 +85,7 @@
84
85
  "tailwind-merge": "^3.5.0",
85
86
  "vaul": "^1.1.2",
86
87
  "zod": "^4.3.6",
87
- "@agent-native/toolkit": "0.14.0"
88
+ "@agent-native/toolkit": "0.14.1"
88
89
  },
89
90
  "devDependencies": {
90
91
  "@react-router/dev": "^8.1.0",
@@ -98,7 +99,7 @@
98
99
  "typescript-7": "npm:typescript@^7.0.2",
99
100
  "vite": "8.1.0",
100
101
  "vitest": "^4.1.5",
101
- "@agent-native/core": "0.152.0"
102
+ "@agent-native/core": "0.153.0"
102
103
  },
103
104
  "peerDependencies": {
104
105
  "@agent-native/core": ">=0.8.0",
@@ -16,11 +16,14 @@ import {
16
16
  IconSettingsAutomation,
17
17
  IconShield,
18
18
  IconShieldCheck,
19
+ IconSearch,
20
+ IconX,
19
21
  } from "@tabler/icons-react";
20
- import { type ReactNode } from "react";
22
+ import { useMemo, useState, type ReactNode } from "react";
21
23
  import { NavLink } from "react-router";
22
24
 
23
25
  import { cn } from "../lib/utils";
26
+ import { DISPATCH_DOCS, DocsLink } from "./docs-link";
24
27
  import { useDispatchExtensions, type DispatchNavItem } from "./layout/Layout";
25
28
 
26
29
  export interface AdminNavGroup {
@@ -211,22 +214,60 @@ export function AdminShell({
211
214
  }) {
212
215
  const t = useT();
213
216
  const groups = useAdminNavGroups();
217
+ const [query, setQuery] = useState("");
218
+ const normalizedQuery = query.trim().toLowerCase();
219
+ const visibleGroups = useMemo(
220
+ () =>
221
+ groups
222
+ .map((group) => ({
223
+ ...group,
224
+ items: group.items.filter((item) => {
225
+ if (!normalizedQuery) return true;
226
+ const label = itemLabel(t, item).toLowerCase();
227
+ return `${label} ${group.label.toLowerCase()}`.includes(
228
+ normalizedQuery,
229
+ );
230
+ }),
231
+ }))
232
+ .filter((group) => group.items.length > 0),
233
+ [groups, normalizedQuery, t],
234
+ );
214
235
 
215
236
  return (
216
237
  <div
217
238
  className="grid min-w-0 gap-6 lg:grid-cols-[13rem_minmax(0,1fr)]"
218
239
  data-dispatch-admin-shell
219
240
  >
220
- <aside className="min-w-0 border-b pb-5 lg:sticky lg:top-2 lg:self-start lg:border-b-0 lg:border-e lg:pe-6">
221
- <div className="px-2">
222
- <div className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
223
- {t("dispatch.nav.admin", { defaultValue: "Admin" })}
241
+ <aside className="min-w-0 border-b pb-5 lg:sticky lg:top-2 lg:self-start lg:border-b-0">
242
+ <div className="flex items-center gap-1 px-2">
243
+ <div className="relative min-w-0 flex-1">
244
+ <IconSearch className="pointer-events-none absolute start-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" />
245
+ <input
246
+ type="search"
247
+ value={query}
248
+ onChange={(event) => setQuery(event.target.value)}
249
+ onKeyDown={(event) => {
250
+ if (event.key === "Escape") setQuery("");
251
+ }}
252
+ placeholder="Search admin"
253
+ aria-label="Search admin"
254
+ className="h-8 w-full rounded-md border border-border bg-background ps-8 pe-7 text-[13px] text-foreground outline-none transition-colors placeholder:text-muted-foreground focus:border-foreground/30 focus:ring-2 focus:ring-accent/40"
255
+ />
256
+ {query ? (
257
+ <button
258
+ type="button"
259
+ onClick={() => setQuery("")}
260
+ aria-label="Clear admin search"
261
+ className="absolute end-1.5 top-1/2 flex size-5 -translate-y-1/2 items-center justify-center rounded text-muted-foreground hover:bg-accent/60 hover:text-foreground"
262
+ >
263
+ <IconX className="size-3.5" />
264
+ </button>
265
+ ) : null}
224
266
  </div>
225
- <p className="mt-1 text-xs leading-5 text-muted-foreground">
226
- {t("dispatch.pages.adminDescription", {
227
- defaultValue: "Workspace controls and operations",
228
- })}
229
- </p>
267
+ <DocsLink
268
+ href={DISPATCH_DOCS.dispatch}
269
+ label="Open Dispatch admin documentation"
270
+ />
230
271
  </div>
231
272
 
232
273
  <nav
@@ -235,7 +276,7 @@ export function AdminShell({
235
276
  })}
236
277
  className="mt-5 space-y-5"
237
278
  >
238
- {groups.map((group) => (
279
+ {visibleGroups.map((group) => (
239
280
  <section key={group.id}>
240
281
  <h2 className="px-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground/70">
241
282
  {t(group.labelKey, { defaultValue: group.label })}
@@ -251,6 +292,11 @@ export function AdminShell({
251
292
  </ul>
252
293
  </section>
253
294
  ))}
295
+ {visibleGroups.length === 0 ? (
296
+ <p className="px-2 py-6 text-center text-xs text-muted-foreground">
297
+ No matching admin pages
298
+ </p>
299
+ ) : null}
254
300
  </nav>
255
301
  </aside>
256
302
 
@@ -2,8 +2,7 @@ import type { ReactNode } from "react";
2
2
 
3
3
  import { cn } from "../lib/utils";
4
4
 
5
- export const APP_LIST_GRID_CLASS =
6
- "mx-auto w-full max-w-[1000px] xl:grid xl:grid-cols-2 xl:gap-3 xl:overflow-visible xl:rounded-none xl:bg-transparent";
5
+ export const APP_LIST_GRID_CLASS = "w-full xl:grid xl:grid-cols-2 xl:gap-3";
7
6
  export const APP_LIST_GRID_ROW_CLASS =
8
7
  "xl:rounded-2xl xl:border xl:border-transparent xl:bg-card xl:transition-[background-color,border-color] xl:hover:border-border xl:focus-within:border-ring/50";
9
8
 
@@ -1,114 +1,5 @@
1
- import { IconChevronDown, IconPlus } from "@tabler/icons-react";
2
-
3
- import { Button } from "./ui/button";
4
- import {
5
- DropdownMenu,
6
- DropdownMenuContent,
7
- DropdownMenuItem,
8
- DropdownMenuTrigger,
9
- } from "./ui/dropdown-menu";
10
-
11
- export interface AppOpenActionLabels {
12
- addApp: string;
13
- openApp: string;
14
- openInline: string;
15
- openInNewTab: string;
16
- }
17
-
18
- const DEFAULT_LABELS: AppOpenActionLabels = {
19
- addApp: "Add app",
20
- openApp: "Open app",
21
- openInline: "Open inline",
22
- openInNewTab: "Open in new tab",
23
- };
24
-
25
- export function AppOpenActions({
26
- name,
27
- href,
28
- target,
29
- rel,
30
- labels: labelOverrides,
31
- onAddApp,
32
- onOpenInline,
33
- showInlineOption = false,
34
- showNewTabOption = false,
35
- }: {
36
- name: string;
37
- href: string | null;
38
- target?: "_blank";
39
- rel?: string;
40
- labels?: Partial<AppOpenActionLabels>;
41
- onAddApp?: () => void;
42
- onOpenInline?: () => void;
43
- showInlineOption?: boolean;
44
- showNewTabOption?: boolean;
45
- }) {
46
- const labels = { ...DEFAULT_LABELS, ...labelOverrides };
47
- const canOfferNewTab = showNewTabOption && target !== "_blank";
48
- const hasMenu =
49
- Boolean(onAddApp) ||
50
- (Boolean(href) && (showInlineOption || canOfferNewTab));
51
-
52
- if (!href && !hasMenu) {
53
- return (
54
- <Button size="sm" variant="outline" disabled>
55
- {labels.openApp}
56
- </Button>
57
- );
58
- }
59
-
60
- return (
61
- <div className="flex shrink-0 overflow-hidden rounded-md border border-border focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2">
62
- <Button
63
- asChild={Boolean(href)}
64
- size="sm"
65
- variant="outline"
66
- className="rounded-none border-0 border-e border-border"
67
- disabled={!href}
68
- >
69
- {href ? (
70
- <a href={href} target={target} rel={rel}>
71
- {labels.openApp}
72
- </a>
73
- ) : (
74
- <span>{labels.openApp}</span>
75
- )}
76
- </Button>
77
- {hasMenu ? (
78
- <DropdownMenu>
79
- <DropdownMenuTrigger asChild>
80
- <Button
81
- type="button"
82
- size="sm"
83
- variant="outline"
84
- className="size-9 rounded-none border-0 p-0"
85
- aria-label={`Open options for ${name}`}
86
- >
87
- <IconChevronDown size={15} />
88
- </Button>
89
- </DropdownMenuTrigger>
90
- <DropdownMenuContent align="end" className="min-w-40">
91
- {onAddApp ? (
92
- <DropdownMenuItem onSelect={onAddApp}>
93
- <IconPlus size={14} className="mr-2" />
94
- {labels.addApp}
95
- </DropdownMenuItem>
96
- ) : null}
97
- {showInlineOption && href ? (
98
- <DropdownMenuItem onSelect={onOpenInline}>
99
- {labels.openInline}
100
- </DropdownMenuItem>
101
- ) : null}
102
- {canOfferNewTab && href ? (
103
- <DropdownMenuItem asChild>
104
- <a href={href} target="_blank" rel="noreferrer">
105
- {labels.openInNewTab}
106
- </a>
107
- </DropdownMenuItem>
108
- ) : null}
109
- </DropdownMenuContent>
110
- </DropdownMenu>
111
- ) : null}
112
- </div>
113
- );
114
- }
1
+ export {
2
+ AppOpenActions,
3
+ type AppOpenActionLabels,
4
+ type AppOpenActionMenuItem,
5
+ } from "@agent-native/core/client/chat-first";
@@ -0,0 +1,54 @@
1
+ import { APP_ACTION_MENU_CONTENT_CLASS } from "@agent-native/core/client/chat-first";
2
+ import { IconPlus, IconSettings } from "@tabler/icons-react";
3
+
4
+ import { Button } from "./ui/button";
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuItem,
9
+ DropdownMenuTrigger,
10
+ } from "./ui/dropdown-menu";
11
+ import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
12
+
13
+ const APP_ROW_ACTION_CLASS =
14
+ "size-7 rounded-md p-0 text-muted-foreground transition-[background-color,color] hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-[state=open]:bg-accent data-[state=open]:text-foreground";
15
+
16
+ export function AppRowSettings({
17
+ name,
18
+ addAppLabel = "Add app",
19
+ onAddApp,
20
+ }: {
21
+ name: string;
22
+ addAppLabel?: string;
23
+ onAddApp: () => void;
24
+ }) {
25
+ return (
26
+ <DropdownMenu>
27
+ <Tooltip>
28
+ <TooltipTrigger asChild>
29
+ <DropdownMenuTrigger asChild>
30
+ <Button
31
+ type="button"
32
+ variant="ghost"
33
+ size="sm"
34
+ aria-label={`Settings for ${name}`}
35
+ className={APP_ROW_ACTION_CLASS}
36
+ >
37
+ <IconSettings size={15} />
38
+ </Button>
39
+ </DropdownMenuTrigger>
40
+ </TooltipTrigger>
41
+ <TooltipContent>App settings</TooltipContent>
42
+ </Tooltip>
43
+ <DropdownMenuContent
44
+ align="end"
45
+ className={APP_ACTION_MENU_CONTENT_CLASS}
46
+ >
47
+ <DropdownMenuItem onSelect={onAddApp}>
48
+ <IconPlus size={14} aria-hidden="true" />
49
+ {addAppLabel}
50
+ </DropdownMenuItem>
51
+ </DropdownMenuContent>
52
+ </DropdownMenu>
53
+ );
54
+ }
@@ -3,7 +3,6 @@ import {
3
3
  IconAlertTriangle,
4
4
  IconClock,
5
5
  IconExternalLink,
6
- IconLoader2,
7
6
  } from "@tabler/icons-react";
8
7
  import { Link } from "react-router";
9
8
 
@@ -21,6 +20,7 @@ import {
21
20
  DialogHeader,
22
21
  DialogTitle,
23
22
  } from "./ui/dialog";
23
+ import { Skeleton } from "./ui/skeleton";
24
24
 
25
25
  interface AutomationRun {
26
26
  id: string;
@@ -178,9 +178,13 @@ export function AutomationRunHistoryDialog({
178
178
 
179
179
  <div className="mt-3">
180
180
  {runsQuery.isLoading ? (
181
- <div className="flex items-center gap-2 rounded-lg border p-4 text-sm text-muted-foreground">
182
- <IconLoader2 className="size-4 animate-spin" />
183
- Loading run history...
181
+ <div className="space-y-3 rounded-lg border p-4">
182
+ {Array.from({ length: 3 }).map((_, index) => (
183
+ <div key={index} className="space-y-2">
184
+ <Skeleton className="h-4 w-2/3" />
185
+ <Skeleton className="h-3 w-1/2" />
186
+ </div>
187
+ ))}
184
188
  </div>
185
189
  ) : runsQuery.isError ? (
186
190
  <ActionQueryError
@@ -31,7 +31,6 @@ export function ConnectedAppCard({
31
31
  campaign: "product",
32
32
  content: "dispatch_app",
33
33
  })}
34
- target="_blank"
35
34
  rel="noopener noreferrer"
36
35
  showNewTabOption
37
36
  />
@@ -339,7 +339,7 @@ describe("CreateAppFlow", () => {
339
339
  });
340
340
  });
341
341
  const branchLink = Array.from(container.querySelectorAll("a")).find(
342
- (candidate) => candidate.textContent?.includes("Open Builder branch"),
342
+ (candidate) => candidate.textContent?.includes("Open in Builder"),
343
343
  );
344
344
  expect(branchLink?.getAttribute("href")).toBe(
345
345
  "https://branch.example.test",
@@ -319,7 +319,7 @@ export function CreateAppFlow({
319
319
  </div>
320
320
  <Button asChild className="w-full sm:w-auto">
321
321
  <a href={branchUrl} target="_blank" rel="noreferrer">
322
- Open Builder branch <IconArrowUpRight aria-hidden="true" />
322
+ Open in Builder <IconArrowUpRight aria-hidden="true" />
323
323
  </a>
324
324
  </Button>
325
325
  </div>
@@ -583,7 +583,7 @@ export function CreateAppFlow({
583
583
  rel="noreferrer"
584
584
  className="inline-flex items-center gap-1 font-medium text-foreground underline"
585
585
  >
586
- Open branch <IconArrowUpRight className="h-3 w-3" />
586
+ Open in Builder <IconArrowUpRight className="h-3 w-3" />
587
587
  </a>
588
588
  ) : null}
589
589
  </div>
@@ -33,6 +33,9 @@ vi.mock("@agent-native/core/client/agent-chat", () => ({
33
33
  path: string,
34
34
  options?: unknown,
35
35
  ) => clientState.navigateWithTransition(navigate, path, options),
36
+ orderChatFirstAppIds: (appIds: string[]) => appIds,
37
+ readChatFirstAppLayout: () => ({ pinnedIds: [], orderedIds: [] }),
38
+ writeChatFirstAppLayout: () => ({ ok: true }),
36
39
  useChatModels: clientState.useChatModels,
37
40
  }));
38
41
 
@@ -53,6 +56,11 @@ vi.mock("@agent-native/core/client/composer", () => ({
53
56
  },
54
57
  }));
55
58
 
59
+ vi.mock("@agent-native/core/client/application-state", () => ({
60
+ readClientAppState: vi.fn(async () => null),
61
+ writeClientAppState: vi.fn(async () => null),
62
+ }));
63
+
56
64
  vi.mock("@agent-native/core/client/hooks", () => ({
57
65
  useActionQuery: (name: string) => ({
58
66
  data:
@@ -129,6 +137,15 @@ describe("DispatchControlPlane", () => {
129
137
  expect(container.textContent).not.toContain("Open chat");
130
138
  expect(container.textContent).not.toContain("Also");
131
139
  expect(container.textContent).not.toContain("active");
140
+ expect(container.textContent).not.toContain(
141
+ "Summarize the current workspace health",
142
+ );
143
+ expect(container.textContent).toContain(
144
+ "Create an app for onboarding requests",
145
+ );
146
+ expect(container.textContent).toContain(
147
+ "Check which agents can help with analytics",
148
+ );
132
149
  expect(container.querySelector("nav")).toBeNull();
133
150
  expect(
134
151
  container.querySelector('[data-placeholder="Ask Dispatch anything..."]'),
@@ -274,16 +291,33 @@ describe("DispatchControlPlane", () => {
274
291
  expect(container.textContent).toContain("Analytics");
275
292
  expect(container.textContent).toContain("Apps");
276
293
  expect(container.textContent).toContain("New");
294
+ const viewAllLink = Array.from(container.querySelectorAll("a")).find(
295
+ (link) => link.textContent?.trim() === "View all",
296
+ );
297
+ expect(viewAllLink?.className).toContain("text-muted-foreground");
277
298
  expect(container.textContent).not.toContain("Other apps");
278
299
  expect(container.textContent).not.toContain("available");
279
300
  expect(container.textContent).not.toContain("Archived app");
280
301
  expect(container.textContent).not.toContain("Duplicate onboarding");
281
302
  expect(container.textContent).not.toContain("CRM");
282
303
  expect(
283
- Array.from(container.querySelectorAll("a")).filter((anchor) =>
284
- anchor.getAttribute("href")?.includes("onboarding"),
304
+ container.querySelectorAll(
305
+ 'button[aria-label="Open options for Onboarding"]',
285
306
  ),
286
307
  ).toHaveLength(1);
308
+ await act(async () => {
309
+ container
310
+ .querySelector<HTMLButtonElement>(
311
+ 'button[aria-label="Open options for Onboarding"]',
312
+ )
313
+ ?.dispatchEvent(
314
+ new MouseEvent("pointerdown", { bubbles: true, button: 0 }),
315
+ );
316
+ });
317
+ const onboardingNewTabLink = document.querySelector<HTMLAnchorElement>(
318
+ 'a[href="/onboarding"][target="_blank"]',
319
+ );
320
+ expect(onboardingNewTabLink).not.toBeNull();
287
321
  const clipsHref = Array.from(container.querySelectorAll("a"))
288
322
  .map((anchor) => anchor.getAttribute("href"))
289
323
  .find((href) => href?.includes("clips.agent-native.com"));