@agent-native/core 0.99.0 → 0.99.1
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/core/CHANGELOG.md +10 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +21 -0
- package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
- package/corpus/core/src/client/analytics.ts +13 -0
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +13 -0
- package/corpus/core/src/server/agent-teams.ts +17 -0
- package/corpus/core/src/server/google-oauth.ts +6 -2
- package/corpus/templates/analytics/app/assets/replay-cursor.svg +17 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +4 -3
- package/corpus/templates/analytics/app/global.css +17 -9
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +32 -4
- package/corpus/templates/analytics/app/pages/monitoring/errors/IssueDetail.tsx +1 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +46 -11
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
- package/corpus/templates/analytics/changelog/2026-07-13-all-time-dashboard-filters-no-longer-send-invalid-dates-to-b.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-13-error-issue-titles-wrap-instead-of-overflowing.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-13-saving-a-new-dashboard-view-no-longer-fails-with-a-server-er.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-13-session-replay-cursors-stay-hidden-until-their-first-recorde.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-13-session-replay-links-now-support-cmd-click-and-middle-click-.md +6 -0
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +43 -4
- package/corpus/templates/analytics/server/lib/error-capture.ts +24 -5
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
- package/corpus/templates/clips/app/components/library/recording-card.tsx +27 -26
- package/corpus/templates/clips/app/components/library/space-card.tsx +4 -6
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +18 -16
- package/dist/agent/production-agent.d.ts +9 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +12 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +11 -0
- package/dist/client/analytics.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +11 -1
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
- package/dist/server/agent-teams.d.ts.map +1 -1
- package/dist/server/agent-teams.js +15 -1
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/google-oauth.d.ts.map +1 -1
- package/dist/server/google-oauth.js +6 -2
- package/dist/server/google-oauth.js.map +1 -1
- package/package.json +2 -2
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
} from "@tabler/icons-react";
|
|
35
35
|
import { useQuery } from "@tanstack/react-query";
|
|
36
36
|
import {
|
|
37
|
+
type CSSProperties,
|
|
37
38
|
type ReactNode,
|
|
38
39
|
useCallback,
|
|
39
40
|
useEffect,
|
|
@@ -711,6 +712,7 @@ function ReplayPlayer({
|
|
|
711
712
|
if (!stageRootRef.current) return;
|
|
712
713
|
let cancelled = false;
|
|
713
714
|
let localReplayer: any = null;
|
|
715
|
+
let stopCursorVisibilityObserver = () => {};
|
|
714
716
|
|
|
715
717
|
async function loadReplay() {
|
|
716
718
|
const replayEvents = eventsRef.current;
|
|
@@ -761,6 +763,8 @@ function ReplayPlayer({
|
|
|
761
763
|
// Do not mutate recorded URLs/CSS; suppress viewer-page referrer leakage
|
|
762
764
|
// at the iframe boundary while retaining historical visual resources.
|
|
763
765
|
localReplayer.iframe?.setAttribute?.("referrerpolicy", "no-referrer");
|
|
766
|
+
stopCursorVisibilityObserver =
|
|
767
|
+
hideReplayCursorUntilPosition(localReplayer);
|
|
764
768
|
replayerRef.current = localReplayer;
|
|
765
769
|
const meta = localReplayer.getMetaData?.();
|
|
766
770
|
const total = Number(meta?.totalTime ?? replayDuration(replayEvents));
|
|
@@ -827,6 +831,7 @@ function ReplayPlayer({
|
|
|
827
831
|
|
|
828
832
|
return () => {
|
|
829
833
|
cancelled = true;
|
|
834
|
+
stopCursorVisibilityObserver();
|
|
830
835
|
if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
|
|
831
836
|
rafRef.current = null;
|
|
832
837
|
try {
|
|
@@ -977,22 +982,26 @@ function ReplayPlayer({
|
|
|
977
982
|
<div
|
|
978
983
|
ref={stageRootRef}
|
|
979
984
|
className="an-replay-stage-root absolute left-1/2 top-1/2"
|
|
980
|
-
style={
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
985
|
+
style={
|
|
986
|
+
{
|
|
987
|
+
width: playerWidth,
|
|
988
|
+
height: playerHeight,
|
|
989
|
+
"--an-replay-cursor-scale": String(1 / fitScale),
|
|
990
|
+
transform: `translate(-50%, -50%) scale(${fitScale})`,
|
|
991
|
+
transformOrigin: "center center",
|
|
992
|
+
} as CSSProperties
|
|
993
|
+
}
|
|
986
994
|
/>
|
|
987
995
|
<button
|
|
988
996
|
type="button"
|
|
989
997
|
className={cn(
|
|
990
998
|
"absolute inset-0 z-20 rounded-[inherit] border-0 bg-transparent p-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-default",
|
|
991
|
-
// INTENTIONAL —
|
|
992
|
-
// The recorded cursor is
|
|
993
|
-
//
|
|
994
|
-
//
|
|
995
|
-
|
|
999
|
+
// INTENTIONAL — KEEP THE VIEWER CURSOR VISIBLE.
|
|
1000
|
+
// The recorded cursor is a separate overlay, while this
|
|
1001
|
+
// button owns the live hover target for pause/play. Do
|
|
1002
|
+
// not add `cursor-none`: it makes the user's cursor
|
|
1003
|
+
// disappear when they move over the preview.
|
|
1004
|
+
"cursor-pointer",
|
|
996
1005
|
)}
|
|
997
1006
|
disabled={disabled}
|
|
998
1007
|
aria-label={
|
|
@@ -2278,6 +2287,32 @@ function hasPlayableReplayEvents(events: unknown[]): boolean {
|
|
|
2278
2287
|
return false;
|
|
2279
2288
|
}
|
|
2280
2289
|
|
|
2290
|
+
function hideReplayCursorUntilPosition(replayer: any): () => void {
|
|
2291
|
+
const cursor = replayer?.mouse as HTMLElement | undefined;
|
|
2292
|
+
if (!cursor || typeof MutationObserver === "undefined") return () => {};
|
|
2293
|
+
|
|
2294
|
+
let observer: MutationObserver | null = null;
|
|
2295
|
+
const revealWhenPositioned = () => {
|
|
2296
|
+
if (!cursor.style.left || !cursor.style.top) return;
|
|
2297
|
+
cursor.classList.add("has-position");
|
|
2298
|
+
observer?.disconnect();
|
|
2299
|
+
observer = null;
|
|
2300
|
+
};
|
|
2301
|
+
|
|
2302
|
+
cursor.classList.remove("has-position");
|
|
2303
|
+
observer = new MutationObserver(revealWhenPositioned);
|
|
2304
|
+
observer.observe(cursor, {
|
|
2305
|
+
attributes: true,
|
|
2306
|
+
attributeFilter: ["style"],
|
|
2307
|
+
});
|
|
2308
|
+
revealWhenPositioned();
|
|
2309
|
+
|
|
2310
|
+
return () => {
|
|
2311
|
+
observer?.disconnect();
|
|
2312
|
+
observer = null;
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2281
2316
|
export function replayViewportDimensions(
|
|
2282
2317
|
events: AnyReplayEvent[],
|
|
2283
2318
|
): ReplayViewportDimensions | null {
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
} from "@tabler/icons-react";
|
|
23
23
|
import { useQuery } from "@tanstack/react-query";
|
|
24
24
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
25
|
-
import { Link,
|
|
25
|
+
import { Link, useSearchParams } from "react-router";
|
|
26
26
|
import { toast } from "sonner";
|
|
27
27
|
|
|
28
28
|
import { Badge } from "@/components/ui/badge";
|
|
@@ -205,7 +205,6 @@ export function useDebouncedUrlFilter(
|
|
|
205
205
|
|
|
206
206
|
export default function SessionsPage() {
|
|
207
207
|
const t = useT();
|
|
208
|
-
const navigate = useNavigate();
|
|
209
208
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
210
209
|
const range = readRange(searchParams.get("range"));
|
|
211
210
|
const app = searchParams.get("app") ?? "";
|
|
@@ -389,11 +388,10 @@ export default function SessionsPage() {
|
|
|
389
388
|
recording.startedAt;
|
|
390
389
|
const deviceLabel = sessionDeviceLabel(recording);
|
|
391
390
|
return (
|
|
392
|
-
<
|
|
391
|
+
<Link
|
|
393
392
|
key={recording.id}
|
|
394
|
-
|
|
393
|
+
to={href}
|
|
395
394
|
className="analytics-session-row grid w-full gap-3 px-4 py-3 text-left transition-colors hover:bg-muted/35 focus-visible:bg-muted/35 focus-visible:outline-none"
|
|
396
|
-
onClick={() => navigate(href)}
|
|
397
395
|
aria-label={t("sessions.watchReplay")}
|
|
398
396
|
>
|
|
399
397
|
<span className="inline-flex h-10 w-[92px] items-center justify-center gap-2 rounded-md bg-primary/10 font-medium text-primary">
|
|
@@ -446,7 +444,7 @@ export default function SessionsPage() {
|
|
|
446
444
|
</span>
|
|
447
445
|
) : null}
|
|
448
446
|
</span>
|
|
449
|
-
</
|
|
447
|
+
</Link>
|
|
450
448
|
);
|
|
451
449
|
})}
|
|
452
450
|
</div>
|
|
@@ -1846,12 +1846,38 @@ export async function saveDashboardView(
|
|
|
1846
1846
|
orgId: ctx.orgId ?? undefined,
|
|
1847
1847
|
});
|
|
1848
1848
|
const db = getDb() as any;
|
|
1849
|
-
|
|
1849
|
+
let id = view.id ?? nanoidFallback();
|
|
1850
|
+
let existing = false;
|
|
1850
1851
|
if (view.id) {
|
|
1852
|
+
const [existingRow] = await db
|
|
1853
|
+
.select({
|
|
1854
|
+
id: schema.dashboardViews.id,
|
|
1855
|
+
dashboardId: schema.dashboardViews.dashboardId,
|
|
1856
|
+
})
|
|
1857
|
+
.from(schema.dashboardViews)
|
|
1858
|
+
.where(eq(schema.dashboardViews.id, view.id))
|
|
1859
|
+
.limit(1);
|
|
1860
|
+
if (existingRow?.dashboardId === dashboardId) {
|
|
1861
|
+
existing = true;
|
|
1862
|
+
} else if (existingRow) {
|
|
1863
|
+
// View ids are generated from names in the browser but remain globally
|
|
1864
|
+
// primary-keyed for backwards compatibility. Avoid colliding with a
|
|
1865
|
+
// same-named view on another dashboard instead of updating that row or
|
|
1866
|
+
// returning a raw unique-constraint 500.
|
|
1867
|
+
id = nanoidFallback();
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
if (existing) {
|
|
1851
1872
|
await db
|
|
1852
1873
|
.update(schema.dashboardViews)
|
|
1853
1874
|
.set({ name: view.name, filters: JSON.stringify(view.filters) })
|
|
1854
|
-
.where(
|
|
1875
|
+
.where(
|
|
1876
|
+
and(
|
|
1877
|
+
eq(schema.dashboardViews.id, id),
|
|
1878
|
+
eq(schema.dashboardViews.dashboardId, dashboardId),
|
|
1879
|
+
),
|
|
1880
|
+
);
|
|
1855
1881
|
} else {
|
|
1856
1882
|
await db.insert(schema.dashboardViews).values({
|
|
1857
1883
|
id,
|
|
@@ -1864,7 +1890,15 @@ export async function saveDashboardView(
|
|
|
1864
1890
|
const [row] = await db
|
|
1865
1891
|
.select()
|
|
1866
1892
|
.from(schema.dashboardViews)
|
|
1867
|
-
.where(
|
|
1893
|
+
.where(
|
|
1894
|
+
and(
|
|
1895
|
+
eq(schema.dashboardViews.id, id),
|
|
1896
|
+
eq(schema.dashboardViews.dashboardId, dashboardId),
|
|
1897
|
+
),
|
|
1898
|
+
);
|
|
1899
|
+
if (!row) {
|
|
1900
|
+
throw new Error("Dashboard view was not persisted");
|
|
1901
|
+
}
|
|
1868
1902
|
const dash = await getDashboard(dashboardId, ctx);
|
|
1869
1903
|
if (dash) {
|
|
1870
1904
|
recordScopedChange(
|
|
@@ -1891,7 +1925,12 @@ export async function deleteDashboardView(
|
|
|
1891
1925
|
const db = getDb() as any;
|
|
1892
1926
|
await db
|
|
1893
1927
|
.delete(schema.dashboardViews)
|
|
1894
|
-
.where(
|
|
1928
|
+
.where(
|
|
1929
|
+
and(
|
|
1930
|
+
eq(schema.dashboardViews.id, viewId),
|
|
1931
|
+
eq(schema.dashboardViews.dashboardId, dashboardId),
|
|
1932
|
+
),
|
|
1933
|
+
);
|
|
1895
1934
|
const dash = await getDashboard(dashboardId, ctx);
|
|
1896
1935
|
if (dash) {
|
|
1897
1936
|
recordScopedChange(
|
|
@@ -547,6 +547,27 @@ export interface RawExceptionInput {
|
|
|
547
547
|
breadcrumbs: unknown[];
|
|
548
548
|
}
|
|
549
549
|
|
|
550
|
+
/**
|
|
551
|
+
* Browser request cancellation is expected during navigation and query
|
|
552
|
+
* invalidation. Keep the first-party issue store aligned with the client
|
|
553
|
+
* Sentry filter, while preserving other AbortError failures for triage.
|
|
554
|
+
*/
|
|
555
|
+
export function isBenignBrowserAbortException(
|
|
556
|
+
input: Pick<RawExceptionInput, "type" | "message">,
|
|
557
|
+
): boolean {
|
|
558
|
+
const exceptionType = input.type.trim().toLowerCase();
|
|
559
|
+
const exceptionValue = input.message.trim().toLowerCase();
|
|
560
|
+
return (
|
|
561
|
+
exceptionValue === "the user aborted a request." ||
|
|
562
|
+
exceptionValue === "signal is aborted without reason" ||
|
|
563
|
+
exceptionValue === "aborterror: the user aborted a request." ||
|
|
564
|
+
exceptionValue === "aborterror: signal is aborted without reason" ||
|
|
565
|
+
(exceptionType === "aborterror" &&
|
|
566
|
+
(exceptionValue.includes("the user aborted a request") ||
|
|
567
|
+
exceptionValue.includes("signal is aborted without reason")))
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
|
|
550
571
|
function nowIso(): string {
|
|
551
572
|
return new Date().toISOString();
|
|
552
573
|
}
|
|
@@ -930,11 +951,9 @@ export async function ingestAnalyticsExceptionEvents(
|
|
|
930
951
|
let ingested = 0;
|
|
931
952
|
for (const item of events) {
|
|
932
953
|
try {
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
item.derived,
|
|
937
|
-
);
|
|
954
|
+
const raw = extractExceptionInput(item.properties);
|
|
955
|
+
if (isBenignBrowserAbortException(raw)) continue;
|
|
956
|
+
await ingestException(scope, raw, item.derived);
|
|
938
957
|
ingested += 1;
|
|
939
958
|
} catch (error) {
|
|
940
959
|
console.warn("[error-capture] Failed to ingest exception event:", error);
|
|
@@ -239,6 +239,11 @@ export function realDataFinalGuard(
|
|
|
239
239
|
// actual query. One corrective turn was enough for Sonnet but caused Luna
|
|
240
240
|
// to hit the fallback before it reached the query.
|
|
241
241
|
maxRetries: 2,
|
|
242
|
+
// The first request may use the compact starter catalog. A corrective
|
|
243
|
+
// retry must be able to reach the real source action directly; otherwise
|
|
244
|
+
// some model families spend the retry on tool-search narration and hit
|
|
245
|
+
// the canned fallback without ever running a query.
|
|
246
|
+
expandToolSurface: true,
|
|
242
247
|
};
|
|
243
248
|
}
|
|
244
249
|
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
IconAlertTriangle,
|
|
14
14
|
} from "@tabler/icons-react";
|
|
15
15
|
import { useCallback, useMemo, useState } from "react";
|
|
16
|
-
import {
|
|
16
|
+
import { Link } from "react-router";
|
|
17
17
|
|
|
18
18
|
import { ViewedByPopover } from "@/components/sharing/viewed-by-popover";
|
|
19
19
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
@@ -85,7 +85,6 @@ export function RecordingCard({
|
|
|
85
85
|
onTrash,
|
|
86
86
|
readOnly = false,
|
|
87
87
|
}: RecordingCardProps) {
|
|
88
|
-
const navigate = useNavigate();
|
|
89
88
|
const t = useT();
|
|
90
89
|
const { formatDate, formatRelativeTime } = useFormatters();
|
|
91
90
|
const [hovered, setHovered] = useState(false);
|
|
@@ -141,26 +140,23 @@ export function RecordingCard({
|
|
|
141
140
|
|
|
142
141
|
const recordingPath = `/r/${recording.id}`;
|
|
143
142
|
|
|
144
|
-
const
|
|
145
|
-
(event: React.MouseEvent<
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
event.
|
|
152
|
-
event.
|
|
153
|
-
|
|
143
|
+
const handleLinkClick = useCallback(
|
|
144
|
+
(event: React.MouseEvent<HTMLAnchorElement>) => {
|
|
145
|
+
if (
|
|
146
|
+
!selectionMode ||
|
|
147
|
+
event.button !== 0 ||
|
|
148
|
+
event.metaKey ||
|
|
149
|
+
event.ctrlKey ||
|
|
150
|
+
event.shiftKey ||
|
|
151
|
+
event.altKey
|
|
152
|
+
) {
|
|
154
153
|
return;
|
|
155
154
|
}
|
|
156
155
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
} else {
|
|
160
|
-
navigate(recordingPath);
|
|
161
|
-
}
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
onToggleSelect?.(recording.id);
|
|
162
158
|
},
|
|
163
|
-
[
|
|
159
|
+
[onToggleSelect, recording.id, selectionMode],
|
|
164
160
|
);
|
|
165
161
|
|
|
166
162
|
const handleCheckbox = useCallback(
|
|
@@ -182,8 +178,6 @@ export function RecordingCard({
|
|
|
182
178
|
return (
|
|
183
179
|
<div
|
|
184
180
|
role="article"
|
|
185
|
-
onClick={handleOpen}
|
|
186
|
-
onAuxClick={handleOpen}
|
|
187
181
|
onMouseEnter={() => setHovered(true)}
|
|
188
182
|
onMouseLeave={() => setHovered(false)}
|
|
189
183
|
className={cn(
|
|
@@ -193,8 +187,15 @@ export function RecordingCard({
|
|
|
193
187
|
selected && "ring-2 ring-primary border-primary",
|
|
194
188
|
)}
|
|
195
189
|
>
|
|
190
|
+
<Link
|
|
191
|
+
to={recordingPath}
|
|
192
|
+
aria-label={displayTitle}
|
|
193
|
+
onClick={handleLinkClick}
|
|
194
|
+
className="absolute inset-0 z-0 rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-inset"
|
|
195
|
+
/>
|
|
196
|
+
|
|
196
197
|
{/* Thumbnail */}
|
|
197
|
-
<div className="relative aspect-video bg-muted
|
|
198
|
+
<div className="relative z-10 aspect-video overflow-hidden bg-muted pointer-events-none">
|
|
198
199
|
{displayThumbnail ? (
|
|
199
200
|
// eslint-disable-next-line jsx-a11y/alt-text
|
|
200
201
|
<img
|
|
@@ -229,7 +230,7 @@ export function RecordingCard({
|
|
|
229
230
|
{canSelect && (selectionMode || hovered || selected) && (
|
|
230
231
|
<div
|
|
231
232
|
onClick={handleCheckbox}
|
|
232
|
-
className="absolute top-2 start-2 flex h-5 w-5 items-center justify-center rounded bg-background/90
|
|
233
|
+
className="pointer-events-auto absolute top-2 start-2 flex h-5 w-5 items-center justify-center rounded border border-border bg-background/90"
|
|
233
234
|
>
|
|
234
235
|
<Checkbox
|
|
235
236
|
checked={selected}
|
|
@@ -285,7 +286,7 @@ export function RecordingCard({
|
|
|
285
286
|
<button
|
|
286
287
|
type="button"
|
|
287
288
|
onClick={handleRemoveFailed}
|
|
288
|
-
className="rounded border border-border px-1.5 py-0.5 text-[10px] text-foreground hover:bg-accent"
|
|
289
|
+
className="pointer-events-auto rounded border border-border px-1.5 py-0.5 text-[10px] text-foreground hover:bg-accent"
|
|
289
290
|
>
|
|
290
291
|
{t("clipsFinalRaw.remove")}
|
|
291
292
|
</button>
|
|
@@ -296,7 +297,7 @@ export function RecordingCard({
|
|
|
296
297
|
</div>
|
|
297
298
|
|
|
298
299
|
{/* Body */}
|
|
299
|
-
<div className="flex-1 p-3
|
|
300
|
+
<div className="relative z-10 flex-1 space-y-2 p-3 pointer-events-none">
|
|
300
301
|
<div className="flex items-start gap-2">
|
|
301
302
|
<div className="min-w-0 flex-1">
|
|
302
303
|
{hasDefaultTitle ? (
|
|
@@ -319,7 +320,7 @@ export function RecordingCard({
|
|
|
319
320
|
{recording.viewCount > 0 && !readOnly ? (
|
|
320
321
|
<ViewedByPopover
|
|
321
322
|
recordingId={recording.id}
|
|
322
|
-
className="underline-offset-2 hover:underline hover:text-foreground"
|
|
323
|
+
className="pointer-events-auto underline-offset-2 hover:underline hover:text-foreground"
|
|
323
324
|
>
|
|
324
325
|
{t("clipsFinalRaw.viewsCount", {
|
|
325
326
|
count: recording.viewCount,
|
|
@@ -341,7 +342,7 @@ export function RecordingCard({
|
|
|
341
342
|
<DropdownMenu>
|
|
342
343
|
<DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}>
|
|
343
344
|
<button
|
|
344
|
-
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
345
|
+
className="pointer-events-auto rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
345
346
|
aria-label={t("clipsFinalRaw.recordingMenu")}
|
|
346
347
|
>
|
|
347
348
|
<IconDots className="h-4 w-4" />
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IconUsersGroup, IconVideo } from "@tabler/icons-react";
|
|
2
|
-
import {
|
|
2
|
+
import { Link } from "react-router";
|
|
3
3
|
|
|
4
4
|
import { cn } from "@/lib/utils";
|
|
5
5
|
|
|
@@ -19,15 +19,13 @@ interface SpaceCardProps {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function SpaceCard({ space, className }: SpaceCardProps) {
|
|
22
|
-
const navigate = useNavigate();
|
|
23
22
|
const color = space.color || "hsl(var(--primary))";
|
|
24
23
|
const members = space.memberEmails ?? [];
|
|
25
24
|
const initial = (space.name.trim().slice(0, 1) || "S").toUpperCase();
|
|
26
25
|
|
|
27
26
|
return (
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
onClick={() => navigate(`/spaces/${space.id}`)}
|
|
27
|
+
<Link
|
|
28
|
+
to={`/spaces/${space.id}`}
|
|
31
29
|
className={cn(
|
|
32
30
|
"group flex flex-col overflow-hidden rounded-lg border border-border bg-card text-start",
|
|
33
31
|
"hover:border-primary/40",
|
|
@@ -92,6 +90,6 @@ export function SpaceCard({ space, className }: SpaceCardProps) {
|
|
|
92
90
|
</div>
|
|
93
91
|
)}
|
|
94
92
|
</div>
|
|
95
|
-
</
|
|
93
|
+
</Link>
|
|
96
94
|
);
|
|
97
95
|
}
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
} from "@tabler/icons-react";
|
|
46
46
|
import { useQueryClient } from "@tanstack/react-query";
|
|
47
47
|
import { useCallback, useMemo, useRef, useState, type ReactNode } from "react";
|
|
48
|
-
import { useLocation, useNavigate } from "react-router";
|
|
48
|
+
import { Link, useLocation, useNavigate } from "react-router";
|
|
49
49
|
import { toast } from "sonner";
|
|
50
50
|
|
|
51
51
|
import { QueryErrorState } from "@/components/QueryErrorState";
|
|
@@ -788,37 +788,37 @@ export function DocumentSidebar({
|
|
|
788
788
|
);
|
|
789
789
|
|
|
790
790
|
const renderLocalFilesNavButton = () => (
|
|
791
|
-
<
|
|
791
|
+
<Link
|
|
792
|
+
to="/local-files"
|
|
792
793
|
className={cn(
|
|
793
794
|
"flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm",
|
|
794
795
|
localFilesActive
|
|
795
796
|
? "bg-accent text-accent-foreground"
|
|
796
797
|
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
|
|
797
798
|
)}
|
|
798
|
-
onClick={() => navigate("/local-files")}
|
|
799
799
|
>
|
|
800
800
|
<IconFolderOpen size={15} className="shrink-0" />
|
|
801
801
|
<span className="min-w-0 flex-1 truncate text-start">
|
|
802
802
|
{t("sidebar.localFiles")}
|
|
803
803
|
</span>
|
|
804
|
-
</
|
|
804
|
+
</Link>
|
|
805
805
|
);
|
|
806
806
|
|
|
807
807
|
const renderSettingsNavButton = () => (
|
|
808
|
-
<
|
|
808
|
+
<Link
|
|
809
|
+
to="/settings"
|
|
809
810
|
className={cn(
|
|
810
811
|
"flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm",
|
|
811
812
|
settingsActive
|
|
812
813
|
? "bg-accent text-accent-foreground"
|
|
813
814
|
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
|
|
814
815
|
)}
|
|
815
|
-
onClick={() => navigate("/settings")}
|
|
816
816
|
>
|
|
817
817
|
<IconSettings size={15} className="shrink-0" />
|
|
818
818
|
<span className="min-w-0 flex-1 truncate text-start">
|
|
819
819
|
{t("navigation.settings")}
|
|
820
820
|
</span>
|
|
821
|
-
</
|
|
821
|
+
</Link>
|
|
822
822
|
);
|
|
823
823
|
|
|
824
824
|
const toggleSection = (id: SidebarSectionId) => {
|
|
@@ -848,9 +848,11 @@ export function DocumentSidebar({
|
|
|
848
848
|
<TooltipContent>{t("sidebar.localFilesActions")}</TooltipContent>
|
|
849
849
|
</Tooltip>
|
|
850
850
|
<DropdownMenuContent align="end" className="w-56">
|
|
851
|
-
<DropdownMenuItem
|
|
852
|
-
<
|
|
853
|
-
|
|
851
|
+
<DropdownMenuItem asChild>
|
|
852
|
+
<Link to="/local-files">
|
|
853
|
+
<IconFolderOpen className="me-2 size-4" />
|
|
854
|
+
{t("sidebar.manageLocalFolders")}
|
|
855
|
+
</Link>
|
|
854
856
|
</DropdownMenuItem>
|
|
855
857
|
{canRemoveLocalFiles && (
|
|
856
858
|
<>
|
|
@@ -1080,33 +1082,33 @@ export function DocumentSidebar({
|
|
|
1080
1082
|
{renderCollapsedNewButton()}
|
|
1081
1083
|
<Tooltip>
|
|
1082
1084
|
<TooltipTrigger asChild>
|
|
1083
|
-
<
|
|
1085
|
+
<Link
|
|
1086
|
+
to="/local-files"
|
|
1084
1087
|
className={cn(
|
|
1085
1088
|
"w-10 h-10 flex items-center justify-center rounded-lg hover:bg-accent",
|
|
1086
1089
|
localFilesActive
|
|
1087
1090
|
? "bg-accent text-accent-foreground"
|
|
1088
1091
|
: "text-muted-foreground hover:text-foreground",
|
|
1089
1092
|
)}
|
|
1090
|
-
onClick={() => navigate("/local-files")}
|
|
1091
1093
|
>
|
|
1092
1094
|
<IconFolderOpen size={16} />
|
|
1093
|
-
</
|
|
1095
|
+
</Link>
|
|
1094
1096
|
</TooltipTrigger>
|
|
1095
1097
|
<TooltipContent>{t("sidebar.localFiles")}</TooltipContent>
|
|
1096
1098
|
</Tooltip>
|
|
1097
1099
|
<Tooltip>
|
|
1098
1100
|
<TooltipTrigger asChild>
|
|
1099
|
-
<
|
|
1101
|
+
<Link
|
|
1102
|
+
to="/settings"
|
|
1100
1103
|
className={cn(
|
|
1101
1104
|
"w-10 h-10 flex items-center justify-center rounded-lg hover:bg-accent",
|
|
1102
1105
|
settingsActive
|
|
1103
1106
|
? "bg-accent text-accent-foreground"
|
|
1104
1107
|
: "text-muted-foreground hover:text-foreground",
|
|
1105
1108
|
)}
|
|
1106
|
-
onClick={() => navigate("/settings")}
|
|
1107
1109
|
>
|
|
1108
1110
|
<IconSettings size={16} />
|
|
1109
|
-
</
|
|
1111
|
+
</Link>
|
|
1110
1112
|
</TooltipTrigger>
|
|
1111
1113
|
<TooltipContent>{t("navigation.settings")}</TooltipContent>
|
|
1112
1114
|
</Tooltip>
|
|
@@ -367,6 +367,15 @@ export type AgentLoopFinalResponseGuardResult = string | {
|
|
|
367
367
|
* guard/model combination from looping indefinitely.
|
|
368
368
|
*/
|
|
369
369
|
maxRetries?: number;
|
|
370
|
+
/**
|
|
371
|
+
* A rejected final answer is a recovery path, not a normal compact
|
|
372
|
+
* first request. When true, expose the complete active registry before
|
|
373
|
+
* the corrective retry so the model can reach the tool the guard is
|
|
374
|
+
* asking for without depending on a second, model-specific tool-search
|
|
375
|
+
* round trip. The registry is still limited to tools already exposed to
|
|
376
|
+
* this run; hidden/agentTool=false actions are never added.
|
|
377
|
+
*/
|
|
378
|
+
expandToolSurface?: boolean;
|
|
370
379
|
};
|
|
371
380
|
export type AgentLoopFinalResponseGuard = (context: AgentLoopFinalResponseGuardContext) => AgentLoopFinalResponseGuardResult | null | undefined | Promise<AgentLoopFinalResponseGuardResult | null | undefined>;
|
|
372
381
|
export declare const AGENT_INTERNAL_CONTINUE_PROMPT = "Continue from where you left off and finish the user's original request. Do not repeat completed work, do not mention internal reconnects, time limits, or step limits, and continue as if this is the same uninterrupted run.";
|