@agent-native/core 0.84.13 → 0.84.16

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 (96) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +25 -0
  3. package/corpus/core/package.json +2 -2
  4. package/corpus/core/src/agent/durable-background.ts +18 -5
  5. package/corpus/core/src/agent/engine/builder-engine.ts +7 -3
  6. package/corpus/core/src/agent/production-agent.ts +48 -7
  7. package/corpus/core/src/agent/run-manager.ts +39 -2
  8. package/corpus/core/src/agent/run-store.ts +55 -11
  9. package/corpus/core/src/client/AssistantChat.tsx +30 -4
  10. package/corpus/core/src/client/MultiTabAssistantChat.tsx +11 -3
  11. package/corpus/core/src/client/agent-chat-adapter.ts +21 -3
  12. package/corpus/core/src/client/agent-chat.ts +18 -3
  13. package/corpus/core/src/client/blocks/library/diagram.tsx +3 -3
  14. package/corpus/core/src/client/chat/message-components.tsx +17 -0
  15. package/corpus/core/src/client/use-agent-engine-configured.ts +7 -4
  16. package/corpus/core/src/db/client.ts +10 -1
  17. package/corpus/core/src/observability/traces.ts +17 -3
  18. package/corpus/core/src/server/agent-chat-plugin.ts +80 -44
  19. package/corpus/core/src/styles/blocks.css +44 -1
  20. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +18 -24
  21. package/corpus/templates/analytics/changelog/2026-07-01-dashboard-charts-no-longer-show-future-dates-when-clients-se.md +6 -0
  22. package/corpus/templates/analytics/changelog/2026-07-01-dashboard-filters-take-less-space-and-keep-view-actions-tucked.md +6 -0
  23. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +34 -9
  24. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +6 -2
  25. package/corpus/templates/analytics/server/lib/session-replay.ts +109 -50
  26. package/corpus/templates/analytics/server/plugins/db.ts +21 -0
  27. package/corpus/templates/clips/changelog/2026-07-01-chrome-extension-recordings-stream-uploads-while-recording.md +6 -0
  28. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  29. package/corpus/templates/clips/chrome-extension/src/background.ts +8 -0
  30. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +61 -16
  31. package/corpus/templates/design/app/pages/DesignEditor.tsx +176 -13
  32. package/corpus/templates/design/changelog/2026-07-01-exporting-a-design-as-png-or-svg-now-downloads-just-the-sele.md +6 -0
  33. package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +44 -1
  34. package/corpus/templates/plan/changelog/2026-07-01-visual-plan-and-recap-diagram-labels-now-wrap-inside-their-b.md +6 -0
  35. package/dist/agent/durable-background.d.ts +1 -0
  36. package/dist/agent/durable-background.d.ts.map +1 -1
  37. package/dist/agent/durable-background.js +17 -3
  38. package/dist/agent/durable-background.js.map +1 -1
  39. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  40. package/dist/agent/engine/builder-engine.js +7 -1
  41. package/dist/agent/engine/builder-engine.js.map +1 -1
  42. package/dist/agent/production-agent.d.ts.map +1 -1
  43. package/dist/agent/production-agent.js +32 -7
  44. package/dist/agent/production-agent.js.map +1 -1
  45. package/dist/agent/run-manager.d.ts +2 -0
  46. package/dist/agent/run-manager.d.ts.map +1 -1
  47. package/dist/agent/run-manager.js +28 -3
  48. package/dist/agent/run-manager.js.map +1 -1
  49. package/dist/agent/run-store.d.ts +9 -0
  50. package/dist/agent/run-store.d.ts.map +1 -1
  51. package/dist/agent/run-store.js +47 -11
  52. package/dist/agent/run-store.js.map +1 -1
  53. package/dist/client/AssistantChat.d.ts +8 -2
  54. package/dist/client/AssistantChat.d.ts.map +1 -1
  55. package/dist/client/AssistantChat.js +18 -2
  56. package/dist/client/AssistantChat.js.map +1 -1
  57. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  58. package/dist/client/MultiTabAssistantChat.js +7 -3
  59. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  60. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  61. package/dist/client/agent-chat-adapter.js +19 -3
  62. package/dist/client/agent-chat-adapter.js.map +1 -1
  63. package/dist/client/agent-chat.d.ts +7 -0
  64. package/dist/client/agent-chat.d.ts.map +1 -1
  65. package/dist/client/agent-chat.js +5 -1
  66. package/dist/client/agent-chat.js.map +1 -1
  67. package/dist/client/blocks/library/diagram.js +3 -3
  68. package/dist/client/blocks/library/diagram.js.map +1 -1
  69. package/dist/client/chat/message-components.d.ts +3 -1
  70. package/dist/client/chat/message-components.d.ts.map +1 -1
  71. package/dist/client/chat/message-components.js +16 -2
  72. package/dist/client/chat/message-components.js.map +1 -1
  73. package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
  74. package/dist/client/use-agent-engine-configured.js +7 -1
  75. package/dist/client/use-agent-engine-configured.js.map +1 -1
  76. package/dist/collab/routes.d.ts +2 -2
  77. package/dist/db/client.d.ts +6 -0
  78. package/dist/db/client.d.ts.map +1 -1
  79. package/dist/db/client.js +8 -1
  80. package/dist/db/client.js.map +1 -1
  81. package/dist/notifications/routes.d.ts +2 -2
  82. package/dist/observability/routes.d.ts +3 -3
  83. package/dist/observability/traces.d.ts +5 -0
  84. package/dist/observability/traces.d.ts.map +1 -1
  85. package/dist/observability/traces.js +9 -3
  86. package/dist/observability/traces.js.map +1 -1
  87. package/dist/progress/routes.d.ts +1 -1
  88. package/dist/resources/handlers.d.ts +3 -3
  89. package/dist/server/agent-chat-plugin.d.ts +2 -1
  90. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  91. package/dist/server/agent-chat-plugin.js +64 -42
  92. package/dist/server/agent-chat-plugin.js.map +1 -1
  93. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  94. package/dist/server/transcribe-voice.d.ts +1 -1
  95. package/dist/styles/blocks.css +44 -1
  96. package/package.json +2 -2
