@agent-native/dispatch 0.13.8 → 0.13.10

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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Agent-Native
2
2
 
3
- ## The framework for agent-native apps
3
+ ## The framework for agentic apps
4
4
 
5
- Agent-Native is an open-source framework for apps where agents and UI share the same actions, state, and context.
5
+ Agent-Native is an open-source framework for rapidly building robust applications with agents at their core.
6
6
 
7
7
  ```ts
8
8
  // One action powers every app surface: UI, agent, HTTP, MCP, A2A, and CLI.
@@ -17,11 +17,23 @@ export default defineAction({
17
17
  });
18
18
  ```
19
19
 
20
- - **Actions**: Define work once. Use it from every app surface: UI, agent, HTTP, MCP, A2A, and CLI.
21
- - **Agent runtime**: Chat, tools, skills, memory, jobs, observability, and handoffs ship together.
22
- - **Backend agnostic**: Plug in any Drizzle-supported SQL database and Nitro-compatible host.
20
+ - **[Actions](https://agent-native.com/docs/actions)**: Define work once. Use it from every app surface: UI, agent, HTTP, MCP, A2A, and CLI.
21
+ - **[Agent runtime](https://agent-native.com/docs/agent-surfaces)**: Chat, tools, skills, memory, jobs, observability, and handoffs ship together.
22
+ - **[Backend agnostic](https://agent-native.com/docs/database)**: Plug in any Drizzle-supported SQL database and Nitro-compatible host.
23
+ - **[Toolkits](https://agent-native.com/docs/agent-native-toolkit)**: Reusable building blocks for collaboration, sharing, settings, teams, and observability.
23
24
 
24
- ## Apps
25
+ ## Don't pick between apps or agents.
26
+
27
+ Agent-native apps are both
28
+
29
+ | | SaaS Tools | Raw AI Agents | Internal Tools | Agent-Native App |
30
+ | ----------------- | ------------------ | ----------------------- | -------------------------- | ----------------------- |
31
+ | **UI** | Polished but rigid | None | Mixed quality | Full UI, fork & go |
32
+ | **AI** | Bolted on | Powerful | Shallowly connected | Agent-first, integrated |
33
+ | **Customization** | Can't | Instructions and skills | Full, but high maintenance | Agent modifies the app |
34
+ | **Ownership** | Rented | Somewhat yours | You own the code | You own the code |
35
+
36
+ ## Try an Agent-Native app
25
37
 
26
38
  Fork a working app and let the agent evolve it. **You can customize everything.**
27
39
 
@@ -33,7 +45,7 @@ Fork a working app and let the agent evolve it. **You can customize everything.*
33
45
 
34
46
  <a href="https://agent-native.com/templates/clips"><img src="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F189ebd9b2f2b4f0ead3b33138d4e4c10?format=webp&width=800" alt="Clips app" width="100%" /></a>
35
47
 
36
- **Agent-Native Loom + Jam**
48
+ **Agent-Native Loom**
37
49
 
38
50
  Record your screen with auto-transcripts and captured browser debug logs, share a link, and let an agent read the transcript, see timestamped frames, and fix the bug.
39
51
 
@@ -55,7 +67,7 @@ Install `/visual-plan` and `/visual-recap` so your coding agent can plan before
55
67
 
56
68
  <a href="https://agent-native.com/templates/design"><img src="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fe2c86908c2fa4f119ee4aa90b4823944?format=webp&width=800" alt="Design app" width="100%" /></a>
57
69
 
58
- **Agent-Native design prototyping**
70
+ **Agent-Native Figma**
59
71
 
60
72
  Generate interactive HTML prototypes, compare variants, refine controls, and export the result.
61
73
 
@@ -68,37 +80,37 @@ Generate interactive HTML prototypes, compare variants, refine controls, and exp
68
80
 
69
81
  <a href="https://agent-native.com/templates/content"><img src="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F89bcfc6106304bfbaf8ec8a7ccd721eb?format=webp&width=800" alt="Content app" width="100%" /></a>
70
82
 
71
- **Open-source Obsidian for MDX**
83
+ **Agent-Native Notion/Obsidian**
72
84
 
73
85
  Edit local Markdown/MDX files, generate rich interactive custom blocks, and draft, rewrite, or publish with an agent.
74
86
 
75
87
  </td>
76
88
  <td width="33%" align="center" valign="top">
77
89
 
78
- **Slides**
90
+ **Analytics**
79
91
 
80
- <a href="https://agent-native.com/templates/slides"><img src="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F2c09b451d40c4a74a89a38d69170c2d8?format=webp&width=800" alt="Slides app" width="100%" /></a>
92
+ <a href="https://agent-native.com/templates/analytics"><img src="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F4933a80cc3134d7e874631f688be828a?format=webp&width=800" alt="Analytics app" width="100%" /></a>
81
93
 
82
- **Agent-Native Google Slides, Pitch**
94
+ **Agent-Native Amplitude, Fullstory**
83
95
 
84
- Generate and edit React-based presentations via prompt or point-and-click.
96
+ Connect analytics data sources, prompt for real charts, and build reusable dashboards.
85
97
 
86
98
  </td>
87
99
  <td width="33%" align="center" valign="top">
88
100
 
89
- **Analytics**
101
+ **Chat**
90
102
 
91
- <a href="https://agent-native.com/templates/analytics"><img src="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F4933a80cc3134d7e874631f688be828a?format=webp&width=800" alt="Analytics app" width="100%" /></a>
103
+ <a href="https://agent-native.com/templates/chat"><img src="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F6b36dc596fca4799815fa34c31e1c406?format=webp&width=800" alt="Chat app" width="100%" /></a>
92
104
 
93
- **Agent-Native Amplitude, Mixpanel**
105
+ **A minimal ChatGPT-style app for your own agent**
94
106
 
95
- Connect analytics data sources, prompt for real charts, and build reusable dashboards.
107
+ Chat-first app scaffold with durable threads, actions, auth, live sync, and a clean path to add screens or plug in your own agent backend.
96
108
 
97
109
  </td>
98
110
  </tr>
99
111
  </table>
100
112
 
101
- View the full app gallery at **[agent-native.com/templates](https://agent-native.com/templates)**.
113
+ View the full app gallery at **[agent-native.com/apps](https://agent-native.com/apps)**, or build from scratch with the **[framework guide](https://agent-native.com/docs/getting-started)**.
102
114
 
103
115
  ## Quick Start
104
116
 
@@ -111,12 +123,6 @@ pnpm install
111
123
  pnpm dev
112
124
  ```
113
125
 
114
- `create` first asks how you want to start:
115
-
116
- - **Full app(s)**: clone one or more complete apps into a workspace. Pick Mail + Calendar + Forms and you get all three wired up and sharing auth.
117
- - **Chat**: a single app with a minimal chat UI and the browser shell already wired, the simplest way to get a UI.
118
- - **Headless**: a single action-first app with no UI shell. The CLI walks you through calling your first action and agent, and you can add a UI later.
119
-
120
126
  Prefer flags? `create my-app --template mail`, `--headless`, or `--standalone` skip the prompt.
121
127
 
122
128
  See the full [getting started docs](https://agent-native.com/docs).
@@ -1,18 +1,6 @@
1
1
  declare const _default: import("@agent-native/core").ActionDefinition<{
2
2
  id: string;
3
3
  }, {
4
- id: string;
5
- ownerEmail: string;
6
- orgId: string | null;
7
- name: string;
8
- platform: string;
9
- destination: string;
10
- threadRef: string | null;
11
- notes: string | null;
12
- createdBy: string;
13
- createdAt: number;
14
- updatedAt: number;
15
- } | {
16
4
  id: string;
17
5
  ownerEmail: string;
18
6
  orgId: string | null;
@@ -29,6 +17,18 @@ declare const _default: import("@agent-native/core").ActionDefinition<{
29
17
  reviewedAt: number | null;
30
18
  createdAt: number;
31
19
  updatedAt: number;
20
+ } | {
21
+ id: string;
22
+ ownerEmail: string;
23
+ orgId: string | null;
24
+ name: string;
25
+ platform: string;
26
+ destination: string;
27
+ threadRef: string | null;
28
+ notes: string | null;
29
+ createdBy: string;
30
+ createdAt: number;
31
+ updatedAt: number;
32
32
  }>;
33
33
  export default _default;
34
34
  //# sourceMappingURL=delete-destination.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch-control-plane.d.ts","sourceRoot":"","sources":["../../src/components/dispatch-control-plane.tsx"],"names":[],"mappings":"AA8oCA,wBAAgB,oBAAoB,oDAoHnC"}
1
+ {"version":3,"file":"dispatch-control-plane.d.ts","sourceRoot":"","sources":["../../src/components/dispatch-control-plane.tsx"],"names":[],"mappings":"AA0KA,wBAAgB,oBAAoB,oDAoBnC"}
@@ -1,209 +1,20 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { PromptComposer, agentNativePath, isInBuilderFrame, useActionQuery, useChangeVersions, useChatModels, useChatThreads, } from "@agent-native/core/client";
3
- import { RunsTray } from "@agent-native/core/client/progress";
4
- import { IconActivity, IconAlertTriangle, IconArrowUpRight, IconBolt, IconBroadcast, IconCheck, IconListCheck, IconMessages, IconPlayerPlay, IconPlugConnected, IconPlus, IconRobot, IconRocket, IconSettingsAutomation, IconShieldCheck, IconStack3, IconUsersGroup, } from "@tabler/icons-react";
5
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
6
- import { useEffect, useMemo, useState } from "react";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PromptComposer, isInBuilderFrame, useActionQuery, useChatModels, } from "@agent-native/core/client";
3
+ import { IconArrowUpRight, IconBroadcast, IconStack3, } from "@tabler/icons-react";
7
4
  import { Link, useNavigate } from "react-router";
8
- import { toast } from "sonner";
9
5
  import { CreateAppPopover } from "../components/create-app-popover.js";
10
6
  import { DispatchShell } from "../components/dispatch-shell.js";
11
- import { Badge } from "../components/ui/badge.js";
12
7
  import { Button } from "../components/ui/button.js";
13
8
  import { Skeleton } from "../components/ui/skeleton.js";
14
- import { Switch } from "../components/ui/switch.js";
15
9
  import { WorkspaceAppCard } from "../components/workspace-app-card.js";
16
- import { listDispatchAutomations, setDispatchAutomationEnabled, } from "../lib/automations.js";
17
10
  import { submitOverviewPrompt } from "../lib/overview-chat.js";
18
- import { cn } from "../lib/utils.js";
19
- const ZERO_TASK_QUEUE_STATS = {
20
- pending: 0,
21
- processing: 0,
22
- completed_last_hour: 0,
23
- failed_last_hour: 0,
24
- oldest_pending_age_seconds: 0,
25
- recent_failures: [],
26
- };
27
11
  const PROMPT_SUGGESTIONS = [
28
12
  "Summarize the current workspace health",
29
13
  "Create an app for onboarding requests",
30
14
  "Check which agents can help with analytics",
31
15
  ];
32
- const AUTOMATIONS_QUERY_KEY = [
33
- "dispatch-control-plane",
34
- "automations",
35
- ];
36
- function formatNumber(value) {
37
- return new Intl.NumberFormat(undefined, {
38
- notation: value >= 10_000 ? "compact" : "standard",
39
- maximumFractionDigits: value >= 10_000 ? 1 : 0,
40
- }).format(value);
41
- }
42
- function formatAgeSeconds(seconds) {
43
- if (!seconds || seconds < 0)
44
- return "0s";
45
- if (seconds < 60)
46
- return `${Math.floor(seconds)}s`;
47
- if (seconds < 3600)
48
- return `${Math.floor(seconds / 60)}m`;
49
- return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;
50
- }
51
- function timeAgo(value) {
52
- if (value == null || value === "")
53
- return "never";
54
- const timestamp = typeof value === "number" ? value : new Date(value).getTime();
55
- if (!Number.isFinite(timestamp))
56
- return "never";
57
- const diff = Math.max(0, Date.now() - timestamp);
58
- if (diff < 60_000)
59
- return "now";
60
- if (diff < 3_600_000)
61
- return `${Math.floor(diff / 60_000)}m`;
62
- if (diff < 86_400_000)
63
- return `${Math.floor(diff / 3_600_000)}h`;
64
- return `${Math.floor(diff / 86_400_000)}d`;
65
- }
66
- function relativeRunTime(value) {
67
- if (!value)
68
- return "never";
69
- const timestamp = new Date(value).getTime();
70
- if (!Number.isFinite(timestamp))
71
- return "never";
72
- const diff = timestamp - Date.now();
73
- const abs = Math.abs(diff);
74
- const suffix = diff >= 0 ? "from now" : "ago";
75
- if (abs < 60_000)
76
- return diff >= 0 ? "soon" : "now";
77
- if (abs < 3_600_000)
78
- return `${Math.floor(abs / 60_000)}m ${suffix}`;
79
- if (abs < 86_400_000)
80
- return `${Math.floor(abs / 3_600_000)}h ${suffix}`;
81
- return `${Math.floor(abs / 86_400_000)}d ${suffix}`;
82
- }
83
- function dateTimeTitle(value) {
84
- if (!value)
85
- return undefined;
86
- const timestamp = new Date(value).getTime();
87
- if (!Number.isFinite(timestamp))
88
- return undefined;
89
- return new Intl.DateTimeFormat(undefined, {
90
- dateStyle: "medium",
91
- timeStyle: "short",
92
- }).format(timestamp);
93
- }
94
- function threadUpdatedAt(thread) {
95
- return Number.isFinite(thread.updatedAt)
96
- ? thread.updatedAt
97
- : Number.isFinite(thread.createdAt)
98
- ? thread.createdAt
99
- : 0;
100
- }
101
- function threadTitle(thread) {
102
- return thread.title || thread.preview || "New chat";
103
- }
104
- function automationTarget(item) {
105
- if (item.triggerType === "event" && item.event)
106
- return item.event;
107
- if (item.scheduleDescription)
108
- return item.scheduleDescription;
109
- if (item.schedule)
110
- return item.schedule;
111
- return item.triggerType || "schedule";
112
- }
113
- function automationLastRun(item) {
114
- return item.lastRun ? relativeRunTime(item.lastRun) : "never";
115
- }
116
- function automationNextRun(item) {
117
- if (!item.enabled)
118
- return "paused";
119
- if (item.triggerType === "event")
120
- return "on event";
121
- return item.nextRun ? relativeRunTime(item.nextRun) : "not scheduled";
122
- }
123
- function automationStatus(item) {
124
- if (!item.enabled)
125
- return { label: "Paused", tone: "muted" };
126
- if (item.lastStatus === "error")
127
- return { label: "Error", tone: "danger" };
128
- if (item.lastStatus === "running")
129
- return { label: "Running", tone: "warning" };
130
- if (item.lastStatus === "skipped")
131
- return { label: "Skipped", tone: "warning" };
132
- if (item.lastStatus === "success")
133
- return { label: "Healthy", tone: "success" };
134
- return { label: "Ready", tone: "default" };
135
- }
136
- function automationIdentity(item) {
137
- return `${item.owner}:${item.path}`;
138
- }
139
- function StatusDot({ tone = "default", }) {
140
- return (_jsx("span", { className: cn("size-2 rounded-full", tone === "success" && "bg-emerald-500", tone === "warning" && "bg-amber-500", tone === "danger" && "bg-destructive", tone === "muted" && "bg-muted-foreground/35", tone === "default" && "bg-primary") }));
141
- }
142
- function useAutomationsStatus() {
143
- const version = useChangeVersions(["action", "screen-refresh"]);
144
- return useQuery({
145
- queryKey: [...AUTOMATIONS_QUERY_KEY, version],
146
- queryFn: listDispatchAutomations,
147
- placeholderData: (prev) => prev,
148
- refetchInterval: 15_000,
149
- staleTime: 5_000,
150
- });
151
- }
152
- function useToggleAutomation() {
153
- const queryClient = useQueryClient();
154
- return useMutation({
155
- mutationFn: setDispatchAutomationEnabled,
156
- onMutate: async (input) => {
157
- await queryClient.cancelQueries({ queryKey: AUTOMATIONS_QUERY_KEY });
158
- const snapshots = queryClient.getQueriesData({
159
- queryKey: AUTOMATIONS_QUERY_KEY,
160
- });
161
- queryClient.setQueriesData({ queryKey: AUTOMATIONS_QUERY_KEY }, (rows) => rows?.map((item) => automationIdentity(item) === automationIdentity(input)
162
- ? { ...item, enabled: input.enabled }
163
- : item));
164
- return { snapshots };
165
- },
166
- onError: (err, _input, context) => {
167
- for (const [queryKey, data] of context?.snapshots ?? []) {
168
- queryClient.setQueryData(queryKey, data);
169
- }
170
- toast.error(`Could not update automation: ${err instanceof Error ? err.message : String(err)}`);
171
- },
172
- onSuccess: (updated) => {
173
- queryClient.setQueriesData({ queryKey: AUTOMATIONS_QUERY_KEY }, (rows) => rows?.map((item) => automationIdentity(item) === automationIdentity(updated)
174
- ? updated
175
- : item));
176
- },
177
- onSettled: () => {
178
- void queryClient.invalidateQueries({ queryKey: AUTOMATIONS_QUERY_KEY });
179
- },
180
- });
181
- }
182
- function useTaskQueueStats() {
183
- return useQuery({
184
- queryKey: ["dispatch-control-plane", "task-queue"],
185
- queryFn: async () => {
186
- const res = await fetch(agentNativePath("/_agent-native/integrations/task-queue/status"));
187
- if (!res.ok)
188
- return ZERO_TASK_QUEUE_STATS;
189
- const stats = await res.json();
190
- if (!stats || typeof stats !== "object")
191
- return ZERO_TASK_QUEUE_STATS;
192
- return {
193
- pending: Number(stats.pending ?? 0),
194
- processing: Number(stats.processing ?? 0),
195
- completed_last_hour: Number(stats.completed_last_hour ?? 0),
196
- failed_last_hour: Number(stats.failed_last_hour ?? 0),
197
- oldest_pending_age_seconds: Number(stats.oldest_pending_age_seconds ?? 0),
198
- recent_failures: Array.isArray(stats.recent_failures)
199
- ? stats.recent_failures
200
- : [],
201
- };
202
- },
203
- placeholderData: (prev) => prev,
204
- refetchInterval: 15_000,
205
- staleTime: 5_000,
206
- });
16
+ function SectionHeader({ icon: Icon, title, detail, action, }) {
17
+ return (_jsxs("div", { className: "flex min-w-0 items-center justify-between gap-3", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(Icon, { size: 16, className: "shrink-0 text-muted-foreground" }), _jsxs("div", { className: "min-w-0", children: [_jsx("h2", { className: "truncate text-sm font-semibold text-foreground", children: title }), detail ? (_jsx("p", { className: "mt-0.5 truncate text-xs text-muted-foreground", children: detail })) : null] })] }), action ? _jsx("div", { className: "shrink-0", children: action }) : null] }));
207
18
  }
208
19
  function CommandPanel() {
209
20
  const { selectedModel } = useChatModels();
@@ -228,205 +39,13 @@ function CommandPanel() {
228
39
  }
229
40
  return (_jsxs("section", { className: "rounded-lg border bg-card p-4", children: [_jsxs("div", { className: "mb-3 flex items-center justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(IconBroadcast, { size: 16, className: "text-muted-foreground" }), _jsx("h2", { className: "text-sm font-semibold text-foreground", children: "Ask Dispatch" })] }), _jsx(Button, { variant: "outline", size: "sm", asChild: true, children: _jsxs(Link, { to: "/chat", children: ["Open chat", _jsx(IconArrowUpRight, { size: 14 })] }) })] }), _jsx(PromptComposer, { placeholder: "Route work, inspect status, or create an app...", onSubmit: (text) => send(text) }), _jsx("div", { className: "mt-3 flex flex-wrap gap-2", children: PROMPT_SUGGESTIONS.map((suggestion) => (_jsx("button", { type: "button", onClick: () => send(suggestion), className: "cursor-pointer rounded-md border border-border bg-background px-2.5 py-1.5 text-xs text-muted-foreground transition hover:border-foreground/30 hover:text-foreground", children: suggestion }, suggestion))) })] }));
230
41
  }
231
- function MetricCard({ label, value, detail, icon: Icon, to, tone = "default", }) {
232
- const body = (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-2 text-xs font-medium text-muted-foreground", children: [_jsx(Icon, { size: 15 }), _jsx("span", { className: "truncate", children: label })] }), _jsx(StatusDot, { tone: tone === "success"
233
- ? "success"
234
- : tone === "warning"
235
- ? "warning"
236
- : tone === "danger"
237
- ? "danger"
238
- : "default" })] }), _jsx("div", { className: "mt-3 text-2xl font-semibold tracking-tight text-foreground", children: value }), _jsx("div", { className: "mt-1 truncate text-xs text-muted-foreground", children: detail })] }));
239
- const className = cn("block min-w-0 rounded-lg border bg-card p-4 transition", to && "hover:border-foreground/30 hover:bg-muted/20");
240
- if (to) {
241
- return (_jsx(Link, { to: to, className: className, children: body }));
242
- }
243
- return _jsx("div", { className: className, children: body });
244
- }
245
- function ControlPlaneMetrics({ overview, apps, agents, automations, threads, taskQueue, }) {
246
- const activeApps = apps.filter((app) => !app.isDispatch && !app.archived);
247
- const pendingApps = activeApps.filter((app) => app.status === "pending");
248
- const enabledAutomations = automations.filter((item) => item.enabled);
249
- const automationErrors = automations.filter((item) => item.enabled && item.lastStatus === "error");
250
- const pendingApprovals = overview?.counts?.pendingApprovals ?? 0;
251
- const activeRuns = taskQueue.processing;
252
- return (_jsxs("section", { className: "grid gap-3 sm:grid-cols-2 xl:grid-cols-6", children: [_jsx(MetricCard, { label: "Chats", value: formatNumber(threads.length), detail: `${threads.filter((thread) => thread.messageCount > 0).length} with messages`, icon: IconMessages, to: "/chat" }), _jsx(MetricCard, { label: "Runs", value: formatNumber(activeRuns), detail: `${formatNumber(taskQueue.pending)} queued`, icon: IconPlayerPlay, tone: taskQueue.failed_last_hour > 0
253
- ? "danger"
254
- : taskQueue.pending > 5
255
- ? "warning"
256
- : "default" }), _jsx(MetricCard, { label: "Apps", value: formatNumber(activeApps.length), detail: `${formatNumber(pendingApps.length)} building`, icon: IconStack3, to: "/apps", tone: pendingApps.length > 0 ? "warning" : "default" }), _jsx(MetricCard, { label: "Agents", value: formatNumber(agents.length), detail: `${formatNumber(agents.filter((agent) => agent.source === "custom").length)} custom`, icon: IconRobot, to: "/agents" }), _jsx(MetricCard, { label: "Automations", value: formatNumber(enabledAutomations.length), detail: `${formatNumber(automationErrors.length)} need attention`, icon: IconSettingsAutomation, tone: automationErrors.length > 0 ? "danger" : "success" }), _jsx(MetricCard, { label: "Approvals", value: formatNumber(pendingApprovals), detail: overview?.settings?.enabled ? "review mode" : "immediate changes", icon: IconShieldCheck, to: "/approvals", tone: pendingApprovals > 0 ? "warning" : "default" })] }));
257
- }
258
- function SectionHeader({ icon: Icon, title, detail, action, }) {
259
- return (_jsxs("div", { className: "flex min-w-0 items-center justify-between gap-3", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(Icon, { size: 16, className: "shrink-0 text-muted-foreground" }), _jsxs("div", { className: "min-w-0", children: [_jsx("h2", { className: "truncate text-sm font-semibold text-foreground", children: title }), detail ? (_jsx("p", { className: "mt-0.5 truncate text-xs text-muted-foreground", children: detail })) : null] })] }), action ? _jsx("div", { className: "shrink-0", children: action }) : null] }));
260
- }
261
- function RecentChatsPanel() {
262
- const navigate = useNavigate();
263
- const { threads, activeThreadId, createThread, switchThread } = useChatThreads(undefined, undefined, undefined, { autoCreate: false });
264
- const [creating, setCreating] = useState(false);
265
- const visibleThreads = useMemo(() => threads
266
- .filter((thread) => thread.messageCount > 0 || thread.id === activeThreadId)
267
- .sort((a, b) => threadUpdatedAt(b) - threadUpdatedAt(a))
268
- .slice(0, 5), [activeThreadId, threads]);
269
- function openThread(threadId, isNew = false) {
270
- switchThread(threadId);
271
- navigate("/chat", {
272
- state: {
273
- dispatchThread: {
274
- id: `${Date.now()}-${threadId}`,
275
- threadId,
276
- },
277
- },
278
- });
279
- window.requestAnimationFrame(() => {
280
- window.dispatchEvent(new CustomEvent("agent-chat:open-thread", {
281
- detail: { threadId, newThread: isNew },
282
- }));
283
- });
284
- }
285
- async function handleNewChat() {
286
- setCreating(true);
287
- try {
288
- const threadId = await createThread();
289
- if (threadId)
290
- openThread(threadId, true);
291
- }
292
- finally {
293
- setCreating(false);
294
- }
295
- }
296
- return (_jsxs("section", { className: "rounded-lg border bg-card p-4", children: [_jsx(SectionHeader, { icon: IconMessages, title: "Chats", detail: `${visibleThreads.length} recent`, action: _jsxs(Button, { variant: "outline", size: "sm", onClick: handleNewChat, disabled: creating, children: [_jsx(IconPlus, { size: 14 }), "New"] }) }), _jsx("div", { className: "mt-3 divide-y rounded-md border", children: visibleThreads.length > 0 ? (visibleThreads.map((thread) => (_jsxs("button", { type: "button", onClick: () => openThread(thread.id), className: "grid w-full grid-cols-[minmax(0,1fr)_auto] gap-3 px-3 py-2.5 text-left transition hover:bg-muted/40", children: [_jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: "block truncate text-sm font-medium text-foreground", children: threadTitle(thread) }), _jsx("span", { className: "mt-0.5 block truncate text-xs text-muted-foreground", children: thread.preview || `${thread.messageCount} messages` })] }), _jsx("span", { className: "text-[11px] text-muted-foreground", children: timeAgo(threadUpdatedAt(thread)) })] }, thread.id)))) : (_jsx("button", { type: "button", onClick: handleNewChat, className: "block w-full px-3 py-8 text-center text-sm text-muted-foreground transition hover:bg-muted/40", children: "Start a Dispatch chat" })) })] }));
297
- }
298
- function RunsPanel({ taskQueue }) {
299
- const hasFailure = taskQueue.failed_last_hour > 0;
300
- const hasBacklog = taskQueue.pending > 5 || taskQueue.oldest_pending_age_seconds > 300;
301
- return (_jsxs("section", { className: "rounded-lg border bg-card p-4", children: [_jsx(SectionHeader, { icon: IconPlayerPlay, title: "Runs", detail: hasFailure
302
- ? `${taskQueue.failed_last_hour} failed in the last hour`
303
- : `${taskQueue.processing} processing`, action: _jsx(RunsTray, { triggerVariant: "pill", hideWhenIdle: false, showRecent: true, limit: 8 }) }), _jsxs("div", { className: "mt-3 grid grid-cols-4 gap-2", children: [_jsx(QueueCell, { label: "Queued", value: taskQueue.pending }), _jsx(QueueCell, { label: "Active", value: taskQueue.processing }), _jsx(QueueCell, { label: "Done 1h", value: taskQueue.completed_last_hour }), _jsx(QueueCell, { label: "Failed 1h", value: taskQueue.failed_last_hour, danger: hasFailure })] }), _jsxs("div", { className: cn("mt-3 rounded-md border px-3 py-2 text-xs", hasFailure
304
- ? "border-destructive/30 bg-destructive/5 text-destructive"
305
- : hasBacklog
306
- ? "border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300"
307
- : "bg-muted/20 text-muted-foreground"), children: ["Oldest queued: ", formatAgeSeconds(taskQueue.oldest_pending_age_seconds)] }), taskQueue.recent_failures.length > 0 ? (_jsx("div", { className: "mt-3 divide-y rounded-md border", children: taskQueue.recent_failures.slice(0, 3).map((failure) => (_jsxs("div", { className: "px-3 py-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-3 text-xs", children: [_jsx("span", { className: "font-medium text-foreground", children: failure.platform }), _jsxs("span", { className: "text-muted-foreground", children: [failure.attempts, " attempts"] })] }), _jsx("div", { className: "mt-1 truncate text-xs text-muted-foreground", children: failure.error || "(no error message)" })] }, failure.id))) })) : null] }));
308
- }
309
- function QueueCell({ label, value, danger, }) {
310
- return (_jsxs("div", { className: "rounded-md border bg-background px-2 py-2", children: [_jsx("div", { className: cn("text-lg font-semibold text-foreground", danger && "text-destructive"), children: formatNumber(value) }), _jsx("div", { className: "truncate text-[11px] text-muted-foreground", children: label })] }));
311
- }
312
42
  function AppsPanel({ apps, isLoading, }) {
313
- const visibleApps = apps
314
- .filter((app) => !app.isDispatch && !app.archived)
315
- .slice(0, 4);
43
+ const visibleApps = apps.filter((app) => !app.isDispatch && !app.archived);
316
44
  const showSkeletons = isLoading && visibleApps.length === 0;
317
- return (_jsxs("section", { className: "space-y-3", children: [_jsx(SectionHeader, { icon: IconStack3, title: "Projects and apps", detail: `${apps.filter((app) => !app.isDispatch && !app.archived).length} active`, action: _jsx(Button, { variant: "outline", size: "sm", asChild: true, children: _jsxs(Link, { to: "/apps", children: ["View all", _jsx(IconArrowUpRight, { size: 14 })] }) }) }), showSkeletons ? (_jsx("div", { className: "grid gap-3 md:grid-cols-2", children: Array.from({ length: 4 }).map((_, index) => (_jsxs("div", { className: "rounded-lg border bg-card p-4", children: [_jsx(Skeleton, { className: "h-4 w-32" }), _jsx(Skeleton, { className: "mt-3 h-3 w-24" }), _jsx(Skeleton, { className: "mt-3 h-3 w-full" })] }, index))) })) : visibleApps.length > 0 ? (_jsx("div", { className: "grid gap-3 md:grid-cols-2", children: visibleApps.map((app) => (_jsx(WorkspaceAppCard, { app: app, className: "min-h-32" }, app.id))) })) : (_jsx(CreateAppPopover, {}))] }));
318
- }
319
- function AutomationsPanel({ automations, isLoading, }) {
320
- const toggleAutomation = useToggleAutomation();
321
- const ordered = useMemo(() => [...automations].sort((a, b) => {
322
- const aError = a.enabled && a.lastStatus === "error" ? 1 : 0;
323
- const bError = b.enabled && b.lastStatus === "error" ? 1 : 0;
324
- if (aError !== bError)
325
- return bError - aError;
326
- return (b.lastRun || "").localeCompare(a.lastRun || "");
327
- }), [automations]);
328
- const enabled = automations.filter((item) => item.enabled).length;
329
- const errors = automations.filter((item) => item.enabled && item.lastStatus === "error").length;
330
- const pendingToggleIdentity = toggleAutomation.isPending
331
- ? toggleAutomation.variables
332
- ? automationIdentity(toggleAutomation.variables)
333
- : null
334
- : null;
335
- function handleToggle(item, enabled) {
336
- toggleAutomation.mutate({
337
- owner: item.owner,
338
- path: item.path,
339
- enabled,
340
- });
341
- }
342
- return (_jsxs("section", { className: "rounded-lg border bg-card p-4", children: [_jsx(SectionHeader, { icon: IconSettingsAutomation, title: "Automations", detail: `${enabled} enabled · ${errors} errors`, action: _jsx(Button, { variant: "outline", size: "sm", asChild: true, children: _jsxs(Link, { to: "/chat", children: ["Create", _jsx(IconArrowUpRight, { size: 14 })] }) }) }), _jsx("div", { className: "mt-3 divide-y rounded-md border", children: isLoading && ordered.length === 0 ? (Array.from({ length: 4 }).map((_, index) => (_jsxs("div", { className: "px-3 py-2.5", children: [_jsx(Skeleton, { className: "h-4 w-40" }), _jsx(Skeleton, { className: "mt-2 h-3 w-28" })] }, index)))) : ordered.length > 0 ? (ordered.slice(0, 6).map((item) => {
343
- const status = automationStatus(item);
344
- const canUpdate = item.canUpdate !== false;
345
- const isToggling = pendingToggleIdentity === automationIdentity(item);
346
- return (_jsxs("div", { className: "grid grid-cols-[minmax(0,1fr)_auto] gap-3 px-3 py-2.5", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(StatusDot, { tone: status.tone }), _jsx("span", { className: "truncate text-sm font-medium text-foreground", children: item.name })] }), _jsx("div", { className: "mt-1 truncate text-xs text-muted-foreground", children: automationTarget(item) }), _jsxs("div", { className: "mt-1 flex flex-wrap gap-x-3 gap-y-1 text-[11px] text-muted-foreground", children: [_jsxs("span", { title: dateTimeTitle(item.lastRun), children: ["Last ", automationLastRun(item)] }), _jsxs("span", { title: dateTimeTitle(item.nextRun), children: ["Next ", automationNextRun(item)] })] }), item.lastError ? (_jsx("div", { className: "mt-1 truncate text-xs text-destructive", children: item.lastError })) : null] }), _jsxs("div", { className: "flex flex-col items-end gap-1", children: [_jsx(Badge, { variant: status.tone === "danger" ? "destructive" : "outline", className: "h-5", children: status.label }), _jsx(Switch, { checked: !!item.enabled, disabled: !canUpdate || isToggling, "aria-label": `${item.enabled ? "Disable" : "Enable"} automation ${item.name}`, onCheckedChange: (checked) => handleToggle(item, checked) })] })] }, item.id));
347
- })) : (_jsx("div", { className: "px-3 py-8 text-center text-sm text-muted-foreground", children: "No automations yet" })) })] }));
348
- }
349
- function AgentsPanelSummary({ agents }) {
350
- const builtin = agents.filter((agent) => agent.source === "builtin");
351
- const extra = agents.filter((agent) => agent.source !== "builtin");
352
- return (_jsxs("section", { className: "rounded-lg border bg-card p-4", children: [_jsx(SectionHeader, { icon: IconPlugConnected, title: "Agents", detail: `${builtin.length} built in · ${extra.length} added`, action: _jsx(Button, { variant: "outline", size: "sm", asChild: true, children: _jsxs(Link, { to: "/agents", children: ["Manage", _jsx(IconArrowUpRight, { size: 14 })] }) }) }), _jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [agents.slice(0, 12).map((agent) => (_jsxs("span", { className: "inline-flex max-w-full items-center gap-2 rounded-md border bg-background px-2.5 py-1.5 text-xs text-muted-foreground", children: [_jsx("span", { className: "size-2 shrink-0 rounded-full", style: { backgroundColor: agent.color || "hsl(var(--primary))" } }), _jsx("span", { className: "truncate", children: agent.name })] }, agent.id))), agents.length === 0 ? (_jsx("div", { className: "w-full rounded-md border border-dashed px-3 py-6 text-center text-sm text-muted-foreground", children: "No agents detected" })) : null] })] }));
353
- }
354
- function ApprovalsAndAuditPanel({ overview, isLoading, }) {
355
- const approvals = overview?.recentApprovals ?? [];
356
- const audit = overview?.recentAudit ?? [];
357
- return (_jsxs("section", { className: "rounded-lg border bg-card p-4", children: [_jsx(SectionHeader, { icon: IconActivity, title: "Activity", detail: `${approvals.length} approvals · ${audit.length} audit rows`, action: _jsx(Button, { variant: "outline", size: "sm", asChild: true, children: _jsxs(Link, { to: "/audit", children: ["Audit", _jsx(IconArrowUpRight, { size: 14 })] }) }) }), _jsxs("div", { className: "mt-3 grid gap-3 lg:grid-cols-2", children: [_jsxs("div", { className: "rounded-md border", children: [_jsxs("div", { className: "flex items-center justify-between gap-3 border-b px-3 py-2", children: [_jsx("div", { className: "text-xs font-medium uppercase text-muted-foreground", children: "Approvals" }), _jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsx(Link, { to: "/approvals", children: "Open" }) })] }), _jsx("div", { className: "divide-y", children: isLoading && approvals.length === 0 ? (Array.from({ length: 3 }).map((_, index) => (_jsxs("div", { className: "px-3 py-2.5", children: [_jsx(Skeleton, { className: "h-4 w-40" }), _jsx(Skeleton, { className: "mt-2 h-3 w-24" })] }, index)))) : approvals.length > 0 ? (approvals.slice(0, 4).map((approval) => (_jsxs("div", { className: "px-3 py-2.5", children: [_jsx("div", { className: "truncate text-sm font-medium text-foreground", children: approval.summary }), _jsxs("div", { className: "mt-1 flex items-center gap-2 text-xs text-muted-foreground", children: [_jsx(StatusDot, { tone: approval.status === "pending" ? "warning" : "muted" }), _jsx("span", { children: approval.status })] })] }, approval.id)))) : (_jsx("div", { className: "px-3 py-8 text-center text-sm text-muted-foreground", children: "No approval requests" })) })] }), _jsxs("div", { className: "rounded-md border", children: [_jsx("div", { className: "border-b px-3 py-2 text-xs font-medium uppercase text-muted-foreground", children: "Recent audit" }), _jsx("div", { className: "divide-y", children: isLoading && audit.length === 0 ? (Array.from({ length: 3 }).map((_, index) => (_jsxs("div", { className: "px-3 py-2.5", children: [_jsx(Skeleton, { className: "h-4 w-44" }), _jsx(Skeleton, { className: "mt-2 h-3 w-28" })] }, index)))) : audit.length > 0 ? (audit.slice(0, 4).map((event) => (_jsxs("div", { className: "px-3 py-2.5", children: [_jsx("div", { className: "truncate text-sm font-medium text-foreground", children: event.summary }), _jsxs("div", { className: "mt-1 truncate text-xs text-muted-foreground", children: [event.actor, " \u00B7 ", timeAgo(event.createdAt)] })] }, event.id)))) : (_jsx("div", { className: "px-3 py-8 text-center text-sm text-muted-foreground", children: "No audit entries" })) })] })] })] }));
358
- }
359
- function ReadinessPanel({ overview, apps, agents, automations, }) {
360
- const rows = [
361
- {
362
- label: "Vault",
363
- detail: (overview?.vault?.secretCount ?? 0) > 0
364
- ? `${overview?.vault?.secretCount ?? 0} secrets`
365
- : "no secrets",
366
- ok: (overview?.vault?.secretCount ?? 0) > 0,
367
- to: "/vault",
368
- icon: IconShieldCheck,
369
- },
370
- {
371
- label: "Apps",
372
- detail: `${apps.filter((app) => !app.isDispatch && !app.archived).length} active`,
373
- ok: apps.some((app) => !app.isDispatch && !app.archived),
374
- to: "/apps",
375
- icon: IconRocket,
376
- },
377
- {
378
- label: "Agents",
379
- detail: `${agents.length} available`,
380
- ok: agents.length > 0,
381
- to: "/agents",
382
- icon: IconRobot,
383
- },
384
- {
385
- label: "Automations",
386
- detail: `${automations.filter((item) => item.enabled).length} enabled`,
387
- ok: automations.every((item) => !item.enabled || item.lastStatus !== "error"),
388
- to: "/chat",
389
- icon: IconBolt,
390
- },
391
- {
392
- label: "Team",
393
- detail: `${overview?.counts?.linkedIdentities ?? 0} linked identities`,
394
- ok: (overview?.counts?.linkedIdentities ?? 0) > 0,
395
- to: "/settings#team",
396
- icon: IconUsersGroup,
397
- },
398
- ];
399
- return (_jsxs("section", { className: "rounded-lg border bg-card p-4", children: [_jsx(SectionHeader, { icon: IconListCheck, title: "Readiness" }), _jsx("div", { className: "mt-3 divide-y rounded-md border", children: rows.map((row) => {
400
- const Icon = row.icon;
401
- return (_jsxs(Link, { to: row.to, className: "flex items-center gap-3 px-3 py-2.5 transition hover:bg-muted/40", children: [_jsx("span", { className: cn("flex size-7 shrink-0 items-center justify-center rounded-md border", row.ok
402
- ? "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"
403
- : "bg-background text-muted-foreground"), children: row.ok ? _jsx(IconCheck, { size: 14 }) : _jsx(Icon, { size: 14 }) }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate text-sm font-medium text-foreground", children: row.label }), _jsx("span", { className: "block truncate text-xs text-muted-foreground", children: row.detail })] }), _jsx(IconArrowUpRight, { size: 14, className: "text-muted-foreground" })] }, row.label));
404
- }) })] }));
45
+ return (_jsxs("section", { className: "flex flex-col gap-3", children: [_jsx(SectionHeader, { icon: IconStack3, title: "Apps", detail: visibleApps.length === 1 ? "1 active" : `${visibleApps.length} active`, action: _jsx(Button, { variant: "outline", size: "sm", asChild: true, children: _jsxs(Link, { to: "/apps", children: ["View all", _jsx(IconArrowUpRight, { size: 14 })] }) }) }), showSkeletons ? (_jsx("div", { className: "grid gap-3 md:grid-cols-2", children: Array.from({ length: 4 }).map((_, index) => (_jsxs("div", { className: "rounded-lg border bg-card p-4", children: [_jsx(Skeleton, { className: "h-4 w-32" }), _jsx(Skeleton, { className: "mt-3 h-3 w-24" }), _jsx(Skeleton, { className: "mt-3 h-3 w-full" })] }, index))) })) : visibleApps.length > 0 ? (_jsx("div", { className: "grid gap-3 md:grid-cols-2", children: visibleApps.map((app) => (_jsx(WorkspaceAppCard, { app: app, className: "min-h-32" }, app.id))) })) : (_jsx(CreateAppPopover, {}))] }));
405
46
  }
406
47
  export function DispatchControlPlane() {
407
- const { data: overviewData, isLoading: overviewLoading } = useActionQuery("list-dispatch-overview", {});
408
- const { data: connectedAgents = [] } = useActionQuery("list-connected-agents", {});
409
48
  const { data: workspaceApps = [], isLoading: appsLoading } = useActionQuery("list-workspace-apps", { includeAgentCards: false, includeArchived: true }, { refetchInterval: 2_000 });
410
- const { threads } = useChatThreads(undefined, undefined, undefined, {
411
- autoCreate: false,
412
- });
413
- const automationsQuery = useAutomationsStatus();
414
- const taskQueueQuery = useTaskQueueStats();
415
- const overview = overviewData;
416
- const apps = workspaceApps ?? [];
417
- const agents = connectedAgents ?? [];
418
- const automations = automationsQuery.data ?? [];
419
- const taskQueue = taskQueueQuery.data ?? ZERO_TASK_QUEUE_STATS;
420
- const visibleThreads = threads.filter((thread) => thread.messageCount > 0);
421
- useEffect(() => {
422
- const handleRunning = () => {
423
- void taskQueueQuery.refetch();
424
- };
425
- window.addEventListener("agentNative.chatRunning", handleRunning);
426
- return () => {
427
- window.removeEventListener("agentNative.chatRunning", handleRunning);
428
- };
429
- }, [taskQueueQuery]);
430
- return (_jsx(DispatchShell, { title: "Control plane", description: "Dispatch is the workspace shell for chats, runs, apps, agents, automations, approvals, and resources.", children: _jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "grid gap-4 xl:grid-cols-[minmax(0,1.08fr)_minmax(360px,0.92fr)]", children: [_jsx(CommandPanel, {}), _jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [_jsx(MetricCard, { label: "Vault", value: formatNumber(overview?.vault?.secretCount ?? 0), detail: `${formatNumber(overview?.vault?.activeGrantCount ?? 0)} active grants`, icon: IconShieldCheck, to: "/vault", tone: (overview?.vault?.secretCount ?? 0) > 0 ? "success" : "default" }), _jsx(MetricCard, { label: "Resources", value: formatNumber(overview?.counts?.destinations ?? 0), detail: "destinations", icon: IconArrowUpRight, to: "/destinations" })] })] }), _jsx(ControlPlaneMetrics, { overview: overview, apps: apps, agents: agents, automations: automations, threads: visibleThreads, taskQueue: taskQueue }), _jsxs("div", { className: "grid gap-4 xl:grid-cols-[minmax(0,1.15fr)_minmax(340px,0.85fr)]", children: [_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [_jsx(RecentChatsPanel, {}), _jsx(RunsPanel, { taskQueue: taskQueue })] }), _jsx(AppsPanel, { apps: apps, isLoading: appsLoading }), _jsx(ApprovalsAndAuditPanel, { overview: overview, isLoading: overviewLoading })] }), _jsxs("div", { className: "space-y-4", children: [_jsx(AutomationsPanel, { automations: automations, isLoading: automationsQuery.isLoading }), _jsx(AgentsPanelSummary, { agents: agents }), _jsx(ReadinessPanel, { overview: overview, apps: apps, agents: agents, automations: automations }), taskQueue.failed_last_hour > 0 ? (_jsxs("div", { className: "rounded-lg border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive", children: [_jsxs("div", { className: "flex items-center gap-2 font-medium", children: [_jsx(IconAlertTriangle, { size: 16 }), "Integration failures detected"] }), _jsx("div", { className: "mt-1 text-xs", children: "Check credentials, destinations, and recent queue errors." })] })) : null] })] })] }) }));
49
+ return (_jsx(DispatchShell, { title: "Overview", description: "Ask Dispatch or jump into a workspace app.", children: _jsxs("div", { className: "flex flex-col gap-6", children: [_jsx(CommandPanel, {}), _jsx(AppsPanel, { apps: workspaceApps ?? [], isLoading: appsLoading })] }) }));
431
50
  }
432
51
  //# sourceMappingURL=dispatch-control-plane.js.map