@agent-native/dispatch 0.14.2 → 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 -122
- package/dist/actions/approve-dispatch-change.d.ts +10 -10
- 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/provider-api-register.js +64 -2
- package/dist/actions/provider-api-register.js.map +1 -1
- package/dist/actions/reject-dispatch-change.d.ts +10 -10
- 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 +24 -21
- package/dist/server/lib/dispatch-store.d.ts.map +1 -1
- package/dist/server/lib/dispatch-store.js +59 -23
- 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/lib/vault-store.d.ts.map +1 -1
- package/dist/server/lib/vault-store.js +102 -37
- package/dist/server/lib/vault-store.js.map +1 -1
- package/dist/server/plugins/agent-chat.d.ts +1 -1
- package/package.json +2 -3
- package/src/actions/list_apps.spec.ts +52 -0
- package/src/actions/list_apps.ts +16 -8
- package/src/actions/provider-api-register.spec.ts +193 -0
- package/src/actions/provider-api-register.ts +66 -1
- 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 +420 -0
- package/src/server/lib/dispatch-store.ts +94 -21
- 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
- package/src/server/lib/vault-store.ts +151 -45
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
useAgentChatHomeHandoff,
|
|
10
10
|
useAgentChatHomeHandoffLinks,
|
|
11
11
|
useChatThreads,
|
|
12
|
-
useFormatters,
|
|
13
12
|
useT,
|
|
14
13
|
type ChatThreadSummary,
|
|
15
14
|
} from "@agent-native/core/client";
|
|
@@ -94,13 +93,6 @@ export interface DispatchExtensionConfig {
|
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
const PRIMARY_NAV_ITEMS = [
|
|
97
|
-
{
|
|
98
|
-
id: "chat",
|
|
99
|
-
to: "/chat",
|
|
100
|
-
label: "Chat",
|
|
101
|
-
icon: IconMessageQuestion,
|
|
102
|
-
section: "primary",
|
|
103
|
-
},
|
|
104
96
|
{
|
|
105
97
|
id: "overview",
|
|
106
98
|
to: "/overview",
|
|
@@ -108,6 +100,13 @@ const PRIMARY_NAV_ITEMS = [
|
|
|
108
100
|
icon: IconBroadcast,
|
|
109
101
|
section: "primary",
|
|
110
102
|
},
|
|
103
|
+
{
|
|
104
|
+
id: "chat",
|
|
105
|
+
to: "/chat",
|
|
106
|
+
label: "Chat",
|
|
107
|
+
icon: IconMessageQuestion,
|
|
108
|
+
section: "primary",
|
|
109
|
+
},
|
|
111
110
|
{
|
|
112
111
|
id: "apps",
|
|
113
112
|
to: "/apps",
|
|
@@ -312,22 +311,17 @@ function threadIdFromPath(pathname: string): string | null {
|
|
|
312
311
|
}
|
|
313
312
|
}
|
|
314
313
|
|
|
315
|
-
function formatThreadAge(
|
|
316
|
-
|
|
317
|
-
formatDate: ReturnType<typeof useFormatters>["formatDate"],
|
|
318
|
-
) {
|
|
319
|
-
const diffMs = Math.max(0, Date.now() - updatedAt);
|
|
314
|
+
export function formatThreadAge(updatedAt: number, now = Date.now()) {
|
|
315
|
+
const diffMs = Math.max(0, now - updatedAt);
|
|
320
316
|
const minutes = Math.floor(diffMs / 60_000);
|
|
321
317
|
if (minutes < 1) return "now";
|
|
322
318
|
if (minutes < 60) return `${minutes}m`;
|
|
323
319
|
const hours = Math.floor(minutes / 60);
|
|
324
320
|
if (hours < 24) return `${hours}h`;
|
|
325
321
|
const days = Math.floor(hours / 24);
|
|
326
|
-
if (days <
|
|
327
|
-
return
|
|
328
|
-
|
|
329
|
-
day: "numeric",
|
|
330
|
-
});
|
|
322
|
+
if (days < 14) return `${days}d`;
|
|
323
|
+
if (days < 365) return `${Math.floor(days / 7)}w`;
|
|
324
|
+
return `${Math.floor(days / 365)}y`;
|
|
331
325
|
}
|
|
332
326
|
|
|
333
327
|
function threadTitle(thread: ChatThreadSummary, fallback: string) {
|
|
@@ -344,7 +338,6 @@ function threadUpdatedAt(thread: ChatThreadSummary) {
|
|
|
344
338
|
|
|
345
339
|
function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
|
|
346
340
|
const t = useT();
|
|
347
|
-
const { formatDate } = useFormatters();
|
|
348
341
|
const navigate = useNavigate();
|
|
349
342
|
const location = useLocation();
|
|
350
343
|
const {
|
|
@@ -510,19 +503,21 @@ function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
|
|
|
510
503
|
<button
|
|
511
504
|
type="button"
|
|
512
505
|
onClick={() => openThread(thread.id)}
|
|
513
|
-
className="min-w-0 flex-1 cursor-pointer px-2 py-1.5 pe-
|
|
506
|
+
className="flex min-w-0 flex-1 cursor-pointer items-center gap-2 px-2 py-1.5 pe-1 text-start text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
514
507
|
>
|
|
515
|
-
<span className="block min-w-0 truncate">
|
|
508
|
+
<span className="block min-w-0 flex-1 truncate">
|
|
509
|
+
{title}
|
|
510
|
+
</span>
|
|
511
|
+
<time className="w-8 shrink-0 whitespace-nowrap text-end text-[11px] tabular-nums text-muted-foreground/60 transition-opacity group-hover/item:opacity-0 group-focus-within/item:opacity-0">
|
|
512
|
+
{isActive
|
|
513
|
+
? ""
|
|
514
|
+
: formatThreadAge(threadUpdatedAt(thread))}
|
|
515
|
+
</time>
|
|
516
516
|
</button>
|
|
517
517
|
</TooltipTrigger>
|
|
518
518
|
<TooltipContent side="right">{title}</TooltipContent>
|
|
519
519
|
</Tooltip>
|
|
520
520
|
<div className="pointer-events-none absolute end-1 top-1/2 flex -translate-y-1/2 items-center gap-0.5">
|
|
521
|
-
<span className="pointer-events-none pe-1 text-[11px] text-muted-foreground/60 transition-opacity group-hover/item:opacity-0 group-focus-within/item:opacity-0">
|
|
522
|
-
{isActive
|
|
523
|
-
? ""
|
|
524
|
-
: formatThreadAge(threadUpdatedAt(thread), formatDate)}
|
|
525
|
-
</span>
|
|
526
521
|
<DropdownMenu>
|
|
527
522
|
<Tooltip>
|
|
528
523
|
<TooltipTrigger asChild>
|
|
@@ -532,7 +527,7 @@ function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
|
|
|
532
527
|
aria-label={t("dispatch.sidebar.chatOptions", {
|
|
533
528
|
title,
|
|
534
529
|
})}
|
|
535
|
-
className="pointer-events-auto rounded p-0.5 text-muted-foreground/50 opacity-0 transition-
|
|
530
|
+
className="pointer-events-auto rounded p-0.5 text-muted-foreground/50 opacity-0 transition-[color,opacity] hover:text-foreground focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover/item:opacity-100 group-focus-within/item:opacity-100 data-[state=open]:opacity-100 data-[state=open]:text-foreground"
|
|
536
531
|
>
|
|
537
532
|
<IconDots className="size-3" />
|
|
538
533
|
</button>
|
|
@@ -29,7 +29,7 @@ const SheetOverlay = React.forwardRef<
|
|
|
29
29
|
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
|
30
30
|
|
|
31
31
|
const sheetVariants = cva(
|
|
32
|
-
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-
|
|
32
|
+
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-150 data-[state=open]:duration-200",
|
|
33
33
|
{
|
|
34
34
|
variants: {
|
|
35
35
|
side: {
|
|
@@ -23,7 +23,7 @@ const ToastViewport = React.forwardRef<
|
|
|
23
23
|
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
|
|
24
24
|
|
|
25
25
|
const toastVariants = cva(
|
|
26
|
-
"group pointer-events-auto relative flex w-full items-center justify-between gap-3 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-
|
|
26
|
+
"group pointer-events-auto relative flex w-full items-center justify-between gap-3 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-transform motion-reduce:animate-none motion-reduce:transition-none data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
|
|
27
27
|
{
|
|
28
28
|
variants: {
|
|
29
29
|
variant: {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import React, { act } from "react";
|
|
3
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
|
|
6
|
+
import { TooltipProvider } from "./ui/tooltip";
|
|
7
|
+
import { WorkspaceAppCard } from "./workspace-app-card";
|
|
8
|
+
|
|
9
|
+
vi.mock("@agent-native/core/client", () => ({
|
|
10
|
+
useActionMutation: () => ({ mutate: vi.fn(), isPending: false }),
|
|
11
|
+
useActionQuery: () => ({
|
|
12
|
+
data: { resources: [], counts: {} },
|
|
13
|
+
isLoading: false,
|
|
14
|
+
isError: false,
|
|
15
|
+
error: null,
|
|
16
|
+
refetch: vi.fn(),
|
|
17
|
+
}),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
describe("WorkspaceAppCard", () => {
|
|
21
|
+
let container: HTMLDivElement;
|
|
22
|
+
let root: Root;
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
26
|
+
container = document.createElement("div");
|
|
27
|
+
document.body.appendChild(container);
|
|
28
|
+
root = createRoot(container);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
act(() => root.unmount());
|
|
33
|
+
container.remove();
|
|
34
|
+
vi.unstubAllGlobals();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("uses one visible, consistent action treatment for context, keys, and more", async () => {
|
|
38
|
+
await act(async () => {
|
|
39
|
+
root.render(
|
|
40
|
+
<TooltipProvider>
|
|
41
|
+
<WorkspaceAppCard
|
|
42
|
+
app={{
|
|
43
|
+
id: "analytics",
|
|
44
|
+
name: "Analytics",
|
|
45
|
+
path: "/analytics",
|
|
46
|
+
description: "Explore product and growth performance.",
|
|
47
|
+
status: "ready",
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
</TooltipProvider>,
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(
|
|
55
|
+
container.querySelector('a[aria-label="Open Analytics"]')?.className,
|
|
56
|
+
).toContain("focus-visible:ring-2");
|
|
57
|
+
|
|
58
|
+
const actions = [
|
|
59
|
+
container.querySelector<HTMLButtonElement>(
|
|
60
|
+
'button[aria-label="View context resources for Analytics"]',
|
|
61
|
+
),
|
|
62
|
+
container.querySelector<HTMLButtonElement>(
|
|
63
|
+
'button[aria-label="Manage keys for Analytics"]',
|
|
64
|
+
),
|
|
65
|
+
container.querySelector<HTMLButtonElement>(
|
|
66
|
+
'button[aria-label="More actions for Analytics"]',
|
|
67
|
+
),
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
for (const action of actions) {
|
|
71
|
+
expect(action).not.toBeNull();
|
|
72
|
+
expect(action?.className).toContain("size-7");
|
|
73
|
+
expect(action?.className).toContain("text-muted-foreground");
|
|
74
|
+
expect(action?.className).toContain(
|
|
75
|
+
"transition-[background-color,color]",
|
|
76
|
+
);
|
|
77
|
+
expect(action?.className).not.toContain("opacity-0");
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -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">
|