@agent-native/core 0.76.0 → 0.76.2

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 (56) hide show
  1. package/corpus/core/CHANGELOG.md +56 -0
  2. package/corpus/core/package.json +1 -1
  3. package/corpus/core/src/agent/durable-background.ts +58 -18
  4. package/corpus/core/src/agent/production-agent.ts +54 -0
  5. package/corpus/core/src/agent/run-manager.ts +27 -0
  6. package/corpus/core/src/agent/run-store.ts +185 -4
  7. package/corpus/core/src/deploy/build.ts +8 -6
  8. package/corpus/core/src/server/agent-chat-plugin.ts +71 -0
  9. package/corpus/core/src/server/auth.ts +13 -0
  10. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +12 -12
  11. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +23 -23
  12. package/corpus/templates/analytics/app/pages/analyses/AnalysesList.tsx +2 -2
  13. package/corpus/templates/brain/app/routes/knowledge.tsx +6 -6
  14. package/corpus/templates/brain/app/routes/ops.tsx +4 -4
  15. package/corpus/templates/brain/app/routes/search.tsx +4 -4
  16. package/corpus/templates/brain/app/routes/settings.tsx +3 -3
  17. package/corpus/templates/brain/app/routes/sources.tsx +3 -3
  18. package/corpus/templates/dispatch/app/routes/integrations.tsx +3 -3
  19. package/corpus/templates/mail/app/components/email/EmailThread.tsx +10 -10
  20. package/corpus/templates/mail/app/pages/DraftQueuePage.tsx +3 -3
  21. package/corpus/templates/mail/app/pages/InboxPage.tsx +3 -3
  22. package/corpus/templates/mail/app/pages/SettingsPage.tsx +2 -2
  23. package/corpus/templates/plan/app/pages/PlansPage.tsx +3 -3
  24. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +4 -4
  25. package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
  26. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +4 -4
  27. package/corpus/templates/slides/app/pages/Index.tsx +3 -3
  28. package/corpus/templates/videos/app/components/layout/NavSidebar.tsx +1 -1
  29. package/corpus/templates/videos/app/pages/ComponentLibraryView.tsx +1 -1
  30. package/corpus/templates/videos/app/pages/DesignSystems.tsx +1 -1
  31. package/dist/agent/durable-background.d.ts +19 -3
  32. package/dist/agent/durable-background.d.ts.map +1 -1
  33. package/dist/agent/durable-background.js +47 -17
  34. package/dist/agent/durable-background.js.map +1 -1
  35. package/dist/agent/production-agent.d.ts.map +1 -1
  36. package/dist/agent/production-agent.js +34 -1
  37. package/dist/agent/production-agent.js.map +1 -1
  38. package/dist/agent/run-manager.d.ts +8 -0
  39. package/dist/agent/run-manager.d.ts.map +1 -1
  40. package/dist/agent/run-manager.js +18 -1
  41. package/dist/agent/run-manager.js.map +1 -1
  42. package/dist/agent/run-store.d.ts +99 -0
  43. package/dist/agent/run-store.d.ts.map +1 -1
  44. package/dist/agent/run-store.js +155 -4
  45. package/dist/agent/run-store.js.map +1 -1
  46. package/dist/deploy/build.d.ts +6 -4
  47. package/dist/deploy/build.d.ts.map +1 -1
  48. package/dist/deploy/build.js +8 -6
  49. package/dist/deploy/build.js.map +1 -1
  50. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  51. package/dist/server/agent-chat-plugin.js +58 -1
  52. package/dist/server/agent-chat-plugin.js.map +1 -1
  53. package/dist/server/auth.d.ts.map +1 -1
  54. package/dist/server/auth.js +12 -0
  55. package/dist/server/auth.js.map +1 -1
  56. package/package.json +1 -1
@@ -152,6 +152,7 @@ import {
152
152
  } from "../a2a/auth-policy.js";
153
153
  import {
154
154
  AGENT_CHAT_PROCESS_RUN_PATH,
155
+ extractProcessRunId,
155
156
  prepareProcessRunRequest,
156
157
  } from "../agent/durable-background.js";
