@agent-native/core 0.124.3 → 0.124.4

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 (254) hide show
  1. package/README.md +1 -1
  2. package/corpus/README.md +2 -2
  3. package/corpus/core/CHANGELOG.md +10 -0
  4. package/corpus/core/docs/content/cloneable-saas.mdx +9 -9
  5. package/corpus/core/docs/content/creating-templates.mdx +2 -2
  6. package/corpus/core/docs/content/deployment.mdx +23 -0
  7. package/corpus/core/docs/content/faq.mdx +4 -4
  8. package/corpus/core/docs/content/key-concepts.mdx +1 -1
  9. package/corpus/core/docs/content/multi-app-workspace.mdx +1 -1
  10. package/corpus/core/docs/content/syncing-template-changes.mdx +1 -1
  11. package/corpus/core/docs/content/template-analytics-developers.mdx +2 -2
  12. package/corpus/core/docs/content/template-analytics.mdx +2 -2
  13. package/corpus/core/docs/content/template-assets-developers.mdx +3 -3
  14. package/corpus/core/docs/content/template-assets.mdx +2 -2
  15. package/corpus/core/docs/content/template-brain-developers.mdx +2 -2
  16. package/corpus/core/docs/content/template-brain.mdx +1 -1
  17. package/corpus/core/docs/content/template-calendar-developers.mdx +1 -1
  18. package/corpus/core/docs/content/template-calendar.mdx +1 -1
  19. package/corpus/core/docs/content/template-chat.mdx +1 -1
  20. package/corpus/core/docs/content/template-clips-developers.mdx +4 -4
  21. package/corpus/core/docs/content/template-clips.mdx +2 -2
  22. package/corpus/core/docs/content/template-content-developers.mdx +2 -2
  23. package/corpus/core/docs/content/template-content.mdx +1 -1
  24. package/corpus/core/docs/content/template-design-brand-and-figma.mdx +1 -1
  25. package/corpus/core/docs/content/template-design-developers.mdx +2 -2
  26. package/corpus/core/docs/content/template-design.mdx +2 -2
  27. package/corpus/core/docs/content/template-dispatch-developers.mdx +5 -5
  28. package/corpus/core/docs/content/template-forms-developers.mdx +1 -1
  29. package/corpus/core/docs/content/template-forms.mdx +1 -1
  30. package/corpus/core/docs/content/template-mail-developers.mdx +2 -2
  31. package/corpus/core/docs/content/template-mail.mdx +2 -2
  32. package/corpus/core/docs/content/template-plan-developers.mdx +3 -3
  33. package/corpus/core/docs/content/template-plan.mdx +3 -3
  34. package/corpus/core/docs/content/template-slides-developers.mdx +4 -4
  35. package/corpus/core/docs/content/template-slides.mdx +1 -1
  36. package/corpus/core/docs/content/what-is-agent-native.mdx +5 -5
  37. package/corpus/core/package.json +1 -1
  38. package/corpus/core/src/a2a/auth-policy.ts +1 -1
  39. package/corpus/core/src/agent/production-agent.ts +14 -1
  40. package/corpus/core/src/agent/run-manager.ts +6 -13
  41. package/corpus/core/src/cli/index.ts +11 -1
  42. package/corpus/core/src/client/route-chunk-recovery.ts +18 -7
  43. package/corpus/core/src/db/client.ts +7 -6
  44. package/corpus/core/src/db/migrations.ts +2 -1
  45. package/corpus/core/src/deploy/build.ts +290 -4
  46. package/corpus/core/src/deploy/nitro-preset.ts +50 -0
  47. package/corpus/core/src/integrations/pending-tasks-retry-job.ts +2 -1
  48. package/corpus/core/src/scripts/call-agent.ts +4 -2
  49. package/corpus/core/src/server/agent-discovery.ts +1 -0
  50. package/corpus/core/src/server/agent-run-context.ts +13 -3
  51. package/corpus/core/src/server/request-context.ts +2 -0
  52. package/corpus/core/src/shared/cloudflare-globals.d.ts +2 -1
  53. package/corpus/core/src/shared/runtime.ts +2 -0
  54. package/corpus/core/src/templates/default/.agents/skills/secrets/SKILL.md +29 -0
  55. package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +6 -6
  56. package/corpus/core/src/templates/default/app/i18n/de-DE.ts +6 -6
  57. package/corpus/core/src/templates/default/app/i18n/en-US.ts +6 -6
  58. package/corpus/core/src/templates/default/app/i18n/es-ES.ts +6 -6
  59. package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +6 -6
  60. package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +6 -6
  61. package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +6 -6
  62. package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +6 -6
  63. package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +6 -6
  64. package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +6 -6
  65. package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +6 -6
  66. package/corpus/core/src/templates/headless/.agents/skills/secrets/SKILL.md +29 -0
  67. package/corpus/core/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -0
  68. package/corpus/core/src/vite/client.ts +20 -1
  69. package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +9 -2
  70. package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +3 -0
  71. package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +1 -1
  72. package/corpus/templates/analytics/app/global.css +10 -9
  73. package/corpus/templates/analytics/app/hooks/use-chart-tooltip-portal.ts +59 -18
  74. package/corpus/templates/analytics/app/hooks/use-dashboard-revisions.ts +14 -2
  75. package/corpus/templates/analytics/app/i18n-data.ts +102 -0
  76. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +232 -2
  77. package/corpus/templates/analytics/changelog/2026-07-27-chart-tooltips-stay-beside-the-cursor-when-they-extend-beyon.md +6 -0
  78. package/corpus/templates/analytics/changelog/2026-07-27-chat-no-longer-claims-a-data-source-is-disconnected-when-it-.md +6 -0
  79. package/corpus/templates/analytics/changelog/2026-07-27-dashboard-changes-can-be-undone-with-cmd-z-and-actions-menu.md +6 -0
  80. package/corpus/templates/analytics/changelog/2026-07-27-dashboard-redo-with-cmd-shift-z-and-actions-menu.md +6 -0
  81. package/corpus/templates/analytics/changelog/2026-07-27-dashboard-skeletons-are-subtler-and-smoother.md +6 -0
  82. package/corpus/templates/analytics/server/lib/dashboards-store.ts +61 -20
  83. package/corpus/templates/analytics/server/lib/real-data-actions.ts +1 -0
  84. package/corpus/templates/analytics/server/plugins/agent-chat.ts +7 -3
  85. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +7 -32
  86. package/corpus/templates/calendar/changelog/2026-07-27-calendar-s-sidebar-now-uses-a-cleaner-layout-with-fewer-util.md +6 -0
  87. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +14 -6
  88. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +32 -37
  89. package/corpus/templates/content/changelog/2026-07-27-content-sidebar-page-lists-now-load-with-quiet-skeletons-and.md +6 -0
  90. package/corpus/templates/design/app/components/layout/Sidebar.tsx +16 -18
  91. package/corpus/templates/design/changelog/2026-07-27-collapsed-sidebar-keeps-the-agent-native-logo.md +6 -0
  92. package/corpus/templates/dispatch/AGENTS.md +7 -2
  93. package/corpus/templates/dispatch/actions/view-screen.ts +3 -2
  94. package/corpus/templates/dispatch/changelog/2026-07-17-curated-template-remixes.md +1 -1
  95. package/corpus/templates/dispatch/changelog/2026-07-27-creating-an-app-from-a-curated-template-now-uses-clearer-mor.md +6 -0
  96. package/corpus/templates/dispatch/changelog/2026-07-27-thread-debug-accepts-request-run-ids.md +6 -0
  97. package/corpus/templates/slides/actions/create-design-system.ts +10 -2
  98. package/corpus/templates/slides/actions/import-file.ts +173 -1
  99. package/corpus/templates/slides/actions/list-slide-comments.ts +16 -1
  100. package/corpus/templates/slides/actions/update-design-system.ts +13 -2
  101. package/corpus/templates/slides/app/components/editor/AssetLibraryPanel.tsx +35 -12
  102. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +36 -0
  103. package/corpus/templates/slides/app/i18n/en-US.ts +3 -0
  104. package/corpus/templates/slides/app/lib/dynamic-import.ts +63 -0
  105. package/corpus/templates/slides/app/lib/export-pdf-client.ts +3 -2
  106. package/corpus/templates/slides/app/lib/export-pptx-client.ts +5 -2
  107. package/corpus/templates/slides/app/pages/DesignSystems.tsx +9 -1
  108. package/corpus/templates/slides/server/db/schema.ts +11 -0
  109. package/corpus/templates/slides/server/handlers/assets.ts +64 -19
  110. package/corpus/templates/slides/server/handlers/import/html-converter.ts +38 -0
  111. package/corpus/templates/slides/server/plugins/db.ts +20 -0
  112. package/corpus/templates/slides/shared/design-system-validation.ts +62 -0
  113. package/dist/a2a/auth-policy.js +1 -1
  114. package/dist/a2a/auth-policy.js.map +1 -1
  115. package/dist/agent/production-agent.d.ts +1 -0
  116. package/dist/agent/production-agent.d.ts.map +1 -1
  117. package/dist/agent/production-agent.js +12 -1
  118. package/dist/agent/production-agent.js.map +1 -1
  119. package/dist/agent/run-manager.d.ts +2 -0
  120. package/dist/agent/run-manager.d.ts.map +1 -1
  121. package/dist/agent/run-manager.js +4 -10
  122. package/dist/agent/run-manager.js.map +1 -1
  123. package/dist/cli/index.js +8 -1
  124. package/dist/cli/index.js.map +1 -1
  125. package/dist/client/route-chunk-recovery.d.ts.map +1 -1
  126. package/dist/client/route-chunk-recovery.js +16 -7
  127. package/dist/client/route-chunk-recovery.js.map +1 -1
  128. package/dist/collab/awareness.d.ts +2 -2
  129. package/dist/collab/awareness.d.ts.map +1 -1
  130. package/dist/collab/routes.d.ts +1 -1
  131. package/dist/collab/struct-routes.d.ts +1 -1
  132. package/dist/db/client.d.ts.map +1 -1
  133. package/dist/db/client.js +4 -2
  134. package/dist/db/client.js.map +1 -1
  135. package/dist/db/migrations.d.ts.map +1 -1
  136. package/dist/db/migrations.js +2 -1
  137. package/dist/db/migrations.js.map +1 -1
  138. package/dist/deploy/build.d.ts +25 -0
  139. package/dist/deploy/build.d.ts.map +1 -1
  140. package/dist/deploy/build.js +227 -4
  141. package/dist/deploy/build.js.map +1 -1
  142. package/dist/deploy/nitro-preset.d.ts +8 -0
  143. package/dist/deploy/nitro-preset.d.ts.map +1 -0
  144. package/dist/deploy/nitro-preset.js +36 -0
  145. package/dist/deploy/nitro-preset.js.map +1 -0
  146. package/dist/integrations/pending-tasks-retry-job.d.ts.map +1 -1
  147. package/dist/integrations/pending-tasks-retry-job.js +2 -1
  148. package/dist/integrations/pending-tasks-retry-job.js.map +1 -1
  149. package/dist/notifications/routes.d.ts +3 -3
  150. package/dist/observability/routes.d.ts +3 -3
  151. package/dist/progress/routes.d.ts +1 -1
  152. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  153. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  154. package/dist/scripts/call-agent.d.ts.map +1 -1
  155. package/dist/scripts/call-agent.js +4 -2
  156. package/dist/scripts/call-agent.js.map +1 -1
  157. package/dist/secrets/routes.d.ts +3 -3
  158. package/dist/server/agent-discovery.d.ts.map +1 -1
  159. package/dist/server/agent-discovery.js +1 -0
  160. package/dist/server/agent-discovery.js.map +1 -1
  161. package/dist/server/agent-run-context.d.ts.map +1 -1
  162. package/dist/server/agent-run-context.js +10 -3
  163. package/dist/server/agent-run-context.js.map +1 -1
  164. package/dist/server/request-context.d.ts +2 -0
  165. package/dist/server/request-context.d.ts.map +1 -1
  166. package/dist/server/request-context.js.map +1 -1
  167. package/dist/server/transcribe-voice.d.ts +1 -1
  168. package/dist/shared/runtime.d.ts.map +1 -1
  169. package/dist/shared/runtime.js +2 -0
  170. package/dist/shared/runtime.js.map +1 -1
  171. package/dist/templates/default/.agents/skills/secrets/SKILL.md +29 -0
  172. package/dist/templates/default/app/i18n/ar-SA.ts +6 -6
  173. package/dist/templates/default/app/i18n/de-DE.ts +6 -6
  174. package/dist/templates/default/app/i18n/en-US.ts +6 -6
  175. package/dist/templates/default/app/i18n/es-ES.ts +6 -6
  176. package/dist/templates/default/app/i18n/fr-FR.ts +6 -6
  177. package/dist/templates/default/app/i18n/hi-IN.ts +6 -6
  178. package/dist/templates/default/app/i18n/ja-JP.ts +6 -6
  179. package/dist/templates/default/app/i18n/ko-KR.ts +6 -6
  180. package/dist/templates/default/app/i18n/pt-BR.ts +6 -6
  181. package/dist/templates/default/app/i18n/zh-CN.ts +6 -6
  182. package/dist/templates/default/app/i18n/zh-TW.ts +6 -6
  183. package/dist/templates/headless/.agents/skills/secrets/SKILL.md +29 -0
  184. package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -0
  185. package/dist/vite/client.d.ts +1 -1
  186. package/dist/vite/client.d.ts.map +1 -1
  187. package/dist/vite/client.js +16 -0
  188. package/dist/vite/client.js.map +1 -1
  189. package/docs/content/cloneable-saas.mdx +9 -9
  190. package/docs/content/creating-templates.mdx +2 -2
  191. package/docs/content/deployment.mdx +23 -0
  192. package/docs/content/faq.mdx +4 -4
  193. package/docs/content/key-concepts.mdx +1 -1
  194. package/docs/content/multi-app-workspace.mdx +1 -1
  195. package/docs/content/syncing-template-changes.mdx +1 -1
  196. package/docs/content/template-analytics-developers.mdx +2 -2
  197. package/docs/content/template-analytics.mdx +2 -2
  198. package/docs/content/template-assets-developers.mdx +3 -3
  199. package/docs/content/template-assets.mdx +2 -2
  200. package/docs/content/template-brain-developers.mdx +2 -2
  201. package/docs/content/template-brain.mdx +1 -1
  202. package/docs/content/template-calendar-developers.mdx +1 -1
  203. package/docs/content/template-calendar.mdx +1 -1
  204. package/docs/content/template-chat.mdx +1 -1
  205. package/docs/content/template-clips-developers.mdx +4 -4
  206. package/docs/content/template-clips.mdx +2 -2
  207. package/docs/content/template-content-developers.mdx +2 -2
  208. package/docs/content/template-content.mdx +1 -1
  209. package/docs/content/template-design-brand-and-figma.mdx +1 -1
  210. package/docs/content/template-design-developers.mdx +2 -2
  211. package/docs/content/template-design.mdx +2 -2
  212. package/docs/content/template-dispatch-developers.mdx +5 -5
  213. package/docs/content/template-forms-developers.mdx +1 -1
  214. package/docs/content/template-forms.mdx +1 -1
  215. package/docs/content/template-mail-developers.mdx +2 -2
  216. package/docs/content/template-mail.mdx +2 -2
  217. package/docs/content/template-plan-developers.mdx +3 -3
  218. package/docs/content/template-plan.mdx +3 -3
  219. package/docs/content/template-slides-developers.mdx +4 -4
  220. package/docs/content/template-slides.mdx +1 -1
  221. package/docs/content/what-is-agent-native.mdx +5 -5
  222. package/package.json +1 -1
  223. package/src/a2a/auth-policy.ts +1 -1
  224. package/src/agent/production-agent.ts +14 -1
  225. package/src/agent/run-manager.ts +6 -13
  226. package/src/cli/index.ts +11 -1
  227. package/src/client/route-chunk-recovery.ts +18 -7
  228. package/src/db/client.ts +7 -6
  229. package/src/db/migrations.ts +2 -1
  230. package/src/deploy/build.ts +290 -4
  231. package/src/deploy/nitro-preset.ts +50 -0
  232. package/src/integrations/pending-tasks-retry-job.ts +2 -1
  233. package/src/scripts/call-agent.ts +4 -2
  234. package/src/server/agent-discovery.ts +1 -0
  235. package/src/server/agent-run-context.ts +13 -3
  236. package/src/server/request-context.ts +2 -0
  237. package/src/shared/cloudflare-globals.d.ts +2 -1
  238. package/src/shared/runtime.ts +2 -0
  239. package/src/templates/default/.agents/skills/secrets/SKILL.md +29 -0
  240. package/src/templates/default/app/i18n/ar-SA.ts +6 -6
  241. package/src/templates/default/app/i18n/de-DE.ts +6 -6
  242. package/src/templates/default/app/i18n/en-US.ts +6 -6
  243. package/src/templates/default/app/i18n/es-ES.ts +6 -6
  244. package/src/templates/default/app/i18n/fr-FR.ts +6 -6
  245. package/src/templates/default/app/i18n/hi-IN.ts +6 -6
  246. package/src/templates/default/app/i18n/ja-JP.ts +6 -6
  247. package/src/templates/default/app/i18n/ko-KR.ts +6 -6
  248. package/src/templates/default/app/i18n/pt-BR.ts +6 -6
  249. package/src/templates/default/app/i18n/zh-CN.ts +6 -6
  250. package/src/templates/default/app/i18n/zh-TW.ts +6 -6
  251. package/src/templates/headless/.agents/skills/secrets/SKILL.md +29 -0
  252. package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -0
  253. package/src/vite/client.ts +20 -1
  254. /package/corpus/templates/slides/server/routes/api/assets/{[filename].delete.ts → [id].delete.ts} +0 -0
