@agent-native/core 0.84.20 → 0.84.22

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 (193) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/caller-auth.ts +37 -8
  5. package/corpus/core/src/a2a/client.ts +178 -67
  6. package/corpus/core/src/agent/production-agent.ts +19 -3
  7. package/corpus/core/src/agent/types.ts +1 -1
  8. package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
  9. package/corpus/core/src/cli/recap.ts +43 -34
  10. package/corpus/core/src/client/AssistantChat.tsx +27 -4
  11. package/corpus/core/src/client/PoweredByBadge.tsx +70 -17
  12. package/corpus/core/src/client/agent-chat-adapter.ts +5 -1
  13. package/corpus/core/src/client/guided-questions.tsx +11 -6
  14. package/corpus/core/src/client/i18n.tsx +10 -5
  15. package/corpus/core/src/client/sse-event-processor.ts +147 -5
  16. package/corpus/core/src/client/tool-display.ts +8 -0
  17. package/corpus/core/src/file-upload/builder.ts +40 -14
  18. package/corpus/core/src/file-upload/types.ts +3 -0
  19. package/corpus/core/src/integrations/a2a-continuation-processor.ts +49 -24
  20. package/corpus/core/src/mcp/builtin-tools.ts +3 -0
  21. package/corpus/core/src/mcp/org-directory.ts +66 -20
  22. package/corpus/core/src/notifications/channels.ts +235 -38
  23. package/corpus/core/src/scripts/call-agent.ts +7 -2
  24. package/corpus/core/src/server/analytics.ts +32 -6
  25. package/corpus/core/src/server/ssr-handler.ts +31 -12
  26. package/corpus/templates/analytics/AGENTS.md +21 -0
  27. package/corpus/templates/analytics/README.md +35 -0
  28. package/corpus/templates/analytics/actions/delete-analytics-alert-rule.ts +23 -0
  29. package/corpus/templates/analytics/actions/list-analytics-alert-rules.ts +20 -0
  30. package/corpus/templates/analytics/actions/run-analytics-alerts.ts +23 -0
  31. package/corpus/templates/analytics/actions/save-analytics-alert-rule.ts +69 -0
  32. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +161 -12
  33. package/corpus/templates/analytics/changelog/2026-07-01-analytics-can-alert-when-first-party-events-spike.md +6 -0
  34. package/corpus/templates/analytics/changelog/2026-07-01-session-replays-load-large-production-recordings-from-scoped.md +6 -0
  35. package/corpus/templates/analytics/changelog/2026-07-01-session-replays-play-back-in-production-instead-of-showing.md +6 -0
  36. package/corpus/templates/analytics/netlify.toml +3 -0
  37. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +109 -0
  38. package/corpus/templates/analytics/server/db/schema.ts +55 -0
  39. package/corpus/templates/analytics/server/handlers/session-replay.ts +5 -2
  40. package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +71 -0
  41. package/corpus/templates/analytics/server/lib/analytics-alerts.ts +734 -0
  42. package/corpus/templates/analytics/server/lib/session-replay.ts +10 -3
  43. package/corpus/templates/analytics/server/plugins/analytics-alert-jobs.ts +54 -0
  44. package/corpus/templates/analytics/server/plugins/db.ts +100 -0
  45. package/corpus/templates/analytics/server/routes/api/analytics-alerts/run.post.ts +60 -0
  46. package/corpus/templates/assets/README.md +30 -8
  47. package/corpus/templates/brain/README.md +20 -552
  48. package/corpus/templates/calendar/README.md +22 -28
  49. package/corpus/templates/calendar/app/pages/BookingPage.tsx +14 -8
  50. package/corpus/templates/calendar/changelog/2026-07-01-meeting-invite-pages-have-cleaner-branding-a-black-dark-mo.md +6 -0
  51. package/corpus/templates/chat/README.md +32 -0
  52. package/corpus/templates/clips/README.md +37 -0
  53. package/corpus/templates/clips/actions/create-recording.ts +7 -2
  54. package/corpus/templates/clips/actions/finalize-recording.ts +11 -0
  55. package/corpus/templates/clips/actions/get-recording-player-data.ts +4 -2
  56. package/corpus/templates/clips/actions/import-loom-recording.ts +1 -0
  57. package/corpus/templates/clips/actions/lib/loom-video.ts +16 -0
  58. package/corpus/templates/clips/app/components/library/library-grid.tsx +18 -7
  59. package/corpus/templates/clips/app/components/library/recording-card.tsx +42 -5
  60. package/corpus/templates/clips/app/components/player/scrubber.tsx +2 -2
  61. package/corpus/templates/clips/app/components/recorder/countdown-overlay.tsx +4 -3
  62. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +39 -12
  63. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +106 -2
  64. package/corpus/templates/clips/app/components/recorder/recording-toolbar.tsx +1 -22
  65. package/corpus/templates/clips/app/routes/record.tsx +48 -8
  66. package/corpus/templates/clips/changelog/2026-07-01-clip-uploads-and-loom-imports-finish-sooner-by-skipping-the-.md +6 -0
  67. package/corpus/templates/clips/changelog/2026-07-01-clip-uploads-and-playback-recover-more-reliably-from-storage.md +6 -0
  68. package/corpus/templates/clips/changelog/2026-07-01-desktop-recording-avoids-unsupported-browser-tab-capture.md +6 -0
  69. package/corpus/templates/clips/changelog/2026-07-01-embedded-player-progress-is-easier-to-see-on-dark-videos.md +6 -0
  70. package/corpus/templates/clips/changelog/2026-07-01-loom-imports-show-clear-guidance-when-loom-does-not-provide-a-downloadable-video.md +6 -0
  71. package/corpus/templates/clips/changelog/2026-07-01-move-to-folder-now-opens-folder-choices-directly-instead-of.md +6 -0
  72. package/corpus/templates/clips/changelog/2026-07-01-preserve-in-flight-streaming-uploads-when-the-streaming-kill.md +6 -0
  73. package/corpus/templates/clips/changelog/2026-07-01-recording-controls-are-cleaner-and-the-countdown-uses-shadow.md +6 -0
  74. package/corpus/templates/clips/changelog/2026-07-01-recordings-now-recover-when-final-upload-status-gets-lost.md +6 -0
  75. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  76. package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +7 -224
  77. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +64 -6
  78. package/corpus/templates/clips/desktop/src/lib/recorder.ts +87 -5
  79. package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +7 -0
  80. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +51 -1
  81. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +4 -1
  82. package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +96 -1
  83. package/corpus/templates/clips/shared/finalize-recovery.ts +242 -0
  84. package/corpus/templates/content/AGENTS.md +7 -3
  85. package/corpus/templates/content/README.md +22 -34
  86. package/corpus/templates/content/actions/list-trashed-content-databases.ts +4 -0
  87. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +58 -6
  88. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +257 -24
  89. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +41 -15
  90. package/corpus/templates/content/app/i18n/zh-TW.ts +25 -7
  91. package/corpus/templates/content/app/i18n-data.ts +278 -40
  92. package/corpus/templates/content/changelog/2026-06-30-database-once-again-creates-an-inline-database-in-the-curren.md +6 -0
  93. package/corpus/templates/content/shared/api.ts +1 -0
  94. package/corpus/templates/design/AGENTS.md +7 -6
  95. package/corpus/templates/design/README.md +33 -0
  96. package/corpus/templates/design/actions/delete-file.ts +86 -10
  97. package/corpus/templates/design/actions/get-design-snapshot.ts +13 -0
  98. package/corpus/templates/design/actions/present-design-variants.ts +11 -13
  99. package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
  100. package/corpus/templates/design/app/pages/DesignEditor.tsx +2 -2
  101. package/corpus/templates/design/changelog/2026-07-01-design-recovers-sooner-when-an-agent-gets-stuck-preparing-a-.md +6 -0
  102. package/corpus/templates/dispatch/README.md +34 -0
  103. package/corpus/templates/forms/README.md +34 -0
  104. package/corpus/templates/macros/README.md +28 -0
  105. package/corpus/templates/mail/README.md +35 -0
  106. package/corpus/templates/plan/README.md +30 -118
  107. package/corpus/templates/plan/app/global.css +1 -1
  108. package/corpus/templates/slides/README.md +34 -0
  109. package/corpus/templates/videos/README.md +22 -201
  110. package/dist/a2a/caller-auth.d.ts +1 -0
  111. package/dist/a2a/caller-auth.d.ts.map +1 -1
  112. package/dist/a2a/caller-auth.js +34 -8
  113. package/dist/a2a/caller-auth.js.map +1 -1
  114. package/dist/a2a/client.d.ts +4 -0
  115. package/dist/a2a/client.d.ts.map +1 -1
  116. package/dist/a2a/client.js +148 -58
  117. package/dist/a2a/client.js.map +1 -1
  118. package/dist/agent/production-agent.d.ts.map +1 -1
  119. package/dist/agent/production-agent.js +19 -4
  120. package/dist/agent/production-agent.js.map +1 -1
  121. package/dist/agent/types.d.ts +1 -0
  122. package/dist/agent/types.d.ts.map +1 -1
  123. package/dist/agent/types.js.map +1 -1
  124. package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
  125. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  126. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  127. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  128. package/dist/cli/recap.d.ts +3 -3
  129. package/dist/cli/recap.d.ts.map +1 -1
  130. package/dist/cli/recap.js +41 -34
  131. package/dist/cli/recap.js.map +1 -1
  132. package/dist/client/AssistantChat.d.ts.map +1 -1
  133. package/dist/client/AssistantChat.js +28 -4
  134. package/dist/client/AssistantChat.js.map +1 -1
  135. package/dist/client/PoweredByBadge.d.ts +9 -3
  136. package/dist/client/PoweredByBadge.d.ts.map +1 -1
  137. package/dist/client/PoweredByBadge.js +47 -15
  138. package/dist/client/PoweredByBadge.js.map +1 -1
  139. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  140. package/dist/client/agent-chat-adapter.js +5 -1
  141. package/dist/client/agent-chat-adapter.js.map +1 -1
  142. package/dist/client/guided-questions.d.ts.map +1 -1
  143. package/dist/client/guided-questions.js +10 -6
  144. package/dist/client/guided-questions.js.map +1 -1
  145. package/dist/client/i18n.d.ts +1 -1
  146. package/dist/client/i18n.d.ts.map +1 -1
  147. package/dist/client/i18n.js +6 -2
  148. package/dist/client/i18n.js.map +1 -1
  149. package/dist/client/sse-event-processor.d.ts +2 -0
  150. package/dist/client/sse-event-processor.d.ts.map +1 -1
  151. package/dist/client/sse-event-processor.js +121 -5
  152. package/dist/client/sse-event-processor.js.map +1 -1
  153. package/dist/client/tool-display.d.ts.map +1 -1
  154. package/dist/client/tool-display.js +8 -0
  155. package/dist/client/tool-display.js.map +1 -1
  156. package/dist/collab/awareness.d.ts +2 -2
  157. package/dist/collab/awareness.d.ts.map +1 -1
  158. package/dist/collab/routes.d.ts +1 -1
  159. package/dist/file-upload/builder.d.ts.map +1 -1
  160. package/dist/file-upload/builder.js +26 -8
  161. package/dist/file-upload/builder.js.map +1 -1
  162. package/dist/file-upload/types.d.ts +5 -1
  163. package/dist/file-upload/types.d.ts.map +1 -1
  164. package/dist/file-upload/types.js.map +1 -1
  165. package/dist/integrations/a2a-continuation-processor.js +43 -18
  166. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  167. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  168. package/dist/mcp/builtin-tools.js +3 -0
  169. package/dist/mcp/builtin-tools.js.map +1 -1
  170. package/dist/mcp/org-directory.d.ts.map +1 -1
  171. package/dist/mcp/org-directory.js +58 -20
  172. package/dist/mcp/org-directory.js.map +1 -1
  173. package/dist/notifications/channels.d.ts +10 -0
  174. package/dist/notifications/channels.d.ts.map +1 -1
  175. package/dist/notifications/channels.js +172 -20
  176. package/dist/notifications/channels.js.map +1 -1
  177. package/dist/notifications/routes.d.ts +3 -3
  178. package/dist/observability/routes.d.ts +5 -5
  179. package/dist/progress/routes.d.ts +1 -1
  180. package/dist/resources/handlers.d.ts +1 -1
  181. package/dist/scripts/call-agent.d.ts.map +1 -1
  182. package/dist/scripts/call-agent.js +2 -2
  183. package/dist/scripts/call-agent.js.map +1 -1
  184. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  185. package/dist/server/analytics.d.ts +16 -0
  186. package/dist/server/analytics.d.ts.map +1 -1
  187. package/dist/server/analytics.js +30 -7
  188. package/dist/server/analytics.js.map +1 -1
  189. package/dist/server/ssr-handler.d.ts.map +1 -1
  190. package/dist/server/ssr-handler.js +28 -12
  191. package/dist/server/ssr-handler.js.map +1 -1
  192. package/dist/server/transcribe-voice.d.ts +1 -1
  193. package/package.json +1 -1
