@agent-native/core 0.84.21 → 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 (96) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +8 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/AssistantChat.tsx +27 -4
  5. package/corpus/core/src/client/guided-questions.tsx +11 -6
  6. package/corpus/core/src/client/tool-display.ts +8 -0
  7. package/corpus/core/src/file-upload/builder.ts +7 -2
  8. package/corpus/core/src/notifications/channels.ts +235 -38
  9. package/corpus/core/src/server/analytics.ts +32 -6
  10. package/corpus/core/src/server/ssr-handler.ts +31 -12
  11. package/corpus/templates/analytics/AGENTS.md +21 -0
  12. package/corpus/templates/analytics/README.md +35 -0
  13. package/corpus/templates/analytics/actions/delete-analytics-alert-rule.ts +23 -0
  14. package/corpus/templates/analytics/actions/list-analytics-alert-rules.ts +20 -0
  15. package/corpus/templates/analytics/actions/run-analytics-alerts.ts +23 -0
  16. package/corpus/templates/analytics/actions/save-analytics-alert-rule.ts +69 -0
  17. package/corpus/templates/analytics/changelog/2026-07-01-analytics-can-alert-when-first-party-events-spike.md +6 -0
  18. package/corpus/templates/analytics/changelog/2026-07-01-session-replays-play-back-in-production-instead-of-showing.md +6 -0
  19. package/corpus/templates/analytics/netlify.toml +3 -0
  20. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +109 -0
  21. package/corpus/templates/analytics/server/db/schema.ts +55 -0
  22. package/corpus/templates/analytics/server/handlers/session-replay.ts +5 -2
  23. package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +71 -0
  24. package/corpus/templates/analytics/server/lib/analytics-alerts.ts +734 -0
  25. package/corpus/templates/analytics/server/lib/session-replay.ts +10 -3
  26. package/corpus/templates/analytics/server/plugins/analytics-alert-jobs.ts +54 -0
  27. package/corpus/templates/analytics/server/plugins/db.ts +100 -0
  28. package/corpus/templates/analytics/server/routes/api/analytics-alerts/run.post.ts +60 -0
  29. package/corpus/templates/assets/README.md +30 -8
  30. package/corpus/templates/brain/README.md +20 -552
  31. package/corpus/templates/calendar/README.md +22 -28
  32. package/corpus/templates/chat/README.md +32 -0
  33. package/corpus/templates/clips/README.md +37 -0
  34. package/corpus/templates/clips/app/components/library/library-grid.tsx +18 -7
  35. package/corpus/templates/clips/app/components/library/recording-card.tsx +42 -5
  36. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +41 -1
  37. package/corpus/templates/clips/changelog/2026-07-01-move-to-folder-now-opens-folder-choices-directly-instead-of.md +6 -0
  38. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  39. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +37 -0
  40. package/corpus/templates/content/AGENTS.md +7 -3
  41. package/corpus/templates/content/README.md +22 -34
  42. package/corpus/templates/content/actions/list-trashed-content-databases.ts +4 -0
  43. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +58 -6
  44. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +257 -24
  45. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +41 -15
  46. package/corpus/templates/content/app/i18n/zh-TW.ts +25 -7
  47. package/corpus/templates/content/app/i18n-data.ts +278 -40
  48. package/corpus/templates/content/changelog/2026-06-30-database-once-again-creates-an-inline-database-in-the-curren.md +6 -0
  49. package/corpus/templates/content/shared/api.ts +1 -0
  50. package/corpus/templates/design/AGENTS.md +7 -6
  51. package/corpus/templates/design/README.md +33 -0
  52. package/corpus/templates/design/actions/delete-file.ts +86 -10
  53. package/corpus/templates/design/actions/get-design-snapshot.ts +13 -0
  54. package/corpus/templates/design/actions/present-design-variants.ts +11 -13
  55. package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
  56. package/corpus/templates/design/app/pages/DesignEditor.tsx +2 -2
  57. package/corpus/templates/dispatch/README.md +34 -0
  58. package/corpus/templates/forms/README.md +34 -0
  59. package/corpus/templates/macros/README.md +28 -0
  60. package/corpus/templates/mail/README.md +35 -0
  61. package/corpus/templates/plan/README.md +30 -118
  62. package/corpus/templates/slides/README.md +34 -0
  63. package/corpus/templates/videos/README.md +22 -201
  64. package/dist/client/AssistantChat.d.ts.map +1 -1
  65. package/dist/client/AssistantChat.js +28 -4
  66. package/dist/client/AssistantChat.js.map +1 -1
  67. package/dist/client/guided-questions.d.ts.map +1 -1
  68. package/dist/client/guided-questions.js +10 -6
  69. package/dist/client/guided-questions.js.map +1 -1
  70. package/dist/client/tool-display.d.ts.map +1 -1
  71. package/dist/client/tool-display.js +8 -0
  72. package/dist/client/tool-display.js.map +1 -1
  73. package/dist/collab/awareness.d.ts +2 -2
  74. package/dist/collab/awareness.d.ts.map +1 -1
  75. package/dist/collab/routes.d.ts +1 -1
  76. package/dist/file-upload/builder.d.ts.map +1 -1
  77. package/dist/file-upload/builder.js +6 -2
  78. package/dist/file-upload/builder.js.map +1 -1
  79. package/dist/notifications/channels.d.ts +10 -0
  80. package/dist/notifications/channels.d.ts.map +1 -1
  81. package/dist/notifications/channels.js +172 -20
  82. package/dist/notifications/channels.js.map +1 -1
  83. package/dist/notifications/routes.d.ts +3 -3
  84. package/dist/observability/routes.d.ts +5 -5
  85. package/dist/progress/routes.d.ts +1 -1
  86. package/dist/resources/handlers.d.ts +1 -1
  87. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  88. package/dist/server/analytics.d.ts +16 -0
  89. package/dist/server/analytics.d.ts.map +1 -1
  90. package/dist/server/analytics.js +30 -7
  91. package/dist/server/analytics.js.map +1 -1
  92. package/dist/server/ssr-handler.d.ts.map +1 -1
  93. package/dist/server/ssr-handler.js +28 -12
  94. package/dist/server/ssr-handler.js.map +1 -1
  95. package/dist/server/transcribe-voice.d.ts +1 -1
  96. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"builder.js","sourceRoot":"","sources":["../../src/file-upload/builder.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n FileUploadProvider,\n FileUploadInput,\n FileUploadResult,\n ResumableUploadSession,\n ResumableChunkResult,\n} from \"./types.js\";\n\nconst DEFAULT_BUILDER_APP_HOST = \"https://builder.io\";\n\n/** Files larger than this are routed through the GCS signed-URL flow. */\nconst LARGE_FILE_THRESHOLD_BYTES = 30 * 1024 * 1024;\nconst UPLOAD_TIMEOUT_MS = 120_000;\nconst SMALL_FILE_RETRY_DELAYS_MS = [600, 1800];\n\nfunction builderUploadHost(): string {\n return (\n process.env.BUILDER_APP_HOST ||\n process.env.BUILDER_PUBLIC_APP_HOST ||\n DEFAULT_BUILDER_APP_HOST\n );\n}\n\nfunction makeBody(bytes: Uint8Array, mimeType: string): BodyInit {\n return typeof Blob !== \"undefined\"\n ? new Blob([bytes as unknown as BlobPart], { type: mimeType })\n : (bytes as unknown as BodyInit);\n}\n\nfunction shouldUseSignedUrlUpload(\n bytes: Uint8Array,\n mimeType: string,\n): boolean {\n return (\n bytes.byteLength > LARGE_FILE_THRESHOLD_BYTES || /^video\\//i.test(mimeType)\n );\n}\n\nfunction fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), UPLOAD_TIMEOUT_MS);\n return fetch(url, { ...init, signal: controller.signal }).finally(() =>\n clearTimeout(timer),\n );\n}\n\nasync function assertOk(res: Response, label: string): Promise<void> {\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n throw new Error(`${label} (${res.status}): ${body || res.statusText}`);\n }\n}\n\nasync function uploadLargeFileViaSignedUrl(\n input: FileUploadInput,\n privateKey: string,\n bareMimeType: string,\n bytes: Uint8Array,\n): Promise<FileUploadResult> {\n const name = input.filename ?? \"upload\";\n const mb = (bytes.byteLength / (1024 * 1024)).toFixed(1);\n\n console.log(\n `[builder-upload] large-file path: ${name} ${mb}MB ${bareMimeType}`,\n );\n\n // Step 1 — request a signed URL.\n console.log(`[builder-upload] step 1: requesting signed URL`);\n const { uploadUrl, assetId, requiredHeaders } = await requestBuilderSignedUrl(\n privateKey,\n name,\n bareMimeType,\n bytes.byteLength,\n );\n console.log(`[builder-upload] step 1 ok: assetId=${assetId}`);\n\n // Step 2 — PUT bytes directly to GCS. Only requiredHeaders; no Authorization\n // (signed URL carries its own auth — extra signed headers break the signature).\n console.log(`[builder-upload] step 2 [${assetId}]: PUT ${mb}MB to GCS`);\n const step2Res = await fetchWithTimeout(uploadUrl, {\n method: \"PUT\",\n headers: requiredHeaders,\n body: makeBody(bytes, bareMimeType),\n });\n await assertOk(step2Res, \"GCS upload failed\");\n console.log(\n `[builder-upload] step 2 ok [${assetId}]: GCS ${step2Res.status} etag=${step2Res.headers.get(\"etag\") ?? \"none\"}`,\n );\n\n // Step 3 — register the asset and get the CDN URL.\n console.log(\n `[builder-upload] step 3: registering asset - ${assetId}, ${input.filename}`,\n );\n const { url, id } = await completeBuilderUpload(\n privateKey,\n assetId,\n input.filename,\n { skipCompressionWait: input.skipCompressionWait },\n );\n console.log(`[builder-upload] done [${assetId}]: ${url}`);\n return { url, id, provider: \"builder\" };\n}\n\nasync function requestBuilderSignedUrl(\n privateKey: string,\n filename: string,\n mimeType: string,\n size: number,\n resumable = false,\n): Promise<{\n uploadUrl: string;\n assetId: string;\n requiredHeaders: Record<string, string>;\n}> {\n const host = builderUploadHost();\n const url = new URL(\"/api/v1/upload/signed-url\", host);\n const res = await fetchWithTimeout(url.toString(), {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${privateKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n fileName: filename,\n contentType: mimeType,\n size,\n resumable,\n }),\n });\n await assertOk(res, \"Builder.io signed-URL request failed\");\n const json = (await res.json()) as {\n uploadUrl?: string;\n assetId?: string;\n requiredHeaders?: Record<string, string>;\n };\n if (!json.uploadUrl || !json.assetId || !json.requiredHeaders) {\n throw new Error(\n `Builder.io signed-URL response missing required fields: ${JSON.stringify(Object.keys(json))}`,\n );\n }\n return {\n uploadUrl: json.uploadUrl,\n assetId: json.assetId,\n requiredHeaders: json.requiredHeaders,\n };\n}\n\nasync function completeBuilderUpload(\n privateKey: string,\n assetId: string,\n filename: string | undefined,\n options?: { skipCompressionWait?: boolean },\n): Promise<{ url: string; id?: string }> {\n const host = builderUploadHost();\n const url = new URL(\"/api/v1/upload/complete\", host);\n if (options?.skipCompressionWait) {\n url.searchParams.set(\"skipCompressionWait\", \"true\");\n }\n const res = await fetchWithTimeout(url.toString(), {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${privateKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ assetId, name: filename }),\n });\n await assertOk(res, \"Builder.io upload complete failed\");\n const json = (await res.json()) as { url?: string; id?: string };\n if (!json.url) throw new Error(\"Builder.io upload/complete returned no URL\");\n return { url: json.url, id: json.id };\n}\n\n// Retry transient 5xx once with backoff. Builder.io's upload service\n// occasionally returns a bodyless 500 (\"Internal Error\") on the first\n// attempt — usually GCS write hiccups that succeed on retry.\nasync function uploadSmallFile(url: URL, init: RequestInit): Promise<Response> {\n let response: Response | null = null;\n let lastErrorBody = \"\";\n\n for (\n let attempt = 0;\n attempt <= SMALL_FILE_RETRY_DELAYS_MS.length;\n attempt++\n ) {\n const retryDelay = SMALL_FILE_RETRY_DELAYS_MS[attempt]; // undefined on last attempt\n try {\n response = await fetchWithTimeout(url.toString(), init);\n } catch (err) {\n if (!retryDelay) throw err;\n await new Promise((r) => setTimeout(r, retryDelay));\n continue;\n }\n if (response.ok) return response;\n lastErrorBody = await response.text().catch(() => \"\");\n const isTransient = response.status >= 500 && response.status !== 501;\n if (!isTransient || !retryDelay) break;\n await new Promise((r) => setTimeout(r, retryDelay));\n }\n\n const status = response?.status ?? 0;\n const statusText = response?.statusText ?? \"no response\";\n throw new Error(\n `Builder.io upload failed (${status}): ${lastErrorBody || statusText}`,\n );\n}\n\n/**\n * Built-in Builder.io file upload provider.\n * Uses the same BUILDER_PRIVATE_KEY as the browser/background-agent flows,\n * so connecting Builder once (via the sidebar \"Connect Builder\" action)\n * automatically enables file uploads.\n *\n * Upload API: https://www.builder.io/c/docs/upload-api\n */\nexport const builderFileUploadProvider: FileUploadProvider = {\n id: \"builder\",\n name: \"Builder.io\",\n isConfigured: () => !!process.env.BUILDER_PRIVATE_KEY,\n upload: async (input: FileUploadInput) => {\n const { data, filename, mimeType } = input;\n const { resolveBuilderPrivateKey } =\n await import(\"../server/credential-provider.js\");\n const privateKey = await resolveBuilderPrivateKey();\n if (!privateKey) {\n throw new Error(\"BUILDER_PRIVATE_KEY is not set\");\n }\n\n // Strip any media-type parameters (e.g. `;codecs=avc1,opus` from\n // MediaRecorder blobs) — Builder's upload API parses the body as raw\n // binary only when Content-Type is a bare MIME type. A parameterized\n // Content-Type falls through to the multipart/base64 paths which look\n // for an `image` field, and returns \"No image specified\" when it\n // doesn't find one.\n const bareMimeType = (mimeType || \"application/octet-stream\")\n .split(\";\")[0]\n .trim();\n\n const bytes =\n data instanceof Uint8Array ? data : new Uint8Array(data as any);\n const mb = (bytes.byteLength / (1024 * 1024)).toFixed(1);\n\n if (shouldUseSignedUrlUpload(bytes, bareMimeType)) {\n return uploadLargeFileViaSignedUrl(\n input,\n privateKey,\n bareMimeType,\n bytes,\n );\n }\n\n console.log(\n `[builder-upload] small-file path: ${filename ?? \"upload\"} ${mb}MB ${bareMimeType}`,\n );\n\n const url = new URL(\"/api/v1/upload\", builderUploadHost());\n if (filename) url.searchParams.set(\"name\", filename);\n if (input.skipCompressionWait) {\n url.searchParams.set(\"skipCompressionWait\", \"true\");\n }\n\n const response = await uploadSmallFile(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${privateKey}`,\n \"Content-Type\": bareMimeType,\n },\n body: makeBody(bytes, bareMimeType),\n });\n\n const json = (await response.json().catch(() => ({}))) as {\n url?: string;\n id?: string;\n };\n if (!json.url) throw new Error(\"Builder.io upload returned no URL\");\n\n console.log(`[builder-upload] done: ${json.url}`);\n return { url: json.url, id: json.id, provider: \"builder\" };\n },\n\n resumable: {\n async startSession(filename, mimeType, maxBytes) {\n const { resolveBuilderPrivateKey } =\n await import(\"../server/credential-provider.js\");\n const privateKey = await resolveBuilderPrivateKey();\n if (!privateKey) throw new Error(\"BUILDER_PRIVATE_KEY is not set\");\n\n console.log(\n `[builder-resumable] starting session: ${filename} ${mimeType} ${maxBytes} bytes`,\n );\n const { uploadUrl, assetId, requiredHeaders } =\n await requestBuilderSignedUrl(\n privateKey,\n filename,\n mimeType,\n maxBytes,\n true,\n );\n console.log(`[builder-resumable] session step 1 ok: assetId=${assetId}`);\n\n const initHeaders: Record<string, string> = {\n \"Content-Type\": mimeType,\n \"x-goog-resumable\": \"start\",\n };\n const contentLengthRange =\n requiredHeaders?.[\"x-goog-content-length-range\"];\n if (contentLengthRange)\n initHeaders[\"x-goog-content-length-range\"] = contentLengthRange;\n\n console.log(`[builder-resumable] session step 2: initiating GCS session`);\n const initRes = await fetchWithTimeout(uploadUrl, {\n method: \"POST\",\n headers: initHeaders,\n body: new Uint8Array(0),\n });\n if (!initRes.ok) {\n const body = await initRes.text().catch(() => \"\");\n throw new Error(\n `GCS resumable session initiation failed (${initRes.status}): ${body}`,\n );\n }\n const sessionUri = initRes.headers.get(\"location\");\n if (!sessionUri)\n throw new Error(\n \"GCS did not return a Location header for the resumable session\",\n );\n\n console.log(`[builder-resumable] session ready: assetId=${assetId}`);\n return {\n sessionId: sessionUri,\n meta: { assetId, filename, mimeType },\n } satisfies ResumableUploadSession;\n },\n\n async relayChunk(session, contentRange, bytes, options) {\n const sessionUri = session.sessionId;\n const MAX_ATTEMPTS = 4;\n const RETRYABLE = new Set([408, 429, 500, 502, 503, 504]);\n const delayMs = (attempt: number) =>\n Math.min(2000, 300 * 2 ** (attempt - 1));\n\n let lastError: unknown = null;\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n try {\n const headers: Record<string, string> = {\n \"Content-Range\": contentRange,\n };\n if (options?.mimeType) headers[\"Content-Type\"] = options.mimeType;\n const res = await fetch(sessionUri, {\n method: \"PUT\",\n headers,\n body: bytes as unknown as BodyInit,\n });\n if (res.status === 308 || res.ok)\n return {\n ok: true,\n status: res.status,\n } satisfies ResumableChunkResult;\n if (RETRYABLE.has(res.status) && attempt < MAX_ATTEMPTS) {\n await res.text().catch(() => \"\");\n console.warn(\n `[builder-resumable] transient ${res.status} on attempt ${attempt}, retrying`,\n );\n await new Promise((r) => setTimeout(r, delayMs(attempt)));\n continue;\n }\n return {\n ok: false,\n status: res.status,\n } satisfies ResumableChunkResult;\n } catch (err) {\n lastError = err;\n if (attempt >= MAX_ATTEMPTS) break;\n console.warn(\n `[builder-resumable] network error on attempt ${attempt}:`,\n err instanceof Error ? err.message : String(err),\n );\n await new Promise((r) => setTimeout(r, delayMs(attempt)));\n }\n }\n throw lastError instanceof Error\n ? lastError\n : new Error(\"GCS PUT failed after retries\");\n },\n\n async completeSession(session, filename, options) {\n const { resolveBuilderPrivateKey } =\n await import(\"../server/credential-provider.js\");\n const privateKey = await resolveBuilderPrivateKey();\n if (!privateKey) throw new Error(\"BUILDER_PRIVATE_KEY is not set\");\n\n const assetId = session.meta.assetId as string;\n console.log(`[builder-resumable] completing upload: assetId=${assetId}`);\n const { url } = await completeBuilderUpload(\n privateKey,\n assetId,\n filename,\n {\n skipCompressionWait:\n options?.skipCompressionWait ||\n session.meta.skipCompressionWait === true,\n },\n );\n console.log(`[builder-resumable] upload complete: ${url}`);\n return url;\n },\n },\n};\n"]}
