@agent-native/dispatch 0.13.8 → 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 +34 -7
- package/dist/actions/delete-destination.d.ts +12 -12
- package/dist/components/dispatch-control-plane.d.ts.map +1 -1
- package/dist/components/dispatch-control-plane.js +8 -389
- package/dist/components/dispatch-control-plane.js.map +1 -1
- package/package.json +2 -2
- package/src/components/dispatch-control-plane.tsx +37 -1129
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Agent-Native
|
|
2
2
|
|
|
3
|
-
## The framework for
|
|
3
|
+
## The framework for agentic apps
|
|
4
4
|
|
|
5
|
-
Agent-Native is an open-source framework for
|
|
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
|
-
##
|
|
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
|
-
|
|
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":"
|
|
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 {
|
|
2
|
-
import { PromptComposer,
|
|
3
|
-
import {
|
|
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
|
-
|
|
33
|
-
"
|
|
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: "
|
|
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
|
-
|
|
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
|