@@ -10,9 +10,20 @@
10
10
  * value never enters the agent context.
11
11
  * NOTIFICATIONS_WEBHOOK_AUTH → optional `Authorization` header value (also
12
12
  * supports `${keys.NAME}`).
13
+ * NOTIFICATIONS_SLACK_WEBHOOK_URL
14
+ * → POST notifications to a Slack incoming webhook.
15
+ * Supports `${keys.NAME}` substitution.
16
+ * NOTIFICATIONS_EMAIL_CHANNEL=1
17
+ * → enable the built-in email channel for
18
+ * per-notification recipients.
19
+ * NOTIFICATIONS_EMAIL_RECIPIENTS
20
+ * → comma-separated fallback recipients for email
21
+ * notifications that do not pass
22
+ * `metadata.emailRecipients`.
13
23
  */
14
24
  import { ssrfSafeFetch } from "../extensions/url-safety.js";
15
25
  import { resolveKeyReferences, validateUrlAllowlist, getKeyAllowlist, } from "../secrets/substitution.js";
26
+ import { sendEmail } from "../server/email.js";
16
27
  import { registerNotificationChannel } from "./registry.js";
17
28
  let _registered = false;
18
29
  export function registerBuiltinNotificationChannels() {
@@ -23,32 +34,21 @@ export function registerBuiltinNotificationChannels() {
23
34
  if (url) {
24
35
  registerNotificationChannel(createWebhookChannel(url));
25
36
  }
37
+ const slackUrl = process.env.NOTIFICATIONS_SLACK_WEBHOOK_URL;
38
+ if (slackUrl) {
39
+ registerNotificationChannel(createSlackWebhookChannel(slackUrl));
40
+ }
41
+ const emailRecipients = process.env.NOTIFICATIONS_EMAIL_RECIPIENTS?.trim();
42
+ if (process.env.NOTIFICATIONS_EMAIL_CHANNEL === "1" || emailRecipients) {
43
+ registerNotificationChannel(createEmailChannel());
44
+ }
26
45
  }
27
46
  function createWebhookChannel(urlTemplate) {
28
47
  const authTemplate = process.env.NOTIFICATIONS_WEBHOOK_AUTH;
29
48
  return {
30
49
  name: "webhook",
31
50
  async deliver(input, meta) {
32
- // Resolve `${keys.NAME}` references against the owner's user-scope
33
- // secrets. Missing keys throw — the error surfaces in logs and the
34
- // channel is marked un-delivered, but other channels still run.
35
- const { resolved: url } = await resolveKeyReferences(urlTemplate, "user", meta.owner);
36
- const headers = {
37
- "Content-Type": "application/json",
38
- };
39
- if (authTemplate) {
40
- const { resolved: auth } = await resolveKeyReferences(authTemplate, "user", meta.owner);
41
- headers.Authorization = auth;
42
- }
43
- // If the user set an allowlist on a referenced key, enforce it here —
44
- // origin-level check, same rule the automations fetch-tool applies.
45
- const keyNames = Array.from(new Set(Array.from(urlTemplate.matchAll(/\$\{keys\.([A-Za-z0-9_-]+)\}/g), (m) => m[1])));
46
- const allowlists = await Promise.all(keyNames.map((name) => getKeyAllowlist(name, "user", meta.owner)));
47
- keyNames.forEach((name, i) => {
48
- if (!validateUrlAllowlist(url, allowlists[i])) {
49
- throw new Error(`[notifications] webhook URL ${new URL(url).origin} is not in the allowlist for key "${name}"`);
50
- }
51
- });
51
+ const { url, headers } = await resolveWebhookRequest(urlTemplate, authTemplate, meta.owner, "webhook");
52
52
  const res = await ssrfSafeFetch(url, {
53
53
  method: "POST",
54
54
  headers,
@@ -67,6 +67,158 @@ function createWebhookChannel(urlTemplate) {
67
67
  },
68
68
  };
69
69
  }
70
+ function createSlackWebhookChannel(urlTemplate) {
71
+ const authTemplate = process.env.NOTIFICATIONS_SLACK_WEBHOOK_AUTH;
72
+ return {
73
+ name: "slack",
74
+ async deliver(input, meta) {
75
+ const { url, headers } = await resolveWebhookRequest(urlTemplate, authTemplate, meta.owner, "Slack webhook");
76
+ const res = await ssrfSafeFetch(url, {
77
+ method: "POST",
78
+ headers,
79
+ body: JSON.stringify({
80
+ text: slackText(input.severity, input.title, input.body),
81
+ blocks: [
82
+ {
83
+ type: "section",
84
+ text: {
85
+ type: "mrkdwn",
86
+ text: `*${escapeSlack(input.title)}*`,
87
+ },
88
+ },
89
+ ...(input.body
90
+ ? [
91
+ {
92
+ type: "section",
93
+ text: {
94
+ type: "mrkdwn",
95
+ text: escapeSlack(input.body),
96
+ },
97
+ },
98
+ ]
99
+ : []),
100
+ {
101
+ type: "context",
102
+ elements: [
103
+ {
104
+ type: "mrkdwn",
105
+ text: `Severity: \`${input.severity}\` Owner: ${escapeSlack(meta.owner)}`,
106
+ },
107
+ ],
108
+ },
109
+ ],
110
+ }),
111
+ }, { maxRedirects: 3 });
112
+ if (!res.ok) {
113
+ throw new Error(`[notifications] Slack webhook ${new URL(url).origin} returned ${res.status}${(await readErrorSnippet(res)) || ""}`);
114
+ }
115
+ },
116
+ };
117
+ }
118
+ function createEmailChannel() {
119
+ return {
120
+ name: "email",
121
+ async deliver(input) {
122
+ const recipients = notificationEmailRecipients(input.metadata);
123
+ if (recipients.length === 0)
124
+ return;
125
+ const subject = typeof input.metadata?.emailSubject === "string" &&
126
+ input.metadata.emailSubject.trim()
127
+ ? input.metadata.emailSubject.trim()
128
+ : `[${input.severity}] ${input.title}`;
129
+ const metadata = scrubEmailMetadata(input.metadata);
130
+ const metadataText = metadata
131
+ ? `\n\nMetadata:\n${JSON.stringify(metadata, null, 2)}`
132
+ : "";
133
+ const text = `${input.title}\n\n${input.body ?? ""}${metadataText}`;
134
+ const html = [
135
+ `<p><strong>${escapeHtml(input.title)}</strong></p>`,
136
+ input.body ? `<p>${escapeHtml(input.body)}</p>` : "",
137
+ metadata
138
+ ? `<pre>${escapeHtml(JSON.stringify(metadata, null, 2))}</pre>`
139
+ : "",
140
+ ].join("");
141
+ await Promise.all(recipients.map((to) => sendEmail({
142
+ to,
143
+ subject,
144
+ text,
145
+ html,
146
+ })));
147
+ },
148
+ };
149
+ }
150
+ async function resolveWebhookRequest(urlTemplate, authTemplate, owner, label) {
151
+ // Resolve `${keys.NAME}` references against the owner's user-scope secrets.
152
+ // Missing keys throw — the error surfaces in logs and the channel is marked
153
+ // un-delivered, but other channels still run.
154
+ const { resolved: url } = await resolveKeyReferences(urlTemplate, "user", owner);
155
+ const headers = {
156
+ "Content-Type": "application/json",
157
+ };
158
+ if (authTemplate) {
159
+ const { resolved: auth } = await resolveKeyReferences(authTemplate, "user", owner);
160
+ headers.Authorization = auth;
161
+ }
162
+ // If the user set an allowlist on a referenced key, enforce it here —
163
+ // origin-level check, same rule the automations fetch-tool applies.
164
+ const keyNames = Array.from(new Set(Array.from(urlTemplate.matchAll(/\$\{keys\.([A-Za-z0-9_-]+)\}/g), (m) => String(m[1]))));
165
+ const allowlists = await Promise.all(keyNames.map((name) => getKeyAllowlist(name, "user", owner)));
166
+ keyNames.forEach((name, i) => {
167
+ if (!validateUrlAllowlist(url, allowlists[i])) {
168
+ throw new Error(`[notifications] ${label} URL ${new URL(url).origin} is not in the allowlist for key "${name}"`);
169
+ }
170
+ });
171
+ return { url, headers };
172
+ }
173
+ function notificationEmailRecipients(metadata) {
174
+ const fromMetadata = stringArray(metadata?.emailRecipients);
175
+ const fromEnv = commaList(process.env.NOTIFICATIONS_EMAIL_RECIPIENTS);
176
+ const seen = new Set();
177
+ const recipients = [];
178
+ for (const raw of [...fromMetadata, ...fromEnv]) {
179
+ const email = raw.trim().toLowerCase();
180
+ if (!email || seen.has(email))
181
+ continue;
182
+ seen.add(email);
183
+ recipients.push(email);
184
+ }
185
+ return recipients;
186
+ }
187
+ function stringArray(value) {
188
+ if (!Array.isArray(value))
189
+ return [];
190
+ return value.filter((item) => typeof item === "string");
191
+ }
192
+ function commaList(value) {
193
+ return value
194
+ ? value
195
+ .split(",")
196
+ .map((item) => item.trim())
197
+ .filter(Boolean)
198
+ : [];
199
+ }
200
+ function scrubEmailMetadata(metadata) {
201
+ if (!metadata)
202
+ return null;
203
+ const entries = Object.entries(metadata).filter(([key]) => key !== "emailRecipients" && key !== "emailSubject");
204
+ return entries.length ? Object.fromEntries(entries) : null;
205
+ }
206
+ function slackText(severity, title, body) {
207
+ return `[${severity}] ${title}${body ? `\n${body}` : ""}`;
208
+ }
209
+ function escapeSlack(value) {
210
+ return value
211
+ .replace(/&/g, "&amp;")
212
+ .replace(/</g, "&lt;")
213
+ .replace(/>/g, "&gt;");
214
+ }
215
+ function escapeHtml(value) {
216
+ return value
217
+ .replace(/&/g, "&amp;")
218
+ .replace(/</g, "&lt;")
219
+ .replace(/>/g, "&gt;")
220
+ .replace(/"/g, "&quot;");
221
+ }
70
222
  /**
71
223
  * Read up to ~1 KB from the body for error context. Streams chunks so a
72
224
  * misbehaving endpoint returning a large error page doesn't pin that whole
@@ -1 +1 @@
1
- {"version":3,"file":"channels.js","sourceRoot":"","sources":["../../src/notifications/channels.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Built-in notification channels.\n *\n * Set environment variables to auto-register the webhook channel at startup.\n * Extra channels can be registered at any time via\n * `registerNotificationChannel()` from a server plugin.\n *\n * NOTIFICATIONS_WEBHOOK_URL → POST notifications as JSON to this URL.\n * Supports `${keys.NAME}` substitution — the raw\n * value never enters the agent context.\n * NOTIFICATIONS_WEBHOOK_AUTH → optional `Authorization` header value (also\n * supports `${keys.NAME}`).\n */\n\nimport { ssrfSafeFetch } from \"../extensions/url-safety.js\";\nimport {\n resolveKeyReferences,\n validateUrlAllowlist,\n getKeyAllowlist,\n} from \"../secrets/substitution.js\";\nimport { registerNotificationChannel } from \"./registry.js\";\nimport type { NotificationChannel } from \"./types.js\";\n\nlet _registered = false;\n\nexport function registerBuiltinNotificationChannels(): void {\n if (_registered) return;\n _registered = true;\n\n const url = process.env.NOTIFICATIONS_WEBHOOK_URL;\n if (url) {\n registerNotificationChannel(createWebhookChannel(url));\n }\n}\n\nfunction createWebhookChannel(urlTemplate: string): NotificationChannel {\n const authTemplate = process.env.NOTIFICATIONS_WEBHOOK_AUTH;\n return {\n name: \"webhook\",\n async deliver(input, meta) {\n // Resolve `${keys.NAME}` references against the owner's user-scope\n // secrets. Missing keys throw — the error surfaces in logs and the\n // channel is marked un-delivered, but other channels still run.\n const { resolved: url } = await resolveKeyReferences(\n urlTemplate,\n \"user\",\n meta.owner,\n );\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n if (authTemplate) {\n const { resolved: auth } = await resolveKeyReferences(\n authTemplate,\n \"user\",\n meta.owner,\n );\n headers.Authorization = auth;\n }\n\n // If the user set an allowlist on a referenced key, enforce it here —\n // origin-level check, same rule the automations fetch-tool applies.\n const keyNames = Array.from(\n new Set(\n Array.from(\n urlTemplate.matchAll(/\\$\\{keys\\.([A-Za-z0-9_-]+)\\}/g),\n (m) => m[1],\n ),\n ),\n );\n const allowlists = await Promise.all(\n keyNames.map((name) => getKeyAllowlist(name, \"user\", meta.owner)),\n );\n keyNames.forEach((name, i) => {\n if (!validateUrlAllowlist(url, allowlists[i])) {\n throw new Error(\n `[notifications] webhook URL ${new URL(url).origin} is not in the allowlist for key \"${name}\"`,\n );\n }\n });\n\n const res = await ssrfSafeFetch(\n url,\n {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n severity: input.severity,\n title: input.title,\n body: input.body,\n metadata: input.metadata,\n owner: meta.owner,\n emittedAt: new Date().toISOString(),\n }),\n },\n { maxRedirects: 3 },\n );\n if (!res.ok) {\n throw new Error(\n `[notifications] webhook ${new URL(url).origin} returned ${res.status}${\n (await readErrorSnippet(res)) || \"\"\n }`,\n );\n }\n },\n };\n}\n\n/**\n * Read up to ~1 KB from the body for error context. Streams chunks so a\n * misbehaving endpoint returning a large error page doesn't pin that whole\n * payload in memory per failed webhook.\n */\nasync function readErrorSnippet(res: Response): Promise<string> {\n const reader = res.body?.getReader();\n if (!reader) return \"\";\n const decoder = new TextDecoder();\n const MAX = 1024;\n let buf = \"\";\n try {\n while (buf.length < MAX) {\n const { value, done } = await reader.read();\n if (done) break;\n buf += decoder.decode(value, { stream: true });\n }\n reader.cancel().catch(() => {});\n } catch {\n return \"\";\n }\n if (!buf) return \"\";\n return `: ${buf.slice(0, 200)}`;\n}\n"]}
1
+ {"version":3,"file":"channels.js","sourceRoot":"","sources":["../../src/notifications/channels.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Built-in notification channels.\n *\n * Set environment variables to auto-register the webhook channel at startup.\n * Extra channels can be registered at any time via\n * `registerNotificationChannel()` from a server plugin.\n *\n * NOTIFICATIONS_WEBHOOK_URL → POST notifications as JSON to this URL.\n * Supports `${keys.NAME}` substitution — the raw\n * value never enters the agent context.\n * NOTIFICATIONS_WEBHOOK_AUTH → optional `Authorization` header value (also\n * supports `${keys.NAME}`).\n * NOTIFICATIONS_SLACK_WEBHOOK_URL\n * → POST notifications to a Slack incoming webhook.\n * Supports `${keys.NAME}` substitution.\n * NOTIFICATIONS_EMAIL_CHANNEL=1\n * → enable the built-in email channel for\n * per-notification recipients.\n * NOTIFICATIONS_EMAIL_RECIPIENTS\n * → comma-separated fallback recipients for email\n * notifications that do not pass\n * `metadata.emailRecipients`.\n */\n\nimport { ssrfSafeFetch } from \"../extensions/url-safety.js\";\nimport {\n resolveKeyReferences,\n validateUrlAllowlist,\n getKeyAllowlist,\n} from \"../secrets/substitution.js\";\nimport { sendEmail } from \"../server/email.js\";\nimport { registerNotificationChannel } from \"./registry.js\";\nimport type { NotificationChannel } from \"./types.js\";\n\nlet _registered = false;\n\nexport function registerBuiltinNotificationChannels(): void {\n if (_registered) return;\n _registered = true;\n\n const url = process.env.NOTIFICATIONS_WEBHOOK_URL;\n if (url) {\n registerNotificationChannel(createWebhookChannel(url));\n }\n\n const slackUrl = process.env.NOTIFICATIONS_SLACK_WEBHOOK_URL;\n if (slackUrl) {\n registerNotificationChannel(createSlackWebhookChannel(slackUrl));\n }\n\n const emailRecipients = process.env.NOTIFICATIONS_EMAIL_RECIPIENTS?.trim();\n if (process.env.NOTIFICATIONS_EMAIL_CHANNEL === \"1\" || emailRecipients) {\n registerNotificationChannel(createEmailChannel());\n }\n}\n\nfunction createWebhookChannel(urlTemplate: string): NotificationChannel {\n const authTemplate = process.env.NOTIFICATIONS_WEBHOOK_AUTH;\n return {\n name: \"webhook\",\n async deliver(input, meta) {\n const { url, headers } = await resolveWebhookRequest(\n urlTemplate,\n authTemplate,\n meta.owner,\n \"webhook\",\n );\n const res = await ssrfSafeFetch(\n url,\n {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n severity: input.severity,\n title: input.title,\n body: input.body,\n metadata: input.metadata,\n owner: meta.owner,\n emittedAt: new Date().toISOString(),\n }),\n },\n { maxRedirects: 3 },\n );\n if (!res.ok) {\n throw new Error(\n `[notifications] webhook ${new URL(url).origin} returned ${res.status}${\n (await readErrorSnippet(res)) || \"\"\n }`,\n );\n }\n },\n };\n}\n\nfunction createSlackWebhookChannel(urlTemplate: string): NotificationChannel {\n const authTemplate = process.env.NOTIFICATIONS_SLACK_WEBHOOK_AUTH;\n return {\n name: \"slack\",\n async deliver(input, meta) {\n const { url, headers } = await resolveWebhookRequest(\n urlTemplate,\n authTemplate,\n meta.owner,\n \"Slack webhook\",\n );\n const res = await ssrfSafeFetch(\n url,\n {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n text: slackText(input.severity, input.title, input.body),\n blocks: [\n {\n type: \"section\",\n text: {\n type: \"mrkdwn\",\n text: `*${escapeSlack(input.title)}*`,\n },\n },\n ...(input.body\n ? [\n {\n type: \"section\",\n text: {\n type: \"mrkdwn\",\n text: escapeSlack(input.body),\n },\n },\n ]\n : []),\n {\n type: \"context\",\n elements: [\n {\n type: \"mrkdwn\",\n text: `Severity: \\`${input.severity}\\` Owner: ${escapeSlack(meta.owner)}`,\n },\n ],\n },\n ],\n }),\n },\n { maxRedirects: 3 },\n );\n if (!res.ok) {\n throw new Error(\n `[notifications] Slack webhook ${new URL(url).origin} returned ${res.status}${\n (await readErrorSnippet(res)) || \"\"\n }`,\n );\n }\n },\n };\n}\n\nfunction createEmailChannel(): NotificationChannel {\n return {\n name: \"email\",\n async deliver(input) {\n const recipients = notificationEmailRecipients(input.metadata);\n if (recipients.length === 0) return;\n const subject =\n typeof input.metadata?.emailSubject === \"string\" &&\n input.metadata.emailSubject.trim()\n ? input.metadata.emailSubject.trim()\n : `[${input.severity}] ${input.title}`;\n const metadata = scrubEmailMetadata(input.metadata);\n const metadataText = metadata\n ? `\\n\\nMetadata:\\n${JSON.stringify(metadata, null, 2)}`\n : \"\";\n const text = `${input.title}\\n\\n${input.body ?? \"\"}${metadataText}`;\n const html = [\n `<p><strong>${escapeHtml(input.title)}</strong></p>`,\n input.body ? `<p>${escapeHtml(input.body)}</p>` : \"\",\n metadata\n ? `<pre>${escapeHtml(JSON.stringify(metadata, null, 2))}</pre>`\n : \"\",\n ].join(\"\");\n\n await Promise.all(\n recipients.map((to) =>\n sendEmail({\n to,\n subject,\n text,\n html,\n }),\n ),\n );\n },\n };\n}\n\nasync function resolveWebhookRequest(\n urlTemplate: string,\n authTemplate: string | undefined,\n owner: string,\n label: string,\n): Promise<{ url: string; headers: Record<string, string> }> {\n // Resolve `${keys.NAME}` references against the owner's user-scope secrets.\n // Missing keys throw — the error surfaces in logs and the channel is marked\n // un-delivered, but other channels still run.\n const { resolved: url } = await resolveKeyReferences(\n urlTemplate,\n \"user\",\n owner,\n );\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n if (authTemplate) {\n const { resolved: auth } = await resolveKeyReferences(\n authTemplate,\n \"user\",\n owner,\n );\n headers.Authorization = auth;\n }\n\n // If the user set an allowlist on a referenced key, enforce it here —\n // origin-level check, same rule the automations fetch-tool applies.\n const keyNames = Array.from(\n new Set(\n Array.from(urlTemplate.matchAll(/\\$\\{keys\\.([A-Za-z0-9_-]+)\\}/g), (m) =>\n String(m[1]),\n ),\n ),\n );\n const allowlists = await Promise.all(\n keyNames.map((name) => getKeyAllowlist(name, \"user\", owner)),\n );\n keyNames.forEach((name, i) => {\n if (!validateUrlAllowlist(url, allowlists[i])) {\n throw new Error(\n `[notifications] ${label} URL ${new URL(url).origin} is not in the allowlist for key \"${name}\"`,\n );\n }\n });\n return { url, headers };\n}\n\nfunction notificationEmailRecipients(\n metadata: Record<string, unknown> | undefined,\n): string[] {\n const fromMetadata = stringArray(metadata?.emailRecipients);\n const fromEnv = commaList(process.env.NOTIFICATIONS_EMAIL_RECIPIENTS);\n const seen = new Set<string>();\n const recipients: string[] = [];\n for (const raw of [...fromMetadata, ...fromEnv]) {\n const email = raw.trim().toLowerCase();\n if (!email || seen.has(email)) continue;\n seen.add(email);\n recipients.push(email);\n }\n return recipients;\n}\n\nfunction stringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.filter((item): item is string => typeof item === \"string\");\n}\n\nfunction commaList(value: string | undefined): string[] {\n return value\n ? value\n .split(\",\")\n .map((item) => item.trim())\n .filter(Boolean)\n : [];\n}\n\nfunction scrubEmailMetadata(\n metadata: Record<string, unknown> | undefined,\n): Record<string, unknown> | null {\n if (!metadata) return null;\n const entries = Object.entries(metadata).filter(\n ([key]) => key !== \"emailRecipients\" && key !== \"emailSubject\",\n );\n return entries.length ? Object.fromEntries(entries) : null;\n}\n\nfunction slackText(\n severity: string,\n title: string,\n body: string | undefined,\n): string {\n return `[${severity}] ${title}${body ? `\\n${body}` : \"\"}`;\n}\n\nfunction escapeSlack(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\n/**\n * Read up to ~1 KB from the body for error context. Streams chunks so a\n * misbehaving endpoint returning a large error page doesn't pin that whole\n * payload in memory per failed webhook.\n */\nasync function readErrorSnippet(res: Response): Promise<string> {\n const reader = res.body?.getReader();\n if (!reader) return \"\";\n const decoder = new TextDecoder();\n const MAX = 1024;\n let buf = \"\";\n try {\n while (buf.length < MAX) {\n const { value, done } = await reader.read();\n if (done) break;\n buf += decoder.decode(value, { stream: true });\n }\n reader.cancel().catch(() => {});\n } catch {\n return \"\";\n }\n if (!buf) return \"\";\n return `: ${buf.slice(0, 200)}`;\n}\n"]}
@@ -11,14 +11,14 @@
11
11
  * DELETE /_agent-native/notifications/:id — delete
12
12
  */
13
13
  export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
14
+ error?: undefined;
14
15
  count: number;
15
16
  updated?: undefined;
16
- error?: undefined;
17
17
  ok?: undefined;
18
18
  } | {
19
+ error?: undefined;
19
20
  count?: undefined;
20
21
  updated: number;
21
- error?: undefined;
22
22
  ok?: undefined;
23
23
  } | {
24
24
  count?: undefined;
@@ -26,9 +26,9 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
26
26
  error: string;
27
27
  ok?: undefined;
28
28
  } | {
29
+ error?: undefined;
29
30
  count?: undefined;
30
31
  updated?: undefined;
31
32
  ok: boolean;
32
- error?: undefined;
33
33
  }>>;
34
34
  //# sourceMappingURL=routes.d.ts.map
@@ -41,28 +41,28 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
+ error?: undefined;
45
+ ok?: undefined;
44
46
  summary: import("./types.js").TraceSummary;
45
47
  spans: import("./types.js").TraceSpan[];
46
48
  id?: undefined;
49
+ } | {
47
50
  error?: undefined;
48
51
  ok?: undefined;
49
- } | {
50
52
  summary?: undefined;
51
53
  spans?: undefined;
52
54
  id: string;
53
- error?: undefined;
54
- ok?: undefined;
55
55
  } | {
56
+ ok?: undefined;
56
57
  summary?: undefined;
57
58
  spans?: undefined;
58
59
  id?: undefined;
59
60
  error: any;
60
- ok?: undefined;
61
61
  } | {
62
+ error?: undefined;
62
63
  summary?: undefined;
63
64
  spans?: undefined;
64
65
  id?: undefined;
65
- error?: undefined;
66
66
  ok: boolean;
67
67
  }>>;
68
68
  //# sourceMappingURL=routes.d.ts.map
@@ -15,7 +15,7 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
15
15
  error: string;
16
16
  ok?: undefined;
17
17
  } | {
18
- error?: undefined;
19
18
  ok: boolean;
19
+ error?: undefined;
20
20
  }>>;