157
158
  import {
@@ -7174,6 +7175,14 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
7174
7175
  status: run.status,
7175
7176
  heartbeatAt: run.heartbeatAt,
7176
7177
  lastProgressAt: run.lastProgressAt,
7178
+ // Durable-background diagnostics: how the run was dispatched and
7179
+ // the last reached `_process-run` worker stage (JSON
7180
+ // `{stage,detail?,at}`). Surfaced here so a silent background
7181
+ // worker death is diagnosable from the client WITHOUT the
7182
+ // unreadable Netlify background-function logs — read
7183
+ // `/runs/active?threadId=...` and inspect `diagStage`.
7184
+ dispatchMode: run.dispatchMode ?? null,
7185
+ diagStage: run.diagStage ?? null,
7177
7186
  // Server clock so the client computes "stuck" elapsed time
7178
7187
  // server-relative, immune to client clock skew.
7179
7188
  serverNow: Date.now(),
@@ -7791,6 +7800,19 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
7791
7800
  setResponseStatus(event, 405);
7792
7801
  return { error: "Method not allowed" };
7793
7802
  }
7803
+ // DIAGNOSTIC: load the run-store diagnostic recorder. Each stage we
7804
+ // reach is written onto the run row (diag_stage) so a silent failure
7805
+ // INSIDE the Netlify background function — whose logs we cannot read —
7806
+ // is still diagnosable from the client via /runs/active. Best-effort:
7807
+ // the import + every record call is wrapped so diagnostics can never
7808
+ // break the worker path.
7809
+ const diag = await import("../agent/run-store.js")
7810
+ .then((m) => ({
7811
+ record: m.recordRunDiagnostic,
7812
+ stages: m.RUN_DIAG_STAGE,
7813
+ }))
7814
+ .catch(() => null);
7815
+
7794
7816
  // Consume the body ONCE (h3 v2's web Request stream is single-use).
7795
7817
  let processBody: any;
7796
7818
  try {
@@ -7800,6 +7822,18 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
7800
7822
  return { error: "Invalid request body" };
7801
7823
  }
7802
7824
 
7825
+ // Record "the route handler was entered" against the run BEFORE auth
7826
+ // runs. This is the proof the bg-fn invocation actually reached Nitro
7827
+ // (vs. dying at the function entry / never being invoked). The runId
7828
+ // is parsed without authenticating so we can attach it even on a
7829
+ // subsequent auth failure.
7830
+ const diagRunId = extractProcessRunId(processBody);
7831
+ if (diag && diagRunId) {
7832
+ await diag
7833
+ .record(diagRunId, diag.stages.routeEntered)
7834
+ .catch(() => {});
7835
+ }
7836
+
7803
7837
  // Validate + HMAC-authenticate the self-dispatch and prepare the
7804
7838
  // background-worker body. Pure decision (unit-tested in
7805
7839
  // durable-background.spec.ts); the route only wires it to h3.
@@ -7808,10 +7842,36 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
7808
7842
  getHeader(event, "authorization"),
7809
7843
  );
7810
7844
  if (!prepared.ok) {
7845
+ // DIAGNOSTIC: record the auth/validation failure ONTO the run
7846
+ // before returning the error status. Without this, a 401 (e.g.
7847
+ // A2A_SECRET missing/mismatched in the bg-fn env, or the path not
7848
+ // bypassing session auth) inside the unreadable bg function would
7849
+ // leave the run to time out with NO clue. The detail carries the
7850
+ // status + whether A2A_SECRET is even present in this isolate.
7851
+ if (diag && prepared.runId) {
7852
+ const a2aPresent = Boolean(
7853
+ process.env.A2A_SECRET && process.env.A2A_SECRET.length > 0,
7854
+ );
7855
+ await diag
7856
+ .record(
7857
+ prepared.runId,
7858
+ diag.stages.authFailed,
7859
+ `status=${prepared.status} error=${prepared.error} a2aSecretPresent=${a2aPresent}`,
7860
+ )
7861
+ .catch(() => {});
7862
+ }
7811
7863
  setResponseStatus(event, prepared.status);
7812
7864
  return { error: prepared.error };
7813
7865
  }
7814
7866
 
7867
+ // DIAGNOSTIC: auth + body validation passed. Reaching here proves the
7868
+ // request was authenticated and we are about to invoke the worker.
7869
+ if (diag) {
7870
+ await diag
7871
+ .record(prepared.runId, diag.stages.authPassed)
7872
+ .catch(() => {});
7873
+ }
7874
+
7815
7875
  // Stash the verified+augmented body for the handler — the body stream
7816
7876
  // is already consumed, so the handler reads this instead.
7817
7877
  (event as any).context = (event as any).context ?? {};
@@ -7821,6 +7881,17 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
7821
7881
  return await invokeAgentChatHandler(event);
