@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
|
@@ -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" />
|
|
@@ -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>
|