21
21
  //# sourceMappingURL=routes.d.ts.map
@@ -49,8 +49,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
49
49
  }>;
50
50
  /** DELETE /_agent-native/resources/:id — delete a resource */
51
51
  export declare function handleDeleteResource(event: any): Promise<{
52
- ok?: undefined;
53
52
  error: string;
53
+ ok?: undefined;
54
54
  } | {
55
55
  error?: undefined;
56
56
  ok: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"call-agent.d.ts","sourceRoot":"","sources":["../../src/scripts/call-agent.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AA4DpD,eAAO,MAAM,IAAI,EAAE,UAuBlB,CAAC;AAEF,wBAAsB,GAAG,CACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,CAAC,EAAE,gBAAgB,EAC1B,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CA0NjB;AAmHD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CASzE"}
1
+ {"version":3,"file":"call-agent.d.ts","sourceRoot":"","sources":["../../src/scripts/call-agent.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AA4DpD,eAAO,MAAM,IAAI,EAAE,UAuBlB,CAAC;AAEF,wBAAsB,GAAG,CACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,CAAC,EAAE,gBAAgB,EAC1B,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CA0NjB;AAmHD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CASzE"}
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { A2ATaskTimeoutError, callAgent, signA2AToken } from "../a2a/client.js";
2
+ import { A2ATaskTimeoutError, callAgent, shouldPreferGlobalA2ASecret, signA2AToken, } from "../a2a/client.js";
3
3
  import { formatLlmCredentialErrorMessage, isLlmCredentialError, } from "../agent/engine/credential-errors.js";
4
4
  import { A2A_CONTINUATION_QUEUED_MARKER } from "../integrations/a2a-continuation-marker.js";
5
5
  import { getOrgDomain, getOrgA2ASecret } from "../org/context.js";
@@ -126,7 +126,7 @@ export async function run(args, context, selfAppId) {
126
126
  try {
127
127
  apiKey = await signA2AToken(callerEmail, callerOrgDomain, callerOrgSecret, {
128
128
  expiresIn: INTEGRATION_A2A_TOKEN_TTL,
129
- preferGlobalSecret: !callerOrgSecret,
129
+ preferGlobalSecret: shouldPreferGlobalA2ASecret(callerOrgSecret),
130
130
  });
131
131
  }
132
132
  catch { }
@@ -1 +1 @@
1
- {"version":3,"file":"call-agent.js","sourceRoot":"","sources":["../../src/scripts/call-agent.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\n\nimport { A2ATaskTimeoutError, callAgent, signA2AToken } from \"../a2a/client.js\";\nimport {\n formatLlmCredentialErrorMessage,\n isLlmCredentialError,\n} from \"../agent/engine/credential-errors.js\";\nimport type { ActionRunContext } from \"../agent/production-agent.js\";\nimport type { ActionTool } from \"../agent/types.js\";\nimport { A2A_CONTINUATION_QUEUED_MARKER } from \"../integrations/a2a-continuation-marker.js\";\nimport { getOrgDomain, getOrgA2ASecret } from \"../org/context.js\";\nimport { findAgent, discoverAgents } from \"../server/agent-discovery.js\";\nimport {\n getRequestUserEmail,\n getRequestOrgId,\n isIntegrationCallerRequest,\n getIntegrationRequestContext,\n} from \"../server/request-context.js\";\n\nconst DEFAULT_SERVERLESS_INTEGRATION_A2A_TIMEOUT_MS = 18_000;\nconst NETLIFY_INTEGRATION_A2A_TIMEOUT_MS = 2_000;\nconst INTEGRATION_A2A_TOKEN_TTL = \"30m\";\n\nfunction parseTimeoutMs(value: string | undefined): number | undefined {\n if (!value) return undefined;\n const parsed = Number(value);\n if (!Number.isFinite(parsed) || parsed <= 0) return undefined;\n return Math.floor(parsed);\n}\n\nfunction isServerlessHost(): boolean {\n // Detection mirrors db/migrations.ts:297-301. On Cloudflare Workers/Pages,\n // `process.env` is shimmed and CF_PAGES isn't reliably populated at runtime —\n // the canonical signal is the `__cf_env` global injected by workerd.\n return (\n !!process.env.NETLIFY ||\n !!process.env.AWS_LAMBDA_FUNCTION_NAME ||\n !!process.env.VERCEL ||\n \"__cf_env\" in globalThis\n );\n}\n\nfunction getIntegrationCallTimeoutMs(): number | undefined {\n if (!isServerlessHost() || !isIntegrationCallerRequest()) return undefined;\n\n const configured = parseTimeoutMs(\n process.env.AGENT_NATIVE_INTEGRATION_A2A_TIMEOUT_MS,\n );\n if (configured !== undefined) return configured;\n\n // Netlify's current synchronous function budget is 60s. Keep delegated\n // calls very short so multi-agent integration requests queue downstream\n // continuations quickly instead of spending the parent Slack/email processor\n // budget waiting on separately deployed apps one-by-one.\n if (process.env.NETLIFY) return NETLIFY_INTEGRATION_A2A_TIMEOUT_MS;\n\n return DEFAULT_SERVERLESS_INTEGRATION_A2A_TIMEOUT_MS;\n}\n\nfunction formatDownstreamLlmCredentialFailure(\n agentName: string,\n value: unknown,\n): string | null {\n return isLlmCredentialError(value)\n ? formatLlmCredentialErrorMessage({ agentName })\n : null;\n}\n\nexport const tool: ActionTool = {\n description:\n \"Call a DIFFERENT, separately-deployed agent app to ask a question or delegate a task. This is strictly for cross-app A2A communication — for example, asking the mail agent to send an email while you are the calendar agent. NEVER use this to call your own app or perform actions you can do with your own tools. Using call-agent on yourself will fail and waste time. \" +\n 'For brand-consistent generated media, the first-party Assets agent is available as agent=\"assets\"; use it when another app needs generated heroes, diagrams, product shots, thumbnails, videos, or design imagery, unless the current app has its own generation action that already delegates there. ' +\n \"IMPORTANT — handling the response: \" +\n \"(a) If it contains a URL or ID, copy it VERBATIM into your reply. Do not 'correct' or pluralize the path (e.g. /deck/ → /decks/), normalize casing, or change the slug — any edit breaks the link. \" +\n '(b) If it does NOT contain a URL/ID and the user asked for one, say so explicitly (e.g. \"the agent created the deck/image but didn\\'t return a link — open the app directly to view it\"). NEVER invent a URL, slug, or path — guessing produces broken links that look real. ' +\n \"(c) If the downstream response reports missing credentials, never repeat raw env var names, Vault key names, token names, secret names, or other credential identifiers. Tell the user the target app needs its LLM/provider connection configured.\",\n parameters: {\n type: \"object\",\n properties: {\n agent: {\n type: \"string\",\n description:\n \"Name or URL of a DIFFERENT deployed agent app (e.g. 'mail', 'calendar', 'analytics'). Must not be the current app's own name.\",\n },\n message: {\n type: \"string\",\n description: \"The message/question to send to the other agent\",\n },\n },\n required: [\"agent\", \"message\"],\n },\n};\n\nexport async function run(\n args: Record<string, string>,\n context?: ActionRunContext,\n selfAppId?: string,\n): Promise<string> {\n const { agent: agentIdOrName, message } = args;\n\n if (!agentIdOrName) return \"Error: --agent is required\";\n if (!message) return \"Error: --message is required\";\n\n // Prevent self-calls — the agent must use its own registered tools instead\n if (selfAppId && agentIdOrName.toLowerCase() === selfAppId.toLowerCase()) {\n return `Error: You cannot use call-agent to call yourself (${selfAppId}). Use your own registered actions/tools instead. call-agent is only for communicating with OTHER separately-deployed apps.`;\n }\n\n const agent = await findAgent(agentIdOrName, selfAppId);\n if (!agent) {\n const available = (await discoverAgents(selfAppId))\n .map((a) => a.name)\n .join(\", \");\n return `Error: Agent \"${agentIdOrName}\" not found. Available agents: ${available || \"(none)\"}`;\n }\n\n // Append a small cross-app hint to the outgoing message so the receiving\n // agent (which may be on an older deploy without the receiver-side hint\n // in handlers.ts) still emits fully-qualified URLs. This is belt-and-\n // suspenders with the receiver hint — but it works against any current\n // deployment, no redeploy required.\n const messageWithHint =\n `${message}\\n\\n` +\n `[Note: this request comes from another app via A2A. The caller cannot see your local UI, deck list, or navigation — only the literal text you put in your reply. ` +\n `If you create or reference a deck/document/design/dashboard, include its FULLY-QUALIFIED URL (e.g. ${agent.url}/deck/<id>) in your reply, not a relative path. ` +\n `Use only artifact IDs and URL paths returned by successful actions — never invent slugs, IDs, or hosts.]`;\n\n try {\n // If we have a send context, use streaming so the UI shows progressive text\n if (context?.send) {\n const callerEmail = getRequestUserEmail();\n\n // Build metadata with identity\n const a2aMetadata: Record<string, unknown> = {};\n if (callerEmail) a2aMetadata.userEmail = callerEmail;\n\n // Include org domain for cross-app org resolution\n let callerOrgDomain: string | undefined;\n let callerOrgSecret: string | undefined;\n const orgId = getRequestOrgId();\n if (orgId) {\n try {\n const domain = await getOrgDomain(orgId);\n if (domain) {\n callerOrgDomain = domain;\n a2aMetadata.orgDomain = domain;\n }\n } catch {}\n try {\n const secret = await getOrgA2ASecret(orgId);\n if (secret) callerOrgSecret = secret;\n } catch {}\n }\n\n // Sign JWT with identity + org domain for the streaming client\n let apiKey: string | undefined;\n if (callerEmail && (callerOrgSecret || process.env.A2A_SECRET)) {\n try {\n apiKey = await signA2AToken(\n callerEmail,\n callerOrgDomain,\n callerOrgSecret,\n {\n expiresIn: INTEGRATION_A2A_TOKEN_TTL,\n preferGlobalSecret: !callerOrgSecret,\n },\n );\n } catch {}\n }\n\n if (process.env.NODE_ENV === \"production\" && callerEmail) {\n try {\n const { listOAuthAccountsByOwner } =\n await import(\"../oauth-tokens/store.js\");\n const accounts = await listOAuthAccountsByOwner(\n \"google\",\n callerEmail,\n );\n const tokens = accounts[0]?.tokens;\n if (tokens?.access_token) {\n a2aMetadata.googleToken = tokens.access_token;\n }\n } catch {}\n }\n\n let responseText = \"\";\n let lastSentLength = 0;\n const existingContinuationText =\n await formatExistingIntegrationContinuationIfRetry(agent, message);\n if (existingContinuationText) return existingContinuationText;\n\n context.send({\n type: \"agent_call\",\n agent: agent.name,\n status: \"start\",\n });\n\n const emitNewText = (newText: string) => {\n if (newText.length > lastSentLength) {\n context.send!({\n type: \"agent_call_text\",\n agent: agent.name,\n text: newText.slice(lastSentLength),\n });\n lastSentLength = newText.length;\n }\n responseText = newText;\n };\n\n // Skip the SSE streaming attempt and go straight to async + poll.\n // Why: on Netlify (Lambda), the receiving server has no streaming\n // response support, so message/stream returns a single JSON-RPC error\n // body in a 200 response that our SSE parser silently consumes — the\n // `for await` loop yields nothing AND keeps the connection open until\n // the function timeout, eating the current serverless budget. By the\n // time we get to the sync fallback, Lambda is dead and the second fetch\n // errors out as \"fetch failed\". Async+poll has its own short fetches\n // with their own budgets, so it works reliably across hosts. The\n // trade-off is we lose progressive in-UI text streaming for cross-app\n // A2A calls, but the receiving agent's full response still surfaces via\n // the tool_result event below.\n try {\n // Apply a polling cap ONLY for integration-platform callers on\n // serverless hosts. Normal chat, local Node, self-hosted Node, and\n // Docker can wait for slow-but-valid answers; integration processors\n // still need to finish before their current function execution dies.\n const callTimeoutMs = getIntegrationCallTimeoutMs();\n responseText = await callAgent(agent.url, messageWithHint, {\n apiKey,\n userEmail: callerEmail,\n orgDomain: callerOrgDomain,\n orgSecret: callerOrgSecret,\n ...(callTimeoutMs ? { timeoutMs: callTimeoutMs } : {}),\n });\n responseText =\n formatDownstreamLlmCredentialFailure(agent.name, responseText) ??\n responseText;\n // Some agents reply with relative paths (e.g. slides emits\n // \"/deck/abc\"). Those resolve against the caller's host, not the\n // receiver's, so they're broken for the user. Expand any leading-slash\n // URL into a fully-qualified one rooted at the receiving agent's host.\n responseText = expandRelativeUrls(responseText, agent.url);\n // Mirror the response into the streaming UI so the user sees it.\n if (responseText) emitNewText(responseText);\n } catch (pollErr: any) {\n const timeoutTaskId = getA2ATaskTimeoutTaskId(pollErr);\n if (timeoutTaskId) {\n const queued = await enqueueIntegrationContinuationIfPossible(\n timeoutTaskId,\n agent,\n message,\n callerEmail,\n );\n if (queued) {\n responseText =\n `${A2A_CONTINUATION_QUEUED_MARKER}\\n` +\n `The ${agent.name} agent accepted this delegated subtask and will post its own final result to the originating integration thread automatically. ` +\n `Do not call ${agent.name} again for this same subtask. Continue any other requested work, then answer with the completed results you have; if needed, mention that ${agent.name} is posting its result separately.`;\n } else {\n const reason = pollErr?.message ?? \"unknown error\";\n responseText = `The ${agent.name} agent is taking longer than expected and didn't reply in time. (${reason})`;\n }\n } else {\n const reason = pollErr?.message ?? \"unknown error\";\n responseText =\n formatDownstreamLlmCredentialFailure(agent.name, pollErr) ??\n `The ${agent.name} agent is taking longer than expected and didn't reply in time. (${reason})`;\n }\n }\n\n context.send({\n type: \"agent_call\",\n agent: agent.name,\n status: \"done\",\n });\n\n return responseText || \"(empty response)\";\n }\n\n // No context — use the async + poll call so we don't get cut off at the\n // serverless gateway's ~30s timeout. callAgent defaults to async:true.\n const email = getRequestUserEmail();\n let domain: string | undefined;\n let orgSecret: string | undefined;\n const currentOrgId = getRequestOrgId();\n if (currentOrgId) {\n try {\n domain = (await getOrgDomain(currentOrgId)) ?? undefined;\n } catch {}\n try {\n orgSecret = (await getOrgA2ASecret(currentOrgId)) ?? undefined;\n } catch {}\n }\n const response = await callAgent(agent.url, messageWithHint, {\n userEmail: email,\n orgDomain: domain,\n orgSecret,\n });\n const sanitized =\n formatDownstreamLlmCredentialFailure(agent.name, response) ?? response;\n return expandRelativeUrls(sanitized, agent.url) || \"(empty response)\";\n } catch (err: any) {\n const msg = err?.message ?? String(err);\n const credentialMessage = formatDownstreamLlmCredentialFailure(\n agent.name,\n err,\n );\n if (credentialMessage) return credentialMessage;\n // Friendlier message for the common timeout case so the calling agent can\n // decide whether to give up or retry.\n if (/timeout|did not complete|Inactivity|504/i.test(msg)) {\n return `The ${agent.name} agent is taking longer than expected. Please try again, ask a simpler question, or open the ${agent.name} app directly.`;\n }\n return `Error calling ${agent.name}: ${msg}`;\n }\n}\n\nasync function enqueueIntegrationContinuationIfPossible(\n taskId: string,\n agent: { name: string; url: string },\n message: string,\n ownerEmail: string | undefined,\n): Promise<boolean> {\n const integration = getIntegrationRequestContext();\n if (!integration || !ownerEmail) return false;\n\n try {\n const [{ insertA2AContinuation }, { dispatchA2AContinuation }] =\n await Promise.all([\n import(\"../integrations/a2a-continuations-store.js\"),\n import(\"../integrations/a2a-continuation-processor.js\"),\n ]);\n const continuation = await insertA2AContinuation({\n integrationTaskId: integration.taskId,\n platform: integration.incoming.platform,\n externalThreadId: integration.incoming.externalThreadId,\n incoming: integration.incoming,\n placeholderRef: integration.placeholderRef,\n ownerEmail,\n orgId: getRequestOrgId() ?? null,\n agentName: agent.name,\n agentUrl: agent.url,\n dedupeKey: getIntegrationContinuationDedupeKey(message),\n a2aTaskId: taskId,\n // Do not persist the short-lived JWT used for the initial send. The\n // continuation processor can mint a fresh token for each poll.\n a2aAuthToken: null,\n });\n await dispatchA2AContinuation(continuation.id).catch((err) => {\n console.error(\n `[call-agent] Failed to dispatch A2A continuation ${continuation.id}:`,\n err,\n );\n });\n return true;\n } catch (err) {\n console.error(\"[call-agent] Failed to enqueue A2A continuation:\", err);\n return false;\n }\n}\n\nfunction getA2ATaskTimeoutTaskId(err: unknown): string | null {\n if (err instanceof A2ATaskTimeoutError) return err.taskId;\n\n const candidate = err as\n | { name?: unknown; taskId?: unknown; message?: unknown }\n | null\n | undefined;\n const message = String(candidate?.message ?? \"\");\n if (\n candidate?.name === \"A2ATaskTimeoutError\" &&\n typeof candidate.taskId === \"string\"\n ) {\n return candidate.taskId;\n }\n\n const match = message.match(/^A2A task ([^\\s]+) did not complete\\b/);\n return match?.[1] ?? null;\n}\n\nasync function formatExistingIntegrationContinuationIfRetry(\n agent: {\n name: string;\n url: string;\n },\n message: string,\n): Promise<string | null> {\n const integration = getIntegrationRequestContext();\n if (!integration || (integration.attempts ?? 1) <= 1) return null;\n\n try {\n const { getA2AContinuationsForIntegrationTaskAgent } =\n await import(\"../integrations/a2a-continuations-store.js\");\n const continuations = await getA2AContinuationsForIntegrationTaskAgent(\n integration.taskId,\n agent.url,\n getIntegrationContinuationDedupeKey(message),\n );\n const active = continuations.find((continuation) =>\n [\"pending\", \"processing\", \"delivering\", \"completed\"].includes(\n continuation.status,\n ),\n );\n if (!active) return null;\n\n const state =\n active.status === \"completed\"\n ? \"already completed this delegated subtask and posted its result to the originating integration thread\"\n : \"already accepted this delegated subtask and is still working on it for the originating integration thread\";\n return (\n `${A2A_CONTINUATION_QUEUED_MARKER}\\n` +\n `The ${agent.name} agent ${state}. Do not call ${agent.name} again for this same subtask. Continue any other requested work, then answer with the completed results you have; if needed, mention that ${agent.name} is posting or has posted its result separately.`\n );\n } catch (err) {\n console.error(\"[call-agent] Failed to inspect existing continuation:\", err);\n return null;\n }\n}\n\nfunction getIntegrationContinuationDedupeKey(message: string): string {\n const normalized = message.trim().replace(/\\s+/g, \" \");\n return createHash(\"sha256\").update(normalized).digest(\"hex\");\n}\n\n// Expand bare leading-slash paths (e.g. \"/deck/abc\") into fully-qualified URLs\n// rooted at the receiving agent's host. The receiver doesn't always know it's\n// being called cross-app, so it may emit relative paths that resolve against\n// the caller's host (broken). Match a path that starts at a word boundary,\n// begins with `/`, and has at least one path segment after that. Skip if it\n// already looks like a fully-qualified URL.\nexport function expandRelativeUrls(text: string, agentUrl: string): string {\n if (!text || !agentUrl) return text;\n const base = agentUrl.replace(/\\/$/, \"\");\n // Path must start at boundary (start, whitespace, or punctuation that isn't\n // ':' — to avoid mangling `https://example.com/foo` or markdown link bodies).\n return text.replace(\n /(^|[\\s([<\"'`])(\\/[a-z0-9_-][a-z0-9_/?&=%#.,:-]*)/gi,\n (_match, lead, path) => `${lead}${base}${path}`,\n );\n}\n"]}
1
+ {"version":3,"file":"call-agent.js","sourceRoot":"","sources":["../../src/scripts/call-agent.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\n\nimport {\n A2ATaskTimeoutError,\n callAgent,\n shouldPreferGlobalA2ASecret,\n signA2AToken,\n} from \"../a2a/client.js\";\nimport {\n formatLlmCredentialErrorMessage,\n isLlmCredentialError,\n} from \"../agent/engine/credential-errors.js\";\nimport type { ActionRunContext } from \"../agent/production-agent.js\";\nimport type { ActionTool } from \"../agent/types.js\";\nimport { A2A_CONTINUATION_QUEUED_MARKER } from \"../integrations/a2a-continuation-marker.js\";\nimport { getOrgDomain, getOrgA2ASecret } from \"../org/context.js\";\nimport { findAgent, discoverAgents } from \"../server/agent-discovery.js\";\nimport {\n getRequestUserEmail,\n getRequestOrgId,\n isIntegrationCallerRequest,\n getIntegrationRequestContext,\n} from \"../server/request-context.js\";\n\nconst DEFAULT_SERVERLESS_INTEGRATION_A2A_TIMEOUT_MS = 18_000;\nconst NETLIFY_INTEGRATION_A2A_TIMEOUT_MS = 2_000;\nconst INTEGRATION_A2A_TOKEN_TTL = \"30m\";\n\nfunction parseTimeoutMs(value: string | undefined): number | undefined {\n if (!value) return undefined;\n const parsed = Number(value);\n if (!Number.isFinite(parsed) || parsed <= 0) return undefined;\n return Math.floor(parsed);\n}\n\nfunction isServerlessHost(): boolean {\n // Detection mirrors db/migrations.ts:297-301. On Cloudflare Workers/Pages,\n // `process.env` is shimmed and CF_PAGES isn't reliably populated at runtime —\n // the canonical signal is the `__cf_env` global injected by workerd.\n return (\n !!process.env.NETLIFY ||\n !!process.env.AWS_LAMBDA_FUNCTION_NAME ||\n !!process.env.VERCEL ||\n \"__cf_env\" in globalThis\n );\n}\n\nfunction getIntegrationCallTimeoutMs(): number | undefined {\n if (!isServerlessHost() || !isIntegrationCallerRequest()) return undefined;\n\n const configured = parseTimeoutMs(\n process.env.AGENT_NATIVE_INTEGRATION_A2A_TIMEOUT_MS,\n );\n if (configured !== undefined) return configured;\n\n // Netlify's current synchronous function budget is 60s. Keep delegated\n // calls very short so multi-agent integration requests queue downstream\n // continuations quickly instead of spending the parent Slack/email processor\n // budget waiting on separately deployed apps one-by-one.\n if (process.env.NETLIFY) return NETLIFY_INTEGRATION_A2A_TIMEOUT_MS;\n\n return DEFAULT_SERVERLESS_INTEGRATION_A2A_TIMEOUT_MS;\n}\n\nfunction formatDownstreamLlmCredentialFailure(\n agentName: string,\n value: unknown,\n): string | null {\n return isLlmCredentialError(value)\n ? formatLlmCredentialErrorMessage({ agentName })\n : null;\n}\n\nexport const tool: ActionTool = {\n description:\n \"Call a DIFFERENT, separately-deployed agent app to ask a question or delegate a task. This is strictly for cross-app A2A communication — for example, asking the mail agent to send an email while you are the calendar agent. NEVER use this to call your own app or perform actions you can do with your own tools. Using call-agent on yourself will fail and waste time. \" +\n 'For brand-consistent generated media, the first-party Assets agent is available as agent=\"assets\"; use it when another app needs generated heroes, diagrams, product shots, thumbnails, videos, or design imagery, unless the current app has its own generation action that already delegates there. ' +\n \"IMPORTANT — handling the response: \" +\n \"(a) If it contains a URL or ID, copy it VERBATIM into your reply. Do not 'correct' or pluralize the path (e.g. /deck/ → /decks/), normalize casing, or change the slug — any edit breaks the link. \" +\n '(b) If it does NOT contain a URL/ID and the user asked for one, say so explicitly (e.g. \"the agent created the deck/image but didn\\'t return a link — open the app directly to view it\"). NEVER invent a URL, slug, or path — guessing produces broken links that look real. ' +\n \"(c) If the downstream response reports missing credentials, never repeat raw env var names, Vault key names, token names, secret names, or other credential identifiers. Tell the user the target app needs its LLM/provider connection configured.\",\n parameters: {\n type: \"object\",\n properties: {\n agent: {\n type: \"string\",\n description:\n \"Name or URL of a DIFFERENT deployed agent app (e.g. 'mail', 'calendar', 'analytics'). Must not be the current app's own name.\",\n },\n message: {\n type: \"string\",\n description: \"The message/question to send to the other agent\",\n },\n },\n required: [\"agent\", \"message\"],\n },\n};\n\nexport async function run(\n args: Record<string, string>,\n context?: ActionRunContext,\n selfAppId?: string,\n): Promise<string> {\n const { agent: agentIdOrName, message } = args;\n\n if (!agentIdOrName) return \"Error: --agent is required\";\n if (!message) return \"Error: --message is required\";\n\n // Prevent self-calls — the agent must use its own registered tools instead\n if (selfAppId && agentIdOrName.toLowerCase() === selfAppId.toLowerCase()) {\n return `Error: You cannot use call-agent to call yourself (${selfAppId}). Use your own registered actions/tools instead. call-agent is only for communicating with OTHER separately-deployed apps.`;\n }\n\n const agent = await findAgent(agentIdOrName, selfAppId);\n if (!agent) {\n const available = (await discoverAgents(selfAppId))\n .map((a) => a.name)\n .join(\", \");\n return `Error: Agent \"${agentIdOrName}\" not found. Available agents: ${available || \"(none)\"}`;\n }\n\n // Append a small cross-app hint to the outgoing message so the receiving\n // agent (which may be on an older deploy without the receiver-side hint\n // in handlers.ts) still emits fully-qualified URLs. This is belt-and-\n // suspenders with the receiver hint — but it works against any current\n // deployment, no redeploy required.\n const messageWithHint =\n `${message}\\n\\n` +\n `[Note: this request comes from another app via A2A. The caller cannot see your local UI, deck list, or navigation — only the literal text you put in your reply. ` +\n `If you create or reference a deck/document/design/dashboard, include its FULLY-QUALIFIED URL (e.g. ${agent.url}/deck/<id>) in your reply, not a relative path. ` +\n `Use only artifact IDs and URL paths returned by successful actions — never invent slugs, IDs, or hosts.]`;\n\n try {\n // If we have a send context, use streaming so the UI shows progressive text\n if (context?.send) {\n const callerEmail = getRequestUserEmail();\n\n // Build metadata with identity\n const a2aMetadata: Record<string, unknown> = {};\n if (callerEmail) a2aMetadata.userEmail = callerEmail;\n\n // Include org domain for cross-app org resolution\n let callerOrgDomain: string | undefined;\n let callerOrgSecret: string | undefined;\n const orgId = getRequestOrgId();\n if (orgId) {\n try {\n const domain = await getOrgDomain(orgId);\n if (domain) {\n callerOrgDomain = domain;\n a2aMetadata.orgDomain = domain;\n }\n } catch {}\n try {\n const secret = await getOrgA2ASecret(orgId);\n if (secret) callerOrgSecret = secret;\n } catch {}\n }\n\n // Sign JWT with identity + org domain for the streaming client\n let apiKey: string | undefined;\n if (callerEmail && (callerOrgSecret || process.env.A2A_SECRET)) {\n try {\n apiKey = await signA2AToken(\n callerEmail,\n callerOrgDomain,\n callerOrgSecret,\n {\n expiresIn: INTEGRATION_A2A_TOKEN_TTL,\n preferGlobalSecret: shouldPreferGlobalA2ASecret(callerOrgSecret),\n },\n );\n } catch {}\n }\n\n if (process.env.NODE_ENV === \"production\" && callerEmail) {\n try {\n const { listOAuthAccountsByOwner } =\n await import(\"../oauth-tokens/store.js\");\n const accounts = await listOAuthAccountsByOwner(\n \"google\",\n callerEmail,\n );\n const tokens = accounts[0]?.tokens;\n if (tokens?.access_token) {\n a2aMetadata.googleToken = tokens.access_token;\n }\n } catch {}\n }\n\n let responseText = \"\";\n let lastSentLength = 0;\n const existingContinuationText =\n await formatExistingIntegrationContinuationIfRetry(agent, message);\n if (existingContinuationText) return existingContinuationText;\n\n context.send({\n type: \"agent_call\",\n agent: agent.name,\n status: \"start\",\n });\n\n const emitNewText = (newText: string) => {\n if (newText.length > lastSentLength) {\n context.send!({\n type: \"agent_call_text\",\n agent: agent.name,\n text: newText.slice(lastSentLength),\n });\n lastSentLength = newText.length;\n }\n responseText = newText;\n };\n\n // Skip the SSE streaming attempt and go straight to async + poll.\n // Why: on Netlify (Lambda), the receiving server has no streaming\n // response support, so message/stream returns a single JSON-RPC error\n // body in a 200 response that our SSE parser silently consumes — the\n // `for await` loop yields nothing AND keeps the connection open until\n // the function timeout, eating the current serverless budget. By the\n // time we get to the sync fallback, Lambda is dead and the second fetch\n // errors out as \"fetch failed\". Async+poll has its own short fetches\n // with their own budgets, so it works reliably across hosts. The\n // trade-off is we lose progressive in-UI text streaming for cross-app\n // A2A calls, but the receiving agent's full response still surfaces via\n // the tool_result event below.\n try {\n // Apply a polling cap ONLY for integration-platform callers on\n // serverless hosts. Normal chat, local Node, self-hosted Node, and\n // Docker can wait for slow-but-valid answers; integration processors\n // still need to finish before their current function execution dies.\n const callTimeoutMs = getIntegrationCallTimeoutMs();\n responseText = await callAgent(agent.url, messageWithHint, {\n apiKey,\n userEmail: callerEmail,\n orgDomain: callerOrgDomain,\n orgSecret: callerOrgSecret,\n ...(callTimeoutMs ? { timeoutMs: callTimeoutMs } : {}),\n });\n responseText =\n formatDownstreamLlmCredentialFailure(agent.name, responseText) ??\n responseText;\n // Some agents reply with relative paths (e.g. slides emits\n // \"/deck/abc\"). Those resolve against the caller's host, not the\n // receiver's, so they're broken for the user. Expand any leading-slash\n // URL into a fully-qualified one rooted at the receiving agent's host.\n responseText = expandRelativeUrls(responseText, agent.url);\n // Mirror the response into the streaming UI so the user sees it.\n if (responseText) emitNewText(responseText);\n } catch (pollErr: any) {\n const timeoutTaskId = getA2ATaskTimeoutTaskId(pollErr);\n if (timeoutTaskId) {\n const queued = await enqueueIntegrationContinuationIfPossible(\n timeoutTaskId,\n agent,\n message,\n callerEmail,\n );\n if (queued) {\n responseText =\n `${A2A_CONTINUATION_QUEUED_MARKER}\\n` +\n `The ${agent.name} agent accepted this delegated subtask and will post its own final result to the originating integration thread automatically. ` +\n `Do not call ${agent.name} again for this same subtask. Continue any other requested work, then answer with the completed results you have; if needed, mention that ${agent.name} is posting its result separately.`;\n } else {\n const reason = pollErr?.message ?? \"unknown error\";\n responseText = `The ${agent.name} agent is taking longer than expected and didn't reply in time. (${reason})`;\n }\n } else {\n const reason = pollErr?.message ?? \"unknown error\";\n responseText =\n formatDownstreamLlmCredentialFailure(agent.name, pollErr) ??\n `The ${agent.name} agent is taking longer than expected and didn't reply in time. (${reason})`;\n }\n }\n\n context.send({\n type: \"agent_call\",\n agent: agent.name,\n status: \"done\",\n });\n\n return responseText || \"(empty response)\";\n }\n\n // No context — use the async + poll call so we don't get cut off at the\n // serverless gateway's ~30s timeout. callAgent defaults to async:true.\n const email = getRequestUserEmail();\n let domain: string | undefined;\n let orgSecret: string | undefined;\n const currentOrgId = getRequestOrgId();\n if (currentOrgId) {\n try {\n domain = (await getOrgDomain(currentOrgId)) ?? undefined;\n } catch {}\n try {\n orgSecret = (await getOrgA2ASecret(currentOrgId)) ?? undefined;\n } catch {}\n }\n const response = await callAgent(agent.url, messageWithHint, {\n userEmail: email,\n orgDomain: domain,\n orgSecret,\n });\n const sanitized =\n formatDownstreamLlmCredentialFailure(agent.name, response) ?? response;\n return expandRelativeUrls(sanitized, agent.url) || \"(empty response)\";\n } catch (err: any) {\n const msg = err?.message ?? String(err);\n const credentialMessage = formatDownstreamLlmCredentialFailure(\n agent.name,\n err,\n );\n if (credentialMessage) return credentialMessage;\n // Friendlier message for the common timeout case so the calling agent can\n // decide whether to give up or retry.\n if (/timeout|did not complete|Inactivity|504/i.test(msg)) {\n return `The ${agent.name} agent is taking longer than expected. Please try again, ask a simpler question, or open the ${agent.name} app directly.`;\n }\n return `Error calling ${agent.name}: ${msg}`;\n }\n}\n\nasync function enqueueIntegrationContinuationIfPossible(\n taskId: string,\n agent: { name: string; url: string },\n message: string,\n ownerEmail: string | undefined,\n): Promise<boolean> {\n const integration = getIntegrationRequestContext();\n if (!integration || !ownerEmail) return false;\n\n try {\n const [{ insertA2AContinuation }, { dispatchA2AContinuation }] =\n await Promise.all([\n import(\"../integrations/a2a-continuations-store.js\"),\n import(\"../integrations/a2a-continuation-processor.js\"),\n ]);\n const continuation = await insertA2AContinuation({\n integrationTaskId: integration.taskId,\n platform: integration.incoming.platform,\n externalThreadId: integration.incoming.externalThreadId,\n incoming: integration.incoming,\n placeholderRef: integration.placeholderRef,\n ownerEmail,\n orgId: getRequestOrgId() ?? null,\n agentName: agent.name,\n agentUrl: agent.url,\n dedupeKey: getIntegrationContinuationDedupeKey(message),\n a2aTaskId: taskId,\n // Do not persist the short-lived JWT used for the initial send. The\n // continuation processor can mint a fresh token for each poll.\n a2aAuthToken: null,\n });\n await dispatchA2AContinuation(continuation.id).catch((err) => {\n console.error(\n `[call-agent] Failed to dispatch A2A continuation ${continuation.id}:`,\n err,\n );\n });\n return true;\n } catch (err) {\n console.error(\"[call-agent] Failed to enqueue A2A continuation:\", err);\n return false;\n }\n}\n\nfunction getA2ATaskTimeoutTaskId(err: unknown): string | null {\n if (err instanceof A2ATaskTimeoutError) return err.taskId;\n\n const candidate = err as\n | { name?: unknown; taskId?: unknown; message?: unknown }\n | null\n | undefined;\n const message = String(candidate?.message ?? \"\");\n if (\n candidate?.name === \"A2ATaskTimeoutError\" &&\n typeof candidate.taskId === \"string\"\n ) {\n return candidate.taskId;\n }\n\n const match = message.match(/^A2A task ([^\\s]+) did not complete\\b/);\n return match?.[1] ?? null;\n}\n\nasync function formatExistingIntegrationContinuationIfRetry(\n agent: {\n name: string;\n url: string;\n },\n message: string,\n): Promise<string | null> {\n const integration = getIntegrationRequestContext();\n if (!integration || (integration.attempts ?? 1) <= 1) return null;\n\n try {\n const { getA2AContinuationsForIntegrationTaskAgent } =\n await import(\"../integrations/a2a-continuations-store.js\");\n const continuations = await getA2AContinuationsForIntegrationTaskAgent(\n integration.taskId,\n agent.url,\n getIntegrationContinuationDedupeKey(message),\n );\n const active = continuations.find((continuation) =>\n [\"pending\", \"processing\", \"delivering\", \"completed\"].includes(\n continuation.status,\n ),\n );\n if (!active) return null;\n\n const state =\n active.status === \"completed\"\n ? \"already completed this delegated subtask and posted its result to the originating integration thread\"\n : \"already accepted this delegated subtask and is still working on it for the originating integration thread\";\n return (\n `${A2A_CONTINUATION_QUEUED_MARKER}\\n` +\n `The ${agent.name} agent ${state}. Do not call ${agent.name} again for this same subtask. Continue any other requested work, then answer with the completed results you have; if needed, mention that ${agent.name} is posting or has posted its result separately.`\n );\n } catch (err) {\n console.error(\"[call-agent] Failed to inspect existing continuation:\", err);\n return null;\n }\n}\n\nfunction getIntegrationContinuationDedupeKey(message: string): string {\n const normalized = message.trim().replace(/\\s+/g, \" \");\n return createHash(\"sha256\").update(normalized).digest(\"hex\");\n}\n\n// Expand bare leading-slash paths (e.g. \"/deck/abc\") into fully-qualified URLs\n// rooted at the receiving agent's host. The receiver doesn't always know it's\n// being called cross-app, so it may emit relative paths that resolve against\n// the caller's host (broken). Match a path that starts at a word boundary,\n// begins with `/`, and has at least one path segment after that. Skip if it\n// already looks like a fully-qualified URL.\nexport function expandRelativeUrls(text: string, agentUrl: string): string {\n if (!text || !agentUrl) return text;\n const base = agentUrl.replace(/\\/$/, \"\");\n // Path must start at boundary (start, whitespace, or punctuation that isn't\n // ':' — to avoid mangling `https://example.com/foo` or markdown link bodies).\n return text.replace(\n /(^|[\\s([<\"'`])(\\/[a-z0-9_-][a-z0-9_/?&=%#.,:-]*)/gi,\n (_match, lead, path) => `${lead}${base}${path}`,\n );\n}\n"]}
@@ -27,11 +27,11 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
27
27
  export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
28
28
  error: any;
29
29
  } | {
30
+ error?: undefined;
30
31
  ok: boolean;
31
32
  key: string;
32
33
  baseUrlKey?: string;
33
34
  scope: AgentEngineApiKeyScope;
34
- error?: undefined;
35
35
  }>>;
36
36
  export {};
37
37
  //# sourceMappingURL=agent-engine-api-key-route.d.ts.map
@@ -20,5 +20,21 @@
20
20
  * return new Response(wrapWithAnalytics(body), { ... });
21
21
  * ```
22
22
  */
23
+ /**
24
+ * Script hosts the injected GA loader pulls executable code from. Google Tag
25
+ * Manager serves `gtag/js`, and GA4 can lazy-load additional collectors from
26
+ * `www.google-analytics.com`. These must be listed in the document `script-src`
27
+ * so the CSP reflects the code the framework itself injects (see
28
+ * `applyDocumentCsp` in `ssr-handler.ts`).
29
+ */
30
+ export declare const GA_CSP_SCRIPT_HOSTS: readonly ["https://www.googletagmanager.com", "https://www.google-analytics.com"];
31
+ /**
32
+ * The exact JS body (no surrounding `<script>` tags) of the inline gtag config
33
+ * block injected next to the gtag.js loader. Returned so the SSR handler can
34
+ * hash it for the `script-src` CSP directive — the hash must be computed from
35
+ * the identical string that `getGaScript()` embeds, so both call this helper.
36
+ * Returns `null` when GA is not configured.
37
+ */
38
+ export declare function getGaInlineConfigScriptBody(): string | null;
23
39
  export declare function wrapWithAnalytics(body: ReadableStream): ReadableStream;
24
40
  //# sourceMappingURL=analytics.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/server/analytics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAwCH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,cAAc,CA4BtE"}
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/server/analytics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAoBH;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,YAC9B,kCAAkC,EAClC,kCAAkC,CAC1B,CAAC;AAEX;;;;;;GAMG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,GAAG,IAAI,CAc3D;AAaD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,cAAc,CA4BtE"}
@@ -30,23 +30,46 @@ function getGaMeasurementId() {
30
30
  ? __AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__
31
31
  : undefined));
32
32
  }
33
- function getGaScript() {
33
+ /**
34
+ * Script hosts the injected GA loader pulls executable code from. Google Tag
35
+ * Manager serves `gtag/js`, and GA4 can lazy-load additional collectors from
36
+ * `www.google-analytics.com`. These must be listed in the document `script-src`
37
+ * so the CSP reflects the code the framework itself injects (see
38
+ * `applyDocumentCsp` in `ssr-handler.ts`).
39
+ */
40
+ export const GA_CSP_SCRIPT_HOSTS = [
41
+ "https://www.googletagmanager.com",
42
+ "https://www.google-analytics.com",
43
+ ];
44
+ /**
45
+ * The exact JS body (no surrounding `<script>` tags) of the inline gtag config
46
+ * block injected next to the gtag.js loader. Returned so the SSR handler can
47
+ * hash it for the `script-src` CSP directive — the hash must be computed from
48
+ * the identical string that `getGaScript()` embeds, so both call this helper.
49
+ * Returns `null` when GA is not configured.
50
+ */
51
+ export function getGaInlineConfigScriptBody() {
34
52
  const id = getGaMeasurementId();
35
53
  if (!id)
36
54
  return null;
37
- const srcId = encodeURIComponent(id);
38
55
  const jsId = JSON.stringify(id);
39
- return (`<script async src="https://www.googletagmanager.com/gtag/js?id=${srcId}"></script>` +
40
- `<script>` +
41
- `window.dataLayer=window.dataLayer||[];` +
56
+ return (`window.dataLayer=window.dataLayer||[];` +
42
57
  `function gtag(){dataLayer.push(arguments);}` +
43
58
  `gtag('js',new Date());` +
44
59
  `gtag('config',${jsId});` +
45
60
  `if(typeof sessionStorage!=='undefined'&&sessionStorage.getItem('__an_signin')){` +
46
61
  `sessionStorage.removeItem('__an_signin');` +
47
62
  `gtag('event','sign_in');` +
48
- `}` +
49
- `</script>`);
63
+ `}`);
64
+ }
65
+ function getGaScript() {
66
+ const id = getGaMeasurementId();
67
+ if (!id)
68
+ return null;
69
+ const srcId = encodeURIComponent(id);
70
+ const inlineBody = getGaInlineConfigScriptBody();
71
+ return (`<script async src="https://www.googletagmanager.com/gtag/js?id=${srcId}"></script>` +
72
+ `<script>${inlineBody}</script>`);
50
73
  }
51
74
  export function wrapWithAnalytics(body) {
52
75
  const scripts = [getGaScript()].filter(Boolean).join("");