@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.
- package/corpus/core/CHANGELOG.md +56 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +58 -18
- package/corpus/core/src/agent/production-agent.ts +54 -0
- package/corpus/core/src/agent/run-manager.ts +27 -0
- package/corpus/core/src/agent/run-store.ts +185 -4
- package/corpus/core/src/deploy/build.ts +8 -6
- package/corpus/core/src/server/agent-chat-plugin.ts +71 -0
- package/corpus/core/src/server/auth.ts +13 -0
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +12 -12
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +23 -23
- package/corpus/templates/analytics/app/pages/analyses/AnalysesList.tsx +2 -2
- package/corpus/templates/brain/app/routes/knowledge.tsx +6 -6
- package/corpus/templates/brain/app/routes/ops.tsx +4 -4
- package/corpus/templates/brain/app/routes/search.tsx +4 -4
- package/corpus/templates/brain/app/routes/settings.tsx +3 -3
- package/corpus/templates/brain/app/routes/sources.tsx +3 -3
- package/corpus/templates/dispatch/app/routes/integrations.tsx +3 -3
- package/corpus/templates/mail/app/components/email/EmailThread.tsx +10 -10
- package/corpus/templates/mail/app/pages/DraftQueuePage.tsx +3 -3
- package/corpus/templates/mail/app/pages/InboxPage.tsx +3 -3
- package/corpus/templates/mail/app/pages/SettingsPage.tsx +2 -2
- package/corpus/templates/plan/app/pages/PlansPage.tsx +3 -3
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +4 -4
- package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/slides/app/components/layout/Sidebar.tsx +4 -4
- package/corpus/templates/slides/app/pages/Index.tsx +3 -3
- package/corpus/templates/videos/app/components/layout/NavSidebar.tsx +1 -1
- package/corpus/templates/videos/app/pages/ComponentLibraryView.tsx +1 -1
- package/corpus/templates/videos/app/pages/DesignSystems.tsx +1 -1
- package/dist/agent/durable-background.d.ts +19 -3
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +47 -17
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +34 -1
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +8 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +18 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +99 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +155 -4
- package/dist/agent/run-store.js.map +1 -1
- package/dist/deploy/build.d.ts +6 -4
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +8 -6
- package/dist/deploy/build.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +58 -1
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +12 -0
- package/dist/server/auth.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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="
|
|
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="
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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-
|
|
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="
|
|
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="
|
|
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-
|
|
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-
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
109
|
-
*
|
|
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`. */
|
|
@@ -122,7 +123,22 @@ export type ProcessRunPreparation = {
|
|
|
122
123
|
status: number;
|
|
123
124
|
/** Error payload. */
|
|
124
125
|
error: string;
|
|
126
|
+
/**
|
|
127
|
+
* The run id parsed from the body, when present. Carried even on failure
|
|
128
|
+
* so the route can RECORD the auth/validation failure ONTO the run
|
|
129
|
+
* (diag_stage) before returning the error status — otherwise a 401/503 in
|
|
130
|
+
* the unreadable Netlify background function would leave the run to time
|
|
131
|
+
* out with no clue why. Null when no run id could be parsed.
|
|
132
|
+
*/
|
|
133
|
+
runId: string | null;
|
|
125
134
|
};
|
|
135
|
+
/**
|
|
136
|
+
* Parse the run id from a `_process-run` request body without authenticating.
|
|
137
|
+
* Mirrors the precedence in `prepareProcessRunRequest` (marker.runId, then
|
|
138
|
+
* top-level taskId). Returns null when neither is a usable string. Used so the
|
|
139
|
+
* route can attach a diagnostic to the run even on an auth/validation failure.
|
|
140
|
+
*/
|
|
141
|
+
export declare function extractProcessRunId(body: unknown): string | null;
|
|
126
142
|
/**
|
|
127
143
|
* Pure, transport-agnostic core of the `_process-run` route: validate the body,
|
|
128
144
|
* authenticate the HMAC self-dispatch, and produce the body the re-entered
|
|
@@ -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;
|
|
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,34 +207,57 @@ 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-
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
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
|
|
222
|
+
return false;
|
|
220
223
|
const normalized = raw.trim().toLowerCase();
|
|
221
|
-
return
|
|
222
|
-
normalized === "
|
|
223
|
-
normalized === "
|
|
224
|
-
normalized === "
|
|
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
|
|
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
|
|
231
|
-
*
|
|
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() &&
|
|
235
239
|
isHostedRuntimeForDurableBackground() &&
|
|
236
240
|
hasConfiguredA2ASecret());
|
|
237
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Parse the run id from a `_process-run` request body without authenticating.
|
|
244
|
+
* Mirrors the precedence in `prepareProcessRunRequest` (marker.runId, then
|
|
245
|
+
* top-level taskId). Returns null when neither is a usable string. Used so the
|
|
246
|
+
* route can attach a diagnostic to the run even on an auth/validation failure.
|
|
247
|
+
*/
|
|
248
|
+
export function extractProcessRunId(body) {
|
|
249
|
+
if (!body || typeof body !== "object")
|
|
250
|
+
return null;
|
|
251
|
+
const record = body;
|
|
252
|
+
const marker = record[AGENT_CHAT_BACKGROUND_RUN_FIELD];
|
|
253
|
+
if (marker && typeof marker.runId === "string" && marker.runId) {
|
|
254
|
+
return marker.runId;
|
|
255
|
+
}
|
|
256
|
+
if (typeof record.taskId === "string" && record.taskId) {
|
|
257
|
+
return record.taskId;
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
238
261
|
/**
|
|
239
262
|
* Pure, transport-agnostic core of the `_process-run` route: validate the body,
|
|
240
263
|
* authenticate the HMAC self-dispatch, and produce the body the re-entered
|
|
@@ -253,7 +276,12 @@ export function isAgentChatDurableBackgroundEnabled() {
|
|
|
253
276
|
*/
|
|
254
277
|
export function prepareProcessRunRequest(body, authHeader) {
|
|
255
278
|
if (!body || typeof body !== "object") {
|
|
256
|
-
return {
|
|
279
|
+
return {
|
|
280
|
+
ok: false,
|
|
281
|
+
status: 400,
|
|
282
|
+
error: "Invalid request body",
|
|
283
|
+
runId: null,
|
|
284
|
+
};
|
|
257
285
|
}
|
|
258
286
|
const record = body;
|
|
259
287
|
const marker = record[AGENT_CHAT_BACKGROUND_RUN_FIELD];
|
|
@@ -263,7 +291,7 @@ export function prepareProcessRunRequest(body, authHeader) {
|
|
|
263
291
|
? record.taskId
|
|
264
292
|
: "";
|
|
265
293
|
if (!runId) {
|
|
266
|
-
return { ok: false, status: 400, error: "runId required" };
|
|
294
|
+
return { ok: false, status: 400, error: "runId required", runId: null };
|
|
267
295
|
}
|
|
268
296
|
if (hasConfiguredA2ASecret()) {
|
|
269
297
|
const token = extractBearerToken(authHeader);
|
|
@@ -272,6 +300,7 @@ export function prepareProcessRunRequest(body, authHeader) {
|
|
|
272
300
|
ok: false,
|
|
273
301
|
status: 401,
|
|
274
302
|
error: "Invalid or expired processor token",
|
|
303
|
+
runId,
|
|
275
304
|
};
|
|
276
305
|
}
|
|
277
306
|
}
|
|
@@ -280,6 +309,7 @@ export function prepareProcessRunRequest(body, authHeader) {
|
|
|
280
309
|
ok: false,
|
|
281
310
|
status: 503,
|
|
282
311
|
error: "Agent chat background processor not configured — set A2A_SECRET on this deployment.",
|
|
312
|
+
runId,
|
|
283
313
|
};
|
|
284
314
|
}
|
|
285
315
|
// Ensure the marker is present so the re-entered handler runs as the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"durable-background.js","sourceRoot":"","sources":["../../src/agent/durable-background.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Durable background agent-chat runs (Netlify background functions).\n *\n * Off by default. When enabled, a long in-app agent-chat turn is dispatched\n * into a Netlify *background* function (15-min budget) instead of completing\n * synchronously under the ~40s soft-timeout. The foreground POST claims the\n * run slot, inserts the run row, fires an HMAC-signed self-dispatch to\n * `AGENT_CHAT_PROCESS_RUN_PATH`, and returns the existing SSE subscription so\n * the client streams the same events (via the cross-isolate SQL-poll path)\n * with no client change.\n *\n * This module owns ONLY the gating decision + shared constants so both the\n * HTTP handler (`production-agent.ts`) and the processor route\n * (`agent-chat-plugin.ts`) agree on when the path is active without a circular\n * import. The actual run machinery is reused verbatim from run-manager /\n * run-store / self-dispatch / internal-token.\n *\n * GUARDRAIL: when `isAgentChatDurableBackgroundEnabled()` returns false, the\n * agent-chat handler must behave byte-for-byte like the current synchronous\n * path. The gate is true only when ALL of these hold:\n * 1. `AGENT_CHAT_DURABLE_BACKGROUND` env is not explicitly disabled. It is\n * DEFAULT-ON: unset/empty/unknown counts as enabled; set it to a falsy\n * value (`false`/`0`/`no`/`off`) to opt a specific app back out.\n * 2. The runtime is hosted/serverless (local dev keeps the inline path so SSE\n * stays a single live stream and no second function is needed).\n * 3. `A2A_SECRET` is configured (the HMAC handoff is required to authenticate\n * the background dispatch; without it the dispatch can't be trusted).\n *\n * Default-on is safe because a *dispatch failure degrades to an inline run*: if\n * the self-dispatch self-POST can't be delivered (fast connection error or\n * fast non-2xx), the foreground handler runs the turn synchronously instead of\n * erroring (see `production-agent.ts` — the inline fallback claims the run row\n * atomically so a delayed delivery can never double-execute). So an app where\n * durable dispatch happens to fail still gets a working chat, just without the\n * 15-min budget.\n */\nimport {\n hasConfiguredA2ASecret,\n isA2AProductionRuntime,\n} from \"../a2a/auth-policy.js\";\nimport {\n extractBearerToken,\n verifyInternalToken,\n} from \"../integrations/internal-token.js\";\n\n/**\n * Framework route the background function actually runs — sibling to\n * `AGENT_TEAM_PROCESS_RUN_PATH`. Reached *through* the Netlify background\n * function, so it inherits the 15-min budget.\n */\nexport const AGENT_CHAT_PROCESS_RUN_PATH =\n \"/_agent-native/agent-chat/_process-run\";\n\n/**\n * Name of the standalone Netlify background function the build emits (see\n * `emitSingleTemplateNetlifyBackgroundFunction` in deploy/build.ts). Shared so\n * the emit and the dispatch-path helper below can never drift on the name.\n *\n * MUST end in `-background` — both because that is the conventional Netlify\n * async-function suffix and because `isInBackgroundFunctionRuntime()` reads the\n * `AWS_LAMBDA_FUNCTION_NAME` `-background` suffix as a secondary runtime signal.\n */\nexport const AGENT_BACKGROUND_FUNCTION_NAME = \"server-agent-background\";\n\n/**\n * Default function URL of the background function on Netlify. Every Netlify\n * function is reachable at `/.netlify/functions/<name>` BY DEFAULT; that default\n * url is removed ONLY if the function declares a custom `config.path`. The\n * emitted background function declares NO custom `config.path` (it sets\n * `background: true` and nothing else routing-related), so it KEEPS this default\n * url — and the Nitro `server` function already excludes `/.netlify/*` from its\n * `/*` catch-all, so this namespace is never shadowed. The foreground therefore\n * dispatches HERE on hosted Netlify (see `resolveAgentChatProcessRunDispatchPath`).\n */\nexport const AGENT_BACKGROUND_FUNCTION_URL_PATH = `/.netlify/functions/${AGENT_BACKGROUND_FUNCTION_NAME}`;\n\n/**\n * The per-app workspace background function URL path. Workspace deploy emits one\n * background function per app named `<app>-agent-background`, reachable at its\n * DEFAULT url `/.netlify/functions/<app>-agent-background` (no custom\n * `config.path`). The foreground resolves the current workspace app id from\n * `AGENT_NATIVE_WORKSPACE_APP_ID` (set by the workspace function entry) so it can\n * dispatch to the right per-app function url. Returns `null` when no workspace\n * app id is configured (single-template deploy).\n */\nfunction resolveWorkspaceBackgroundFunctionUrlPath(): string | null {\n const raw = process.env.AGENT_NATIVE_WORKSPACE_APP_ID;\n if (typeof raw !== \"string\") return null;\n // Mirror the workspace app-id normalization (resources/store.ts): take the\n // first path segment and accept only the safe slug shape used for function\n // names. Anything else falls back to the single-template name.\n const candidate = raw.trim().replace(/^\\/+/, \"\").split(\"/\")[0] ?? \"\";\n if (!/^[a-z0-9][a-z0-9-]{0,127}$/.test(candidate)) return null;\n return `/.netlify/functions/${candidate}-agent-background`;\n}\n\n/**\n * Resolve the path the foreground POST should self-dispatch the chat background\n * worker to.\n *\n * GROUNDED IN THE REAL NETLIFY BUILD OUTPUT + THE NETLIFY DOCS DEFAULT-URL RULE:\n * the background function is emitted INTO the scanned dir\n * (`.netlify/functions-internal/server-agent-background`, or per-app\n * `<app>-agent-background` for workspaces) with `export const config = {\n * background: true, ... }` and NO custom `config.path`. Because it has no custom\n * path, Netlify keeps its DEFAULT function url `/.netlify/functions/<name>`, and\n * `background: true` makes any invocation of that url ASYNC (immediate 202,\n * 15-min budget). The Nitro `server` function already excludes `/.netlify/*`\n * from its `/*` catch-all, so the default-url namespace is NEVER shadowed by the\n * synchronous function.\n *\n * Therefore on hosted Netlify the foreground dispatches to the function's DEFAULT\n * url (`/.netlify/functions/<name>`); the function entry then rewrites the\n * incoming pathname to `AGENT_CHAT_PROCESS_RUN_PATH` (base-path-prefixed for\n * workspaces) before delegating to the Nitro router, so the `_process-run`\n * plugin runs with the async 15-min budget. Everywhere else (local dev, `netlify\n * dev`, non-Netlify hosts where no second function exists) there is no second\n * function, so the foreground dispatches to the framework route\n * `AGENT_CHAT_PROCESS_RUN_PATH` and the same in-process catch-all handles it\n * inline. The HMAC token (signed over the runId) is unchanged either way.\n *\n * NOTE: this is the DOC-CORRECT approach. An earlier attempt gave the function a\n * custom `config.path` + a catch-all `excludedPath` patch; the custom path was\n * NOT honored as a route in prod (probe → 404). Using the default function url\n * (no custom path) is what Netlify documents and is simpler — there is nothing\n * to shadow because `/.netlify/*` is already excluded from the `server` catch-all.\n */\nexport function resolveAgentChatProcessRunDispatchPath(): string {\n if (\n process.env.NETLIFY &&\n process.env.NETLIFY !== \"false\" &&\n process.env.NETLIFY_LOCAL !== \"true\"\n ) {\n return (\n resolveWorkspaceBackgroundFunctionUrlPath() ??\n AGENT_BACKGROUND_FUNCTION_URL_PATH\n );\n }\n return AGENT_CHAT_PROCESS_RUN_PATH;\n}\n\n/**\n * Env flag for durable background runs. DEFAULT-ON: unset means enabled; an app\n * opts OUT with an explicit falsy value (`false`/`0`/`no`/`off`).\n */\nexport const AGENT_CHAT_DURABLE_BACKGROUND_ENV =\n \"AGENT_CHAT_DURABLE_BACKGROUND\";\n\n/**\n * Body field the foreground handler injects when self-dispatching to the\n * background processor. Its presence is how the re-entered handler knows it is\n * the background worker (run inline with the background soft-timeout; do NOT\n * re-claim the slot or re-dispatch). Untrusted on its own — the route also\n * verifies the HMAC token before invoking the handler.\n */\nexport const AGENT_CHAT_BACKGROUND_RUN_FIELD = \"__backgroundRun\";\n\n/**\n * Mirror of run-manager's private `isHostedRuntime`. Kept in sync deliberately:\n * the durable-background gate must agree with the soft-timeout regime about\n * what \"hosted\" means.\n */\nexport function isHostedRuntimeForDurableBackground(): boolean {\n if (\n process.env.NETLIFY &&\n process.env.NETLIFY !== \"false\" &&\n process.env.NETLIFY_LOCAL !== \"true\"\n ) {\n return true;\n }\n if (\n process.env.AWS_LAMBDA_FUNCTION_NAME &&\n process.env.NETLIFY_LOCAL !== \"true\"\n ) {\n return true;\n }\n return Boolean(\n process.env.CF_PAGES ||\n process.env.VERCEL ||\n process.env.VERCEL_ENV ||\n process.env.RENDER ||\n process.env.FLY_APP_NAME ||\n process.env.K_SERVICE,\n );\n}\n\n/**\n * True when THIS process is actually executing inside a Netlify *background*\n * function (the long, 15-min-budget async function whose deployed name ends in\n * `-background`). Netlify runs functions on AWS Lambda and sets\n * `AWS_LAMBDA_FUNCTION_NAME` to the function's name, so a `-background` suffix is\n * the runtime proof that the ~60s synchronous wall does NOT apply here.\n *\n * This is the SAFETY GUARD for the soft-timeout regime. The `_process-run`\n * self-dispatch worker (`isBackgroundWorker`) is NOT enough on its own: if the\n * `-background` function was never emitted (deploy gate off, or Netlify routed\n * the path to the synchronous function), the self-POST lands on the regular\n * ~60s `server` function. A worker there MUST use the 40s soft-timeout and\n * checkpoint before the 60s wall — using the ~13min budget would overshoot the\n * hard wall and get killed at 60s, then re-dispatch/resume in a wasteful loop.\n * So the 13-min budget is taken ONLY when this returns true.\n *\n * The PRIMARY signal is a `globalThis` marker the emitted background function's\n * entry sets at cold start — the deployed Lambda name is not guaranteed to end\n * in `-background` on Netlify, so the entry marks its own runtime. A `globalThis`\n * flag (not `process.env`) keeps the no-env-mutation guard satisfied and carries\n * no cross-request state (set once per isolate). The `AWS_LAMBDA_FUNCTION_NAME`\n * suffix and the explicit `AGENT_CHAT_FORCE_BACKGROUND_RUNTIME` env (truthy) are\n * additional signals — the latter an operator escape hatch. Off by default.\n */\nexport function isInBackgroundFunctionRuntime(): boolean {\n // Set by the emitted `-background` function entry at cold start (the primary,\n // most reliable signal — see the emit in deploy/build.ts).\n if (\n (globalThis as Record<string, unknown>)\n .__AGENT_NATIVE_BACKGROUND_RUNTIME__ === true\n ) {\n return true;\n }\n const lambdaName = process.env.AWS_LAMBDA_FUNCTION_NAME;\n if (\n typeof lambdaName === \"string\" &&\n lambdaName.toLowerCase().endsWith(\"-background\")\n ) {\n return true;\n }\n const forced = process.env.AGENT_CHAT_FORCE_BACKGROUND_RUNTIME;\n if (forced != null) {\n const v = forced.trim().toLowerCase();\n return v === \"1\" || v === \"true\" || v === \"yes\" || v === \"on\";\n }\n return false;\n}\n\nfunction isFlagEnabled(): boolean {\n // Read the literal key (not `process.env[CONST]`) so guard:no-env-credentials\n // can statically verify it against the allowlisted `AGENT_*` prefix. Keep this\n // in sync with AGENT_CHAT_DURABLE_BACKGROUND_ENV.\n //\n // DEFAULT-ON: durable background runs are the desired behavior for every\n // hosted app. So an unset/empty/unknown flag means ON; an app opts OUT only\n // with an explicit falsy value. This still composes with the hosted +\n // A2A_SECRET gates below, so non-hosted / unconfigured apps stay synchronous.\n // Safety net: a failed dispatch degrades to a synchronous inline run (see\n // production-agent.ts), so default-on cannot break chat even if the\n // self-dispatch can't be delivered on a given app.\n const raw = process.env.AGENT_CHAT_DURABLE_BACKGROUND;\n if (raw == null) return true;\n const normalized = raw.trim().toLowerCase();\n return !(\n normalized === \"0\" ||\n normalized === \"false\" ||\n normalized === \"no\" ||\n normalized === \"off\"\n );\n}\n\n/**\n * The single gate. True when the flag is not explicitly disabled (default-on)\n * AND the runtime is hosted AND A2A_SECRET is configured. False otherwise — and\n * false means the current synchronous behavior is used, unchanged. So a local /\n * non-hosted / unconfigured app stays synchronous even with the flag defaulting\n * on; durable only engages where the runtime actually supports it.\n */\nexport function isAgentChatDurableBackgroundEnabled(): boolean {\n return (\n isFlagEnabled() &&\n isHostedRuntimeForDurableBackground() &&\n hasConfiguredA2ASecret()\n );\n}\n\n/** Decision returned by `prepareProcessRunRequest`. */\nexport type ProcessRunPreparation =\n | {\n ok: true;\n /** The pre-claimed run id the background worker must reuse. */\n runId: string;\n /** Body to stash for the re-entered handler (marker guaranteed present). */\n body: Record<string, unknown>;\n }\n | {\n ok: false;\n /** HTTP status the route should return. */\n status: number;\n /** Error payload. */\n error: string;\n };\n\n/**\n * Pure, transport-agnostic core of the `_process-run` route: validate the body,\n * authenticate the HMAC self-dispatch, and produce the body the re-entered\n * agent-chat handler should run as the background worker.\n *\n * Auth policy mirrors the agent-teams processor exactly:\n * - `A2A_SECRET` set → require a valid `verifyInternalToken(runId, token)`.\n * - no secret but a production runtime → refuse (503) — never run unsigned in\n * prod.\n * - no secret + non-prod (local dev) → allow unsigned; the SQL atomic claim\n * in the worker still prevents double-processing.\n *\n * Extracted from the route handler so the auth + marker-prep decision is unit\n * testable without booting the whole Nitro plugin. The route only adds body\n * reading and the final handler invocation around this.\n */\nexport function prepareProcessRunRequest(\n body: unknown,\n authHeader: string | undefined,\n): ProcessRunPreparation {\n if (!body || typeof body !== \"object\") {\n return { ok: false, status: 400, error: \"Invalid request body\" };\n }\n const record = body as Record<string, unknown>;\n const marker = record[AGENT_CHAT_BACKGROUND_RUN_FIELD] as\n | { runId?: unknown }\n | undefined;\n const runId =\n marker && typeof marker.runId === \"string\"\n ? marker.runId\n : typeof record.taskId === \"string\"\n ? (record.taskId as string)\n : \"\";\n if (!runId) {\n return { ok: false, status: 400, error: \"runId required\" };\n }\n\n if (hasConfiguredA2ASecret()) {\n const token = extractBearerToken(authHeader);\n if (!verifyInternalToken(runId, token ?? \"\")) {\n return {\n ok: false,\n status: 401,\n error: \"Invalid or expired processor token\",\n };\n }\n } else if (isA2AProductionRuntime()) {\n return {\n ok: false,\n status: 503,\n error:\n \"Agent chat background processor not configured — set A2A_SECRET on this deployment.\",\n };\n }\n\n // Ensure the marker is present so the re-entered handler runs as the\n // background worker (reuses runId/turnId, no re-claim, no re-dispatch).\n if (!marker || typeof marker.runId !== \"string\") {\n record[AGENT_CHAT_BACKGROUND_RUN_FIELD] = { runId };\n }\n return { ok: true, runId, body: record };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"durable-background.js","sourceRoot":"","sources":["../../src/agent/durable-background.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Durable background agent-chat runs (Netlify background functions).\n *\n * Off by default. When enabled, a long in-app agent-chat turn is dispatched\n * into a Netlify *background* function (15-min budget) instead of completing\n * synchronously under the ~40s soft-timeout. The foreground POST claims the\n * run slot, inserts the run row, fires an HMAC-signed self-dispatch to\n * `AGENT_CHAT_PROCESS_RUN_PATH`, and returns the existing SSE subscription so\n * the client streams the same events (via the cross-isolate SQL-poll path)\n * with no client change.\n *\n * This module owns ONLY the gating decision + shared constants so both the\n * HTTP handler (`production-agent.ts`) and the processor route\n * (`agent-chat-plugin.ts`) agree on when the path is active without a circular\n * import. The actual run machinery is reused verbatim from run-manager /\n * run-store / self-dispatch / internal-token.\n *\n * GUARDRAIL: when `isAgentChatDurableBackgroundEnabled()` returns false, the\n * agent-chat handler must behave byte-for-byte like the current synchronous\n * path. The gate is true only when ALL of these hold:\n * 1. `AGENT_CHAT_DURABLE_BACKGROUND` env is not explicitly disabled. It is\n * DEFAULT-ON: unset/empty/unknown counts as enabled; set it to a falsy\n * value (`false`/`0`/`no`/`off`) to opt a specific app back out.\n * 2. The runtime is hosted/serverless (local dev keeps the inline path so SSE\n * stays a single live stream and no second function is needed).\n * 3. `A2A_SECRET` is configured (the HMAC handoff is required to authenticate\n * the background dispatch; without it the dispatch can't be trusted).\n *\n * Default-on is safe because a *dispatch failure degrades to an inline run*: if\n * the self-dispatch self-POST can't be delivered (fast connection error or\n * fast non-2xx), the foreground handler runs the turn synchronously instead of\n * erroring (see `production-agent.ts` — the inline fallback claims the run row\n * atomically so a delayed delivery can never double-execute). So an app where\n * durable dispatch happens to fail still gets a working chat, just without the\n * 15-min budget.\n */\nimport {\n hasConfiguredA2ASecret,\n isA2AProductionRuntime,\n} from \"../a2a/auth-policy.js\";\nimport {\n extractBearerToken,\n verifyInternalToken,\n} from \"../integrations/internal-token.js\";\n\n/**\n * Framework route the background function actually runs — sibling to\n * `AGENT_TEAM_PROCESS_RUN_PATH`. Reached *through* the Netlify background\n * function, so it inherits the 15-min budget.\n */\nexport const AGENT_CHAT_PROCESS_RUN_PATH =\n \"/_agent-native/agent-chat/_process-run\";\n\n/**\n * Name of the standalone Netlify background function the build emits (see\n * `emitSingleTemplateNetlifyBackgroundFunction` in deploy/build.ts). Shared so\n * the emit and the dispatch-path helper below can never drift on the name.\n *\n * MUST end in `-background` — both because that is the conventional Netlify\n * async-function suffix and because `isInBackgroundFunctionRuntime()` reads the\n * `AWS_LAMBDA_FUNCTION_NAME` `-background` suffix as a secondary runtime signal.\n */\nexport const AGENT_BACKGROUND_FUNCTION_NAME = \"server-agent-background\";\n\n/**\n * Default function URL of the background function on Netlify. Every Netlify\n * function is reachable at `/.netlify/functions/<name>` BY DEFAULT; that default\n * url is removed ONLY if the function declares a custom `config.path`. The\n * emitted background function declares NO custom `config.path` (it sets\n * `background: true` and nothing else routing-related), so it KEEPS this default\n * url — and the Nitro `server` function already excludes `/.netlify/*` from its\n * `/*` catch-all, so this namespace is never shadowed. The foreground therefore\n * dispatches HERE on hosted Netlify (see `resolveAgentChatProcessRunDispatchPath`).\n */\nexport const AGENT_BACKGROUND_FUNCTION_URL_PATH = `/.netlify/functions/${AGENT_BACKGROUND_FUNCTION_NAME}`;\n\n/**\n * The per-app workspace background function URL path. Workspace deploy emits one\n * background function per app named `<app>-agent-background`, reachable at its\n * DEFAULT url `/.netlify/functions/<app>-agent-background` (no custom\n * `config.path`). The foreground resolves the current workspace app id from\n * `AGENT_NATIVE_WORKSPACE_APP_ID` (set by the workspace function entry) so it can\n * dispatch to the right per-app function url. Returns `null` when no workspace\n * app id is configured (single-template deploy).\n */\nfunction resolveWorkspaceBackgroundFunctionUrlPath(): string | null {\n const raw = process.env.AGENT_NATIVE_WORKSPACE_APP_ID;\n if (typeof raw !== \"string\") return null;\n // Mirror the workspace app-id normalization (resources/store.ts): take the\n // first path segment and accept only the safe slug shape used for function\n // names. Anything else falls back to the single-template name.\n const candidate = raw.trim().replace(/^\\/+/, \"\").split(\"/\")[0] ?? \"\";\n if (!/^[a-z0-9][a-z0-9-]{0,127}$/.test(candidate)) return null;\n return `/.netlify/functions/${candidate}-agent-background`;\n}\n\n/**\n * Resolve the path the foreground POST should self-dispatch the chat background\n * worker to.\n *\n * GROUNDED IN THE REAL NETLIFY BUILD OUTPUT + THE NETLIFY DOCS DEFAULT-URL RULE:\n * the background function is emitted INTO the scanned dir\n * (`.netlify/functions-internal/server-agent-background`, or per-app\n * `<app>-agent-background` for workspaces) with `export const config = {\n * background: true, ... }` and NO custom `config.path`. Because it has no custom\n * path, Netlify keeps its DEFAULT function url `/.netlify/functions/<name>`, and\n * `background: true` makes any invocation of that url ASYNC (immediate 202,\n * 15-min budget). The Nitro `server` function already excludes `/.netlify/*`\n * from its `/*` catch-all, so the default-url namespace is NEVER shadowed by the\n * synchronous function.\n *\n * Therefore on hosted Netlify the foreground dispatches to the function's DEFAULT\n * url (`/.netlify/functions/<name>`); the function entry then rewrites the\n * incoming pathname to `AGENT_CHAT_PROCESS_RUN_PATH` (base-path-prefixed for\n * workspaces) before delegating to the Nitro router, so the `_process-run`\n * plugin runs with the async 15-min budget. Everywhere else (local dev, `netlify\n * dev`, non-Netlify hosts where no second function exists) there is no second\n * function, so the foreground dispatches to the framework route\n * `AGENT_CHAT_PROCESS_RUN_PATH` and the same in-process catch-all handles it\n * inline. The HMAC token (signed over the runId) is unchanged either way.\n *\n * NOTE: this is the DOC-CORRECT approach. An earlier attempt gave the function a\n * custom `config.path` + a catch-all `excludedPath` patch; the custom path was\n * NOT honored as a route in prod (probe → 404). Using the default function url\n * (no custom path) is what Netlify documents and is simpler — there is nothing\n * to shadow because `/.netlify/*` is already excluded from the `server` catch-all.\n */\nexport function resolveAgentChatProcessRunDispatchPath(): string {\n if (\n process.env.NETLIFY &&\n process.env.NETLIFY !== \"false\" &&\n process.env.NETLIFY_LOCAL !== \"true\"\n ) {\n return (\n resolveWorkspaceBackgroundFunctionUrlPath() ??\n AGENT_BACKGROUND_FUNCTION_URL_PATH\n );\n }\n return AGENT_CHAT_PROCESS_RUN_PATH;\n}\n\n/**\n * Env flag for durable background runs. DEFAULT-ON: unset means enabled; an app\n * opts OUT with an explicit falsy value (`false`/`0`/`no`/`off`).\n */\nexport const AGENT_CHAT_DURABLE_BACKGROUND_ENV =\n \"AGENT_CHAT_DURABLE_BACKGROUND\";\n\n/**\n * Body field the foreground handler injects when self-dispatching to the\n * background processor. Its presence is how the re-entered handler knows it is\n * the background worker (run inline with the background soft-timeout; do NOT\n * re-claim the slot or re-dispatch). Untrusted on its own — the route also\n * verifies the HMAC token before invoking the handler.\n */\nexport const AGENT_CHAT_BACKGROUND_RUN_FIELD = \"__backgroundRun\";\n\n/**\n * Mirror of run-manager's private `isHostedRuntime`. Kept in sync deliberately:\n * the durable-background gate must agree with the soft-timeout regime about\n * what \"hosted\" means.\n */\nexport function isHostedRuntimeForDurableBackground(): boolean {\n if (\n process.env.NETLIFY &&\n process.env.NETLIFY !== \"false\" &&\n process.env.NETLIFY_LOCAL !== \"true\"\n ) {\n return true;\n }\n if (\n process.env.AWS_LAMBDA_FUNCTION_NAME &&\n process.env.NETLIFY_LOCAL !== \"true\"\n ) {\n return true;\n }\n return Boolean(\n process.env.CF_PAGES ||\n process.env.VERCEL ||\n process.env.VERCEL_ENV ||\n process.env.RENDER ||\n process.env.FLY_APP_NAME ||\n process.env.K_SERVICE,\n );\n}\n\n/**\n * True when THIS process is actually executing inside a Netlify *background*\n * function (the long, 15-min-budget async function whose deployed name ends in\n * `-background`). Netlify runs functions on AWS Lambda and sets\n * `AWS_LAMBDA_FUNCTION_NAME` to the function's name, so a `-background` suffix is\n * the runtime proof that the ~60s synchronous wall does NOT apply here.\n *\n * This is the SAFETY GUARD for the soft-timeout regime. The `_process-run`\n * self-dispatch worker (`isBackgroundWorker`) is NOT enough on its own: if the\n * `-background` function was never emitted (deploy gate off, or Netlify routed\n * the path to the synchronous function), the self-POST lands on the regular\n * ~60s `server` function. A worker there MUST use the 40s soft-timeout and\n * checkpoint before the 60s wall — using the ~13min budget would overshoot the\n * hard wall and get killed at 60s, then re-dispatch/resume in a wasteful loop.\n * So the 13-min budget is taken ONLY when this returns true.\n *\n * The PRIMARY signal is a `globalThis` marker the emitted background function's\n * entry sets at cold start — the deployed Lambda name is not guaranteed to end\n * in `-background` on Netlify, so the entry marks its own runtime. A `globalThis`\n * flag (not `process.env`) keeps the no-env-mutation guard satisfied and carries\n * no cross-request state (set once per isolate). The `AWS_LAMBDA_FUNCTION_NAME`\n * suffix and the explicit `AGENT_CHAT_FORCE_BACKGROUND_RUNTIME` env (truthy) are\n * additional signals — the latter an operator escape hatch. Off by default.\n */\nexport function isInBackgroundFunctionRuntime(): boolean {\n // Set by the emitted `-background` function entry at cold start (the primary,\n // most reliable signal — see the emit in deploy/build.ts).\n if (\n (globalThis as Record<string, unknown>)\n .__AGENT_NATIVE_BACKGROUND_RUNTIME__ === true\n ) {\n return true;\n }\n const lambdaName = process.env.AWS_LAMBDA_FUNCTION_NAME;\n if (\n typeof lambdaName === \"string\" &&\n lambdaName.toLowerCase().endsWith(\"-background\")\n ) {\n return true;\n }\n const forced = process.env.AGENT_CHAT_FORCE_BACKGROUND_RUNTIME;\n if (forced != null) {\n const v = forced.trim().toLowerCase();\n return v === \"1\" || v === \"true\" || v === \"yes\" || v === \"on\";\n }\n return false;\n}\n\nfunction isFlagEnabled(): boolean {\n // Read the literal key (not `process.env[CONST]`) so guard:no-env-credentials\n // can statically verify it against the allowlisted `AGENT_*` prefix. Keep this\n // in sync with AGENT_CHAT_DURABLE_BACKGROUND_ENV.\n //\n // DEFAULT-OFF (opt-in): durable background runs are still being hardened. A\n // premature fleet-wide default-on caused real-user incidents (Assets/Analytics\n // hit \"Failed to dispatch\" + stalls, 2026-06-24) because the async background\n // worker path is not yet proven end-to-end and the deploy-time env opt-out is\n // not reliably baked into a given deploy. So an unset/empty/unknown flag means\n // OFF; an app opts IN only with an explicit truthy value\n // (AGENT_CHAT_DURABLE_BACKGROUND=true). This still composes with the hosted +\n // A2A_SECRET gates below. Flip back to default-on only after the 15-min\n // background-function worker is verified live in production (see the\n // project_durable_bg_prod_verified memory).\n const raw = process.env.AGENT_CHAT_DURABLE_BACKGROUND;\n if (raw == null) return false;\n const normalized = raw.trim().toLowerCase();\n return (\n normalized === \"1\" ||\n normalized === \"true\" ||\n normalized === \"yes\" ||\n normalized === \"on\"\n );\n}\n\n/**\n * The single gate. True when the flag is explicitly enabled (opt-in/default-off)\n * AND the runtime is hosted AND A2A_SECRET is configured. False otherwise — and\n * false means the current synchronous behavior is used, unchanged. So a local /\n * non-hosted / unconfigured app stays synchronous, and an app that has not opted\n * in stays synchronous; durable only engages where it is explicitly enabled and\n * the runtime actually supports it.\n */\nexport function isAgentChatDurableBackgroundEnabled(): boolean {\n return (\n isFlagEnabled() &&\n isHostedRuntimeForDurableBackground() &&\n hasConfiguredA2ASecret()\n );\n}\n\n/** Decision returned by `prepareProcessRunRequest`. */\nexport type ProcessRunPreparation =\n | {\n ok: true;\n /** The pre-claimed run id the background worker must reuse. */\n runId: string;\n /** Body to stash for the re-entered handler (marker guaranteed present). */\n body: Record<string, unknown>;\n }\n | {\n ok: false;\n /** HTTP status the route should return. */\n status: number;\n /** Error payload. */\n error: string;\n /**\n * The run id parsed from the body, when present. Carried even on failure\n * so the route can RECORD the auth/validation failure ONTO the run\n * (diag_stage) before returning the error status — otherwise a 401/503 in\n * the unreadable Netlify background function would leave the run to time\n * out with no clue why. Null when no run id could be parsed.\n */\n runId: string | null;\n };\n\n/**\n * Parse the run id from a `_process-run` request body without authenticating.\n * Mirrors the precedence in `prepareProcessRunRequest` (marker.runId, then\n * top-level taskId). Returns null when neither is a usable string. Used so the\n * route can attach a diagnostic to the run even on an auth/validation failure.\n */\nexport function extractProcessRunId(body: unknown): string | null {\n if (!body || typeof body !== \"object\") return null;\n const record = body as Record<string, unknown>;\n const marker = record[AGENT_CHAT_BACKGROUND_RUN_FIELD] as\n | { runId?: unknown }\n | undefined;\n if (marker && typeof marker.runId === \"string\" && marker.runId) {\n return marker.runId;\n }\n if (typeof record.taskId === \"string\" && record.taskId) {\n return record.taskId;\n }\n return null;\n}\n\n/**\n * Pure, transport-agnostic core of the `_process-run` route: validate the body,\n * authenticate the HMAC self-dispatch, and produce the body the re-entered\n * agent-chat handler should run as the background worker.\n *\n * Auth policy mirrors the agent-teams processor exactly:\n * - `A2A_SECRET` set → require a valid `verifyInternalToken(runId, token)`.\n * - no secret but a production runtime → refuse (503) — never run unsigned in\n * prod.\n * - no secret + non-prod (local dev) → allow unsigned; the SQL atomic claim\n * in the worker still prevents double-processing.\n *\n * Extracted from the route handler so the auth + marker-prep decision is unit\n * testable without booting the whole Nitro plugin. The route only adds body\n * reading and the final handler invocation around this.\n */\nexport function prepareProcessRunRequest(\n body: unknown,\n authHeader: string | undefined,\n): ProcessRunPreparation {\n if (!body || typeof body !== \"object\") {\n return {\n ok: false,\n status: 400,\n error: \"Invalid request body\",\n runId: null,\n };\n }\n const record = body as Record<string, unknown>;\n const marker = record[AGENT_CHAT_BACKGROUND_RUN_FIELD] as\n | { runId?: unknown }\n | undefined;\n const runId =\n marker && typeof marker.runId === \"string\"\n ? marker.runId\n : typeof record.taskId === \"string\"\n ? (record.taskId as string)\n : \"\";\n if (!runId) {\n return { ok: false, status: 400, error: \"runId required\", runId: null };\n }\n\n if (hasConfiguredA2ASecret()) {\n const token = extractBearerToken(authHeader);\n if (!verifyInternalToken(runId, token ?? \"\")) {\n return {\n ok: false,\n status: 401,\n error: \"Invalid or expired processor token\",\n runId,\n };\n }\n } else if (isA2AProductionRuntime()) {\n return {\n ok: false,\n status: 503,\n error:\n \"Agent chat background processor not configured — set A2A_SECRET on this deployment.\",\n runId,\n };\n }\n\n // Ensure the marker is present so the re-entered handler runs as the\n // background worker (reuses runId/turnId, no re-claim, no re-dispatch).\n if (!marker || typeof marker.runId !== \"string\") {\n record[AGENT_CHAT_BACKGROUND_RUN_FIELD] = { runId };\n }\n return { ok: true, runId, body: record };\n}\n"]}
|