@@ -815,6 +815,134 @@ function sanitizeHtml2CanvasClone(
815
815
  });
816
816
  }
817
817
 
818
+ /**
819
+ * Editor-chrome overlays that editor-chrome.bridge.ts appends inside the preview
820
+ * iframe (the selection outline + resize handles, hover highlight, marquee,
821
+ * spacing/measurement guides, and badges). They live in the iframe DOM, so image
822
+ * exports must strip them from the clone — otherwise a download captures the
823
+ * editor's selection outline instead of just the design. Keep this in sync with
824
+ * the data-agent-native-* markers set in editor-chrome.bridge.ts.
825
+ */
826
+ export const EDITOR_CHROME_OVERLAY_SELECTOR = [
827
+ "[data-agent-native-edit-overlay]",
828
+ "[data-agent-native-edit-handle]",
829
+ "[data-agent-native-edge-handle]",
830
+ "[data-agent-native-rotate-handle]",
831
+ "[data-agent-native-transform-badge]",
832
+ "[data-agent-native-spacing-badge]",
833
+ "[data-agent-native-spacing-overlay]",
834
+ "[data-agent-native-spacing-line]",
835
+ "[data-agent-native-spacing-region]",
836
+ "[data-agent-native-insertion-guide]",
837
+ "[data-agent-native-measurement-overlay]",
838
+ ].join(",");
839
+
840
+ /**
841
+ * Remove editor-chrome overlays from a cloned document/element before it is
842
+ * rasterized (PNG) or serialized (SVG) for export.
843
+ */
844
+ function removeEditorChromeOverlays(root: ParentNode): void {
845
+ root
846
+ .querySelectorAll(EDITOR_CHROME_OVERLAY_SELECTOR)
847
+ .forEach((element) => element.remove());
848
+ }
849
+
850
+ /**
851
+ * Resolve the document-space rect of the currently selected element inside the
852
+ * preview iframe so image exports (PNG/SVG) can crop to just that frame instead
853
+ * of the whole screen. Returns null — meaning "export the whole screen" — when
854
+ * there is no element selection, when the selection is the screen root
855
+ * (BODY/HTML, which is the whole screen anyway), or when the element can no
856
+ * longer be resolved in the live document.
857
+ */
858
+ function resolveExportCropRect(
859
+ doc: Document,
860
+ selected: ElementInfo | null | undefined,
861
+ ): { x: number; y: number; width: number; height: number } | null {
862
+ if (!selected || isScreenRootElementInfo(selected)) return null;
863
+ const view = doc.defaultView;
864
+ if (!view) return null;
865
+ let element: Element | null = null;
866
+ if (selected.sourceId) {
867
+ try {
868
+ element = doc.querySelector(
869
+ `[data-agent-native-node-id="${CSS.escape(selected.sourceId)}"]`,
870
+ );
871
+ } catch {
872
+ element = null;
873
+ }
874
+ }
875
+ if (!element && selected.selector) {
876
+ try {
877
+ element = doc.querySelector(selected.selector);
878
+ } catch {
879
+ element = null;
880
+ }
881
+ }
882
+ if (!element) return null;
883
+ const rect = element.getBoundingClientRect();
884
+ if (rect.width <= 0 || rect.height <= 0) return null;
885
+ // getBoundingClientRect is viewport-relative; add the iframe scroll offset so
886
+ // coordinates match the full-document render (which starts at the page top).
887
+ return {
888
+ x: rect.left + (view.scrollX ?? 0),
889
+ y: rect.top + (view.scrollY ?? 0),
890
+ width: rect.width,
891
+ height: rect.height,
892
+ };
893
+ }
894
+
895
+ /**
896
+ * Map a document-space rect onto pixel coordinates within a rendered canvas of
897
+ * the given size, clamped to stay inside the canvas. `scale` must match the
898
+ * scale passed to html2canvas. Returns null when the crop would be empty or
899
+ * lands fully outside the canvas, so callers can fall back to the full render.
900
+ */
901
+ export function computeExportCropBox(
902
+ sourceWidth: number,
903
+ sourceHeight: number,
904
+ rect: { x: number; y: number; width: number; height: number },
905
+ scale: number,
906
+ ): { sx: number; sy: number; sw: number; sh: number } | null {
907
+ const sx = Math.max(0, Math.round(rect.x * scale));
908
+ const sy = Math.max(0, Math.round(rect.y * scale));
909
+ const sw = Math.min(sourceWidth - sx, Math.round(rect.width * scale));
910
+ const sh = Math.min(sourceHeight - sy, Math.round(rect.height * scale));
911
+ if (sw <= 0 || sh <= 0) return null;
912
+ return { sx, sy, sw, sh };
913
+ }
914
+
915
+ /**
916
+ * Crop a rendered html2canvas canvas down to a document-space rect so image
917
+ * exports capture just the selected frame. Returns null when the crop is empty,
918
+ * so callers can fall back to the full render.
919
+ */
920
+ function cropCanvasToRect(
921
+ source: HTMLCanvasElement,
922
+ rect: { x: number; y: number; width: number; height: number },
923
+ scale: number,
924
+ ): HTMLCanvasElement | null {
925
+ const box = computeExportCropBox(source.width, source.height, rect, scale);
926
+ if (!box) return null;
927
+ const cropped = document.createElement("canvas");
928
+ cropped.width = box.sw;
929
+ cropped.height = box.sh;
930
+ const context = cropped.getContext("2d");
931
+ if (!context) return null;
932
+ context.drawImage(
933
+ source,
934
+ box.sx,
935
+ box.sy,
936
+ box.sw,
937
+ box.sh,
938
+ 0,
939
+ 0,
940
+ box.sw,
941
+ box.sh,
942
+ );
943
+ return cropped;
944
+ }
945
+
818
946
  function byteLength(value: string): number {
819
947
  if (typeof TextEncoder === "undefined") return value.length;
820
948
  return new TextEncoder().encode(value).length;
@@ -9068,6 +9196,11 @@ export default function DesignEditor() {
9068
9196
  title: shortLabel,
9069
9197
  context: contextLines.join("\n"),
9070
9198
  openSidebar: false,
9199
+ // Mirror the selection into chat context without stealing focus: this
9200
+ // effect re-fires on every selection change and on each get-design poll
9201
+ // during an agent run, and focusing the composer here would blur (and
9202
+ // tear down) an in-progress inline text edit on the canvas.
9203
+ focus: false,
9071
9204
  });
