@agent-native/dispatch 0.11.5 → 0.11.7
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 +57 -120
- package/dist/actions/create-vault-secret.d.ts.map +1 -1
- package/dist/actions/create-vault-secret.js +5 -0
- package/dist/actions/create-vault-secret.js.map +1 -1
- package/dist/actions/navigate.d.ts +2 -0
- package/dist/actions/navigate.d.ts.map +1 -1
- package/dist/actions/navigate.js +15 -4
- package/dist/actions/navigate.js.map +1 -1
- package/dist/actions/update-vault-secret.d.ts.map +1 -1
- package/dist/actions/update-vault-secret.js +3 -0
- package/dist/actions/update-vault-secret.js.map +1 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +26 -4
- package/dist/components/layout/Layout.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 +23 -3
- package/dist/hooks/use-navigation-state.js.map +1 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +1 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/pages/chat.d.ts.map +1 -1
- package/dist/routes/pages/chat.js +51 -3
- package/dist/routes/pages/chat.js.map +1 -1
- package/package.json +2 -2
- package/src/actions/create-vault-secret.ts +5 -0
- package/src/actions/navigate.ts +13 -4
- package/src/actions/update-vault-secret.ts +3 -0
- package/src/components/layout/Layout.tsx +31 -4
- package/src/hooks/use-navigation-state.ts +25 -4
- package/src/routes/index.ts +1 -0
- package/src/routes/pages/chat.tsx +59 -1
package/src/actions/navigate.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* Options:
|
|
13
13
|
* --view View name to navigate to
|
|
14
14
|
* --path URL path to navigate to
|
|
15
|
+
* --threadId Chat thread ID to open on the chat route
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
18
|
import { defineAction } from "@agent-native/core";
|
|
@@ -20,7 +21,7 @@ import { writeAppState } from "@agent-native/core/application-state";
|
|
|
20
21
|
|
|
21
22
|
export default defineAction({
|
|
22
23
|
description:
|
|
23
|
-
"Navigate the UI to a specific view or path. Writes a navigate command to application state which the UI reads and auto-deletes.",
|
|
24
|
+
"Navigate the UI to a specific view or path. Use threadId to open a specific chat thread on the chat route. Writes a navigate command to application state which the UI reads and auto-deletes.",
|
|
24
25
|
schema: z.object({
|
|
25
26
|
view: z
|
|
26
27
|
.string()
|
|
@@ -29,16 +30,24 @@ export default defineAction({
|
|
|
29
30
|
"Named dispatch view to navigate to. Built-in views include chat, overview, apps, metrics, new-app, vault, integrations, messaging, workspace, agents, destinations, identities, approvals, audit, thread-debug, dreams, and team. Generated Dispatch extension tabs can also use their nav item id.",
|
|
30
31
|
),
|
|
31
32
|
path: z.string().optional().describe("URL path to navigate to"),
|
|
33
|
+
threadId: z
|
|
34
|
+
.string()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Chat thread ID to open on the chat route"),
|
|
32
37
|
}),
|
|
33
38
|
http: false,
|
|
34
39
|
run: async (args) => {
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
const threadId = args.threadId?.trim();
|
|
41
|
+
if (!args.view && !args.path && !threadId) {
|
|
42
|
+
return "Error: At least --view, --path, or --threadId is required.";
|
|
37
43
|
}
|
|
38
44
|
const nav: Record<string, string> = {};
|
|
45
|
+
// A thread id without an explicit view implies the chat surface.
|
|
39
46
|
if (args.view) nav.view = args.view;
|
|
47
|
+
else if (threadId) nav.view = "chat";
|
|
40
48
|
if (args.path) nav.path = args.path;
|
|
49
|
+
if (threadId) nav.threadId = threadId;
|
|
41
50
|
await writeAppState("navigate", nav);
|
|
42
|
-
return `Navigating to ${args.view || args.path}`;
|
|
51
|
+
return `Navigating to ${args.view || args.path || `chat thread ${threadId}`}`;
|
|
43
52
|
},
|
|
44
53
|
});
|
|
@@ -41,5 +41,8 @@ export default defineAction({
|
|
|
41
41
|
description !== undefined,
|
|
42
42
|
"At least one secret field must be updated",
|
|
43
43
|
),
|
|
44
|
+
// Carries a secret `value`. Record THAT the secret changed, never the value —
|
|
45
|
+
// keep the audit trail from becoming a second credential store.
|
|
46
|
+
audit: { recordInputs: false },
|
|
44
47
|
run: async (args) => updateSecret(args.id, args),
|
|
45
48
|
});
|
|
@@ -286,6 +286,21 @@ function dispatchNavLinkTarget(path: string): string {
|
|
|
286
286
|
return routerHasBasename ? path : appPath(path);
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
+
function chatThreadPath(threadId: string): string {
|
|
290
|
+
return `/chat/${encodeURIComponent(threadId)}`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function threadIdFromPath(pathname: string): string | null {
|
|
294
|
+
const match = pathname.match(/^\/chat\/([^/]+)/);
|
|
295
|
+
if (!match) return null;
|
|
296
|
+
try {
|
|
297
|
+
const value = decodeURIComponent(match[1]).trim();
|
|
298
|
+
return value || null;
|
|
299
|
+
} catch {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
289
304
|
function formatThreadAge(updatedAt: number) {
|
|
290
305
|
const diffMs = Math.max(0, Date.now() - updatedAt);
|
|
291
306
|
const minutes = Math.floor(diffMs / 60_000);
|
|
@@ -315,6 +330,7 @@ function threadUpdatedAt(thread: ChatThreadSummary) {
|
|
|
315
330
|
|
|
316
331
|
function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
|
|
317
332
|
const navigate = useNavigate();
|
|
333
|
+
const location = useLocation();
|
|
318
334
|
const {
|
|
319
335
|
threads,
|
|
320
336
|
activeThreadId,
|
|
@@ -370,7 +386,9 @@ function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
|
|
|
370
386
|
switchThread(threadId);
|
|
371
387
|
navigateWithAgentChatViewTransition(
|
|
372
388
|
navigate,
|
|
373
|
-
dispatchNavLinkTarget(
|
|
389
|
+
dispatchNavLinkTarget(
|
|
390
|
+
options?.isNew ? "/chat" : chatThreadPath(threadId),
|
|
391
|
+
),
|
|
374
392
|
);
|
|
375
393
|
onNavigate?.();
|
|
376
394
|
window.requestAnimationFrame(() => {
|
|
@@ -439,7 +457,11 @@ function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
|
|
|
439
457
|
<div className="grid gap-0.5">
|
|
440
458
|
{visibleThreads.length > 0 ? (
|
|
441
459
|
visibleThreads.map((thread) => {
|
|
442
|
-
const
|
|
460
|
+
const localPathname = localDispatchPath(location.pathname);
|
|
461
|
+
const isActive =
|
|
462
|
+
thread.id ===
|
|
463
|
+
(threadIdFromPath(localPathname) ??
|
|
464
|
+
(localPathname === "/chat" ? null : activeThreadId));
|
|
443
465
|
const isRenaming = thread.id === renamingThreadId;
|
|
444
466
|
return (
|
|
445
467
|
<div
|
|
@@ -692,13 +714,18 @@ export function Layout({
|
|
|
692
714
|
const navigate = useNavigate();
|
|
693
715
|
const [mobileOpen, setMobileOpen] = useState(false);
|
|
694
716
|
const localPathname = localDispatchPath(location.pathname);
|
|
695
|
-
const isChatRoute =
|
|
717
|
+
const isChatRoute =
|
|
718
|
+
localPathname === "/chat" || localPathname.startsWith("/chat/");
|
|
696
719
|
const chatHomeHandoffActive = useAgentChatHomeHandoff({
|
|
697
720
|
storageKey: "dispatch",
|
|
698
721
|
activePath: localPathname,
|
|
699
722
|
enabled: !isChatRoute,
|
|
700
723
|
});
|
|
701
|
-
useAgentChatHomeHandoffLinks({
|
|
724
|
+
useAgentChatHomeHandoffLinks({
|
|
725
|
+
storageKey: "dispatch",
|
|
726
|
+
isChatPath: (pathname) =>
|
|
727
|
+
pathname === "/chat" || pathname.startsWith("/chat/"),
|
|
728
|
+
});
|
|
702
729
|
|
|
703
730
|
if (CHROMELESS_PATHS.some((path) => localPathname === path)) {
|
|
704
731
|
return <>{children}</>;
|
|
@@ -21,6 +21,7 @@ export interface NavigationState {
|
|
|
21
21
|
dreamId?: string;
|
|
22
22
|
sourceId?: string;
|
|
23
23
|
query?: string;
|
|
24
|
+
threadId?: string;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export function useNavigationState(extensions?: DispatchExtensionConfig) {
|
|
@@ -82,8 +83,8 @@ export function useNavigationState(extensions?: DispatchExtensionConfig) {
|
|
|
82
83
|
: resolvedPath;
|
|
83
84
|
const nextPath = routerPath(path);
|
|
84
85
|
if (
|
|
85
|
-
routerPath(location.pathname)
|
|
86
|
-
pathnameFromPath(nextPath)
|
|
86
|
+
isChatPath(routerPath(location.pathname)) &&
|
|
87
|
+
!isChatPath(pathnameFromPath(nextPath))
|
|
87
88
|
) {
|
|
88
89
|
markAgentChatHomeHandoff("dispatch");
|
|
89
90
|
}
|
|
@@ -96,6 +97,21 @@ function pathnameFromPath(path: string): string {
|
|
|
96
97
|
return path.split(/[?#]/, 1)[0] || "/";
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
function isChatPath(pathname: string): boolean {
|
|
101
|
+
return pathname === "/chat" || pathname.startsWith("/chat/");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function threadIdFromPath(pathname: string): string | null {
|
|
105
|
+
const match = pathname.match(/^\/chat\/([^/]+)/);
|
|
106
|
+
if (!match) return null;
|
|
107
|
+
try {
|
|
108
|
+
const value = decodeURIComponent(match[1]).trim();
|
|
109
|
+
return value || null;
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
99
115
|
export function buildDispatchNavigationState(
|
|
100
116
|
pathname: string,
|
|
101
117
|
search = "",
|
|
@@ -106,6 +122,9 @@ export function buildDispatchNavigationState(
|
|
|
106
122
|
path: appPath(pathname),
|
|
107
123
|
};
|
|
108
124
|
|
|
125
|
+
const threadId = threadIdFromPath(pathname);
|
|
126
|
+
if (threadId) state.threadId = threadId;
|
|
127
|
+
|
|
109
128
|
const extensionId = extensionIdFromPathname(pathname);
|
|
110
129
|
if (extensionId) {
|
|
111
130
|
state.view = "extensions";
|
|
@@ -206,12 +225,14 @@ function resolveView(
|
|
|
206
225
|
function resolvePath(
|
|
207
226
|
view?: string,
|
|
208
227
|
extensions?: DispatchExtensionConfig,
|
|
209
|
-
command?: Pick<NavigationState, "extensionId">,
|
|
228
|
+
command?: Pick<NavigationState, "extensionId" | "threadId">,
|
|
210
229
|
): string | undefined {
|
|
211
230
|
switch (view) {
|
|
212
231
|
case "chat":
|
|
213
232
|
case "ask":
|
|
214
|
-
return
|
|
233
|
+
return command?.threadId && command.threadId.trim()
|
|
234
|
+
? `/chat/${encodeURIComponent(command.threadId.trim())}`
|
|
235
|
+
: "/chat";
|
|
215
236
|
case "overview":
|
|
216
237
|
return "/overview";
|
|
217
238
|
case "apps":
|
package/src/routes/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ import { type RouteConfig, route, index } from "@react-router/dev/routes";
|
|
|
32
32
|
export const dispatchRoutes: RouteConfig = [
|
|
33
33
|
index("./pages/_index.js"),
|
|
34
34
|
route("chat", "./pages/chat.js"),
|
|
35
|
+
route("chat/:threadId", "./pages/chat.js"),
|
|
35
36
|
route("overview", "./pages/overview.js"),
|
|
36
37
|
route("metrics", "./pages/metrics.js"),
|
|
37
38
|
route("apps", "./pages/apps.js"),
|
|
@@ -1,11 +1,53 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
2
|
import { useLocation, useNavigate } from "react-router";
|
|
3
3
|
import {
|
|
4
4
|
AgentChatSurface,
|
|
5
|
+
appBasePath,
|
|
6
|
+
appPath,
|
|
5
7
|
markAgentChatHomeHandoff,
|
|
6
8
|
} from "@agent-native/core/client";
|
|
7
9
|
import { submitOverviewPrompt } from "@/lib/overview-chat";
|
|
8
10
|
|
|
11
|
+
function chatThreadPath(threadId: string | null): string {
|
|
12
|
+
return threadId ? `/chat/${encodeURIComponent(threadId)}` : "/chat";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function stripBasePath(pathname: string): string {
|
|
16
|
+
const basePath = appBasePath();
|
|
17
|
+
if (!basePath) return pathname;
|
|
18
|
+
if (pathname === basePath) return "/";
|
|
19
|
+
if (pathname.startsWith(`${basePath}/`)) {
|
|
20
|
+
return pathname.slice(basePath.length) || "/";
|
|
21
|
+
}
|
|
22
|
+
return pathname;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// The chat surface renders for both `/chat` and the `/chat/:threadId` deep
|
|
26
|
+
// link. The thread id is read from the pathname (not `useParams`) because the
|
|
27
|
+
// param is owned by the nested deep-link route, not this leaf component.
|
|
28
|
+
function threadIdFromPath(pathname: string): string | null {
|
|
29
|
+
const match = stripBasePath(pathname).match(/^\/chat\/([^/]+)/);
|
|
30
|
+
if (!match) return null;
|
|
31
|
+
try {
|
|
32
|
+
const value = decodeURIComponent(match[1]).trim();
|
|
33
|
+
return value || null;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Mirror the basename handling Dispatch's nav links use: pass a router-local
|
|
40
|
+
// path when the live URL is already under the mount, otherwise prefix it.
|
|
41
|
+
function dispatchNavTarget(path: string): string {
|
|
42
|
+
if (typeof window === "undefined") return path;
|
|
43
|
+
const basePath = appBasePath();
|
|
44
|
+
if (!basePath) return path;
|
|
45
|
+
const pathname = window.location.pathname;
|
|
46
|
+
const routerHasBasename =
|
|
47
|
+
pathname === basePath || pathname.startsWith(`${basePath}/`);
|
|
48
|
+
return routerHasBasename ? path : appPath(path);
|
|
49
|
+
}
|
|
50
|
+
|
|
9
51
|
interface DispatchChatLocationState {
|
|
10
52
|
dispatchPrompt?: {
|
|
11
53
|
id?: string | number;
|
|
@@ -25,7 +67,22 @@ export function meta() {
|
|
|
25
67
|
export default function ChatRoute() {
|
|
26
68
|
const location = useLocation();
|
|
27
69
|
const navigate = useNavigate();
|
|
70
|
+
const routeThreadId = threadIdFromPath(location.pathname);
|
|
28
71
|
const handledStateIds = useRef(new Set<string>());
|
|
72
|
+
|
|
73
|
+
const navigateThreadUrl = useCallback(
|
|
74
|
+
(path: string, options?: { replace?: boolean }) =>
|
|
75
|
+
navigate(dispatchNavTarget(path), options),
|
|
76
|
+
[navigate],
|
|
77
|
+
);
|
|
78
|
+
const threadUrlSync = useMemo(
|
|
79
|
+
() => ({
|
|
80
|
+
routeThreadId: routeThreadId ?? null,
|
|
81
|
+
getPath: chatThreadPath,
|
|
82
|
+
navigate: navigateThreadUrl,
|
|
83
|
+
}),
|
|
84
|
+
[routeThreadId, navigateThreadUrl],
|
|
85
|
+
);
|
|
29
86
|
const state = location.state as DispatchChatLocationState | null;
|
|
30
87
|
const prompt = state?.dispatchPrompt;
|
|
31
88
|
const thread = state?.dispatchThread;
|
|
@@ -92,6 +149,7 @@ export default function ChatRoute() {
|
|
|
92
149
|
className="dispatch-chat-panel"
|
|
93
150
|
defaultMode="chat"
|
|
94
151
|
storageKey="dispatch"
|
|
152
|
+
threadUrlSync={threadUrlSync}
|
|
95
153
|
showHeader={false}
|
|
96
154
|
showTabBar={false}
|
|
97
155
|
dynamicSuggestions={false}
|