@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.
Files changed (55) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +10 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +21 -0
  5. package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
  6. package/corpus/core/src/client/analytics.ts +13 -0
  7. package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +13 -0
  8. package/corpus/core/src/server/agent-teams.ts +17 -0
  9. package/corpus/core/src/server/google-oauth.ts +6 -2
  10. package/corpus/templates/analytics/app/assets/replay-cursor.svg +17 -0
  11. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +4 -3
  12. package/corpus/templates/analytics/app/global.css +17 -9
  13. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +32 -4
  14. package/corpus/templates/analytics/app/pages/monitoring/errors/IssueDetail.tsx +1 -1
  15. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +46 -11
  16. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
  17. package/corpus/templates/analytics/changelog/2026-07-13-all-time-dashboard-filters-no-longer-send-invalid-dates-to-b.md +6 -0
  18. package/corpus/templates/analytics/changelog/2026-07-13-error-issue-titles-wrap-instead-of-overflowing.md +6 -0
  19. package/corpus/templates/analytics/changelog/2026-07-13-saving-a-new-dashboard-view-no-longer-fails-with-a-server-er.md +6 -0
  20. package/corpus/templates/analytics/changelog/2026-07-13-session-replay-cursors-stay-hidden-until-their-first-recorde.md +6 -0
  21. package/corpus/templates/analytics/changelog/2026-07-13-session-replay-links-now-support-cmd-click-and-middle-click-.md +6 -0
  22. package/corpus/templates/analytics/server/lib/dashboards-store.ts +43 -4
  23. package/corpus/templates/analytics/server/lib/error-capture.ts +24 -5
  24. package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
  25. package/corpus/templates/clips/app/components/library/recording-card.tsx +27 -26
  26. package/corpus/templates/clips/app/components/library/space-card.tsx +4 -6
  27. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +18 -16
  28. package/dist/agent/production-agent.d.ts +9 -0
  29. package/dist/agent/production-agent.d.ts.map +1 -1
  30. package/dist/agent/production-agent.js +12 -0
  31. package/dist/agent/production-agent.js.map +1 -1
  32. package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
  33. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  34. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  35. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  36. package/dist/client/analytics.d.ts.map +1 -1
  37. package/dist/client/analytics.js +11 -0
  38. package/dist/client/analytics.js.map +1 -1
  39. package/dist/collab/awareness.d.ts +2 -2
  40. package/dist/collab/awareness.d.ts.map +1 -1
  41. package/dist/collab/routes.d.ts +1 -1
  42. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  43. package/dist/observability/routes.d.ts +5 -5
  44. package/dist/progress/routes.d.ts +1 -1
  45. package/dist/secrets/routes.d.ts +9 -9
  46. package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
  47. package/dist/server/agent-chat/action-filters-a2a.js +11 -1
  48. package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
  49. package/dist/server/agent-teams.d.ts.map +1 -1
  50. package/dist/server/agent-teams.js +15 -1
  51. package/dist/server/agent-teams.js.map +1 -1
  52. package/dist/server/google-oauth.d.ts.map +1 -1
  53. package/dist/server/google-oauth.js +6 -2
  54. package/dist/server/google-oauth.js.map +1 -1
  55. 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
- width: playerWidth,
982
- height: playerHeight,
983
- transform: `translate(-50%, -50%) scale(${fitScale})`,
984
- transformOrigin: "center center",
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 — DO NOT REMOVE `cursor-none` while playing.
992
- // The recorded cursor is the meaningful pointer here;
993
- // hiding the viewer's stationary pointer keeps the
994
- // Mac-style replay cursor easy to follow.
995
- playing ? "cursor-none" : "cursor-pointer",
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, useNavigate, useSearchParams } from "react-router";
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
- <button
391
+ <Link
393
392
  key={recording.id}
394
- type="button"
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
- </button>
447
+ </Link>
450
448
  );
451
449
  })}
452
450
  </div>
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-13
4
+ ---
5
+
6
+ All-time dashboard filters no longer send invalid dates to BigQuery
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-13
4
+ ---
5
+
6
+ Error issue titles wrap instead of overflowing
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-13
4
+ ---
5
+
6
+ Saving a new dashboard view no longer fails with a server error
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-13
4
+ ---
5
+
6
+ Session replay cursors stay hidden until their first recorded position
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-13
4
+ ---
5
+
6
+ Session replay links now support Cmd-click and middle-click to open in a new tab
@@ -1846,12 +1846,38 @@ export async function saveDashboardView(
1846
1846
  orgId: ctx.orgId ?? undefined,
1847
1847
  });
1848
1848
  const db = getDb() as any;
1849
- const id = view.id ?? nanoidFallback();
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(eq(schema.dashboardViews.id, id));
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(eq(schema.dashboardViews.id, id));
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(eq(schema.dashboardViews.id, viewId));
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
- await ingestException(
934
- scope,
935
- extractExceptionInput(item.properties),
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 { useNavigate } from "react-router";
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 handleOpen = useCallback(
145
- (event: React.MouseEvent<HTMLElement>) => {
146
- const shouldOpenNewTab =
147
- event.button === 1 ||
148
- (event.button === 0 && (event.metaKey || event.ctrlKey));
149
-
150
- if (shouldOpenNewTab) {
151
- event.preventDefault();
152
- event.stopPropagation();
153
- window.open(recordingPath, "_blank", "noopener,noreferrer");
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
- if (selectionMode) {
158
- onToggleSelect?.(recording.id);
159
- } else {
160
- navigate(recordingPath);
161
- }
156
+ event.preventDefault();
157
+ onToggleSelect?.(recording.id);
162
158
  },
163
- [navigate, onToggleSelect, recording.id, recordingPath, selectionMode],
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 overflow-hidden">
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 border border-border"
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 space-y-2">
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 { useNavigate } from "react-router";
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
- <button
29
- type="button"
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
- </button>
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
- <button
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
- </button>
804
+ </Link>
805
805
  );
806
806
 
807
807
  const renderSettingsNavButton = () => (
808
- <button
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
- </button>
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 onClick={() => navigate("/local-files")}>
852
- <IconFolderOpen className="me-2 size-4" />
853
- {t("sidebar.manageLocalFolders")}
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
- <button
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
- </button>
1095
+ </Link>
1094
1096
  </TooltipTrigger>
1095
1097
  <TooltipContent>{t("sidebar.localFiles")}</TooltipContent>
1096
1098
  </Tooltip>
1097
1099
  <Tooltip>
1098
1100
  <TooltipTrigger asChild>
1099
- <button
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
- </button>
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.";