@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
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
import { useState } from "react";
|
|
26
26
|
import { toast } from "sonner";
|
|
27
27
|
|
|
28
|
+
import { ActionQueryError } from "../../components/action-query-error";
|
|
28
29
|
import { CreateAppPopover } from "../../components/create-app-popover";
|
|
29
30
|
import { DispatchShell } from "../../components/dispatch-shell";
|
|
30
31
|
import { Button } from "../../components/ui/button";
|
|
@@ -75,20 +76,22 @@ export default function AppsRoute() {
|
|
|
75
76
|
const t = useT();
|
|
76
77
|
const [showHidden, setShowHidden] = useState(false);
|
|
77
78
|
const [templatesOpen, setTemplatesOpen] = useState(false);
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
);
|
|
79
|
+
const appsQuery = useActionQuery("list-workspace-apps", {
|
|
80
|
+
includeAgentCards: false,
|
|
81
|
+
includeArchived: true,
|
|
82
|
+
});
|
|
83
|
+
const { data: apps = [], isLoading: appsLoading } = appsQuery;
|
|
82
84
|
const { data: workspace } = useActionQuery(
|
|
83
85
|
"get-workspace-info",
|
|
84
86
|
{},
|
|
85
87
|
{ staleTime: 60_000 },
|
|
86
88
|
);
|
|
87
|
-
const
|
|
89
|
+
const templatesQuery = useActionQuery(
|
|
88
90
|
"list-available-workspace-templates",
|
|
89
91
|
{},
|
|
90
92
|
{ refetchInterval: 5_000 },
|
|
91
93
|
);
|
|
94
|
+
const { data: templates = [], isLoading: templatesLoading } = templatesQuery;
|
|
92
95
|
|
|
93
96
|
const ws = workspace as WorkspaceInfo | undefined;
|
|
94
97
|
const workspaceLabel = ws?.displayName ?? ws?.name ?? null;
|
|
@@ -152,7 +155,12 @@ export default function AppsRoute() {
|
|
|
152
155
|
) : null}
|
|
153
156
|
</div>
|
|
154
157
|
|
|
155
|
-
{
|
|
158
|
+
{appsQuery.isError ? (
|
|
159
|
+
<ActionQueryError
|
|
160
|
+
error={appsQuery.error}
|
|
161
|
+
onRetry={() => void appsQuery.refetch()}
|
|
162
|
+
/>
|
|
163
|
+
) : showAppSkeletons ? (
|
|
156
164
|
<AppsSkeletonGrid />
|
|
157
165
|
) : visibleApps.length > 0 ? (
|
|
158
166
|
<div className="grid auto-rows-fr gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
@@ -165,7 +173,12 @@ export default function AppsRoute() {
|
|
|
165
173
|
)}
|
|
166
174
|
</section>
|
|
167
175
|
|
|
168
|
-
{
|
|
176
|
+
{templatesQuery.isError ? (
|
|
177
|
+
<ActionQueryError
|
|
178
|
+
error={templatesQuery.error}
|
|
179
|
+
onRetry={() => void templatesQuery.refetch()}
|
|
180
|
+
/>
|
|
181
|
+
) : typedTemplates.length > 0 || templatesLoading ? (
|
|
169
182
|
<Collapsible open={templatesOpen} onOpenChange={setTemplatesOpen}>
|
|
170
183
|
<section className="space-y-3">
|
|
171
184
|
<div className="flex flex-wrap items-center justify-between gap-3 border-t pt-4">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useActionQuery } from "@agent-native/core/client";
|
|
2
2
|
|
|
3
|
+
import { ActionQueryError } from "../../components/action-query-error";
|
|
3
4
|
import { DispatchShell } from "../../components/dispatch-shell";
|
|
4
5
|
|
|
5
6
|
export function meta() {
|
|
@@ -7,7 +8,8 @@ export function meta() {
|
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export default function AuditRoute() {
|
|
10
|
-
const
|
|
11
|
+
const query = useActionQuery("list-dispatch-audit", { limit: 100 });
|
|
12
|
+
const { data } = query;
|
|
11
13
|
|
|
12
14
|
return (
|
|
13
15
|
<DispatchShell
|
|
@@ -15,27 +17,34 @@ export default function AuditRoute() {
|
|
|
15
17
|
description="Change history for routes, settings, and approvals."
|
|
16
18
|
>
|
|
17
19
|
<section className="rounded-2xl border bg-card p-5">
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
{query.isError ? (
|
|
21
|
+
<ActionQueryError
|
|
22
|
+
error={query.error}
|
|
23
|
+
onRetry={() => void query.refetch()}
|
|
24
|
+
/>
|
|
25
|
+
) : (
|
|
26
|
+
<div className="space-y-3">
|
|
27
|
+
{(data || []).map((event: any) => (
|
|
28
|
+
<div
|
|
29
|
+
key={event.id}
|
|
30
|
+
className="rounded-xl border bg-muted/30 px-4 py-3"
|
|
31
|
+
>
|
|
32
|
+
<div className="text-sm font-medium text-foreground">
|
|
33
|
+
{event.summary}
|
|
34
|
+
</div>
|
|
35
|
+
<div className="mt-1 text-xs text-muted-foreground">
|
|
36
|
+
{event.actor} · {event.action} ·{" "}
|
|
37
|
+
{new Date(event.createdAt).toLocaleString()}
|
|
38
|
+
</div>
|
|
26
39
|
</div>
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
40
|
+
))}
|
|
41
|
+
{(data?.length || 0) === 0 && (
|
|
42
|
+
<div className="rounded-xl border border-dashed px-4 py-6 text-sm text-muted-foreground">
|
|
43
|
+
No audit entries yet.
|
|
30
44
|
</div>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<div className="rounded-xl border border-dashed px-4 py-6 text-sm text-muted-foreground">
|
|
35
|
-
No audit entries yet.
|
|
36
|
-
</div>
|
|
37
|
-
)}
|
|
38
|
-
</div>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
)}
|
|
39
48
|
</section>
|
|
40
49
|
</DispatchShell>
|
|
41
50
|
);
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import React, { act, type ReactNode } from "react";
|
|
3
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
4
|
+
import { MemoryRouter } from "react-router";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
|
|
7
|
+
import ChatRoute from "./chat";
|
|
8
|
+
|
|
9
|
+
const clientState = vi.hoisted(() => ({
|
|
10
|
+
surfaceProps: null as Record<string, unknown> | null,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock("@agent-native/core/client", () => ({
|
|
14
|
+
AgentChatSurface: (props: Record<string, unknown>) => {
|
|
15
|
+
clientState.surfaceProps = props;
|
|
16
|
+
return <>{props.composerSlot as ReactNode}</>;
|
|
17
|
+
},
|
|
18
|
+
appBasePath: () => "",
|
|
19
|
+
appPath: (path: string) => path,
|
|
20
|
+
isInBuilderFrame: () => false,
|
|
21
|
+
markAgentChatHomeHandoff: vi.fn(),
|
|
22
|
+
sendToAgentChat: vi.fn(),
|
|
23
|
+
useT: () => (key: string, values?: { defaultValue?: string }) =>
|
|
24
|
+
values?.defaultValue ?? key,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
describe("Dispatch ChatRoute", () => {
|
|
28
|
+
let container: HTMLDivElement;
|
|
29
|
+
let root: Root;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
33
|
+
clientState.surfaceProps = null;
|
|
34
|
+
container = document.createElement("div");
|
|
35
|
+
document.body.appendChild(container);
|
|
36
|
+
root = createRoot(container);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
act(() => root.unmount());
|
|
41
|
+
container.remove();
|
|
42
|
+
vi.unstubAllGlobals();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("keeps the centered hero layout for a direct new Chat", async () => {
|
|
46
|
+
await act(async () => {
|
|
47
|
+
root.render(
|
|
48
|
+
<MemoryRouter initialEntries={["/chat"]}>
|
|
49
|
+
<ChatRoute />
|
|
50
|
+
</MemoryRouter>,
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(clientState.surfaceProps).toMatchObject({
|
|
55
|
+
mode: "page",
|
|
56
|
+
chatViewTransition: true,
|
|
57
|
+
centerComposerWhenEmpty: true,
|
|
58
|
+
composerLayoutVariant: "hero",
|
|
59
|
+
composerPlaceholder: "Ask Dispatch...",
|
|
60
|
+
});
|
|
61
|
+
expect(container.textContent).toContain("Chat across your apps");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("starts bottom-pinned when an Overview prompt is transitioning in", async () => {
|
|
65
|
+
await act(async () => {
|
|
66
|
+
root.render(
|
|
67
|
+
<MemoryRouter
|
|
68
|
+
initialEntries={[
|
|
69
|
+
{
|
|
70
|
+
pathname: "/chat",
|
|
71
|
+
state: {
|
|
72
|
+
dispatchPrompt: {
|
|
73
|
+
id: "overview-prompt",
|
|
74
|
+
message: "Route this across my apps",
|
|
75
|
+
selectedModel: "auto",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
]}
|
|
80
|
+
>
|
|
81
|
+
<ChatRoute />
|
|
82
|
+
</MemoryRouter>,
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(clientState.surfaceProps).not.toHaveProperty(
|
|
87
|
+
"centerComposerWhenEmpty",
|
|
88
|
+
);
|
|
89
|
+
expect(clientState.surfaceProps).not.toHaveProperty(
|
|
90
|
+
"composerLayoutVariant",
|
|
91
|
+
);
|
|
92
|
+
expect(container.textContent).not.toContain("Chat across your apps");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
appBasePath,
|
|
4
4
|
appPath,
|
|
5
5
|
markAgentChatHomeHandoff,
|
|
6
|
+
useT,
|
|
6
7
|
} from "@agent-native/core/client";
|
|
7
8
|
import {
|
|
8
9
|
useCallback,
|
|
@@ -86,6 +87,7 @@ export function meta() {
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
export default function ChatRoute() {
|
|
90
|
+
const t = useT();
|
|
89
91
|
const location = useLocation();
|
|
90
92
|
const navigate = useNavigate();
|
|
91
93
|
const routeThreadId = threadIdFromPath(location.pathname);
|
|
@@ -175,18 +177,36 @@ export default function ChatRoute() {
|
|
|
175
177
|
showTabBar={false}
|
|
176
178
|
dynamicSuggestions={false}
|
|
177
179
|
suggestions={[]}
|
|
178
|
-
emptyStateText="
|
|
180
|
+
emptyStateText={t("dispatch.pages.chatAcrossAppsDescription", {
|
|
181
|
+
defaultValue:
|
|
182
|
+
"Route work, inspect status, or create something new from one place.",
|
|
183
|
+
})}
|
|
179
184
|
emptyStateDisplay="hidden"
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
185
|
+
{...(!prompt?.message
|
|
186
|
+
? {
|
|
187
|
+
centerComposerWhenEmpty: true,
|
|
188
|
+
composerLayoutVariant: "hero" as const,
|
|
189
|
+
}
|
|
190
|
+
: {})}
|
|
191
|
+
composerPlaceholder={t("dispatch.pages.chatPromptPlaceholder", {
|
|
192
|
+
defaultValue: "Ask Dispatch...",
|
|
193
|
+
})}
|
|
183
194
|
composerSlot={
|
|
184
|
-
|
|
185
|
-
<
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
195
|
+
!prompt?.message ? (
|
|
196
|
+
<div className="dispatch-chat-intro">
|
|
197
|
+
<h1>
|
|
198
|
+
{t("dispatch.pages.chatAcrossApps", {
|
|
199
|
+
defaultValue: "Chat across your apps",
|
|
200
|
+
})}
|
|
201
|
+
</h1>
|
|
202
|
+
<p>
|
|
203
|
+
{t("dispatch.pages.chatAcrossAppsDescription", {
|
|
204
|
+
defaultValue:
|
|
205
|
+
"Route work, inspect status, or create something new from one place.",
|
|
206
|
+
})}
|
|
207
|
+
</p>
|
|
208
|
+
</div>
|
|
209
|
+
) : null
|
|
190
210
|
}
|
|
191
211
|
/>
|
|
192
212
|
</div>
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import { useState } from "react";
|
|
7
7
|
import { toast } from "sonner";
|
|
8
8
|
|
|
9
|
+
import { ActionQueryError } from "../../components/action-query-error";
|
|
9
10
|
import { DispatchShell } from "../../components/dispatch-shell";
|
|
10
11
|
import { TaskQueueHealth } from "../../components/task-queue-health";
|
|
11
12
|
import {
|
|
@@ -78,7 +79,8 @@ function QuickSendRow({
|
|
|
78
79
|
|
|
79
80
|
export default function DestinationsRoute() {
|
|
80
81
|
const t = useT();
|
|
81
|
-
const
|
|
82
|
+
const destinationsQuery = useActionQuery("list-destinations", {});
|
|
83
|
+
const { data } = destinationsQuery;
|
|
82
84
|
const [form, setForm] = useState({
|
|
83
85
|
name: "",
|
|
84
86
|
platform: "slack",
|
|
@@ -115,70 +117,81 @@ export default function DestinationsRoute() {
|
|
|
115
117
|
<h2 className="text-lg font-semibold text-foreground">
|
|
116
118
|
{t("dispatch.pages.savedDestinations")}
|
|
117
119
|
</h2>
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
120
|
+
{destinationsQuery.isError ? (
|
|
121
|
+
<ActionQueryError
|
|
122
|
+
className="mt-4"
|
|
123
|
+
error={destinationsQuery.error}
|
|
124
|
+
onRetry={() => void destinationsQuery.refetch()}
|
|
125
|
+
/>
|
|
126
|
+
) : (
|
|
127
|
+
<div className="mt-4 space-y-3">
|
|
128
|
+
{(data || []).map((destination: any) => (
|
|
129
|
+
<div
|
|
130
|
+
key={destination.id}
|
|
131
|
+
className="rounded-xl border bg-muted/30 p-4"
|
|
132
|
+
>
|
|
133
|
+
<div className="flex items-start justify-between gap-3">
|
|
134
|
+
<div>
|
|
135
|
+
<div className="text-sm font-medium text-foreground">
|
|
136
|
+
{destination.name}
|
|
137
|
+
</div>
|
|
138
|
+
<div className="mt-1 text-xs text-muted-foreground">
|
|
139
|
+
{destination.platform} · {destination.destination}
|
|
140
|
+
{destination.threadRef
|
|
141
|
+
? ` · thread ${destination.threadRef}`
|
|
142
|
+
: ""}
|
|
143
|
+
</div>
|
|
144
|
+
{destination.notes && (
|
|
145
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
146
|
+
{destination.notes}
|
|
147
|
+
</p>
|
|
148
|
+
)}
|
|
134
149
|
</div>
|
|
135
|
-
|
|
136
|
-
<
|
|
137
|
-
|
|
138
|
-
</p>
|
|
139
|
-
)}
|
|
140
|
-
</div>
|
|
141
|
-
<AlertDialog>
|
|
142
|
-
<AlertDialogTrigger asChild>
|
|
143
|
-
<Button variant="outline" size="sm">
|
|
144
|
-
{t("dispatch.pages.delete")}
|
|
145
|
-
</Button>
|
|
146
|
-
</AlertDialogTrigger>
|
|
147
|
-
<AlertDialogContent>
|
|
148
|
-
<AlertDialogHeader>
|
|
149
|
-
<AlertDialogTitle>
|
|
150
|
-
{t("dispatch.pages.deleteDestinationTitle")}
|
|
151
|
-
</AlertDialogTitle>
|
|
152
|
-
<AlertDialogDescription>
|
|
153
|
-
{t("dispatch.pages.deleteDestinationDescription", {
|
|
154
|
-
name: destination.name,
|
|
155
|
-
})}
|
|
156
|
-
</AlertDialogDescription>
|
|
157
|
-
</AlertDialogHeader>
|
|
158
|
-
<AlertDialogFooter>
|
|
159
|
-
<AlertDialogCancel>
|
|
160
|
-
{t("dispatch.pages.cancel")}
|
|
161
|
-
</AlertDialogCancel>
|
|
162
|
-
<AlertDialogAction
|
|
163
|
-
onClick={() =>
|
|
164
|
-
remove.mutate({ id: destination.id })
|
|
165
|
-
}
|
|
166
|
-
>
|
|
150
|
+
<AlertDialog>
|
|
151
|
+
<AlertDialogTrigger asChild>
|
|
152
|
+
<Button variant="outline" size="sm">
|
|
167
153
|
{t("dispatch.pages.delete")}
|
|
168
|
-
</
|
|
169
|
-
</
|
|
170
|
-
|
|
171
|
-
|
|
154
|
+
</Button>
|
|
155
|
+
</AlertDialogTrigger>
|
|
156
|
+
<AlertDialogContent>
|
|
157
|
+
<AlertDialogHeader>
|
|
158
|
+
<AlertDialogTitle>
|
|
159
|
+
{t("dispatch.pages.deleteDestinationTitle")}
|
|
160
|
+
</AlertDialogTitle>
|
|
161
|
+
<AlertDialogDescription>
|
|
162
|
+
{t(
|
|
163
|
+
"dispatch.pages.deleteDestinationDescription",
|
|
164
|
+
{
|
|
165
|
+
name: destination.name,
|
|
166
|
+
},
|
|
167
|
+
)}
|
|
168
|
+
</AlertDialogDescription>
|
|
169
|
+
</AlertDialogHeader>
|
|
170
|
+
<AlertDialogFooter>
|
|
171
|
+
<AlertDialogCancel>
|
|
172
|
+
{t("dispatch.pages.cancel")}
|
|
173
|
+
</AlertDialogCancel>
|
|
174
|
+
<AlertDialogAction
|
|
175
|
+
onClick={() =>
|
|
176
|
+
remove.mutate({ id: destination.id })
|
|
177
|
+
}
|
|
178
|
+
>
|
|
179
|
+
{t("dispatch.pages.delete")}
|
|
180
|
+
</AlertDialogAction>
|
|
181
|
+
</AlertDialogFooter>
|
|
182
|
+
</AlertDialogContent>
|
|
183
|
+
</AlertDialog>
|
|
184
|
+
</div>
|
|
185
|
+
<QuickSendRow destination={destination} />
|
|
172
186
|
</div>
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
</div>
|
|
187
|
+
))}
|
|
188
|
+
{(data?.length || 0) === 0 && (
|
|
189
|
+
<div className="rounded-xl border border-dashed px-4 py-8 text-sm text-muted-foreground">
|
|
190
|
+
{t("dispatch.pages.noDestinations")}
|
|
191
|
+
</div>
|
|
192
|
+
)}
|
|
193
|
+
</div>
|
|
194
|
+
)}
|
|
182
195
|
</section>
|
|
183
196
|
|
|
184
197
|
<section className="rounded-2xl border bg-card p-5">
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useActionMutation, useActionQuery } from "@agent-native/core/client";
|
|
2
2
|
import { toast } from "sonner";
|
|
3
3
|
|
|
4
|
+
import { ActionQueryError } from "../../components/action-query-error";
|
|
4
5
|
import { DispatchShell } from "../../components/dispatch-shell";
|
|
5
6
|
import { Button } from "../../components/ui/button";
|
|
6
7
|
|
|
@@ -9,7 +10,8 @@ export function meta() {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export default function IdentitiesRoute() {
|
|
12
|
-
const
|
|
13
|
+
const query = useActionQuery("list-linked-identities", {});
|
|
14
|
+
const { data } = query;
|
|
13
15
|
const createToken = useActionMutation("create-link-token", {
|
|
14
16
|
onSuccess: () => toast.success("Link token created"),
|
|
15
17
|
});
|
|
@@ -19,74 +21,83 @@ export default function IdentitiesRoute() {
|
|
|
19
21
|
title="Identities"
|
|
20
22
|
description="Link external senders to workspace users."
|
|
21
23
|
>
|
|
22
|
-
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
24
|
+
{query.isError ? (
|
|
25
|
+
<ActionQueryError
|
|
26
|
+
error={query.error}
|
|
27
|
+
onRetry={() => void query.refetch()}
|
|
28
|
+
/>
|
|
29
|
+
) : (
|
|
30
|
+
<div className="grid gap-4 xl:grid-cols-2">
|
|
31
|
+
<section className="rounded-2xl border bg-card p-5">
|
|
32
|
+
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
33
|
+
<h2 className="text-lg font-semibold text-foreground">
|
|
34
|
+
Active links
|
|
35
|
+
</h2>
|
|
36
|
+
<div className="flex shrink-0 gap-2">
|
|
37
|
+
<Button
|
|
38
|
+
variant="outline"
|
|
39
|
+
onClick={() => createToken.mutate({ platform: "slack" })}
|
|
40
|
+
>
|
|
41
|
+
New Slack token
|
|
42
|
+
</Button>
|
|
43
|
+
<Button
|
|
44
|
+
onClick={() => createToken.mutate({ platform: "telegram" })}
|
|
45
|
+
>
|
|
46
|
+
New Telegram token
|
|
47
|
+
</Button>
|
|
48
|
+
</div>
|
|
40
49
|
</div>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
<div className="mt-4 space-y-3">
|
|
51
|
+
{(data?.links || []).map((link: any) => (
|
|
52
|
+
<div
|
|
53
|
+
key={link.id}
|
|
54
|
+
className="rounded-xl border bg-muted/30 px-4 py-3"
|
|
55
|
+
>
|
|
56
|
+
<div className="text-sm font-medium text-foreground">
|
|
57
|
+
{link.externalUserName || link.externalUserId}
|
|
58
|
+
</div>
|
|
59
|
+
<div className="mt-1 text-xs text-muted-foreground">
|
|
60
|
+
{link.platform} → {link.ownerEmail}
|
|
61
|
+
</div>
|
|
50
62
|
</div>
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
))}
|
|
64
|
+
{(data?.links?.length || 0) === 0 && (
|
|
65
|
+
<div className="rounded-xl border border-dashed px-4 py-8 text-sm text-muted-foreground">
|
|
66
|
+
No linked identities yet. Generate a token and ask the user to
|
|
67
|
+
send <code>/link TOKEN</code> from Slack or Telegram.
|
|
53
68
|
</div>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
<div className="rounded-xl border border-dashed px-4 py-8 text-sm text-muted-foreground">
|
|
58
|
-
No linked identities yet. Generate a token and ask the user to
|
|
59
|
-
send <code>/link TOKEN</code> from Slack or Telegram.
|
|
60
|
-
</div>
|
|
61
|
-
)}
|
|
62
|
-
</div>
|
|
63
|
-
</section>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
</section>
|
|
64
72
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
<section className="rounded-2xl border bg-card p-5">
|
|
74
|
+
<h2 className="text-lg font-semibold text-foreground">
|
|
75
|
+
Link tokens
|
|
76
|
+
</h2>
|
|
77
|
+
<div className="mt-4 space-y-3">
|
|
78
|
+
{(data?.tokens || []).map((token: any) => (
|
|
79
|
+
<div key={token.id} className="rounded-xl border px-4 py-3">
|
|
80
|
+
<div className="text-sm font-medium text-foreground">
|
|
81
|
+
/link {token.token}
|
|
82
|
+
</div>
|
|
83
|
+
<div className="mt-1 text-xs text-muted-foreground">
|
|
84
|
+
{token.platform} · expires{" "}
|
|
85
|
+
{new Date(token.expiresAt).toLocaleString()}
|
|
86
|
+
{token.claimedAt
|
|
87
|
+
? ` · claimed by ${token.claimedByExternalUserName || token.claimedByExternalUserId}`
|
|
88
|
+
: " · waiting to be claimed"}
|
|
89
|
+
</div>
|
|
72
90
|
</div>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
? ` · claimed by ${token.claimedByExternalUserName || token.claimedByExternalUserId}`
|
|
78
|
-
: " · waiting to be claimed"}
|
|
91
|
+
))}
|
|
92
|
+
{(data?.tokens?.length || 0) === 0 && (
|
|
93
|
+
<div className="rounded-xl border border-dashed px-4 py-8 text-sm text-muted-foreground">
|
|
94
|
+
No active link tokens.
|
|
79
95
|
</div>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
</div>
|
|
86
|
-
)}
|
|
87
|
-
</div>
|
|
88
|
-
</section>
|
|
89
|
-
</div>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
</section>
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
90
101
|
</DispatchShell>
|
|
91
102
|
);
|
|
92
103
|
}
|