7822
7882
  } catch (err: any) {
7823
7883
  console.error("[agent-chat] _process-run failed:", err);
7884
+ // DIAGNOSTIC: the worker invocation threw at the route boundary —
7885
+ // record the message so the failure cause is readable client-side.
7886
+ if (diag) {
7887
+ await diag
7888
+ .record(
7889
+ prepared.runId,
7890
+ diag.stages.routeThrew,
7891
+ err instanceof Error ? err.message : String(err),
7892
+ )
7893
+ .catch(() => {});
7894
+ }
7824
7895
  setResponseStatus(event, 500);
7825
7896
  return { error: "process-run failed" };
7826
7897
  }
@@ -1627,6 +1627,19 @@ function createAuthGuardFn(): (
1627
1627
  return;
1628
1628
  }
1629
1629
 
1630
+ // Durable-background AGENT-CHAT processor. The foreground POST self-dispatches
1631
+ // a long chat turn here (through the Netlify `-background` function, which
1632
+ // rewrites its default url to this path); the route HMAC-verifies the
1633
+ // dispatch (same internal-token scheme as agent-teams above) plus an atomic
1634
+ // SQL claim. The self-dispatch carries ONLY a Bearer HMAC token and NO
1635
+ // session cookie, so without this bypass the blanket 401-for-/_agent-native/*
1636
+ // gate below blocks the worker before `prepareProcessRunRequest` ever runs —
1637
+ // the run is never claimed, its heartbeat never starts, and it times out with
1638
+ // no visible progress. Exact path only (mirrors agent-teams).
1639
+ if (p === "/_agent-native/agent-chat/_process-run") {
1640
+ return;
1641
+ }
1642
+
1630
1643
  // Read-only agent chat share links. The random token is the bearer secret;
1631
1644
  // the route returns a sanitized transcript plus bounded run summaries and
1632
1645
  // exposes no write surface, live event stream, tool payloads, or owner APIs.
