@agent-native/core 0.133.3 → 0.134.1
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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +62 -0
- package/corpus/core/docs/content/observability.mdx +41 -1
- package/corpus/core/docs/content/template-content-developers.mdx +1 -1
- package/corpus/core/docs/content/tracking.mdx +25 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/run-manager.ts +7 -0
- package/corpus/core/src/client/agent-chat-adapter.ts +10 -0
- package/corpus/core/src/client/analytics.ts +105 -1
- package/corpus/core/src/deploy/build.ts +38 -1
- package/corpus/core/src/observability/posthog-ai.ts +294 -0
- package/corpus/core/src/observability/routes.ts +29 -6
- package/corpus/core/src/observability/traces.ts +287 -12
- package/corpus/core/src/observability/types.ts +11 -0
- package/corpus/core/src/secrets/register-framework-secrets.ts +16 -0
- package/corpus/core/src/server/agent-run-context.ts +21 -0
- package/corpus/core/src/server/capture-error.ts +8 -0
- package/corpus/core/src/server/core-routes-plugin.ts +75 -4
- package/corpus/core/src/server/deploy-environment.ts +57 -0
- package/corpus/core/src/server/error-noise-filter.ts +268 -0
- package/corpus/core/src/server/posthog-config.ts +75 -0
- package/corpus/core/src/server/request-context.ts +6 -0
- package/corpus/core/src/server/sentry-config.ts +4 -8
- package/corpus/core/src/server/sentry-plugin.ts +13 -29
- package/corpus/core/src/server/sentry.ts +13 -156
- package/corpus/core/src/server/ssr-handler.ts +6 -1
- package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
- package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
- package/corpus/core/src/tracking/error-capture.ts +44 -109
- package/corpus/core/src/tracking/index.ts +11 -0
- package/corpus/core/src/tracking/posthog-exception.ts +305 -0
- package/corpus/core/src/tracking/providers.ts +91 -14
- package/corpus/core/src/tracking/redaction.ts +105 -0
- package/corpus/templates/clips/changelog/2026-07-29-meeting-notes-no-longer-transcribe-the-other-side-twice-when.md +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +37 -48
- package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +7 -5
- package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +182 -106
- package/corpus/templates/clips/desktop/src/overlays/live-transcript.tsx +43 -14
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +3 -3
- package/corpus/templates/clips/desktop/src-tauri/src/echo_guard.rs +443 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +125 -16
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -4
- package/corpus/templates/content/actions/_database-membership-lock.ts +25 -0
- package/corpus/templates/content/actions/_database-row-batch.ts +6 -2
- package/corpus/templates/content/actions/_database-source-utils.ts +289 -129
- package/corpus/templates/content/actions/_database-utils.ts +12 -3
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +39 -21
- package/corpus/templates/content/actions/attach-content-database-source.ts +4 -1
- package/corpus/templates/content/actions/change-content-database-source-role.ts +3 -5
- package/corpus/templates/content/actions/duplicate-document-property.ts +59 -46
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +77 -71
- package/corpus/templates/content/actions/materialize-builder-required-fields.ts +7 -0
- package/corpus/templates/content/actions/remove-database-items.ts +183 -0
- package/corpus/templates/content/actions/set-document-property.ts +57 -39
- package/corpus/templates/content/actions/sync-local-folder-source.ts +7 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +471 -329
- package/corpus/templates/content/app/components/editor/database/row-access.ts +45 -0
- package/corpus/templates/content/app/components/editor/database/shared.tsx +141 -38
- package/corpus/templates/content/app/hooks/use-content-database.ts +2 -2
- package/corpus/templates/content/app/i18n-data.ts +130 -0
- package/corpus/templates/content/changelog/2026-07-30-database-rows-can-now-be-removed-without-deleting-their-page.md +6 -0
- package/corpus/templates/content/parity/eval-scenarios.ts +2 -2
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +4 -5
- package/corpus/templates/content/shared/api.ts +4 -0
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +26 -11
- package/corpus/templates/design/actions/take-design-screenshot.ts +7 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +11 -3
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +4 -1
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +54 -17
- package/corpus/templates/design/app/components/design/design-canvas/local-runtime.ts +126 -0
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +2 -0
- package/corpus/templates/design/app/pages/Index.tsx +3 -1
- package/corpus/templates/design/app/pages/Present.tsx +2 -1
- package/corpus/templates/design/changelog/2026-07-30-a-broken-inline-script-in-a-generated-screen-is-now-caught-o.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-design-html-is-now-validated-with-a-spec-grade-html-parser-i.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-designs-now-load-their-tailwind-and-alpine-runtimes-from-the.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-designs-with-a-broken-alpine-expression-are-now-caught-on-sa.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-editing-a-design-no-longer-strips-its-styling-the-canvas-kep.md +6 -0
- package/corpus/templates/design/package.json +4 -0
- package/corpus/templates/design/shared/html-integrity.ts +802 -556
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +7 -0
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +12 -0
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/analytics.d.ts +8 -0
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +95 -1
- package/dist/client/analytics.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +38 -1
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/posthog-ai.d.ts +126 -0
- package/dist/observability/posthog-ai.d.ts.map +1 -0
- package/dist/observability/posthog-ai.js +190 -0
- package/dist/observability/posthog-ai.js.map +1 -0
- package/dist/observability/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts.map +1 -1
- package/dist/observability/routes.js +26 -6
- package/dist/observability/routes.js.map +1 -1
- package/dist/observability/traces.d.ts +9 -0
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +219 -10
- package/dist/observability/traces.js.map +1 -1
- package/dist/observability/types.d.ts +10 -0
- package/dist/observability/types.d.ts.map +1 -1
- package/dist/observability/types.js +1 -0
- package/dist/observability/types.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
- package/dist/secrets/register-framework-secrets.js +14 -0
- package/dist/secrets/register-framework-secrets.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-run-context.d.ts +8 -0
- package/dist/server/agent-run-context.d.ts.map +1 -1
- package/dist/server/agent-run-context.js +18 -0
- package/dist/server/agent-run-context.js.map +1 -1
- package/dist/server/capture-error.d.ts +8 -0
- package/dist/server/capture-error.d.ts.map +1 -1
- package/dist/server/capture-error.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +0 -23
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +71 -7
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/deploy-environment.d.ts +17 -0
- package/dist/server/deploy-environment.d.ts.map +1 -0
- package/dist/server/deploy-environment.js +47 -0
- package/dist/server/deploy-environment.js.map +1 -0
- package/dist/server/error-noise-filter.d.ts +80 -0
- package/dist/server/error-noise-filter.d.ts.map +1 -0
- package/dist/server/error-noise-filter.js +173 -0
- package/dist/server/error-noise-filter.js.map +1 -0
- package/dist/server/posthog-config.d.ts +28 -0
- package/dist/server/posthog-config.d.ts.map +1 -0
- package/dist/server/posthog-config.js +53 -0
- package/dist/server/posthog-config.js.map +1 -0
- package/dist/server/request-context.d.ts +6 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/sentry-config.d.ts +1 -0
- package/dist/server/sentry-config.d.ts.map +1 -1
- package/dist/server/sentry-config.js +3 -1
- package/dist/server/sentry-config.js.map +1 -1
- package/dist/server/sentry-plugin.d.ts.map +1 -1
- package/dist/server/sentry-plugin.js +12 -27
- package/dist/server/sentry-plugin.js.map +1 -1
- package/dist/server/sentry.d.ts.map +1 -1
- package/dist/server/sentry.js +8 -124
- package/dist/server/sentry.js.map +1 -1
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +6 -1
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
- package/dist/shared/mcp-embed-headers.js +1 -1
- package/dist/shared/mcp-embed-headers.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
- package/dist/tracking/error-capture.d.ts +7 -0
- package/dist/tracking/error-capture.d.ts.map +1 -1
- package/dist/tracking/error-capture.js +10 -73
- package/dist/tracking/error-capture.js.map +1 -1
- package/dist/tracking/index.d.ts +1 -0
- package/dist/tracking/index.d.ts.map +1 -1
- package/dist/tracking/index.js +1 -0
- package/dist/tracking/index.js.map +1 -1
- package/dist/tracking/posthog-exception.d.ts +103 -0
- package/dist/tracking/posthog-exception.d.ts.map +1 -0
- package/dist/tracking/posthog-exception.js +181 -0
- package/dist/tracking/posthog-exception.js.map +1 -0
- package/dist/tracking/providers.d.ts +14 -0
- package/dist/tracking/providers.d.ts.map +1 -1
- package/dist/tracking/providers.js +63 -11
- package/dist/tracking/providers.js.map +1 -1
- package/dist/tracking/redaction.d.ts +25 -0
- package/dist/tracking/redaction.d.ts.map +1 -0
- package/dist/tracking/redaction.js +85 -0
- package/dist/tracking/redaction.js.map +1 -0
- package/docs/content/observability.mdx +41 -1
- package/docs/content/template-content-developers.mdx +1 -1
- package/docs/content/tracking.mdx +25 -0
- package/package.json +1 -1
- package/src/agent/run-manager.ts +7 -0
- package/src/client/agent-chat-adapter.ts +10 -0
- package/src/client/analytics.ts +105 -1
- package/src/deploy/build.ts +38 -1
- package/src/observability/posthog-ai.ts +294 -0
- package/src/observability/routes.ts +29 -6
- package/src/observability/traces.ts +287 -12
- package/src/observability/types.ts +11 -0
- package/src/secrets/register-framework-secrets.ts +16 -0
- package/src/server/agent-run-context.ts +21 -0
- package/src/server/capture-error.ts +8 -0
- package/src/server/core-routes-plugin.ts +75 -4
- package/src/server/deploy-environment.ts +57 -0
- package/src/server/error-noise-filter.ts +268 -0
- package/src/server/posthog-config.ts +75 -0
- package/src/server/request-context.ts +6 -0
- package/src/server/sentry-config.ts +4 -8
- package/src/server/sentry-plugin.ts +13 -29
- package/src/server/sentry.ts +13 -156
- package/src/server/ssr-handler.ts +6 -1
- package/src/shared/mcp-embed-headers.ts +1 -1
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
- package/src/tracking/error-capture.ts +44 -109
- package/src/tracking/index.ts +11 -0
- package/src/tracking/posthog-exception.ts +305 -0
- package/src/tracking/providers.ts +91 -14
- package/src/tracking/redaction.ts +105 -0
- package/corpus/templates/content/actions/delete-database-items.ts +0 -89
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-agnostic drop rules for server error reporting.
|
|
3
|
+
*
|
|
4
|
+
* These rules were tuned against real production volume while Sentry was the
|
|
5
|
+
* only backend, and they lived inside its `beforeSend`. They are not Sentry
|
|
6
|
+
* policy — they describe which server errors are non-bugs in *this* framework
|
|
7
|
+
* (expected 4xx, access-control rejections, Lambda freeze/thaw socket noise).
|
|
8
|
+
* Any second backend that skips them receives a firehose rather than a signal:
|
|
9
|
+
* the `socket hang up` rule alone accounts for ~10k events/day.
|
|
10
|
+
*
|
|
11
|
+
* The rules operate on a normalized signal so the same predicate can judge a
|
|
12
|
+
* Sentry `Event` (already parsed by the SDK) and a raw `Error` reaching the
|
|
13
|
+
* PostHog provider. Fields a given source cannot supply stay `undefined`, and
|
|
14
|
+
* every rule requires the evidence it depends on — an unknown never reads as
|
|
15
|
+
* a match.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { parseStackFrames } from "../tracking/posthog-exception.js";
|
|
19
|
+
|
|
20
|
+
export interface ErrorSignalFrame {
|
|
21
|
+
function?: string;
|
|
22
|
+
filename?: string;
|
|
23
|
+
in_app?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface NormalizedErrorSignal {
|
|
27
|
+
/** Error class name, e.g. `ValidationError`. */
|
|
28
|
+
type?: string;
|
|
29
|
+
/** Error message. */
|
|
30
|
+
value?: string;
|
|
31
|
+
/** Capture mechanism, e.g. `onunhandledrejection`. */
|
|
32
|
+
mechanismType?: string;
|
|
33
|
+
frames?: ErrorSignalFrame[];
|
|
34
|
+
/** HTTP status carried by h3's `HTTPError` / `H3Error`. */
|
|
35
|
+
statusCode?: number;
|
|
36
|
+
tags?: Record<string, string | undefined>;
|
|
37
|
+
/**
|
|
38
|
+
* Sentry-only: some SDK-internal rejections arrive with no `exception.values`
|
|
39
|
+
* at all and only a `metadata` blob. Left `undefined` elsewhere.
|
|
40
|
+
*/
|
|
41
|
+
metadataValue?: string;
|
|
42
|
+
metadataFilename?: string;
|
|
43
|
+
hasExceptionValues?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isUnhandledRejection(signal: NormalizedErrorSignal): boolean {
|
|
47
|
+
return (
|
|
48
|
+
typeof signal.mechanismType === "string" &&
|
|
49
|
+
signal.mechanismType.endsWith("onunhandledrejection")
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Expected user-input rejections. The framework and CLI both throw these. */
|
|
54
|
+
function isValidationNoise(signal: NormalizedErrorSignal): boolean {
|
|
55
|
+
return (
|
|
56
|
+
signal.type === "ValidationError" || signal.tags?.handled === "validation"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Access-control rejections. These are 4xx user-facing errors that reached the
|
|
62
|
+
* error hook because a route forgot to catch them — fixing the route is the
|
|
63
|
+
* right answer, but until then they bury real bugs. Auth routes report their
|
|
64
|
+
* own failures at `warning` level, so this only sees the escape path.
|
|
65
|
+
*/
|
|
66
|
+
function isAccessControlNoise(signal: NormalizedErrorSignal): boolean {
|
|
67
|
+
return (
|
|
68
|
+
signal.type === "ForbiddenError" || signal.type === "UnauthorizedError"
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* `socket hang up` unhandled rejections from Lambda freeze cycles. AWS recycles
|
|
74
|
+
* long-lived sockets (MCP Streamable HTTP long-polls, keep-alive agents) ~60s
|
|
75
|
+
* after a function returns 200; the next thaw delivers a socket-end event whose
|
|
76
|
+
* Promise has nobody left to await it. The function already returned correctly,
|
|
77
|
+
* so there is no user impact. The narrow frame match keeps genuine
|
|
78
|
+
* application-thrown socket errors visible.
|
|
79
|
+
*/
|
|
80
|
+
function isLambdaSocketHangUpNoise(signal: NormalizedErrorSignal): boolean {
|
|
81
|
+
if (signal.value !== "socket hang up" || !isUnhandledRejection(signal)) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return (signal.frames ?? []).some(
|
|
85
|
+
(frame) =>
|
|
86
|
+
frame?.function === "Socket.socketOnEnd" ||
|
|
87
|
+
frame?.filename === "node:_http_client",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* SDK-only `ErrorEvent` promise rejections — typically the Neon serverless
|
|
93
|
+
* driver's WebSocket dying across a freeze/thaw and rejecting a floating
|
|
94
|
+
* promise with the raw ErrorEvent (`db/client.ts` already records these with
|
|
95
|
+
* context). Events with real application frames stay visible.
|
|
96
|
+
*
|
|
97
|
+
* "Application frame" must exclude the Sentry SDK's own bundled chunks:
|
|
98
|
+
* serverless bundles place them under the app root (e.g.
|
|
99
|
+
* `/var/task/_libs/@sentry/node+….mjs`), outside node_modules, so the SDK marks
|
|
100
|
+
* them `in_app` and the instrumentation stack alone would defeat this filter.
|
|
101
|
+
*/
|
|
102
|
+
function isSdkErrorEventNoise(signal: NormalizedErrorSignal): boolean {
|
|
103
|
+
if (signal.value === "[object ErrorEvent]" && isUnhandledRejection(signal)) {
|
|
104
|
+
const frames = signal.frames ?? [];
|
|
105
|
+
const hasApplicationFrame = frames.some(
|
|
106
|
+
(frame) =>
|
|
107
|
+
frame?.in_app && !String(frame?.filename ?? "").includes("sentry"),
|
|
108
|
+
);
|
|
109
|
+
const hasSentryFrame = frames.some((frame) =>
|
|
110
|
+
String(frame?.filename ?? "").includes("sentry"),
|
|
111
|
+
);
|
|
112
|
+
if (!hasApplicationFrame && hasSentryFrame) return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
signal.metadataValue === "[object ErrorEvent]" &&
|
|
117
|
+
signal.hasExceptionValues === false &&
|
|
118
|
+
String(signal.metadataFilename ?? "").includes("sentry")
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* h3's `createError({ statusCode: 4xx })` produces an `HTTPError` (h3 v2) /
|
|
124
|
+
* `H3Error` (h3 v1). 4xx ones are handler-controlled "expected failure"
|
|
125
|
+
* responses that route through the error hook only because they bubble out of
|
|
126
|
+
* `defineEventHandler`. Capture when the status looks 5xx, or is missing on a
|
|
127
|
+
* generic Error masquerading as an HTTPError.
|
|
128
|
+
*/
|
|
129
|
+
function isExpectedHttpNoise(signal: NormalizedErrorSignal): boolean {
|
|
130
|
+
if (signal.type !== "HTTPError" && signal.type !== "H3Error") return false;
|
|
131
|
+
|
|
132
|
+
const code = signal.statusCode;
|
|
133
|
+
if (typeof code === "number" && Number.isFinite(code)) {
|
|
134
|
+
return code >= 400 && code < 500;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// No status in the payload — match the common 4xx messages so handler-thrown
|
|
138
|
+
// 404/400/403/401 don't pollute the backend. A heuristic, but the
|
|
139
|
+
// alternatives (every 4xx becomes a real issue, or every route grows a
|
|
140
|
+
// catch+return) are worse.
|
|
141
|
+
const value = signal.value ?? "";
|
|
142
|
+
return (
|
|
143
|
+
/^Cannot find any route matching/i.test(value) ||
|
|
144
|
+
/ not found$/i.test(value) ||
|
|
145
|
+
/Unauthenticated$/i.test(value) ||
|
|
146
|
+
/^Unauthorized$/i.test(value) ||
|
|
147
|
+
/^No access to /i.test(value)
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* `false` when the error is known non-bug noise and should not be reported.
|
|
153
|
+
*
|
|
154
|
+
* Deliberately fails open: anything the rules cannot positively identify as
|
|
155
|
+
* noise is reported. A dropped real error is invisible; a kept noisy one is
|
|
156
|
+
* merely loud.
|
|
157
|
+
*/
|
|
158
|
+
export function shouldReportErrorSignal(
|
|
159
|
+
signal: NormalizedErrorSignal,
|
|
160
|
+
): boolean {
|
|
161
|
+
return !(
|
|
162
|
+
isValidationNoise(signal) ||
|
|
163
|
+
isAccessControlNoise(signal) ||
|
|
164
|
+
isLambdaSocketHangUpNoise(signal) ||
|
|
165
|
+
isSdkErrorEventNoise(signal) ||
|
|
166
|
+
isExpectedHttpNoise(signal)
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
interface SentryLikeEvent {
|
|
171
|
+
exception?: {
|
|
172
|
+
values?: Array<{
|
|
173
|
+
type?: string;
|
|
174
|
+
value?: string;
|
|
175
|
+
mechanism?: { type?: string };
|
|
176
|
+
stacktrace?: { frames?: ErrorSignalFrame[] };
|
|
177
|
+
}>;
|
|
178
|
+
};
|
|
179
|
+
tags?: Record<string, unknown>;
|
|
180
|
+
contexts?: Record<string, Record<string, unknown> | undefined>;
|
|
181
|
+
metadata?: { value?: unknown; filename?: unknown };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function toNumericStatus(value: unknown): number | undefined {
|
|
185
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
186
|
+
if (typeof value === "string") {
|
|
187
|
+
const parsed = parseInt(value, 10);
|
|
188
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
189
|
+
}
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Normalize a Sentry event. Typed structurally so this module stays SDK-free. */
|
|
194
|
+
export function errorSignalFromSentryEvent(
|
|
195
|
+
event: SentryLikeEvent,
|
|
196
|
+
): NormalizedErrorSignal {
|
|
197
|
+
const first = event.exception?.values?.[0];
|
|
198
|
+
const tags: Record<string, string | undefined> = {};
|
|
199
|
+
for (const [key, value] of Object.entries(event.tags ?? {})) {
|
|
200
|
+
if (typeof value === "string") tags[key] = value;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
type: first?.type,
|
|
205
|
+
value: first?.value ?? "",
|
|
206
|
+
mechanismType: first?.mechanism?.type,
|
|
207
|
+
frames: first?.stacktrace?.frames ?? [],
|
|
208
|
+
statusCode: toNumericStatus(
|
|
209
|
+
event.tags?.statusCode ?? event.contexts?.h3?.statusCode,
|
|
210
|
+
),
|
|
211
|
+
tags,
|
|
212
|
+
metadataValue:
|
|
213
|
+
typeof event.metadata?.value === "string"
|
|
214
|
+
? event.metadata.value
|
|
215
|
+
: undefined,
|
|
216
|
+
metadataFilename:
|
|
217
|
+
typeof event.metadata?.filename === "string"
|
|
218
|
+
? event.metadata.filename
|
|
219
|
+
: undefined,
|
|
220
|
+
hasExceptionValues: Boolean(event.exception?.values?.length),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface ErrorSignalFromErrorOptions {
|
|
225
|
+
mechanismType?: string;
|
|
226
|
+
tags?: Record<string, string | undefined>;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Normalize a raw thrown value for backends that never see a Sentry event. */
|
|
230
|
+
export function errorSignalFromError(
|
|
231
|
+
error: unknown,
|
|
232
|
+
options: ErrorSignalFromErrorOptions = {},
|
|
233
|
+
): NormalizedErrorSignal {
|
|
234
|
+
if (!(error instanceof Error)) {
|
|
235
|
+
return {
|
|
236
|
+
type: "Error",
|
|
237
|
+
value: typeof error === "string" ? error : String(error ?? ""),
|
|
238
|
+
mechanismType: options.mechanismType,
|
|
239
|
+
tags: options.tags,
|
|
240
|
+
hasExceptionValues: true,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const withStatus = error as Error & {
|
|
245
|
+
statusCode?: unknown;
|
|
246
|
+
status?: unknown;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
type: error.name || "Error",
|
|
251
|
+
value: error.message ?? "",
|
|
252
|
+
mechanismType: options.mechanismType,
|
|
253
|
+
frames: parseStackFrames(error.stack),
|
|
254
|
+
statusCode:
|
|
255
|
+
toNumericStatus(withStatus.statusCode) ??
|
|
256
|
+
toNumericStatus(withStatus.status),
|
|
257
|
+
tags: options.tags,
|
|
258
|
+
hasExceptionValues: true,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Convenience wrapper for the raw-error path. */
|
|
263
|
+
export function shouldReportError(
|
|
264
|
+
error: unknown,
|
|
265
|
+
options: ErrorSignalFromErrorOptions = {},
|
|
266
|
+
): boolean {
|
|
267
|
+
return shouldReportErrorSignal(errorSignalFromError(error, options));
|
|
268
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public PostHog config for the browser.
|
|
3
|
+
*
|
|
4
|
+
* PostHog project API keys are publishable — that is how `posthog-js` ships in
|
|
5
|
+
* every customer's bundle — so this mirrors how the Sentry client DSN already
|
|
6
|
+
* reaches the browser: env-derived, identical for every visitor, and therefore
|
|
7
|
+
* safe inside the CDN-cached SSR shell (see `guard:ssr-cache-shell`).
|
|
8
|
+
*
|
|
9
|
+
* The browser sends exceptions straight to PostHog rather than relaying them
|
|
10
|
+
* through `/_agent-native/track`: that route requires a resolved session by
|
|
11
|
+
* design, so relaying would silently drop every signed-out crash — exactly the
|
|
12
|
+
* class of failure that looks like "no errors" instead of "no reporting".
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const POSTHOG_DEFAULT_HOST = "https://us.i.posthog.com";
|
|
16
|
+
|
|
17
|
+
function firstNonEmpty(
|
|
18
|
+
...values: Array<string | undefined>
|
|
19
|
+
): string | undefined {
|
|
20
|
+
for (const value of values) {
|
|
21
|
+
const trimmed = value?.trim();
|
|
22
|
+
if (trimmed) return trimmed;
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface PublicPostHogConfig {
|
|
28
|
+
posthogKey: string;
|
|
29
|
+
posthogHost: string;
|
|
30
|
+
posthogErrorTracking: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the browser-facing PostHog config, or `undefined` when none is set.
|
|
35
|
+
*
|
|
36
|
+
* Note this deliberately does NOT fall back to `POSTHOG_API_KEY`: a
|
|
37
|
+
* server-only personal/private key must never be inlined into the public HTML
|
|
38
|
+
* shell. Operators opt into browser capture by setting a public key explicitly.
|
|
39
|
+
*/
|
|
40
|
+
export function resolvePublicPostHogConfig(): PublicPostHogConfig | undefined {
|
|
41
|
+
const posthogKey = firstNonEmpty(
|
|
42
|
+
process.env.POSTHOG_PUBLIC_KEY,
|
|
43
|
+
process.env.VITE_POSTHOG_KEY,
|
|
44
|
+
process.env.VITE_POSTHOG_PUBLIC_KEY,
|
|
45
|
+
);
|
|
46
|
+
if (!posthogKey) return undefined;
|
|
47
|
+
|
|
48
|
+
const posthogHost = (
|
|
49
|
+
firstNonEmpty(
|
|
50
|
+
process.env.POSTHOG_PUBLIC_HOST,
|
|
51
|
+
process.env.VITE_POSTHOG_HOST,
|
|
52
|
+
process.env.POSTHOG_HOST,
|
|
53
|
+
) ?? POSTHOG_DEFAULT_HOST
|
|
54
|
+
).replace(/\/+$/, "");
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
posthogKey,
|
|
58
|
+
posthogHost,
|
|
59
|
+
posthogErrorTracking:
|
|
60
|
+
process.env.POSTHOG_ERROR_TRACKING?.trim().toLowerCase() !== "false",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getPostHogClientConfigScript(): string | null {
|
|
65
|
+
const config = resolvePublicPostHogConfig();
|
|
66
|
+
if (!config) return null;
|
|
67
|
+
|
|
68
|
+
return [
|
|
69
|
+
"<script data-agent-native-posthog-config>",
|
|
70
|
+
"window.__AGENT_NATIVE_CONFIG__=Object.assign({},window.__AGENT_NATIVE_CONFIG__,",
|
|
71
|
+
JSON.stringify(config),
|
|
72
|
+
");",
|
|
73
|
+
"</script>",
|
|
74
|
+
].join("");
|
|
75
|
+
}
|
|
@@ -140,6 +140,12 @@ export interface RequestContext {
|
|
|
140
140
|
*/
|
|
141
141
|
authCapability?: string;
|
|
142
142
|
timezone?: string;
|
|
143
|
+
/**
|
|
144
|
+
* The caller's browser analytics session id, when the request came from a
|
|
145
|
+
* page. Emitted as PostHog's `$session_id` so agent traces join to session
|
|
146
|
+
* replay; never used for authorization.
|
|
147
|
+
*/
|
|
148
|
+
browserSessionId?: string;
|
|
143
149
|
/**
|
|
144
150
|
* Set when code reads authenticated request context. Public SSR shell/data
|
|
145
151
|
* should not depend on this value; user/org-specific reads belong behind
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolveDeployEnvironment } from "./deploy-environment.js";
|
|
2
|
+
|
|
1
3
|
function firstNonEmpty(
|
|
2
4
|
...values: Array<string | undefined>
|
|
3
5
|
): string | undefined {
|
|
@@ -25,15 +27,9 @@ function resolveSentryDsnFromKeyProject(): string | undefined {
|
|
|
25
27
|
return `https://${key}@${host}/${projectId}`;
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
/** @deprecated Use `resolveDeployEnvironment()` — it is not Sentry-specific. */
|
|
28
31
|
export function resolveSentryEnvironment(): string {
|
|
29
|
-
return (
|
|
30
|
-
firstNonEmpty(
|
|
31
|
-
process.env.SENTRY_ENVIRONMENT,
|
|
32
|
-
process.env.NETLIFY_CONTEXT,
|
|
33
|
-
process.env.VERCEL_ENV,
|
|
34
|
-
process.env.NODE_ENV,
|
|
35
|
-
) ?? "production"
|
|
36
|
-
);
|
|
32
|
+
return resolveDeployEnvironment();
|
|
37
33
|
}
|
|
38
34
|
|
|
39
35
|
export function resolveServerSentryDsn(): string | undefined {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type H3Event } from "h3";
|
|
2
2
|
|
|
3
3
|
import { getSession } from "./auth.js";
|
|
4
4
|
import { registerErrorCaptureProvider } from "./capture-error.js";
|
|
@@ -9,12 +9,16 @@ import { registerErrorCaptureProvider } from "./capture-error.js";
|
|
|
9
9
|
* Wires three pieces:
|
|
10
10
|
* 1. On startup, `initServerSentry()` reads `SENTRY_SERVER_DSN`/`SENTRY_DSN` and arms
|
|
11
11
|
* the SDK (no-op when the env var is unset).
|
|
12
|
-
* 2.
|
|
12
|
+
* 2. Registers Sentry as a `captureError()` backend, so route errors and
|
|
13
|
+
* explicit captures alike reach it through the shared registry.
|
|
14
|
+
* 3. On every request, hook into Nitro's `request` event: resolve the
|
|
13
15
|
* session via `getSession(event)` and tag the per-request isolation
|
|
14
16
|
* scope with the user's id/email/orgId. Wrapped in try/catch so a
|
|
15
17
|
* session-resolution failure can never 500 the request.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
+
*
|
|
19
|
+
* It does NOT hook Nitro's `error` event — that is provider-agnostic and lives
|
|
20
|
+
* in `core-routes-plugin.ts`, so error reporting works with PostHog (or any
|
|
21
|
+
* other backend) configured and no Sentry DSN set at all.
|
|
18
22
|
*
|
|
19
23
|
* Mounted as a default plugin from `framework-request-handler.ts` —
|
|
20
24
|
* templates that don't define `server/plugins/sentry.ts` get this for
|
|
@@ -44,14 +48,6 @@ function readRoute(event: H3Event): string | undefined {
|
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
function readUserAgent(event: H3Event): string | undefined {
|
|
48
|
-
try {
|
|
49
|
-
return getHeader(event, "user-agent");
|
|
50
|
-
} catch {
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
51
|
/**
|
|
56
52
|
* Skip session resolution for paths that obviously don't need one. Avoids
|
|
57
53
|
* a DB round-trip on every static-asset / favicon / public-share request
|
|
@@ -109,23 +105,11 @@ export function createSentryPlugin(): NitroPluginDef {
|
|
|
109
105
|
setSentryRequestContext({ userEmail: ctx.userEmail, orgId: ctx.orgId });
|
|
110
106
|
});
|
|
111
107
|
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
const event = ctx?.event;
|
|
119
|
-
captureRouteError(error, {
|
|
120
|
-
route: event ? readRoute(event) : undefined,
|
|
121
|
-
method: event ? getMethod(event) : undefined,
|
|
122
|
-
userAgent: event ? readUserAgent(event) : undefined,
|
|
123
|
-
});
|
|
124
|
-
} catch {
|
|
125
|
-
// Sentry capture must never escape into Nitro's error path.
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
);
|
|
108
|
+
// Route errors are NOT hooked here. `core-routes-plugin.ts` owns the
|
|
109
|
+
// provider-agnostic Nitro `error` hook and routes it through
|
|
110
|
+
// `captureError()`, which reaches Sentry via the registration above along
|
|
111
|
+
// with every other configured backend. Hooking it here too would report
|
|
112
|
+
// each route error to Sentry twice.
|
|
129
113
|
};
|
|
130
114
|
}
|
|
131
115
|
|
package/src/server/sentry.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
|
|
5
1
|
/**
|
|
6
2
|
* Server-side Sentry initialization for Nitro.
|
|
7
3
|
*
|
|
@@ -24,36 +20,18 @@ import * as Sentry from "@sentry/node";
|
|
|
24
20
|
|
|
25
21
|
import type { AuthSession } from "./auth.js";
|
|
26
22
|
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
} from "./
|
|
23
|
+
resolveDeployEnvironment,
|
|
24
|
+
resolveServerRelease,
|
|
25
|
+
} from "./deploy-environment.js";
|
|
26
|
+
import {
|
|
27
|
+
errorSignalFromSentryEvent,
|
|
28
|
+
shouldReportErrorSignal,
|
|
29
|
+
} from "./error-noise-filter.js";
|
|
30
|
+
import { resolveServerSentryDsn } from "./sentry-config.js";
|
|
30
31
|
|
|
31
32
|
let _initStarted = false;
|
|
32
33
|
let _initSucceeded = false;
|
|
33
34
|
|
|
34
|
-
/**
|
|
35
|
-
* Resolve the agent-native version baked into core's package.json so Sentry
|
|
36
|
-
* "release" reflects the running framework version. Mirrors how the CLI
|
|
37
|
-
* computes `_version` — same dist layout, same fallback string. Guarded so
|
|
38
|
-
* a missing/unreadable package.json never crashes server boot.
|
|
39
|
-
*/
|
|
40
|
-
function resolveServerRelease(): string {
|
|
41
|
-
const explicit = process.env.AGENT_NATIVE_RELEASE;
|
|
42
|
-
if (explicit) return explicit;
|
|
43
|
-
try {
|
|
44
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
45
|
-
// dist/server/sentry.js → ../../package.json
|
|
46
|
-
const pkgPath = path.resolve(here, "../../package.json");
|
|
47
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")) as {
|
|
48
|
-
version?: string;
|
|
49
|
-
};
|
|
50
|
-
if (pkg?.version) return `agent-native-server@${pkg.version}`;
|
|
51
|
-
} catch {
|
|
52
|
-
// ignore — fall through to "unknown"
|
|
53
|
-
}
|
|
54
|
-
return "agent-native-server@unknown";
|
|
55
|
-
}
|
|
56
|
-
|
|
57
35
|
function parseTracesSampleRate(): number {
|
|
58
36
|
const raw = process.env.SENTRY_SERVER_TRACES_SAMPLE_RATE;
|
|
59
37
|
if (!raw) return 0;
|
|
@@ -88,7 +66,7 @@ export function initServerSentry(): boolean {
|
|
|
88
66
|
|
|
89
67
|
Sentry.init({
|
|
90
68
|
dsn,
|
|
91
|
-
environment:
|
|
69
|
+
environment: resolveDeployEnvironment(),
|
|
92
70
|
release: resolveServerRelease(),
|
|
93
71
|
tracesSampleRate: parseTracesSampleRate(),
|
|
94
72
|
// sendDefaultPii MUST stay false — the framework runs inside customer
|
|
@@ -96,133 +74,12 @@ export function initServerSentry(): boolean {
|
|
|
96
74
|
// cookies, or process.env contents to Sentry without explicit consent.
|
|
97
75
|
sendDefaultPii: false,
|
|
98
76
|
beforeSend(event) {
|
|
99
|
-
// Drop
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
const exceptionType = event.exception?.values?.[0]?.type;
|
|
104
|
-
if (
|
|
105
|
-
exceptionType === "ValidationError" ||
|
|
106
|
-
event.tags?.handled === "validation"
|
|
107
|
-
) {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
// Drop access-control rejections (caller lacks permission, signed
|
|
111
|
-
// out, etc.). These are 4xx user-facing errors that propagated to
|
|
112
|
-
// Nitro's error hook because a route forgot to catch them — fixing
|
|
113
|
-
// the route is the right answer, but in the meantime they bury
|
|
114
|
-
// real bugs and don't represent server failures. Auth-routes use
|
|
115
|
-
// `captureAuthError` directly with `level: warning` so this filter
|
|
116
|
-
// only sees the generic-handler escape path.
|
|
117
|
-
if (
|
|
118
|
-
exceptionType === "ForbiddenError" ||
|
|
119
|
-
exceptionType === "UnauthorizedError"
|
|
120
|
-
) {
|
|
77
|
+
// Drop rules live in `error-noise-filter.ts` so every backend applies
|
|
78
|
+
// the same ones — they describe which server errors are non-bugs in
|
|
79
|
+
// this framework, not a Sentry preference.
|
|
80
|
+
if (!shouldReportErrorSignal(errorSignalFromSentryEvent(event))) {
|
|
121
81
|
return null;
|
|
122
82
|
}
|
|
123
|
-
// Drop "socket hang up" unhandled promise rejections that fire from
|
|
124
|
-
// Lambda freeze cycles. AWS Lambda recycles long-lived sockets (e.g.
|
|
125
|
-
// MCP Streamable HTTP long-polls, keep-alive HTTP agents) ~60s after
|
|
126
|
-
// a function returns 200; the next thaw delivers a socket-end event
|
|
127
|
-
// whose Promise has nobody left to await it. The function itself
|
|
128
|
-
// already returned correctly, so there's no user impact — just
|
|
129
|
-
// ~10k events/day of noise. The narrow shape (unhandled rejection
|
|
130
|
-
// from `Socket.socketOnEnd` in `node:_http_client`) keeps real
|
|
131
|
-
// application-thrown socket errors from being silenced.
|
|
132
|
-
const exceptionValue = event.exception?.values?.[0]?.value ?? "";
|
|
133
|
-
const exceptionMechanism = event.exception?.values?.[0]?.mechanism?.type;
|
|
134
|
-
const isUnhandledRejection =
|
|
135
|
-
typeof exceptionMechanism === "string" &&
|
|
136
|
-
exceptionMechanism.endsWith("onunhandledrejection");
|
|
137
|
-
if (exceptionValue === "socket hang up" && isUnhandledRejection) {
|
|
138
|
-
const frames = event.exception?.values?.[0]?.stacktrace?.frames ?? [];
|
|
139
|
-
const fromHttpClient = frames.some(
|
|
140
|
-
(f) =>
|
|
141
|
-
f?.function === "Socket.socketOnEnd" ||
|
|
142
|
-
f?.filename === "node:_http_client",
|
|
143
|
-
);
|
|
144
|
-
if (fromHttpClient) {
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
// Drop SDK-only ErrorEvent promise rejections. These arrive as Node
|
|
149
|
-
// unhandled rejections with no application frames — typically the Neon
|
|
150
|
-
// serverless driver's WebSocket dying across a Lambda freeze/thaw and
|
|
151
|
-
// rejecting a floating promise with the raw ErrorEvent (the per-client
|
|
152
|
-
// logger in db/client.ts already records these with context). Keep any
|
|
153
|
-
// event with real app frames so thrown ErrorEvents stay visible.
|
|
154
|
-
//
|
|
155
|
-
// "Application frame" must exclude the Sentry SDK's own bundled chunks:
|
|
156
|
-
// serverless bundles place them under the app root (e.g.
|
|
157
|
-
// `/var/task/_libs/@sentry/node+….mjs`), outside node_modules, so the
|
|
158
|
-
// SDK marks them in_app and the unhandled-rejection instrumentation
|
|
159
|
-
// stack alone would defeat this filter.
|
|
160
|
-
if (exceptionValue === "[object ErrorEvent]" && isUnhandledRejection) {
|
|
161
|
-
const frames = event.exception?.values?.[0]?.stacktrace?.frames ?? [];
|
|
162
|
-
const hasApplicationFrame = frames.some(
|
|
163
|
-
(frame) =>
|
|
164
|
-
frame?.in_app && !String(frame?.filename ?? "").includes("sentry"),
|
|
165
|
-
);
|
|
166
|
-
const hasSentryFrame = frames.some((frame) =>
|
|
167
|
-
String(frame?.filename ?? "").includes("sentry"),
|
|
168
|
-
);
|
|
169
|
-
if (!hasApplicationFrame && hasSentryFrame) {
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
const metadata = (
|
|
174
|
-
event as {
|
|
175
|
-
metadata?: {
|
|
176
|
-
filename?: unknown;
|
|
177
|
-
value?: unknown;
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
).metadata;
|
|
181
|
-
if (
|
|
182
|
-
metadata?.value === "[object ErrorEvent]" &&
|
|
183
|
-
!event.exception?.values?.length &&
|
|
184
|
-
String(metadata.filename ?? "").includes("sentry")
|
|
185
|
-
) {
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
// h3's `createError({ statusCode: 4xx, ... })` produces an
|
|
189
|
-
// `HTTPError` (h3 v2) / `H3Error` (h3 v1). 4xx HTTPErrors are
|
|
190
|
-
// handler-controlled "expected failure" responses (404 not found,
|
|
191
|
-
// 400 bad input) that route through Nitro's error hook just
|
|
192
|
-
// because they bubble out of `defineEventHandler`. They aren't
|
|
193
|
-
// bugs — they're the documented way to return a 4xx in h3.
|
|
194
|
-
// Capture only when the statusCode looks like 5xx (real failure)
|
|
195
|
-
// or is missing (generic Error masquerading as HTTPError).
|
|
196
|
-
if (exceptionType === "HTTPError" || exceptionType === "H3Error") {
|
|
197
|
-
const statusCode = (event.tags?.statusCode ??
|
|
198
|
-
(
|
|
199
|
-
event.contexts as
|
|
200
|
-
| Record<string, Record<string, unknown>>
|
|
201
|
-
| undefined
|
|
202
|
-
)?.h3?.statusCode) as number | string | undefined;
|
|
203
|
-
const code =
|
|
204
|
-
typeof statusCode === "string"
|
|
205
|
-
? parseInt(statusCode, 10)
|
|
206
|
-
: statusCode;
|
|
207
|
-
if (typeof code === "number" && code >= 400 && code < 500) {
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
// No statusCode in the event payload — fall back to matching the
|
|
211
|
-
// common 4xx messages so handler-thrown 404/400/403/401 don't
|
|
212
|
-
// pollute Sentry. This is a heuristic, but the alternatives
|
|
213
|
-
// (every 4xx becomes a "real" issue, or we patch every route to
|
|
214
|
-
// catch+return) are worse.
|
|
215
|
-
const value = event.exception?.values?.[0]?.value ?? "";
|
|
216
|
-
if (
|
|
217
|
-
/^Cannot find any route matching/i.test(value) ||
|
|
218
|
-
/ not found$/i.test(value) ||
|
|
219
|
-
/Unauthenticated$/i.test(value) ||
|
|
220
|
-
/^Unauthorized$/i.test(value) ||
|
|
221
|
-
/^No access to /i.test(value)
|
|
222
|
-
) {
|
|
223
|
-
return null;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
83
|
|
|
227
84
|
// Defense in depth: scrub PII even if some integration auto-attached
|
|
228
85
|
// request metadata despite sendDefaultPii: false.
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
stripAppBasePath as canonicalStripAppBasePath,
|
|
37
37
|
} from "./app-base-path.js";
|
|
38
38
|
import { captureError } from "./capture-error.js";
|
|
39
|
+
import { getPostHogClientConfigScript } from "./posthog-config.js";
|
|
39
40
|
import { runWithRequestContext } from "./request-context.js";
|
|
40
41
|
import {
|
|
41
42
|
getRealtimeClientConfigScript,
|
|
@@ -357,7 +358,11 @@ async function rewriteMountedResponse(
|
|
|
357
358
|
requestUrl: string,
|
|
358
359
|
): Promise<Response> {
|
|
359
360
|
const clientConfigScript =
|
|
360
|
-
[
|
|
361
|
+
[
|
|
362
|
+
getSentryClientConfigScript(),
|
|
363
|
+
getPostHogClientConfigScript(),
|
|
364
|
+
getRealtimeClientConfigScript(),
|
|
365
|
+
]
|
|
361
366
|
.filter(Boolean)
|
|
362
367
|
.join("") || null;
|
|
363
368
|
const headers = new Headers(response.headers);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const MCP_EMBED_CORS_ALLOW_HEADERS =
|
|
2
|
-
"Content-Type,Authorization,X-Requested-With,X-Request-Source,X-Agent-Native-CSRF,X-Agent-Native-Frontend,X-Agent-Native-Client-Compatibility,X-Agent-Native-Build-Id,X-User-Timezone,X-Agent-Native-Embed-Target,X-Agent-Native-Embed-Transplant";
|
|
2
|
+
"Content-Type,Authorization,X-Requested-With,X-Request-Source,X-Agent-Native-CSRF,X-Agent-Native-Frontend,X-Agent-Native-Client-Compatibility,X-Agent-Native-Build-Id,X-User-Timezone,X-Agent-Native-Session-Id,X-Agent-Native-Embed-Target,X-Agent-Native-Embed-Transplant";
|
|
3
3
|
export const EMBED_TRANSPLANT_HEADER = "x-agent-native-embed-transplant";
|
|
4
4
|
|
|
5
5
|
const CLAUDE_MCP_CONTENT_HOST_RE = /^[a-f0-9]{32}\.claudemcpcontent\.com$/i;
|