@agent-native/dispatch 0.14.3 → 0.14.5
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/task-queue-health.d.ts.map +1 -1
- package/dist/components/task-queue-health.js +9 -1
- package/dist/components/task-queue-health.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 +10 -42
- 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/automations.d.ts.map +1 -1
- package/dist/routes/pages/automations.js +0 -1
- package/dist/routes/pages/automations.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/metrics.d.ts.map +1 -1
- package/dist/routes/pages/metrics.js +1 -1
- package/dist/routes/pages/metrics.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/task-queue-health.tsx +9 -1
- 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 +13 -177
- package/src/routes/pages/audit.tsx +29 -20
- package/src/routes/pages/automations.tsx +0 -1
- 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/metrics.tsx +0 -1
- 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
- package/src/styles/dispatch.css +5 -5
|
@@ -1,30 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useActionMutation,
|
|
3
|
-
useActionQuery,
|
|
4
|
-
useT,
|
|
5
|
-
} from "@agent-native/core/client";
|
|
1
|
+
import { useActionQuery, useT } from "@agent-native/core/client";
|
|
6
2
|
import {
|
|
7
3
|
IconApps,
|
|
8
|
-
IconBrain,
|
|
9
|
-
IconBrush,
|
|
10
|
-
IconCalendarMonth,
|
|
11
|
-
IconChartBar,
|
|
12
4
|
IconChevronDown,
|
|
13
|
-
IconClipboardList,
|
|
14
5
|
IconEyeOff,
|
|
15
|
-
IconFileText,
|
|
16
|
-
IconLoader2,
|
|
17
|
-
IconMail,
|
|
18
|
-
IconPhoto,
|
|
19
6
|
IconPlus,
|
|
20
|
-
IconPresentation,
|
|
21
|
-
IconScreenShare,
|
|
22
|
-
IconSparkles,
|
|
23
|
-
IconStack3,
|
|
24
7
|
} from "@tabler/icons-react";
|
|
25
8
|
import { useState } from "react";
|
|
26
|
-
import { toast } from "sonner";
|
|
27
9
|
|
|
10
|
+
import { ActionQueryError } from "../../components/action-query-error";
|
|
28
11
|
import { CreateAppPopover } from "../../components/create-app-popover";
|
|
29
12
|
import { DispatchShell } from "../../components/dispatch-shell";
|
|
30
13
|
import { Button } from "../../components/ui/button";
|
|
@@ -48,48 +31,19 @@ interface WorkspaceInfo {
|
|
|
48
31
|
appCount: number;
|
|
49
32
|
}
|
|
50
33
|
|
|
51
|
-
interface AvailableTemplate {
|
|
52
|
-
name: string;
|
|
53
|
-
label: string;
|
|
54
|
-
hint: string;
|
|
55
|
-
icon: string;
|
|
56
|
-
color: string;
|
|
57
|
-
colorRgb: string;
|
|
58
|
-
core: boolean;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const TEMPLATE_ICONS: Record<string, typeof IconMail> = {
|
|
62
|
-
Mail: IconMail,
|
|
63
|
-
CalendarMonth: IconCalendarMonth,
|
|
64
|
-
FileText: IconFileText,
|
|
65
|
-
Presentation: IconPresentation,
|
|
66
|
-
ScreenShare: IconScreenShare,
|
|
67
|
-
Brain: IconBrain,
|
|
68
|
-
Photo: IconPhoto,
|
|
69
|
-
ChartBar: IconChartBar,
|
|
70
|
-
ClipboardList: IconClipboardList,
|
|
71
|
-
Brush: IconBrush,
|
|
72
|
-
};
|
|
73
|
-
|
|
74
34
|
export default function AppsRoute() {
|
|
75
35
|
const t = useT();
|
|
76
36
|
const [showHidden, setShowHidden] = useState(false);
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
37
|
+
const appsQuery = useActionQuery("list-workspace-apps", {
|
|
38
|
+
includeAgentCards: false,
|
|
39
|
+
includeArchived: true,
|
|
40
|
+
});
|
|
41
|
+
const { data: apps = [], isLoading: appsLoading } = appsQuery;
|
|
82
42
|
const { data: workspace } = useActionQuery(
|
|
83
43
|
"get-workspace-info",
|
|
84
44
|
{},
|
|
85
45
|
{ staleTime: 60_000 },
|
|
86
46
|
);
|
|
87
|
-
const { data: templates = [], isLoading: templatesLoading } = useActionQuery(
|
|
88
|
-
"list-available-workspace-templates",
|
|
89
|
-
{},
|
|
90
|
-
{ refetchInterval: 5_000 },
|
|
91
|
-
);
|
|
92
|
-
|
|
93
47
|
const ws = workspace as WorkspaceInfo | undefined;
|
|
94
48
|
const workspaceLabel = ws?.displayName ?? ws?.name ?? null;
|
|
95
49
|
const allApps = (apps as WorkspaceAppSummary[]).filter(
|
|
@@ -97,7 +51,6 @@ export default function AppsRoute() {
|
|
|
97
51
|
);
|
|
98
52
|
const visibleApps = allApps.filter((app) => !app.archived);
|
|
99
53
|
const archivedApps = allApps.filter((app) => app.archived);
|
|
100
|
-
const typedTemplates = templates as AvailableTemplate[];
|
|
101
54
|
const showAppSkeletons = appsLoading && allApps.length === 0;
|
|
102
55
|
|
|
103
56
|
return (
|
|
@@ -152,7 +105,12 @@ export default function AppsRoute() {
|
|
|
152
105
|
) : null}
|
|
153
106
|
</div>
|
|
154
107
|
|
|
155
|
-
{
|
|
108
|
+
{appsQuery.isError ? (
|
|
109
|
+
<ActionQueryError
|
|
110
|
+
error={appsQuery.error}
|
|
111
|
+
onRetry={() => void appsQuery.refetch()}
|
|
112
|
+
/>
|
|
113
|
+
) : showAppSkeletons ? (
|
|
156
114
|
<AppsSkeletonGrid />
|
|
157
115
|
) : visibleApps.length > 0 ? (
|
|
158
116
|
<div className="grid auto-rows-fr gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
@@ -165,66 +123,6 @@ export default function AppsRoute() {
|
|
|
165
123
|
)}
|
|
166
124
|
</section>
|
|
167
125
|
|
|
168
|
-
{typedTemplates.length > 0 || templatesLoading ? (
|
|
169
|
-
<Collapsible open={templatesOpen} onOpenChange={setTemplatesOpen}>
|
|
170
|
-
<section className="space-y-3">
|
|
171
|
-
<div className="flex flex-wrap items-center justify-between gap-3 border-t pt-4">
|
|
172
|
-
<div className="flex min-w-0 items-center gap-2">
|
|
173
|
-
<IconStack3
|
|
174
|
-
size={16}
|
|
175
|
-
className="shrink-0 text-muted-foreground"
|
|
176
|
-
/>
|
|
177
|
-
<div className="min-w-0">
|
|
178
|
-
<h2 className="text-sm font-semibold text-foreground">
|
|
179
|
-
{t("dispatch.pages.templates")}
|
|
180
|
-
</h2>
|
|
181
|
-
<p className="text-xs text-muted-foreground">
|
|
182
|
-
{templatesLoading
|
|
183
|
-
? t("dispatch.pages.checkingTemplates")
|
|
184
|
-
: t("dispatch.pages.templatesAvailable", {
|
|
185
|
-
count: typedTemplates.length,
|
|
186
|
-
})}
|
|
187
|
-
</p>
|
|
188
|
-
</div>
|
|
189
|
-
</div>
|
|
190
|
-
<CollapsibleTrigger asChild>
|
|
191
|
-
<Button
|
|
192
|
-
type="button"
|
|
193
|
-
variant="outline"
|
|
194
|
-
size="sm"
|
|
195
|
-
className="gap-1.5"
|
|
196
|
-
>
|
|
197
|
-
{templatesOpen
|
|
198
|
-
? t("dispatch.pages.hide")
|
|
199
|
-
: t("dispatch.pages.show")}
|
|
200
|
-
<IconChevronDown
|
|
201
|
-
size={14}
|
|
202
|
-
className={cn(
|
|
203
|
-
"transition-transform",
|
|
204
|
-
templatesOpen && "rotate-180",
|
|
205
|
-
)}
|
|
206
|
-
/>
|
|
207
|
-
</Button>
|
|
208
|
-
</CollapsibleTrigger>
|
|
209
|
-
</div>
|
|
210
|
-
<CollapsibleContent>
|
|
211
|
-
{templatesLoading && typedTemplates.length === 0 ? (
|
|
212
|
-
<AppsSkeletonGrid />
|
|
213
|
-
) : (
|
|
214
|
-
<div className="grid auto-rows-fr gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
215
|
-
{typedTemplates.map((template) => (
|
|
216
|
-
<AddTemplateCard
|
|
217
|
-
key={template.name}
|
|
218
|
-
template={template}
|
|
219
|
-
/>
|
|
220
|
-
))}
|
|
221
|
-
</div>
|
|
222
|
-
)}
|
|
223
|
-
</CollapsibleContent>
|
|
224
|
-
</section>
|
|
225
|
-
</Collapsible>
|
|
226
|
-
) : null}
|
|
227
|
-
|
|
228
126
|
{archivedApps.length > 0 ? (
|
|
229
127
|
<Collapsible open={showHidden} onOpenChange={setShowHidden}>
|
|
230
128
|
<section className="space-y-3">
|
|
@@ -332,65 +230,3 @@ function EmptyAppsState() {
|
|
|
332
230
|
</div>
|
|
333
231
|
);
|
|
334
232
|
}
|
|
335
|
-
|
|
336
|
-
function AddTemplateCard({ template }: { template: AvailableTemplate }) {
|
|
337
|
-
const Icon = TEMPLATE_ICONS[template.icon] ?? IconSparkles;
|
|
338
|
-
const scaffold = useActionMutation("scaffold-workspace-app", {
|
|
339
|
-
onSuccess: (result: any) => {
|
|
340
|
-
toast.success(
|
|
341
|
-
`Scaffolded apps/${result?.appId || template.name}. The gateway will pick it up shortly.`,
|
|
342
|
-
);
|
|
343
|
-
},
|
|
344
|
-
onError: (err) => {
|
|
345
|
-
toast.error(
|
|
346
|
-
`Could not scaffold ${template.label}: ${
|
|
347
|
-
err instanceof Error ? err.message : String(err)
|
|
348
|
-
}`,
|
|
349
|
-
);
|
|
350
|
-
},
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
return (
|
|
354
|
-
<div className="group relative flex h-full min-h-36 items-stretch gap-3 rounded-lg border bg-card p-4 transition hover:border-foreground/30">
|
|
355
|
-
<div
|
|
356
|
-
className="flex h-9 w-9 shrink-0 items-center justify-center self-start rounded-md"
|
|
357
|
-
style={{
|
|
358
|
-
backgroundColor: `rgb(${template.colorRgb} / 0.12)`,
|
|
359
|
-
color: template.color,
|
|
360
|
-
}}
|
|
361
|
-
>
|
|
362
|
-
<Icon size={18} />
|
|
363
|
-
</div>
|
|
364
|
-
<div className="flex min-w-0 flex-1 flex-col">
|
|
365
|
-
<div className="flex min-w-0 items-center gap-2">
|
|
366
|
-
<h3 className="truncate text-sm font-semibold text-foreground">
|
|
367
|
-
{template.label}
|
|
368
|
-
</h3>
|
|
369
|
-
</div>
|
|
370
|
-
<p className="mt-1 line-clamp-2 text-xs leading-relaxed text-muted-foreground">
|
|
371
|
-
{template.hint}
|
|
372
|
-
</p>
|
|
373
|
-
<div className="mt-auto pt-3">
|
|
374
|
-
<Button
|
|
375
|
-
size="sm"
|
|
376
|
-
variant="outline"
|
|
377
|
-
disabled={scaffold.isPending}
|
|
378
|
-
onClick={() => scaffold.mutate({ template: template.name })}
|
|
379
|
-
>
|
|
380
|
-
{scaffold.isPending ? (
|
|
381
|
-
<>
|
|
382
|
-
<IconLoader2 size={14} className="mr-1.5 animate-spin" />
|
|
383
|
-
Adding...
|
|
384
|
-
</>
|
|
385
|
-
) : (
|
|
386
|
-
<>
|
|
387
|
-
<IconPlus size={14} className="mr-1.5" />
|
|
388
|
-
Add to workspace
|
|
389
|
-
</>
|
|
390
|
-
)}
|
|
391
|
-
</Button>
|
|
392
|
-
</div>
|
|
393
|
-
</div>
|
|
394
|
-
</div>
|
|
395
|
-
);
|
|
396
|
-
}
|
|
@@ -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">
|