@agent-native/dispatch 0.24.6 → 0.25.0
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 +12 -1
- package/dist/actions/connect-external-agent.d.ts +18 -0
- package/dist/actions/connect-external-agent.d.ts.map +1 -0
- package/dist/actions/connect-external-agent.js +68 -0
- package/dist/actions/connect-external-agent.js.map +1 -0
- package/dist/actions/create-workspace-resource.d.ts +14 -14
- package/dist/actions/create-workspace-resource.js +9 -2
- package/dist/actions/create-workspace-resource.js.map +1 -1
- package/dist/actions/create_workspace_app_embed_session.d.ts +13 -0
- package/dist/actions/create_workspace_app_embed_session.d.ts.map +1 -0
- package/dist/actions/create_workspace_app_embed_session.js +30 -0
- package/dist/actions/create_workspace_app_embed_session.js.map +1 -0
- package/dist/actions/import-agent-pack.d.ts +96 -0
- package/dist/actions/import-agent-pack.d.ts.map +1 -0
- package/dist/actions/import-agent-pack.js +116 -0
- package/dist/actions/import-agent-pack.js.map +1 -0
- package/dist/actions/import-agent.d.ts +60 -0
- package/dist/actions/import-agent.d.ts.map +1 -0
- package/dist/actions/import-agent.js +82 -0
- package/dist/actions/import-agent.js.map +1 -0
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +10 -0
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/list-agent-pack.d.ts +36 -0
- package/dist/actions/list-agent-pack.d.ts.map +1 -0
- package/dist/actions/list-agent-pack.js +32 -0
- package/dist/actions/list-agent-pack.js.map +1 -0
- package/dist/actions/list-workspace-resource-options.d.ts +1 -1
- package/dist/actions/list-workspace-resource-options.js +8 -1
- package/dist/actions/list-workspace-resource-options.js.map +1 -1
- package/dist/actions/list-workspace-resources.d.ts +1 -1
- package/dist/actions/list-workspace-resources.js +8 -1
- package/dist/actions/list-workspace-resources.js.map +1 -1
- package/dist/actions/navigate.js +1 -1
- package/dist/actions/navigate.js.map +1 -1
- package/dist/actions/provider-api-register.d.ts +10 -10
- package/dist/actions/update-workspace-resource.d.ts +13 -13
- package/dist/actions/view-screen.js +18 -1
- package/dist/actions/view-screen.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 -8
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +33 -106
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/simple-agents-panel.d.ts +9 -0
- package/dist/components/simple-agents-panel.d.ts.map +1 -0
- package/dist/components/simple-agents-panel.js +405 -0
- package/dist/components/simple-agents-panel.js.map +1 -0
- package/dist/components/workspace-app-host.d.ts +29 -0
- package/dist/components/workspace-app-host.d.ts.map +1 -0
- package/dist/components/workspace-app-host.js +145 -0
- package/dist/components/workspace-app-host.js.map +1 -0
- package/dist/db/migrations.d.ts +2 -4
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +25 -0
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/schema.js +1 -1
- package/dist/db/schema.js.map +1 -1
- package/dist/hooks/use-navigation-state.d.ts +1 -0
- package/dist/hooks/use-navigation-state.d.ts.map +1 -1
- package/dist/hooks/use-navigation-state.js +10 -0
- package/dist/hooks/use-navigation-state.js.map +1 -1
- package/dist/lib/agent-pack.d.ts +31 -0
- package/dist/lib/agent-pack.d.ts.map +1 -0
- package/dist/lib/agent-pack.js +149 -0
- package/dist/lib/agent-pack.js.map +1 -0
- package/dist/lib/simple-agent-profile.d.ts +36 -0
- package/dist/lib/simple-agent-profile.d.ts.map +1 -0
- package/dist/lib/simple-agent-profile.js +183 -0
- package/dist/lib/simple-agent-profile.js.map +1 -0
- package/dist/routes/pages/agents.d.ts.map +1 -1
- package/dist/routes/pages/agents.js +11 -1
- package/dist/routes/pages/agents.js.map +1 -1
- package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
- package/dist/routes/pages/apps.$appId.js +4 -95
- package/dist/routes/pages/apps.$appId.js.map +1 -1
- package/dist/routes/pages/chat.d.ts.map +1 -1
- package/dist/routes/pages/chat.js +62 -10
- package/dist/routes/pages/chat.js.map +1 -1
- package/dist/server/lib/agent-pack-store.d.ts +24 -0
- package/dist/server/lib/agent-pack-store.d.ts.map +1 -0
- package/dist/server/lib/agent-pack-store.js +22 -0
- package/dist/server/lib/agent-pack-store.js.map +1 -0
- package/dist/server/lib/dispatch-store.d.ts.map +1 -1
- package/dist/server/lib/dispatch-store.js +4 -0
- package/dist/server/lib/dispatch-store.js.map +1 -1
- package/dist/server/lib/mcp-gateway.d.ts +11 -0
- package/dist/server/lib/mcp-gateway.d.ts.map +1 -1
- package/dist/server/lib/mcp-gateway.js +155 -9
- package/dist/server/lib/mcp-gateway.js.map +1 -1
- package/dist/server/lib/workspace-resources-store.d.ts +15 -1
- package/dist/server/lib/workspace-resources-store.d.ts.map +1 -1
- package/dist/server/lib/workspace-resources-store.js +11 -2
- package/dist/server/lib/workspace-resources-store.js.map +1 -1
- package/dist/server/plugins/agent-chat.js +13 -0
- package/dist/server/plugins/agent-chat.js.map +1 -1
- package/dist/shared/feature-flags.d.ts +7 -0
- package/dist/shared/feature-flags.d.ts.map +1 -0
- package/dist/shared/feature-flags.js +12 -0
- package/dist/shared/feature-flags.js.map +1 -0
- package/dist/shared/workspace-sso.d.ts +36 -0
- package/dist/shared/workspace-sso.d.ts.map +1 -0
- package/dist/shared/workspace-sso.js +99 -0
- package/dist/shared/workspace-sso.js.map +1 -0
- package/package.json +5 -3
- package/src/actions/connect-external-agent.ts +92 -0
- package/src/actions/create-workspace-resource.ts +9 -2
- package/src/actions/create_workspace_app_embed_session.ts +32 -0
- package/src/actions/import-agent-pack.ts +146 -0
- package/src/actions/import-agent.ts +102 -0
- package/src/actions/index.spec.ts +13 -0
- package/src/actions/index.ts +10 -0
- package/src/actions/list-agent-pack.ts +43 -0
- package/src/actions/list-workspace-resource-options.ts +8 -1
- package/src/actions/list-workspace-resources.ts +8 -1
- package/src/actions/navigate.ts +1 -1
- package/src/actions/view-screen.spec.ts +29 -1
- package/src/actions/view-screen.ts +19 -1
- package/src/components/app-icon.spec.tsx +10 -0
- package/src/components/index.ts +1 -0
- package/src/components/layout/Layout.spec.tsx +2 -0
- package/src/components/layout/Layout.tsx +59 -171
- package/src/components/simple-agents-panel.spec.tsx +73 -0
- package/src/components/simple-agents-panel.tsx +1226 -0
- package/src/components/workspace-app-host.spec.tsx +204 -0
- package/src/components/workspace-app-host.tsx +359 -0
- package/src/db/migrations.spec.ts +5 -1
- package/src/db/migrations.ts +28 -1
- package/src/db/schema.ts +1 -1
- package/src/hooks/use-navigation-state.spec.ts +25 -0
- package/src/hooks/use-navigation-state.ts +11 -0
- package/src/lib/agent-pack.spec.ts +47 -0
- package/src/lib/agent-pack.ts +226 -0
- package/src/lib/simple-agent-profile.spec.ts +77 -0
- package/src/lib/simple-agent-profile.ts +247 -0
- package/src/routes/index.spec.ts +1 -0
- package/src/routes/pages/agents.tsx +21 -1
- package/src/routes/pages/apps.$appId.tsx +3 -240
- package/src/routes/pages/chat.spec.tsx +62 -0
- package/src/routes/pages/chat.tsx +117 -9
- package/src/server/lib/agent-pack-store.ts +35 -0
- package/src/server/lib/dispatch-store.ts +8 -0
- package/src/server/lib/mcp-gateway.spec.ts +141 -0
- package/src/server/lib/mcp-gateway.ts +227 -15
- package/src/server/lib/workspace-resources-store.ts +9 -2
- package/src/server/plugins/agent-chat.ts +13 -0
- package/src/shared/feature-flags.ts +13 -0
- package/src/shared/workspace-sso.ts +118 -0
|
@@ -1,249 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useActionMutation,
|
|
3
|
-
useActionQuery,
|
|
4
|
-
} from "@agent-native/core/client/hooks";
|
|
5
|
-
import { useT } from "@agent-native/core/client/i18n";
|
|
6
|
-
import { withBuilderUtmTrackingParams } from "@agent-native/core/shared/builder-link-tracking";
|
|
7
|
-
import { IconArrowLeft, IconClockHour4 } from "@tabler/icons-react";
|
|
8
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
9
|
-
import { Link, useParams } from "react-router";
|
|
1
|
+
import { useParams } from "react-router";
|
|
10
2
|
|
|
11
|
-
import {
|
|
12
|
-
import { Badge } from "../../components/ui/badge";
|
|
13
|
-
import { Button } from "../../components/ui/button";
|
|
14
|
-
import { Skeleton } from "../../components/ui/skeleton";
|
|
15
|
-
import { Spinner } from "../../components/ui/spinner";
|
|
16
|
-
import { isEmbedSessionExpiredMessage } from "../../lib/embed-session-recovery";
|
|
17
|
-
import {
|
|
18
|
-
workspaceAppDirectHref,
|
|
19
|
-
workspaceAppEmbedTarget,
|
|
20
|
-
workspaceAppHref,
|
|
21
|
-
type WorkspaceAppSummary,
|
|
22
|
-
} from "../../lib/workspace-apps";
|
|
23
|
-
|
|
24
|
-
interface EmbedSessionResult {
|
|
25
|
-
startUrl: string;
|
|
26
|
-
targetPath?: string;
|
|
27
|
-
expiresAt?: number;
|
|
28
|
-
app: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface EmbedSessionInput {
|
|
32
|
-
app: string;
|
|
33
|
-
path?: string;
|
|
34
|
-
url?: string;
|
|
35
|
-
chrome: "minimal";
|
|
36
|
-
}
|
|
3
|
+
import { WorkspaceAppHost } from "../../components/workspace-app-host";
|
|
37
4
|
|
|
38
5
|
export function meta() {
|
|
39
6
|
return [{ title: "Workspace app - Dispatch" }];
|
|
40
7
|
}
|
|
41
8
|
|
|
42
9
|
export default function WorkspaceAppRoute() {
|
|
43
|
-
const t = useT();
|
|
44
10
|
const { appId } = useParams();
|
|
45
|
-
|
|
46
|
-
includeAgentCards: false,
|
|
47
|
-
});
|
|
48
|
-
const { data: apps = [], isLoading } = appsQuery;
|
|
49
|
-
const app = useMemo(
|
|
50
|
-
() =>
|
|
51
|
-
(apps as WorkspaceAppSummary[]).find((item) => item.id === appId) ?? null,
|
|
52
|
-
[appId, apps],
|
|
53
|
-
);
|
|
54
|
-
const href = app ? workspaceAppHref(app) : null;
|
|
55
|
-
const [embedUrl, setEmbedUrl] = useState<string | null>(null);
|
|
56
|
-
const [embedError, setEmbedError] = useState<Error | null>(null);
|
|
57
|
-
const [embedAttempt, setEmbedAttempt] = useState(0);
|
|
58
|
-
const embedFrameRef = useRef<HTMLIFrameElement>(null);
|
|
59
|
-
const createEmbedSession = useActionMutation<
|
|
60
|
-
EmbedSessionResult,
|
|
61
|
-
EmbedSessionInput
|
|
62
|
-
>("create_embed_session", {
|
|
63
|
-
skipActionQueryInvalidation: true,
|
|
64
|
-
});
|
|
65
|
-
const embedInput = useMemo<EmbedSessionInput | null>(() => {
|
|
66
|
-
if (!app || !href) return null;
|
|
67
|
-
return {
|
|
68
|
-
app: app.id,
|
|
69
|
-
...workspaceAppEmbedTarget(app),
|
|
70
|
-
chrome: "minimal",
|
|
71
|
-
};
|
|
72
|
-
}, [app?.id, app?.path, app?.url, href]);
|
|
73
|
-
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
if (!app || app.status === "pending" || !embedInput) return;
|
|
76
|
-
let cancelled = false;
|
|
77
|
-
setEmbedUrl(null);
|
|
78
|
-
setEmbedError(null);
|
|
79
|
-
void createEmbedSession
|
|
80
|
-
.mutateAsync(embedInput)
|
|
81
|
-
.then((result) => {
|
|
82
|
-
if (!cancelled) setEmbedUrl(result.startUrl);
|
|
83
|
-
})
|
|
84
|
-
.catch((cause: unknown) => {
|
|
85
|
-
if (!cancelled) {
|
|
86
|
-
const directAppUrl = workspaceAppDirectHref(app, "/");
|
|
87
|
-
if (directAppUrl) setEmbedUrl(directAppUrl);
|
|
88
|
-
setEmbedError(
|
|
89
|
-
cause instanceof Error ? cause : new Error(String(cause)),
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
return () => {
|
|
94
|
-
cancelled = true;
|
|
95
|
-
};
|
|
96
|
-
}, [
|
|
97
|
-
app?.id,
|
|
98
|
-
app?.path,
|
|
99
|
-
app?.status,
|
|
100
|
-
createEmbedSession.mutateAsync,
|
|
101
|
-
embedAttempt,
|
|
102
|
-
embedInput,
|
|
103
|
-
]);
|
|
104
|
-
|
|
105
|
-
useEffect(() => {
|
|
106
|
-
const handleEmbedSessionExpired = (event: MessageEvent) => {
|
|
107
|
-
if (!isEmbedSessionExpiredMessage(event, embedFrameRef.current, null))
|
|
108
|
-
return;
|
|
109
|
-
setEmbedAttempt((attempt) => attempt + 1);
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
window.addEventListener("message", handleEmbedSessionExpired);
|
|
113
|
-
return () =>
|
|
114
|
-
window.removeEventListener("message", handleEmbedSessionExpired);
|
|
115
|
-
}, []);
|
|
116
|
-
|
|
117
|
-
if (appsQuery.isError) {
|
|
118
|
-
return (
|
|
119
|
-
<div className="flex h-full min-h-0 items-center justify-center p-6">
|
|
120
|
-
<div className="w-full max-w-2xl">
|
|
121
|
-
<ActionQueryError
|
|
122
|
-
error={appsQuery.error}
|
|
123
|
-
onRetry={() => void appsQuery.refetch()}
|
|
124
|
-
/>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (isLoading && !app) {
|
|
131
|
-
return (
|
|
132
|
-
<div className="flex h-full min-h-0 items-center justify-center p-6">
|
|
133
|
-
<div className="w-full max-w-2xl space-y-3 rounded-xl border bg-card p-6">
|
|
134
|
-
<Skeleton className="h-5 w-48" />
|
|
135
|
-
<Skeleton className="h-4 w-full" />
|
|
136
|
-
<Skeleton className="h-4 w-2/3" />
|
|
137
|
-
</div>
|
|
138
|
-
</div>
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (!app) {
|
|
143
|
-
return (
|
|
144
|
-
<div className="flex h-full min-h-0 items-center justify-center p-6">
|
|
145
|
-
<div className="w-full max-w-2xl rounded-xl border bg-card p-6">
|
|
146
|
-
<Button asChild size="sm" variant="ghost" className="-ml-2 mb-4">
|
|
147
|
-
<Link to="/apps">
|
|
148
|
-
<IconArrowLeft size={15} className="mr-1.5" />
|
|
149
|
-
{t("dispatch.nav.apps")}
|
|
150
|
-
</Link>
|
|
151
|
-
</Button>
|
|
152
|
-
<div className="space-y-3">
|
|
153
|
-
<h2 className="text-base font-semibold text-foreground">
|
|
154
|
-
{t("dispatch.pages.appNotFound")}
|
|
155
|
-
</h2>
|
|
156
|
-
<p className="text-sm text-muted-foreground">
|
|
157
|
-
{t("dispatch.pages.pageNotFoundDescription")}
|
|
158
|
-
</p>
|
|
159
|
-
</div>
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (app.status === "pending") {
|
|
166
|
-
return (
|
|
167
|
-
<div className="flex h-full min-h-0 items-center justify-center p-6">
|
|
168
|
-
<div className="w-full max-w-2xl rounded-xl border bg-card p-6">
|
|
169
|
-
<Button asChild size="sm" variant="ghost" className="-ml-2 mb-4">
|
|
170
|
-
<Link to="/apps">
|
|
171
|
-
<IconArrowLeft size={15} className="mr-1.5" />
|
|
172
|
-
{t("dispatch.nav.apps")}
|
|
173
|
-
</Link>
|
|
174
|
-
</Button>
|
|
175
|
-
<div className="space-y-4">
|
|
176
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
177
|
-
<h2 className="text-base font-semibold text-foreground">
|
|
178
|
-
{app.name}
|
|
179
|
-
</h2>
|
|
180
|
-
<Badge
|
|
181
|
-
variant="outline"
|
|
182
|
-
className="gap-1 border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300"
|
|
183
|
-
>
|
|
184
|
-
<IconClockHour4 size={12} />
|
|
185
|
-
{t("dispatch.pages.building")}
|
|
186
|
-
</Badge>
|
|
187
|
-
</div>
|
|
188
|
-
<p className="text-sm text-muted-foreground">
|
|
189
|
-
{t("dispatch.pages.appBuildingPrefix")}{" "}
|
|
190
|
-
<span className="font-mono text-foreground">{app.path}</span>{" "}
|
|
191
|
-
{t("dispatch.pages.appBuildingSuffix")}
|
|
192
|
-
</p>
|
|
193
|
-
{app.builderUrl ? (
|
|
194
|
-
<Button asChild>
|
|
195
|
-
<a
|
|
196
|
-
href={withBuilderUtmTrackingParams(app.builderUrl, {
|
|
197
|
-
campaign: "product",
|
|
198
|
-
content: "dispatch_branch",
|
|
199
|
-
})}
|
|
200
|
-
target="_blank"
|
|
201
|
-
rel="noreferrer"
|
|
202
|
-
>
|
|
203
|
-
{t("dispatch.pages.openBuilderBranch", {
|
|
204
|
-
defaultValue: "Open in Builder",
|
|
205
|
-
})}
|
|
206
|
-
</a>
|
|
207
|
-
</Button>
|
|
208
|
-
) : null}
|
|
209
|
-
</div>
|
|
210
|
-
</div>
|
|
211
|
-
</div>
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return (
|
|
216
|
-
<div
|
|
217
|
-
data-dispatch-workspace-app-host
|
|
218
|
-
className="flex h-full min-h-0 flex-col bg-background"
|
|
219
|
-
>
|
|
220
|
-
<div className="min-h-0 flex-1 bg-muted/20">
|
|
221
|
-
{embedUrl ? (
|
|
222
|
-
<iframe
|
|
223
|
-
key={`${embedUrl}:${embedAttempt}`}
|
|
224
|
-
data-dispatch-workspace-app-frame
|
|
225
|
-
src={embedUrl}
|
|
226
|
-
title={app.name}
|
|
227
|
-
ref={embedFrameRef}
|
|
228
|
-
referrerPolicy="no-referrer"
|
|
229
|
-
className="h-full w-full border-0 bg-background"
|
|
230
|
-
/>
|
|
231
|
-
) : embedError ? (
|
|
232
|
-
<div className="flex h-full items-center justify-center p-6">
|
|
233
|
-
<div className="w-full max-w-2xl">
|
|
234
|
-
<ActionQueryError
|
|
235
|
-
error={embedError}
|
|
236
|
-
onRetry={() => setEmbedAttempt((attempt) => attempt + 1)}
|
|
237
|
-
/>
|
|
238
|
-
</div>
|
|
239
|
-
</div>
|
|
240
|
-
) : (
|
|
241
|
-
<div className="flex h-full items-center justify-center">
|
|
242
|
-
<Spinner className="size-5 text-muted-foreground" />
|
|
243
|
-
<span className="sr-only">{t("dispatch.pages.loading")}</span>
|
|
244
|
-
</div>
|
|
245
|
-
)}
|
|
246
|
-
</div>
|
|
247
|
-
</div>
|
|
248
|
-
);
|
|
11
|
+
return <WorkspaceAppHost appId={appId} />;
|
|
249
12
|
}
|
|
@@ -8,6 +8,15 @@ import ChatRoute from "./chat";
|
|
|
8
8
|
|
|
9
9
|
const clientState = vi.hoisted(() => ({
|
|
10
10
|
surfaceProps: null as Record<string, unknown> | null,
|
|
11
|
+
agents: [] as Array<{
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
description: string | null;
|
|
15
|
+
path: string;
|
|
16
|
+
content: string;
|
|
17
|
+
scope: "all" | "selected";
|
|
18
|
+
updatedAt: number;
|
|
19
|
+
}>,
|
|
11
20
|
}));
|
|
12
21
|
|
|
13
22
|
vi.mock("@agent-native/core/client/agent-chat", () => ({
|
|
@@ -15,6 +24,7 @@ vi.mock("@agent-native/core/client/agent-chat", () => ({
|
|
|
15
24
|
clientState.surfaceProps = props;
|
|
16
25
|
return <>{props.composerSlot as ReactNode}</>;
|
|
17
26
|
},
|
|
27
|
+
insertAgentComposerReference: vi.fn(),
|
|
18
28
|
markAgentChatHomeHandoff: vi.fn(),
|
|
19
29
|
readChatFirstMode: () => true,
|
|
20
30
|
navigateWithAgentChatViewTransition: (
|
|
@@ -24,6 +34,16 @@ vi.mock("@agent-native/core/client/agent-chat", () => ({
|
|
|
24
34
|
sendToAgentChat: vi.fn(),
|
|
25
35
|
}));
|
|
26
36
|
|
|
37
|
+
vi.mock("@agent-native/core/client/hooks", () => ({
|
|
38
|
+
useActionQuery: () => ({
|
|
39
|
+
data: clientState.agents,
|
|
40
|
+
error: null,
|
|
41
|
+
isError: false,
|
|
42
|
+
isLoading: false,
|
|
43
|
+
refetch: vi.fn(),
|
|
44
|
+
}),
|
|
45
|
+
}));
|
|
46
|
+
|
|
27
47
|
vi.mock("../../components/layout/Layout", () => ({
|
|
28
48
|
useDispatchExtensions: () => undefined,
|
|
29
49
|
}));
|
|
@@ -52,6 +72,7 @@ describe("Dispatch ChatRoute", () => {
|
|
|
52
72
|
vi.useFakeTimers();
|
|
53
73
|
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
54
74
|
clientState.surfaceProps = null;
|
|
75
|
+
clientState.agents = [];
|
|
55
76
|
container = document.createElement("div");
|
|
56
77
|
document.body.appendChild(container);
|
|
57
78
|
root = createRoot(container);
|
|
@@ -118,4 +139,45 @@ describe("Dispatch ChatRoute", () => {
|
|
|
118
139
|
);
|
|
119
140
|
expect(container.textContent).not.toContain("Chat across your apps");
|
|
120
141
|
});
|
|
142
|
+
|
|
143
|
+
it("keeps an agent chat scoped and preserves the scope in thread URLs", async () => {
|
|
144
|
+
clientState.agents = [
|
|
145
|
+
{
|
|
146
|
+
id: "agent-1",
|
|
147
|
+
name: "Research Partner",
|
|
148
|
+
description: "Synthesizes research",
|
|
149
|
+
path: "agents/research-partner.md",
|
|
150
|
+
content: "instructions",
|
|
151
|
+
scope: "all",
|
|
152
|
+
updatedAt: 1,
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
await act(async () => {
|
|
157
|
+
root.render(
|
|
158
|
+
<MemoryRouter
|
|
159
|
+
initialEntries={["/chat?agent=agents/research-partner.md"]}
|
|
160
|
+
>
|
|
161
|
+
<ChatRoute />
|
|
162
|
+
</MemoryRouter>,
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(clientState.surfaceProps).toMatchObject({
|
|
167
|
+
scope: {
|
|
168
|
+
type: "agent",
|
|
169
|
+
id: "agent-1",
|
|
170
|
+
label: "Research Partner",
|
|
171
|
+
},
|
|
172
|
+
storageKey: "dispatch-agent-agent-1",
|
|
173
|
+
composerPlaceholder: "Ask Research Partner...",
|
|
174
|
+
});
|
|
175
|
+
expect(
|
|
176
|
+
(
|
|
177
|
+
clientState.surfaceProps?.threadUrlSync as {
|
|
178
|
+
getPath: (id: string) => string;
|
|
179
|
+
}
|
|
180
|
+
).getPath("thread-1"),
|
|
181
|
+
).toBe("/chat/thread-1?agent=agents%2Fresearch-partner.md");
|
|
182
|
+
});
|
|
121
183
|
});
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AgentChatSurface,
|
|
3
|
+
insertAgentComposerReference,
|
|
3
4
|
markAgentChatHomeHandoff,
|
|
4
5
|
readChatFirstMode,
|
|
5
6
|
} from "@agent-native/core/client/agent-chat";
|
|
6
7
|
import { appBasePath, appPath } from "@agent-native/core/client/api-path";
|
|
8
|
+
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
7
9
|
import { useT } from "@agent-native/core/client/i18n";
|
|
8
10
|
import {
|
|
9
11
|
useCallback,
|
|
@@ -14,11 +16,24 @@ import {
|
|
|
14
16
|
} from "react";
|
|
15
17
|
import { useLocation, useNavigate } from "react-router";
|
|
16
18
|
|
|
19
|
+
import { ActionQueryError } from "../../components/action-query-error";
|
|
17
20
|
import { useDispatchExtensions } from "../../components/layout/Layout";
|
|
21
|
+
import { Skeleton } from "../../components/ui/skeleton";
|
|
18
22
|
import { submitOverviewPrompt } from "../../lib/overview-chat";
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
interface WorkspaceAgentResource {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
description: string | null;
|
|
28
|
+
path: string;
|
|
29
|
+
content: string;
|
|
30
|
+
scope: "all" | "selected";
|
|
31
|
+
updatedAt: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function chatThreadPath(threadId: string | null, agentPath?: string): string {
|
|
35
|
+
const path = threadId ? `/chat/${encodeURIComponent(threadId)}` : "/chat";
|
|
36
|
+
return agentPath ? `${path}?agent=${encodeURIComponent(agentPath)}` : path;
|
|
22
37
|
}
|
|
23
38
|
|
|
24
39
|
function stripBasePath(pathname: string): string {
|
|
@@ -94,6 +109,40 @@ export default function ChatRoute() {
|
|
|
94
109
|
const location = useLocation();
|
|
95
110
|
const navigate = useNavigate();
|
|
96
111
|
const routeThreadId = threadIdFromPath(location.pathname);
|
|
112
|
+
const agentPath = new URLSearchParams(location.search).get("agent");
|
|
113
|
+
const agentsQuery = useActionQuery<WorkspaceAgentResource[]>(
|
|
114
|
+
"list-workspace-resources",
|
|
115
|
+
{ kind: "agent" },
|
|
116
|
+
{ enabled: Boolean(agentPath), staleTime: 30_000 },
|
|
117
|
+
);
|
|
118
|
+
const agent = useMemo(
|
|
119
|
+
() =>
|
|
120
|
+
agentPath
|
|
121
|
+
? (agentsQuery.data ?? []).find((resource) => {
|
|
122
|
+
const profileId = resource.path
|
|
123
|
+
.replace(/^agents\//, "")
|
|
124
|
+
.replace(/\.md$/, "");
|
|
125
|
+
return (
|
|
126
|
+
resource.path === agentPath ||
|
|
127
|
+
resource.id === agentPath ||
|
|
128
|
+
profileId === agentPath
|
|
129
|
+
);
|
|
130
|
+
})
|
|
131
|
+
: undefined,
|
|
132
|
+
[agentPath, agentsQuery.data],
|
|
133
|
+
);
|
|
134
|
+
const agentScope = useMemo(
|
|
135
|
+
() =>
|
|
136
|
+
agent
|
|
137
|
+
? {
|
|
138
|
+
type: "agent",
|
|
139
|
+
id: agent.id,
|
|
140
|
+
label: agent.name,
|
|
141
|
+
contextKey: `dispatch-agent:${agent.id}`,
|
|
142
|
+
}
|
|
143
|
+
: null,
|
|
144
|
+
[agent],
|
|
145
|
+
);
|
|
97
146
|
const handledStateIds = useRef(new Set<string>());
|
|
98
147
|
const extensions = useDispatchExtensions();
|
|
99
148
|
const suppressInlineOpenApp =
|
|
@@ -109,10 +158,10 @@ export default function ChatRoute() {
|
|
|
109
158
|
const threadUrlSync = useMemo<DispatchThreadUrlSync>(
|
|
110
159
|
() => ({
|
|
111
160
|
routeThreadId: routeThreadId ?? null,
|
|
112
|
-
getPath: chatThreadPath,
|
|
161
|
+
getPath: (threadId) => chatThreadPath(threadId, agentPath ?? undefined),
|
|
113
162
|
navigate: navigateThreadUrl,
|
|
114
163
|
}),
|
|
115
|
-
[routeThreadId, navigateThreadUrl],
|
|
164
|
+
[agentPath, routeThreadId, navigateThreadUrl],
|
|
116
165
|
);
|
|
117
166
|
const state = location.state as DispatchChatLocationState | null;
|
|
118
167
|
const prompt = state?.dispatchPrompt;
|
|
@@ -176,14 +225,64 @@ export default function ChatRoute() {
|
|
|
176
225
|
window.removeEventListener("agentNative.chatRunning", handleChatRunning);
|
|
177
226
|
}, []);
|
|
178
227
|
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
if (!agent) return;
|
|
230
|
+
const timer = window.setTimeout(() => {
|
|
231
|
+
insertAgentComposerReference(
|
|
232
|
+
{
|
|
233
|
+
label: agent.name,
|
|
234
|
+
icon: "agent",
|
|
235
|
+
source: "agent:custom",
|
|
236
|
+
refType: "custom-agent",
|
|
237
|
+
refId: agent.id,
|
|
238
|
+
refPath: agent.path,
|
|
239
|
+
slotKey: "dispatch-agent",
|
|
240
|
+
slotLabel: "Agent",
|
|
241
|
+
},
|
|
242
|
+
{ openSidebar: false },
|
|
243
|
+
);
|
|
244
|
+
}, 0);
|
|
245
|
+
return () => window.clearTimeout(timer);
|
|
246
|
+
}, [agent?.id, agent?.name, agent?.path]);
|
|
247
|
+
|
|
248
|
+
if (agentPath && agentsQuery.isLoading) {
|
|
249
|
+
return (
|
|
250
|
+
<div className="flex h-full min-h-0 flex-col gap-4 bg-background px-4 py-6 sm:px-6">
|
|
251
|
+
<Skeleton className="mx-auto h-6 w-48" />
|
|
252
|
+
<Skeleton className="mx-auto h-12 w-full max-w-2xl" />
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (agentPath && agentsQuery.isError) {
|
|
258
|
+
return (
|
|
259
|
+
<div className="flex h-full min-h-0 items-center justify-center bg-background px-4">
|
|
260
|
+
<ActionQueryError
|
|
261
|
+
error={agentsQuery.error}
|
|
262
|
+
onRetry={() => void agentsQuery.refetch()}
|
|
263
|
+
/>
|
|
264
|
+
</div>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (agentPath && !agent) {
|
|
269
|
+
return (
|
|
270
|
+
<div className="flex h-full min-h-0 items-center justify-center bg-background px-4 text-sm text-muted-foreground">
|
|
271
|
+
This agent is no longer available.
|
|
272
|
+
</div>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
179
276
|
return (
|
|
180
277
|
<div className="flex h-full min-h-0 flex-col bg-background">
|
|
181
278
|
<DispatchAgentChatSurface
|
|
279
|
+
key={agent ? `agent-${agent.id}` : "dispatch"}
|
|
182
280
|
mode="page"
|
|
183
281
|
chatViewTransition
|
|
184
282
|
className="dispatch-chat-panel px-4 sm:px-6"
|
|
185
283
|
defaultMode="chat"
|
|
186
|
-
storageKey="dispatch"
|
|
284
|
+
storageKey={agent ? `dispatch-agent-${agent.id}` : "dispatch"}
|
|
285
|
+
scope={agentScope}
|
|
187
286
|
threadUrlSync={threadUrlSync}
|
|
188
287
|
showHeader={false}
|
|
189
288
|
showTabBar={false}
|
|
@@ -201,11 +300,20 @@ export default function ChatRoute() {
|
|
|
201
300
|
composerLayoutVariant: "hero" as const,
|
|
202
301
|
}
|
|
203
302
|
: {})}
|
|
204
|
-
composerPlaceholder={
|
|
205
|
-
|
|
206
|
-
|
|
303
|
+
composerPlaceholder={
|
|
304
|
+
agent
|
|
305
|
+
? `Ask ${agent.name}...`
|
|
306
|
+
: t("dispatch.pages.chatPromptPlaceholder", {
|
|
307
|
+
defaultValue: "Ask Dispatch...",
|
|
308
|
+
})
|
|
309
|
+
}
|
|
207
310
|
composerSlot={
|
|
208
|
-
|
|
311
|
+
agent ? (
|
|
312
|
+
<div className="dispatch-chat-intro">
|
|
313
|
+
<h1>{agent.name}</h1>
|
|
314
|
+
{agent.description ? <p>{agent.description}</p> : null}
|
|
315
|
+
</div>
|
|
316
|
+
) : !prompt?.message ? (
|
|
209
317
|
<div className="dispatch-chat-intro">
|
|
210
318
|
<h1>
|
|
211
319
|
{t("dispatch.pages.chatAcrossApps", {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyWorkspaceResourceCreate,
|
|
3
|
+
getWorkspaceResourceByPath,
|
|
4
|
+
requireWorkspaceResourceCtx,
|
|
5
|
+
type WorkspaceResourceInput,
|
|
6
|
+
} from "./workspace-resources-store.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Apply a pack as one logical operation. Approval is handled by the action
|
|
10
|
+
* before this helper is called, so this function must never call the
|
|
11
|
+
* approval-aware create wrapper for individual files.
|
|
12
|
+
*/
|
|
13
|
+
export async function applyAgentPackCreate(
|
|
14
|
+
inputs: WorkspaceResourceInput[],
|
|
15
|
+
actor?: string,
|
|
16
|
+
ctx?: { ownerEmail: string; orgId: string | null },
|
|
17
|
+
) {
|
|
18
|
+
const resourceCtx = ctx ?? requireWorkspaceResourceCtx();
|
|
19
|
+
const existing = await Promise.all(
|
|
20
|
+
inputs.map((input) => getWorkspaceResourceByPath(input.path, resourceCtx)),
|
|
21
|
+
);
|
|
22
|
+
const duplicate = existing.find(Boolean);
|
|
23
|
+
if (duplicate) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`An agent pack resource already exists at ${duplicate.path}. Rename the source before importing it.`,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const created = [];
|
|
30
|
+
for (const input of inputs) {
|
|
31
|
+
const resource = await applyWorkspaceResourceCreate(input, actor, ctx);
|
|
32
|
+
if (resource) created.push(resource);
|
|
33
|
+
}
|
|
34
|
+
return created;
|
|
35
|
+
}
|
|
@@ -588,6 +588,14 @@ async function applyApprovedRequest(request: DispatchApprovalRequest) {
|
|
|
588
588
|
requestCtx,
|
|
589
589
|
);
|
|
590
590
|
}
|
|
591
|
+
if (request.changeType === "workspace-agent-pack.create") {
|
|
592
|
+
const { applyAgentPackCreate } = await import("./agent-pack-store.js");
|
|
593
|
+
return applyAgentPackCreate(
|
|
594
|
+
payload.inputs,
|
|
595
|
+
request.reviewedBy || currentOwnerEmail(),
|
|
596
|
+
requestCtx,
|
|
597
|
+
);
|
|
598
|
+
}
|
|
591
599
|
if (request.changeType === "workspace-resource.update") {
|
|
592
600
|
const { applyWorkspaceResourceUpdate } =
|
|
593
601
|
await import("./workspace-resources-store.js");
|