@agent-native/core 0.120.4 → 0.121.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +20 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/durable-background.ts +12 -4
  5. package/corpus/core/src/agent/run-manager.ts +3 -0
  6. package/corpus/core/src/client/use-db-sync.ts +23 -21
  7. package/corpus/core/src/deploy/build.ts +1 -0
  8. package/corpus/core/src/server/core-routes-plugin.ts +3 -0
  9. package/corpus/core/src/server/gateway-access-check.ts +67 -0
  10. package/corpus/core/src/server/poll.ts +295 -9
  11. package/corpus/core/src/server/short-lived-token.ts +122 -0
  12. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +29 -4
  13. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +365 -9
  14. package/corpus/templates/slides/app/components/editor/bullet-editing.ts +350 -0
  15. package/corpus/templates/slides/app/context/DeckContext.tsx +1 -3
  16. package/corpus/templates/slides/app/i18n/en-US.ts +2 -0
  17. package/corpus/templates/slides/app/pages/DeckEditor.tsx +5 -0
  18. package/corpus/templates/slides/changelog/2026-07-23-new-bullet-rows-created-with-enter-now-keep-the-list-item-s-.md +6 -0
  19. package/corpus/templates/slides/changelog/2026-07-23-pressing-enter-in-generated-checkbox-shape-marker-lists-now-.md +6 -0
  20. package/corpus/templates/slides/changelog/2026-07-24-typing-a-markdown-style-dash-space-at-the-start-of-a-text-bl.md +6 -0
  21. package/dist/agent/durable-background.d.ts.map +1 -1
  22. package/dist/agent/durable-background.js +16 -4
  23. package/dist/agent/durable-background.js.map +1 -1
  24. package/dist/agent/run-manager.d.ts.map +1 -1
  25. package/dist/agent/run-manager.js +6 -0
  26. package/dist/agent/run-manager.js.map +1 -1
  27. package/dist/client/use-db-sync.d.ts.map +1 -1
  28. package/dist/client/use-db-sync.js +27 -22
  29. package/dist/client/use-db-sync.js.map +1 -1
  30. package/dist/collab/awareness.d.ts +2 -2
  31. package/dist/collab/awareness.d.ts.map +1 -1
  32. package/dist/deploy/build.d.ts.map +1 -1
  33. package/dist/deploy/build.js +1 -0
  34. package/dist/deploy/build.js.map +1 -1
  35. package/dist/observability/routes.d.ts +3 -3
  36. package/dist/progress/routes.d.ts +1 -1
  37. package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
  38. package/dist/provider-api/actions/provider-api.d.ts +6 -6
  39. package/dist/resources/handlers.d.ts +1 -1
  40. package/dist/secrets/routes.d.ts +9 -9
  41. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  42. package/dist/server/core-routes-plugin.js +3 -0
  43. package/dist/server/core-routes-plugin.js.map +1 -1
  44. package/dist/server/gateway-access-check.d.ts +12 -0
  45. package/dist/server/gateway-access-check.d.ts.map +1 -0
  46. package/dist/server/gateway-access-check.js +49 -0
  47. package/dist/server/gateway-access-check.js.map +1 -0
  48. package/dist/server/poll.d.ts +55 -1
  49. package/dist/server/poll.d.ts.map +1 -1
  50. package/dist/server/poll.js +254 -7
  51. package/dist/server/poll.js.map +1 -1
  52. package/dist/server/realtime-token.d.ts +1 -1
  53. package/dist/server/short-lived-token.d.ts +28 -0
  54. package/dist/server/short-lived-token.d.ts.map +1 -1
  55. package/dist/server/short-lived-token.js +78 -0
  56. package/dist/server/short-lived-token.js.map +1 -1
  57. package/dist/server/transcribe-voice.d.ts +1 -1
  58. package/package.json +1 -1
  59. package/src/agent/durable-background.ts +12 -4
  60. package/src/agent/run-manager.ts +3 -0
  61. package/src/client/use-db-sync.ts +23 -21
  62. package/src/deploy/build.ts +1 -0
  63. package/src/server/core-routes-plugin.ts +3 -0
  64. package/src/server/gateway-access-check.ts +67 -0
  65. package/src/server/poll.ts +295 -9
  66. package/src/server/short-lived-token.ts +122 -0
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Helpers for editing styled bullet lists — list-item rows built from a marker
3
+ * glyph plus text (e.g. `<div><span>●</span><span>Point</span></div>`) rather
4
+ * than real <ul>/<li> markup, which is how generated decks represent bullets.
5
+ *
6
+ * Kept in a standalone module (no React exports) so SlideEditor stays
7
+ * Fast-Refresh friendly and this logic is unit-testable.
8
+ */
9
+
10
+ /** Zero-width space: keeps the caret inside an otherwise-empty text span so
11
+ * typed characters inherit that span's font instead of the container's. */
12
+ export const ZERO_WIDTH_SPACE = "\u200B";
13
+
14
+ /** Single glyphs commonly used as bullet markers in styled (non-<ul>) lists. */
15
+ const BULLET_GLYPHS = new Set([
16
+ "\u2022", // •
17
+ "\u25CF", // ●
18
+ "\u25E6", // ◦
19
+ "\u25AA", // ▪
20
+ "\u2023", // ‣
21
+ "\u00B7", // ·
22
+ "\u2043", // ⁃
23
+ "-",
24
+ "\u2013", // –
25
+ "\u2014", // —
26
+ "*",
27
+ ]);
28
+
29
+ /** True if an element is a bullet marker — either a text glyph (a leading ●
30
+ * span) or an empty CSS shape (a small square/dot/box span used as a marker). */
31
+ export function isBulletMarker(el: Element): boolean {
32
+ return isGlyphMarker(el) || isShapeMarker(el);
33
+ }
34
+
35
+ /** A leading span whose text is only bullet glyph characters (e.g. "●"). */
36
+ function isGlyphMarker(el: Element): boolean {
37
+ const text = (el.textContent ?? "").trim();
38
+ return text.length > 0 && [...text].every((c) => BULLET_GLYPHS.has(c));
39
+ }
40
+
41
+ /** An empty, small, roughly-square span drawn as a marker via border/background
42
+ * (e.g. `<span style="width:21px;height:21px;border:2px solid ...">`), which is
43
+ * how generated decks often render checkbox/dot bullets with no text glyph. */
44
+ function isShapeMarker(el: Element): boolean {
45
+ if ((el.textContent ?? "").trim().length > 0) return false;
46
+ if (el.childElementCount > 0) return false;
47
+ const w = parseCssPx(styleValue(el, "width"));
48
+ const h = parseCssPx(styleValue(el, "height"));
49
+ if (!(w > 0 && h > 0) || w > 48 || h > 48) return false;
50
+ const ratio = w / h;
51
+ if (ratio < 0.5 || ratio > 2) return false;
52
+ const hasBorder =
53
+ parseCssPx(styleValue(el, "border-top-width")) > 0 ||
54
+ parseCssPx(styleValue(el, "border-left-width")) > 0 ||
55
+ parseCssPx(styleValue(el, "border-width")) > 0;
56
+ const bg = styleValue(el, "background-color");
57
+ const hasBg = !!bg && bg !== "transparent" && bg !== "rgba(0, 0, 0, 0)";
58
+ const hasRadius = parseCssPx(styleValue(el, "border-radius")) > 0;
59
+ return hasBorder || hasBg || hasRadius;
60
+ }
61
+
62
+ /** Read a style property, preferring inline styles and falling back to computed
63
+ * styles when available (jsdom-safe). */
64
+ function styleValue(el: Element, prop: string): string {
65
+ const inline = (el as HTMLElement).style?.getPropertyValue(prop);
66
+ if (inline) return inline;
67
+ if (typeof window !== "undefined" && window.getComputedStyle) {
68
+ try {
69
+ return window.getComputedStyle(el).getPropertyValue(prop);
70
+ } catch {
71
+ return "";
72
+ }
73
+ }
74
+ return "";
75
+ }
76
+
77
+ function parseCssPx(value: string): number {
78
+ const parsed = Number.parseFloat(value);
79
+ return Number.isFinite(parsed) ? parsed : 0;
80
+ }
81
+
82
+ /** The bullet-marker element enclosing a node (or the node itself), bounded by
83
+ * `root`, or null when the node isn't inside a marker glyph. */
84
+ function enclosingMarker(node: Node, root: HTMLElement): HTMLElement | null {
85
+ let el: HTMLElement | null =
86
+ node.nodeType === Node.ELEMENT_NODE
87
+ ? (node as HTMLElement)
88
+ : node.parentElement;
89
+ while (el && el !== root && root.contains(el)) {
90
+ if (isBulletMarker(el)) return el;
91
+ el = el.parentElement;
92
+ }
93
+ return null;
94
+ }
95
+
96
+ /**
97
+ * A "bullet row" is a styled list item whose first element child is a marker
98
+ * glyph. The text after it may be a <span> or a bare text node (contentEditable
99
+ * often unwraps spans while editing), and may be empty for a freshly-added
100
+ * bullet — so only the leading marker is required.
101
+ */
102
+ export function isBulletRow(el: HTMLElement): boolean {
103
+ if (el.tagName !== "DIV" && el.tagName !== "LI" && el.tagName !== "P") {
104
+ return false;
105
+ }
106
+ const first = el.firstElementChild;
107
+ return !!first && isBulletMarker(first);
108
+ }
109
+
110
+ /** Count the styled bullet rows directly inside a container. */
111
+ export function bulletRowCount(el: HTMLElement): number {
112
+ return Array.from(el.children).filter((k) => isBulletRow(k as HTMLElement))
113
+ .length;
114
+ }
115
+
116
+ /** A container whose element children are (essentially) all styled bullet rows. */
117
+ export function isBulletList(el: HTMLElement): boolean {
118
+ const kids = Array.from(el.children);
119
+ if (kids.length === 0) return false;
120
+ const rows = bulletRowCount(el);
121
+ // Tolerate one stray non-bullet child (e.g. a stray <br>/<div> left behind by
122
+ // contentEditable) as long as the container is clearly a bullet list.
123
+ return rows >= 1 && rows >= kids.length - 1;
124
+ }
125
+
126
+ /** Regex for a markdown-style bullet prefix: a dash or asterisk plus a space,
127
+ * at the very start of a block's content (e.g. "- " or "* "). */
128
+ const MARKDOWN_BULLET_PREFIX = /^[-*] $/;
129
+
130
+ /**
131
+ * If `el`'s content starts with a markdown-style "- "/"* " prefix and the
132
+ * caret sits right after it, convert `el`'s content into a styled bullet row
133
+ * — a small marker span plus a text span holding the rest of `el`'s content —
134
+ * nested inside `el`, which becomes the list container. `el` itself must stay
135
+ * the contentEditable root (nesting the row rather than turning `el` itself
136
+ * into the row) so a later Enter's cloned sibling row lands inside the same
137
+ * contentEditable boundary and is actually typeable. Returns false when `el`
138
+ * is already a bullet row/list, there's no such prefix, or the selection
139
+ * isn't a collapsed caret.
140
+ */
141
+ export function convertMarkdownPrefixToBullet(el: HTMLElement): boolean {
142
+ if (isBulletRow(el) || isBulletList(el)) return false;
143
+ const sel = window.getSelection();
144
+ if (!sel || sel.rangeCount === 0) return false;
145
+ const caretRange = sel.getRangeAt(0);
146
+ if (!caretRange.collapsed) return false;
147
+
148
+ const beforeCaretRange = document.createRange();
149
+ beforeCaretRange.selectNodeContents(el);
150
+ beforeCaretRange.setEnd(caretRange.endContainer, caretRange.endOffset);
151
+ // A freshly-placed text box seeds its content with a zero-width-space
152
+ // placeholder (see placeTextBoxAt) so it has a font to inherit before any
153
+ // real text exists. Strip it before testing so "- " typed as the very
154
+ // first characters is still recognized as a bullet prefix.
155
+ const beforeCaretText = beforeCaretRange
156
+ .toString()
157
+ .replace(new RegExp(ZERO_WIDTH_SPACE, "g"), "");
158
+ if (!MARKDOWN_BULLET_PREFIX.test(beforeCaretText)) return false;
159
+ beforeCaretRange.deleteContents();
160
+
161
+ const marker = document.createElement("span");
162
+ marker.style.fontSize = "0.3em";
163
+ marker.style.position = "relative";
164
+ marker.style.top = "-0.15em";
165
+ marker.textContent = "\u25CF";
166
+
167
+ const textSpan = document.createElement("span");
168
+ while (el.firstChild) textSpan.appendChild(el.firstChild);
169
+ const restFirstChild = textSpan.firstChild;
170
+ let placeholderZws: Text | null = null;
171
+ if (!restFirstChild) {
172
+ placeholderZws = document.createTextNode(ZERO_WIDTH_SPACE);
173
+ textSpan.appendChild(placeholderZws);
174
+ }
175
+
176
+ const row = document.createElement("div");
177
+ row.style.display = "flex";
178
+ row.style.alignItems = "baseline";
179
+ row.style.gap = "0.7em";
180
+ row.append(marker, textSpan);
181
+ el.append(row);
182
+
183
+ if (!el.style.display) el.style.display = "flex";
184
+ if (!el.style.flexDirection) el.style.flexDirection = "column";
185
+ if (!el.style.gap) el.style.gap = "0.6em";
186
+
187
+ const range = document.createRange();
188
+ if (restFirstChild) {
189
+ range.setStartBefore(restFirstChild);
190
+ } else {
191
+ // See primeNewRow: anchor the caret inside the placeholder text node
192
+ // (not an element-based position) so it keeps the text span's font
193
+ // instead of falling back to the marker's.
194
+ range.setStart(placeholderZws as Text, ZERO_WIDTH_SPACE.length);
195
+ }
196
+ range.collapse(true);
197
+ sel.removeAllRanges();
198
+ sel.addRange(range);
199
+ return true;
200
+ }
201
+
202
+ /**
203
+ * Walk up from a text leaf to the nearest enclosing styled bullet-row
204
+ * container, so Enter can add a new item to the whole list instead of being
205
+ * trapped inside one item.
206
+ */
207
+ export function findEnclosingList(
208
+ el: HTMLElement,
209
+ root: HTMLElement,
210
+ ): HTMLElement | null {
211
+ let node: HTMLElement | null = el;
212
+ while (node && root.contains(node)) {
213
+ const parentEl: HTMLElement | null = node.parentElement;
214
+ if (!parentEl) break;
215
+ if (isBulletRow(node) && isBulletList(parentEl)) return parentEl;
216
+ // Even from a bullet row whose siblings aren't all bullets, treat the
217
+ // parent as a list once it holds two or more bullet rows.
218
+ if (isBulletRow(node) && bulletRowCount(parentEl) >= 2) return parentEl;
219
+ node = parentEl;
220
+ }
221
+ return null;
222
+ }
223
+
224
+ /** The non-marker text container of a row: a dedicated text <span> if present,
225
+ * otherwise the row itself (rows whose text is a bare node). */
226
+ function rowTextContainer(
227
+ row: HTMLElement,
228
+ marker: HTMLElement | null,
229
+ ): HTMLElement {
230
+ const textSpan = Array.from(row.children).find(
231
+ (c) => c.tagName === "SPAN" && c !== marker && !isBulletMarker(c),
232
+ ) as HTMLElement | undefined;
233
+ return textSpan ?? row;
234
+ }
235
+
236
+ /**
237
+ * Seed a freshly-inserted row with the caret's trailing content and place the
238
+ * caret at the start of its editable text. `tail` is a DOM fragment (not a
239
+ * string) so inline formatting such as <strong>/<em> carried over from the
240
+ * split point is preserved. When there is no tail, a zero-width space text node
241
+ * keeps the caret inside the font-carrying text span rather than dropping it to
242
+ * the container.
243
+ */
244
+ function primeNewRow(row: HTMLElement, tail: DocumentFragment | null): void {
245
+ const marker =
246
+ row.firstElementChild && isBulletMarker(row.firstElementChild)
247
+ ? (row.firstElementChild as HTMLElement)
248
+ : null;
249
+ const container = rowTextContainer(row, marker);
250
+
251
+ // Clear existing text content, preserving the marker glyph.
252
+ if (container !== row) {
253
+ container.replaceChildren();
254
+ } else {
255
+ while (marker?.nextSibling) marker.nextSibling.remove();
256
+ if (!marker) row.replaceChildren();
257
+ }
258
+
259
+ const firstTailNode = tail?.firstChild ?? null;
260
+ if (tail && firstTailNode) container.appendChild(tail);
261
+
262
+ const sel = window.getSelection();
263
+ if (!sel) return;
264
+ const range = document.createRange();
265
+ if (firstTailNode) {
266
+ // Caret at the very start of the moved tail (before the marker is not
267
+ // possible: setStartBefore anchors relative to the tail's first node).
268
+ range.setStartBefore(firstTailNode);
269
+ } else {
270
+ const zws = document.createTextNode(ZERO_WIDTH_SPACE);
271
+ container.appendChild(zws);
272
+ range.setStart(zws, ZERO_WIDTH_SPACE.length);
273
+ }
274
+ range.collapse(true);
275
+ sel.removeAllRanges();
276
+ sel.addRange(range);
277
+ }
278
+
279
+ /**
280
+ * Insert a new list item after the caret's current row. Content after the caret
281
+ * moves into the new row (with inline formatting preserved); the marker glyph is
282
+ * preserved on both rows. Returns false when the caret isn't inside a direct row
283
+ * of the list so the caller can fall back.
284
+ */
285
+ export function insertBulletAfterCaret(list: HTMLElement): boolean {
286
+ const sel = window.getSelection();
287
+ if (!sel || sel.rangeCount === 0) return false;
288
+ const range = sel.getRangeAt(0);
289
+ if (!range.collapsed) {
290
+ // A selection that spans a row's marker glyph would delete it here, blanking
291
+ // the bullet on the surviving row (and its clone). Clamp both boundaries out
292
+ // of any enclosing marker so deletion never touches the glyphs.
293
+ const startMarker = enclosingMarker(range.startContainer, list);
294
+ if (startMarker) range.setStartAfter(startMarker);
295
+ const endMarker = enclosingMarker(range.endContainer, list);
296
+ if (endMarker) range.setEndBefore(endMarker);
297
+ if (!range.collapsed) range.deleteContents();
298
+ }
299
+
300
+ let row: HTMLElement | null = null;
301
+ let node: Node | null = range.endContainer;
302
+ while (node && node !== list) {
303
+ if (node.parentNode === list) {
304
+ row = node as HTMLElement;
305
+ break;
306
+ }
307
+ node = node.parentNode;
308
+ }
309
+ if (!row) return false;
310
+
311
+ const marker =
312
+ row.firstElementChild && isBulletMarker(row.firstElementChild)
313
+ ? (row.firstElementChild as HTMLElement)
314
+ : null;
315
+
316
+ // Never split inside the marker glyph itself: a caret at offset 0 of the "●"
317
+ // text node (e.g. clicking the marker's leading edge) would otherwise blank
318
+ // the marker and un-bullet the row. In that case add an empty bullet instead.
319
+ const caretInMarker = !!marker && marker.contains(range.endContainer);
320
+
321
+ const container = rowTextContainer(row, marker);
322
+ let tail: DocumentFragment | null = null;
323
+ if (!caretInMarker && container.contains(range.endContainer)) {
324
+ const tailRange = document.createRange();
325
+ tailRange.setStart(range.endContainer, range.endOffset);
326
+ const lastChild = container.lastChild;
327
+ if (lastChild) tailRange.setEndAfter(lastChild);
328
+ else tailRange.setEnd(container, container.childNodes.length);
329
+ // extractContents() moves the trailing DOM subtree (preserving <strong>/
330
+ // <em>) out of the original row so it can be reparented into the new one.
331
+ tail = tailRange.extractContents();
332
+ // A caret at the very end of the text (the common case) makes tailRange
333
+ // collapsed, but extractContents() on a collapsed range still clones the
334
+ // boundary text node with empty data instead of returning an empty
335
+ // fragment. Treat that as "no tail" so primeNewRow falls through to the
336
+ // zero-width-space placeholder — otherwise it moves in an empty text node
337
+ // with no character to anchor the caret's font to, and typing falls back
338
+ // to the marker span's formatting instead of the text span's.
339
+ if (tail.textContent === "") tail = null;
340
+ }
341
+
342
+ const newRow = row.cloneNode(true) as HTMLElement;
343
+ for (const el of [newRow, ...Array.from(newRow.querySelectorAll("*"))]) {
344
+ el.removeAttribute("data-builder-id");
345
+ el.removeAttribute("data-fusion-element-id");
346
+ }
347
+ row.after(newRow);
348
+ primeNewRow(newRow, tail);
349
+ return true;
350
+ }
@@ -945,9 +945,7 @@ export const defaultSlideContent: Record<SlideLayout, string> = {
945
945
  "full-image": `<div class="fmd-slide" style="padding: 0; align-items: center; justify-content: center;">
946
946
  <div class="fmd-img-placeholder" style="width: 100%; height: 100%;">Full-bleed image or screenshot</div>
947
947
  </div>`,
948
- blank: `<div class="fmd-slide" style="padding: 80px 110px; display: flex; flex-direction: column; justify-content: center; align-items: flex-start; font-family: 'Poppins', sans-serif;">
949
- <div style="font-size: 28px; font-weight: 600; color: rgba(255,255,255,0.4); line-height: 1.3; font-family: 'Poppins', sans-serif;">Double-click to edit</div>
950
- </div>`,
948
+ blank: `<div class="fmd-slide" style="padding: 80px 110px; position: relative; font-family: 'Poppins', sans-serif;"></div>`,
951
949
  };