1
+ {"version":3,"file":"builder.js","sourceRoot":"","sources":["../../src/file-upload/builder.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n FileUploadProvider,\n FileUploadInput,\n FileUploadResult,\n ResumableUploadSession,\n ResumableChunkResult,\n} from \"./types.js\";\n\nconst DEFAULT_BUILDER_APP_HOST = \"https://builder.io\";\n\n/** Files larger than this are routed through the GCS signed-URL flow. */\nconst LARGE_FILE_THRESHOLD_BYTES = 30 * 1024 * 1024;\nconst UPLOAD_TIMEOUT_MS = 120_000;\nconst SMALL_FILE_RETRY_DELAYS_MS = [600, 1800];\n\nfunction builderUploadHost(): string {\n return (\n process.env.BUILDER_APP_HOST ||\n process.env.BUILDER_PUBLIC_APP_HOST ||\n DEFAULT_BUILDER_APP_HOST\n );\n}\n\nfunction makeBody(bytes: Uint8Array, mimeType: string): BodyInit {\n return typeof Blob !== \"undefined\"\n ? new Blob([bytes as unknown as BlobPart], { type: mimeType })\n : (bytes as unknown as BodyInit);\n}\n\nfunction shouldUseSignedUrlUpload(\n bytes: Uint8Array,\n mimeType: string,\n): boolean {\n return (\n bytes.byteLength > LARGE_FILE_THRESHOLD_BYTES || /^video\\//i.test(mimeType)\n );\n}\n\nfunction fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), UPLOAD_TIMEOUT_MS);\n return fetch(url, { ...init, signal: controller.signal }).finally(() =>\n clearTimeout(timer),\n );\n}\n\nfunction setSkipCompressionQueryParams(url: URL): void {\n url.searchParams.set(\"skipCompressionWait\", \"true\");\n url.searchParams.set(\"skipCompression\", \"true\");\n}\n\nasync function assertOk(res: Response, label: string): Promise<void> {\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n throw new Error(`${label} (${res.status}): ${body || res.statusText}`);\n }\n}\n\nasync function uploadLargeFileViaSignedUrl(\n input: FileUploadInput,\n privateKey: string,\n bareMimeType: string,\n bytes: Uint8Array,\n): Promise<FileUploadResult> {\n const name = input.filename ?? \"upload\";\n const mb = (bytes.byteLength / (1024 * 1024)).toFixed(1);\n\n console.log(\n `[builder-upload] large-file path: ${name} ${mb}MB ${bareMimeType}`,\n );\n\n // Step 1 — request a signed URL.\n console.log(`[builder-upload] step 1: requesting signed URL`);\n const { uploadUrl, assetId, requiredHeaders } = await requestBuilderSignedUrl(\n privateKey,\n name,\n bareMimeType,\n bytes.byteLength,\n );\n console.log(`[builder-upload] step 1 ok: assetId=${assetId}`);\n\n // Step 2 — PUT bytes directly to GCS. Only requiredHeaders; no Authorization\n // (signed URL carries its own auth — extra signed headers break the signature).\n console.log(`[builder-upload] step 2 [${assetId}]: PUT ${mb}MB to GCS`);\n const step2Res = await fetchWithTimeout(uploadUrl, {\n method: \"PUT\",\n headers: requiredHeaders,\n body: makeBody(bytes, bareMimeType),\n });\n await assertOk(step2Res, \"GCS upload failed\");\n console.log(\n `[builder-upload] step 2 ok [${assetId}]: GCS ${step2Res.status} etag=${step2Res.headers.get(\"etag\") ?? \"none\"}`,\n );\n\n // Step 3 — register the asset and get the CDN URL.\n console.log(\n `[builder-upload] step 3: registering asset - ${assetId}, ${input.filename}`,\n );\n const { url, id } = await completeBuilderUpload(\n privateKey,\n assetId,\n input.filename,\n { skipCompressionWait: input.skipCompressionWait },\n );\n console.log(`[builder-upload] done [${assetId}]: ${url}`);\n return { url, id, provider: \"builder\" };\n}\n\nasync function requestBuilderSignedUrl(\n privateKey: string,\n filename: string,\n mimeType: string,\n size: number,\n resumable = false,\n): Promise<{\n uploadUrl: string;\n assetId: string;\n requiredHeaders: Record<string, string>;\n}> {\n const host = builderUploadHost();\n const url = new URL(\"/api/v1/upload/signed-url\", host);\n const res = await fetchWithTimeout(url.toString(), {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${privateKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n fileName: filename,\n contentType: mimeType,\n size,\n resumable,\n }),\n });\n await assertOk(res, \"Builder.io signed-URL request failed\");\n const json = (await res.json()) as {\n uploadUrl?: string;\n assetId?: string;\n requiredHeaders?: Record<string, string>;\n };\n if (!json.uploadUrl || !json.assetId || !json.requiredHeaders) {\n throw new Error(\n `Builder.io signed-URL response missing required fields: ${JSON.stringify(Object.keys(json))}`,\n );\n }\n return {\n uploadUrl: json.uploadUrl,\n assetId: json.assetId,\n requiredHeaders: json.requiredHeaders,\n };\n}\n\nasync function completeBuilderUpload(\n privateKey: string,\n assetId: string,\n filename: string | undefined,\n options?: { skipCompressionWait?: boolean },\n): Promise<{ url: string; id?: string }> {\n const host = builderUploadHost();\n const url = new URL(\"/api/v1/upload/complete\", host);\n if (options?.skipCompressionWait) {\n setSkipCompressionQueryParams(url);\n }\n const res = await fetchWithTimeout(url.toString(), {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${privateKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ assetId, name: filename }),\n });\n await assertOk(res, \"Builder.io upload complete failed\");\n const json = (await res.json()) as { url?: string; id?: string };\n if (!json.url) throw new Error(\"Builder.io upload/complete returned no URL\");\n return { url: json.url, id: json.id };\n}\n\n// Retry transient 5xx once with backoff. Builder.io's upload service\n// occasionally returns a bodyless 500 (\"Internal Error\") on the first\n// attempt — usually GCS write hiccups that succeed on retry.\nasync function uploadSmallFile(url: URL, init: RequestInit): Promise<Response> {\n let response: Response | null = null;\n let lastErrorBody = \"\";\n\n for (\n let attempt = 0;\n attempt <= SMALL_FILE_RETRY_DELAYS_MS.length;\n attempt++\n ) {\n const retryDelay = SMALL_FILE_RETRY_DELAYS_MS[attempt]; // undefined on last attempt\n try {\n response = await fetchWithTimeout(url.toString(), init);\n } catch (err) {\n if (!retryDelay) throw err;\n await new Promise((r) => setTimeout(r, retryDelay));\n continue;\n }\n if (response.ok) return response;\n lastErrorBody = await response.text().catch(() => \"\");\n const isTransient = response.status >= 500 && response.status !== 501;\n if (!isTransient || !retryDelay) break;\n await new Promise((r) => setTimeout(r, retryDelay));\n }\n\n const status = response?.status ?? 0;\n const statusText = response?.statusText ?? \"no response\";\n throw new Error(\n `Builder.io upload failed (${status}): ${lastErrorBody || statusText}`,\n );\n}\n\n/**\n * Built-in Builder.io file upload provider.\n * Uses the same BUILDER_PRIVATE_KEY as the browser/background-agent flows,\n * so connecting Builder once (via the sidebar \"Connect Builder\" action)\n * automatically enables file uploads.\n *\n * Upload API: https://www.builder.io/c/docs/upload-api\n */\nexport const builderFileUploadProvider: FileUploadProvider = {\n id: \"builder\",\n name: \"Builder.io\",\n isConfigured: () => !!process.env.BUILDER_PRIVATE_KEY,\n upload: async (input: FileUploadInput) => {\n const { data, filename, mimeType } = input;\n const { resolveBuilderPrivateKey } =\n await import(\"../server/credential-provider.js\");\n const privateKey = await resolveBuilderPrivateKey();\n if (!privateKey) {\n throw new Error(\"BUILDER_PRIVATE_KEY is not set\");\n }\n\n // Strip any media-type parameters (e.g. `;codecs=avc1,opus` from\n // MediaRecorder blobs) — Builder's upload API parses the body as raw\n // binary only when Content-Type is a bare MIME type. A parameterized\n // Content-Type falls through to the multipart/base64 paths which look\n // for an `image` field, and returns \"No image specified\" when it\n // doesn't find one.\n const bareMimeType = (mimeType || \"application/octet-stream\")\n .split(\";\")[0]\n .trim();\n\n const bytes =\n data instanceof Uint8Array ? data : new Uint8Array(data as any);\n const mb = (bytes.byteLength / (1024 * 1024)).toFixed(1);\n\n if (shouldUseSignedUrlUpload(bytes, bareMimeType)) {\n return uploadLargeFileViaSignedUrl(\n input,\n privateKey,\n bareMimeType,\n bytes,\n );\n }\n\n console.log(\n `[builder-upload] small-file path: ${filename ?? \"upload\"} ${mb}MB ${bareMimeType}`,\n );\n\n const url = new URL(\"/api/v1/upload\", builderUploadHost());\n if (filename) url.searchParams.set(\"name\", filename);\n if (input.skipCompressionWait) {\n setSkipCompressionQueryParams(url);\n }\n\n const response = await uploadSmallFile(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${privateKey}`,\n \"Content-Type\": bareMimeType,\n },\n body: makeBody(bytes, bareMimeType),\n });\n\n const json = (await response.json().catch(() => ({}))) as {\n url?: string;\n id?: string;\n };\n if (!json.url) throw new Error(\"Builder.io upload returned no URL\");\n\n console.log(`[builder-upload] done: ${json.url}`);\n return { url: json.url, id: json.id, provider: \"builder\" };\n },\n\n resumable: {\n async startSession(filename, mimeType, maxBytes) {\n const { resolveBuilderPrivateKey } =\n await import(\"../server/credential-provider.js\");\n const privateKey = await resolveBuilderPrivateKey();\n if (!privateKey) throw new Error(\"BUILDER_PRIVATE_KEY is not set\");\n\n console.log(\n `[builder-resumable] starting session: ${filename} ${mimeType} ${maxBytes} bytes`,\n );\n const { uploadUrl, assetId, requiredHeaders } =\n await requestBuilderSignedUrl(\n privateKey,\n filename,\n mimeType,\n maxBytes,\n true,\n );\n console.log(`[builder-resumable] session step 1 ok: assetId=${assetId}`);\n\n const initHeaders: Record<string, string> = {\n \"Content-Type\": mimeType,\n \"x-goog-resumable\": \"start\",\n };\n const contentLengthRange =\n requiredHeaders?.[\"x-goog-content-length-range\"];\n if (contentLengthRange)\n initHeaders[\"x-goog-content-length-range\"] = contentLengthRange;\n\n console.log(`[builder-resumable] session step 2: initiating GCS session`);\n const initRes = await fetchWithTimeout(uploadUrl, {\n method: \"POST\",\n headers: initHeaders,\n body: new Uint8Array(0),\n });\n if (!initRes.ok) {\n const body = await initRes.text().catch(() => \"\");\n throw new Error(\n `GCS resumable session initiation failed (${initRes.status}): ${body}`,\n );\n }\n const sessionUri = initRes.headers.get(\"location\");\n if (!sessionUri)\n throw new Error(\n \"GCS did not return a Location header for the resumable session\",\n );\n\n console.log(`[builder-resumable] session ready: assetId=${assetId}`);\n return {\n sessionId: sessionUri,\n meta: { assetId, filename, mimeType },\n } satisfies ResumableUploadSession;\n },\n\n async relayChunk(session, contentRange, bytes, options) {\n const sessionUri = session.sessionId;\n const MAX_ATTEMPTS = 4;\n const RETRYABLE = new Set([408, 429, 500, 502, 503, 504]);\n const delayMs = (attempt: number) =>\n Math.min(2000, 300 * 2 ** (attempt - 1));\n\n let lastError: unknown = null;\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n try {\n const headers: Record<string, string> = {\n \"Content-Range\": contentRange,\n };\n if (options?.mimeType) headers[\"Content-Type\"] = options.mimeType;\n const res = await fetch(sessionUri, {\n method: \"PUT\",\n headers,\n body: bytes as unknown as BodyInit,\n });\n if (res.status === 308 || res.ok)\n return {\n ok: true,\n status: res.status,\n } satisfies ResumableChunkResult;\n if (RETRYABLE.has(res.status) && attempt < MAX_ATTEMPTS) {\n await res.text().catch(() => \"\");\n console.warn(\n `[builder-resumable] transient ${res.status} on attempt ${attempt}, retrying`,\n );\n await new Promise((r) => setTimeout(r, delayMs(attempt)));\n continue;\n }\n return {\n ok: false,\n status: res.status,\n } satisfies ResumableChunkResult;\n } catch (err) {\n lastError = err;\n if (attempt >= MAX_ATTEMPTS) break;\n console.warn(\n `[builder-resumable] network error on attempt ${attempt}:`,\n err instanceof Error ? err.message : String(err),\n );\n await new Promise((r) => setTimeout(r, delayMs(attempt)));\n }\n }\n throw lastError instanceof Error\n ? lastError\n : new Error(\"GCS PUT failed after retries\");\n },\n\n async completeSession(session, filename, options) {\n const { resolveBuilderPrivateKey } =\n await import(\"../server/credential-provider.js\");\n const privateKey = await resolveBuilderPrivateKey();\n if (!privateKey) throw new Error(\"BUILDER_PRIVATE_KEY is not set\");\n\n const assetId = session.meta.assetId as string;\n console.log(`[builder-resumable] completing upload: assetId=${assetId}`);\n const { url } = await completeBuilderUpload(\n privateKey,\n assetId,\n filename,\n {\n skipCompressionWait:\n options?.skipCompressionWait ||\n session.meta.skipCompressionWait === true,\n },\n );\n console.log(`[builder-resumable] upload complete: ${url}`);\n return url;\n },\n },\n};\n"]}
@@ -10,6 +10,16 @@
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
  export declare function registerBuiltinNotificationChannels(): void;
15
25
  //# sourceMappingURL=channels.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"channels.d.ts","sourceRoot":"","sources":["../../src/notifications/channels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAaH,wBAAgB,mCAAmC,IAAI,IAAI,CAQ1D"}
1
+ {"version":3,"file":"channels.d.ts","sourceRoot":"","sources":["../../src/notifications/channels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAcH,wBAAgB,mCAAmC,IAAI,IAAI,CAkB1D"}
@@ -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;
@@ -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("");