@agent-native/core 0.76.1 → 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.
@@ -1,5 +1,20 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.76.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 93c06b0: Make durable background agent runs opt-in (default-off) again. Both the runtime
8
+ gate (`isFlagEnabled` in durable-background.ts) and the deploy-time `-background`
9
+ emit gate (`isDurableBackgroundDeployEnabled` in deploy/build.ts) now default to
10
+ OFF when `AGENT_CHAT_DURABLE_BACKGROUND` is unset; an app opts in only with an
11
+ explicit truthy value (`true`/`1`/`yes`/`on`). A premature fleet-wide default-on
12
+ caused real-user incidents (apps hit "Failed to dispatch background run" + chat
13
+ stalls) because the async background-function worker path is not yet proven
14
+ end-to-end and the deploy-time env opt-out is not reliably baked into a given
15
+ deploy. Re-enable default-on only after the 15-min background-function worker is
16
+ verified live in production.
17
+
3
18
  ## 0.76.1
4
19
 
5
20
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.76.1",
3
+ "version": "0.76.2",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -237,30 +237,34 @@ function isFlagEnabled(): boolean {
237
237
  // can statically verify it against the allowlisted `AGENT_*` prefix. Keep this
238
238
  // in sync with AGENT_CHAT_DURABLE_BACKGROUND_ENV.
239
239
  //
240
- // DEFAULT-ON: durable background runs are the desired behavior for every
241
- // hosted app. So an unset/empty/unknown flag means ON; an app opts OUT only
242
- // with an explicit falsy value. This still composes with the hosted +
243
- // A2A_SECRET gates below, so non-hosted / unconfigured apps stay synchronous.
244
- // Safety net: a failed dispatch degrades to a synchronous inline run (see
245
- // production-agent.ts), so default-on cannot break chat even if the
246
- // self-dispatch can't be delivered on a given app.
240
+ // DEFAULT-OFF (opt-in): durable background runs are still being hardened. A
241
+ // premature fleet-wide default-on caused real-user incidents (Assets/Analytics
242
+ // hit "Failed to dispatch" + stalls, 2026-06-24) because the async background
243
+ // worker path is not yet proven end-to-end and the deploy-time env opt-out is
244
+ // not reliably baked into a given deploy. So an unset/empty/unknown flag means
245
+ // OFF; an app opts IN only with an explicit truthy value
246
+ // (AGENT_CHAT_DURABLE_BACKGROUND=true). This still composes with the hosted +
247
+ // A2A_SECRET gates below. Flip back to default-on only after the 15-min
248
+ // background-function worker is verified live in production (see the
249
+ // project_durable_bg_prod_verified memory).
247
250
  const raw = process.env.AGENT_CHAT_DURABLE_BACKGROUND;
248
- if (raw == null) return true;
251
+ if (raw == null) return false;
249
252
  const normalized = raw.trim().toLowerCase();
250
- return !(
251
- normalized === "0" ||
252
- normalized === "false" ||
253
- normalized === "no" ||
254
- normalized === "off"
253
+ return (
254
+ normalized === "1" ||
255
+ normalized === "true" ||
256
+ normalized === "yes" ||
257
+ normalized === "on"
255
258
  );
256
259
  }
257
260
 
258
261
  /**
259
- * The single gate. True when the flag is not explicitly disabled (default-on)
262
+ * The single gate. True when the flag is explicitly enabled (opt-in/default-off)
260
263
  * AND the runtime is hosted AND A2A_SECRET is configured. False otherwise — and
261
264
  * false means the current synchronous behavior is used, unchanged. So a local /
262
- * non-hosted / unconfigured app stays synchronous even with the flag defaulting
263
- * on; durable only engages where the runtime actually supports it.
265
+ * non-hosted / unconfigured app stays synchronous, and an app that has not opted
266
+ * in stays synchronous; durable only engages where it is explicitly enabled and
267
+ * the runtime actually supports it.
264
268
  */
265
269
  export function isAgentChatDurableBackgroundEnabled(): boolean {
266
270
  return (
@@ -1485,10 +1485,12 @@ export function findInstalledResvgPackages(
1485
1485
  * Reads the same env flag the runtime gate uses
1486
1486
  * (`AGENT_CHAT_DURABLE_BACKGROUND`).
1487
1487
  *
1488
- * DEFAULT-ON, matching the runtime gate (`isFlagEnabled` in
1489
- * durable-background.ts): unset/empty/unknown means enabled; an app opts OUT
1490
- * only with an explicit falsy value (`false`/`0`/`no`/`off`). This is what
1491
- * actually emits the 15-min `-background` function so the `_process-run`
1488
+ * DEFAULT-OFF (opt-in), matching the runtime gate (`isFlagEnabled` in
1489
+ * durable-background.ts): unset/empty/unknown means DISABLED; an app opts IN
1490
+ * only with an explicit truthy value (`true`/`1`/`yes`/`on`). A premature
1491
+ * fleet-wide default-on caused real-user incidents (2026-06-24) before the
1492
+ * async worker path was proven, so durable is opt-in until verified live. This
1493
+ * gate is what emits the 15-min `-background` function so the `_process-run`
1492
1494
  * dispatch lands on it (async 202 → the worker runs with the real 15-min budget
1493
1495
  * → its ~13-min soft-timeout fits). The deploy gate and runtime gate MUST agree:
1494
1496
  * if the deploy emitted no `-background` function but the runtime still routed
@@ -1499,9 +1501,9 @@ export function findInstalledResvgPackages(
1499
1501
  */
1500
1502
  export function isDurableBackgroundDeployEnabled(): boolean {
1501
1503
  const raw = process.env.AGENT_CHAT_DURABLE_BACKGROUND;
1502
- if (raw == null) return true;
1504
+ if (raw == null) return false;
1503
1505
  const v = raw.trim().toLowerCase();
1504
- return !(v === "0" || v === "false" || v === "no" || v === "off");
1506
+ return v === "1" || v === "true" || v === "yes" || v === "on";
1505
1507
  }
1506
1508
 
1507
1509
  /**
@@ -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
@@ -1119,7 +1119,7 @@ export function EmailThread({
1119
1119
  onClick={goBack}
1120
1120
  className="mt-0.5 flex h-9 w-9 sm:h-7 sm:w-7 shrink-0 items-center justify-center rounded-full text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
1121
1121
  >
1122
- <IconArrowLeft className="h-[14px] w-[14px]" />
1122
+ <IconArrowLeft className="h-[14px] w-[14px] rtl:-scale-x-100" />
1123
1123
  </button>
1124
1124
  </TooltipTrigger>
1125
1125
  <TooltipContent>Back (Esc)</TooltipContent>
@@ -1166,7 +1166,7 @@ export function EmailThread({
1166
1166
  )}
1167
1167
  <button
1168
1168
  onClick={() => goToSibling(-1)}
1169
- className="flex h-7 w-7 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent transition-colors ml-1"
1169
+ className="flex h-7 w-7 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent transition-colors ms-1"
1170
1170
  >
1171
1171
  <IconChevronUp className="h-3.5 w-3.5" />
1172
1172
  </button>
@@ -1181,7 +1181,7 @@ export function EmailThread({
1181
1181
  <TooltipTrigger asChild>
1182
1182
  <button
1183
1183
  onClick={onToggleMaximize}
1184
- className="flex h-7 w-7 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent transition-colors ml-1"
1184
+ className="flex h-7 w-7 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent transition-colors ms-1"
1185
1185
  aria-label={isMaximized ? "Minimize" : "Maximize"}
1186
1186
  aria-pressed={isMaximized}
1187
1187
  >
@@ -1498,7 +1498,7 @@ function ThreadLoadingState({
1498
1498
  onClick={onBack}
1499
1499
  className="mt-0.5 flex h-9 w-9 sm:h-7 sm:w-7 shrink-0 items-center justify-center rounded-full text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
1500
1500
  >
1501
- <IconArrowLeft className="h-[14px] w-[14px]" />
1501
+ <IconArrowLeft className="h-[14px] w-[14px] rtl:-scale-x-100" />
1502
1502
  </button>
1503
1503
  </TooltipTrigger>
1504
1504
  <TooltipContent>Back (Esc)</TooltipContent>
@@ -1639,7 +1639,7 @@ const CollapsedMessageRow = forwardRef<
1639
1639
  <span className="text-[13px] text-muted-foreground truncate flex-1">
1640
1640
  {email.snippet}
1641
1641
  </span>
1642
- <span className="text-[12px] text-muted-foreground/60 tabular-nums shrink-0 ml-2">
1642
+ <span className="text-[12px] text-muted-foreground/60 tabular-nums shrink-0 ms-2">
1643
1643
  {formatEmailDate(email.date)}
1644
1644
  </span>
1645
1645
  </div>
@@ -1795,7 +1795,7 @@ const ExpandedMessageCard = forwardRef<
1795
1795
  e.stopPropagation();
1796
1796
  setShowDetails(true);
1797
1797
  }}
1798
- className="text-[12px] text-muted-foreground/50 hover:text-muted-foreground transition-colors truncate text-left"
1798
+ className="text-[12px] text-muted-foreground/50 hover:text-muted-foreground transition-colors truncate text-start"
1799
1799
  >
1800
1800
  to {recipients}
1801
1801
  </button>
@@ -1812,7 +1812,7 @@ const ExpandedMessageCard = forwardRef<
1812
1812
  }}
1813
1813
  className="flex h-9 w-9 sm:h-6 sm:w-6 items-center justify-center rounded text-muted-foreground/40 hover:text-foreground transition-colors"
1814
1814
  >
1815
- <IconArrowBackUp className="h-4 w-4 sm:h-[14px] sm:w-[14px]" />
1815
+ <IconArrowBackUp className="h-4 w-4 sm:h-[14px] sm:w-[14px] rtl:-scale-x-100" />
1816
1816
  </button>
1817
1817
  </TooltipTrigger>
1818
1818
  <TooltipContent>Reply</TooltipContent>
@@ -1826,7 +1826,7 @@ const ExpandedMessageCard = forwardRef<
1826
1826
  }}
1827
1827
  className="flex h-9 w-9 sm:h-6 sm:w-6 items-center justify-center rounded text-muted-foreground/40 hover:text-foreground transition-colors"
1828
1828
  >
1829
- <IconArrowBackUpDouble className="h-4 w-4 sm:h-[14px] sm:w-[14px]" />
1829
+ <IconArrowBackUpDouble className="h-4 w-4 sm:h-[14px] sm:w-[14px] rtl:-scale-x-100" />
1830
1830
  </button>
1831
1831
  </TooltipTrigger>
1832
1832
  <TooltipContent>Reply All</TooltipContent>
@@ -1840,7 +1840,7 @@ const ExpandedMessageCard = forwardRef<
1840
1840
  }}
1841
1841
  className="flex h-9 w-9 sm:h-6 sm:w-6 items-center justify-center rounded text-muted-foreground/40 hover:text-foreground transition-colors"
1842
1842
  >
1843
- <IconArrowForwardUp className="h-4 w-4 sm:h-[14px] sm:w-[14px]" />
1843
+ <IconArrowForwardUp className="h-4 w-4 sm:h-[14px] sm:w-[14px] rtl:-scale-x-100" />
1844
1844
  </button>
1845
1845
  </TooltipTrigger>
1846
1846
  <TooltipContent>Forward</TooltipContent>
@@ -3409,7 +3409,7 @@ function ThreadSearchBar({
3409
3409
  <TooltipTrigger asChild>
3410
3410
  <button
3411
3411
  onClick={onClose}
3412
- className="flex h-8 w-8 sm:h-6 sm:w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent transition-colors ml-1"
3412
+ className="flex h-8 w-8 sm:h-6 sm:w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent transition-colors ms-1"
3413
3413
  >
3414
3414
  <IconX className="h-3.5 w-3.5 sm:h-3 sm:w-3" />
3415
3415
  </button>
@@ -303,7 +303,7 @@ function QueueList({
303
303
  key={draft.id}
304
304
  onClick={() => onSelect(draft.id)}
305
305
  className={cn(
306
- "mb-1.5 w-full rounded-md border px-3 py-2.5 text-left transition-colors",
306
+ "mb-1.5 w-full rounded-md border px-3 py-2.5 text-start transition-colors",
307
307
  isActive
308
308
  ? "border-primary/40 bg-primary/10"
309
309
  : "border-border/20 bg-card/40 hover:border-border/50 hover:bg-accent/30",
@@ -488,7 +488,7 @@ function DraftDetail({
488
488
  {sendDraft.isPending ? (
489
489
  <IconLoader2 className="h-3.5 w-3.5 animate-spin" />
490
490
  ) : (
491
- <IconSend className="h-3.5 w-3.5" />
491
+ <IconSend className="h-3.5 w-3.5 rtl:-scale-x-100" />
492
492
  )}
493
493
  Send
494
494
  </Button>
@@ -718,7 +718,7 @@ export function DraftQueuePage() {
718
718
  </div>
719
719
  ) : (
720
720
  <div className="flex min-h-0 flex-1 flex-col sm:flex-row">
721
- <aside className="h-[34dvh] shrink-0 border-b border-border/30 sm:h-auto sm:w-[340px] sm:border-b-0 sm:border-r">
721
+ <aside className="h-[34dvh] shrink-0 border-b border-border/30 sm:h-auto sm:w-[340px] sm:border-b-0 sm:border-e">
722
722
  <QueueList
723
723
  drafts={queue.drafts}
724
724
  selectedId={selectedId}
@@ -125,7 +125,7 @@ function ThreadListSidebar({
125
125
  useKeyboardShortcuts([{ key: "a", meta: true, handler: selectAllThreads }]);
126
126
 
127
127
  return (
128
- <div className="flex h-full w-[220px] min-w-0 shrink-0 flex-col overflow-hidden border-r border-border/30 bg-muted/50 dark:bg-[hsl(220,6%,5%)]">
128
+ <div className="flex h-full w-[220px] min-w-0 shrink-0 flex-col overflow-hidden border-e border-border/30 bg-muted/50 dark:bg-[hsl(220,6%,5%)]">
129
129
  <div className="flex-1 overflow-y-auto">
130
130
  {threads.map((thread) => {
131
131
  const email = thread.latestMessage;
@@ -151,7 +151,7 @@ function ThreadListSidebar({
151
151
  navigate(`/${view}/${threadKey}${routeSearchSuffix}`);
152
152
  }}
153
153
  className={cn(
154
- "w-full text-left px-3 h-[38px] flex items-center border-b border-border/10 transition-colors",
154
+ "w-full text-start px-3 h-[38px] flex items-center border-b border-border/10 transition-colors",
155
155
  isMultiSelected
156
156
  ? "bg-primary/20 ring-1 ring-inset ring-primary/40"
157
157
  : isActive
@@ -682,7 +682,7 @@ export function InboxPage() {
682
682
 
683
683
  {/* Right contact panel — hidden during initial load or when maximized */}
684
684
  {!emailListLoading && !(hasThread && isMaximized) && (
685
- <div className="hidden lg:flex w-[260px] shrink-0 flex-col border-l border-border/30 bg-muted/50 dark:bg-[hsl(220,6%,5%)]">
685
+ <div className="hidden lg:flex w-[260px] shrink-0 flex-col border-s border-border/30 bg-muted/50 dark:bg-[hsl(220,6%,5%)]">
686
686
  <ContactPanel
687
687
  emailId={contactEmailId}
688
688
  contactEmail={sidebarContactEmail}
@@ -1510,7 +1510,7 @@ export function SettingsPage() {
1510
1510
  return (
1511
1511
  <div className="flex flex-1 flex-col sm:flex-row overflow-hidden">
1512
1512
  {/* Top tabs on mobile, left sidebar on desktop */}
1513
- <div className="sm:w-[200px] shrink-0 sm:border-r border-b sm:border-b-0 border-border/30 bg-muted/50 dark:bg-[hsl(220,6%,5%)] sm:p-3 flex sm:flex-col gap-0.5 overflow-x-auto">
1513
+ <div className="sm:w-[200px] shrink-0 sm:border-e border-b sm:border-b-0 border-border/30 bg-muted/50 dark:bg-[hsl(220,6%,5%)] sm:p-3 flex sm:flex-col gap-0.5 overflow-x-auto">
1514
1514
  <p className="hidden sm:block px-2 py-1.5 text-[10px] font-medium text-muted-foreground/40 uppercase tracking-wider mb-1">
1515
1515
  {t("settings.title")}
1516
1516
  </p>
@@ -1523,7 +1523,7 @@ export function SettingsPage() {
1523
1523
  key={item.id}
1524
1524
  onClick={() => setActiveSection(item.id)}
1525
1525
  className={cn(
1526
- "flex items-center gap-2.5 sm:w-full rounded-md px-3 sm:px-2.5 py-2.5 sm:py-2 text-[13px] transition-colors text-left whitespace-nowrap",
1526
+ "flex items-center gap-2.5 sm:w-full rounded-md px-3 sm:px-2.5 py-2.5 sm:py-2 text-[13px] transition-colors text-start whitespace-nowrap",
1527
1527
  isActive
1528
1528
  ? "bg-indigo-500/15 text-indigo-300 font-medium"
1529
1529
  : "text-muted-foreground hover:text-foreground hover:bg-accent/50",
@@ -7271,12 +7271,12 @@ function LocalPlanLoadError({
7271
7271
  </div>
7272
7272
  <div className="mt-5 flex flex-wrap gap-2">
7273
7273
  <Button type="button" variant="outline" onClick={onRetry}>
7274
- <IconRefresh className="mr-2 size-4" />
7274
+ <IconRefresh className="me-2 size-4" />
7275
7275
  Retry
7276
7276
  </Button>
7277
7277
  <Button asChild type="button" variant="ghost">
7278
7278
  <Link to="/plans">
7279
- <IconArrowLeft className="mr-2 size-4" />
7279
+ <IconArrowLeft className="me-2 size-4 rtl:-scale-x-100" />
7280
7280
  Plans
7281
7281
  </Link>
7282
7282
  </Button>
@@ -7443,7 +7443,7 @@ function PlanLoadError({
7443
7443
 
7444
7444
  return (
7445
7445
  <div className="flex h-full flex-col items-center justify-center bg-background p-8">
7446
- <div className="w-full max-w-md rounded-lg border border-border bg-background p-5 text-left shadow-sm">
7446
+ <div className="w-full max-w-md rounded-lg border border-border bg-background p-5 text-start shadow-sm">
7447
7447
  <div className={cn("flex items-start", !showAccessHelp && "gap-3")}>
7448
7448
  {!showAccessHelp && !planMissing && (
7449
7449
  <div className="flex size-10 shrink-0 items-center justify-center rounded-lg border border-amber-500/25 bg-amber-500/10 text-amber-600 dark:text-amber-300">
@@ -139,7 +139,7 @@ export default function DeckCard({
139
139
  </Link>
140
140
 
141
141
  {/* Menu Button - always visible on touch devices */}
142
- <div className="absolute top-2 right-2 sm:opacity-0 sm:group-hover:opacity-100">
142
+ <div className="absolute top-2 end-2 sm:opacity-0 sm:group-hover:opacity-100">
143
143
  <DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>
144
144
  <DropdownMenuTrigger asChild>
145
145
  <button
@@ -170,7 +170,7 @@ export default function DeckCard({
170
170
  startRename();
171
171
  }}
172
172
  >
173
- <IconPencil className="w-3.5 h-3.5 mr-2" />
173
+ <IconPencil className="w-3.5 h-3.5 me-2" />
174
174
  Rename
175
175
  </DropdownMenuItem>
176
176
  <DropdownMenuItem
@@ -182,7 +182,7 @@ export default function DeckCard({
182
182
  }}
183
183
  disabled={isDuplicating}
184
184
  >
185
- <IconCopy className="w-3.5 h-3.5 mr-2" />
185
+ <IconCopy className="w-3.5 h-3.5 me-2" />
186
186
  {isDuplicating ? "Duplicating..." : "Duplicate"}
187
187
  </DropdownMenuItem>
188
188
  <DropdownMenuSeparator />
@@ -194,7 +194,7 @@ export default function DeckCard({
194
194
  }}
195
195
  className="text-red-400 focus:text-red-400"
196
196
  >
197
- <IconTrash className="w-3.5 h-3.5 mr-2" />
197
+ <IconTrash className="w-3.5 h-3.5 me-2" />
198
198
  Delete
199
199
  </DropdownMenuItem>
200
200
  </DropdownMenuContent>
@@ -85,7 +85,7 @@ export function Layout({ children }: LayoutProps) {
85
85
  )}
86
86
  <div
87
87
  className={cn(
88
- "fixed inset-y-0 left-0 z-50 md:static md:z-auto",
88
+ "fixed inset-y-0 start-0 z-50 md:static md:z-auto",
89
89
  sidebarOpen
90
90
  ? "translate-x-0"
91
91
  : "-translate-x-full md:translate-x-0",
@@ -51,7 +51,7 @@ export function Sidebar({ collapsed, onToggleCollapsed }: SidebarProps) {
51
51
 
52
52
  if (collapsed) {
53
53
  return (
54
- <aside className="flex h-full w-12 shrink-0 flex-col items-center gap-1 overflow-hidden border-r border-border bg-sidebar py-2 text-sidebar-foreground">
54
+ <aside className="flex h-full w-12 shrink-0 flex-col items-center gap-1 overflow-hidden border-e border-border bg-sidebar py-2 text-sidebar-foreground">
55
55
  {onToggleCollapsed && (
56
56
  <Tooltip delayDuration={0}>
57
57
  <TooltipTrigger asChild>
@@ -60,7 +60,7 @@ export function Sidebar({ collapsed, onToggleCollapsed }: SidebarProps) {
60
60
  aria-label={t("sidebar.expandSidebar")}
61
61
  className="flex h-10 w-10 items-center justify-center rounded-md text-muted-foreground hover:bg-sidebar-accent/50 hover:text-foreground"
62
62
  >
63
- <IconLayoutSidebarLeftExpand className="h-4 w-4" />
63
+ <IconLayoutSidebarLeftExpand className="h-4 w-4 rtl:-scale-x-100" />
64
64
  </button>
65
65
  </TooltipTrigger>
66
66
  <TooltipContent side="right">
@@ -98,7 +98,7 @@ export function Sidebar({ collapsed, onToggleCollapsed }: SidebarProps) {
98
98
  }
99
99
 
100
100
  return (
101
- <aside className="flex h-full w-56 min-w-0 shrink-0 flex-col overflow-hidden border-r border-border bg-sidebar text-sidebar-foreground">
101
+ <aside className="flex h-full w-56 min-w-0 shrink-0 flex-col overflow-hidden border-e border-border bg-sidebar text-sidebar-foreground">
102
102
  <div className="flex h-12 shrink-0 items-center justify-between border-b border-border px-4">
103
103
  <div className="flex items-center gap-2">
104
104
  <img
@@ -125,7 +125,7 @@ export function Sidebar({ collapsed, onToggleCollapsed }: SidebarProps) {
125
125
  aria-label={t("sidebar.collapseSidebar")}
126
126
  className="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-sidebar-accent/50 hover:text-foreground"
127
127
  >
128
- <IconLayoutSidebarLeftCollapse className="h-4 w-4" />
128
+ <IconLayoutSidebarLeftCollapse className="h-4 w-4 rtl:-scale-x-100" />
129
129
  </button>
130
130
  </TooltipTrigger>
131
131
  <TooltipContent>{t("sidebar.collapseSidebar")}</TooltipContent>
@@ -506,7 +506,7 @@ export default function Index() {
506
506
  aria-label="Show all decks"
507
507
  className="h-7 rounded-md px-3 text-xs data-[state=on]:bg-accent"
508
508
  >
509
- <IconStack2 className="mr-1.5 h-3.5 w-3.5" />
509
+ <IconStack2 className="me-1.5 h-3.5 w-3.5" />
510
510
  All
511
511
  </ToggleGroupItem>
512
512
  <ToggleGroupItem
@@ -514,7 +514,7 @@ export default function Index() {
514
514
  aria-label="Show decks created by me"
515
515
  className="h-7 rounded-md px-3 text-xs data-[state=on]:bg-accent"
516
516
  >
517
- <IconUserCircle className="mr-1.5 h-3.5 w-3.5" />
517
+ <IconUserCircle className="me-1.5 h-3.5 w-3.5" />
518
518
  Mine
519
519
  </ToggleGroupItem>
520
520
  </ToggleGroup>
@@ -533,7 +533,7 @@ export default function Index() {
533
533
  {/* New deck card */}
534
534
  <button
535
535
  onClick={openNewDeck}
536
- className="group relative rounded-xl border border-dashed border-border bg-card hover:border-foreground/15 overflow-hidden text-left cursor-pointer"
536
+ className="group relative rounded-xl border border-dashed border-border bg-card hover:border-foreground/15 overflow-hidden text-start cursor-pointer"
537
537
  >
538
538
  <div className="aspect-video flex items-center justify-center bg-muted/30">
539
539
  <div className="w-12 h-12 rounded-xl bg-accent/50 flex items-center justify-center group-hover:bg-accent">
@@ -37,7 +37,7 @@ export function NavSidebar() {
37
37
  const t = useT();
38
38
 
39
39
  return (
40
- <aside className="flex h-full w-56 shrink-0 flex-col border-r border-border bg-sidebar text-sidebar-foreground">
40
+ <aside className="flex h-full w-56 shrink-0 flex-col border-e border-border bg-sidebar text-sidebar-foreground">
41
41
  <div className="flex h-12 shrink-0 items-center gap-2 px-4 border-b border-border">
42
42
  <img
43
43
  src={appPath("/agent-native-icon-light.svg")}
@@ -302,7 +302,7 @@ export function ComponentLibraryView({
302
302
  Press <strong>Play</strong> to see the cursor demonstrate hover
303
303
  and click interactions:
304
304
  </p>
305
- <ul className="text-xs text-muted-foreground space-y-1 ml-4 list-disc">
305
+ <ul className="text-xs text-muted-foreground space-y-1 ms-4 list-disc">
306
306
  <li>
307
307
  <strong>0.0s - 1.3s</strong>: Cursor approaches
308
308
  </li>
@@ -106,7 +106,7 @@ export default function DesignSystems() {
106
106
  setEditingId(null);
107
107
  setShowSetup(true);
108
108
  }}
109
- className="group relative rounded-xl border border-dashed border-border bg-card hover:border-foreground/15 overflow-hidden text-left cursor-pointer"
109
+ className="group relative rounded-xl border border-dashed border-border bg-card hover:border-foreground/15 overflow-hidden text-start cursor-pointer"
110
110
  >
111
111
  <div className="aspect-video flex items-center justify-center bg-muted/30">
112
112
  <div className="w-12 h-12 rounded-xl bg-accent/50 flex items-center justify-center group-hover:bg-accent">
@@ -102,11 +102,12 @@ export declare function isHostedRuntimeForDurableBackground(): boolean;
102
102
  */
103
103
  export declare function isInBackgroundFunctionRuntime(): boolean;
104
104
  /**
105
- * The single gate. True when the flag is not explicitly disabled (default-on)
105
+ * The single gate. True when the flag is explicitly enabled (opt-in/default-off)
106
106
  * AND the runtime is hosted AND A2A_SECRET is configured. False otherwise — and
107
107
  * false means the current synchronous behavior is used, unchanged. So a local /
108
- * non-hosted / unconfigured app stays synchronous even with the flag defaulting
109
- * on; durable only engages where the runtime actually supports it.
108
+ * non-hosted / unconfigured app stays synchronous, and an app that has not opted
109
+ * in stays synchronous; durable only engages where it is explicitly enabled and
110
+ * the runtime actually supports it.
110
111
  */
111
112
  export declare function isAgentChatDurableBackgroundEnabled(): boolean;
112
113
  /** Decision returned by `prepareProcessRunRequest`. */
@@ -1 +1 @@
1
- {"version":3,"file":"durable-background.d.ts","sourceRoot":"","sources":["../../src/agent/durable-background.ts"],"names":[],"mappings":"AA6CA;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,2CACE,CAAC;AAE3C;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,4BAA4B,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,kCAAkC,gDAA0D,CAAC;AAsB1G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,sCAAsC,IAAI,MAAM,CAY/D;AAED;;;GAGG;AACH,eAAO,MAAM,iCAAiC,kCACb,CAAC;AAElC;;;;;;GAMG;AACH,eAAO,MAAM,+BAA+B,oBAAoB,CAAC;AAEjE;;;;GAIG;AACH,wBAAgB,mCAAmC,IAAI,OAAO,CAsB7D;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,6BAA6B,IAAI,OAAO,CAsBvD;AAyBD;;;;;;GAMG;AACH,wBAAgB,mCAAmC,IAAI,OAAO,CAM7D;AAED,uDAAuD;AACvD,MAAM,MAAM,qBAAqB,GAC7B;IACE,EAAE,EAAE,IAAI,CAAC;IACT,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAEN;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAahE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,qBAAqB,CAiDvB"}
1
+ {"version":3,"file":"durable-background.d.ts","sourceRoot":"","sources":["../../src/agent/durable-background.ts"],"names":[],"mappings":"AA6CA;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,2CACE,CAAC;AAE3C;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,4BAA4B,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,kCAAkC,gDAA0D,CAAC;AAsB1G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,sCAAsC,IAAI,MAAM,CAY/D;AAED;;;GAGG;AACH,eAAO,MAAM,iCAAiC,kCACb,CAAC;AAElC;;;;;;GAMG;AACH,eAAO,MAAM,+BAA+B,oBAAoB,CAAC;AAEjE;;;;GAIG;AACH,wBAAgB,mCAAmC,IAAI,OAAO,CAsB7D;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,6BAA6B,IAAI,OAAO,CAsBvD;AA4BD;;;;;;;GAOG;AACH,wBAAgB,mCAAmC,IAAI,OAAO,CAM7D;AAED,uDAAuD;AACvD,MAAM,MAAM,qBAAqB,GAC7B;IACE,EAAE,EAAE,IAAI,CAAC;IACT,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAEN;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAahE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,qBAAqB,CAiDvB"}
@@ -207,28 +207,32 @@ function isFlagEnabled() {
207
207
  // can statically verify it against the allowlisted `AGENT_*` prefix. Keep this
208
208
  // in sync with AGENT_CHAT_DURABLE_BACKGROUND_ENV.
209
209
  //
210
- // DEFAULT-ON: durable background runs are the desired behavior for every
211
- // hosted app. So an unset/empty/unknown flag means ON; an app opts OUT only
212
- // with an explicit falsy value. This still composes with the hosted +
213
- // A2A_SECRET gates below, so non-hosted / unconfigured apps stay synchronous.
214
- // Safety net: a failed dispatch degrades to a synchronous inline run (see
215
- // production-agent.ts), so default-on cannot break chat even if the
216
- // self-dispatch can't be delivered on a given app.
210
+ // DEFAULT-OFF (opt-in): durable background runs are still being hardened. A
211
+ // premature fleet-wide default-on caused real-user incidents (Assets/Analytics
212
+ // hit "Failed to dispatch" + stalls, 2026-06-24) because the async background
213
+ // worker path is not yet proven end-to-end and the deploy-time env opt-out is
214
+ // not reliably baked into a given deploy. So an unset/empty/unknown flag means
215
+ // OFF; an app opts IN only with an explicit truthy value
216
+ // (AGENT_CHAT_DURABLE_BACKGROUND=true). This still composes with the hosted +
217
+ // A2A_SECRET gates below. Flip back to default-on only after the 15-min
218
+ // background-function worker is verified live in production (see the
219
+ // project_durable_bg_prod_verified memory).
217
220
  const raw = process.env.AGENT_CHAT_DURABLE_BACKGROUND;
218
221
  if (raw == null)
219
- return true;
222
+ return false;
220
223
  const normalized = raw.trim().toLowerCase();
221
- return !(normalized === "0" ||
222
- normalized === "false" ||
223
- normalized === "no" ||
224
- normalized === "off");
224
+ return (normalized === "1" ||
225
+ normalized === "true" ||
226
+ normalized === "yes" ||
227
+ normalized === "on");
225
228
  }
226
229
  /**
227
- * The single gate. True when the flag is not explicitly disabled (default-on)
230
+ * The single gate. True when the flag is explicitly enabled (opt-in/default-off)
228
231
  * AND the runtime is hosted AND A2A_SECRET is configured. False otherwise — and
229
232
  * false means the current synchronous behavior is used, unchanged. So a local /
230
- * non-hosted / unconfigured app stays synchronous even with the flag defaulting
231
- * on; durable only engages where the runtime actually supports it.
233
+ * non-hosted / unconfigured app stays synchronous, and an app that has not opted
234
+ * in stays synchronous; durable only engages where it is explicitly enabled and
235
+ * the runtime actually supports it.
232
236
  */
233
237
  export function isAgentChatDurableBackgroundEnabled() {
234
238
  return (isFlagEnabled() &&