@@ -76,7 +76,7 @@ function CommandLoadingGroup({
76
76
  forceMount
77
77
  value={`${heading} loading ${index + 1}`}
78
78
  >
79
- <Skeleton className="mr-2 h-4 w-4 shrink-0 rounded-sm" />
79
+ <Skeleton className="me-2 h-4 w-4 shrink-0 rounded-sm" />
80
80
  <Skeleton
81
81
  className={`h-4 rounded ${
82
82
  loadingRowWidths[index % loadingRowWidths.length]
@@ -303,10 +303,10 @@ export function CommandPalette() {
303
303
  "dashboard",
304
304
  )}
305
305
  >
306
- <IconLayoutDashboard className="mr-2 h-4 w-4 text-muted-foreground" />
306
+ <IconLayoutDashboard className="me-2 h-4 w-4 text-muted-foreground" />
307
307
  <span className="truncate">{d.name}</span>
308
308
  {d.hiddenAt ? (
309
- <span className="ml-2 rounded border border-border px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
309
+ <span className="ms-2 rounded border border-border px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
310
310
  Hidden
311
311
  </span>
312
312
  ) : null}
@@ -331,10 +331,10 @@ export function CommandPalette() {
331
331
  "dashboard",
332
332
  )}
333
333
  >
334
- <IconLayoutDashboard className="mr-2 h-4 w-4 text-muted-foreground" />
334
+ <IconLayoutDashboard className="me-2 h-4 w-4 text-muted-foreground" />
335
335
  <span className="truncate">{d.name}</span>
336
336
  {d.hiddenAt ? (
337
- <span className="ml-2 rounded border border-border px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
337
+ <span className="ms-2 rounded border border-border px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
338
338
  Hidden
339
339
  </span>
340
340
  ) : null}
@@ -362,7 +362,7 @@ export function CommandPalette() {
362
362
  "tool",
363
363
  )}
364
364
  >
365
- <IconTool className="mr-2 h-4 w-4 text-muted-foreground" />
365
+ <IconTool className="me-2 h-4 w-4 text-muted-foreground" />
366
366
  {extension.name}
367
367
  </CommandItem>
368
368
  ))}
@@ -376,7 +376,7 @@ export function CommandPalette() {
376
376
  onSelect={() => go(`/dashboards/${d.id}`)}
377
377
  keywords={commandPaletteKeywords(d.name, "dashboard")}
378
378
  >
379
- <IconFlask className="mr-2 h-4 w-4 text-muted-foreground" />
379
+ <IconFlask className="me-2 h-4 w-4 text-muted-foreground" />
380
380
  {d.name}
381
381
  </CommandItem>
382
382
  ))}
@@ -389,7 +389,7 @@ export function CommandPalette() {
389
389
  onSelect={() => go(t.href)}
390
390
  keywords={commandPaletteKeywords(t.name, "tool")}
391
391
  >
392
- <IconTool className="mr-2 h-4 w-4 text-muted-foreground" />
392
+ <IconTool className="me-2 h-4 w-4 text-muted-foreground" />
393
393
  {t.name}
394
394
  </CommandItem>
395
395
  ))}
@@ -405,9 +405,9 @@ export function CommandPalette() {
405
405
  keywords={["theme", "dark", "light", "mode"]}
406
406
  >
407
407
  {isDark ? (
408
- <IconSun className="mr-2 h-4 w-4 text-muted-foreground" />
408
+ <IconSun className="me-2 h-4 w-4 text-muted-foreground" />
409
409
  ) : (
410
- <IconMoon className="mr-2 h-4 w-4 text-muted-foreground" />
410
+ <IconMoon className="me-2 h-4 w-4 text-muted-foreground" />
411
411
  )}
412
412
  Toggle {isDark ? "light" : "dark"} mode
413
413
  </CommandItem>
@@ -427,7 +427,7 @@ export function CommandPalette() {
427
427
  "changes",
428
428
  )}
429
429
  >
430
- <IconHistory className="mr-2 h-4 w-4 text-muted-foreground" />
430
+ <IconHistory className="me-2 h-4 w-4 text-muted-foreground" />
431
431
  What's new
432
432
  </CommandItem>
433
433
  </CommandGroup>
@@ -448,7 +448,7 @@ export function CommandPalette() {
448
448
  "chart",
449
449
  )}
450
450
  >
451
- <IconChartBar className="mr-2 h-4 w-4 text-muted-foreground" />
451
+ <IconChartBar className="me-2 h-4 w-4 text-muted-foreground" />
452
452
  {c.name}
453
453
  </CommandItem>
454
454
  ))}
@@ -499,7 +499,7 @@ function SortableRow({
499
499
  <div ref={setNodeRef} style={style} className="group/item relative min-w-0">
500
500
  <button
501
501
  type="button"
502
- className="absolute -left-4 top-1/2 z-10 -translate-y-1/2 cursor-grab rounded p-1 text-muted-foreground/30 opacity-0 transition-colors hover:text-muted-foreground/60 group-hover/item:opacity-100 active:cursor-grabbing"
502
+ className="absolute -start-4 top-1/2 z-10 -translate-y-1/2 cursor-grab rounded p-1 text-muted-foreground/30 opacity-0 transition-colors hover:text-muted-foreground/60 group-hover/item:opacity-100 active:cursor-grabbing"
503
503
  aria-label={`Drag ${name}`}
504
504
  {...attributes}
505
505
  {...listeners}
@@ -527,7 +527,7 @@ function SortableRow({
527
527
  setIsRenaming(false);
528
528
  }
529
529
  }}
530
- className="min-w-0 flex-1 bg-transparent px-2 py-1.5 pr-12 text-xs outline-none"
530
+ className="min-w-0 flex-1 bg-transparent px-2 py-1.5 pe-12 text-xs outline-none"
531
531
  />
532
532
  ) : (
533
533
  <Tooltip>
@@ -537,7 +537,7 @@ function SortableRow({
537
537
  onFocus={onPrefetch}
538
538
  onMouseEnter={onPrefetch}
539
539
  onTouchStart={onPrefetch}
540
- className="min-w-0 flex-1 px-2 py-1.5 pr-12 text-xs transition-[padding] md:pr-2 md:group-hover/item:pr-12 md:group-focus-within/item:pr-12"
540
+ className="min-w-0 flex-1 px-2 py-1.5 pe-12 text-xs transition-[padding] md:pe-2 md:group-hover/item:pe-12 md:group-focus-within/item:pe-12"
541
541
  >
542
542
  <span className="block truncate">{name}</span>
543
543
  </Link>
@@ -545,7 +545,7 @@ function SortableRow({
545
545
  <TooltipContent side="right">{name}</TooltipContent>
546
546
  </Tooltip>
547
547
  )}
548
- <div className="pointer-events-none absolute right-1 top-1/2 flex -translate-y-1/2 items-center gap-0.5 opacity-100 transition-opacity md:opacity-0 md:group-hover/item:opacity-100 md:group-focus-within/item:opacity-100">
548
+ <div className="pointer-events-none absolute end-1 top-1/2 flex -translate-y-1/2 items-center gap-0.5 opacity-100 transition-opacity md:opacity-0 md:group-hover/item:opacity-100 md:group-focus-within/item:opacity-100">
549
549
  <Tooltip>
550
550
  <TooltipTrigger asChild>
551
551
  <button
@@ -588,7 +588,7 @@ function SortableRow({
588
588
  setIsRenaming(true);
589
589
  }}
590
590
  >
591
- <IconPencil className="mr-2 h-3.5 w-3.5" />
591
+ <IconPencil className="me-2 h-3.5 w-3.5" />
592
592
  Rename
593
593
  </DropdownMenuItem>
594
594
  {onSetVisibility && visibility !== undefined && (
@@ -601,15 +601,15 @@ function SortableRow({
601
601
  }}
602
602
  >
603
603
  {visibility === "private" ? (
604
- <IconBuilding className="mr-2 h-3.5 w-3.5" />
604
+ <IconBuilding className="me-2 h-3.5 w-3.5" />
605
605
  ) : (
606
- <IconLock className="mr-2 h-3.5 w-3.5" />
606
+ <IconLock className="me-2 h-3.5 w-3.5" />
607
607
  )}
608
608
  {visibility === "private" ? "Share with org" : "Make private"}
609
609
  </DropdownMenuItem>
610
610
  )}
611
611
  <DropdownMenuItem onSelect={copyLink}>
612
- <IconLink className="mr-2 h-3.5 w-3.5" />
612
+ <IconLink className="me-2 h-3.5 w-3.5" />
613
613
  Copy link
614
614
  </DropdownMenuItem>
615
615
  {onUnhide && hidden ? (
@@ -619,7 +619,7 @@ function SortableRow({
619
619
  void runUnhide();
620
620
  }}
621
621
  >
622
- <IconEye className="mr-2 h-3.5 w-3.5" />
622
+ <IconEye className="me-2 h-3.5 w-3.5" />
623
623
  Unhide
624
624
  </DropdownMenuItem>
625
625
  ) : onHide ? (
@@ -629,7 +629,7 @@ function SortableRow({
629
629
  void runHide();
630
630
  }}
631
631
  >
632
- <IconEyeOff className="mr-2 h-3.5 w-3.5" />
632
+ <IconEyeOff className="me-2 h-3.5 w-3.5" />
633
633
  Hide
634
634
  </DropdownMenuItem>
635
635
  ) : null}
@@ -642,7 +642,7 @@ function SortableRow({
642
642
  void runArchive();
643
643
  }}
644
644
  >
645
- <IconArchive className="mr-2 h-3.5 w-3.5" />
645
+ <IconArchive className="me-2 h-3.5 w-3.5" />
646
646
  Archive
647
647
  </DropdownMenuItem>
648
648
  <DropdownMenuSeparator />
@@ -654,7 +654,7 @@ function SortableRow({
654
654
  }}
655
655
  className="text-destructive focus:text-destructive"
656
656
  >
657
- <IconTrash className="mr-2 h-3.5 w-3.5" />
657
+ <IconTrash className="me-2 h-3.5 w-3.5" />
658
658
  Delete permanently
659
659
  </DropdownMenuItem>
660
660
  </>
@@ -667,7 +667,7 @@ function SortableRow({
667
667
  }}
668
668
  className="text-destructive focus:text-destructive"
669
669
  >
670
- <IconTrash className="mr-2 h-3.5 w-3.5" />
670
+ <IconTrash className="me-2 h-3.5 w-3.5" />
671
671
  Delete
672
672
  </DropdownMenuItem>
673
673
  )}
@@ -789,7 +789,7 @@ function SortableDashboardItem({
789
789
  }
790
790
  >
791
791
  {isActive && allSubviews.length > 0 && (
792
- <div className="ml-6 mt-0.5 space-y-0.5">
792
+ <div className="ms-6 mt-0.5 space-y-0.5">
793
793
  {allSubviews.map((sv) => {
794
794
  const currentSearch = new URLSearchParams(location.search);
795
795
  const svUrl = new URL(sv.href, window.location.origin);
@@ -805,7 +805,7 @@ function SortableDashboardItem({
805
805
  <div
806
806
  key={sv.id}
807
807
  className={cn(
808
- "group/sv flex items-center gap-1 rounded-md pr-1 transition-all",
808
+ "group/sv flex items-center gap-1 rounded-md pe-1 transition-all",
809
809
  isSubviewActive
810
810
  ? "bg-primary/10 text-primary"
811
811
  : "text-muted-foreground/70 hover:bg-sidebar-accent/50 hover:text-primary",
@@ -1842,7 +1842,7 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
1842
1842
  {!mobile && (
1843
1843
  <div
1844
1844
  onMouseDown={handleResizeStart}
1845
- className="absolute right-0 top-0 bottom-0 w-1 cursor-col-resize hover:bg-primary/30 active:bg-primary/50 z-10"
1845
+ className="absolute end-0 top-0 bottom-0 w-1 cursor-col-resize hover:bg-primary/30 active:bg-primary/50 z-10"
1846
1846
  />
1847
1847
  )}
1848
1848
  <div className="flex h-12 shrink-0 items-center border-b border-border px-4 lg:px-6">
@@ -1959,7 +1959,7 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
1959
1959
  <button
1960
1960
  type="button"
1961
1961
  onClick={toggleDashOpen}
1962
- className="flex min-w-0 flex-1 items-center gap-3 px-3 py-2 text-left"
1962
+ className="flex min-w-0 flex-1 items-center gap-3 px-3 py-2 text-start"
1963
1963
  aria-expanded={dashOpen}
1964
1964
  >
1965
1965
  <IconChartBar className="h-4 w-4 shrink-0" />
@@ -1977,7 +1977,7 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
1977
1977
  <button
1978
1978
  type="button"
1979
1979
  onClick={toggleDashOpen}
1980
- className="mr-1 flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground/70 hover:bg-sidebar-accent hover:text-foreground"
1980
+ className="me-1 flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground/70 hover:bg-sidebar-accent hover:text-foreground"
1981
1981
  aria-label={
1982
1982
  dashOpen ? "Collapse dashboards" : "Expand dashboards"
1983
1983
  }
@@ -2001,7 +2001,7 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
2001
2001
  items={displayedDashboards.map((d) => d.id)}
2002
2002
  strategy={verticalListSortingStrategy}
2003
2003
  >
2004
- <div className="ml-4 min-w-0 space-y-0.5">
2004
+ <div className="ms-4 min-w-0 space-y-0.5">
2005
2005
  {displayedDashboards.map((d) => (
2006
2006
  <SortableDashboardItem
2007
2007
  key={d.id}
@@ -2062,7 +2062,7 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
2062
2062
  <button
2063
2063
  type="button"
2064
2064
  onClick={toggleAnalysesOpen}
2065
- className="flex min-w-0 flex-1 items-center gap-3 px-3 py-2 text-left"
2065
+ className="flex min-w-0 flex-1 items-center gap-3 px-3 py-2 text-start"
2066
2066
  aria-expanded={analysesOpen}
2067
2067
  >
2068
2068
  <IconReportAnalytics className="h-4 w-4 shrink-0" />
@@ -2084,7 +2084,7 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
2084
2084
  <button
2085
2085
  type="button"
2086
2086
  onClick={toggleAnalysesOpen}
2087
- className="mr-1 flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground/70 hover:bg-sidebar-accent hover:text-foreground"
2087
+ className="me-1 flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground/70 hover:bg-sidebar-accent hover:text-foreground"
2088
2088
  aria-label={
2089
2089
  analysesOpen ? "Collapse analyses" : "Expand analyses"
2090
2090
  }
@@ -2108,7 +2108,7 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
2108
2108
  items={displayedAnalyses.map((a) => a.id)}
2109
2109
  strategy={verticalListSortingStrategy}
2110
2110
  >
2111
- <div className="ml-4 min-w-0 space-y-0.5">
2111
+ <div className="ms-4 min-w-0 space-y-0.5">
2112
2112
  {displayedAnalyses.map((a) => (
2113
2113
  <SortableRow
2114
2114
  key={a.id}
@@ -2233,7 +2233,7 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
2233
2233
  <TooltipTrigger asChild>
2234
2234
  <PopoverTrigger asChild>
2235
2235
  <button className="flex items-center justify-center rounded-lg p-2 text-muted-foreground transition-all hover:text-primary cursor-pointer hover:bg-sidebar-accent/50">
2236
- <IconLogout className="h-4 w-4" />
2236
+ <IconLogout className="h-4 w-4 rtl:-scale-x-100" />
2237
2237
  </button>
2238
2238
  </PopoverTrigger>
2239
2239
  </TooltipTrigger>
@@ -78,13 +78,13 @@ export default function AnalysesList() {
78
78
  </p>
79
79
  {analyses && analyses.length > 0 && (
80
80
  <div className="relative">
81
- <IconSearch className="absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground/60 pointer-events-none" />
81
+ <IconSearch className="absolute start-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground/60 pointer-events-none" />
82
82
  <input
83
83
  type="search"
84
84
  placeholder="Search analyses…"
85
85
  value={searchQuery}
86
86
  onChange={(e) => setSearchQuery(e.target.value)}
87
- className="h-8 rounded-md border border-input bg-background pl-8 pr-3 text-xs placeholder:text-muted-foreground/60 focus:outline-none focus:ring-2 focus:ring-ring"
87
+ className="h-8 rounded-md border border-input bg-background ps-8 pe-3 text-xs placeholder:text-muted-foreground/60 focus:outline-none focus:ring-2 focus:ring-ring"
88
88
  />
89
89
  </div>
90
90
  )}
@@ -163,12 +163,12 @@ export default function KnowledgeRoute() {
163
163
  <Card>
164
164
  <CardContent className="flex flex-col gap-3 p-4 lg:flex-row lg:items-center">
165
165
  <div className="relative min-w-0 flex-1">
166
- <IconSearch className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
166
+ <IconSearch className="pointer-events-none absolute start-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
167
167
  <Input
168
168
  value={query}
169
169
  onChange={(event) => updateParam("q", event.target.value)}
170
170
  placeholder="Search memories, topics, source names..."
171
- className="pl-9"
171
+ className="ps-9"
172
172
  />
173
173
  </div>
174
174
  <div className="flex flex-col gap-2 sm:flex-row">
@@ -217,8 +217,8 @@ export default function KnowledgeRoute() {
217
217
  <TableHead>Source</TableHead>
218
218
  <TableHead>Status</TableHead>
219
219
  <TableHead>Company context</TableHead>
220
- <TableHead className="text-right">Confidence</TableHead>
221
- <TableHead className="text-right">Cites</TableHead>
220
+ <TableHead className="text-end">Confidence</TableHead>
221
+ <TableHead className="text-end">Cites</TableHead>
222
222
  </TableRow>
223
223
  </TableHeader>
224
224
  <TableBody>
@@ -262,12 +262,12 @@ export default function KnowledgeRoute() {
262
262
  onPreview={() => void openCanonicalPreview(row)}
263
263
  />
264
264
  </TableCell>
265
- <TableCell className="text-right">
265
+ <TableCell className="text-end">
266
266
  {typeof row.confidence === "number"
267
267
  ? formatPercent(row.confidence)
268
268
  : "n/a"}
269
269
  </TableCell>
270
- <TableCell className="text-right">
270
+ <TableCell className="text-end">
271
271
  {row.citations ?? 0}
272
272
  </TableCell>
273
273
  </TableRow>
@@ -416,11 +416,11 @@ export default function OpsRoute() {
416
416
  <TableHead>Status</TableHead>
417
417
  <TableHead>Capture</TableHead>
418
418
  <TableHead>Source</TableHead>
419
- <TableHead className="text-right">Attempts</TableHead>
419
+ <TableHead className="text-end">Attempts</TableHead>
420
420
  <TableHead>Run after</TableHead>
421
421
  <TableHead>Updated</TableHead>
422
422
  <TableHead>Reason</TableHead>
423
- <TableHead className="text-right">Action</TableHead>
423
+ <TableHead className="text-end">Action</TableHead>
424
424
  </TableRow>
425
425
  </TableHeader>
426
426
  <TableBody>
@@ -474,7 +474,7 @@ export default function OpsRoute() {
474
474
  </p>
475
475
  </div>
476
476
  </TableCell>
477
- <TableCell className="text-right tabular-nums">
477
+ <TableCell className="text-end tabular-nums">
478
478
  {item.attempts}
479
479
  </TableCell>
480
480
  <TableCell className="whitespace-nowrap text-sm text-muted-foreground">
@@ -491,7 +491,7 @@ export default function OpsRoute() {
491
491
  "No issue recorded"}
492
492
  </p>
493
493
  </TableCell>
494
- <TableCell className="text-right">
494
+ <TableCell className="text-end">
495
495
  <Button
496
496
  size="sm"
497
497
  variant="outline"
@@ -144,12 +144,12 @@ export default function SearchRoute() {
144
144
  <Card>
145
145
  <CardContent className="grid gap-3 p-4">
146
146
  <div className="relative">
147
- <IconSearch className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
147
+ <IconSearch className="pointer-events-none absolute start-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
148
148
  <Input
149
149
  value={query}
150
150
  onChange={(event) => updateParam("q", event.target.value)}
151
151
  placeholder="Search decisions, customer facts, source names, transcripts, or policy snippets..."
152
- className="h-11 pl-9 text-base"
152
+ className="h-11 ps-9 text-base"
153
153
  autoFocus
154
154
  />
155
155
  </div>
@@ -420,10 +420,10 @@ function SearchResultDetails({
420
420
  </Badge>
421
421
  ) : null}
422
422
  </div>
423
- <SheetTitle className="text-left text-xl leading-7">
423
+ <SheetTitle className="text-start text-xl leading-7">
424
424
  {result.title}
425
425
  </SheetTitle>
426
- <SheetDescription className="text-left leading-6">
426
+ <SheetDescription className="text-start leading-6">
427
427
  {body}
428
428
  </SheetDescription>
429
429
  </SheetHeader>
@@ -608,9 +608,9 @@ function NumberField({
608
608
  max={max}
609
609
  value={value}
610
610
  onChange={(event) => onChange(Number(event.target.value))}
611
- className="rounded-r-none"
611
+ className="rounded-e-none"
612
612
  />
613
- <div className="flex min-w-20 items-center justify-center rounded-r-md border border-l-0 border-input bg-muted px-3 text-sm text-muted-foreground">
613
+ <div className="flex min-w-20 items-center justify-center rounded-e-md border border-s-0 border-input bg-muted px-3 text-sm text-muted-foreground">
614
614
  {suffix}
615
615
  </div>
616
616
  </div>
@@ -653,7 +653,7 @@ function PolicyRow({ label, value }: { label: string; value: string }) {
653
653
  return (
654
654
  <div className="flex items-center justify-between gap-3">
655
655
  <span className="min-w-0 text-muted-foreground">{label}</span>
656
- <span className="max-w-40 truncate text-right font-medium capitalize">
656
+ <span className="max-w-40 truncate text-end font-medium capitalize">
657
657
  {value.replace(/_/g, " ")}
658
658
  </span>
659
659
  </div>
@@ -1074,7 +1074,7 @@ function ProviderCatalog({
1074
1074
  variant="outline"
1075
1075
  className={`${grantStateClass(grantState)} w-fit max-w-full`}
1076
1076
  >
1077
- <GrantIcon className="mr-1 size-3" />
1077
+ <GrantIcon className="me-1 size-3" />
1078
1078
  {grantStateLabel(grantState)}
1079
1079
  </Badge>
1080
1080
  <Badge
@@ -2037,7 +2037,7 @@ export default function SourcesRoute() {
2037
2037
  {enqueueCapturesDistillation.isPending ? (
2038
2038
  <IconLoader2 className="size-4 animate-spin" />
2039
2039
  ) : (
2040
- <IconSend className="size-4" />
2040
+ <IconSend className="size-4 rtl:-scale-x-100" />
2041
2041
  )}
2042
2042
  Queue selected
2043
2043
  </Button>
@@ -2205,7 +2205,7 @@ export default function SourcesRoute() {
2205
2205
  {enqueueDistillation.isPending ? (
2206
2206
  <IconLoader2 className="size-4 animate-spin" />
2207
2207
  ) : (
2208
- <IconSend className="size-4" />
2208
+ <IconSend className="size-4 rtl:-scale-x-100" />
2209
2209
  )}
2210
2210
  {queueActionLabel(queue)}
2211
2211
  </Button>
@@ -1072,7 +1072,7 @@ function Modal({
1072
1072
  return (
1073
1073
  <Dialog open={open} onOpenChange={(next) => !next && onClose()}>
1074
1074
  <DialogContent className="max-h-[92vh] max-w-2xl overflow-y-auto p-0">
1075
- <DialogHeader className="border-b p-4 pr-10">
1075
+ <DialogHeader className="border-b p-4 pe-10">
1076
1076
  <DialogTitle className="text-base">{title}</DialogTitle>
1077
1077
  {description ? (
1078
1078
  <DialogDescription>{description}</DialogDescription>
@@ -1784,7 +1784,7 @@ function SetupWizard({
1784
1784
  onClick={() => onStepChange(step - 1)}
1785
1785
  disabled={saving}
1786
1786
  >
1787
- <IconArrowLeft size={14} />
1787
+ <IconArrowLeft size={14} className="rtl:-scale-x-100" />
1788
1788
  Back
1789
1789
  </Button>
1790
1790
  ) : null}
@@ -1795,7 +1795,7 @@ function SetupWizard({
1795
1795
  disabled={!canAdvance || loading || saving}
1796
1796
  >
1797
1797
  Next
1798
- <IconArrowRight size={14} />
1798
+ <IconArrowRight size={14} className="rtl:-scale-x-100" />
1799
1799
  </Button>
1800
1800
  ) : (
1801
1801
  <Button