@agent-native/dispatch 0.14.3 → 0.14.4
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 +114 -128
- package/dist/actions/list_apps.d.ts +1 -0
- package/dist/actions/list_apps.js +16 -8
- package/dist/actions/list_apps.js.map +1 -1
- package/dist/actions/set-mcp-app-access.js +1 -4
- package/dist/actions/set-mcp-app-access.js.map +1 -1
- package/dist/components/action-query-error.d.ts +8 -0
- package/dist/components/action-query-error.d.ts.map +1 -0
- package/dist/components/action-query-error.js +10 -0
- package/dist/components/action-query-error.js.map +1 -0
- package/dist/components/app-keys-popover.d.ts +4 -0
- package/dist/components/app-keys-popover.d.ts.map +1 -1
- package/dist/components/app-keys-popover.js +26 -14
- package/dist/components/app-keys-popover.js.map +1 -1
- package/dist/components/dispatch-control-plane.d.ts.map +1 -1
- package/dist/components/dispatch-control-plane.js +31 -27
- package/dist/components/dispatch-control-plane.js.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/layout/Layout.d.ts +1 -0
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +19 -21
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/ui/sheet.js +1 -1
- package/dist/components/ui/sheet.js.map +1 -1
- package/dist/components/ui/toast.js +1 -1
- package/dist/components/ui/toast.js.map +1 -1
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +8 -5
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/components/workspace-resource-effective-stack.d.ts.map +1 -1
- package/dist/components/workspace-resource-effective-stack.js +6 -1
- package/dist/components/workspace-resource-effective-stack.js.map +1 -1
- package/dist/components/workspace-resource-impact-preview.d.ts.map +1 -1
- package/dist/components/workspace-resource-impact-preview.js +6 -1
- package/dist/components/workspace-resource-impact-preview.js.map +1 -1
- package/dist/routes/pages/$appId.d.ts.map +1 -1
- package/dist/routes/pages/$appId.js +6 -1
- package/dist/routes/pages/$appId.js.map +1 -1
- package/dist/routes/pages/agents.d.ts.map +1 -1
- package/dist/routes/pages/agents.js +5 -8
- package/dist/routes/pages/agents.js.map +1 -1
- package/dist/routes/pages/approval.d.ts.map +1 -1
- package/dist/routes/pages/approval.js +6 -1
- package/dist/routes/pages/approval.js.map +1 -1
- package/dist/routes/pages/approvals.d.ts.map +1 -1
- package/dist/routes/pages/approvals.js +8 -5
- package/dist/routes/pages/approvals.js.map +1 -1
- package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
- package/dist/routes/pages/apps.$appId.js +4 -2
- package/dist/routes/pages/apps.$appId.js.map +1 -1
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +9 -3
- package/dist/routes/pages/apps.js.map +1 -1
- package/dist/routes/pages/audit.d.ts.map +1 -1
- package/dist/routes/pages/audit.js +4 -2
- package/dist/routes/pages/audit.js.map +1 -1
- package/dist/routes/pages/chat.d.ts.map +1 -1
- package/dist/routes/pages/chat.js +16 -2
- package/dist/routes/pages/chat.js.map +1 -1
- package/dist/routes/pages/destinations.d.ts.map +1 -1
- package/dist/routes/pages/destinations.js +5 -3
- package/dist/routes/pages/destinations.js.map +1 -1
- package/dist/routes/pages/identities.d.ts.map +1 -1
- package/dist/routes/pages/identities.js +5 -3
- package/dist/routes/pages/identities.js.map +1 -1
- package/dist/routes/pages/integrations.d.ts.map +1 -1
- package/dist/routes/pages/integrations.js +9 -3
- package/dist/routes/pages/integrations.js.map +1 -1
- package/dist/routes/pages/thread-debug.d.ts.map +1 -1
- package/dist/routes/pages/thread-debug.js +7 -6
- package/dist/routes/pages/thread-debug.js.map +1 -1
- package/dist/routes/pages/vault.d.ts.map +1 -1
- package/dist/routes/pages/vault.js +23 -7
- package/dist/routes/pages/vault.js.map +1 -1
- package/dist/routes/pages/workspace.d.ts.map +1 -1
- package/dist/routes/pages/workspace.js +9 -3
- package/dist/routes/pages/workspace.js.map +1 -1
- package/dist/server/lib/dispatch-store.d.ts.map +1 -1
- package/dist/server/lib/dispatch-store.js +2 -2
- package/dist/server/lib/dispatch-store.js.map +1 -1
- package/dist/server/lib/mcp-access-store.d.ts.map +1 -1
- package/dist/server/lib/mcp-access-store.js +30 -2
- package/dist/server/lib/mcp-access-store.js.map +1 -1
- package/dist/server/plugins/agent-chat.d.ts +1 -1
- package/package.json +2 -2
- package/src/actions/list_apps.spec.ts +52 -0
- package/src/actions/list_apps.ts +16 -8
- package/src/actions/set-mcp-app-access.spec.ts +73 -0
- package/src/actions/set-mcp-app-access.ts +1 -5
- package/src/components/action-query-error.spec.tsx +56 -0
- package/src/components/action-query-error.tsx +32 -0
- package/src/components/app-keys-popover.spec.tsx +85 -0
- package/src/components/app-keys-popover.tsx +76 -50
- package/src/components/dispatch-control-plane.spec.tsx +106 -0
- package/src/components/dispatch-control-plane.tsx +71 -115
- package/src/components/index.ts +1 -0
- package/src/components/layout/Layout.spec.tsx +42 -2
- package/src/components/layout/Layout.tsx +22 -27
- package/src/components/ui/sheet.tsx +1 -1
- package/src/components/ui/toast.tsx +1 -1
- package/src/components/workspace-app-card.spec.tsx +80 -0
- package/src/components/workspace-app-card.tsx +36 -25
- package/src/components/workspace-resource-effective-stack.tsx +13 -1
- package/src/components/workspace-resource-impact-preview.tsx +12 -1
- package/src/routes/pages/$appId.tsx +17 -1
- package/src/routes/pages/agents.tsx +24 -11
- package/src/routes/pages/approval.tsx +16 -4
- package/src/routes/pages/approvals.tsx +108 -89
- package/src/routes/pages/apps.$appId.tsx +9 -2
- package/src/routes/pages/apps.tsx +20 -7
- package/src/routes/pages/audit.tsx +29 -20
- package/src/routes/pages/chat.spec.tsx +94 -0
- package/src/routes/pages/chat.tsx +30 -10
- package/src/routes/pages/destinations.tsx +75 -62
- package/src/routes/pages/identities.tsx +74 -63
- package/src/routes/pages/integrations.tsx +17 -10
- package/src/routes/pages/thread-debug.tsx +18 -12
- package/src/routes/pages/vault.tsx +59 -26
- package/src/routes/pages/workspace.tsx +69 -55
- package/src/server/lib/approval-fencing.spec.ts +113 -2
- package/src/server/lib/dispatch-store.ts +2 -0
- package/src/server/lib/mcp-access-store.spec.ts +116 -4
- package/src/server/lib/mcp-access-store.ts +37 -2
- package/src/server/lib/mcp-gateway.spec.ts +79 -3
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useActionMutation, useActionQuery } from "@agent-native/core/client";
|
|
2
2
|
import {
|
|
3
|
-
IconArrowUpRight,
|
|
4
3
|
IconChevronDown,
|
|
5
4
|
IconChevronRight,
|
|
6
5
|
IconClockHour4,
|
|
@@ -21,6 +20,7 @@ import {
|
|
|
21
20
|
workspaceAppHref,
|
|
22
21
|
type WorkspaceAppSummary,
|
|
23
22
|
} from "../lib/workspace-apps";
|
|
23
|
+
import { ActionQueryError } from "./action-query-error";
|
|
24
24
|
import { AppKeysPopover } from "./app-keys-popover";
|
|
25
25
|
import { Badge } from "./ui/badge";
|
|
26
26
|
import { Button } from "./ui/button";
|
|
@@ -45,6 +45,9 @@ import { Textarea } from "./ui/textarea";
|
|
|
45
45
|
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
|
46
46
|
import { AppResourceEffectiveStack } from "./workspace-resource-effective-stack";
|
|
47
47
|
|
|
48
|
+
const APP_CARD_ACTION_CLASS =
|
|
49
|
+
"size-7 rounded-md p-0 text-muted-foreground transition-[background-color,color] hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-[state=open]:bg-accent data-[state=open]:text-foreground";
|
|
50
|
+
|
|
48
51
|
export function WorkspaceAppCard({
|
|
49
52
|
app,
|
|
50
53
|
className,
|
|
@@ -121,7 +124,7 @@ export function WorkspaceAppCard({
|
|
|
121
124
|
<div
|
|
122
125
|
aria-disabled={!href}
|
|
123
126
|
className={cn(
|
|
124
|
-
"group relative rounded-
|
|
127
|
+
"group relative rounded-xl border border-border/60 bg-card/40 p-4 transition-[background-color,border-color] hover:border-foreground/20 hover:bg-accent/15 focus-within:border-foreground/20 focus-within:bg-accent/15 aria-disabled:opacity-60",
|
|
125
128
|
isArchived && "opacity-70",
|
|
126
129
|
className,
|
|
127
130
|
)}
|
|
@@ -132,7 +135,7 @@ export function WorkspaceAppCard({
|
|
|
132
135
|
target={openInNewTab ? "_blank" : undefined}
|
|
133
136
|
rel={openInNewTab ? "noreferrer" : undefined}
|
|
134
137
|
aria-label={`Open ${app.name}`}
|
|
135
|
-
className="absolute inset-0 z-0 rounded-
|
|
138
|
+
className="absolute inset-0 z-0 rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
|
136
139
|
/>
|
|
137
140
|
) : null}
|
|
138
141
|
|
|
@@ -164,7 +167,7 @@ export function WorkspaceAppCard({
|
|
|
164
167
|
</Badge>
|
|
165
168
|
) : null}
|
|
166
169
|
</div>
|
|
167
|
-
<p className="mt-1 truncate font-mono text-
|
|
170
|
+
<p className="mt-1 truncate font-mono text-[11px] text-muted-foreground">
|
|
168
171
|
{app.path}
|
|
169
172
|
</p>
|
|
170
173
|
{isPending && app.branchName ? (
|
|
@@ -173,7 +176,7 @@ export function WorkspaceAppCard({
|
|
|
173
176
|
</p>
|
|
174
177
|
) : null}
|
|
175
178
|
{app.description ? (
|
|
176
|
-
<p className="mt-2 line-clamp-2 text-
|
|
179
|
+
<p className="mt-2 min-h-10 line-clamp-2 text-[13px] leading-5 text-muted-foreground">
|
|
177
180
|
{app.description}
|
|
178
181
|
</p>
|
|
179
182
|
) : null}
|
|
@@ -191,16 +194,24 @@ export function WorkspaceAppCard({
|
|
|
191
194
|
) : null}
|
|
192
195
|
<div className="pointer-events-auto">
|
|
193
196
|
<DropdownMenu>
|
|
194
|
-
<
|
|
195
|
-
<
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
197
|
+
<Tooltip>
|
|
198
|
+
<TooltipTrigger asChild>
|
|
199
|
+
<DropdownMenuTrigger asChild>
|
|
200
|
+
<button
|
|
201
|
+
type="button"
|
|
202
|
+
aria-label={`More actions for ${app.name}`}
|
|
203
|
+
className={cn(
|
|
204
|
+
APP_CARD_ACTION_CLASS,
|
|
205
|
+
"inline-flex cursor-pointer items-center justify-center",
|
|
206
|
+
)}
|
|
207
|
+
onClick={(e) => e.stopPropagation()}
|
|
208
|
+
>
|
|
209
|
+
<IconDots size={15} />
|
|
210
|
+
</button>
|
|
211
|
+
</DropdownMenuTrigger>
|
|
212
|
+
</TooltipTrigger>
|
|
213
|
+
<TooltipContent>More actions</TooltipContent>
|
|
214
|
+
</Tooltip>
|
|
204
215
|
<DropdownMenuContent align="end" className="w-44">
|
|
205
216
|
<DropdownMenuItem
|
|
206
217
|
onSelect={(event) => {
|
|
@@ -233,12 +244,6 @@ export function WorkspaceAppCard({
|
|
|
233
244
|
</DropdownMenuContent>
|
|
234
245
|
</DropdownMenu>
|
|
235
246
|
</div>
|
|
236
|
-
{href && !isArchived ? (
|
|
237
|
-
<IconArrowUpRight
|
|
238
|
-
size={16}
|
|
239
|
-
className="text-muted-foreground transition group-hover:text-foreground"
|
|
240
|
-
/>
|
|
241
|
-
) : null}
|
|
242
247
|
</div>
|
|
243
248
|
</div>
|
|
244
249
|
<Dialog open={editOpen} onOpenChange={setEditOpen}>
|
|
@@ -290,11 +295,12 @@ function AppResourcesDialog({ app }: { app: WorkspaceAppSummary }) {
|
|
|
290
295
|
const [inspectedResourceId, setInspectedResourceId] = useState<string | null>(
|
|
291
296
|
null,
|
|
292
297
|
);
|
|
293
|
-
const
|
|
298
|
+
const query = useActionQuery(
|
|
294
299
|
"list-workspace-resources-for-app",
|
|
295
300
|
{ appId: app.id },
|
|
296
301
|
{ enabled: open },
|
|
297
302
|
);
|
|
303
|
+
const { data, isLoading } = query;
|
|
298
304
|
|
|
299
305
|
const resources = ((data as any)?.resources ?? []) as any[];
|
|
300
306
|
const counts = (data as any)?.counts;
|
|
@@ -315,14 +321,14 @@ function AppResourcesDialog({ app }: { app: WorkspaceAppSummary }) {
|
|
|
315
321
|
variant="ghost"
|
|
316
322
|
size="sm"
|
|
317
323
|
aria-label={`View context resources for ${app.name}`}
|
|
318
|
-
className=
|
|
324
|
+
className={APP_CARD_ACTION_CLASS}
|
|
319
325
|
onClick={(e) => e.stopPropagation()}
|
|
320
326
|
>
|
|
321
327
|
<IconFileText size={14} />
|
|
322
328
|
</Button>
|
|
323
329
|
</DialogTrigger>
|
|
324
330
|
</TooltipTrigger>
|
|
325
|
-
<TooltipContent>
|
|
331
|
+
<TooltipContent>View context</TooltipContent>
|
|
326
332
|
</Tooltip>
|
|
327
333
|
<DialogContent className="max-w-2xl">
|
|
328
334
|
<DialogHeader>
|
|
@@ -349,7 +355,12 @@ function AppResourcesDialog({ app }: { app: WorkspaceAppSummary }) {
|
|
|
349
355
|
</Badge>
|
|
350
356
|
</div>
|
|
351
357
|
|
|
352
|
-
{
|
|
358
|
+
{query.isError ? (
|
|
359
|
+
<ActionQueryError
|
|
360
|
+
error={query.error}
|
|
361
|
+
onRetry={() => void query.refetch()}
|
|
362
|
+
/>
|
|
363
|
+
) : isLoading ? (
|
|
353
364
|
<div className="space-y-2">
|
|
354
365
|
<div className="h-14 rounded-lg border bg-muted/30" />
|
|
355
366
|
<div className="h-14 rounded-lg border bg-muted/30" />
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useActionQuery } from "@agent-native/core/client";
|
|
2
2
|
|
|
3
3
|
import { cn } from "../lib/utils";
|
|
4
|
+
import { ActionQueryError } from "./action-query-error";
|
|
4
5
|
import { Badge } from "./ui/badge";
|
|
5
6
|
|
|
6
7
|
export function appAvailabilityLabel(value?: string) {
|
|
@@ -54,11 +55,12 @@ export function AppResourceEffectiveStack({
|
|
|
54
55
|
appId: string;
|
|
55
56
|
resource: any;
|
|
56
57
|
}) {
|
|
57
|
-
const
|
|
58
|
+
const query = useActionQuery(
|
|
58
59
|
"get-workspace-resource-effective-context",
|
|
59
60
|
{ resourceId: resource.id, appId },
|
|
60
61
|
{ enabled: !!resource.id },
|
|
61
62
|
);
|
|
63
|
+
const { data: context, isLoading } = query;
|
|
62
64
|
const layers = ((context as any)?.layers ?? []) as any[];
|
|
63
65
|
const active = (context as any)?.effectiveResource;
|
|
64
66
|
const availability = (context as any)?.availability;
|
|
@@ -76,6 +78,16 @@ export function AppResourceEffectiveStack({
|
|
|
76
78
|
);
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
if (query.isError) {
|
|
82
|
+
return (
|
|
83
|
+
<ActionQueryError
|
|
84
|
+
className="mt-3"
|
|
85
|
+
error={query.error}
|
|
86
|
+
onRetry={() => void query.refetch()}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
79
91
|
return (
|
|
80
92
|
<div className="mt-3 rounded-lg border bg-muted/20 p-3">
|
|
81
93
|
<div className="flex flex-wrap items-start justify-between gap-2">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useActionQuery } from "@agent-native/core/client";
|
|
2
2
|
|
|
3
|
+
import { ActionQueryError } from "./action-query-error";
|
|
3
4
|
import { Badge } from "./ui/badge";
|
|
4
5
|
import { Skeleton } from "./ui/skeleton";
|
|
5
6
|
import { formatResourceTimestamp } from "./workspace-resource-effective-stack";
|
|
@@ -32,7 +33,7 @@ export function ImpactPreview({
|
|
|
32
33
|
scope?: "all" | "selected";
|
|
33
34
|
enabled?: boolean;
|
|
34
35
|
}) {
|
|
35
|
-
const
|
|
36
|
+
const query = useActionQuery(
|
|
36
37
|
"preview-workspace-resource-change",
|
|
37
38
|
{
|
|
38
39
|
operation,
|
|
@@ -42,6 +43,7 @@ export function ImpactPreview({
|
|
|
42
43
|
},
|
|
43
44
|
{ enabled: enabled && Boolean(resourceId || path) },
|
|
44
45
|
);
|
|
46
|
+
const { data: impact, isLoading } = query;
|
|
45
47
|
|
|
46
48
|
if (!enabled || (!resourceId && !path)) return null;
|
|
47
49
|
|
|
@@ -54,6 +56,15 @@ export function ImpactPreview({
|
|
|
54
56
|
);
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
if (query.isError) {
|
|
60
|
+
return (
|
|
61
|
+
<ActionQueryError
|
|
62
|
+
error={query.error}
|
|
63
|
+
onRetry={() => void query.refetch()}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
const data = impact as any;
|
|
58
69
|
if (!data) return null;
|
|
59
70
|
const affectsAllApps = data.affectsAllApps === true;
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type LoaderFunctionArgs,
|
|
15
15
|
} from "react-router";
|
|
16
16
|
|
|
17
|
+
import { ActionQueryError } from "../../components/action-query-error";
|
|
17
18
|
import { DispatchShell } from "../../components/dispatch-shell";
|
|
18
19
|
import { Badge } from "../../components/ui/badge";
|
|
19
20
|
import { Button } from "../../components/ui/button";
|
|
@@ -96,9 +97,10 @@ export async function clientLoader({
|
|
|
96
97
|
export default function WorkspaceAppCatchAllRoute() {
|
|
97
98
|
const t = useT();
|
|
98
99
|
const { appId } = useParams();
|
|
99
|
-
const
|
|
100
|
+
const appsQuery = useActionQuery("list-workspace-apps", {
|
|
100
101
|
includeAgentCards: false,
|
|
101
102
|
});
|
|
103
|
+
const { data: apps = [], isLoading } = appsQuery;
|
|
102
104
|
const app = useMemo(
|
|
103
105
|
() =>
|
|
104
106
|
(apps as WorkspaceAppSummary[]).find((item) => item.id === appId) ?? null,
|
|
@@ -117,6 +119,20 @@ export default function WorkspaceAppCatchAllRoute() {
|
|
|
117
119
|
return <Navigate to={appPath("/overview")} replace />;
|
|
118
120
|
}
|
|
119
121
|
|
|
122
|
+
if (appsQuery.isError) {
|
|
123
|
+
return (
|
|
124
|
+
<DispatchShell
|
|
125
|
+
title={t("dispatch.pages.dataLoadFailed")}
|
|
126
|
+
description={t("dispatch.pages.pageNotFoundDescription")}
|
|
127
|
+
>
|
|
128
|
+
<ActionQueryError
|
|
129
|
+
error={appsQuery.error}
|
|
130
|
+
onRetry={() => void appsQuery.refetch()}
|
|
131
|
+
/>
|
|
132
|
+
</DispatchShell>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
120
136
|
if ((isLoading && !app) || (app && app.status !== "pending" && href)) {
|
|
121
137
|
return (
|
|
122
138
|
<div className="flex h-screen w-full items-center justify-center">
|
|
@@ -8,6 +8,7 @@ import { IconCheck, IconCopy, IconPlugConnected } from "@tabler/icons-react";
|
|
|
8
8
|
import { useMemo, useState } from "react";
|
|
9
9
|
import { toast } from "sonner";
|
|
10
10
|
|
|
11
|
+
import { ActionQueryError } from "../../components/action-query-error";
|
|
11
12
|
import {
|
|
12
13
|
AgentsPanel,
|
|
13
14
|
type ConnectedAgent,
|
|
@@ -45,7 +46,10 @@ function dispatchMcpUrl(): string {
|
|
|
45
46
|
|
|
46
47
|
function DispatchMcpAccessPanel() {
|
|
47
48
|
const t = useT();
|
|
48
|
-
const { data, isLoading } = useActionQuery(
|
|
49
|
+
const { data, isLoading, isError, error, refetch } = useActionQuery(
|
|
50
|
+
"list-mcp-app-access",
|
|
51
|
+
{},
|
|
52
|
+
);
|
|
49
53
|
const [optimistic, setOptimistic] = useState<McpAccessState | null>(null);
|
|
50
54
|
const saveAccess = useActionMutation("set-mcp-app-access", {
|
|
51
55
|
onSuccess: () => {
|
|
@@ -78,10 +82,6 @@ function DispatchMcpAccessPanel() {
|
|
|
78
82
|
const mcpUrl = dispatchMcpUrl();
|
|
79
83
|
|
|
80
84
|
function persist(next: McpAccessState) {
|
|
81
|
-
if (next.mode === "selected-apps" && next.selectedAppIds.length === 0) {
|
|
82
|
-
toast.error(t("dispatch.pages.selectAppForMcp"));
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
85
|
setOptimistic(next);
|
|
86
86
|
saveAccess.mutate(next);
|
|
87
87
|
}
|
|
@@ -153,7 +153,13 @@ function DispatchMcpAccessPanel() {
|
|
|
153
153
|
</Button>
|
|
154
154
|
</div>
|
|
155
155
|
|
|
156
|
-
{
|
|
156
|
+
{isError ? (
|
|
157
|
+
<ActionQueryError
|
|
158
|
+
className="mt-4"
|
|
159
|
+
error={error}
|
|
160
|
+
onRetry={() => void refetch()}
|
|
161
|
+
/>
|
|
162
|
+
) : access.mode === "selected-apps" ? (
|
|
157
163
|
<div className="mt-4 grid gap-2 sm:grid-cols-2 xl:grid-cols-3">
|
|
158
164
|
{apps.map((app) => {
|
|
159
165
|
const isSelected = selected.has(app.id);
|
|
@@ -197,7 +203,7 @@ function DispatchMcpAccessPanel() {
|
|
|
197
203
|
|
|
198
204
|
export default function AgentsRoute() {
|
|
199
205
|
const t = useT();
|
|
200
|
-
const
|
|
206
|
+
const agentsQuery = useActionQuery("list-connected-agents", {});
|
|
201
207
|
|
|
202
208
|
return (
|
|
203
209
|
<DispatchShell
|
|
@@ -206,10 +212,17 @@ export default function AgentsRoute() {
|
|
|
206
212
|
>
|
|
207
213
|
<div className="space-y-4">
|
|
208
214
|
<DispatchMcpAccessPanel />
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
215
|
+
{agentsQuery.isError ? (
|
|
216
|
+
<ActionQueryError
|
|
217
|
+
error={agentsQuery.error}
|
|
218
|
+
onRetry={() => void agentsQuery.refetch()}
|
|
219
|
+
/>
|
|
220
|
+
) : (
|
|
221
|
+
<AgentsPanel
|
|
222
|
+
agents={(agentsQuery.data || []) as ConnectedAgent[]}
|
|
223
|
+
onRefresh={agentsQuery.refetch}
|
|
224
|
+
/>
|
|
225
|
+
)}
|
|
213
226
|
</div>
|
|
214
227
|
</DispatchShell>
|
|
215
228
|
);
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import { useSearchParams } from "react-router";
|
|
17
17
|
import { toast } from "sonner";
|
|
18
18
|
|
|
19
|
+
import { ActionQueryError } from "../../components/action-query-error";
|
|
19
20
|
import {
|
|
20
21
|
ApprovalValueBlock,
|
|
21
22
|
parseApprovalValue,
|
|
@@ -66,10 +67,8 @@ export default function ApprovalPreviewRoute() {
|
|
|
66
67
|
const [searchParams] = useSearchParams();
|
|
67
68
|
const id = searchParams.get("id") ?? "";
|
|
68
69
|
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
{},
|
|
72
|
-
);
|
|
70
|
+
const approvalsQuery = useActionQuery("list-dispatch-approvals", {});
|
|
71
|
+
const { data: approvals, isLoading } = approvalsQuery;
|
|
73
72
|
|
|
74
73
|
const approve = useActionMutation("approve-dispatch-change", {
|
|
75
74
|
onSuccess: () => toast.success("Change approved"),
|
|
@@ -141,6 +140,19 @@ export default function ApprovalPreviewRoute() {
|
|
|
141
140
|
);
|
|
142
141
|
}
|
|
143
142
|
|
|
143
|
+
if (approvalsQuery.isError) {
|
|
144
|
+
return (
|
|
145
|
+
<div className="flex min-h-screen items-start justify-center bg-background p-6">
|
|
146
|
+
<div className="w-full max-w-md">
|
|
147
|
+
<ActionQueryError
|
|
148
|
+
error={approvalsQuery.error}
|
|
149
|
+
onRetry={() => void approvalsQuery.refetch()}
|
|
150
|
+
/>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
144
156
|
if (!approval) {
|
|
145
157
|
return (
|
|
146
158
|
<div className="flex min-h-screen items-center justify-center bg-background p-6">
|
|
@@ -3,6 +3,7 @@ import { useOrg } from "@agent-native/core/client/org";
|
|
|
3
3
|
import { useMemo, useState } from "react";
|
|
4
4
|
import { toast } from "sonner";
|
|
5
5
|
|
|
6
|
+
import { ActionQueryError } from "../../components/action-query-error";
|
|
6
7
|
import { DispatchShell } from "../../components/dispatch-shell";
|
|
7
8
|
import { Button } from "../../components/ui/button";
|
|
8
9
|
import { Input } from "../../components/ui/input";
|
|
@@ -13,8 +14,10 @@ export function meta() {
|
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export default function ApprovalsRoute() {
|
|
16
|
-
const
|
|
17
|
-
const
|
|
17
|
+
const settingsQuery = useActionQuery("get-dispatch-settings", {});
|
|
18
|
+
const approvalsQuery = useActionQuery("list-dispatch-approvals", {});
|
|
19
|
+
const { data: settings } = settingsQuery;
|
|
20
|
+
const { data: approvals } = approvalsQuery;
|
|
18
21
|
const { data: org } = useOrg();
|
|
19
22
|
const hasOrg = !!org?.orgId;
|
|
20
23
|
const [emails, setEmails] = useState("");
|
|
@@ -51,106 +54,122 @@ export default function ApprovalsRoute() {
|
|
|
51
54
|
<h2 className="text-lg font-semibold text-foreground">
|
|
52
55
|
Approval policy
|
|
53
56
|
</h2>
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
{settingsQuery.isError ? (
|
|
58
|
+
<ActionQueryError
|
|
59
|
+
className="mt-4"
|
|
60
|
+
error={settingsQuery.error}
|
|
61
|
+
onRetry={() => void settingsQuery.refetch()}
|
|
62
|
+
/>
|
|
63
|
+
) : (
|
|
64
|
+
<div className="mt-4 space-y-4">
|
|
65
|
+
<label className="flex items-center justify-between rounded-xl border px-4 py-3">
|
|
66
|
+
<div>
|
|
67
|
+
<div className="text-sm font-medium text-foreground">
|
|
68
|
+
Require approval for durable changes
|
|
69
|
+
</div>
|
|
70
|
+
<div className="mt-1 text-xs text-muted-foreground">
|
|
71
|
+
{hasOrg
|
|
72
|
+
? "Applies to saved destinations, shared dream proposals, All-app workspace resources, and dispatch settings."
|
|
73
|
+
: "Requires a team workspace. Set one up on the Team page."}
|
|
74
|
+
</div>
|
|
59
75
|
</div>
|
|
60
|
-
<
|
|
61
|
-
{
|
|
62
|
-
|
|
63
|
-
|
|
76
|
+
<Switch
|
|
77
|
+
checked={settings?.enabled || false}
|
|
78
|
+
disabled={!hasOrg || savePolicy.isPending}
|
|
79
|
+
onCheckedChange={(checked) =>
|
|
80
|
+
savePolicy.mutate({
|
|
81
|
+
enabled: checked,
|
|
82
|
+
approverEmails: settings?.approverEmails || approverList,
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
/>
|
|
86
|
+
</label>
|
|
87
|
+
<div className="space-y-2">
|
|
88
|
+
<div className="text-sm font-medium text-foreground">
|
|
89
|
+
Approver emails
|
|
64
90
|
</div>
|
|
91
|
+
<Input
|
|
92
|
+
value={emails}
|
|
93
|
+
onChange={(event) => setEmails(event.target.value)}
|
|
94
|
+
placeholder={(settings?.approverEmails || []).join(", ")}
|
|
95
|
+
disabled={!hasOrg}
|
|
96
|
+
/>
|
|
97
|
+
<Button
|
|
98
|
+
className="w-full"
|
|
99
|
+
variant="outline"
|
|
100
|
+
disabled={!hasOrg || savePolicy.isPending}
|
|
101
|
+
onClick={() =>
|
|
102
|
+
savePolicy.mutate({
|
|
103
|
+
enabled: settings?.enabled || false,
|
|
104
|
+
approverEmails: approverList,
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
>
|
|
108
|
+
Save approvers
|
|
109
|
+
</Button>
|
|
65
110
|
</div>
|
|
66
|
-
<Switch
|
|
67
|
-
checked={settings?.enabled || false}
|
|
68
|
-
disabled={!hasOrg || savePolicy.isPending}
|
|
69
|
-
onCheckedChange={(checked) =>
|
|
70
|
-
savePolicy.mutate({
|
|
71
|
-
enabled: checked,
|
|
72
|
-
approverEmails: settings?.approverEmails || approverList,
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
/>
|
|
76
|
-
</label>
|
|
77
|
-
<div className="space-y-2">
|
|
78
|
-
<div className="text-sm font-medium text-foreground">
|
|
79
|
-
Approver emails
|
|
80
|
-
</div>
|
|
81
|
-
<Input
|
|
82
|
-
value={emails}
|
|
83
|
-
onChange={(event) => setEmails(event.target.value)}
|
|
84
|
-
placeholder={(settings?.approverEmails || []).join(", ")}
|
|
85
|
-
disabled={!hasOrg}
|
|
86
|
-
/>
|
|
87
|
-
<Button
|
|
88
|
-
className="w-full"
|
|
89
|
-
variant="outline"
|
|
90
|
-
disabled={!hasOrg || savePolicy.isPending}
|
|
91
|
-
onClick={() =>
|
|
92
|
-
savePolicy.mutate({
|
|
93
|
-
enabled: settings?.enabled || false,
|
|
94
|
-
approverEmails: approverList,
|
|
95
|
-
})
|
|
96
|
-
}
|
|
97
|
-
>
|
|
98
|
-
Save approvers
|
|
99
|
-
</Button>
|
|
100
111
|
</div>
|
|
101
|
-
|
|
112
|
+
)}
|
|
102
113
|
</section>
|
|
103
114
|
|
|
104
115
|
<section className="rounded-2xl border bg-card p-5">
|
|
105
116
|
<h2 className="text-lg font-semibold text-foreground">
|
|
106
117
|
Pending and recent requests
|
|
107
118
|
</h2>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
{approvalsQuery.isError ? (
|
|
120
|
+
<ActionQueryError
|
|
121
|
+
className="mt-4"
|
|
122
|
+
error={approvalsQuery.error}
|
|
123
|
+
onRetry={() => void approvalsQuery.refetch()}
|
|
124
|
+
/>
|
|
125
|
+
) : (
|
|
126
|
+
<div className="mt-4 space-y-3">
|
|
127
|
+
{(approvals || []).map((approval: any) => (
|
|
128
|
+
<div
|
|
129
|
+
key={approval.id}
|
|
130
|
+
className="rounded-xl border bg-muted/30 px-4 py-3"
|
|
131
|
+
>
|
|
132
|
+
<div className="flex items-start justify-between gap-3">
|
|
133
|
+
<div>
|
|
134
|
+
<div className="text-sm font-medium text-foreground">
|
|
135
|
+
{approval.summary}
|
|
136
|
+
</div>
|
|
137
|
+
<div className="mt-1 text-xs text-muted-foreground">
|
|
138
|
+
{approval.status} · requested by {approval.requestedBy}
|
|
139
|
+
</div>
|
|
121
140
|
</div>
|
|
141
|
+
{approval.status === "pending" && (
|
|
142
|
+
<div className="flex gap-2">
|
|
143
|
+
<Button
|
|
144
|
+
size="sm"
|
|
145
|
+
onClick={() => approve.mutate({ id: approval.id })}
|
|
146
|
+
>
|
|
147
|
+
Approve
|
|
148
|
+
</Button>
|
|
149
|
+
<Button
|
|
150
|
+
size="sm"
|
|
151
|
+
variant="outline"
|
|
152
|
+
onClick={() =>
|
|
153
|
+
reject.mutate({
|
|
154
|
+
id: approval.id,
|
|
155
|
+
reason: "Rejected in dispatch UI",
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
>
|
|
159
|
+
Reject
|
|
160
|
+
</Button>
|
|
161
|
+
</div>
|
|
162
|
+
)}
|
|
122
163
|
</div>
|
|
123
|
-
{approval.status === "pending" && (
|
|
124
|
-
<div className="flex gap-2">
|
|
125
|
-
<Button
|
|
126
|
-
size="sm"
|
|
127
|
-
onClick={() => approve.mutate({ id: approval.id })}
|
|
128
|
-
>
|
|
129
|
-
Approve
|
|
130
|
-
</Button>
|
|
131
|
-
<Button
|
|
132
|
-
size="sm"
|
|
133
|
-
variant="outline"
|
|
134
|
-
onClick={() =>
|
|
135
|
-
reject.mutate({
|
|
136
|
-
id: approval.id,
|
|
137
|
-
reason: "Rejected in dispatch UI",
|
|
138
|
-
})
|
|
139
|
-
}
|
|
140
|
-
>
|
|
141
|
-
Reject
|
|
142
|
-
</Button>
|
|
143
|
-
</div>
|
|
144
|
-
)}
|
|
145
164
|
</div>
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
165
|
+
))}
|
|
166
|
+
{(approvals?.length || 0) === 0 && (
|
|
167
|
+
<div className="rounded-xl border border-dashed px-4 py-8 text-sm text-muted-foreground">
|
|
168
|
+
No approval requests yet.
|
|
169
|
+
</div>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
154
173
|
</section>
|
|
155
174
|
</div>
|
|
156
175
|
</DispatchShell>
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { useEffect, useMemo } from "react";
|
|
8
8
|
import { Link, useParams } from "react-router";
|
|
9
9
|
|
|
10
|
+
import { ActionQueryError } from "../../components/action-query-error";
|
|
10
11
|
import { DispatchShell } from "../../components/dispatch-shell";
|
|
11
12
|
import { Badge } from "../../components/ui/badge";
|
|
12
13
|
import { Button } from "../../components/ui/button";
|
|
@@ -23,9 +24,10 @@ export function meta() {
|
|
|
23
24
|
export default function WorkspaceAppRoute() {
|
|
24
25
|
const t = useT();
|
|
25
26
|
const { appId } = useParams();
|
|
26
|
-
const
|
|
27
|
+
const appsQuery = useActionQuery("list-workspace-apps", {
|
|
27
28
|
includeAgentCards: false,
|
|
28
29
|
});
|
|
30
|
+
const { data: apps = [], isLoading } = appsQuery;
|
|
29
31
|
const app = useMemo(
|
|
30
32
|
() =>
|
|
31
33
|
(apps as WorkspaceAppSummary[]).find((item) => item.id === appId) ?? null,
|
|
@@ -51,7 +53,12 @@ export default function WorkspaceAppRoute() {
|
|
|
51
53
|
</Link>
|
|
52
54
|
</Button>
|
|
53
55
|
|
|
54
|
-
{
|
|
56
|
+
{appsQuery.isError ? (
|
|
57
|
+
<ActionQueryError
|
|
58
|
+
error={appsQuery.error}
|
|
59
|
+
onRetry={() => void appsQuery.refetch()}
|
|
60
|
+
/>
|
|
61
|
+
) : isLoading && !app ? (
|
|
55
62
|
<div className="space-y-3">
|
|
56
63
|
<Skeleton className="h-5 w-48" />
|
|
57
64
|
<Skeleton className="h-4 w-full" />
|