9072
9205
  }, [activeFile, design?.title, id, selectedCodeLayerNode, selectedElement]);
9073
9206
 
@@ -13174,26 +13307,38 @@ export default function DesignEditor() {
13174
13307
  doc.body?.scrollHeight ?? 0,
13175
13308
  iframe?.clientHeight ?? 0,
13176
13309
  );
13310
+ const exportScale = Math.max(
13311
+ 0.1,
13312
+ Math.min(
13313
+ 4,
13314
+ settings?.scale ?? Math.min(2, window.devicePixelRatio || 1),
13315
+ ),
13316
+ );
13317
+ // When an element is selected, crop the export to just that frame.
13318
+ const cropRect = resolveExportCropRect(doc, selectedElement);
13177
13319
  const canvas = await html2canvas(doc.documentElement, {
13178
13320
  width,
13179
13321
  height,
13180
13322
  windowWidth: width,
13181
13323
  windowHeight: height,
13182
- scale: Math.max(
13183
- 0.1,
13184
- Math.min(
13185
- 4,
13186
- settings?.scale ?? Math.min(2, window.devicePixelRatio || 1),
13187
- ),
13188
- ),
13324
+ scale: exportScale,
13189
13325
  useCORS: true,
13190
13326
  foreignObjectRendering: true,
13191
13327
  backgroundColor: null,
13192
- onclone: (clonedDocument) =>
13193
- sanitizeHtml2CanvasClone(doc, clonedDocument),
13328
+ onclone: (clonedDocument) => {
13329
+ // Sanitize colors first: it aligns source/clone elements by index,
13330
+ // so remove the editor-chrome overlays only afterwards.
13331
+ sanitizeHtml2CanvasClone(doc, clonedDocument);
13332
+ removeEditorChromeOverlays(clonedDocument);
13333
+ },
13194
13334
  });
