@agent-native/core 0.133.2 → 0.134.0

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 (213) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +62 -0
  3. package/corpus/core/docs/content/observability.mdx +41 -1
  4. package/corpus/core/docs/content/tracking.mdx +25 -0
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/agent/run-manager.ts +7 -0
  7. package/corpus/core/src/client/agent-chat-adapter.ts +10 -0
  8. package/corpus/core/src/client/analytics.ts +105 -1
  9. package/corpus/core/src/client/use-action.ts +6 -0
  10. package/corpus/core/src/deploy/build.ts +38 -1
  11. package/corpus/core/src/observability/posthog-ai.ts +294 -0
  12. package/corpus/core/src/observability/routes.ts +29 -6
  13. package/corpus/core/src/observability/traces.ts +287 -12
  14. package/corpus/core/src/observability/types.ts +11 -0
  15. package/corpus/core/src/secrets/register-framework-secrets.ts +16 -0
  16. package/corpus/core/src/server/agent-run-context.ts +21 -0
  17. package/corpus/core/src/server/capture-error.ts +8 -0
  18. package/corpus/core/src/server/core-routes-plugin.ts +75 -4
  19. package/corpus/core/src/server/deploy-environment.ts +57 -0
  20. package/corpus/core/src/server/error-noise-filter.ts +268 -0
  21. package/corpus/core/src/server/posthog-config.ts +75 -0
  22. package/corpus/core/src/server/request-context.ts +6 -0
  23. package/corpus/core/src/server/sentry-config.ts +4 -8
  24. package/corpus/core/src/server/sentry-plugin.ts +13 -29
  25. package/corpus/core/src/server/sentry.ts +13 -156
  26. package/corpus/core/src/server/ssr-handler.ts +6 -1
  27. package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
  28. package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
  29. package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
  30. package/corpus/core/src/tracking/error-capture.ts +44 -109
  31. package/corpus/core/src/tracking/index.ts +11 -0
  32. package/corpus/core/src/tracking/posthog-exception.ts +305 -0
  33. package/corpus/core/src/tracking/providers.ts +91 -14
  34. package/corpus/core/src/tracking/redaction.ts +105 -0
  35. package/corpus/templates/content/actions/_batch-utils.ts +33 -0
  36. package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -81
  37. package/corpus/templates/content/actions/_content-files.ts +31 -21
  38. package/corpus/templates/content/actions/_database-source-utils.ts +653 -155
  39. package/corpus/templates/content/actions/_database-utils.ts +535 -41
  40. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +291 -240
  41. package/corpus/templates/content/actions/add-database-item.ts +15 -1
  42. package/corpus/templates/content/actions/attach-content-database-source.ts +156 -58
  43. package/corpus/templates/content/actions/bind-content-database-source-field.ts +2 -2
  44. package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +4 -1
  45. package/corpus/templates/content/actions/delete-database-items.ts +8 -2
  46. package/corpus/templates/content/actions/disconnect-content-database-source.ts +2 -2
  47. package/corpus/templates/content/actions/duplicate-database-item.ts +15 -1
  48. package/corpus/templates/content/actions/duplicate-database-items.ts +19 -1
  49. package/corpus/templates/content/actions/execute-builder-source-execution.ts +2 -1
  50. package/corpus/templates/content/actions/get-content-database.ts +12 -64
  51. package/corpus/templates/content/actions/move-database-item.ts +4 -1
  52. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +1 -1
  53. package/corpus/templates/content/actions/prepare-builder-source-review.ts +4 -8
  54. package/corpus/templates/content/actions/preview-content-database-source-attach.ts +92 -0
  55. package/corpus/templates/content/actions/process-builder-body-hydration.ts +2 -1
  56. package/corpus/templates/content/actions/query-content-database-items.ts +64 -0
  57. package/corpus/templates/content/actions/refresh-content-database-source.ts +7 -0
  58. package/corpus/templates/content/actions/review-content-database-source-change-set.ts +1 -1
  59. package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +1 -1
  60. package/corpus/templates/content/actions/stage-builder-revision.ts +1 -1
  61. package/corpus/templates/content/actions/update-content-database-personal-view.ts +29 -3
  62. package/corpus/templates/content/actions/update-content-database-view.ts +1 -1
  63. package/corpus/templates/content/actions/validate-builder-source-execution.ts +1 -1
  64. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +3 -22
  65. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +267 -105
  66. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +82 -1
  67. package/corpus/templates/content/app/hooks/use-content-database.ts +285 -24
  68. package/corpus/templates/content/app/hooks/use-document-properties.ts +7 -6
  69. package/corpus/templates/content/app/i18n-data.ts +10 -0
  70. package/corpus/templates/content/changelog/2026-07-29-large-databases-keep-useful-rows-visible.md +6 -0
  71. package/corpus/templates/content/changelog/2026-07-30-builder-source-columns-now-appear-immediately-when-connected.md +6 -0
  72. package/corpus/templates/content/changelog/2026-07-30-large-builder-backed-tables-show-useful-rows-sooner-and-fini.md +6 -0
  73. package/corpus/templates/content/changelog/2026-08-01-large-builder-databases-now-show-rows-immediately-and-finish.md +6 -0
  74. package/corpus/templates/content/parity/matrix.md +2 -1
  75. package/corpus/templates/content/parity/matrix.ts +25 -0
  76. package/corpus/templates/content/shared/api.ts +54 -4
  77. package/corpus/templates/content/shared/database-query.ts +359 -0
  78. package/dist/agent/run-manager.d.ts.map +1 -1
  79. package/dist/agent/run-manager.js +7 -0
  80. package/dist/agent/run-manager.js.map +1 -1
  81. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  82. package/dist/client/agent-chat-adapter.js +12 -0
  83. package/dist/client/agent-chat-adapter.js.map +1 -1
  84. package/dist/client/analytics.d.ts +8 -0
  85. package/dist/client/analytics.d.ts.map +1 -1
  86. package/dist/client/analytics.js +95 -1
  87. package/dist/client/analytics.js.map +1 -1
  88. package/dist/client/use-action.d.ts.map +1 -1
  89. package/dist/client/use-action.js +6 -0
  90. package/dist/client/use-action.js.map +1 -1
  91. package/dist/collab/routes.d.ts +1 -1
  92. package/dist/collab/struct-routes.d.ts +1 -1
  93. package/dist/deploy/build.d.ts.map +1 -1
  94. package/dist/deploy/build.js +38 -1
  95. package/dist/deploy/build.js.map +1 -1
  96. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  97. package/dist/notifications/routes.d.ts +3 -3
  98. package/dist/observability/posthog-ai.d.ts +126 -0
  99. package/dist/observability/posthog-ai.d.ts.map +1 -0
  100. package/dist/observability/posthog-ai.js +190 -0
  101. package/dist/observability/posthog-ai.js.map +1 -0
  102. package/dist/observability/routes.d.ts +1 -1
  103. package/dist/observability/routes.d.ts.map +1 -1
  104. package/dist/observability/routes.js +26 -6
  105. package/dist/observability/routes.js.map +1 -1
  106. package/dist/observability/traces.d.ts +9 -0
  107. package/dist/observability/traces.d.ts.map +1 -1
  108. package/dist/observability/traces.js +219 -10
  109. package/dist/observability/traces.js.map +1 -1
  110. package/dist/observability/types.d.ts +10 -0
  111. package/dist/observability/types.d.ts.map +1 -1
  112. package/dist/observability/types.js +1 -0
  113. package/dist/observability/types.js.map +1 -1
  114. package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
  115. package/dist/secrets/register-framework-secrets.js +14 -0
  116. package/dist/secrets/register-framework-secrets.js.map +1 -1
  117. package/dist/server/agent-run-context.d.ts +8 -0
  118. package/dist/server/agent-run-context.d.ts.map +1 -1
  119. package/dist/server/agent-run-context.js +18 -0
  120. package/dist/server/agent-run-context.js.map +1 -1
  121. package/dist/server/capture-error.d.ts +8 -0
  122. package/dist/server/capture-error.d.ts.map +1 -1
  123. package/dist/server/capture-error.js.map +1 -1
  124. package/dist/server/core-routes-plugin.d.ts +0 -23
  125. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  126. package/dist/server/core-routes-plugin.js +71 -7
  127. package/dist/server/core-routes-plugin.js.map +1 -1
  128. package/dist/server/deploy-environment.d.ts +17 -0
  129. package/dist/server/deploy-environment.d.ts.map +1 -0
  130. package/dist/server/deploy-environment.js +47 -0
  131. package/dist/server/deploy-environment.js.map +1 -0
  132. package/dist/server/error-noise-filter.d.ts +80 -0
  133. package/dist/server/error-noise-filter.d.ts.map +1 -0
  134. package/dist/server/error-noise-filter.js +173 -0
  135. package/dist/server/error-noise-filter.js.map +1 -0
  136. package/dist/server/posthog-config.d.ts +28 -0
  137. package/dist/server/posthog-config.d.ts.map +1 -0
  138. package/dist/server/posthog-config.js +53 -0
  139. package/dist/server/posthog-config.js.map +1 -0
  140. package/dist/server/request-context.d.ts +6 -0
  141. package/dist/server/request-context.d.ts.map +1 -1
  142. package/dist/server/request-context.js.map +1 -1
  143. package/dist/server/sentry-config.d.ts +1 -0
  144. package/dist/server/sentry-config.d.ts.map +1 -1
  145. package/dist/server/sentry-config.js +3 -1
  146. package/dist/server/sentry-config.js.map +1 -1
  147. package/dist/server/sentry-plugin.d.ts.map +1 -1
  148. package/dist/server/sentry-plugin.js +12 -27
  149. package/dist/server/sentry-plugin.js.map +1 -1
  150. package/dist/server/sentry.d.ts.map +1 -1
  151. package/dist/server/sentry.js +8 -124
  152. package/dist/server/sentry.js.map +1 -1
  153. package/dist/server/ssr-handler.d.ts.map +1 -1
  154. package/dist/server/ssr-handler.js +6 -1
  155. package/dist/server/ssr-handler.js.map +1 -1
  156. package/dist/shared/mcp-embed-headers.d.ts +1 -1
  157. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  158. package/dist/shared/mcp-embed-headers.js +1 -1
  159. package/dist/shared/mcp-embed-headers.js.map +1 -1
  160. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
  161. package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
  162. package/dist/tracking/error-capture.d.ts +7 -0
  163. package/dist/tracking/error-capture.d.ts.map +1 -1
  164. package/dist/tracking/error-capture.js +10 -73
  165. package/dist/tracking/error-capture.js.map +1 -1
  166. package/dist/tracking/index.d.ts +1 -0
  167. package/dist/tracking/index.d.ts.map +1 -1
  168. package/dist/tracking/index.js +1 -0
  169. package/dist/tracking/index.js.map +1 -1
  170. package/dist/tracking/posthog-exception.d.ts +103 -0
  171. package/dist/tracking/posthog-exception.d.ts.map +1 -0
  172. package/dist/tracking/posthog-exception.js +181 -0
  173. package/dist/tracking/posthog-exception.js.map +1 -0
  174. package/dist/tracking/providers.d.ts +14 -0
  175. package/dist/tracking/providers.d.ts.map +1 -1
  176. package/dist/tracking/providers.js +63 -11
  177. package/dist/tracking/providers.js.map +1 -1
  178. package/dist/tracking/redaction.d.ts +25 -0
  179. package/dist/tracking/redaction.d.ts.map +1 -0
  180. package/dist/tracking/redaction.js +85 -0
  181. package/dist/tracking/redaction.js.map +1 -0
  182. package/docs/content/observability.mdx +41 -1
  183. package/docs/content/tracking.mdx +25 -0
  184. package/package.json +1 -1
  185. package/src/agent/run-manager.ts +7 -0
  186. package/src/client/agent-chat-adapter.ts +10 -0
  187. package/src/client/analytics.ts +105 -1
  188. package/src/client/use-action.ts +6 -0
  189. package/src/deploy/build.ts +38 -1
  190. package/src/observability/posthog-ai.ts +294 -0
  191. package/src/observability/routes.ts +29 -6
  192. package/src/observability/traces.ts +287 -12
  193. package/src/observability/types.ts +11 -0
  194. package/src/secrets/register-framework-secrets.ts +16 -0
  195. package/src/server/agent-run-context.ts +21 -0
  196. package/src/server/capture-error.ts +8 -0
  197. package/src/server/core-routes-plugin.ts +75 -4
  198. package/src/server/deploy-environment.ts +57 -0
  199. package/src/server/error-noise-filter.ts +268 -0
  200. package/src/server/posthog-config.ts +75 -0
  201. package/src/server/request-context.ts +6 -0
  202. package/src/server/sentry-config.ts +4 -8
  203. package/src/server/sentry-plugin.ts +13 -29
  204. package/src/server/sentry.ts +13 -156
  205. package/src/server/ssr-handler.ts +6 -1
  206. package/src/shared/mcp-embed-headers.ts +1 -1
  207. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
  208. package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
  209. package/src/tracking/error-capture.ts +44 -109
  210. package/src/tracking/index.ts +11 -0
  211. package/src/tracking/posthog-exception.ts +305 -0
  212. package/src/tracking/providers.ts +91 -14
  213. package/src/tracking/redaction.ts +105 -0