952
950
 
953
951
  export function DeckProvider({ children }: { children: ReactNode }) {
@@ -121,6 +121,7 @@ const messages = {
121
121
  uploadAttachedFailed: "Could not upload the attached file.",
122
122
  uploadFailed: "Upload failed",
123
123
  doubleClickEdit: "Double-click any text to edit",
124
+ dragToMove: "Drag to move",
124
125
  aiEditing: "AI editing",
125
126
  startTypingCommands: "Start typing… or press / for commands",
126
127
  pinDropHint: "Click anywhere to drop a comment pin",
@@ -210,6 +211,7 @@ const messages = {
210
211
  elementAnimations: "Element animations",
211
212
  tweaks: "Tweaks",
212
213
  drawOnSlide: "Draw on slide",
214
+ addTextBox: "Add text box",
213
215
  pinComments: "Pin comments",
214
216
  pinCommentsDescription:
215
217
  "Click spots on the slide to queue several edits, then send them all at once.",
@@ -196,6 +196,7 @@ export default function DeckEditor() {
196
196
  const [tweaksOpen, setTweaksOpen] = useState(false);
197
197
  const [drawMode, setDrawMode] = useState(false);
198
198
  const [pinMode, setPinMode] = useState(false);
199
+ const [textBoxMode, setTextBoxMode] = useState(false);
199
200
  const [pendingComment, setPendingComment] = useState<{
200
201
  quotedText: string;
201
202
  } | null>(null);
@@ -816,6 +817,8 @@ export default function DeckEditor() {
816
817
  onToggleDrawMode={() => setDrawMode((v) => !v)}
817
818
  pinMode={pinMode}
818
819
  onTogglePinMode={() => setPinMode((v) => !v)}
820
+ textBoxMode={textBoxMode}
821
+ onToggleTextBoxMode={() => setTextBoxMode((v) => !v)}
819
822
  onDuplicateDeck={() => {
820
823
  const newId = `deck-${nanoid()}`;
821
824
  const optimistic = duplicateDeck(id, newId);
@@ -992,6 +995,8 @@ export default function DeckEditor() {
992
995
  onExitDrawMode={() => setDrawMode(false)}
993
996
  pinMode={pinMode}
994
997
  onExitPinMode={() => setPinMode(false)}
998
+ textBoxMode={textBoxMode}
999
+ onExitTextBoxMode={() => setTextBoxMode(false)}
995
1000
  slideId={currentSlide.id}
996
1001
  slideTitle={(() => {
997
1002
  const m = currentSlide.content?.match(
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-23
4
+ ---
5
+
6
+ New bullet rows created with Enter now keep the list item's font size instead of shrinking to the base font
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-23
4
+ ---
5
+
6
+ Pressing Enter in generated checkbox/shape-marker lists now creates a new list item
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-07-24
4
+ ---
5
+
6
+ Typing a markdown-style dash-space at the start of a text block now converts it into an editable bullet list
@@ -1 +1 @@
1
- {"version":3,"file":"durable-background.d.ts","sourceRoot":"","sources":["../../src/agent/durable-background.ts"],"names":[],"mappings":"AAgDA;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,2CACE,CAAC;AAE3C;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,4BAA4B,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,kCAAkC,gDAA0D,CAAC;AAE1G;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,2BAA2B,CAAC;AACzE,eAAO,MAAM,8BAA8B,QAAQ,CAAC;AACpD,eAAO,MAAM,sCAAsC,gBAAgB,CAAC;AACpE,eAAO,MAAM,gCAAgC,UAAU,CAAC;AACxD,eAAO,MAAM,sCAAsC,gCACpB,CAAC;AAiChC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,sCAAsC,IAAI,MAAM,CAQ/D;AAED,wBAAgB,oCAAoC,CAClD,YAAY,EAAE,MAAM,GACnB,MAAM,CAQR;AAED,wBAAgB,4CAA4C,CAC1D,YAAY,EAAE,MAAM,GACnB,OAAO,CAET;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;AAED,wBAAgB,2CAA2C,CACzD,MAAM,EAAE,OAAO,GACd,OAAO,CAOT;AAED,wBAAgB,2CAA2C,CACzD,OAAO,EAAE,OAAO,GACf,OAAO,CAKT;AAED,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAQzE;AAwCD;;;;;;;GAOG;AACH,wBAAgB,mCAAmC,CAAC,OAAO,CAAC,EAAE;IAC5D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,GAAG,OAAO,CAiBV;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,oCAAoC,qCACb,CAAC;AAiBrC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qCAAqC,IAAI,OAAO,CAM/D;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;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,QAAQ,GAAE,OAAe,GACxB,qBAAqB,CAqDvB"}
1
+ {"version":3,"file":"durable-background.d.ts","sourceRoot":"","sources":["../../src/agent/durable-background.ts"],"names":[],"mappings":"AAgDA;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,2CACE,CAAC;AAE3C;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,4BAA4B,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,kCAAkC,gDAA0D,CAAC;AAE1G;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,2BAA2B,CAAC;AACzE,eAAO,MAAM,8BAA8B,QAAQ,CAAC;AACpD,eAAO,MAAM,sCAAsC,gBAAgB,CAAC;AACpE,eAAO,MAAM,gCAAgC,UAAU,CAAC;AACxD,eAAO,MAAM,sCAAsC,gCACpB,CAAC;AAsChC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,sCAAsC,IAAI,MAAM,CAQ/D;AAED,wBAAgB,oCAAoC,CAClD,YAAY,EAAE,MAAM,GACnB,MAAM,CAQR;AAED,wBAAgB,4CAA4C,CAC1D,YAAY,EAAE,MAAM,GACnB,OAAO,CAET;AAED;;;GAGG;AACH,eAAO,MAAM,iCAAiC,kCACb,CAAC;AAElC;;;;;;GAMG;AACH,eAAO,MAAM,+BAA+B,oBAAoB,CAAC;AAEjE;;;;GAIG;AACH,wBAAgB,mCAAmC,IAAI,OAAO,CAyB7D;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,6BAA6B,IAAI,OAAO,CAsBvD;AAED,wBAAgB,2CAA2C,CACzD,MAAM,EAAE,OAAO,GACd,OAAO,CAOT;AAED,wBAAgB,2CAA2C,CACzD,OAAO,EAAE,OAAO,GACf,OAAO,CAKT;AAED,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAQzE;AAwCD;;;;;;;GAOG;AACH,wBAAgB,mCAAmC,CAAC,OAAO,CAAC,EAAE;IAC5D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,GAAG,OAAO,CAiBV;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,oCAAoC,qCACb,CAAC;AAiBrC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qCAAqC,IAAI,OAAO,CAM/D;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;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,QAAQ,GAAE,OAAe,GACxB,qBAAqB,CAqDvB"}
@@ -105,10 +105,16 @@ function isNetlifyHostedRuntimeForDispatch() {
105
105
  return false;
106
106
  if (process.env.NETLIFY && process.env.NETLIFY !== "false")
107
107
  return true;
108
- // Netlify sets AWS Lambda runtime env on deployed Functions, but the build-time
109
- // NETLIFY flag is not always present in the runtime isolate. Treat Lambda as
110
- // Netlify here unless Netlify was explicitly disabled above; non-Netlify AWS
111
- // falls back inline if the /.netlify/functions dispatch fast-fails.
108
+ // NETLIFY is a build-only read-only variable. In deployed Functions Netlify
109
+ // documents URL, SITE_NAME, and SITE_ID as the runtime read-only variables;
110
+ // SITE_ID is the unambiguous host marker. Lambda compatibility mode also
111
+ // exposes AWS runtime variables, so keep the function-name fallback for older
112
+ // deploys. Without either check a modern Netlify Function silently selects the
113
+ // portable framework route even though the emitted background function exists.
114
+ if (process.env.SITE_ID)
115
+ return true; // guard:allow-env-credential -- Netlify's read-only public site identifier is a runtime host marker, not a user credential.
116
+ // Non-Netlify AWS falls back inline if the /.netlify/functions dispatch
117
+ // fast-fails.
112
118
  return Boolean(process.env.AWS_LAMBDA_FUNCTION_NAME);
113
119
  }
114
120
  /**
@@ -178,6 +184,12 @@ export const AGENT_CHAT_BACKGROUND_RUN_FIELD = "__backgroundRun";
178
184
  * what "hosted" means.
179
185
  */
180
186
  export function isHostedRuntimeForDurableBackground() {
187
+ if (process.env.NETLIFY_LOCAL === "true")
188
+ return false;
189
+ if (process.env.NETLIFY === "false")
190
+ return false;
191
+ if (process.env.SITE_ID)
192
+ return true; // guard:allow-env-credential -- Netlify's read-only public site identifier is a runtime host marker, not a user credential.
181
193
  if (process.env.NETLIFY &&
182
194
  process.env.NETLIFY !== "false" &&
183
195
  process.env.NETLIFY_LOCAL !== "true") {
@@ -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 explicitly enabled, or a\n * workspace app's agent-chat plugin opts in with `durableBackgroundRuns`\n * where the workspace deploy emits a per-app background function by\n * default. Single-template Netlify deploys must set the env flag because\n * that same flag controls whether `server-agent-background` is emitted.\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 * Opt-in keeps the blast radius small while the worker path is still being\n * proven. And even when enabled, a *dispatch failure degrades to an inline run*:\n * if 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 isTrustedLocalRuntime,\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 * Marker carried in a Netlify background-function body when the shared\n * long-running worker should route to a processor other than agent chat.\n * The emitted wrapper defaults to the normal agent-chat `_process-run` route;\n * A2A uses this marker to reuse the same 15-minute function for async tasks.\n */\nexport const AGENT_BACKGROUND_PROCESSOR_FIELD = \"__agentNativeProcessor\";\nexport const AGENT_BACKGROUND_PROCESSOR_A2A = \"a2a\";\nexport const AGENT_BACKGROUND_PROCESSOR_INTEGRATION = \"integration\";\nexport const AGENT_BACKGROUND_PROCESSOR_ROUTE = \"route\";\nexport const AGENT_BACKGROUND_PROCESSOR_ROUTE_FIELD =\n \"__agentNativeProcessorRoute\";\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\nfunction isNetlifyHostedRuntimeForDispatch(): boolean {\n if (process.env.NETLIFY_LOCAL === \"true\") return false;\n if (process.env.NETLIFY === \"false\") return false;\n if (process.env.NETLIFY && process.env.NETLIFY !== \"false\") return true;\n // Netlify sets AWS Lambda runtime env on deployed Functions, but the build-time\n // NETLIFY flag is not always present in the runtime isolate. Treat Lambda as\n // Netlify here unless Netlify was explicitly disabled above; non-Netlify AWS\n // falls back inline if the /.netlify/functions dispatch fast-fails.\n return Boolean(process.env.AWS_LAMBDA_FUNCTION_NAME);\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 (isNetlifyHostedRuntimeForDispatch()) {\n return (\n resolveWorkspaceBackgroundFunctionUrlPath() ??\n AGENT_BACKGROUND_FUNCTION_URL_PATH\n );\n }\n return AGENT_CHAT_PROCESS_RUN_PATH;\n}\n\nexport function resolveDurableBackgroundDispatchPath(\n fallbackPath: string,\n): string {\n if (isNetlifyHostedRuntimeForDispatch()) {\n return (\n resolveWorkspaceBackgroundFunctionUrlPath() ??\n AGENT_BACKGROUND_FUNCTION_URL_PATH\n );\n }\n return fallbackPath;\n}\n\nexport function dispatchPathTargetsNetlifyBackgroundFunction(\n dispatchPath: string,\n): boolean {\n return dispatchPath.startsWith(\"/.netlify/functions/\");\n}\n\n/**\n * Env flag for durable background runs. DEFAULT-OFF (opt-in): unset means\n * disabled; an app opts IN with an explicit truthy value (`true`/`1`/`yes`/`on`).\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\nexport function backgroundRunMarkerExpectsBackgroundRuntime(\n marker: unknown,\n): boolean {\n return (\n typeof marker === \"object\" &&\n marker !== null &&\n (marker as { backgroundFunctionRuntimeExpected?: unknown })\n .backgroundFunctionRuntimeExpected === true\n );\n}\n\nexport function shouldUseBackgroundFunctionTimeoutForWorker(\n _marker: unknown,\n): boolean {\n // The dispatch marker says which URL the foreground targeted, not where the\n // request actually landed. Only the worker runtime proof can safely lift the\n // hosted 40s clamp to the 15-minute background-function budget.\n return isInBackgroundFunctionRuntime();\n}\n\nexport function backgroundRuntimeDiagnosticDetail(marker: unknown): string {\n return [\n `markerExpected=${backgroundRunMarkerExpectsBackgroundRuntime(marker)}`,\n `runtimeDetected=${isInBackgroundFunctionRuntime()}`,\n `globalMarker=${(globalThis as Record<string, unknown>).__AGENT_NATIVE_BACKGROUND_RUNTIME__ === true}`,\n `lambdaNameEndsBackground=${typeof process.env.AWS_LAMBDA_FUNCTION_NAME === \"string\" && process.env.AWS_LAMBDA_FUNCTION_NAME.toLowerCase().endsWith(\"-background\")}`,\n `forceEnv=${typeof process.env.AGENT_CHAT_FORCE_BACKGROUND_RUNTIME === \"string\" && process.env.AGENT_CHAT_FORCE_BACKGROUND_RUNTIME.trim().length > 0}`,\n ].join(\" \");\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\nfunction isFlagExplicitlyDisabled(): boolean {\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 === \"0\" ||\n normalized === \"false\" ||\n normalized === \"no\" ||\n normalized === \"off\"\n );\n}\n\n/**\n * The single gate. True when the env flag is explicitly enabled, or a workspace\n * app opted in and has a per-app background-function target, AND the runtime is\n * hosted AND A2A_SECRET is configured. False otherwise — and false means the\n * current synchronous behavior is used unchanged. Single-template Netlify app\n * opt-ins deliberately require the env flag too because that flag controls\n * whether the `server-agent-background` function exists in the deploy output.\n */\nexport function isAgentChatDurableBackgroundEnabled(options?: {\n appOptIn?: boolean;\n}): boolean {\n // An app-level opt-out must win over a stale deploy-wide env flag. Netlify\n // environment variables can outlive the source config that originally set\n // them; allowing that flag to re-enable a worker an app explicitly disabled\n // recreates the missing-background-function failure this gate is meant to\n // prevent.\n if (options?.appOptIn === false) return false;\n const envOptIn = isFlagEnabled();\n const workspaceAppOptIn =\n options?.appOptIn === true &&\n !isFlagExplicitlyDisabled() &&\n resolveWorkspaceBackgroundFunctionUrlPath() !== null;\n return (\n (envOptIn || workspaceAppOptIn) &&\n isHostedRuntimeForDurableBackground() &&\n hasConfiguredA2ASecret()\n );\n}\n\n/**\n * Env flag for the FOREGROUND server-driven self-chain. DEFAULT-OFF: a hosted\n * app must explicitly opt in with a truthy value (`true`/`1`/`yes`/`on`). A\n * regular Netlify function has a fixed 60-second wall, and a self-dispatched\n * successor can otherwise be killed before it persists its next continuation.\n * Keep this separate from `AGENT_CHAT_DURABLE_BACKGROUND` so the experimental\n * regular-function chain can be enabled independently after its deployment is\n * proven safe.\n */\nexport const AGENT_CHAT_FOREGROUND_SELF_CHAIN_ENV =\n \"AGENT_CHAT_FOREGROUND_SELF_CHAIN\";\n\nfunction isForegroundSelfChainExplicitlyEnabled(): 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_FOREGROUND_SELF_CHAIN_ENV.\n const raw = process.env.AGENT_CHAT_FOREGROUND_SELF_CHAIN;\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 * Gate for the foreground self-chain: a normal (non-durable-background)\n * agent-chat turn that hits its soft-timeout chunk boundary continues via a\n * server-side self-dispatch on the REGULAR function (not a Netlify\n * `-background` function) instead of depending on the client to re-POST\n * `auto_continue`. Composes exactly like `isAgentChatDurableBackgroundEnabled`:\n * true only when the env flag is explicitly truthy, the runtime is hosted, and\n * `A2A_SECRET` is configured (the HMAC handoff authenticates the dispatch).\n * False means the existing client-driven `auto_continue` re-POST path is used.\n *\n * Deliberately independent of `isAgentChatDurableBackgroundEnabled`: an app can\n * use this narrower capability without opting into the full 15-min\n * background-function worker path, and the two gates never need to agree.\n * When BOTH would be true for a given run, the durable-background dispatch\n * decision in `production-agent.ts` is evaluated first and takes precedence —\n * a run already dispatched to the durable background worker chains via the\n * existing `isBackgroundWorker` path, not this one.\n */\nexport function isAgentChatForegroundSelfChainEnabled(): boolean {\n return (\n isForegroundSelfChainExplicitlyEnabled() &&\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 → require `isTrustedLocalRuntime({ loopback })` (see\n * auth-policy.ts): refuse (503) unless `A2A_ALLOW_UNSIGNED_INTERNAL=1` is\n * set. This function has no h3 `event` of its own, so callers that CAN\n * see the inbound socket peer (the route handler, which has the event)\n * should compute `loopback` from it and pass it through; callers that\n * can't determine the peer address should omit it (defaults to `false`\n * — never trust unsigned dispatch without an explicit opt-in).\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 loopback: boolean = false,\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 (!isTrustedLocalRuntime({ loopback })) {\n // Callers that can see the h3 `event` (the route handler) pass the real\n // loopback signal; callers without one default to non-loopback. Unsigned\n // dispatch is still allowed via A2A_ALLOW_UNSIGNED_INTERNAL=1 for trusted\n // local/dev setups; see auth-policy.ts `isTrustedLocalRuntime`.\n return {\n ok: false,\n status: 503,\n error:\n \"Agent chat background processor not configured — set A2A_SECRET on this deployment (or A2A_ALLOW_UNSIGNED_INTERNAL=1 for trusted local dev).\",\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"]}
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 explicitly enabled, or a\n * workspace app's agent-chat plugin opts in with `durableBackgroundRuns`\n * where the workspace deploy emits a per-app background function by\n * default. Single-template Netlify deploys must set the env flag because\n * that same flag controls whether `server-agent-background` is emitted.\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 * Opt-in keeps the blast radius small while the worker path is still being\n * proven. And even when enabled, a *dispatch failure degrades to an inline run*:\n * if 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 isTrustedLocalRuntime,\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 * Marker carried in a Netlify background-function body when the shared\n * long-running worker should route to a processor other than agent chat.\n * The emitted wrapper defaults to the normal agent-chat `_process-run` route;\n * A2A uses this marker to reuse the same 15-minute function for async tasks.\n */\nexport const AGENT_BACKGROUND_PROCESSOR_FIELD = \"__agentNativeProcessor\";\nexport const AGENT_BACKGROUND_PROCESSOR_A2A = \"a2a\";\nexport const AGENT_BACKGROUND_PROCESSOR_INTEGRATION = \"integration\";\nexport const AGENT_BACKGROUND_PROCESSOR_ROUTE = \"route\";\nexport const AGENT_BACKGROUND_PROCESSOR_ROUTE_FIELD =\n \"__agentNativeProcessorRoute\";\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\nfunction isNetlifyHostedRuntimeForDispatch(): boolean {\n if (process.env.NETLIFY_LOCAL === \"true\") return false;\n if (process.env.NETLIFY === \"false\") return false;\n if (process.env.NETLIFY && process.env.NETLIFY !== \"false\") return true;\n // NETLIFY is a build-only read-only variable. In deployed Functions Netlify\n // documents URL, SITE_NAME, and SITE_ID as the runtime read-only variables;\n // SITE_ID is the unambiguous host marker. Lambda compatibility mode also\n // exposes AWS runtime variables, so keep the function-name fallback for older\n // deploys. Without either check a modern Netlify Function silently selects the\n // portable framework route even though the emitted background function exists.\n if (process.env.SITE_ID) return true; // guard:allow-env-credential -- Netlify's read-only public site identifier is a runtime host marker, not a user credential.\n // Non-Netlify AWS falls back inline if the /.netlify/functions dispatch\n // fast-fails.\n return Boolean(process.env.AWS_LAMBDA_FUNCTION_NAME);\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 (isNetlifyHostedRuntimeForDispatch()) {\n return (\n resolveWorkspaceBackgroundFunctionUrlPath() ??\n AGENT_BACKGROUND_FUNCTION_URL_PATH\n );\n }\n return AGENT_CHAT_PROCESS_RUN_PATH;\n}\n\nexport function resolveDurableBackgroundDispatchPath(\n fallbackPath: string,\n): string {\n if (isNetlifyHostedRuntimeForDispatch()) {\n return (\n resolveWorkspaceBackgroundFunctionUrlPath() ??\n AGENT_BACKGROUND_FUNCTION_URL_PATH\n );\n }\n return fallbackPath;\n}\n\nexport function dispatchPathTargetsNetlifyBackgroundFunction(\n dispatchPath: string,\n): boolean {\n return dispatchPath.startsWith(\"/.netlify/functions/\");\n}\n\n/**\n * Env flag for durable background runs. DEFAULT-OFF (opt-in): unset means\n * disabled; an app opts IN with an explicit truthy value (`true`/`1`/`yes`/`on`).\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 (process.env.NETLIFY_LOCAL === \"true\") return false;\n if (process.env.NETLIFY === \"false\") return false;\n if (process.env.SITE_ID) return true; // guard:allow-env-credential -- Netlify's read-only public site identifier is a runtime host marker, not a user credential.\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\nexport function backgroundRunMarkerExpectsBackgroundRuntime(\n marker: unknown,\n): boolean {\n return (\n typeof marker === \"object\" &&\n marker !== null &&\n (marker as { backgroundFunctionRuntimeExpected?: unknown })\n .backgroundFunctionRuntimeExpected === true\n );\n}\n\nexport function shouldUseBackgroundFunctionTimeoutForWorker(\n _marker: unknown,\n): boolean {\n // The dispatch marker says which URL the foreground targeted, not where the\n // request actually landed. Only the worker runtime proof can safely lift the\n // hosted 40s clamp to the 15-minute background-function budget.\n return isInBackgroundFunctionRuntime();\n}\n\nexport function backgroundRuntimeDiagnosticDetail(marker: unknown): string {\n return [\n `markerExpected=${backgroundRunMarkerExpectsBackgroundRuntime(marker)}`,\n `runtimeDetected=${isInBackgroundFunctionRuntime()}`,\n `globalMarker=${(globalThis as Record<string, unknown>).__AGENT_NATIVE_BACKGROUND_RUNTIME__ === true}`,\n `lambdaNameEndsBackground=${typeof process.env.AWS_LAMBDA_FUNCTION_NAME === \"string\" && process.env.AWS_LAMBDA_FUNCTION_NAME.toLowerCase().endsWith(\"-background\")}`,\n `forceEnv=${typeof process.env.AGENT_CHAT_FORCE_BACKGROUND_RUNTIME === \"string\" && process.env.AGENT_CHAT_FORCE_BACKGROUND_RUNTIME.trim().length > 0}`,\n ].join(\" \");\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\nfunction isFlagExplicitlyDisabled(): boolean {\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 === \"0\" ||\n normalized === \"false\" ||\n normalized === \"no\" ||\n normalized === \"off\"\n );\n}\n\n/**\n * The single gate. True when the env flag is explicitly enabled, or a workspace\n * app opted in and has a per-app background-function target, AND the runtime is\n * hosted AND A2A_SECRET is configured. False otherwise — and false means the\n * current synchronous behavior is used unchanged. Single-template Netlify app\n * opt-ins deliberately require the env flag too because that flag controls\n * whether the `server-agent-background` function exists in the deploy output.\n */\nexport function isAgentChatDurableBackgroundEnabled(options?: {\n appOptIn?: boolean;\n}): boolean {\n // An app-level opt-out must win over a stale deploy-wide env flag. Netlify\n // environment variables can outlive the source config that originally set\n // them; allowing that flag to re-enable a worker an app explicitly disabled\n // recreates the missing-background-function failure this gate is meant to\n // prevent.\n if (options?.appOptIn === false) return false;\n const envOptIn = isFlagEnabled();\n const workspaceAppOptIn =\n options?.appOptIn === true &&\n !isFlagExplicitlyDisabled() &&\n resolveWorkspaceBackgroundFunctionUrlPath() !== null;\n return (\n (envOptIn || workspaceAppOptIn) &&\n isHostedRuntimeForDurableBackground() &&\n hasConfiguredA2ASecret()\n );\n}\n\n/**\n * Env flag for the FOREGROUND server-driven self-chain. DEFAULT-OFF: a hosted\n * app must explicitly opt in with a truthy value (`true`/`1`/`yes`/`on`). A\n * regular Netlify function has a fixed 60-second wall, and a self-dispatched\n * successor can otherwise be killed before it persists its next continuation.\n * Keep this separate from `AGENT_CHAT_DURABLE_BACKGROUND` so the experimental\n * regular-function chain can be enabled independently after its deployment is\n * proven safe.\n */\nexport const AGENT_CHAT_FOREGROUND_SELF_CHAIN_ENV =\n \"AGENT_CHAT_FOREGROUND_SELF_CHAIN\";\n\nfunction isForegroundSelfChainExplicitlyEnabled(): 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_FOREGROUND_SELF_CHAIN_ENV.\n const raw = process.env.AGENT_CHAT_FOREGROUND_SELF_CHAIN;\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 * Gate for the foreground self-chain: a normal (non-durable-background)\n * agent-chat turn that hits its soft-timeout chunk boundary continues via a\n * server-side self-dispatch on the REGULAR function (not a Netlify\n * `-background` function) instead of depending on the client to re-POST\n * `auto_continue`. Composes exactly like `isAgentChatDurableBackgroundEnabled`:\n * true only when the env flag is explicitly truthy, the runtime is hosted, and\n * `A2A_SECRET` is configured (the HMAC handoff authenticates the dispatch).\n * False means the existing client-driven `auto_continue` re-POST path is used.\n *\n * Deliberately independent of `isAgentChatDurableBackgroundEnabled`: an app can\n * use this narrower capability without opting into the full 15-min\n * background-function worker path, and the two gates never need to agree.\n * When BOTH would be true for a given run, the durable-background dispatch\n * decision in `production-agent.ts` is evaluated first and takes precedence —\n * a run already dispatched to the durable background worker chains via the\n * existing `isBackgroundWorker` path, not this one.\n */\nexport function isAgentChatForegroundSelfChainEnabled(): boolean {\n return (\n isForegroundSelfChainExplicitlyEnabled() &&\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 → require `isTrustedLocalRuntime({ loopback })` (see\n * auth-policy.ts): refuse (503) unless `A2A_ALLOW_UNSIGNED_INTERNAL=1` is\n * set. This function has no h3 `event` of its own, so callers that CAN\n * see the inbound socket peer (the route handler, which has the event)\n * should compute `loopback` from it and pass it through; callers that\n * can't determine the peer address should omit it (defaults to `false`\n * — never trust unsigned dispatch without an explicit opt-in).\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 loopback: boolean = false,\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 (!isTrustedLocalRuntime({ loopback })) {\n // Callers that can see the h3 `event` (the route handler) pass the real\n // loopback signal; callers without one default to non-loopback. Unsigned\n // dispatch is still allowed via A2A_ALLOW_UNSIGNED_INTERNAL=1 for trusted\n // local/dev setups; see auth-policy.ts `isTrustedLocalRuntime`.\n return {\n ok: false,\n status: 503,\n error:\n \"Agent chat background processor not configured — set A2A_SECRET on this deployment (or A2A_ALLOW_UNSIGNED_INTERNAL=1 for trusted local dev).\",\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"]}