13335
+ // Render the whole page first, then crop, so ancestor backgrounds show
13336
+ // through the selected frame exactly as they do on screen.
13337
+ const outputCanvas = cropRect
13338
+ ? (cropCanvasToRect(canvas, cropRect, exportScale) ?? canvas)
13339
+ : canvas;
13195
13340
  await new Promise<void>((resolve) => {
13196
- canvas.toBlob((blob) => {
13341
+ outputCanvas.toBlob((blob) => {
13197
13342
  try {
13198
13343
  if (!blob) {
13199
13344
  toast.error(t("designEditor.toasts.pngCreateError"));
@@ -13235,7 +13380,7 @@ export default function DesignEditor() {
13235
13380
  setPngExporting(false);
13236
13381
  }
13237
13382
  },
13238
- [fallbackExportName, t, triggerBlobDownload],
13383
+ [fallbackExportName, selectedElement, t, triggerBlobDownload],
13239
13384
  );
13240
13385
 
13241
13386
  const handleDownloadSvg = useCallback(
@@ -13297,6 +13442,9 @@ export default function DesignEditor() {
13297
13442
  clonedStylesheetLinks[index]?.replaceWith(style);
13298
13443
  });
13299
13444
  clone.querySelectorAll("script").forEach((node) => node.remove());
13445
+ // Strip the editor's selection outline / handles so the SVG shows only
13446
+ // the design, not the editor chrome.
13447
+ removeEditorChromeOverlays(clone);
13300
13448
  clone.style.width = `${width}px`;
13301
13449
  clone.style.minHeight = `${height}px`;
13302
13450
 
@@ -13315,8 +13463,17 @@ export default function DesignEditor() {
13315
13463
  .replace(/</g, "&lt;")
13316
13464
  .replace(/>/g, "&gt;") || t("designEditor.designExport");
13317
13465
  const exportScale = Math.max(0.1, Math.min(4, settings?.scale ?? 1));
13466
+ // When an element is selected, crop to just that frame by narrowing the
13467
+ // SVG viewBox to its document-space rect. The foreignObject still holds
13468
+ // the full document so layout and inherited styles stay intact; the
13469
+ // viewBox clips the visible region to the selection.
13470
+ const cropRect = resolveExportCropRect(doc, selectedElement);
13471
+ const viewX = cropRect?.x ?? 0;
13472
+ const viewY = cropRect?.y ?? 0;
13473
+ const viewWidth = cropRect?.width ?? width;
13474
+ const viewHeight = cropRect?.height ?? height;
13318
13475
  const svg = `<?xml version="1.0" encoding="UTF-8"?>
13319
- <svg xmlns="http://www.w3.org/2000/svg" width="${width * exportScale}" height="${height * exportScale}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${safeTitle}">
13476
+ <svg xmlns="http://www.w3.org/2000/svg" width="${viewWidth * exportScale}" height="${viewHeight * exportScale}" viewBox="${viewX} ${viewY} ${viewWidth} ${viewHeight}" role="img" aria-label="${safeTitle}">
13320
13477
  <title>${safeTitle}</title>
13321
13478
  <foreignObject width="${width}" height="${height}">
13322
13479
  ${serializedHtml}
@@ -13339,7 +13496,13 @@ ${serializedHtml}
13339
13496
  setSvgExporting(false);
13340
13497
  }
13341
13498
  },
13342
- [design?.title, fallbackExportName, t, triggerBlobDownload],
13499
+ [
13500
+ design?.title,
13501
+ fallbackExportName,
13502
+ selectedElement,
13503
+ t,
13504
+ triggerBlobDownload,
13505
+ ],
13343
13506
  );
13344
13507
 
13345
13508
  const handleInspectorExport = useCallback(
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-01
4
+ ---
5
+
6
+ Exporting a design as PNG or SVG now downloads just the selected frame instead of the entire screen.
@@ -405,8 +405,37 @@
405
405
  color: var(--wf-ink) !important;
406
406
  }
407
407
 
408
- .plan-diagram-frame :is(.diagram-node, .diagram-box, .diagram-card) {
408
+ .plan-diagram-frame
409
+ :is(
410
+ .diagram-panel,
411
+ .diagram-card,
412
+ .diagram-node,
413
+ .diagram-box,
414
+ [class*="card"],
415
+ [class*="panel"],
416
+ [class*="box"]
417
+ ) {
409
418
  max-width: 100%;
419
+ overflow-wrap: anywhere !important;
420
+ word-break: break-word;
421
+ white-space: normal !important;
422
+ }
423
+
424
+ .plan-diagram-frame
425
+ :is(
426
+ .diagram-panel,
427
+ .diagram-card,
428
+ .diagram-node,
429
+ .diagram-box,
430
+ [class*="card"],
431
+ [class*="panel"],
432
+ [class*="box"]
433
+ )
434
+ :is(h1, h2, h3, p, small, strong, span, li) {
435
+ max-width: 100%;
436
+ overflow-wrap: anywhere !important;
437
+ word-break: break-word;
438
+ white-space: normal !important;
410
439
  }
411
440
 
412
441
  /* Base padding so primitive text never touches the box edge, even when an author
@@ -472,6 +501,7 @@
472
501
  display: inline-flex;
473
502
  align-items: center;
474
503
  justify-content: center;
504
+ flex-wrap: wrap;
475
505
  /* A pill hugs its label. `fit-content` is a definite cross size, so a flex
476
506
  * column with the default `align-items: stretch` no longer blows the pill out
477
507
  * to full width (the number chips and short status labels stay pill-shaped).
@@ -484,6 +514,19 @@
484
514
  padding: 3px 11px;
485
515
  color: var(--wf-ink);
486
516
  background: var(--wf-paper);
517
+ overflow-wrap: anywhere !important;
518
+ text-align: center;
519
+ white-space: normal !important;
520
+ word-break: break-word;
521
+ }
522
+
523
+ .plan-diagram-frame
524
+ :is(.diagram-pill, [class*="pill"], [class*="badge"], [class*="chip"])
525
+ > * {
526
+ min-width: 0;
527
+ max-width: 100%;
528
+ overflow-wrap: anywhere !important;
529
+ white-space: normal !important;
487
530
  }
488
531
 
489
532
  .plan-diagram-frame :is(.diagram-accent, .diagram-pill.accent) {
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-01
4
+ ---
5
+
6
+ Visual plan and recap diagram labels now wrap inside their boxes instead of spilling outside.
@@ -57,6 +57,7 @@ export declare const AGENT_BACKGROUND_FUNCTION_URL_PATH = "/.netlify/functions/s
57
57
  * to shadow because `/.netlify/*` is already excluded from the `server` catch-all.
58
58
  */
59
59
  export declare function resolveAgentChatProcessRunDispatchPath(): string;
60
+ export declare function dispatchPathTargetsNetlifyBackgroundFunction(dispatchPath: string): boolean;
60
61
  /**
61
62
  * Env flag for durable background runs. DEFAULT-OFF (opt-in): unset means
62
63
  * disabled; an app opts IN with an explicit truthy value (`true`/`1`/`yes`/`on`).
@@ -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;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;AAED,wBAAgB,2CAA2C,CACzD,MAAM,EAAE,OAAO,GACd,OAAO,CAOT;AAED,wBAAgB,2CAA2C,CACzD,MAAM,EAAE,OAAO,GACd,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,CAWV;AAED,uDAAuD;AACvD,MAAM,MAAM,qBAAqB,GAC7B;IACE,EAAE,EAAE,IAAI,CAAC;IACT,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAEN;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAahE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,qBAAqB,CAiDvB"}
1
+ {"version":3,"file":"durable-background.d.ts","sourceRoot":"","sources":["../../src/agent/durable-background.ts"],"names":[],"mappings":"AAgDA;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,2CACE,CAAC;AAE3C;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,4BAA4B,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,kCAAkC,gDAA0D,CAAC;AAiC1G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,sCAAsC,IAAI,MAAM,CAQ/D;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,MAAM,EAAE,OAAO,GACd,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,CAWV;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"}
@@ -87,6 +87,19 @@ function resolveWorkspaceBackgroundFunctionUrlPath() {
87
87
  return null;
88
88
  return `/.netlify/functions/${candidate}-agent-background`;
89
89
  }
90
+ function isNetlifyHostedRuntimeForDispatch() {
91
+ if (process.env.NETLIFY_LOCAL === "true")
92
+ return false;
93
+ if (process.env.NETLIFY === "false")
94
+ return false;
95
+ if (process.env.NETLIFY && process.env.NETLIFY !== "false")
96
+ return true;
97
+ // Netlify sets AWS Lambda runtime env on deployed Functions, but the build-time
98
+ // NETLIFY flag is not always present in the runtime isolate. Treat Lambda as
99
+ // Netlify here unless Netlify was explicitly disabled above; non-Netlify AWS
100
+ // falls back inline if the /.netlify/functions dispatch fast-fails.
101
+ return Boolean(process.env.AWS_LAMBDA_FUNCTION_NAME);
102
+ }
90
103
  /**
91
104
  * Resolve the path the foreground POST should self-dispatch the chat background
92
105
  * worker to.
@@ -119,14 +132,15 @@ function resolveWorkspaceBackgroundFunctionUrlPath() {
119
132
  * to shadow because `/.netlify/*` is already excluded from the `server` catch-all.
120
133
  */
121
134
  export function resolveAgentChatProcessRunDispatchPath() {
122
- if (process.env.NETLIFY &&
123
- process.env.NETLIFY !== "false" &&
124
- process.env.NETLIFY_LOCAL !== "true") {
135
+ if (isNetlifyHostedRuntimeForDispatch()) {
125
136
  return (resolveWorkspaceBackgroundFunctionUrlPath() ??
126
137
  AGENT_BACKGROUND_FUNCTION_URL_PATH);
127
138
  }
128
139
  return AGENT_CHAT_PROCESS_RUN_PATH;
129
140
  }
141
+ export function dispatchPathTargetsNetlifyBackgroundFunction(dispatchPath) {
142
+ return dispatchPath.startsWith("/.netlify/functions/");
143
+ }
130
144
  /**
131
145
  * Env flag for durable background runs. DEFAULT-OFF (opt-in): unset means
132
146
  * disabled; an app opts IN with an explicit truthy value (`true`/`1`/`yes`/`on`).
@@ -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 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-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 return (\n isInBackgroundFunctionRuntime() ||\n backgroundRunMarkerExpectsBackgroundRuntime(marker)\n );\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 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/** 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"]}
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 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\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 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 return (\n isInBackgroundFunctionRuntime() ||\n backgroundRunMarkerExpectsBackgroundRuntime(marker)\n );\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 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/** 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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"builder-engine.d.ts","sourceRoot":"","sources":["../../../src/agent/engine/builder-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAyBH,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAInB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,oBAAoB,EAAE,kBAMlC,CAAC;AAEF,eAAO,MAAM,wBAAwB,sOAAuC,CAAC;AAkB7E,eAAO,MAAM,qBAAqB,yCAAoC,CAAC;AAotBvE,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACpC,WAAW,CAEb"}
1
+ {"version":3,"file":"builder-engine.d.ts","sourceRoot":"","sources":["../../../src/agent/engine/builder-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAyBH,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAInB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,oBAAoB,EAAE,kBAMlC,CAAC;AAEF,eAAO,MAAM,wBAAwB,sOAAuC,CAAC;AAkB7E,eAAO,MAAM,qBAAqB,yCAAoC,CAAC;AAwtBvE,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACpC,WAAW,CAEb"}
@@ -143,7 +143,13 @@ class BuilderEngine {
143
143
  // content block so the entire conversation prefix is cached.
144
144
  let cachedMessages = messages;
145
145
  if (cacheEnabled && messages.length > 0) {
146
- const lastUserIdx = [...messages].findLastIndex((m) => m.role === "user");
146
+ let lastUserIdx = -1;
147
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
148
+ if (messages[i].role === "user") {
149
+ lastUserIdx = i;
150
+ break;
151
+ }
152
+ }
147
153
  if (lastUserIdx >= 0) {
148
154
  cachedMessages = [...messages];
149
155
  const lastMsg = { ...cachedMessages[lastUserIdx] };