@agent-native/dispatch 0.13.7 → 0.13.9

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
 
@@ -94,11 +106,26 @@ Generate and edit React-based presentations via prompt or point-and-click.
94
106
 
95
107
  Connect analytics data sources, prompt for real charts, and build reusable dashboards.
96
108
 
109
+ </td>
110
+ </tr>
111
+ <tr>
112
+ <td colspan="3" align="center" valign="top">
113
+
114
+ **Chat**
115
+
116
+ <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="66%" /></a>
117
+
118
+ **A minimal ChatGPT-style app for your own agent**
119
+
120
+ 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.
121
+
97
122
  </td>
98
123
  </tr>
99
124
  </table>
100
125
 
101
- View the full app gallery at **[agent-native.com/templates](https://agent-native.com/templates)**.
126
+ **Build from scratch.** Use the [framework guide](https://agent-native.com/docs/getting-started).
127
+
128
+ View the full app gallery at **[agent-native.com/apps](https://agent-native.com/apps)**.
102
129
 
103
130
  ## Quick Start
104
131
 
@@ -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