@@ -1,3 +1,4 @@
1
+ /** @deprecated Use `resolveDeployEnvironment()` — it is not Sentry-specific. */
1
2
  export declare function resolveSentryEnvironment(): string;
2
3
  export declare function resolveServerSentryDsn(): string | undefined;
3
4
  export declare function resolvePublicSentryDsn(): string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"sentry-config.d.ts","sourceRoot":"","sources":["../../src/server/sentry-config.ts"],"names":[],"mappings":"AA2BA,wBAAgB,wBAAwB,IAAI,MAAM,CASjD;AAED,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,SAAS,CAK3D;AAED,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,SAAS,CAS3D;AAED,wBAAgB,2BAA2B,IAAI,MAAM,GAAG,IAAI,CAgB3D;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,IAAI;IAC7C,SAAS,EAAE,QAAQ,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB,GAAG,IAAI,CAeP;AAED,wBAAgB,6BAA6B,IAAI,MAAM,GAAG,IAAI,CAW7D"}
1
+ {"version":3,"file":"sentry-config.d.ts","sourceRoot":"","sources":["../../src/server/sentry-config.ts"],"names":[],"mappings":"AA6BA,gFAAgF;AAChF,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAED,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,SAAS,CAK3D;AAED,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,SAAS,CAS3D;AAED,wBAAgB,2BAA2B,IAAI,MAAM,GAAG,IAAI,CAgB3D;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,IAAI;IAC7C,SAAS,EAAE,QAAQ,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB,GAAG,IAAI,CAeP;AAED,wBAAgB,6BAA6B,IAAI,MAAM,GAAG,IAAI,CAW7D"}
@@ -1,3 +1,4 @@
1
+ import { resolveDeployEnvironment } from "./deploy-environment.js";
1
2
  function firstNonEmpty(...values) {
2
3
  for (const value of values) {
3
4
  const trimmed = value?.trim();
@@ -14,8 +15,9 @@ function resolveSentryDsnFromKeyProject() {
14
15
  return undefined;
15
16
  return `https://${key}@${host}/${projectId}`;
16
17
  }
18
+ /** @deprecated Use `resolveDeployEnvironment()` — it is not Sentry-specific. */
17
19
  export function resolveSentryEnvironment() {
18
- return (firstNonEmpty(process.env.SENTRY_ENVIRONMENT, process.env.NETLIFY_CONTEXT, process.env.VERCEL_ENV, process.env.NODE_ENV) ?? "production");
20
+ return resolveDeployEnvironment();
19
21
  }
20
22
  export function resolveServerSentryDsn() {
21
23
  return (firstNonEmpty(process.env.SENTRY_SERVER_DSN, process.env.SENTRY_DSN) ??
@@ -1 +1 @@
1
- {"version":3,"file":"sentry-config.js","sourceRoot":"","sources":["../../src/server/sentry-config.ts"],"names":[],"mappings":"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","sourcesContent":["function firstNonEmpty(\n ...values: Array<string | undefined>\n): string | undefined {\n for (const value of values) {\n const trimmed = value?.trim();\n if (trimmed) return trimmed;\n }\n return undefined;\n}\n\nfunction resolveSentryDsnFromKeyProject(): string | undefined {\n const key = firstNonEmpty(\n process.env.SENTRY_CLIENT_KEY,\n process.env.VITE_SENTRY_CLIENT_KEY,\n );\n const projectId = firstNonEmpty(\n process.env.SENTRY_PROJECT_ID,\n process.env.VITE_SENTRY_PROJECT_ID,\n );\n const host = firstNonEmpty(\n process.env.SENTRY_INGEST_HOST,\n process.env.VITE_SENTRY_INGEST_HOST,\n );\n if (!key || !projectId || !host) return undefined;\n return `https://${key}@${host}/${projectId}`;\n}\n\nexport function resolveSentryEnvironment(): string {\n return (\n firstNonEmpty(\n process.env.SENTRY_ENVIRONMENT,\n process.env.NETLIFY_CONTEXT,\n process.env.VERCEL_ENV,\n process.env.NODE_ENV,\n ) ?? \"production\"\n );\n}\n\nexport function resolveServerSentryDsn(): string | undefined {\n return (\n firstNonEmpty(process.env.SENTRY_SERVER_DSN, process.env.SENTRY_DSN) ??\n resolveSentryDsnFromKeyProject()\n );\n}\n\nexport function resolvePublicSentryDsn(): string | undefined {\n return (\n firstNonEmpty(\n process.env.SENTRY_CLIENT_DSN,\n process.env.VITE_SENTRY_CLIENT_DSN,\n process.env.VITE_SENTRY_DSN,\n process.env.SENTRY_DSN,\n ) ?? resolveSentryDsnFromKeyProject()\n );\n}\n\nexport function getSentryClientConfigScript(): string | null {\n const dsn = resolvePublicSentryDsn();\n if (!dsn) return null;\n\n const config = {\n sentryDsn: dsn,\n sentryEnvironment: resolveSentryEnvironment(),\n };\n\n return [\n \"<script data-agent-native-sentry-config>\",\n \"window.__AGENT_NATIVE_CONFIG__=Object.assign({},window.__AGENT_NATIVE_CONFIG__,\",\n JSON.stringify(config),\n \");\",\n \"</script>\",\n ].join(\"\");\n}\n\n/**\n * Hosted Realtime Gateway config for the client, or null for the in-process\n * (local) transport. Values are env-derived and identical for every visitor,\n * so this is safe inside the CDN-cached SSR shell (see `guard:ssr-cache-shell`).\n * The per-user subscribe token is NOT here — it is minted client-side after\n * load from `/_agent-native/realtime-token`.\n */\nexport function resolveRealtimeClientConfig(): {\n transport: \"hosted\";\n gatewayBaseUrl: string;\n} | null {\n // Fail closed: emit hosted config only when BOTH the transport is hosted AND\n // an explicit gateway URL is set. No production default — this ships into the\n // CDN-cached shell served to every visitor, so a mis-set staging/preview/\n // self-hosted env must stay on the local transport rather than silently\n // point every browser at api.builder.io. This gating is mirrored byte-for-\n // byte in the worker emitter in `deploy/build.ts` (kept in sync deliberately).\n if (firstNonEmpty(process.env.AGENT_NATIVE_REALTIME_TRANSPORT) !== \"hosted\") {\n return null;\n }\n const gatewayBaseUrl = firstNonEmpty(\n process.env.AGENT_NATIVE_REALTIME_GATEWAY_URL,\n );\n if (!gatewayBaseUrl) return null;\n return { transport: \"hosted\", gatewayBaseUrl };\n}\n\nexport function getRealtimeClientConfigScript(): string | null {\n const realtime = resolveRealtimeClientConfig();\n if (!realtime) return null;\n\n return [\n \"<script data-agent-native-realtime-config>\",\n \"window.__AGENT_NATIVE_CONFIG__=Object.assign({},window.__AGENT_NATIVE_CONFIG__,\",\n JSON.stringify({ realtime }),\n \");\",\n \"</script>\",\n ].join(\"\");\n}\n"]}
1
+ {"version":3,"file":"sentry-config.js","sourceRoot":"","sources":["../../src/server/sentry-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { resolveDeployEnvironment } from \"./deploy-environment.js\";\n\nfunction firstNonEmpty(\n ...values: Array<string | undefined>\n): string | undefined {\n for (const value of values) {\n const trimmed = value?.trim();\n if (trimmed) return trimmed;\n }\n return undefined;\n}\n\nfunction resolveSentryDsnFromKeyProject(): string | undefined {\n const key = firstNonEmpty(\n process.env.SENTRY_CLIENT_KEY,\n process.env.VITE_SENTRY_CLIENT_KEY,\n );\n const projectId = firstNonEmpty(\n process.env.SENTRY_PROJECT_ID,\n process.env.VITE_SENTRY_PROJECT_ID,\n );\n const host = firstNonEmpty(\n process.env.SENTRY_INGEST_HOST,\n process.env.VITE_SENTRY_INGEST_HOST,\n );\n if (!key || !projectId || !host) return undefined;\n return `https://${key}@${host}/${projectId}`;\n}\n\n/** @deprecated Use `resolveDeployEnvironment()` — it is not Sentry-specific. */\nexport function resolveSentryEnvironment(): string {\n return resolveDeployEnvironment();\n}\n\nexport function resolveServerSentryDsn(): string | undefined {\n return (\n firstNonEmpty(process.env.SENTRY_SERVER_DSN, process.env.SENTRY_DSN) ??\n resolveSentryDsnFromKeyProject()\n );\n}\n\nexport function resolvePublicSentryDsn(): string | undefined {\n return (\n firstNonEmpty(\n process.env.SENTRY_CLIENT_DSN,\n process.env.VITE_SENTRY_CLIENT_DSN,\n process.env.VITE_SENTRY_DSN,\n process.env.SENTRY_DSN,\n ) ?? resolveSentryDsnFromKeyProject()\n );\n}\n\nexport function getSentryClientConfigScript(): string | null {\n const dsn = resolvePublicSentryDsn();\n if (!dsn) return null;\n\n const config = {\n sentryDsn: dsn,\n sentryEnvironment: resolveSentryEnvironment(),\n };\n\n return [\n \"<script data-agent-native-sentry-config>\",\n \"window.__AGENT_NATIVE_CONFIG__=Object.assign({},window.__AGENT_NATIVE_CONFIG__,\",\n JSON.stringify(config),\n \");\",\n \"</script>\",\n ].join(\"\");\n}\n\n/**\n * Hosted Realtime Gateway config for the client, or null for the in-process\n * (local) transport. Values are env-derived and identical for every visitor,\n * so this is safe inside the CDN-cached SSR shell (see `guard:ssr-cache-shell`).\n * The per-user subscribe token is NOT here — it is minted client-side after\n * load from `/_agent-native/realtime-token`.\n */\nexport function resolveRealtimeClientConfig(): {\n transport: \"hosted\";\n gatewayBaseUrl: string;\n} | null {\n // Fail closed: emit hosted config only when BOTH the transport is hosted AND\n // an explicit gateway URL is set. No production default — this ships into the\n // CDN-cached shell served to every visitor, so a mis-set staging/preview/\n // self-hosted env must stay on the local transport rather than silently\n // point every browser at api.builder.io. This gating is mirrored byte-for-\n // byte in the worker emitter in `deploy/build.ts` (kept in sync deliberately).\n if (firstNonEmpty(process.env.AGENT_NATIVE_REALTIME_TRANSPORT) !== \"hosted\") {\n return null;\n }\n const gatewayBaseUrl = firstNonEmpty(\n process.env.AGENT_NATIVE_REALTIME_GATEWAY_URL,\n );\n if (!gatewayBaseUrl) return null;\n return { transport: \"hosted\", gatewayBaseUrl };\n}\n\nexport function getRealtimeClientConfigScript(): string | null {\n const realtime = resolveRealtimeClientConfig();\n if (!realtime) return null;\n\n return [\n \"<script data-agent-native-realtime-config>\",\n \"window.__AGENT_NATIVE_CONFIG__=Object.assign({},window.__AGENT_NATIVE_CONFIG__,\",\n JSON.stringify({ realtime }),\n \");\",\n \"</script>\",\n ].join(\"\");\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"sentry-plugin.d.ts","sourceRoot":"","sources":["../../src/server/sentry-plugin.ts"],"names":[],"mappings":"AAoCA,KAAK,cAAc,GAAG,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAqC9D,wBAAgB,kBAAkB,IAAI,cAAc,CAwDnD;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,EAAE,cAAqC,CAAC"}
1
+ {"version":3,"file":"sentry-plugin.d.ts","sourceRoot":"","sources":["../../src/server/sentry-plugin.ts"],"names":[],"mappings":"AAwCA,KAAK,cAAc,GAAG,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AA6B9D,wBAAgB,kBAAkB,IAAI,cAAc,CA4CnD;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,EAAE,cAAqC,CAAC"}
@@ -1,4 +1,3 @@
1
- import { getHeader, getMethod } from "h3";
2
1
  import { getSession } from "./auth.js";
3
2
  import { registerErrorCaptureProvider } from "./capture-error.js";
4
3
  /**
@@ -8,12 +7,16 @@ import { registerErrorCaptureProvider } from "./capture-error.js";
8
7
  * Wires three pieces:
9
8
  * 1. On startup, `initServerSentry()` reads `SENTRY_SERVER_DSN`/`SENTRY_DSN` and arms
10
9
  * the SDK (no-op when the env var is unset).
11
- * 2. On every request, hook into Nitro's `request` event: resolve the
10
+ * 2. Registers Sentry as a `captureError()` backend, so route errors and
11
+ * explicit captures alike reach it through the shared registry.
12
+ * 3. On every request, hook into Nitro's `request` event: resolve the
12
13
  * session via `getSession(event)` and tag the per-request isolation
13
14
  * scope with the user's id/email/orgId. Wrapped in try/catch so a
14
15
  * session-resolution failure can never 500 the request.
15
- * 3. On every Nitro `error` event, capture the exception with the route,
16
- * method, and user-agent attached as searchable tags.
16
+ *
17
+ * It does NOT hook Nitro's `error` event — that is provider-agnostic and lives
18
+ * in `core-routes-plugin.ts`, so error reporting works with PostHog (or any
19
+ * other backend) configured and no Sentry DSN set at all.
17
20
  *
18
21
  * Mounted as a default plugin from `framework-request-handler.ts` —
19
22
  * templates that don't define `server/plugins/sentry.ts` get this for
@@ -31,14 +34,6 @@ function readRoute(event) {
31
34
  return undefined;
32
35
  }
33
36
  }
34
- function readUserAgent(event) {
35
- try {
36
- return getHeader(event, "user-agent");
37
- }
38
- catch {
39
- return undefined;
40
- }
41
- }
42
37
  /**
43
38
  * Skip session resolution for paths that obviously don't need one. Avoids
44
39
  * a DB round-trip on every static-asset / favicon / public-share request
@@ -91,21 +86,11 @@ export function createSentryPlugin() {
91
86
  addRequestContextObserver((ctx) => {
92
87
  setSentryRequestContext({ userEmail: ctx.userEmail, orgId: ctx.orgId });
93
88
  });
94
- // Per-error: capture with route/method/UA tags. Nitro's `error` hook
95
- // signature is (error, { event, tags }) — we forward what we can.
96
- nitroApp.hooks?.hook?.("error", (error, ctx) => {
97
- try {
98
- const event = ctx?.event;
99
- captureRouteError(error, {
100
- route: event ? readRoute(event) : undefined,
101
- method: event ? getMethod(event) : undefined,
102
- userAgent: event ? readUserAgent(event) : undefined,
103
- });
104
- }
105
- catch {
106
- // Sentry capture must never escape into Nitro's error path.
107
- }
108
- });
89
+ // Route errors are NOT hooked here. `core-routes-plugin.ts` owns the
90
+ // provider-agnostic Nitro `error` hook and routes it through
91
+ // `captureError()`, which reaches Sentry via the registration above along
92
+ // with every other configured backend. Hooking it here too would report
93
+ // each route error to Sentry twice.
109
94
  };
110
95
  }
111
96
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"sentry-plugin.js","sourceRoot":"","sources":["../../src/server/sentry-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAgB,MAAM,IAAI,CAAC;AAExD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAClE;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EACL,cAAc,EACd,yBAAyB,GAC1B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAIrB,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,CAAC;QACH,OAAO,KAAK,CAAC,GAAG,EAAE,QAAQ,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,CAAC;QACH,OAAO,SAAS,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,oBAAoB,CAAC,IAAwB;IACpD,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,iDAAiD;IACjD,IACE,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAC3B,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAC3B,IAAI,KAAK,cAAc;QACvB,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAC3B,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,KAAK,EAAE,QAAa,EAAE,EAAE;QAC7B,yBAAyB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC9C,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;QAE/B,gBAAgB,EAAE,CAAC;QACnB,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC;YAC7B,kEAAkE;YAClE,qDAAqD;YACrD,OAAO;QACT,CAAC;QAED,4BAA4B,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;QAE1D,uEAAuE;QACvE,wEAAwE;QACxE,uEAAuE;QACvE,oEAAoE;QACpE,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,KAAc,EAAE,EAAE;YACzD,IAAI,CAAC,oBAAoB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;gBAAE,OAAO;YACpD,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;gBACxC,uBAAuB,CAAC,OAAO,CAAC,CAAC;YACnC,CAAC;YAAC,MAAM,CAAC;gBACP,wCAAwC;YAC1C,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,sEAAsE;QACtE,oEAAoE;QACpE,kEAAkE;QAClE,6DAA6D;QAC7D,qEAAqE;QACrE,0BAA0B;QAC1B,yBAAyB,CAAC,CAAC,GAAG,EAAE,EAAE;YAChC,uBAAuB,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;QAEH,qEAAqE;QACrE,kEAAkE;QAClE,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,CACpB,OAAO,EACP,CAAC,KAAc,EAAE,GAAyB,EAAE,EAAE;YAC5C,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,GAAG,EAAE,KAAK,CAAC;gBACzB,iBAAiB,CAAC,KAAK,EAAE;oBACvB,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS;oBAC3C,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS;oBAC5C,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS;iBACpD,CAAC,CAAC;YACL,CAAC;YAAC,MAAM,CAAC;gBACP,4DAA4D;YAC9D,CAAC;QACH,CAAC,CACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAmB,kBAAkB,EAAE,CAAC","sourcesContent":["import { getHeader, getMethod, type H3Event } from \"h3\";\n\nimport { getSession } from \"./auth.js\";\nimport { registerErrorCaptureProvider } from \"./capture-error.js\";\n/**\n * Nitro plugin that initializes server-side Sentry and attaches per-request\n * user context.\n *\n * Wires three pieces:\n * 1. On startup, `initServerSentry()` reads `SENTRY_SERVER_DSN`/`SENTRY_DSN` and arms\n * the SDK (no-op when the env var is unset).\n * 2. On every request, hook into Nitro's `request` event: resolve the\n * session via `getSession(event)` and tag the per-request isolation\n * scope with the user's id/email/orgId. Wrapped in try/catch so a\n * session-resolution failure can never 500 the request.\n * 3. On every Nitro `error` event, capture the exception with the route,\n * method, and user-agent attached as searchable tags.\n *\n * Mounted as a default plugin from `framework-request-handler.ts` —\n * templates that don't define `server/plugins/sentry.ts` get this for\n * free. Templates that need to customize (e.g. add custom tags / skip\n * Sentry) can override by exporting their own `sentry.ts` plugin.\n */\nimport {\n awaitBootstrap,\n markDefaultPluginProvided,\n} from \"./framework-request-handler.js\";\nimport { addRequestContextObserver } from \"./request-context.js\";\nimport {\n captureRouteError,\n initServerSentry,\n isServerSentryEnabled,\n setSentryRequestContext,\n setSentryUserForRequest,\n} from \"./sentry.js\";\n\ntype NitroPluginDef = (nitroApp: any) => void | Promise<void>;\n\nfunction readRoute(event: H3Event): string | undefined {\n try {\n return event.url?.pathname;\n } catch {\n return undefined;\n }\n}\n\nfunction readUserAgent(event: H3Event): string | undefined {\n try {\n return getHeader(event, \"user-agent\");\n } catch {\n return undefined;\n }\n}\n\n/**\n * Skip session resolution for paths that obviously don't need one. Avoids\n * a DB round-trip on every static-asset / favicon / public-share request\n * while keeping API + framework routes covered.\n */\nfunction shouldResolveSession(path: string | undefined): boolean {\n if (!path) return false;\n // Vite / React Router static assets and similar.\n if (\n path.startsWith(\"/assets/\") ||\n path.startsWith(\"/_build/\") ||\n path === \"/favicon.ico\" ||\n path.startsWith(\"/static/\")\n ) {\n return false;\n }\n return true;\n}\n\nexport function createSentryPlugin(): NitroPluginDef {\n return async (nitroApp: any) => {\n markDefaultPluginProvided(nitroApp, \"sentry\");\n await awaitBootstrap(nitroApp);\n\n initServerSentry();\n if (!isServerSentryEnabled()) {\n // No DSN — skip wiring per-request hooks. We'd just be paying the\n // call-site overhead for every request to no effect.\n return;\n }\n\n registerErrorCaptureProvider(\"sentry\", captureRouteError);\n\n // Per-request: resolve session and attach to Sentry isolation scope so\n // any exception captured later in the request carries the user. Wrapped\n // in try/catch so a session-DB hiccup or auth-broken state never turns\n // into a 500 — the worst case is we lose user context on the event.\n nitroApp.hooks?.hook?.(\"request\", async (event: H3Event) => {\n if (!shouldResolveSession(readRoute(event))) return;\n try {\n const session = await getSession(event);\n setSentryUserForRequest(session);\n } catch {\n // best-effort — don't break the request\n }\n });\n\n // Wrap-time: every `runWithRequestContext({ userEmail, orgId, ... })`\n // call also pins user/org onto Sentry's per-async-context isolation\n // scope. Covers paths the cookie-based `request` hook can't see —\n // integration webhook processors, A2A calls, agent-chat tool\n // re-entries, and any internal call chain that opens a request scope\n // without an HTTP cookie.\n addRequestContextObserver((ctx) => {\n setSentryRequestContext({ userEmail: ctx.userEmail, orgId: ctx.orgId });\n });\n\n // Per-error: capture with route/method/UA tags. Nitro's `error` hook\n // signature is (error, { event, tags }) — we forward what we can.\n nitroApp.hooks?.hook?.(\n \"error\",\n (error: unknown, ctx?: { event?: H3Event }) => {\n try {\n const event = ctx?.event;\n captureRouteError(error, {\n route: event ? readRoute(event) : undefined,\n method: event ? getMethod(event) : undefined,\n userAgent: event ? readUserAgent(event) : undefined,\n });\n } catch {\n // Sentry capture must never escape into Nitro's error path.\n }\n },\n );\n };\n}\n\n/**\n * Default Sentry plugin — auto-mounts when a template doesn't define its\n * own `server/plugins/sentry.ts`. Reads `SENTRY_SERVER_DSN`/`SENTRY_DSN` from env and\n * silently no-ops when it's unset, so this is safe to default-mount in\n * every template (including local dev with no DSN configured).\n */\nexport const defaultSentryPlugin: NitroPluginDef = createSentryPlugin();\n"]}
1
+ {"version":3,"file":"sentry-plugin.js","sourceRoot":"","sources":["../../src/server/sentry-plugin.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAClE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EACL,cAAc,EACd,yBAAyB,GAC1B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAIrB,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,CAAC;QACH,OAAO,KAAK,CAAC,GAAG,EAAE,QAAQ,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,oBAAoB,CAAC,IAAwB;IACpD,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,iDAAiD;IACjD,IACE,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAC3B,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAC3B,IAAI,KAAK,cAAc;QACvB,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAC3B,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,KAAK,EAAE,QAAa,EAAE,EAAE;QAC7B,yBAAyB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC9C,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;QAE/B,gBAAgB,EAAE,CAAC;QACnB,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC;YAC7B,kEAAkE;YAClE,qDAAqD;YACrD,OAAO;QACT,CAAC;QAED,4BAA4B,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;QAE1D,uEAAuE;QACvE,wEAAwE;QACxE,uEAAuE;QACvE,oEAAoE;QACpE,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,KAAc,EAAE,EAAE;YACzD,IAAI,CAAC,oBAAoB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;gBAAE,OAAO;YACpD,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;gBACxC,uBAAuB,CAAC,OAAO,CAAC,CAAC;YACnC,CAAC;YAAC,MAAM,CAAC;gBACP,wCAAwC;YAC1C,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,sEAAsE;QACtE,oEAAoE;QACpE,kEAAkE;QAClE,6DAA6D;QAC7D,qEAAqE;QACrE,0BAA0B;QAC1B,yBAAyB,CAAC,CAAC,GAAG,EAAE,EAAE;YAChC,uBAAuB,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;QAEH,qEAAqE;QACrE,6DAA6D;QAC7D,0EAA0E;QAC1E,wEAAwE;QACxE,oCAAoC;IACtC,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAmB,kBAAkB,EAAE,CAAC","sourcesContent":["import { type H3Event } from \"h3\";\n\nimport { getSession } from \"./auth.js\";\nimport { registerErrorCaptureProvider } from \"./capture-error.js\";\n/**\n * Nitro plugin that initializes server-side Sentry and attaches per-request\n * user context.\n *\n * Wires three pieces:\n * 1. On startup, `initServerSentry()` reads `SENTRY_SERVER_DSN`/`SENTRY_DSN` and arms\n * the SDK (no-op when the env var is unset).\n * 2. Registers Sentry as a `captureError()` backend, so route errors and\n * explicit captures alike reach it through the shared registry.\n * 3. On every request, hook into Nitro's `request` event: resolve the\n * session via `getSession(event)` and tag the per-request isolation\n * scope with the user's id/email/orgId. Wrapped in try/catch so a\n * session-resolution failure can never 500 the request.\n *\n * It does NOT hook Nitro's `error` event that is provider-agnostic and lives\n * in `core-routes-plugin.ts`, so error reporting works with PostHog (or any\n * other backend) configured and no Sentry DSN set at all.\n *\n * Mounted as a default plugin from `framework-request-handler.ts` —\n * templates that don't define `server/plugins/sentry.ts` get this for\n * free. Templates that need to customize (e.g. add custom tags / skip\n * Sentry) can override by exporting their own `sentry.ts` plugin.\n */\nimport {\n awaitBootstrap,\n markDefaultPluginProvided,\n} from \"./framework-request-handler.js\";\nimport { addRequestContextObserver } from \"./request-context.js\";\nimport {\n captureRouteError,\n initServerSentry,\n isServerSentryEnabled,\n setSentryRequestContext,\n setSentryUserForRequest,\n} from \"./sentry.js\";\n\ntype NitroPluginDef = (nitroApp: any) => void | Promise<void>;\n\nfunction readRoute(event: H3Event): string | undefined {\n try {\n return event.url?.pathname;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Skip session resolution for paths that obviously don't need one. Avoids\n * a DB round-trip on every static-asset / favicon / public-share request\n * while keeping API + framework routes covered.\n */\nfunction shouldResolveSession(path: string | undefined): boolean {\n if (!path) return false;\n // Vite / React Router static assets and similar.\n if (\n path.startsWith(\"/assets/\") ||\n path.startsWith(\"/_build/\") ||\n path === \"/favicon.ico\" ||\n path.startsWith(\"/static/\")\n ) {\n return false;\n }\n return true;\n}\n\nexport function createSentryPlugin(): NitroPluginDef {\n return async (nitroApp: any) => {\n markDefaultPluginProvided(nitroApp, \"sentry\");\n await awaitBootstrap(nitroApp);\n\n initServerSentry();\n if (!isServerSentryEnabled()) {\n // No DSN — skip wiring per-request hooks. We'd just be paying the\n // call-site overhead for every request to no effect.\n return;\n }\n\n registerErrorCaptureProvider(\"sentry\", captureRouteError);\n\n // Per-request: resolve session and attach to Sentry isolation scope so\n // any exception captured later in the request carries the user. Wrapped\n // in try/catch so a session-DB hiccup or auth-broken state never turns\n // into a 500 — the worst case is we lose user context on the event.\n nitroApp.hooks?.hook?.(\"request\", async (event: H3Event) => {\n if (!shouldResolveSession(readRoute(event))) return;\n try {\n const session = await getSession(event);\n setSentryUserForRequest(session);\n } catch {\n // best-effort — don't break the request\n }\n });\n\n // Wrap-time: every `runWithRequestContext({ userEmail, orgId, ... })`\n // call also pins user/org onto Sentry's per-async-context isolation\n // scope. Covers paths the cookie-based `request` hook can't see —\n // integration webhook processors, A2A calls, agent-chat tool\n // re-entries, and any internal call chain that opens a request scope\n // without an HTTP cookie.\n addRequestContextObserver((ctx) => {\n setSentryRequestContext({ userEmail: ctx.userEmail, orgId: ctx.orgId });\n });\n\n // Route errors are NOT hooked here. `core-routes-plugin.ts` owns the\n // provider-agnostic Nitro `error` hook and routes it through\n // `captureError()`, which reaches Sentry via the registration above along\n // with every other configured backend. Hooking it here too would report\n // each route error to Sentry twice.\n };\n}\n\n/**\n * Default Sentry plugin — auto-mounts when a template doesn't define its\n * own `server/plugins/sentry.ts`. Reads `SENTRY_SERVER_DSN`/`SENTRY_DSN` from env and\n * silently no-ops when it's unset, so this is safe to default-mount in\n * every template (including local dev with no DSN configured).\n */\nexport const defaultSentryPlugin: NitroPluginDef = createSentryPlugin();\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"sentry.d.ts","sourceRoot":"","sources":["../../src/server/sentry.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAwC7C;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAwM1C;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAsBzE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,IAAI,CAgBP;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,OAAO,EAAE;IAAE,KAAK,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAChE,MAAM,GAAG,SAAS,CAcpB;AAED,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC1C;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACpD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,iBAAsB,GAC9B,MAAM,GAAG,SAAS,CA2BpB"}
1
+ {"version":3,"file":"sentry.d.ts","sourceRoot":"","sources":["../../src/server/sentry.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAsB7C;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CA+E1C;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAsBzE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,IAAI,CAgBP;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,OAAO,EAAE;IAAE,KAAK,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAChE,MAAM,GAAG,SAAS,CAcpB;AAED,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC1C;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACpD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,iBAAsB,GAC9B,MAAM,GAAG,SAAS,CA2BpB"}
@@ -1,6 +1,3 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
1
  /**
5
2
  * Server-side Sentry initialization for Nitro.
6
3
  *
@@ -20,32 +17,11 @@ import { fileURLToPath } from "node:url";
20
17
  * requirement.
21
18
  */
22
19
  import * as Sentry from "@sentry/node";
23
- import { resolveSentryEnvironment, resolveServerSentryDsn, } from "./sentry-config.js";
20
+ import { resolveDeployEnvironment, resolveServerRelease, } from "./deploy-environment.js";
21
+ import { errorSignalFromSentryEvent, shouldReportErrorSignal, } from "./error-noise-filter.js";
22
+ import { resolveServerSentryDsn } from "./sentry-config.js";
24
23
  let _initStarted = false;
25
24
  let _initSucceeded = false;
26
- /**
27
- * Resolve the agent-native version baked into core's package.json so Sentry
28
- * "release" reflects the running framework version. Mirrors how the CLI
29
- * computes `_version` — same dist layout, same fallback string. Guarded so
30
- * a missing/unreadable package.json never crashes server boot.
31
- */
32
- function resolveServerRelease() {
33
- const explicit = process.env.AGENT_NATIVE_RELEASE;
34
- if (explicit)
35
- return explicit;
36
- try {
37
- const here = path.dirname(fileURLToPath(import.meta.url));
38
- // dist/server/sentry.js → ../../package.json
39
- const pkgPath = path.resolve(here, "../../package.json");
40
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
41
- if (pkg?.version)
42
- return `agent-native-server@${pkg.version}`;
43
- }
44
- catch {
45
- // ignore — fall through to "unknown"
46
- }
47
- return "agent-native-server@unknown";
48
- }
49
25
  function parseTracesSampleRate() {
50
26
  const raw = process.env.SENTRY_SERVER_TRACES_SAMPLE_RATE;
51
27
  if (!raw)
@@ -78,7 +54,7 @@ export function initServerSentry() {
78
54
  }
79
55
  Sentry.init({
80
56
  dsn,
81
- environment: resolveSentryEnvironment(),
57
+ environment: resolveDeployEnvironment(),
82
58
  release: resolveServerRelease(),
83
59
  tracesSampleRate: parseTracesSampleRate(),
84
60
  // sendDefaultPii MUST stay false — the framework runs inside customer
@@ -86,104 +62,12 @@ export function initServerSentry() {
86
62
  // cookies, or process.env contents to Sentry without explicit consent.
87
63
  sendDefaultPii: false,
88
64
  beforeSend(event) {
89
- // Drop expected user-input rejections so they don't pollute Sentry
90
- // with non-bug noise. Mirrors the CLI's drop list the framework
91
- // and CLI both throw `ValidationError` for the same class of input
92
- // failures, and exception type comes through as the class name.
93
- const exceptionType = event.exception?.values?.[0]?.type;
94
- if (exceptionType === "ValidationError" ||
95
- event.tags?.handled === "validation") {
96
- return null;
97
- }
98
- // Drop access-control rejections (caller lacks permission, signed
99
- // out, etc.). These are 4xx user-facing errors that propagated to
100
- // Nitro's error hook because a route forgot to catch them — fixing
101
- // the route is the right answer, but in the meantime they bury
102
- // real bugs and don't represent server failures. Auth-routes use
103
- // `captureAuthError` directly with `level: warning` so this filter
104
- // only sees the generic-handler escape path.
105
- if (exceptionType === "ForbiddenError" ||
106
- exceptionType === "UnauthorizedError") {
65
+ // Drop rules live in `error-noise-filter.ts` so every backend applies
66
+ // the same ones they describe which server errors are non-bugs in
67
+ // this framework, not a Sentry preference.
68
+ if (!shouldReportErrorSignal(errorSignalFromSentryEvent(event))) {
107
69
  return null;
108
70
  }
109
- // Drop "socket hang up" unhandled promise rejections that fire from
110
- // Lambda freeze cycles. AWS Lambda recycles long-lived sockets (e.g.
111
- // MCP Streamable HTTP long-polls, keep-alive HTTP agents) ~60s after
112
- // a function returns 200; the next thaw delivers a socket-end event
113
- // whose Promise has nobody left to await it. The function itself
114
- // already returned correctly, so there's no user impact — just
115
- // ~10k events/day of noise. The narrow shape (unhandled rejection
116
- // from `Socket.socketOnEnd` in `node:_http_client`) keeps real
117
- // application-thrown socket errors from being silenced.
118
- const exceptionValue = event.exception?.values?.[0]?.value ?? "";
119
- const exceptionMechanism = event.exception?.values?.[0]?.mechanism?.type;
120
- const isUnhandledRejection = typeof exceptionMechanism === "string" &&
121
- exceptionMechanism.endsWith("onunhandledrejection");
122
- if (exceptionValue === "socket hang up" && isUnhandledRejection) {
123
- const frames = event.exception?.values?.[0]?.stacktrace?.frames ?? [];
124
- const fromHttpClient = frames.some((f) => f?.function === "Socket.socketOnEnd" ||
125
- f?.filename === "node:_http_client");
126
- if (fromHttpClient) {
127
- return null;
128
- }
129
- }
130
- // Drop SDK-only ErrorEvent promise rejections. These arrive as Node
131
- // unhandled rejections with no application frames — typically the Neon
132
- // serverless driver's WebSocket dying across a Lambda freeze/thaw and
133
- // rejecting a floating promise with the raw ErrorEvent (the per-client
134
- // logger in db/client.ts already records these with context). Keep any
135
- // event with real app frames so thrown ErrorEvents stay visible.
136
- //
137
- // "Application frame" must exclude the Sentry SDK's own bundled chunks:
138
- // serverless bundles place them under the app root (e.g.
139
- // `/var/task/_libs/@sentry/node+….mjs`), outside node_modules, so the
140
- // SDK marks them in_app and the unhandled-rejection instrumentation
141
- // stack alone would defeat this filter.
142
- if (exceptionValue === "[object ErrorEvent]" && isUnhandledRejection) {
143
- const frames = event.exception?.values?.[0]?.stacktrace?.frames ?? [];
144
- const hasApplicationFrame = frames.some((frame) => frame?.in_app && !String(frame?.filename ?? "").includes("sentry"));
145
- const hasSentryFrame = frames.some((frame) => String(frame?.filename ?? "").includes("sentry"));
146
- if (!hasApplicationFrame && hasSentryFrame) {
147
- return null;
148
- }
149
- }
150
- const metadata = event.metadata;
151
- if (metadata?.value === "[object ErrorEvent]" &&
152
- !event.exception?.values?.length &&
153
- String(metadata.filename ?? "").includes("sentry")) {
154
- return null;
155
- }
156
- // h3's `createError({ statusCode: 4xx, ... })` produces an
157
- // `HTTPError` (h3 v2) / `H3Error` (h3 v1). 4xx HTTPErrors are
158
- // handler-controlled "expected failure" responses (404 not found,
159
- // 400 bad input) that route through Nitro's error hook just
160
- // because they bubble out of `defineEventHandler`. They aren't
161
- // bugs — they're the documented way to return a 4xx in h3.
162
- // Capture only when the statusCode looks like 5xx (real failure)
163
- // or is missing (generic Error masquerading as HTTPError).
164
- if (exceptionType === "HTTPError" || exceptionType === "H3Error") {
165
- const statusCode = (event.tags?.statusCode ??
166
- event.contexts?.h3?.statusCode);
167
- const code = typeof statusCode === "string"
168
- ? parseInt(statusCode, 10)
169
- : statusCode;
170
- if (typeof code === "number" && code >= 400 && code < 500) {
171
- return null;
172
- }
173
- // No statusCode in the event payload — fall back to matching the
174
- // common 4xx messages so handler-thrown 404/400/403/401 don't
175
- // pollute Sentry. This is a heuristic, but the alternatives
176
- // (every 4xx becomes a "real" issue, or we patch every route to
177
- // catch+return) are worse.
178
- const value = event.exception?.values?.[0]?.value ?? "";
179
- if (/^Cannot find any route matching/i.test(value) ||
180
- / not found$/i.test(value) ||
181
- /Unauthenticated$/i.test(value) ||
182
- /^Unauthorized$/i.test(value) ||
183
- /^No access to /i.test(value)) {
184
- return null;
185
- }
186
- }
187
71
  // Defense in depth: scrub PII even if some integration auto-attached
188
72
  // request metadata despite sendDefaultPii: false.
189
73
  if (event.request) {
@@ -1 +1 @@
1
- {"version":3,"file":"sentry.js","sourceRoot":"","sources":["../../src/server/sentry.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * Server-side Sentry initialization for Nitro.\n *\n * Errors thrown inside Nitro routes (the framework's own /_agent-native/*\n * handlers, the template's API routes, action handlers, agent-chat streams)\n * never reach the CLI's Sentry init — that only covers the developer's\n * machine. Without server-side Sentry the only signal a 500 ever produces\n * is a server-side console.error that lives and dies with the request.\n *\n * This module is the third Sentry init point in the framework:\n * - cli/index.ts → @sentry/node, hardcoded DSN, \"agent-native-cli\"\n * - client/analytics.ts → @sentry/browser, VITE_SENTRY_CLIENT_DSN / runtime config\n * - server/sentry.ts → @sentry/node, SENTRY_SERVER_DSN / SENTRY_DSN\n *\n * The browser and server can share a Sentry project/DSN. Separate projects\n * are an operational choice for noise, ownership, or quotas; not a runtime\n * requirement.\n */\nimport * as Sentry from \"@sentry/node\";\n\nimport type { AuthSession } from \"./auth.js\";\nimport {\n resolveSentryEnvironment,\n resolveServerSentryDsn,\n} from \"./sentry-config.js\";\n\nlet _initStarted = false;\nlet _initSucceeded = false;\n\n/**\n * Resolve the agent-native version baked into core's package.json so Sentry\n * \"release\" reflects the running framework version. Mirrors how the CLI\n * computes `_version` — same dist layout, same fallback string. Guarded so\n * a missing/unreadable package.json never crashes server boot.\n */\nfunction resolveServerRelease(): string {\n const explicit = process.env.AGENT_NATIVE_RELEASE;\n if (explicit) return explicit;\n try {\n const here = path.dirname(fileURLToPath(import.meta.url));\n // dist/server/sentry.js → ../../package.json\n const pkgPath = path.resolve(here, \"../../package.json\");\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\")) as {\n version?: string;\n };\n if (pkg?.version) return `agent-native-server@${pkg.version}`;\n } catch {\n // ignore — fall through to \"unknown\"\n }\n return \"agent-native-server@unknown\";\n}\n\nfunction parseTracesSampleRate(): number {\n const raw = process.env.SENTRY_SERVER_TRACES_SAMPLE_RATE;\n if (!raw) return 0;\n const n = Number(raw);\n if (!Number.isFinite(n) || n < 0 || n > 1) return 0;\n return n;\n}\n\n/**\n * Initialize server-side Sentry. Idempotent — safe to call from multiple\n * plugin entrypoints. Returns `true` if initialization actually happened\n * (DSN was set), `false` if Sentry is disabled (no DSN).\n *\n * No DSN is hardcoded: unlike the CLI (a published binary that always wants\n * to phone home crashes), the server runs in customer environments. Operators\n * set `SENTRY_SERVER_DSN` or the common `SENTRY_DSN` when they want their own\n * Sentry project to receive these events; without one the module no-ops.\n */\nexport function initServerSentry(): boolean {\n if (_initStarted) return _initSucceeded;\n _initStarted = true;\n\n const dsn = resolveServerSentryDsn();\n if (!dsn) {\n if (process.env.DEBUG) {\n console.log(\n \"[agent-native] SENTRY_SERVER_DSN/SENTRY_DSN not set — server Sentry disabled.\",\n );\n }\n return false;\n }\n\n Sentry.init({\n dsn,\n environment: resolveSentryEnvironment(),\n release: resolveServerRelease(),\n tracesSampleRate: parseTracesSampleRate(),\n // sendDefaultPii MUST stay false — the framework runs inside customer\n // environments and we never want to silently ship request headers,\n // cookies, or process.env contents to Sentry without explicit consent.\n sendDefaultPii: false,\n beforeSend(event) {\n // Drop expected user-input rejections so they don't pollute Sentry\n // with non-bug noise. Mirrors the CLI's drop list — the framework\n // and CLI both throw `ValidationError` for the same class of input\n // failures, and exception type comes through as the class name.\n const exceptionType = event.exception?.values?.[0]?.type;\n if (\n exceptionType === \"ValidationError\" ||\n event.tags?.handled === \"validation\"\n ) {\n return null;\n }\n // Drop access-control rejections (caller lacks permission, signed\n // out, etc.). These are 4xx user-facing errors that propagated to\n // Nitro's error hook because a route forgot to catch them — fixing\n // the route is the right answer, but in the meantime they bury\n // real bugs and don't represent server failures. Auth-routes use\n // `captureAuthError` directly with `level: warning` so this filter\n // only sees the generic-handler escape path.\n if (\n exceptionType === \"ForbiddenError\" ||\n exceptionType === \"UnauthorizedError\"\n ) {\n return null;\n }\n // Drop \"socket hang up\" unhandled promise rejections that fire from\n // Lambda freeze cycles. AWS Lambda recycles long-lived sockets (e.g.\n // MCP Streamable HTTP long-polls, keep-alive HTTP agents) ~60s after\n // a function returns 200; the next thaw delivers a socket-end event\n // whose Promise has nobody left to await it. The function itself\n // already returned correctly, so there's no user impact — just\n // ~10k events/day of noise. The narrow shape (unhandled rejection\n // from `Socket.socketOnEnd` in `node:_http_client`) keeps real\n // application-thrown socket errors from being silenced.\n const exceptionValue = event.exception?.values?.[0]?.value ?? \"\";\n const exceptionMechanism = event.exception?.values?.[0]?.mechanism?.type;\n const isUnhandledRejection =\n typeof exceptionMechanism === \"string\" &&\n exceptionMechanism.endsWith(\"onunhandledrejection\");\n if (exceptionValue === \"socket hang up\" && isUnhandledRejection) {\n const frames = event.exception?.values?.[0]?.stacktrace?.frames ?? [];\n const fromHttpClient = frames.some(\n (f) =>\n f?.function === \"Socket.socketOnEnd\" ||\n f?.filename === \"node:_http_client\",\n );\n if (fromHttpClient) {\n return null;\n }\n }\n // Drop SDK-only ErrorEvent promise rejections. These arrive as Node\n // unhandled rejections with no application frames — typically the Neon\n // serverless driver's WebSocket dying across a Lambda freeze/thaw and\n // rejecting a floating promise with the raw ErrorEvent (the per-client\n // logger in db/client.ts already records these with context). Keep any\n // event with real app frames so thrown ErrorEvents stay visible.\n //\n // \"Application frame\" must exclude the Sentry SDK's own bundled chunks:\n // serverless bundles place them under the app root (e.g.\n // `/var/task/_libs/@sentry/node+….mjs`), outside node_modules, so the\n // SDK marks them in_app and the unhandled-rejection instrumentation\n // stack alone would defeat this filter.\n if (exceptionValue === \"[object ErrorEvent]\" && isUnhandledRejection) {\n const frames = event.exception?.values?.[0]?.stacktrace?.frames ?? [];\n const hasApplicationFrame = frames.some(\n (frame) =>\n frame?.in_app && !String(frame?.filename ?? \"\").includes(\"sentry\"),\n );\n const hasSentryFrame = frames.some((frame) =>\n String(frame?.filename ?? \"\").includes(\"sentry\"),\n );\n if (!hasApplicationFrame && hasSentryFrame) {\n return null;\n }\n }\n const metadata = (\n event as {\n metadata?: {\n filename?: unknown;\n value?: unknown;\n };\n }\n ).metadata;\n if (\n metadata?.value === \"[object ErrorEvent]\" &&\n !event.exception?.values?.length &&\n String(metadata.filename ?? \"\").includes(\"sentry\")\n ) {\n return null;\n }\n // h3's `createError({ statusCode: 4xx, ... })` produces an\n // `HTTPError` (h3 v2) / `H3Error` (h3 v1). 4xx HTTPErrors are\n // handler-controlled \"expected failure\" responses (404 not found,\n // 400 bad input) that route through Nitro's error hook just\n // because they bubble out of `defineEventHandler`. They aren't\n // bugs — they're the documented way to return a 4xx in h3.\n // Capture only when the statusCode looks like 5xx (real failure)\n // or is missing (generic Error masquerading as HTTPError).\n if (exceptionType === \"HTTPError\" || exceptionType === \"H3Error\") {\n const statusCode = (event.tags?.statusCode ??\n (\n event.contexts as\n | Record<string, Record<string, unknown>>\n | undefined\n )?.h3?.statusCode) as number | string | undefined;\n const code =\n typeof statusCode === \"string\"\n ? parseInt(statusCode, 10)\n : statusCode;\n if (typeof code === \"number\" && code >= 400 && code < 500) {\n return null;\n }\n // No statusCode in the event payload — fall back to matching the\n // common 4xx messages so handler-thrown 404/400/403/401 don't\n // pollute Sentry. This is a heuristic, but the alternatives\n // (every 4xx becomes a \"real\" issue, or we patch every route to\n // catch+return) are worse.\n const value = event.exception?.values?.[0]?.value ?? \"\";\n if (\n /^Cannot find any route matching/i.test(value) ||\n / not found$/i.test(value) ||\n /Unauthenticated$/i.test(value) ||\n /^Unauthorized$/i.test(value) ||\n /^No access to /i.test(value)\n ) {\n return null;\n }\n }\n\n // Defense in depth: scrub PII even if some integration auto-attached\n // request metadata despite sendDefaultPii: false.\n if (event.request) {\n if (event.request.headers) {\n const headers = event.request.headers as Record<string, string>;\n for (const k of Object.keys(headers)) {\n const lk = k.toLowerCase();\n if (\n lk === \"cookie\" ||\n lk === \"authorization\" ||\n lk === \"set-cookie\" ||\n lk === \"proxy-authorization\"\n ) {\n delete headers[k];\n }\n }\n }\n // Cookies live in their own field too.\n delete (event.request as Record<string, unknown>).cookies;\n }\n\n // Keep user info that was explicitly set via Sentry.setUser\n // (id/email/username) so we can attribute crashes back to a real\n // operator. Always strip ip_address — auto-collected, no consent.\n if (event.user) {\n const user = event.user as Record<string, unknown>;\n delete user.ip_address;\n const hasIdentity =\n typeof user.id === \"string\" ||\n typeof user.email === \"string\" ||\n typeof user.username === \"string\";\n if (!hasIdentity) {\n delete event.user;\n }\n }\n\n // Sentry's contexts can carry process.env snapshots — strip env-shaped\n // contexts so we don't leak deployment secrets.\n if (event.contexts && typeof event.contexts === \"object\") {\n delete (event.contexts as Record<string, unknown>).runtime_env;\n }\n\n return event;\n },\n });\n\n _initSucceeded = true;\n return true;\n}\n\n/**\n * `true` once `initServerSentry()` has succeeded with a DSN. Plugins that\n * want to skip work when Sentry is disabled can check this before calling\n * the helpers below.\n */\nexport function isServerSentryEnabled(): boolean {\n return _initSucceeded;\n}\n\n/**\n * Attach the current request's user to Sentry's isolation scope so any\n * `captureException` triggered later in the request carries the right\n * `user.id` / `user.email` / `user.username` and `orgId` tag.\n *\n * Sentry node 10 uses Node's AsyncLocalStorage to give each async context\n * its own isolation scope, so setting on `getIsolationScope()` here only\n * affects events emitted while this request's async context is active.\n *\n * No-ops gracefully when Sentry isn't initialized or no session exists —\n * never throws into the request path.\n */\nexport function setSentryUserForRequest(session: AuthSession | null): void {\n if (!_initSucceeded) return;\n try {\n const scope = Sentry.getIsolationScope();\n if (!session) {\n scope.setUser(null);\n scope.setTag(\"orgId\", null);\n return;\n }\n scope.setUser({\n id: session.userId ?? session.email,\n email: session.email,\n username: session.name,\n });\n scope.setTag(\"orgId\", session.orgId ?? null);\n if (session.orgRole) {\n scope.setTag(\"orgRole\", session.orgRole);\n }\n } catch {\n // Sentry scope APIs should never throw, but if they do we'd rather\n // continue serving the request than crash on observability.\n }\n}\n\n/**\n * Pin a user/org onto the current isolation scope from a lighter\n * `RequestContext`-shaped payload. Used by the request-context observer so\n * action handlers, agent-chat runs, and integration webhook processors —\n * all of which already wrap their work in `runWithRequestContext({ userEmail,\n * orgId, ... })` — automatically tag Sentry events with the right user even\n * when the Nitro `request` hook didn't see a cookie (e.g. webhook delivery,\n * A2A calls, internal background runs).\n *\n * Skips overwriting a richer user identity already set by\n * `setSentryUserForRequest` — the cookie-resolved session has\n * userId/username on top of email, which we shouldn't clobber.\n */\nexport function setSentryRequestContext(ctx: {\n userEmail?: string;\n orgId?: string;\n}): void {\n if (!_initSucceeded) return;\n try {\n const scope = Sentry.getIsolationScope();\n if (ctx.userEmail) {\n const existing = scope.getScopeData().user;\n if (!existing?.id && !existing?.email) {\n scope.setUser({ id: ctx.userEmail, email: ctx.userEmail });\n }\n }\n if (ctx.orgId) {\n scope.setTag(\"orgId\", ctx.orgId);\n }\n } catch {\n // never throw\n }\n}\n\n/**\n * Capture an error from one of the auth attempt routes (login / signup)\n * with the email pinned to the event so support can filter by user. Sets\n * Sentry level to `warning` (not `error`) — bad-password attempts aren't\n * actionable, but a sustained spike of warnings on a route IS the signal\n * we care about.\n *\n * Caller should still return their normal HTTP response (401/409/etc.);\n * this just records the error for observability.\n */\nexport function captureAuthError(\n error: unknown,\n context: { route: \"login\" | \"signup\" | \"logout\"; email?: string },\n): string | undefined {\n if (!_initSucceeded) return undefined;\n try {\n return Sentry.withScope((scope) => {\n scope.setLevel(\"warning\");\n scope.setTag(\"auth\", context.route);\n if (context.email) {\n scope.setUser({ id: context.email, email: context.email });\n }\n return Sentry.captureException(error);\n });\n } catch {\n return undefined;\n }\n}\n\nexport interface RouteErrorContext {\n /** The full request path (e.g. `/_agent-native/agent-chat`). */\n route?: string;\n /** HTTP method (e.g. `GET`, `POST`). */\n method?: string;\n /** Caller's `User-Agent` header. */\n userAgent?: string;\n /** Free-form extra tags to add to the event (low-cardinality). */\n tags?: Record<string, string | undefined>;\n /**\n * High-cardinality / structured payload — not searchable but visible in\n * the Sentry event detail (recording IDs, byte counts, compression\n * metadata, response body tails, etc.).\n */\n extra?: Record<string, unknown>;\n /**\n * Grouped contexts shown as separate cards in the Sentry event UI.\n */\n contexts?: Record<string, Record<string, unknown>>;\n}\n\n/**\n * Capture an exception that surfaced in a Nitro route handler with the\n * request's route/method/userAgent attached as searchable Sentry tags.\n *\n * Non-throwing: if Sentry isn't initialized or the underlying capture\n * fails, this is a no-op. Returns the Sentry event ID when capture\n * succeeded, otherwise `undefined`.\n */\nexport function captureRouteError(\n error: unknown,\n context: RouteErrorContext = {},\n): string | undefined {\n if (!_initSucceeded) return undefined;\n try {\n return Sentry.withScope((scope) => {\n if (context.route) scope.setTag(\"route\", context.route);\n if (context.method) scope.setTag(\"method\", context.method);\n if (context.userAgent) scope.setTag(\"userAgent\", context.userAgent);\n if (context.tags) {\n for (const [k, v] of Object.entries(context.tags)) {\n if (typeof v === \"string\") scope.setTag(k, v);\n }\n }\n if (context.extra) {\n for (const [k, v] of Object.entries(context.extra)) {\n if (v !== undefined) scope.setExtra(k, v);\n }\n }\n if (context.contexts) {\n for (const [k, v] of Object.entries(context.contexts)) {\n scope.setContext(k, v);\n }\n }\n return Sentry.captureException(error);\n });\n } catch {\n return undefined;\n }\n}\n"]}
1
+ {"version":3,"file":"sentry.js","sourceRoot":"","sources":["../../src/server/sentry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Server-side Sentry initialization for Nitro.\n *\n * Errors thrown inside Nitro routes (the framework's own /_agent-native/*\n * handlers, the template's API routes, action handlers, agent-chat streams)\n * never reach the CLI's Sentry init — that only covers the developer's\n * machine. Without server-side Sentry the only signal a 500 ever produces\n * is a server-side console.error that lives and dies with the request.\n *\n * This module is the third Sentry init point in the framework:\n * - cli/index.ts → @sentry/node, hardcoded DSN, \"agent-native-cli\"\n * - client/analytics.ts → @sentry/browser, VITE_SENTRY_CLIENT_DSN / runtime config\n * - server/sentry.ts → @sentry/node, SENTRY_SERVER_DSN / SENTRY_DSN\n *\n * The browser and server can share a Sentry project/DSN. Separate projects\n * are an operational choice for noise, ownership, or quotas; not a runtime\n * requirement.\n */\nimport * as Sentry from \"@sentry/node\";\n\nimport type { AuthSession } from \"./auth.js\";\nimport {\n resolveDeployEnvironment,\n resolveServerRelease,\n} from \"./deploy-environment.js\";\nimport {\n errorSignalFromSentryEvent,\n shouldReportErrorSignal,\n} from \"./error-noise-filter.js\";\nimport { resolveServerSentryDsn } from \"./sentry-config.js\";\n\nlet _initStarted = false;\nlet _initSucceeded = false;\n\nfunction parseTracesSampleRate(): number {\n const raw = process.env.SENTRY_SERVER_TRACES_SAMPLE_RATE;\n if (!raw) return 0;\n const n = Number(raw);\n if (!Number.isFinite(n) || n < 0 || n > 1) return 0;\n return n;\n}\n\n/**\n * Initialize server-side Sentry. Idempotent — safe to call from multiple\n * plugin entrypoints. Returns `true` if initialization actually happened\n * (DSN was set), `false` if Sentry is disabled (no DSN).\n *\n * No DSN is hardcoded: unlike the CLI (a published binary that always wants\n * to phone home crashes), the server runs in customer environments. Operators\n * set `SENTRY_SERVER_DSN` or the common `SENTRY_DSN` when they want their own\n * Sentry project to receive these events; without one the module no-ops.\n */\nexport function initServerSentry(): boolean {\n if (_initStarted) return _initSucceeded;\n _initStarted = true;\n\n const dsn = resolveServerSentryDsn();\n if (!dsn) {\n if (process.env.DEBUG) {\n console.log(\n \"[agent-native] SENTRY_SERVER_DSN/SENTRY_DSN not set — server Sentry disabled.\",\n );\n }\n return false;\n }\n\n Sentry.init({\n dsn,\n environment: resolveDeployEnvironment(),\n release: resolveServerRelease(),\n tracesSampleRate: parseTracesSampleRate(),\n // sendDefaultPii MUST stay false — the framework runs inside customer\n // environments and we never want to silently ship request headers,\n // cookies, or process.env contents to Sentry without explicit consent.\n sendDefaultPii: false,\n beforeSend(event) {\n // Drop rules live in `error-noise-filter.ts` so every backend applies\n // the same ones — they describe which server errors are non-bugs in\n // this framework, not a Sentry preference.\n if (!shouldReportErrorSignal(errorSignalFromSentryEvent(event))) {\n return null;\n }\n\n // Defense in depth: scrub PII even if some integration auto-attached\n // request metadata despite sendDefaultPii: false.\n if (event.request) {\n if (event.request.headers) {\n const headers = event.request.headers as Record<string, string>;\n for (const k of Object.keys(headers)) {\n const lk = k.toLowerCase();\n if (\n lk === \"cookie\" ||\n lk === \"authorization\" ||\n lk === \"set-cookie\" ||\n lk === \"proxy-authorization\"\n ) {\n delete headers[k];\n }\n }\n }\n // Cookies live in their own field too.\n delete (event.request as Record<string, unknown>).cookies;\n }\n\n // Keep user info that was explicitly set via Sentry.setUser\n // (id/email/username) so we can attribute crashes back to a real\n // operator. Always strip ip_address — auto-collected, no consent.\n if (event.user) {\n const user = event.user as Record<string, unknown>;\n delete user.ip_address;\n const hasIdentity =\n typeof user.id === \"string\" ||\n typeof user.email === \"string\" ||\n typeof user.username === \"string\";\n if (!hasIdentity) {\n delete event.user;\n }\n }\n\n // Sentry's contexts can carry process.env snapshots — strip env-shaped\n // contexts so we don't leak deployment secrets.\n if (event.contexts && typeof event.contexts === \"object\") {\n delete (event.contexts as Record<string, unknown>).runtime_env;\n }\n\n return event;\n },\n });\n\n _initSucceeded = true;\n return true;\n}\n\n/**\n * `true` once `initServerSentry()` has succeeded with a DSN. Plugins that\n * want to skip work when Sentry is disabled can check this before calling\n * the helpers below.\n */\nexport function isServerSentryEnabled(): boolean {\n return _initSucceeded;\n}\n\n/**\n * Attach the current request's user to Sentry's isolation scope so any\n * `captureException` triggered later in the request carries the right\n * `user.id` / `user.email` / `user.username` and `orgId` tag.\n *\n * Sentry node 10 uses Node's AsyncLocalStorage to give each async context\n * its own isolation scope, so setting on `getIsolationScope()` here only\n * affects events emitted while this request's async context is active.\n *\n * No-ops gracefully when Sentry isn't initialized or no session exists —\n * never throws into the request path.\n */\nexport function setSentryUserForRequest(session: AuthSession | null): void {\n if (!_initSucceeded) return;\n try {\n const scope = Sentry.getIsolationScope();\n if (!session) {\n scope.setUser(null);\n scope.setTag(\"orgId\", null);\n return;\n }\n scope.setUser({\n id: session.userId ?? session.email,\n email: session.email,\n username: session.name,\n });\n scope.setTag(\"orgId\", session.orgId ?? null);\n if (session.orgRole) {\n scope.setTag(\"orgRole\", session.orgRole);\n }\n } catch {\n // Sentry scope APIs should never throw, but if they do we'd rather\n // continue serving the request than crash on observability.\n }\n}\n\n/**\n * Pin a user/org onto the current isolation scope from a lighter\n * `RequestContext`-shaped payload. Used by the request-context observer so\n * action handlers, agent-chat runs, and integration webhook processors —\n * all of which already wrap their work in `runWithRequestContext({ userEmail,\n * orgId, ... })` — automatically tag Sentry events with the right user even\n * when the Nitro `request` hook didn't see a cookie (e.g. webhook delivery,\n * A2A calls, internal background runs).\n *\n * Skips overwriting a richer user identity already set by\n * `setSentryUserForRequest` — the cookie-resolved session has\n * userId/username on top of email, which we shouldn't clobber.\n */\nexport function setSentryRequestContext(ctx: {\n userEmail?: string;\n orgId?: string;\n}): void {\n if (!_initSucceeded) return;\n try {\n const scope = Sentry.getIsolationScope();\n if (ctx.userEmail) {\n const existing = scope.getScopeData().user;\n if (!existing?.id && !existing?.email) {\n scope.setUser({ id: ctx.userEmail, email: ctx.userEmail });\n }\n }\n if (ctx.orgId) {\n scope.setTag(\"orgId\", ctx.orgId);\n }\n } catch {\n // never throw\n }\n}\n\n/**\n * Capture an error from one of the auth attempt routes (login / signup)\n * with the email pinned to the event so support can filter by user. Sets\n * Sentry level to `warning` (not `error`) — bad-password attempts aren't\n * actionable, but a sustained spike of warnings on a route IS the signal\n * we care about.\n *\n * Caller should still return their normal HTTP response (401/409/etc.);\n * this just records the error for observability.\n */\nexport function captureAuthError(\n error: unknown,\n context: { route: \"login\" | \"signup\" | \"logout\"; email?: string },\n): string | undefined {\n if (!_initSucceeded) return undefined;\n try {\n return Sentry.withScope((scope) => {\n scope.setLevel(\"warning\");\n scope.setTag(\"auth\", context.route);\n if (context.email) {\n scope.setUser({ id: context.email, email: context.email });\n }\n return Sentry.captureException(error);\n });\n } catch {\n return undefined;\n }\n}\n\nexport interface RouteErrorContext {\n /** The full request path (e.g. `/_agent-native/agent-chat`). */\n route?: string;\n /** HTTP method (e.g. `GET`, `POST`). */\n method?: string;\n /** Caller's `User-Agent` header. */\n userAgent?: string;\n /** Free-form extra tags to add to the event (low-cardinality). */\n tags?: Record<string, string | undefined>;\n /**\n * High-cardinality / structured payload — not searchable but visible in\n * the Sentry event detail (recording IDs, byte counts, compression\n * metadata, response body tails, etc.).\n */\n extra?: Record<string, unknown>;\n /**\n * Grouped contexts shown as separate cards in the Sentry event UI.\n */\n contexts?: Record<string, Record<string, unknown>>;\n}\n\n/**\n * Capture an exception that surfaced in a Nitro route handler with the\n * request's route/method/userAgent attached as searchable Sentry tags.\n *\n * Non-throwing: if Sentry isn't initialized or the underlying capture\n * fails, this is a no-op. Returns the Sentry event ID when capture\n * succeeded, otherwise `undefined`.\n */\nexport function captureRouteError(\n error: unknown,\n context: RouteErrorContext = {},\n): string | undefined {\n if (!_initSucceeded) return undefined;\n try {\n return Sentry.withScope((scope) => {\n if (context.route) scope.setTag(\"route\", context.route);\n if (context.method) scope.setTag(\"method\", context.method);\n if (context.userAgent) scope.setTag(\"userAgent\", context.userAgent);\n if (context.tags) {\n for (const [k, v] of Object.entries(context.tags)) {\n if (typeof v === \"string\") scope.setTag(k, v);\n }\n }\n if (context.extra) {\n for (const [k, v] of Object.entries(context.extra)) {\n if (v !== undefined) scope.setExtra(k, v);\n }\n }\n if (context.contexts) {\n for (const [k, v] of Object.entries(context.contexts)) {\n scope.setContext(k, v);\n }\n }\n return Sentry.captureException(error);\n });\n } catch {\n return undefined;\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-handler.d.ts","sourceRoot":"","sources":["../../src/server/ssr-handler.ts"],"names":[],"mappings":"AA4CA,OAAO,EACL,yBAAyB,EACzB,gCAAgC,EAChC,yBAAyB,EACzB,0BAA0B,EAC1B,iBAAiB,EACjB,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,4BAA4B,CAAC;AAkWpC;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,2FAwE5E"}
1
+ {"version":3,"file":"ssr-handler.d.ts","sourceRoot":"","sources":["../../src/server/ssr-handler.ts"],"names":[],"mappings":"AA6CA,OAAO,EACL,yBAAyB,EACzB,gCAAgC,EAChC,yBAAyB,EACzB,0BAA0B,EAC1B,iBAAiB,EACjB,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,4BAA4B,CAAC;AAsWpC;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,2FAwE5E"}
@@ -22,6 +22,7 @@ import { DEFAULT_SPECULATION_RULES_PATH, resolveSsrCacheHeaders, } from "../shar
22
22
  import { AGENT_NATIVE_SOCIAL_IMAGE_ALT, AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT, AGENT_NATIVE_SOCIAL_IMAGE_PATH, AGENT_NATIVE_SOCIAL_IMAGE_TYPE, AGENT_NATIVE_SOCIAL_IMAGE_WIDTH, withAgentNativeSocialImageCacheBuster, } from "../shared/social-meta.js";
23
23
  import { getAppBasePathFromViteEnv, stripAppBasePath as canonicalStripAppBasePath, } from "./app-base-path.js";
24
24
  import { captureError } from "./capture-error.js";
25
+ import { getPostHogClientConfigScript } from "./posthog-config.js";
25
26
  import { runWithRequestContext } from "./request-context.js";
26
27
  import { getRealtimeClientConfigScript, getSentryClientConfigScript, } from "./sentry-config.js";
27
28
  export { DEFAULT_SSR_CACHE_HEADERS, DEFAULT_SPECULATION_RULES_HEADER, DEFAULT_SSR_CACHE_CONTROL, DISABLED_SSR_CACHE_HEADERS, isSsrCacheEnabled, resolveSsrCacheHeaders, SSR_CACHE_ENV_VAR, } from "../shared/cache-control.js";
@@ -273,7 +274,11 @@ function isFrameworkOrAssetPath(pathname) {
273
274
  (/\.\w+$/.test(pathname) && !pathname.endsWith(".data")));
274
275
  }
275
276
  async function rewriteMountedResponse(response, basePath, pathname, requestUrl) {
276
- const clientConfigScript = [getSentryClientConfigScript(), getRealtimeClientConfigScript()]
277
+ const clientConfigScript = [
278
+ getSentryClientConfigScript(),
279
+ getPostHogClientConfigScript(),
280
+ getRealtimeClientConfigScript(),
281
+ ]
277
282
  .filter(Boolean)
278
283
  .join("") || null;
279
284
  const headers = new Headers(response.headers);