@@ -3,8 +3,10 @@ import { useT } from "@agent-native/core/client/i18n";
3
3
  import { IconUpload, IconTrash, IconLoader2, IconX } from "@tabler/icons-react";
4
4
  import { useState, useEffect, useCallback, useRef } from "react";
5
5
  import { createPortal } from "react-dom";
6
+ import { toast } from "sonner";
6
7
 
7
8
  interface Asset {
9
+ id: string;
8
10
  url: string;
9
11
  filename: string;
10
12
  size: number;
@@ -67,33 +69,54 @@ export default function AssetLibraryPanel({
67
69
  const files = e.target.files;
68
70
  if (!files || files.length === 0) return;
69
71
  setUploading(true);
72
+ const failures: string[] = [];
70
73
  try {
71
74
  for (const file of Array.from(files)) {
72
75
  const form = new FormData();
73
76
  form.append("file", file);
74
- await fetch(`${appBasePath()}/api/assets/upload`, {
75
- method: "POST",
76
- body: form,
77
- });
77
+ try {
78
+ const res = await fetch(`${appBasePath()}/api/assets/upload`, {
79
+ method: "POST",
80
+ body: form,
81
+ });
82
+ if (!res.ok) {
83
+ const body = await res.json().catch(() => null);
84
+ failures.push(
85
+ `${file.name}: ${body?.error || `HTTP ${res.status}`}`,
86
+ );
87
+ }
88
+ } catch {
89
+ failures.push(`${file.name}: upload failed`);
90
+ }
78
91
  }
79
92
  await fetchAssets();
80
- } catch {
93
+ if (failures.length > 0) {
94
+ toast.error(t("raw.assetUploadFailed"), {
95
+ description: failures.join("\n"),
96
+ });
97
+ }
81
98
  } finally {
82
99
  setUploading(false);
83
100
  e.target.value = "";
84
101
  }
85
102
  };
86
103
 
87
- const handleDelete = async (filename: string) => {
104
+ const handleDelete = async (id: string) => {
88
105
  try {
89
- await fetch(
90
- `${appBasePath()}/api/assets/${encodeURIComponent(filename)}`,
106
+ const res = await fetch(
107
+ `${appBasePath()}/api/assets/${encodeURIComponent(id)}`,
91
108
  {
92
109
  method: "DELETE",
93
110
  },
94
111
  );
95
- setAssets((prev) => prev.filter((a) => a.filename !== filename));
96
- } catch {}
112
+ if (!res.ok) {
113
+ toast.error(t("raw.assetDeleteFailed"));
114
+ return;
115
+ }
116
+ setAssets((prev) => prev.filter((a) => a.id !== id));
117
+ } catch {
118
+ toast.error(t("raw.assetDeleteFailed"));
119
+ }
97
120
  };
98
121
 
99
122
  const handleSelect = (url: string) => {
@@ -185,7 +208,7 @@ export default function AssetLibraryPanel({
185
208
  <div className="grid grid-cols-3 gap-2">
186
209
  {assets.map((asset) => (
187
210
  <div
188
- key={asset.filename}
211
+ key={asset.id}
189
212
  className="group relative aspect-square rounded-md overflow-hidden border border-border bg-muted"
190
213
  >
191
214
  <button
@@ -204,7 +227,7 @@ export default function AssetLibraryPanel({
204
227
  <button
205
228
  onClick={(e) => {
206
229
  e.stopPropagation();
207
- handleDelete(asset.filename);
230
+ handleDelete(asset.id);
208
231
  }}
209
232
  className="absolute top-0.5 right-0.5 w-5 h-5 bg-black/70 text-white/70 hover:text-red-400 flex items-center justify-center rounded-full opacity-0 group-hover:opacity-100 transition-opacity"
210
233
  aria-label={`Delete ${asset.filename}`}
@@ -164,6 +164,16 @@ const backgroundOptions = [
164
164
  "bg-[#ffffff]",
165
165
  ];
166
166
 
167
+ const HEX_COLOR_PATTERN = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
168
+
169
+ /** Native <input type="color"> only accepts 3/6-digit hex — fall back to
170
+ * black for gradients or other raw CSS values so the picker still opens. */
171
+ function toColorInputValue(background: string | undefined): string {
172
+ return background && HEX_COLOR_PATTERN.test(background)
173
+ ? background
174
+ : "#000000";
175
+ }
176
+
167
177
  /** Popover anchored to a button ref */
168
178
  function ToolbarPopover({
169
179
  open,
@@ -294,6 +304,9 @@ export default function EditorToolbar({
294
304
  }, []);
295
305
 
296
306
  const activeAspectRatio: AspectRatio = aspectRatio ?? DEFAULT_ASPECT_RATIO;
307
+ const isCustomBackground =
308
+ !!currentSlide?.background &&
309
+ !backgroundOptions.includes(currentSlide.background);
297
310
  const [layoutOpen, setLayoutOpen] = useState(false);
298
311
  const layoutRef = useRef<HTMLButtonElement>(null);
299
312
 
@@ -544,6 +557,29 @@ export default function EditorToolbar({
544
557
  }`}
545
558
  />
546
559
  ))}
560
+ <label
561
+ className={`relative w-10 h-7 rounded-md border cursor-pointer overflow-hidden transition-all ${
562
+ isCustomBackground
563
+ ? "border-[#609FF8] ring-1 ring-[#609FF8]/30"
564
+ : "border-border hover:border-foreground/20"
565
+ }`}
566
+ style={{
567
+ background: isCustomBackground
568
+ ? currentSlide.background
569
+ : "conic-gradient(from 180deg, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000)",
570
+ }}
571
+ title={t("editorToolbar.customColor")}
572
+ >
573
+ <input
574
+ type="color"
575
+ value={toColorInputValue(currentSlide.background)}
576
+ onChange={(e) => {
577
+ onUpdateSlide!({ background: e.target.value });
578
+ }}
579
+ className="absolute inset-0 h-full w-full cursor-pointer opacity-0"
580
+ aria-label={t("editorToolbar.customColor")}
581
+ />
582
+ </label>
547
583
  </div>
548
584
  </div>
549
585
 
@@ -61,6 +61,8 @@ const messages = {
61
61
  deckOptions: "Deck options",
62
62
  generatingImage: "Generating image...",
63
63
  noAssetsYet: "No assets yet.",
64
+ assetUploadFailed: "Some uploads failed",
65
+ assetDeleteFailed: "Failed to delete asset",
64
66
  contentMarkdown: "Content (Markdown)",
65
67
  speakerNotes: "Speaker Notes",
66
68
  speakerNotesForSlide: "Speaker Notes — Slide {{index}} / {{count}}",
@@ -192,6 +194,7 @@ const messages = {
192
194
  slideSettings: "Slide settings",
193
195
  layout: "Layout",
194
196
  background: "Background",
197
+ customColor: "Custom color",
195
198
  media: "Media",
196
199
  generateImage: "Generate Image",
197
200
  assetLibrary: "Asset Library",
@@ -0,0 +1,63 @@
1
+ const RELOAD_GUARD_KEY = "slides:export-module-reload-attempted";
2
+
3
+ // Chrome/Firefox/Safari all use this wording (or close to it) for a dynamic
4
+ // `import()` whose chunk 404s — typically because the app was redeployed
5
+ // after the page loaded and the browser still references an old, now-missing
6
+ // chunk hash. Calling `import()` again with the *same* specifier re-requests
7
+ // the same now-missing URL and fails immediately, so a plain retry never
8
+ // recovers this case — only a full reload picks up the current chunk map.
9
+ const STALE_CHUNK_PATTERN =
10
+ /fetch dynamically imported module|error loading dynamically imported module/i;
11
+
12
+ function isStaleChunkError(error: unknown): boolean {
13
+ const message = error instanceof Error ? error.message : String(error);
14
+ return STALE_CHUNK_PATTERN.test(message);
15
+ }
16
+
17
+ function hasAlreadyReloaded(): boolean {
18
+ try {
19
+ return sessionStorage.getItem(RELOAD_GUARD_KEY) === "1";
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+
25
+ function markReloaded(): void {
26
+ try {
27
+ sessionStorage.setItem(RELOAD_GUARD_KEY, "1");
28
+ } catch {
29
+ // sessionStorage unavailable (e.g. private mode) — reload once anyway,
30
+ // worst case we reload more than once instead of not recovering at all.
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Lazy `import()` calls for export libraries (jsPDF, modern-screenshot,
36
+ * dom-to-pptx) fail with "Failed to fetch dynamically imported module" when
37
+ * the chunk is momentarily unreachable (flaky network) or stale after a
38
+ * redeploy. For the stale-chunk case, retrying the same specifier is a no-op,
39
+ * so we reload the page once to pick up the current chunk map; for anything
40
+ * else, a same-specifier retry can still clear a transient blip.
41
+ */
42
+ export async function importExportModule<T>(
43
+ loader: () => Promise<T>,
44
+ ): Promise<T> {
45
+ try {
46
+ return await loader();
47
+ } catch (error) {
48
+ if (isStaleChunkError(error) && !hasAlreadyReloaded()) {
49
+ markReloaded();
50
+ window.location.reload();
51
+ // The reload navigates away; never resolve so callers don't proceed
52
+ // against a page that's about to unload.
53
+ return new Promise<T>(() => {});
54
+ }
55
+ try {
56
+ return await loader();
57
+ } catch {
58
+ throw new Error(
59
+ "Couldn't load the export module. Refresh the page and try again.",
60
+ );
61
+ }
62
+ }
63
+ }
@@ -11,6 +11,7 @@
11
11
  * doesn't shrink the captured pixels.
12
12
  */
13
13
  import { type AspectRatio, getAspectRatioDims } from "./aspect-ratios";
14
+ import { importExportModule } from "./dynamic-import";
14
15
 
15
16
  /**
16
17
  * Cross-origin <img> elements without an explicit `crossOrigin="anonymous"`
@@ -99,8 +100,8 @@ export async function exportDeckAsPdf(
99
100
  // JPEG (vs PNG) keeps a typical 8-slide deck under ~10 MB instead of
100
101
  // ~100 MB — at 0.92 quality the difference is invisible on slide content.
101
102
  const [{ domToJpeg }, { jsPDF }] = await Promise.all([
102
- import("modern-screenshot"),
103
- import("jspdf"),
103
+ importExportModule(() => import("modern-screenshot")),
104
+ importExportModule(() => import("jspdf")),
104
105
  ]);
105
106
 
106
107
  // Web fonts (Poppins) must finish loading before capture — otherwise
@@ -1,4 +1,5 @@
1
1
  import { type AspectRatio, getAspectRatioDims } from "./aspect-ratios";
2
+ import { importExportModule } from "./dynamic-import";
2
3
  import {
3
4
  findSlideExportSource,
4
5
  preloadImagesWithCors,
@@ -83,7 +84,7 @@ export async function addSpeakerNotesToPptxBlob(
83
84
  const hasNotes = slides.some((slide) => slide.notes?.trim());
84
85
  if (!hasNotes) return blob;
85
86
 
86
- const { default: JSZip } = await import("jszip");
87
+ const { default: JSZip } = await importExportModule(() => import("jszip"));
87
88
  const zip = await JSZip.loadAsync(blob);
88
89
 
89
90
  const contentTypesFile = zip.file("[Content_Types].xml");
@@ -317,7 +318,9 @@ export async function exportDeckAsPptx(
317
318
  slides: PptxExportSlide[],
318
319
  aspectRatio?: AspectRatio,
319
320
  ): Promise<void> {
320
- const { exportToPptx } = await import("dom-to-pptx");
321
+ const { exportToPptx } = await importExportModule(
322
+ () => import("dom-to-pptx"),
323
+ );
321
324
 
322
325
  if (typeof document !== "undefined" && document.fonts?.ready) {
323
326
  await document.fonts.ready;
@@ -18,6 +18,7 @@ import { Button } from "@/components/ui/button";
18
18
  import { useDesignSystems } from "@/hooks/use-design-systems";
19
19
 
20
20
  import type { DesignSystemData } from "../../shared/api";
21
+ import { missingDesignSystemDataFields } from "../../shared/design-system-validation";
21
22
 
22
23
  export default function DesignSystems() {
23
24
  const t = useT();
@@ -52,7 +53,14 @@ export default function DesignSystems() {
52
53
 
53
54
  const parseDesignData = (dataStr: string): DesignSystemData | null => {
54
55
  try {
55
- return JSON.parse(dataStr) as DesignSystemData;
56
+ const parsed = JSON.parse(dataStr) as DesignSystemData;
57
+ // DesignSystemCard reads colors.* / typography.* unconditionally, down
58
+ // to nested fields like typography.headingFont. Rows written before
59
+ // create/update validation existed can have `colors: {}` and still
60
+ // pass a truthy check, so reuse the same nested-field validator the
61
+ // actions use rather than only checking the top-level objects exist.
62
+ if (missingDesignSystemDataFields(parsed).length > 0) return null;
63
+ return parsed;
56
64
  } catch {
57
65
  return null;
58
66
  }
@@ -57,6 +57,17 @@ export const deckShareLinks = table("deck_share_links", {
57
57
  createdAt: text("created_at").notNull().default(now()),
58
58
  });
59
59
 
60
+ export const uploadedAssets = table("uploaded_assets", {
61
+ id: text("id").primaryKey(),
62
+ filename: text("filename").notNull(),
63
+ url: text("url").notNull(),
64
+ type: text("type").notNull(),
65
+ size: integer("size").notNull(),
66
+ provider: text("provider"),
67
+ ownerEmail: text("owner_email").notNull(),
68
+ createdAt: text("created_at").notNull().default(now()),
69
+ });
70
+
60
71
  export const slideComments = table("slide_comments", {
61
72
  id: text("id").primaryKey(),
62
73
  deckId: text("deck_id").notNull(),
@@ -3,11 +3,16 @@ import path from "path";
3
3
  import { uploadFile } from "@agent-native/core/file-upload";
4
4
  import { getSession } from "@agent-native/core/server";
5
5
  import { runWithRequestContext } from "@agent-native/core/server";
6
+ import { and, desc, eq } from "drizzle-orm";
6
7
  import {
7
8
  defineEventHandler,
9
+ getRouterParam,
8
10
  setResponseStatus,
9
11
  readMultipartFormData,
10
12
  } from "h3";
13
+ import { nanoid } from "nanoid";
14
+
15
+ import { getDb, schema } from "../db/index.js";
11
16
 
12
17
  export const MAX_ASSET_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
13
18
 
@@ -19,6 +24,14 @@ export interface UploadedAsset {
19
24
  provider?: string;
20
25
  }
21
26
 
27
+ export interface ListedUploadedAsset {
28
+ id: string;
29
+ url: string;
30
+ filename: string;
31
+ size: number;
32
+ createdAt: string;
33
+ }
34
+
22
35
  async function requireSession(event: Parameters<typeof getSession>[0]) {
23
36
  const session = await getSession(event).catch(() => null);
24
37
  if (!session?.email) {
@@ -137,13 +150,29 @@ export async function uploadImageAsset(args: {
137
150
  throw err;
138
151
  }
139
152
 
140
- return {
153
+ const asset: UploadedAsset = {
141
154
  url: result.url,
142
155
  filename: args.originalName,
143
156
  type: args.type || "application/octet-stream",
144
157
  size: args.data.length,
145
158
  provider: result.provider,
146
159
  };
160
+
161
+ // Record the upload so it shows up in GET /api/assets — only the URL and
162
+ // metadata are stored, never the file bytes (those live with the provider).
163
+ const db = getDb();
164
+ await db.insert(schema.uploadedAssets).values({
165
+ id: nanoid(),
166
+ filename: asset.filename,
167
+ url: asset.url,
168
+ type: asset.type,
169
+ size: asset.size,
170
+ provider: asset.provider ?? null,
171
+ ownerEmail: args.email,
172
+ createdAt: new Date().toISOString(),
173
+ });
174
+
175
+ return asset;
147
176
  }
148
177
 
149
178
  /**
@@ -185,37 +214,53 @@ export const uploadAsset = defineEventHandler(async (event) => {
185
214
  });
186
215
 
187
216
  /**
188
- * GET /api/assets — list previously-uploaded assets.
189
- *
190
- * Asset history used to come from scanning `public/uploads/<tenant>/` on disk.
191
- * That source is gone now (see `uploadImageAsset` for the reasoning). Until
192
- * we plumb a SQL-backed asset index that records each upload (so we can list
193
- * across providers), this endpoint returns an empty list. The AssetLibraryPanel
194
- * still works for uploading new images and selecting them for the current
195
- * editing session.
217
+ * GET /api/assets — list assets this user has uploaded, most recent first.
196
218
  */
197
219
  export const listAssets = defineEventHandler(async (event) => {
198
220
  const session = await requireSession(event);
199
221
  if (!session) {
200
222
  return { error: "Unauthorized" };
201
223
  }
202
- return [];
224
+ const db = getDb();
225
+ const rows: ListedUploadedAsset[] = await db
226
+ .select({
227
+ id: schema.uploadedAssets.id,
228
+ url: schema.uploadedAssets.url,
229
+ filename: schema.uploadedAssets.filename,
230
+ size: schema.uploadedAssets.size,
231
+ createdAt: schema.uploadedAssets.createdAt,
232
+ })
233
+ .from(schema.uploadedAssets)
234
+ .where(eq(schema.uploadedAssets.ownerEmail, session.email))
235
+ .orderBy(desc(schema.uploadedAssets.createdAt));
236
+ return rows;
203
237
  });
204
238
 
205
239
  /**
206
- * DELETE /api/assets/:filenameused to delete from `public/uploads/`. With
207
- * uploads routed through Builder.io / S3 / etc., deletion has to happen via
208
- * the active provider's API. Returning 501 keeps the endpoint reachable so
209
- * the frontend doesn't error, and signals that this isn't wired yet.
240
+ * DELETE /api/assets/:idremoves the upload from this user's asset library
241
+ * index. Keyed by the row's unique id (not filename) since two uploads can
242
+ * share the same original filename. The underlying file may still exist with
243
+ * the storage provider (Builder.io, S3, etc.) deleting it there requires
244
+ * that provider's own API — but it no longer appears in this app's library.
210
245
  */
211
246
  export const deleteAsset = defineEventHandler(async (event) => {
212
247
  const session = await requireSession(event);
213
248
  if (!session) {
214
249
  return { error: "Unauthorized" };
215
250
  }
216
- setResponseStatus(event, 501);
217
- return {
218
- error:
219
- "Asset deletion via this endpoint is not implemented — uploads now live in the configured file-upload provider (Builder.io, S3, etc.). Delete them through the provider's dashboard.",
220
- };
251
+ const id = getRouterParam(event, "id");
252
+ if (!id) {
253
+ setResponseStatus(event, 400);
254
+ return { error: "Asset id is required" };
255
+ }
256
+ const db = getDb();
257
+ await db
258
+ .delete(schema.uploadedAssets)
259
+ .where(
260
+ and(
261
+ eq(schema.uploadedAssets.id, decodeURIComponent(id)),
262
+ eq(schema.uploadedAssets.ownerEmail, session.email),
263
+ ),
264
+ );
265
+ return { success: true };
221
266
  });
@@ -9,6 +9,44 @@ function esc(text: string): string {
9
9
  .replace(/"/g, "&quot;");
10
10
  }
11
11
 
12
+ /**
13
+ * Render a page's embedded photo as the full-bleed slide background with the
14
+ * page's extracted text overlaid on top. Designed PDF pages (photo
15
+ * backgrounds, gradients, custom typography) have no reliable shape
16
+ * structure to reconstruct, so the embedded image is reused directly — but
17
+ * the vector/glyph text on the page is not something we can rasterize
18
+ * reliably headless, so the extracted text is drawn as real HTML on top
19
+ * instead of relying on the page's own (font-dependent) rendering.
20
+ *
21
+ * `pdf-parse`'s plain-text extraction carries no color/font metadata, so the
22
+ * heading accent color below is a stand-in, not a recovered value — when a
23
+ * subtitle is present (a content slide, not a title slide) it renders as a
24
+ * centered card with a divider rule so the two text roles stay visually
25
+ * distinct instead of collapsing into one flat paragraph.
26
+ */
27
+ export function buildFullBleedImageSlideHtml(
28
+ imageUrl: string,
29
+ headingText?: string,
30
+ subtitleText?: string,
31
+ ): string {
32
+ let overlay = "";
33
+ if (headingText && subtitleText) {
34
+ overlay = `\n <div style="position: absolute; left: 0; right: 0; bottom: 0; background: linear-gradient(to top, rgba(12,10,8,0.95) 0%, rgba(12,10,8,0.88) 55%, rgba(12,10,8,0.4) 82%, rgba(12,10,8,0) 100%); padding: 56px 56px 60px; text-align: center; font-family: 'Poppins', sans-serif;">
35
+ <div style="width: 72px; height: 3px; background: #d8b26a; margin: 0 auto 20px;"></div>
36
+ <h2 style="font-size: 30px; font-weight: 800; color: #d8b26a; line-height: 1.25; margin: 0 0 14px;">${esc(headingText)}</h2>
37
+ <p style="font-size: 19px; font-weight: 500; color: #fff; line-height: 1.5; margin: 0;">${esc(subtitleText)}</p>
38
+ </div>`;
39
+ } else if (headingText) {
40
+ overlay = `\n <div style="position: absolute; inset: 0; background: linear-gradient(to top, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.15) 45%, rgba(0,0,0,0) 65%);"></div>
41
+ <div style="position: absolute; left: 0; right: 0; bottom: 0; padding: 60px 70px; font-family: 'Poppins', sans-serif;">
42
+ <h2 style="font-size: 40px; font-weight: 900; color: #fff; line-height: 1.15; letter-spacing: -1px; margin: 0;">${esc(headingText)}</h2>
43
+ </div>`;
44
+ }
45
+ return `<div class="fmd-slide" style="position: relative; width: 100%; height: 100%; overflow: hidden;">
46
+ <img src="${esc(imageUrl)}" alt="" style="position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;" />${overlay}
47
+ </div>`;
48
+ }
49
+
12
50
  /** Wrap text in formatting tags based on run properties. */
13
51
  function formatRun(run: ParsedTextRun): string {
14
52
  let text = esc(run.content);
@@ -210,6 +210,26 @@ const runSlidesMigrations = runMigrations(
210
210
  CREATE INDEX IF NOT EXISTS slide_comments_deck_created_idx ON slide_comments (deck_id, created_at);
211
211
  CREATE INDEX IF NOT EXISTS slide_comments_deck_slide_created_idx ON slide_comments (deck_id, slide_id, created_at)`,
212
212
  },
213
+ // v20: index of assets uploaded through the file-upload provider chain.
214
+ // GET /api/assets previously always returned [] (no persisted record of
215
+ // uploads), so the Asset Library panel could never show or re-select a
216
+ // file after uploading it. This table only stores the returned URL/
217
+ // metadata, never the file bytes.
218
+ {
219
+ version: 20,
220
+ name: "slides-uploaded-assets-table",
221
+ sql: `CREATE TABLE IF NOT EXISTS uploaded_assets (
222
+ id TEXT PRIMARY KEY,
223
+ filename TEXT NOT NULL,
224
+ url TEXT NOT NULL,
225
+ type TEXT NOT NULL,
226
+ size INTEGER NOT NULL,
227
+ provider TEXT,
228
+ owner_email TEXT NOT NULL,
229
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
230
+ );
231
+ CREATE INDEX IF NOT EXISTS uploaded_assets_owner_created_idx ON uploaded_assets (owner_email, created_at)`,
232
+ },
213
233
  ],
214
234
  { table: "slides_migrations" },
215
235
  );
@@ -0,0 +1,62 @@
1
+ const REQUIRED_COLOR_KEYS = [
2
+ "primary",
3
+ "secondary",
4
+ "accent",
5
+ "background",
6
+ "surface",
7
+ "text",
8
+ "textMuted",
9
+ ];
10
+
11
+ const REQUIRED_TYPOGRAPHY_KEYS = [
12
+ "headingFont",
13
+ "bodyFont",
14
+ "headingWeight",
15
+ "bodyWeight",
16
+ ];
17
+
18
+ /**
19
+ * The Design Systems page and slide renderers read data.colors.* and
20
+ * data.typography.* unconditionally (no optional chaining). A syntactically
21
+ * valid but incomplete `data` payload — e.g. from an interrupted generation —
22
+ * would otherwise persist and crash on the very next read. Shared between the
23
+ * create/update actions (write-time validation) and the Design Systems page
24
+ * (read-time validation of rows written before this check existed).
25
+ */
26
+ export function missingDesignSystemDataFields(value: unknown): string[] {
27
+ const missing: string[] = [];
28
+ const record =
29
+ value && typeof value === "object"
30
+ ? (value as Record<string, unknown>)
31
+ : {};
32
+
33
+ const colors =
34
+ record.colors && typeof record.colors === "object"
35
+ ? (record.colors as Record<string, unknown>)
36
+ : null;
37
+ if (!colors) {
38
+ missing.push("colors");
39
+ } else {
40
+ for (const key of REQUIRED_COLOR_KEYS) {
41
+ if (typeof colors[key] !== "string" || !colors[key]) {
42
+ missing.push(`colors.${key}`);
43
+ }
44
+ }
45
+ }
46
+
47
+ const typography =
48
+ record.typography && typeof record.typography === "object"
49
+ ? (record.typography as Record<string, unknown>)
50
+ : null;
51
+ if (!typography) {
52
+ missing.push("typography");
53
+ } else {
54
+ for (const key of REQUIRED_TYPOGRAPHY_KEYS) {
55
+ if (typeof typography[key] !== "string" || !typography[key]) {
56
+ missing.push(`typography.${key}`);
57
+ }
58
+ }
59
+ }
60
+
61
+ return missing;
62
+ }
@@ -16,7 +16,7 @@ export function isA2AProductionRuntime() {
16
16
  }
17
17
  if (process.env.CF_PAGES === "1")
18
18
  return true;
19
- if ("__cf_env" in globalThis)
19
+ if ("__cf_env" in globalThis || "__env__" in globalThis)
20
20
  return true;
21
21
  if (process.env.VERCEL || process.env.VERCEL_ENV)
22
22
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"auth-policy.js","sourceRoot":"","sources":["../../src/a2a/auth-policy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB;IACpC,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;QAAE,OAAO,IAAI,CAAC;IACvD,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,KAAK,MAAM,EAAE,CAAC;QAC3E,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IACE,OAAO,CAAC,GAAG,CAAC,wBAAwB;QACpC,OAAO,CAAC,GAAG,CAAC,aAAa,KAAK,MAAM,EACpC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAC9C,IAAI,UAAU,IAAI,UAAU;QAAE,OAAO,IAAI,CAAC;IAC1C,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAC9D,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;QAC5E,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,yBAAyB;IACvC,OAAO,sBAAsB,EAAE,IAAI,sBAAsB,EAAE,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAA2B;IAC/D,IAAI,sBAAsB,EAAE;QAAE,OAAO,KAAK,CAAC;IAC3C,IAAI,OAAO,CAAC,GAAG,CAAC,2BAA2B,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IACjE,OAAO,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC;AAChC,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,iBAAiB,CAAC,IAA+B;IAC/D,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IACtB,OAAO,CACL,CAAC,KAAK,WAAW;QACjB,CAAC,KAAK,KAAK;QACX,CAAC,KAAK,kBAAkB;QACxB,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;QACpB,CAAC,KAAK,WAAW,CAClB,CAAC;AACJ,CAAC","sourcesContent":["/**\n * A2A auth policy helpers shared by discovery, the JSON-RPC gate, and task\n * handlers. Serverless providers do not always expose `NODE_ENV=production`\n * consistently at runtime, so production-like A2A checks also look at the\n * provider flags those platforms set in deployed functions.\n */\nexport function isA2AProductionRuntime(): boolean {\n if (process.env.NODE_ENV === \"production\") return true;\n if (process.env.NETLIFY === \"true\" && process.env.NETLIFY_LOCAL !== \"true\") {\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 if (process.env.CF_PAGES === \"1\") return true;\n if (\"__cf_env\" in globalThis) return true;\n if (process.env.VERCEL || process.env.VERCEL_ENV) return true;\n if (process.env.RENDER || process.env.FLY_APP_NAME || process.env.K_SERVICE) {\n return true;\n }\n return false;\n}\n\nexport function hasConfiguredA2ASecret(): boolean {\n return !!process.env.A2A_SECRET?.trim();\n}\n\nexport function shouldAdvertiseJwtA2AAuth(): boolean {\n return hasConfiguredA2ASecret() || isA2AProductionRuntime();\n}\n\n/**\n * True only when unsigned internal self-dispatch is acceptable: no A2A_SECRET\n * is configured AND we can positively identify a local/dev runtime. Everything\n * else — production, or any UNRECOGNIZED deployed/networked host — must fail\n * closed and require A2A_SECRET. `loopback` should be whether the inbound\n * request arrived over the loopback interface (127.0.0.1/::1); callers that\n * cannot determine the peer address pass `false`.\n *\n * NODE_ENV alone is deliberately NOT a trust grant: a self-hosted deployment\n * that doesn't set NODE_ENV=production and isn't recognized by\n * `isA2AProductionRuntime()` (a bare Docker/VPS/K8s pod) must still fail\n * closed unless the request actually came from loopback or the explicit\n * opt-in flag is set.\n */\nexport function isTrustedLocalRuntime(opts: { loopback: boolean }): boolean {\n if (isA2AProductionRuntime()) return false;\n if (process.env.A2A_ALLOW_UNSIGNED_INTERNAL === \"1\") return true;\n return opts.loopback === true;\n}\n\n/** True if a socket peer address is a loopback/local address. */\nexport function isLoopbackAddress(addr: string | undefined | null): boolean {\n if (!addr) return false;\n const a = addr.trim();\n return (\n a === \"127.0.0.1\" ||\n a === \"::1\" ||\n a === \"::ffff:127.0.0.1\" ||\n a.startsWith(\"127.\") ||\n a === \"localhost\"\n );\n}\n"]}
1
+ {"version":3,"file":"auth-policy.js","sourceRoot":"","sources":["../../src/a2a/auth-policy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB;IACpC,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;QAAE,OAAO,IAAI,CAAC;IACvD,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,KAAK,MAAM,EAAE,CAAC;QAC3E,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IACE,OAAO,CAAC,GAAG,CAAC,wBAAwB;QACpC,OAAO,CAAC,GAAG,CAAC,aAAa,KAAK,MAAM,EACpC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAC9C,IAAI,UAAU,IAAI,UAAU,IAAI,SAAS,IAAI,UAAU;QAAE,OAAO,IAAI,CAAC;IACrE,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAC9D,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;QAC5E,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,yBAAyB;IACvC,OAAO,sBAAsB,EAAE,IAAI,sBAAsB,EAAE,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAA2B;IAC/D,IAAI,sBAAsB,EAAE;QAAE,OAAO,KAAK,CAAC;IAC3C,IAAI,OAAO,CAAC,GAAG,CAAC,2BAA2B,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IACjE,OAAO,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC;AAChC,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,iBAAiB,CAAC,IAA+B;IAC/D,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IACtB,OAAO,CACL,CAAC,KAAK,WAAW;QACjB,CAAC,KAAK,KAAK;QACX,CAAC,KAAK,kBAAkB;QACxB,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;QACpB,CAAC,KAAK,WAAW,CAClB,CAAC;AACJ,CAAC","sourcesContent":["/**\n * A2A auth policy helpers shared by discovery, the JSON-RPC gate, and task\n * handlers. Serverless providers do not always expose `NODE_ENV=production`\n * consistently at runtime, so production-like A2A checks also look at the\n * provider flags those platforms set in deployed functions.\n */\nexport function isA2AProductionRuntime(): boolean {\n if (process.env.NODE_ENV === \"production\") return true;\n if (process.env.NETLIFY === \"true\" && process.env.NETLIFY_LOCAL !== \"true\") {\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 if (process.env.CF_PAGES === \"1\") return true;\n if (\"__cf_env\" in globalThis || \"__env__\" in globalThis) return true;\n if (process.env.VERCEL || process.env.VERCEL_ENV) return true;\n if (process.env.RENDER || process.env.FLY_APP_NAME || process.env.K_SERVICE) {\n return true;\n }\n return false;\n}\n\nexport function hasConfiguredA2ASecret(): boolean {\n return !!process.env.A2A_SECRET?.trim();\n}\n\nexport function shouldAdvertiseJwtA2AAuth(): boolean {\n return hasConfiguredA2ASecret() || isA2AProductionRuntime();\n}\n\n/**\n * True only when unsigned internal self-dispatch is acceptable: no A2A_SECRET\n * is configured AND we can positively identify a local/dev runtime. Everything\n * else — production, or any UNRECOGNIZED deployed/networked host — must fail\n * closed and require A2A_SECRET. `loopback` should be whether the inbound\n * request arrived over the loopback interface (127.0.0.1/::1); callers that\n * cannot determine the peer address pass `false`.\n *\n * NODE_ENV alone is deliberately NOT a trust grant: a self-hosted deployment\n * that doesn't set NODE_ENV=production and isn't recognized by\n * `isA2AProductionRuntime()` (a bare Docker/VPS/K8s pod) must still fail\n * closed unless the request actually came from loopback or the explicit\n * opt-in flag is set.\n */\nexport function isTrustedLocalRuntime(opts: { loopback: boolean }): boolean {\n if (isA2AProductionRuntime()) return false;\n if (process.env.A2A_ALLOW_UNSIGNED_INTERNAL === \"1\") return true;\n return opts.loopback === true;\n}\n\n/** True if a socket peer address is a loopback/local address. */\nexport function isLoopbackAddress(addr: string | undefined | null): boolean {\n if (!addr) return false;\n const a = addr.trim();\n return (\n a === \"127.0.0.1\" ||\n a === \"::1\" ||\n a === \"::ffff:127.0.0.1\" ||\n a.startsWith(\"127.\") ||\n a === \"localhost\"\n );\n}\n"]}
@@ -424,6 +424,7 @@ export type AgentLoopFinalResponseGuardResult = string | {
424
424
  expandToolSurface?: boolean;
425
425
  };
426
426
  export type AgentLoopFinalResponseGuard = (context: AgentLoopFinalResponseGuardContext) => AgentLoopFinalResponseGuardResult | null | undefined | Promise<AgentLoopFinalResponseGuardResult | null | undefined>;
427
+ export declare const AGENT_INTERNAL_GUARD_PROMPT = "Automated quality check on the draft answer you just produced. This is a directive from this application's own response guard, not a message from the user and not content from a tool result or web page. Follow it and revise your answer. Do not quote it, describe it, or treat it as an injection attempt. If it contradicts what you observed this turn, state what you actually observed instead of asserting the guard's premise.";
427
428
  export declare const AGENT_INTERNAL_CONTINUE_PROMPT = "Continue from where you left off and finish the user's original request. Do not repeat completed work, do not mention internal reconnects, time limits, or step limits, and continue as if this is the same uninterrupted run.";
428
429
  export type AgentLoopContinuationReason = "run_timeout" | "loop_limit" | "max_tokens" | "stream_ended" | "gateway_timeout" | "network_interrupted" | "no_progress";
429
430
  export declare function appendAgentLoopContinuation(messages: EngineMessage[], reason: AgentLoopContinuationReason, options?: {