@agent-native/core 0.172.10 → 0.173.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/corpus/README.md +1 -1
- package/corpus/templates/calendar/actions/view-screen.ts +0 -5
- package/corpus/templates/calendar/app/components/calendar/CommandPalette.tsx +1 -1
- package/corpus/templates/clips/app/components/player/video-player.tsx +3 -1
- package/corpus/templates/clips/changelog/2026-08-24-shared-clips-now-expose-agent-readable-transcript-and-frame-metadata-from-public-links.md +6 -0
- package/corpus/templates/clips/server/routes/[...page].get.ts +172 -3
- package/corpus/templates/plan/app/components/layout/Header.tsx +1 -1
- package/corpus/templates/plan/app/components/layout/Layout.tsx +19 -3
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +11 -10
- package/corpus/templates/plan/app/hooks/use-navigation-state.ts +10 -9
- package/corpus/templates/plan/app/pages/PlansPage.tsx +1 -1
- package/corpus/templates/plan/app/root.tsx +9 -8
- package/corpus/templates/plan/app/routes/_index.tsx +6 -30
- package/corpus/templates/plan/app/routes/chat.tsx +34 -0
- package/corpus/templates/plan/server/plugins/auth.ts +1 -0
- package/dist/client/AgentPanel.d.ts +3 -1
- package/dist/client/AgentPanel.js +67 -46
- package/dist/client/AssistantChat.d.ts +8 -0
- package/dist/client/AssistantChat.js +2 -1
- package/dist/client/agent-chat/index.d.ts +2 -0
- package/dist/client/agent-chat/index.js +2 -0
- package/dist/client/chat/index.d.ts +2 -0
- package/dist/client/chat/index.js +2 -0
- package/dist/client/chat/message-components.d.ts +5 -14
- package/dist/client/chat/message-components.js +15 -8
- package/dist/client/chat/tool-call-display.js +24 -11
- package/dist/client/thinking-display.d.ts +23 -0
- package/dist/client/thinking-display.js +86 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.js +4 -0
- package/dist/localization/core-messages/de-DE.js +4 -0
- package/dist/localization/core-messages/en-US.d.ts +4 -0
- package/dist/localization/core-messages/en-US.js +4 -0
- package/dist/localization/core-messages/es-ES.js +4 -0
- package/dist/localization/core-messages/fr-FR.js +4 -0
- package/dist/localization/core-messages/hi-IN.js +4 -0
- package/dist/localization/core-messages/ja-JP.js +4 -0
- package/dist/localization/core-messages/ko-KR.js +4 -0
- package/dist/localization/core-messages/pt-BR.js +4 -0
- package/dist/localization/core-messages/zh-CN.js +4 -0
- package/dist/localization/core-messages/zh-TW.js +4 -0
- package/dist/localization/core-messages.d.ts +4 -0
- package/dist/observability/posthog-ai.d.ts +6 -0
- package/dist/observability/posthog-ai.js +6 -0
- package/dist/observability/routes.d.ts +3 -3
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/thinking-display.d.ts +23 -0
- package/dist/shared/thinking-display.js +31 -0
- package/dist/tracking/providers.js +22 -1
- package/package.json +3 -3
package/corpus/README.md
CHANGED
|
@@ -55,11 +55,6 @@ async function fetchEventsForRange(
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function dateKeyFromParts(date: Date): string {
|
|
59
|
-
const pad = (value: number) => String(value).padStart(2, "0");
|
|
60
|
-
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
58
|
export default defineAction({
|
|
64
59
|
description:
|
|
65
60
|
"See what the user is currently looking at on screen. Returns the current view, date range, and visible events. Always call this first before taking any action.",
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
IconExternalLink,
|
|
14
14
|
} from "@tabler/icons-react";
|
|
15
15
|
import * as chrono from "chrono-node";
|
|
16
|
-
import { format,
|
|
16
|
+
import { format, parse, isValid } from "date-fns";
|
|
17
17
|
|
|
18
18
|
import { cn } from "@/lib/utils";
|
|
19
19
|
|
|
@@ -1629,6 +1629,8 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
1629
1629
|
const fullscreenMenuContainer = isFullscreen ? containerRef.current : null;
|
|
1630
1630
|
|
|
1631
1631
|
const showThroughoutCta = cta && cta.placement === "throughout";
|
|
1632
|
+
const controlsVisible =
|
|
1633
|
+
showControls || !isPlaying || isPlayPending || isBuffering;
|
|
1632
1634
|
// Mobile Safari may defer loadeddata/canplay until playback starts. Keep
|
|
1633
1635
|
// the paused state actionable even when those readiness events have not
|
|
1634
1636
|
// fired yet; once the user asks to play, the pending/buffering states give
|
|
@@ -2067,7 +2069,7 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
2067
2069
|
<div
|
|
2068
2070
|
className={cn(
|
|
2069
2071
|
"absolute inset-x-0 bottom-0 z-20 transition-opacity duration-200",
|
|
2070
|
-
|
|
2072
|
+
controlsVisible ? "opacity-100" : "opacity-0 pointer-events-none",
|
|
2071
2073
|
)}
|
|
2072
2074
|
>
|
|
2073
2075
|
<PlayerControls
|
|
@@ -1,21 +1,190 @@
|
|
|
1
|
+
import { verifyScopedAgentAccessToken } from "@agent-native/core/server";
|
|
1
2
|
import { createH3SSRHandler } from "@agent-native/core/server/ssr-handler";
|
|
2
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
injectDocumentMarkup,
|
|
5
|
+
safeJsonForHtml,
|
|
6
|
+
} from "@agent-native/core/shared";
|
|
7
|
+
import { eq } from "drizzle-orm";
|
|
8
|
+
import {
|
|
9
|
+
defineEventHandler,
|
|
10
|
+
getQuery,
|
|
11
|
+
getRequestURL,
|
|
12
|
+
setResponseHeader,
|
|
13
|
+
type H3Event,
|
|
14
|
+
} from "h3";
|
|
3
15
|
|
|
16
|
+
import {
|
|
17
|
+
buildAgentApiUrls,
|
|
18
|
+
buildAgentDiscoveryPayload,
|
|
19
|
+
CLIP_AGENT_ACCESS_TOKEN_PREFIX,
|
|
20
|
+
CLIPS_AGENT_ACCESS_PARAM,
|
|
21
|
+
} from "../../shared/agent-context.js";
|
|
22
|
+
import { getDb, schema } from "../db/index.js";
|
|
4
23
|
import {
|
|
5
24
|
MEDIA_CAPTURE_PERMISSIONS_POLICY,
|
|
6
25
|
withMediaCapturePermissions,
|
|
7
26
|
} from "../lib/media-permissions.js";
|
|
27
|
+
import {
|
|
28
|
+
getServerAppBasePath,
|
|
29
|
+
queryString,
|
|
30
|
+
} from "../lib/public-agent-context.js";
|
|
31
|
+
import { isRecordingExpired } from "../lib/recording-page-access.js";
|
|
8
32
|
|
|
9
33
|
const ssrHandler = createH3SSRHandler(
|
|
10
34
|
() => import("virtual:react-router/server-build"),
|
|
11
35
|
);
|
|
12
36
|
|
|
13
|
-
|
|
14
|
-
const
|
|
37
|
+
function stripAppBasePath(pathname: string): string {
|
|
38
|
+
const basePath = getServerAppBasePath();
|
|
39
|
+
if (!basePath) return pathname;
|
|
40
|
+
if (pathname === basePath) return "/";
|
|
41
|
+
return pathname.startsWith(`${basePath}/`)
|
|
42
|
+
? pathname.slice(basePath.length) || "/"
|
|
43
|
+
: pathname;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function clipIdFromPath(pathname: string): string | null {
|
|
47
|
+
const match = stripAppBasePath(pathname).match(/^\/(?:share|r)\/([^/]+)\/?$/);
|
|
48
|
+
if (!match?.[1]) return null;
|
|
49
|
+
try {
|
|
50
|
+
return decodeURIComponent(match[1]);
|
|
51
|
+
} catch {
|
|
52
|
+
return match[1];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function escapeHtmlAttribute(value: string): string {
|
|
57
|
+
return value.replace(/[&<>\"']/g, (character) => {
|
|
58
|
+
switch (character) {
|
|
59
|
+
case "&":
|
|
60
|
+
return "&";
|
|
61
|
+
case "<":
|
|
62
|
+
return "<";
|
|
63
|
+
case ">":
|
|
64
|
+
return ">";
|
|
65
|
+
case '\"':
|
|
66
|
+
return """;
|
|
67
|
+
case "'":
|
|
68
|
+
return "'";
|
|
69
|
+
default:
|
|
70
|
+
return character;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function buildClipAgentDiscovery(event: H3Event): Promise<{
|
|
76
|
+
markup: string;
|
|
77
|
+
noReferrer: boolean;
|
|
78
|
+
} | null> {
|
|
79
|
+
const requestUrl = getRequestURL(event);
|
|
80
|
+
const recordingId = clipIdFromPath(requestUrl.pathname);
|
|
81
|
+
if (!recordingId) return null;
|
|
82
|
+
|
|
83
|
+
const [recording] = await getDb()
|
|
84
|
+
.select({
|
|
85
|
+
id: schema.recordings.id,
|
|
86
|
+
title: schema.recordings.title,
|
|
87
|
+
status: schema.recordings.status,
|
|
88
|
+
visibility: schema.recordings.visibility,
|
|
89
|
+
password: schema.recordings.password,
|
|
90
|
+
expiresAt: schema.recordings.expiresAt,
|
|
91
|
+
archivedAt: schema.recordings.archivedAt,
|
|
92
|
+
trashedAt: schema.recordings.trashedAt,
|
|
93
|
+
})
|
|
94
|
+
.from(schema.recordings)
|
|
95
|
+
.where(eq(schema.recordings.id, recordingId))
|
|
96
|
+
.limit(1);
|
|
97
|
+
|
|
98
|
+
if (
|
|
99
|
+
!recording ||
|
|
100
|
+
recording.archivedAt ||
|
|
101
|
+
recording.trashedAt ||
|
|
102
|
+
isRecordingExpired(recording.expiresAt)
|
|
103
|
+
) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const query = getQuery(event);
|
|
108
|
+
const suppliedToken =
|
|
109
|
+
queryString(query[CLIPS_AGENT_ACCESS_PARAM]) || queryString(query.t);
|
|
110
|
+
const tokenGrantsAgentAccess = suppliedToken
|
|
111
|
+
? verifyScopedAgentAccessToken(suppliedToken, {
|
|
112
|
+
resourceKind: CLIP_AGENT_ACCESS_TOKEN_PREFIX,
|
|
113
|
+
resourceId: recording.id,
|
|
114
|
+
}).ok
|
|
115
|
+
: false;
|
|
116
|
+
const anonymousAccess =
|
|
117
|
+
recording.visibility === "public" && !recording.password;
|
|
118
|
+
if (!anonymousAccess && !tokenGrantsAgentAccess) return null;
|
|
119
|
+
|
|
120
|
+
const token = tokenGrantsAgentAccess ? suppliedToken : undefined;
|
|
121
|
+
const agentContextUrl = buildAgentApiUrls(recording.id, {
|
|
122
|
+
origin: requestUrl.origin,
|
|
123
|
+
basePath: getServerAppBasePath(),
|
|
124
|
+
token,
|
|
125
|
+
}).contextUrl;
|
|
126
|
+
const discovery = buildAgentDiscoveryPayload({
|
|
127
|
+
recordingId: recording.id,
|
|
128
|
+
title: recording.title,
|
|
129
|
+
status: recording.status,
|
|
130
|
+
agentContextUrl,
|
|
131
|
+
});
|
|
132
|
+
const contextHref = escapeHtmlAttribute(agentContextUrl);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
markup: `<link rel="alternate" type="application/json" href="${contextHref}" title="Agent-readable clip context"><script type="application/agent-native+json" id="clips-agent-context">${safeJsonForHtml(discovery)}</script>`,
|
|
136
|
+
noReferrer: tokenGrantsAgentAccess,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function injectClipAgentDiscovery(html: string, markup: string): string {
|
|
141
|
+
const scriptId = 'id="clips-agent-context"';
|
|
142
|
+
const scriptMarkup = html.includes(scriptId)
|
|
143
|
+
? ""
|
|
144
|
+
: markup.slice(markup.indexOf("<script"));
|
|
145
|
+
const linkMarkup = markup.slice(0, markup.indexOf("<script"));
|
|
146
|
+
const injection = `${html.includes(linkMarkup) ? "" : linkMarkup}${scriptMarkup}`;
|
|
147
|
+
return injectDocumentMarkup(html, injection, { target: "head" });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function withClipsResponseHeaders(
|
|
151
|
+
event: H3Event,
|
|
152
|
+
response: Response,
|
|
153
|
+
): Response {
|
|
15
154
|
setResponseHeader(
|
|
16
155
|
event,
|
|
17
156
|
"Permissions-Policy",
|
|
18
157
|
MEDIA_CAPTURE_PERMISSIONS_POLICY,
|
|
19
158
|
);
|
|
20
159
|
return withMediaCapturePermissions(response);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export default defineEventHandler(async (event) => {
|
|
163
|
+
const response = (await ssrHandler(event)) as Response;
|
|
164
|
+
const discovery = await buildClipAgentDiscovery(event);
|
|
165
|
+
if (!discovery || !response.ok)
|
|
166
|
+
return withClipsResponseHeaders(event, response);
|
|
167
|
+
|
|
168
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
169
|
+
if (!contentType.includes("text/html")) {
|
|
170
|
+
return withClipsResponseHeaders(event, response);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const headers = new Headers(response.headers);
|
|
174
|
+
headers.delete("content-length");
|
|
175
|
+
if (discovery.noReferrer) {
|
|
176
|
+
headers.set("Referrer-Policy", "no-referrer");
|
|
177
|
+
setResponseHeader(event, "Referrer-Policy", "no-referrer");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const html = await response.text();
|
|
181
|
+
const discoveredResponse = new Response(
|
|
182
|
+
injectClipAgentDiscovery(html, discovery.markup),
|
|
183
|
+
{
|
|
184
|
+
status: response.status,
|
|
185
|
+
statusText: response.statusText,
|
|
186
|
+
headers,
|
|
187
|
+
},
|
|
188
|
+
);
|
|
189
|
+
return withClipsResponseHeaders(event, discoveredResponse);
|
|
21
190
|
});
|
|
@@ -10,7 +10,7 @@ import { useLocation } from "react-router";
|
|
|
10
10
|
import { APP_TITLE } from "@/lib/app-config";
|
|
11
11
|
|
|
12
12
|
const pageTitleKeys: Record<string, string> = {
|
|
13
|
-
"/": "header.plan",
|
|
13
|
+
"/chat": "header.plan",
|
|
14
14
|
"/plans": "header.plan",
|
|
15
15
|
"/agent": "settings.agentTitle",
|
|
16
16
|
"/settings": "header.settings",
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
useAgentChatHomeHandoff,
|
|
5
5
|
useAgentChatHomeHandoffLinks,
|
|
6
6
|
} from "@agent-native/core/client/agent-chat";
|
|
7
|
+
import { useSession } from "@agent-native/core/client/hooks";
|
|
7
8
|
import { useT } from "@agent-native/core/client/i18n";
|
|
8
9
|
import { HeaderActionsProvider } from "@agent-native/toolkit/app-shell";
|
|
9
10
|
import { IconMenu2 } from "@tabler/icons-react";
|
|
@@ -47,6 +48,7 @@ function isPlanDetailRoute(pathname: string): boolean {
|
|
|
47
48
|
|
|
48
49
|
export function Layout({ children }: LayoutProps) {
|
|
49
50
|
const location = useLocation();
|
|
51
|
+
const pathname = location.pathname.replace(/\/+$/, "") || "/";
|
|
50
52
|
const t = useT();
|
|
51
53
|
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
|
52
54
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {
|
|
@@ -87,19 +89,22 @@ export function Layout({ children }: LayoutProps) {
|
|
|
87
89
|
|
|
88
90
|
const ownsToolbar = routeOwnsToolbar(location.pathname);
|
|
89
91
|
const planDetailRoute = isPlanDetailRoute(location.pathname);
|
|
90
|
-
const chatRoute =
|
|
92
|
+
const chatRoute = pathname === "/chat";
|
|
93
|
+
const { session, isLoading: sessionLoading } = useSession();
|
|
91
94
|
const chatHomeHandoffActive = useAgentChatHomeHandoff({
|
|
92
95
|
storageKey: "plans",
|
|
93
|
-
activePath:
|
|
96
|
+
activePath: pathname,
|
|
94
97
|
enabled: !chatRoute,
|
|
95
98
|
});
|
|
96
99
|
const chatHomeHandoffPending = isAgentChatHomeHandoffActive("plans");
|
|
97
100
|
useAgentChatHomeHandoffLinks({
|
|
98
101
|
storageKey: "plans",
|
|
99
|
-
chatPath: "/",
|
|
102
|
+
chatPath: "/chat",
|
|
103
|
+
isChatPath: (path) => (path.replace(/\/+$/, "") || "/") === "/chat",
|
|
100
104
|
requireActiveHandoff: true,
|
|
101
105
|
});
|
|
102
106
|
const hideAppNavigation = planDetailRoute && planReaderImmersive;
|
|
107
|
+
const hideAppHeader = pathname === "/plans" && !sessionLoading && !session;
|
|
103
108
|
const effectiveSidebarCollapsed = chatRoute
|
|
104
109
|
? chatSidebarCollapsed
|
|
105
110
|
: sidebarCollapsed;
|
|
@@ -168,6 +173,17 @@ export function Layout({ children }: LayoutProps) {
|
|
|
168
173
|
</button>
|
|
169
174
|
</div>
|
|
170
175
|
)
|
|
176
|
+
) : hideAppHeader ? (
|
|
177
|
+
<div className="flex h-12 items-center border-b border-border px-4 md:hidden shrink-0">
|
|
178
|
+
<button
|
|
179
|
+
type="button"
|
|
180
|
+
onClick={() => setMobileSidebarOpen(true)}
|
|
181
|
+
aria-label={t("sidebar.openNavigation")}
|
|
182
|
+
className="flex h-9 w-9 cursor-pointer items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-accent"
|
|
183
|
+
>
|
|
184
|
+
<IconMenu2 className="h-4 w-4" />
|
|
185
|
+
</button>
|
|
186
|
+
</div>
|
|
171
187
|
) : (
|
|
172
188
|
<Header onOpenMobileSidebar={() => setMobileSidebarOpen(true)} />
|
|
173
189
|
)}
|
|
@@ -72,7 +72,7 @@ function buildBrandingCustomizationMessage(request: string) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const navItems = [
|
|
75
|
-
{ icon: IconMessageCircle, labelKey: "navigation.ask", href: "/" },
|
|
75
|
+
{ icon: IconMessageCircle, labelKey: "navigation.ask", href: "/chat" },
|
|
76
76
|
{ icon: IconClipboardCheck, labelKey: "navigation.plan", href: "/plans" },
|
|
77
77
|
];
|
|
78
78
|
|
|
@@ -216,7 +216,7 @@ function PlanChatsSection({
|
|
|
216
216
|
|
|
217
217
|
function openThread(threadId: string, options?: { isNew?: boolean }) {
|
|
218
218
|
switchThread(threadId);
|
|
219
|
-
navigateWithAgentChatViewTransition(navigate, "/");
|
|
219
|
+
navigateWithAgentChatViewTransition(navigate, "/chat");
|
|
220
220
|
window.requestAnimationFrame(() => {
|
|
221
221
|
window.dispatchEvent(
|
|
222
222
|
new CustomEvent("agent-chat:open-thread", {
|
|
@@ -536,6 +536,7 @@ export function Sidebar({
|
|
|
536
536
|
onCollapsedChange,
|
|
537
537
|
}: SidebarProps) {
|
|
538
538
|
const location = useLocation();
|
|
539
|
+
const pathname = location.pathname.replace(/\/+$/, "") || "/";
|
|
539
540
|
const { session, isLoading: sessionLoading } = useSession();
|
|
540
541
|
const t = useT();
|
|
541
542
|
const returnPath = planReturnPathFromLocation(location);
|
|
@@ -652,13 +653,13 @@ export function Sidebar({
|
|
|
652
653
|
{navItems.map((item) => {
|
|
653
654
|
const Icon = item.icon;
|
|
654
655
|
const isActive =
|
|
655
|
-
item.href === "/"
|
|
656
|
-
?
|
|
656
|
+
item.href === "/chat"
|
|
657
|
+
? pathname === "/chat"
|
|
657
658
|
: item.href === "/plans"
|
|
658
|
-
?
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
:
|
|
659
|
+
? pathname.startsWith("/plans") ||
|
|
660
|
+
pathname.startsWith("/recaps") ||
|
|
661
|
+
pathname.startsWith("/local-plans")
|
|
662
|
+
: pathname.startsWith(item.href);
|
|
662
663
|
const link = (
|
|
663
664
|
<Link
|
|
664
665
|
to={item.href}
|
|
@@ -681,7 +682,7 @@ export function Sidebar({
|
|
|
681
682
|
return (
|
|
682
683
|
<div key={item.href}>
|
|
683
684
|
{link}
|
|
684
|
-
{item.href === "/" ? (
|
|
685
|
+
{item.href === "/chat" ? (
|
|
685
686
|
<PlanChatsSection collapsed={collapsed} open={isActive} />
|
|
686
687
|
) : null}
|
|
687
688
|
{item.href === "/plans" && isActive ? (
|
|
@@ -695,7 +696,7 @@ export function Sidebar({
|
|
|
695
696
|
<nav className="grid shrink-0 gap-1 px-2 py-1">
|
|
696
697
|
{bottomNavItems.map((item) => {
|
|
697
698
|
const Icon = item.icon;
|
|
698
|
-
const isActive =
|
|
699
|
+
const isActive = pathname.startsWith(item.href);
|
|
699
700
|
const link = (
|
|
700
701
|
<Link
|
|
701
702
|
to={item.href}
|
|
@@ -122,22 +122,23 @@ export function useNavigationState() {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
function viewForPath(pathname: string): string {
|
|
125
|
+
const normalizedPathname = pathname.replace(/\/+$/, "") || "/";
|
|
125
126
|
// Recaps are a kind of plan; both detail routes map to the "plan" view so the
|
|
126
127
|
// agent's navigation/selection state is the same surface regardless of route.
|
|
127
128
|
if (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
normalizedPathname.startsWith("/plans/") ||
|
|
130
|
+
normalizedPathname.startsWith("/recaps/") ||
|
|
131
|
+
normalizedPathname.startsWith("/local-plans/")
|
|
131
132
|
) {
|
|
132
133
|
return "plan";
|
|
133
134
|
}
|
|
134
|
-
if (
|
|
135
|
+
if (normalizedPathname === "/chat") {
|
|
135
136
|
return "chat";
|
|
136
137
|
}
|
|
137
138
|
if (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
normalizedPathname.startsWith("/plans") ||
|
|
140
|
+
normalizedPathname.startsWith("/recaps") ||
|
|
141
|
+
normalizedPathname.startsWith("/local-plans")
|
|
141
142
|
) {
|
|
142
143
|
return "plans";
|
|
143
144
|
}
|
|
@@ -187,7 +188,7 @@ function localPathFromCommandPath(value: unknown): string | null {
|
|
|
187
188
|
function pathForView(view?: string): string {
|
|
188
189
|
switch (view) {
|
|
189
190
|
case "chat":
|
|
190
|
-
return "/";
|
|
191
|
+
return "/chat";
|
|
191
192
|
case "plan":
|
|
192
193
|
case "plans":
|
|
193
194
|
return "/plans";
|
|
@@ -198,7 +199,7 @@ function pathForView(view?: string): string {
|
|
|
198
199
|
case "team":
|
|
199
200
|
return "/settings/organization";
|
|
200
201
|
default:
|
|
201
|
-
return "/";
|
|
202
|
+
return "/chat";
|
|
202
203
|
}
|
|
203
204
|
}
|
|
204
205
|
|
|
@@ -7448,7 +7448,7 @@ function LoggedOutEmptyPlan() {
|
|
|
7448
7448
|
}, []);
|
|
7449
7449
|
|
|
7450
7450
|
return (
|
|
7451
|
-
<div className="flex min-h-full items-center justify-center overflow-auto p-4 sm:p-8">
|
|
7451
|
+
<div className="flex min-h-full -translate-y-[10vh] items-center justify-center overflow-auto p-4 sm:p-8">
|
|
7452
7452
|
<div className="flex w-full max-w-4xl flex-col items-center gap-3 py-8 text-center">
|
|
7453
7453
|
<h2 className="text-2xl font-semibold tracking-tight">
|
|
7454
7454
|
{t("plansPage.loggedOut.title")}
|
|
@@ -166,7 +166,7 @@ function AppContent() {
|
|
|
166
166
|
changelogKey="plan"
|
|
167
167
|
>
|
|
168
168
|
<CommandMenu.Group heading={t("root.commandActions")}>
|
|
169
|
-
<CommandMenu.Item onSelect={() => go("/")}>
|
|
169
|
+
<CommandMenu.Item onSelect={() => go("/chat")}>
|
|
170
170
|
{t("root.askPlan")}
|
|
171
171
|
</CommandMenu.Item>
|
|
172
172
|
<CommandMenu.Item onSelect={() => go("/plans")}>
|
|
@@ -223,14 +223,15 @@ function AppContent() {
|
|
|
223
223
|
export default function Root() {
|
|
224
224
|
const [queryClient] = useState(() => createAgentNativeQueryClient());
|
|
225
225
|
const location = useLocation();
|
|
226
|
+
const pathname = location.pathname.replace(/\/+$/, "") || "/";
|
|
226
227
|
const sessionBypass =
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
228
|
+
pathname === "/chat" ||
|
|
229
|
+
pathname === "/plans" ||
|
|
230
|
+
pathname.startsWith("/plans/") ||
|
|
231
|
+
pathname === "/recaps" ||
|
|
232
|
+
pathname.startsWith("/recaps/") ||
|
|
233
|
+
pathname === "/local-plans" ||
|
|
234
|
+
pathname.startsWith("/local-plans/");
|
|
234
235
|
const localPlanPrivacyRoute = !shouldCapturePlanContent(location.pathname);
|
|
235
236
|
return (
|
|
236
237
|
// Pass the plan-specific styled Toaster via `toaster` so only one sonner
|
|
@@ -1,34 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { PlanChatPage } from "@/pages/PlanChatPage";
|
|
1
|
+
import { withSsrHtmlContentType } from "@agent-native/core/shared";
|
|
2
|
+
import { redirect } from "react-router";
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"Open Source planning workspace for coding agents with visual plans, PR recaps, diagrams, wireframes, API specs, and prototypes.";
|
|
8
|
-
|
|
9
|
-
export function meta() {
|
|
10
|
-
return [
|
|
11
|
-
{ title: SEO_TITLE },
|
|
12
|
-
{
|
|
13
|
-
name: "description",
|
|
14
|
-
content: SEO_DESCRIPTION,
|
|
15
|
-
},
|
|
16
|
-
{ property: "og:title", content: SEO_TITLE },
|
|
17
|
-
{ property: "og:description", content: SEO_DESCRIPTION },
|
|
18
|
-
{ name: "twitter:card", content: "summary" },
|
|
19
|
-
{ name: "twitter:title", content: SEO_TITLE },
|
|
20
|
-
{ name: "twitter:description", content: SEO_DESCRIPTION },
|
|
21
|
-
];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function HydrateFallback() {
|
|
25
|
-
return (
|
|
26
|
-
<div className="flex items-center justify-center h-screen w-full">
|
|
27
|
-
<Spinner className="size-8 text-foreground" />
|
|
28
|
-
</div>
|
|
29
|
-
);
|
|
4
|
+
export function loader() {
|
|
5
|
+
return withSsrHtmlContentType(redirect("/plans"));
|
|
30
6
|
}
|
|
31
7
|
|
|
32
|
-
export default function
|
|
33
|
-
return
|
|
8
|
+
export default function IndexRedirect() {
|
|
9
|
+
return null;
|
|
34
10
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Spinner } from "@/components/ui/spinner";
|
|
2
|
+
import { APP_TITLE } from "@/lib/app-config";
|
|
3
|
+
import { PlanChatPage } from "@/pages/PlanChatPage";
|
|
4
|
+
|
|
5
|
+
const SEO_TITLE = `${APP_TITLE} - Open Source visual planning and PR recaps for coding agents`;
|
|
6
|
+
const SEO_DESCRIPTION =
|
|
7
|
+
"Open Source planning workspace for coding agents with visual plans, PR recaps, diagrams, wireframes, API specs, and prototypes.";
|
|
8
|
+
|
|
9
|
+
export function meta() {
|
|
10
|
+
return [
|
|
11
|
+
{ title: SEO_TITLE },
|
|
12
|
+
{
|
|
13
|
+
name: "description",
|
|
14
|
+
content: SEO_DESCRIPTION,
|
|
15
|
+
},
|
|
16
|
+
{ property: "og:title", content: SEO_TITLE },
|
|
17
|
+
{ property: "og:description", content: SEO_DESCRIPTION },
|
|
18
|
+
{ name: "twitter:card", content: "summary" },
|
|
19
|
+
{ name: "twitter:title", content: SEO_TITLE },
|
|
20
|
+
{ name: "twitter:description", content: SEO_DESCRIPTION },
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function HydrateFallback() {
|
|
25
|
+
return (
|
|
26
|
+
<div className="flex h-screen w-full items-center justify-center">
|
|
27
|
+
<Spinner className="size-8 text-foreground" />
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default function ChatRoute() {
|
|
33
|
+
return <PlanChatPage />;
|
|
34
|
+
}
|
|
@@ -264,12 +264,14 @@ export interface AgentSidebarProps {
|
|
|
264
264
|
agentPageHref?: string;
|
|
265
265
|
/** Suppress first-run onboarding while a deep-linked resource is open. */
|
|
266
266
|
suppressFirstRunOnboarding?: boolean;
|
|
267
|
+
/** Pin how much model reasoning the chat shows. Omit to let the reader choose. */
|
|
268
|
+
thinkingDisplay?: AssistantChatProps["thinkingDisplay"];
|
|
267
269
|
}
|
|
268
270
|
/**
|
|
269
271
|
* Wraps app content with a toggleable agent sidebar.
|
|
270
272
|
* Use AgentToggleButton in your header to open/close it.
|
|
271
273
|
*/
|
|
272
|
-
export declare function AgentSidebar({ children, enabled, emptyStateText, suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, onConnectProvider, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position, defaultOpen, animateMobile, animateDesktop, chatViewTransition, chatViewTransitionHandoff, storageKey, openStorageKey, restoreActiveThread, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning, onFullscreenRequest, scope, toggleScopeId, isolateHistoryByScope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding, }: AgentSidebarProps): React.JSX.Element;
|
|
274
|
+
export declare function AgentSidebar({ children, enabled, emptyStateText, suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, onConnectProvider, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position, defaultOpen, animateMobile, animateDesktop, chatViewTransition, chatViewTransitionHandoff, storageKey, openStorageKey, restoreActiveThread, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning, onFullscreenRequest, scope, toggleScopeId, isolateHistoryByScope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding, thinkingDisplay, }: AgentSidebarProps): React.JSX.Element;
|
|
273
275
|
/**
|
|
274
276
|
* Focus the agent chat composer input.
|
|
275
277
|
* Opens the sidebar if closed, then focuses the text input.
|