@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,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deployment identity shared by every error-reporting backend.
|
|
3
|
+
*
|
|
4
|
+
* These were originally Sentry-private helpers. They describe the deployment,
|
|
5
|
+
* not the vendor, and a second backend that reports errors without them
|
|
6
|
+
* produces issues nobody can tie to a release.
|
|
7
|
+
*/
|
|
8
|
+
/** The deploy environment name, e.g. `production`, `deploy-preview`. */
|
|
9
|
+
export declare function resolveDeployEnvironment(): string;
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the agent-native version baked into core's package.json so the
|
|
12
|
+
* reported "release" reflects the running framework version. Mirrors how the
|
|
13
|
+
* CLI computes `_version` — same dist layout, same fallback string. Guarded so
|
|
14
|
+
* a missing/unreadable package.json never crashes server boot.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveServerRelease(): string;
|
|
17
|
+
//# sourceMappingURL=deploy-environment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy-environment.d.ts","sourceRoot":"","sources":["../../src/server/deploy-environment.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgBH,wEAAwE;AACxE,wBAAgB,wBAAwB,IAAI,MAAM,CASjD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAgB7C"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deployment identity shared by every error-reporting backend.
|
|
3
|
+
*
|
|
4
|
+
* These were originally Sentry-private helpers. They describe the deployment,
|
|
5
|
+
* not the vendor, and a second backend that reports errors without them
|
|
6
|
+
* produces issues nobody can tie to a release.
|
|
7
|
+
*/
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
function firstNonEmpty(...values) {
|
|
12
|
+
for (const value of values) {
|
|
13
|
+
const trimmed = value?.trim();
|
|
14
|
+
if (trimmed)
|
|
15
|
+
return trimmed;
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
/** The deploy environment name, e.g. `production`, `deploy-preview`. */
|
|
20
|
+
export function resolveDeployEnvironment() {
|
|
21
|
+
return (firstNonEmpty(process.env.SENTRY_ENVIRONMENT, process.env.NETLIFY_CONTEXT, process.env.VERCEL_ENV, process.env.NODE_ENV) ?? "production");
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the agent-native version baked into core's package.json so the
|
|
25
|
+
* reported "release" reflects the running framework version. Mirrors how the
|
|
26
|
+
* CLI computes `_version` — same dist layout, same fallback string. Guarded so
|
|
27
|
+
* a missing/unreadable package.json never crashes server boot.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveServerRelease() {
|
|
30
|
+
const explicit = process.env.AGENT_NATIVE_RELEASE;
|
|
31
|
+
if (explicit)
|
|
32
|
+
return explicit;
|
|
33
|
+
try {
|
|
34
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
// dist/server/deploy-environment.js → ../../package.json
|
|
36
|
+
const pkgPath = path.resolve(here, "../../package.json");
|
|
37
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
38
|
+
if (pkg?.version)
|
|
39
|
+
return `agent-native-server@${pkg.version}`;
|
|
40
|
+
// coercion-ok: falls through to the distinct "unknown" release marker
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// ignore — fall through to "unknown"
|
|
44
|
+
}
|
|
45
|
+
return "agent-native-server@unknown";
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=deploy-environment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy-environment.js","sourceRoot":"","sources":["../../src/server/deploy-environment.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,SAAS,aAAa,CACpB,GAAG,MAAiC;IAEpC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;QAC9B,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;IAC9B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,wBAAwB;IACtC,OAAO,CACL,aAAa,CACX,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAC9B,OAAO,CAAC,GAAG,CAAC,eAAe,EAC3B,OAAO,CAAC,GAAG,CAAC,UAAU,EACtB,OAAO,CAAC,GAAG,CAAC,QAAQ,CACrB,IAAI,YAAY,CAClB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IAClD,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1D,yDAAyD;QACzD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC;QACzD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAEvD,CAAC;QACF,IAAI,GAAG,EAAE,OAAO;YAAE,OAAO,uBAAuB,GAAG,CAAC,OAAO,EAAE,CAAC;QAC9D,sEAAsE;IACxE,CAAC;IAAC,MAAM,CAAC;QACP,qCAAqC;IACvC,CAAC;IACD,OAAO,6BAA6B,CAAC;AACvC,CAAC","sourcesContent":["/**\n * Deployment identity shared by every error-reporting backend.\n *\n * These were originally Sentry-private helpers. They describe the deployment,\n * not the vendor, and a second backend that reports errors without them\n * produces issues nobody can tie to a release.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\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\n/** The deploy environment name, e.g. `production`, `deploy-preview`. */\nexport function resolveDeployEnvironment(): 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\n/**\n * Resolve the agent-native version baked into core's package.json so the\n * reported \"release\" reflects the running framework version. Mirrors how the\n * CLI computes `_version` — same dist layout, same fallback string. Guarded so\n * a missing/unreadable package.json never crashes server boot.\n */\nexport function 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/deploy-environment.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 // coercion-ok: falls through to the distinct \"unknown\" release marker\n } catch {\n // ignore — fall through to \"unknown\"\n }\n return \"agent-native-server@unknown\";\n}\n"]}
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
export interface ErrorSignalFrame {
|
|
18
|
+
function?: string;
|
|
19
|
+
filename?: string;
|
|
20
|
+
in_app?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface NormalizedErrorSignal {
|
|
23
|
+
/** Error class name, e.g. `ValidationError`. */
|
|
24
|
+
type?: string;
|
|
25
|
+
/** Error message. */
|
|
26
|
+
value?: string;
|
|
27
|
+
/** Capture mechanism, e.g. `onunhandledrejection`. */
|
|
28
|
+
mechanismType?: string;
|
|
29
|
+
frames?: ErrorSignalFrame[];
|
|
30
|
+
/** HTTP status carried by h3's `HTTPError` / `H3Error`. */
|
|
31
|
+
statusCode?: number;
|
|
32
|
+
tags?: Record<string, string | undefined>;
|
|
33
|
+
/**
|
|
34
|
+
* Sentry-only: some SDK-internal rejections arrive with no `exception.values`
|
|
35
|
+
* at all and only a `metadata` blob. Left `undefined` elsewhere.
|
|
36
|
+
*/
|
|
37
|
+
metadataValue?: string;
|
|
38
|
+
metadataFilename?: string;
|
|
39
|
+
hasExceptionValues?: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* `false` when the error is known non-bug noise and should not be reported.
|
|
43
|
+
*
|
|
44
|
+
* Deliberately fails open: anything the rules cannot positively identify as
|
|
45
|
+
* noise is reported. A dropped real error is invisible; a kept noisy one is
|
|
46
|
+
* merely loud.
|
|
47
|
+
*/
|
|
48
|
+
export declare function shouldReportErrorSignal(signal: NormalizedErrorSignal): boolean;
|
|
49
|
+
interface SentryLikeEvent {
|
|
50
|
+
exception?: {
|
|
51
|
+
values?: Array<{
|
|
52
|
+
type?: string;
|
|
53
|
+
value?: string;
|
|
54
|
+
mechanism?: {
|
|
55
|
+
type?: string;
|
|
56
|
+
};
|
|
57
|
+
stacktrace?: {
|
|
58
|
+
frames?: ErrorSignalFrame[];
|
|
59
|
+
};
|
|
60
|
+
}>;
|
|
61
|
+
};
|
|
62
|
+
tags?: Record<string, unknown>;
|
|
63
|
+
contexts?: Record<string, Record<string, unknown> | undefined>;
|
|
64
|
+
metadata?: {
|
|
65
|
+
value?: unknown;
|
|
66
|
+
filename?: unknown;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** Normalize a Sentry event. Typed structurally so this module stays SDK-free. */
|
|
70
|
+
export declare function errorSignalFromSentryEvent(event: SentryLikeEvent): NormalizedErrorSignal;
|
|
71
|
+
export interface ErrorSignalFromErrorOptions {
|
|
72
|
+
mechanismType?: string;
|
|
73
|
+
tags?: Record<string, string | undefined>;
|
|
74
|
+
}
|
|
75
|
+
/** Normalize a raw thrown value for backends that never see a Sentry event. */
|
|
76
|
+
export declare function errorSignalFromError(error: unknown, options?: ErrorSignalFromErrorOptions): NormalizedErrorSignal;
|
|
77
|
+
/** Convenience wrapper for the raw-error path. */
|
|
78
|
+
export declare function shouldReportError(error: unknown, options?: ErrorSignalFromErrorOptions): boolean;
|
|
79
|
+
export {};
|
|
80
|
+
//# sourceMappingURL=error-noise-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-noise-filter.d.ts","sourceRoot":"","sources":["../../src/server/error-noise-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC5B,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC1C;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AA2GD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAQT;AAED,UAAU,eAAe;IACvB,SAAS,CAAC,EAAE;QACV,MAAM,CAAC,EAAE,KAAK,CAAC;YACb,IAAI,CAAC,EAAE,MAAM,CAAC;YACd,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,SAAS,CAAC,EAAE;gBAAE,IAAI,CAAC,EAAE,MAAM,CAAA;aAAE,CAAC;YAC9B,UAAU,CAAC,EAAE;gBAAE,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAA;aAAE,CAAC;SAC9C,CAAC,CAAC;KACJ,CAAC;IACF,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAC/D,QAAQ,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACpD;AAWD,kFAAkF;AAClF,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,eAAe,GACrB,qBAAqB,CA0BvB;AAED,MAAM,WAAW,2BAA2B;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC3C;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,2BAAgC,GACxC,qBAAqB,CA2BvB;AAED,kDAAkD;AAClD,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAET"}
|
|
@@ -0,0 +1,173 @@
|
|
|
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
|
+
import { parseStackFrames } from "../tracking/posthog-exception.js";
|
|
18
|
+
function isUnhandledRejection(signal) {
|
|
19
|
+
return (typeof signal.mechanismType === "string" &&
|
|
20
|
+
signal.mechanismType.endsWith("onunhandledrejection"));
|
|
21
|
+
}
|
|
22
|
+
/** Expected user-input rejections. The framework and CLI both throw these. */
|
|
23
|
+
function isValidationNoise(signal) {
|
|
24
|
+
return (signal.type === "ValidationError" || signal.tags?.handled === "validation");
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Access-control rejections. These are 4xx user-facing errors that reached the
|
|
28
|
+
* error hook because a route forgot to catch them — fixing the route is the
|
|
29
|
+
* right answer, but until then they bury real bugs. Auth routes report their
|
|
30
|
+
* own failures at `warning` level, so this only sees the escape path.
|
|
31
|
+
*/
|
|
32
|
+
function isAccessControlNoise(signal) {
|
|
33
|
+
return (signal.type === "ForbiddenError" || signal.type === "UnauthorizedError");
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* `socket hang up` unhandled rejections from Lambda freeze cycles. AWS recycles
|
|
37
|
+
* long-lived sockets (MCP Streamable HTTP long-polls, keep-alive agents) ~60s
|
|
38
|
+
* after a function returns 200; the next thaw delivers a socket-end event whose
|
|
39
|
+
* Promise has nobody left to await it. The function already returned correctly,
|
|
40
|
+
* so there is no user impact. The narrow frame match keeps genuine
|
|
41
|
+
* application-thrown socket errors visible.
|
|
42
|
+
*/
|
|
43
|
+
function isLambdaSocketHangUpNoise(signal) {
|
|
44
|
+
if (signal.value !== "socket hang up" || !isUnhandledRejection(signal)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return (signal.frames ?? []).some((frame) => frame?.function === "Socket.socketOnEnd" ||
|
|
48
|
+
frame?.filename === "node:_http_client");
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* SDK-only `ErrorEvent` promise rejections — typically the Neon serverless
|
|
52
|
+
* driver's WebSocket dying across a freeze/thaw and rejecting a floating
|
|
53
|
+
* promise with the raw ErrorEvent (`db/client.ts` already records these with
|
|
54
|
+
* context). Events with real application frames stay visible.
|
|
55
|
+
*
|
|
56
|
+
* "Application frame" must exclude the Sentry SDK's own bundled chunks:
|
|
57
|
+
* serverless bundles place them under the app root (e.g.
|
|
58
|
+
* `/var/task/_libs/@sentry/node+….mjs`), outside node_modules, so the SDK marks
|
|
59
|
+
* them `in_app` and the instrumentation stack alone would defeat this filter.
|
|
60
|
+
*/
|
|
61
|
+
function isSdkErrorEventNoise(signal) {
|
|
62
|
+
if (signal.value === "[object ErrorEvent]" && isUnhandledRejection(signal)) {
|
|
63
|
+
const frames = signal.frames ?? [];
|
|
64
|
+
const hasApplicationFrame = frames.some((frame) => frame?.in_app && !String(frame?.filename ?? "").includes("sentry"));
|
|
65
|
+
const hasSentryFrame = frames.some((frame) => String(frame?.filename ?? "").includes("sentry"));
|
|
66
|
+
if (!hasApplicationFrame && hasSentryFrame)
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return (signal.metadataValue === "[object ErrorEvent]" &&
|
|
70
|
+
signal.hasExceptionValues === false &&
|
|
71
|
+
String(signal.metadataFilename ?? "").includes("sentry"));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* h3's `createError({ statusCode: 4xx })` produces an `HTTPError` (h3 v2) /
|
|
75
|
+
* `H3Error` (h3 v1). 4xx ones are handler-controlled "expected failure"
|
|
76
|
+
* responses that route through the error hook only because they bubble out of
|
|
77
|
+
* `defineEventHandler`. Capture when the status looks 5xx, or is missing on a
|
|
78
|
+
* generic Error masquerading as an HTTPError.
|
|
79
|
+
*/
|
|
80
|
+
function isExpectedHttpNoise(signal) {
|
|
81
|
+
if (signal.type !== "HTTPError" && signal.type !== "H3Error")
|
|
82
|
+
return false;
|
|
83
|
+
const code = signal.statusCode;
|
|
84
|
+
if (typeof code === "number" && Number.isFinite(code)) {
|
|
85
|
+
return code >= 400 && code < 500;
|
|
86
|
+
}
|
|
87
|
+
// No status in the payload — match the common 4xx messages so handler-thrown
|
|
88
|
+
// 404/400/403/401 don't pollute the backend. A heuristic, but the
|
|
89
|
+
// alternatives (every 4xx becomes a real issue, or every route grows a
|
|
90
|
+
// catch+return) are worse.
|
|
91
|
+
const value = signal.value ?? "";
|
|
92
|
+
return (/^Cannot find any route matching/i.test(value) ||
|
|
93
|
+
/ not found$/i.test(value) ||
|
|
94
|
+
/Unauthenticated$/i.test(value) ||
|
|
95
|
+
/^Unauthorized$/i.test(value) ||
|
|
96
|
+
/^No access to /i.test(value));
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* `false` when the error is known non-bug noise and should not be reported.
|
|
100
|
+
*
|
|
101
|
+
* Deliberately fails open: anything the rules cannot positively identify as
|
|
102
|
+
* noise is reported. A dropped real error is invisible; a kept noisy one is
|
|
103
|
+
* merely loud.
|
|
104
|
+
*/
|
|
105
|
+
export function shouldReportErrorSignal(signal) {
|
|
106
|
+
return !(isValidationNoise(signal) ||
|
|
107
|
+
isAccessControlNoise(signal) ||
|
|
108
|
+
isLambdaSocketHangUpNoise(signal) ||
|
|
109
|
+
isSdkErrorEventNoise(signal) ||
|
|
110
|
+
isExpectedHttpNoise(signal));
|
|
111
|
+
}
|
|
112
|
+
function toNumericStatus(value) {
|
|
113
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
114
|
+
return value;
|
|
115
|
+
if (typeof value === "string") {
|
|
116
|
+
const parsed = parseInt(value, 10);
|
|
117
|
+
if (Number.isFinite(parsed))
|
|
118
|
+
return parsed;
|
|
119
|
+
}
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
/** Normalize a Sentry event. Typed structurally so this module stays SDK-free. */
|
|
123
|
+
export function errorSignalFromSentryEvent(event) {
|
|
124
|
+
const first = event.exception?.values?.[0];
|
|
125
|
+
const tags = {};
|
|
126
|
+
for (const [key, value] of Object.entries(event.tags ?? {})) {
|
|
127
|
+
if (typeof value === "string")
|
|
128
|
+
tags[key] = value;
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
type: first?.type,
|
|
132
|
+
value: first?.value ?? "",
|
|
133
|
+
mechanismType: first?.mechanism?.type,
|
|
134
|
+
frames: first?.stacktrace?.frames ?? [],
|
|
135
|
+
statusCode: toNumericStatus(event.tags?.statusCode ?? event.contexts?.h3?.statusCode),
|
|
136
|
+
tags,
|
|
137
|
+
metadataValue: typeof event.metadata?.value === "string"
|
|
138
|
+
? event.metadata.value
|
|
139
|
+
: undefined,
|
|
140
|
+
metadataFilename: typeof event.metadata?.filename === "string"
|
|
141
|
+
? event.metadata.filename
|
|
142
|
+
: undefined,
|
|
143
|
+
hasExceptionValues: Boolean(event.exception?.values?.length),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/** Normalize a raw thrown value for backends that never see a Sentry event. */
|
|
147
|
+
export function errorSignalFromError(error, options = {}) {
|
|
148
|
+
if (!(error instanceof Error)) {
|
|
149
|
+
return {
|
|
150
|
+
type: "Error",
|
|
151
|
+
value: typeof error === "string" ? error : String(error ?? ""),
|
|
152
|
+
mechanismType: options.mechanismType,
|
|
153
|
+
tags: options.tags,
|
|
154
|
+
hasExceptionValues: true,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const withStatus = error;
|
|
158
|
+
return {
|
|
159
|
+
type: error.name || "Error",
|
|
160
|
+
value: error.message ?? "",
|
|
161
|
+
mechanismType: options.mechanismType,
|
|
162
|
+
frames: parseStackFrames(error.stack),
|
|
163
|
+
statusCode: toNumericStatus(withStatus.statusCode) ??
|
|
164
|
+
toNumericStatus(withStatus.status),
|
|
165
|
+
tags: options.tags,
|
|
166
|
+
hasExceptionValues: true,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/** Convenience wrapper for the raw-error path. */
|
|
170
|
+
export function shouldReportError(error, options = {}) {
|
|
171
|
+
return shouldReportErrorSignal(errorSignalFromError(error, options));
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=error-noise-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-noise-filter.js","sourceRoot":"","sources":["../../src/server/error-noise-filter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Provider-agnostic drop rules for server error reporting.\n *\n * These rules were tuned against real production volume while Sentry was the\n * only backend, and they lived inside its `beforeSend`. They are not Sentry\n * policy — they describe which server errors are non-bugs in *this* framework\n * (expected 4xx, access-control rejections, Lambda freeze/thaw socket noise).\n * Any second backend that skips them receives a firehose rather than a signal:\n * the `socket hang up` rule alone accounts for ~10k events/day.\n *\n * The rules operate on a normalized signal so the same predicate can judge a\n * Sentry `Event` (already parsed by the SDK) and a raw `Error` reaching the\n * PostHog provider. Fields a given source cannot supply stay `undefined`, and\n * every rule requires the evidence it depends on — an unknown never reads as\n * a match.\n */\n\nimport { parseStackFrames } from \"../tracking/posthog-exception.js\";\n\nexport interface ErrorSignalFrame {\n function?: string;\n filename?: string;\n in_app?: boolean;\n}\n\nexport interface NormalizedErrorSignal {\n /** Error class name, e.g. `ValidationError`. */\n type?: string;\n /** Error message. */\n value?: string;\n /** Capture mechanism, e.g. `onunhandledrejection`. */\n mechanismType?: string;\n frames?: ErrorSignalFrame[];\n /** HTTP status carried by h3's `HTTPError` / `H3Error`. */\n statusCode?: number;\n tags?: Record<string, string | undefined>;\n /**\n * Sentry-only: some SDK-internal rejections arrive with no `exception.values`\n * at all and only a `metadata` blob. Left `undefined` elsewhere.\n */\n metadataValue?: string;\n metadataFilename?: string;\n hasExceptionValues?: boolean;\n}\n\nfunction isUnhandledRejection(signal: NormalizedErrorSignal): boolean {\n return (\n typeof signal.mechanismType === \"string\" &&\n signal.mechanismType.endsWith(\"onunhandledrejection\")\n );\n}\n\n/** Expected user-input rejections. The framework and CLI both throw these. */\nfunction isValidationNoise(signal: NormalizedErrorSignal): boolean {\n return (\n signal.type === \"ValidationError\" || signal.tags?.handled === \"validation\"\n );\n}\n\n/**\n * Access-control rejections. These are 4xx user-facing errors that reached the\n * error hook because a route forgot to catch them — fixing the route is the\n * right answer, but until then they bury real bugs. Auth routes report their\n * own failures at `warning` level, so this only sees the escape path.\n */\nfunction isAccessControlNoise(signal: NormalizedErrorSignal): boolean {\n return (\n signal.type === \"ForbiddenError\" || signal.type === \"UnauthorizedError\"\n );\n}\n\n/**\n * `socket hang up` unhandled rejections from Lambda freeze cycles. AWS recycles\n * long-lived sockets (MCP Streamable HTTP long-polls, keep-alive agents) ~60s\n * after a function returns 200; the next thaw delivers a socket-end event whose\n * Promise has nobody left to await it. The function already returned correctly,\n * so there is no user impact. The narrow frame match keeps genuine\n * application-thrown socket errors visible.\n */\nfunction isLambdaSocketHangUpNoise(signal: NormalizedErrorSignal): boolean {\n if (signal.value !== \"socket hang up\" || !isUnhandledRejection(signal)) {\n return false;\n }\n return (signal.frames ?? []).some(\n (frame) =>\n frame?.function === \"Socket.socketOnEnd\" ||\n frame?.filename === \"node:_http_client\",\n );\n}\n\n/**\n * SDK-only `ErrorEvent` promise rejections — typically the Neon serverless\n * driver's WebSocket dying across a freeze/thaw and rejecting a floating\n * promise with the raw ErrorEvent (`db/client.ts` already records these with\n * context). Events with real application frames 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 SDK marks\n * them `in_app` and the instrumentation stack alone would defeat this filter.\n */\nfunction isSdkErrorEventNoise(signal: NormalizedErrorSignal): boolean {\n if (signal.value === \"[object ErrorEvent]\" && isUnhandledRejection(signal)) {\n const frames = signal.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) return true;\n }\n\n return (\n signal.metadataValue === \"[object ErrorEvent]\" &&\n signal.hasExceptionValues === false &&\n String(signal.metadataFilename ?? \"\").includes(\"sentry\")\n );\n}\n\n/**\n * h3's `createError({ statusCode: 4xx })` produces an `HTTPError` (h3 v2) /\n * `H3Error` (h3 v1). 4xx ones are handler-controlled \"expected failure\"\n * responses that route through the error hook only because they bubble out of\n * `defineEventHandler`. Capture when the status looks 5xx, or is missing on a\n * generic Error masquerading as an HTTPError.\n */\nfunction isExpectedHttpNoise(signal: NormalizedErrorSignal): boolean {\n if (signal.type !== \"HTTPError\" && signal.type !== \"H3Error\") return false;\n\n const code = signal.statusCode;\n if (typeof code === \"number\" && Number.isFinite(code)) {\n return code >= 400 && code < 500;\n }\n\n // No status in the payload — match the common 4xx messages so handler-thrown\n // 404/400/403/401 don't pollute the backend. A heuristic, but the\n // alternatives (every 4xx becomes a real issue, or every route grows a\n // catch+return) are worse.\n const value = signal.value ?? \"\";\n return (\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}\n\n/**\n * `false` when the error is known non-bug noise and should not be reported.\n *\n * Deliberately fails open: anything the rules cannot positively identify as\n * noise is reported. A dropped real error is invisible; a kept noisy one is\n * merely loud.\n */\nexport function shouldReportErrorSignal(\n signal: NormalizedErrorSignal,\n): boolean {\n return !(\n isValidationNoise(signal) ||\n isAccessControlNoise(signal) ||\n isLambdaSocketHangUpNoise(signal) ||\n isSdkErrorEventNoise(signal) ||\n isExpectedHttpNoise(signal)\n );\n}\n\ninterface SentryLikeEvent {\n exception?: {\n values?: Array<{\n type?: string;\n value?: string;\n mechanism?: { type?: string };\n stacktrace?: { frames?: ErrorSignalFrame[] };\n }>;\n };\n tags?: Record<string, unknown>;\n contexts?: Record<string, Record<string, unknown> | undefined>;\n metadata?: { value?: unknown; filename?: unknown };\n}\n\nfunction toNumericStatus(value: unknown): number | undefined {\n if (typeof value === \"number\" && Number.isFinite(value)) return value;\n if (typeof value === \"string\") {\n const parsed = parseInt(value, 10);\n if (Number.isFinite(parsed)) return parsed;\n }\n return undefined;\n}\n\n/** Normalize a Sentry event. Typed structurally so this module stays SDK-free. */\nexport function errorSignalFromSentryEvent(\n event: SentryLikeEvent,\n): NormalizedErrorSignal {\n const first = event.exception?.values?.[0];\n const tags: Record<string, string | undefined> = {};\n for (const [key, value] of Object.entries(event.tags ?? {})) {\n if (typeof value === \"string\") tags[key] = value;\n }\n\n return {\n type: first?.type,\n value: first?.value ?? \"\",\n mechanismType: first?.mechanism?.type,\n frames: first?.stacktrace?.frames ?? [],\n statusCode: toNumericStatus(\n event.tags?.statusCode ?? event.contexts?.h3?.statusCode,\n ),\n tags,\n metadataValue:\n typeof event.metadata?.value === \"string\"\n ? event.metadata.value\n : undefined,\n metadataFilename:\n typeof event.metadata?.filename === \"string\"\n ? event.metadata.filename\n : undefined,\n hasExceptionValues: Boolean(event.exception?.values?.length),\n };\n}\n\nexport interface ErrorSignalFromErrorOptions {\n mechanismType?: string;\n tags?: Record<string, string | undefined>;\n}\n\n/** Normalize a raw thrown value for backends that never see a Sentry event. */\nexport function errorSignalFromError(\n error: unknown,\n options: ErrorSignalFromErrorOptions = {},\n): NormalizedErrorSignal {\n if (!(error instanceof Error)) {\n return {\n type: \"Error\",\n value: typeof error === \"string\" ? error : String(error ?? \"\"),\n mechanismType: options.mechanismType,\n tags: options.tags,\n hasExceptionValues: true,\n };\n }\n\n const withStatus = error as Error & {\n statusCode?: unknown;\n status?: unknown;\n };\n\n return {\n type: error.name || \"Error\",\n value: error.message ?? \"\",\n mechanismType: options.mechanismType,\n frames: parseStackFrames(error.stack),\n statusCode:\n toNumericStatus(withStatus.statusCode) ??\n toNumericStatus(withStatus.status),\n tags: options.tags,\n hasExceptionValues: true,\n };\n}\n\n/** Convenience wrapper for the raw-error path. */\nexport function shouldReportError(\n error: unknown,\n options: ErrorSignalFromErrorOptions = {},\n): boolean {\n return shouldReportErrorSignal(errorSignalFromError(error, options));\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
export interface PublicPostHogConfig {
|
|
15
|
+
posthogKey: string;
|
|
16
|
+
posthogHost: string;
|
|
17
|
+
posthogErrorTracking: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the browser-facing PostHog config, or `undefined` when none is set.
|
|
21
|
+
*
|
|
22
|
+
* Note this deliberately does NOT fall back to `POSTHOG_API_KEY`: a
|
|
23
|
+
* server-only personal/private key must never be inlined into the public HTML
|
|
24
|
+
* shell. Operators opt into browser capture by setting a public key explicitly.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolvePublicPostHogConfig(): PublicPostHogConfig | undefined;
|
|
27
|
+
export declare function getPostHogClientConfigScript(): string | null;
|
|
28
|
+
//# sourceMappingURL=posthog-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"posthog-config.d.ts","sourceRoot":"","sources":["../../src/server/posthog-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAcH,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,oBAAoB,EAAE,OAAO,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,IAAI,mBAAmB,GAAG,SAAS,CAsB5E;AAED,wBAAgB,4BAA4B,IAAI,MAAM,GAAG,IAAI,CAW5D"}
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
const POSTHOG_DEFAULT_HOST = "https://us.i.posthog.com";
|
|
15
|
+
function firstNonEmpty(...values) {
|
|
16
|
+
for (const value of values) {
|
|
17
|
+
const trimmed = value?.trim();
|
|
18
|
+
if (trimmed)
|
|
19
|
+
return trimmed;
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the browser-facing PostHog config, or `undefined` when none is set.
|
|
25
|
+
*
|
|
26
|
+
* Note this deliberately does NOT fall back to `POSTHOG_API_KEY`: a
|
|
27
|
+
* server-only personal/private key must never be inlined into the public HTML
|
|
28
|
+
* shell. Operators opt into browser capture by setting a public key explicitly.
|
|
29
|
+
*/
|
|
30
|
+
export function resolvePublicPostHogConfig() {
|
|
31
|
+
const posthogKey = firstNonEmpty(process.env.POSTHOG_PUBLIC_KEY, process.env.VITE_POSTHOG_KEY, process.env.VITE_POSTHOG_PUBLIC_KEY);
|
|
32
|
+
if (!posthogKey)
|
|
33
|
+
return undefined;
|
|
34
|
+
const posthogHost = (firstNonEmpty(process.env.POSTHOG_PUBLIC_HOST, process.env.VITE_POSTHOG_HOST, process.env.POSTHOG_HOST) ?? POSTHOG_DEFAULT_HOST).replace(/\/+$/, "");
|
|
35
|
+
return {
|
|
36
|
+
posthogKey,
|
|
37
|
+
posthogHost,
|
|
38
|
+
posthogErrorTracking: process.env.POSTHOG_ERROR_TRACKING?.trim().toLowerCase() !== "false",
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function getPostHogClientConfigScript() {
|
|
42
|
+
const config = resolvePublicPostHogConfig();
|
|
43
|
+
if (!config)
|
|
44
|
+
return null;
|
|
45
|
+
return [
|
|
46
|
+
"<script data-agent-native-posthog-config>",
|
|
47
|
+
"window.__AGENT_NATIVE_CONFIG__=Object.assign({},window.__AGENT_NATIVE_CONFIG__,",
|
|
48
|
+
JSON.stringify(config),
|
|
49
|
+
");",
|
|
50
|
+
"</script>",
|
|
51
|
+
].join("");
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=posthog-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"posthog-config.js","sourceRoot":"","sources":["../../src/server/posthog-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,oBAAoB,GAAG,0BAA0B,CAAC;AAExD,SAAS,aAAa,CACpB,GAAG,MAAiC;IAEpC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;QAC9B,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;IAC9B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAQD;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B;IACxC,MAAM,UAAU,GAAG,aAAa,CAC9B,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAC9B,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAC5B,OAAO,CAAC,GAAG,CAAC,uBAAuB,CACpC,CAAC;IACF,IAAI,CAAC,UAAU;QAAE,OAAO,SAAS,CAAC;IAElC,MAAM,WAAW,GAAG,CAClB,aAAa,CACX,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAC/B,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAC7B,OAAO,CAAC,GAAG,CAAC,YAAY,CACzB,IAAI,oBAAoB,CAC1B,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEtB,OAAO;QACL,UAAU;QACV,WAAW;QACX,oBAAoB,EAClB,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,OAAO;KACvE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC1C,MAAM,MAAM,GAAG,0BAA0B,EAAE,CAAC;IAC5C,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,OAAO;QACL,2CAA2C;QAC3C,iFAAiF;QACjF,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;QACtB,IAAI;QACJ,WAAW;KACZ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACb,CAAC","sourcesContent":["/**\n * Public PostHog config for the browser.\n *\n * PostHog project API keys are publishable — that is how `posthog-js` ships in\n * every customer's bundle — so this mirrors how the Sentry client DSN already\n * reaches the browser: env-derived, identical for every visitor, and therefore\n * safe inside the CDN-cached SSR shell (see `guard:ssr-cache-shell`).\n *\n * The browser sends exceptions straight to PostHog rather than relaying them\n * through `/_agent-native/track`: that route requires a resolved session by\n * design, so relaying would silently drop every signed-out crash — exactly the\n * class of failure that looks like \"no errors\" instead of \"no reporting\".\n */\n\nconst POSTHOG_DEFAULT_HOST = \"https://us.i.posthog.com\";\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\nexport interface PublicPostHogConfig {\n posthogKey: string;\n posthogHost: string;\n posthogErrorTracking: boolean;\n}\n\n/**\n * Resolve the browser-facing PostHog config, or `undefined` when none is set.\n *\n * Note this deliberately does NOT fall back to `POSTHOG_API_KEY`: a\n * server-only personal/private key must never be inlined into the public HTML\n * shell. Operators opt into browser capture by setting a public key explicitly.\n */\nexport function resolvePublicPostHogConfig(): PublicPostHogConfig | undefined {\n const posthogKey = firstNonEmpty(\n process.env.POSTHOG_PUBLIC_KEY,\n process.env.VITE_POSTHOG_KEY,\n process.env.VITE_POSTHOG_PUBLIC_KEY,\n );\n if (!posthogKey) return undefined;\n\n const posthogHost = (\n firstNonEmpty(\n process.env.POSTHOG_PUBLIC_HOST,\n process.env.VITE_POSTHOG_HOST,\n process.env.POSTHOG_HOST,\n ) ?? POSTHOG_DEFAULT_HOST\n ).replace(/\\/+$/, \"\");\n\n return {\n posthogKey,\n posthogHost,\n posthogErrorTracking:\n process.env.POSTHOG_ERROR_TRACKING?.trim().toLowerCase() !== \"false\",\n };\n}\n\nexport function getPostHogClientConfigScript(): string | null {\n const config = resolvePublicPostHogConfig();\n if (!config) return null;\n\n return [\n \"<script data-agent-native-posthog-config>\",\n \"window.__AGENT_NATIVE_CONFIG__=Object.assign({},window.__AGENT_NATIVE_CONFIG__,\",\n JSON.stringify(config),\n \");\",\n \"</script>\",\n ].join(\"\");\n}\n"]}
|
|
@@ -89,6 +89,12 @@ export interface RequestContext {
|
|
|
89
89
|
*/
|
|
90
90
|
authCapability?: string;
|
|
91
91
|
timezone?: string;
|
|
92
|
+
/**
|
|
93
|
+
* The caller's browser analytics session id, when the request came from a
|
|
94
|
+
* page. Emitted as PostHog's `$session_id` so agent traces join to session
|
|
95
|
+
* replay; never used for authorization.
|
|
96
|
+
*/
|
|
97
|
+
browserSessionId?: string;
|
|
92
98
|
/**
|
|
93
99
|
* Set when code reads authenticated request context. Public SSR shell/data
|
|
94
100
|
* should not depend on this value; user/org-specific reads belong behind
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../src/server/request-context.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../src/server/request-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-context.js","sourceRoot":"","sources":["../../src/server/request-context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Per-request context using AsyncLocalStorage.\n *\n * Replaces the unsafe pattern of mutating `process.env.AGENT_USER_EMAIL` /\n * `process.env.AGENT_ORG_ID` on every request. On Node.js (Netlify, self-hosted)\n * concurrent requests would overwrite each other's env vars. AsyncLocalStorage\n * gives each async call-chain its own isolated context.\n *\n * Supported on all deployment targets:\n * - Node.js (native)\n * - Cloudflare Workers (via nodejs_compat flag)\n * - Deno Deploy (via node:async_hooks compat)\n *\n * For CLI scripts that run outside a request context, the getters fall back to\n * process.env so existing `AGENT_USER_EMAIL=x pnpm action foo` invocations\n * continue to work.\n */\n\ntype AsyncLocalStorageLike<T> = {\n getStore(): T | undefined;\n run<R>(store: T, callback: () => R): R;\n};\n\ntype AsyncLocalStorageCtor = new <T>() => AsyncLocalStorageLike<T>;\n\nclass StackAsyncLocalStorage<T> implements AsyncLocalStorageLike<T> {\n private readonly stack: T[] = [];\n\n getStore(): T | undefined {\n return this.stack.at(-1);\n }\n\n run<R>(store: T, callback: () => R): R {\n this.stack.push(store);\n try {\n const result = callback();\n const maybePromise = result as unknown as\n | { finally?: (callback: () => void) => unknown }\n | undefined;\n if (maybePromise && typeof maybePromise.finally === \"function\") {\n return maybePromise.finally(() => {\n this.stack.pop();\n }) as R;\n }\n this.stack.pop();\n return result;\n } catch (error) {\n this.stack.pop();\n throw error;\n }\n }\n}\n\nfunction getAsyncLocalStorageCtor(): AsyncLocalStorageCtor | undefined {\n if (\n typeof window !== \"undefined\" ||\n typeof process === \"undefined\" ||\n !process.versions?.node ||\n typeof process.getBuiltinModule !== \"function\"\n ) {\n return undefined;\n }\n return process.getBuiltinModule(\"node:async_hooks\")?.AsyncLocalStorage as\n | AsyncLocalStorageCtor\n | undefined;\n}\n\nconst AsyncLocalStorageCtor = getAsyncLocalStorageCtor();\n\nfunction processEnv(name: string): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n return process.env?.[name];\n}\n\n/**\n * Per-request agent-run state. Lives on `RequestContext.run` so the\n * agent-chat plugin can populate fields as the run progresses (owner,\n * resolved API key, system prompt, engine, model, threadId) without\n * mutating module-scope `let` bindings — those leak across concurrent\n * requests on a single Node.js process.\n *\n * Mutated in-place by `prepareRun`, `onEngineResolved`, `onRunStart` so\n * tool factory closures (automation, fetch, team, builder-browser) read\n * the live per-request value via `getRequestRunContext()`.\n */\nexport interface RequestRunContext {\n /** Request-scoped serverless continuation hook, when the runtime provides one. */\n waitUntil?: (promise: Promise<unknown>) => void;\n /** Origin of the current request (used by the builder-browser tool). */\n requestOrigin?: string;\n /** Stable browser tab id for tab-scoped app-state reads/writes. */\n browserTabId?: string;\n /** Resource scope for the current chat thread, e.g. the active deck. */\n chatScope?: {\n type: string;\n id: string;\n label?: string;\n } | null;\n /** Resolved owner email (set by prepareRun). */\n owner?: string;\n /** Owner's active Anthropic API key (set by prepareRun). */\n userApiKey?: string;\n /** Thread ID for the current run (set by onRunStart). */\n threadId?: string;\n /** System prompt actually sent to the model for this run. */\n systemPrompt?: string;\n /** Engine instance for this run (set by onEngineResolved). */\n engine?: import(\"../agent/engine/types.js\").AgentEngine;\n /** Model name for this run (set by onEngineResolved). */\n model?: string;\n /**\n * True when this run is executing inside the durable background-function\n * worker (the `_process-run` self-dispatch), not the synchronous foreground\n * request. Template `extraContext` / system-prompt builders can read this to\n * skip heavy, hang-prone enrichment (large data-dictionary DB reads, etc.)\n * in the worker so it reliably claims its run within the setup budget.\n */\n isBackgroundWorker?: boolean;\n /** Tool calls made so far in the current agent loop. */\n toolCalls?: Array<{ name: string; input: unknown }>;\n /** Tool results returned so far in the current agent loop. */\n toolResults?: Array<{ name: string; content: string; isError: boolean }>;\n /** Per-run fingerprints for large extension bodies already sent to the LLM. */\n extensionContentReads?: Record<string, string>;\n /** Per-run fingerprints for repeated tool-search calls already sent to the LLM. */\n toolSearchReads?: Record<\n string,\n { totalTools: number; resultNames: string[] }\n >;\n}\n\nexport interface RequestContext {\n userEmail?: string;\n userName?: string;\n orgId?: string;\n /**\n * Narrow authorization capability verified from an embed session. This is\n * deliberately separate from user identity: capability-only sessions must\n * not satisfy account-backed auth or inherit the ticket owner's privileges.\n */\n authCapability?: string;\n timezone?: string;\n /**\n * Set when code reads authenticated request context. Public SSR shell/data\n * should not depend on this value; user/org-specific reads belong behind\n * client-side actions/API after hydration.\n */\n authContextAccessed?: boolean;\n /**\n * Origin of the inbound request (e.g. `http://127.0.0.1:8100`). Set by the\n * MCP mount from the request headers so actions that build externally\n * fetchable URLs (e.g. design `export-coding-handoff`'s signed raw-code URL)\n * resolve the real local-workspace origin instead of a prod/localhost\n * fallback. Optional — absent on paths that don't populate it.\n */\n requestOrigin?: string;\n /**\n * True when the request's real socket peer is loopback, captured by the\n * action-route handler while the h3 event is still in scope (nothing below\n * that layer can see the event). Derived from `getRequestIP()` WITHOUT\n * `x-forwarded-for`, so a remote client cannot set it via headers.\n *\n * A local-dev gate only. A tunnel or reverse proxy that reaches the dev\n * server over loopback also presents as loopback, so this is necessary but\n * not sufficient on its own — pair it with something that scopes the blast\n * radius (a resource that is itself local-only, NODE_ENV, etc.).\n */\n isLoopbackRequest?: boolean;\n /**\n * True when this request is being processed by an integration-platform\n * webhook (Slack, Telegram, etc.) where the function timeout is the\n * binding constraint. Code that calls slow remote APIs can use this to apply\n * tighter budgets on this path while leaving normal agent-chat callers\n * (5+ min budget) unaffected.\n */\n isIntegrationCaller?: boolean;\n /**\n * Metadata for the currently-processing integration task. This lets tools\n * that start long-running remote work persist a continuation that can update\n * the originating platform thread after the current function budget ends.\n */\n integration?: {\n taskId: string;\n attempts?: number;\n incoming: import(\"../integrations/types.js\").IncomingMessage;\n placeholderRef?: string;\n /** Opaque provider-native progress surface for a durable continuation. */\n progressRef?: import(\"../integrations/types.js\").PlatformRunProgressRef;\n installationId?: string;\n scopeId?: string;\n principalType?: \"user\" | \"service\";\n lineage?: {\n runId?: string;\n parentTaskId?: string;\n source?: {\n kind: string;\n platform?: string;\n id: string;\n url?: string;\n };\n network?: {\n protocol: \"a2a\" | \"mcp\" | \"provider-api\";\n id: string;\n peer?: string;\n };\n };\n };\n /**\n * Mutable per-request agent-run state. Populated by the agent-chat plugin\n * during a run; tool closures dereference it on each invocation.\n */\n run?: RequestRunContext;\n}\n\nconst GLOBAL_KEY = \"__agentNativeRequestContextAls\" as const;\nconst OBSERVERS_KEY = \"__agentNativeRequestContextObservers\" as const;\nconst BOUNDARY_KEY = \"__agentNativeRequestBoundaryInstalled\" as const;\ntype RequestContextObserver = (ctx: RequestContext) => void;\ntype GlobalWithRequestContext = typeof globalThis & {\n [GLOBAL_KEY]?: AsyncLocalStorageLike<RequestContext>;\n [OBSERVERS_KEY]?: RequestContextObserver[];\n [BOUNDARY_KEY]?: boolean;\n};\nconst globalRef = globalThis as GlobalWithRequestContext;\nif (!globalRef[GLOBAL_KEY]) {\n globalRef[GLOBAL_KEY] = AsyncLocalStorageCtor\n ? new AsyncLocalStorageCtor<RequestContext>()\n : new StackAsyncLocalStorage<RequestContext>();\n}\nif (!globalRef[OBSERVERS_KEY]) {\n globalRef[OBSERVERS_KEY] = [];\n}\nconst als = globalRef[GLOBAL_KEY]!;\nconst observers = globalRef[OBSERVERS_KEY]!;\n\n/**\n * Register a callback fired every time `runWithRequestContext` enters a new\n * scope. The hook runs INSIDE the AsyncLocalStorage scope, so observability\n * helpers that read the current isolation scope (e.g. Sentry) attach to the\n * right per-request context.\n *\n * Returned function unregisters the observer. Observers must never throw —\n * any error is swallowed so a misbehaving observer can't break the request\n * path.\n */\nexport function addRequestContextObserver(\n observer: RequestContextObserver,\n): () => void {\n observers.push(observer);\n return () => {\n const i = observers.indexOf(observer);\n if (i !== -1) observers.splice(i, 1);\n };\n}\n\n/**\n * Run a callback within a per-request context. The context is available to all\n * async operations spawned from `fn` via `getRequestUserEmail()` / `getRequestOrgId()`.\n *\n * Any registered `addRequestContextObserver` callbacks fire inside the new\n * scope before `fn` runs, so observability code can pin user/org info onto\n * isolation-scoped backends (Sentry, OpenTelemetry, etc.).\n */\nexport function runWithRequestContext<T>(\n ctx: RequestContext,\n fn: () => T | Promise<T>,\n): T | Promise<T> {\n return als.run(ctx, () => {\n if (observers.length > 0) {\n for (const obs of observers) {\n try {\n obs(ctx);\n } catch {\n // Observers must never break the request path.\n }\n }\n }\n return fn();\n });\n}\n\n/**\n * Return the active request context, if this call chain is running under one.\n *\n * This is intentionally distinct from `getRequestUserEmail()`: callers that\n * have an active context with no authenticated user must not fall through to\n * process-wide CLI fallbacks such as `AGENT_USER_EMAIL` or \"latest session\".\n */\nexport function getRequestContext(): RequestContext | undefined {\n const store = als.getStore();\n markAuthContextAccess(store);\n return store;\n}\n\n/**\n * True when AsyncLocalStorage has an active context for this call chain.\n * Useful for helpers that support both HTTP requests and standalone CLI runs.\n */\nexport function hasRequestContext(): boolean {\n return als.getStore() !== undefined;\n}\n\n/**\n * Record that the framework's request-boundary middleware is installed in this\n * process, so every inbound HTTP request runs inside a `RequestContext`.\n *\n * Once that is true, a request-scoped identity read that finds no store can no\n * longer be an HTTP caller — which is what makes the ambient-identity warning\n * in `getRequestUserEmail()` specific enough to be worth emitting.\n */\nexport function markRequestBoundaryInstalled(): void {\n globalRef[BOUNDARY_KEY] = true;\n}\n\nexport function hasRequestBoundary(): boolean {\n return globalRef[BOUNDARY_KEY] === true;\n}\n\n/**\n * The ambient, process-wide identity configured for this deployment\n * (`AGENT_USER_EMAIL`). Legitimate callers are the ones with no request behind\n * them at all: CLI invocations, cron/scheduled jobs, seed and QA scripts.\n *\n * TRAP: this is not the caller's identity, and a request handler that reads it\n * authorizes whoever the deploy env names rather than whoever signed in — it\n * fails open toward more privilege. Request handlers read\n * `getRequestUserEmail()` and fail closed when it returns undefined.\n */\nexport function getAmbientUserEmail(): string | undefined {\n return processEnv(\"AGENT_USER_EMAIL\");\n}\n\n/** Ambient process-wide org (`AGENT_ORG_ID`). Same trap as `getAmbientUserEmail()`. */\nexport function getAmbientOrgId(): string | undefined {\n return processEnv(\"AGENT_ORG_ID\");\n}\n\nconst warnedAmbientIdentities = new Set<string>();\n\nfunction warnAmbientIdentitySatisfiedRead(email: string): void {\n if (!hasRequestBoundary()) return;\n if (warnedAmbientIdentities.has(email)) return;\n warnedAmbientIdentities.add(email);\n console.warn(\n `[agent-native] getRequestUserEmail() found no request context and answered with the ambient ` +\n `AGENT_USER_EMAIL identity (${email}). This process serves HTTP requests, so a request-scoped ` +\n `read reaching the ambient identity is a bug: it authorizes the deploy env, not the signed-in ` +\n `user. Wrap the caller in runWithRequestContext({ userEmail }), or call getAmbientUserEmail() ` +\n `explicitly if the process identity really is what you mean.`,\n );\n}\n\n/**\n * Get the current request's user email.\n *\n * - If a request context exists (HTTP/A2A path), returns its `userEmail` —\n * even when that value is `undefined`. The env fallback MUST NOT fire here:\n * a stale process-wide `AGENT_USER_EMAIL` from a CLI run or previous bug\n * would leak into an unauthenticated A2A/API call (e.g. unsigned or API-key\n * modes where `runWithRequestContext({ userEmail: undefined })` is used).\n * - Only when there is NO request context (CLI scripts) do we fall back to\n * `process.env.AGENT_USER_EMAIL`. In a process that serves HTTP requests the\n * framework installs a request boundary so that case cannot be a request;\n * if it happens anyway we warn loudly rather than answer silently.\n */\nexport function getRequestUserEmail(): string | undefined {\n const store = als.getStore();\n if (store !== undefined) {\n if (store.userEmail) markAuthContextAccess(store);\n return store.userEmail;\n }\n const ambient = processEnv(\"AGENT_USER_EMAIL\");\n if (ambient) warnAmbientIdentitySatisfiedRead(ambient);\n return ambient;\n}\n\n/**\n * Get the current request's display name, when the auth provider supplied one.\n *\n * The same request-context fallback rules as `getRequestUserEmail()` apply:\n * HTTP/A2A calls only read AsyncLocalStorage, while CLI scripts may opt in via\n * `AGENT_USER_NAME`.\n */\nexport function getRequestUserName(): string | undefined {\n const store = als.getStore();\n if (store !== undefined) {\n if (store.userName) markAuthContextAccess(store);\n return store.userName;\n }\n return processEnv(\"AGENT_USER_NAME\");\n}\n\n/**\n * Get the current request's org ID.\n *\n * Same store-aware semantics as `getRequestUserEmail()` — env fallback is\n * CLI-only, so a request that explicitly has no org doesn't inherit a stale\n * `process.env.AGENT_ORG_ID` from a prior request on the same Lambda instance.\n */\nexport function getRequestOrgId(): string | undefined {\n const store = als.getStore();\n if (store !== undefined) {\n if (store.orgId) markAuthContextAccess(store);\n return store.orgId;\n }\n return processEnv(\"AGENT_ORG_ID\");\n}\n\n/** Return the verified capability for this request, without implying identity. */\nexport function getRequestAuthCapability(): string | undefined {\n const store = als.getStore();\n if (!store) return undefined;\n if (store.authCapability) markAuthContextAccess(store);\n return store.authCapability;\n}\n\n/**\n * Whether the current request came from a loopback socket peer. Fails closed:\n * outside a request store (CLI, background job, agent run) there is no peer to\n * vouch for, so this is `false` rather than inheriting an ambient default.\n */\nexport function getRequestIsLoopback(): boolean {\n return als.getStore()?.isLoopbackRequest === true;\n}\n\nfunction markAuthContextAccess(ctx: RequestContext | undefined) {\n if (!ctx) return;\n if (ctx.userEmail || ctx.userName || ctx.orgId || ctx.authCapability) {\n ctx.authContextAccessed = true;\n }\n}\n\nexport function hasAuthContextAccess(ctx: RequestContext | undefined): boolean {\n return Boolean(ctx?.authContextAccessed);\n}\n\n/**\n * Get the current request's IANA timezone (e.g. \"America/Los_Angeles\").\n * The UI sends this via the `x-user-timezone` header on every action call, and\n * the agent chat plugin propagates it into the request context so that\n * agent-initiated tool calls also see the user's timezone. Falls back to\n * `process.env.AGENT_USER_TIMEZONE` only for CLI scripts (no request context).\n */\nexport function getRequestTimezone(): string | undefined {\n const store = als.getStore();\n if (store !== undefined) return store.timezone;\n return processEnv(\"AGENT_USER_TIMEZONE\");\n}\n\n/**\n * Returns true when this request is on an integration-platform path (Slack,\n * Telegram, etc.) — i.e. we're inside the integration plugin's processor\n * function and the platform's deliver-by deadline plus the host's function\n * timeout are the binding budget. Non-integration callers (CLI, normal\n * agent chat) should treat this as `false`.\n */\nexport function isIntegrationCallerRequest(): boolean {\n return als.getStore()?.isIntegrationCaller === true;\n}\n\nexport function getIntegrationRequestContext():\n | NonNullable<RequestContext[\"integration\"]>\n | undefined {\n return als.getStore()?.integration;\n}\n\n/**\n * Convenience: returns `{ userEmail, orgId }` from the active request context,\n * suitable for passing to `resolveCredential(key, ctx)`. Returns `null` when\n * no user is associated with the call (e.g. an unauthenticated public route).\n *\n * For framework actions auto-mounted at `/_agent-native/actions/...` this is\n * always populated because action-routes wraps every invocation in\n * `runWithRequestContext`. For hand-written `/api/*` routes the calling code\n * is responsible for setting up the context (see `runWithRequestContext`).\n */\nexport function getCredentialContext(): {\n userEmail: string;\n orgId: string | null;\n} | null {\n const userEmail = getRequestUserEmail();\n if (!userEmail) return null;\n return { userEmail, orgId: getRequestOrgId() ?? null };\n}\n\n/**\n * Get the active request's mutable agent-run state. Returns `undefined` when\n * called outside an agent run (e.g. before `prepareRun` or in a non-agent\n * code path). Callers must tolerate the field absence; use the helper\n * `requireRequestRunContext()` if missing context is a programming error.\n */\nexport function getRequestRunContext(): RequestRunContext | undefined {\n const store = als.getStore();\n if (!store) return undefined;\n return store.run;\n}\n\n/**\n * Ensure a `RequestRunContext` exists on the active request store and\n * return it. Used by the agent-chat handler to attach run state once it\n * starts processing a chat request. Returns `undefined` if there is no\n * active request store (caller should not be invoking this outside ALS).\n */\nexport function ensureRequestRunContext(): RequestRunContext | undefined {\n const store = als.getStore();\n if (!store) return undefined;\n if (!store.run) store.run = {};\n return store.run;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"request-context.js","sourceRoot":"","sources":["../../src/server/request-context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Per-request context using AsyncLocalStorage.\n *\n * Replaces the unsafe pattern of mutating `process.env.AGENT_USER_EMAIL` /\n * `process.env.AGENT_ORG_ID` on every request. On Node.js (Netlify, self-hosted)\n * concurrent requests would overwrite each other's env vars. AsyncLocalStorage\n * gives each async call-chain its own isolated context.\n *\n * Supported on all deployment targets:\n * - Node.js (native)\n * - Cloudflare Workers (via nodejs_compat flag)\n * - Deno Deploy (via node:async_hooks compat)\n *\n * For CLI scripts that run outside a request context, the getters fall back to\n * process.env so existing `AGENT_USER_EMAIL=x pnpm action foo` invocations\n * continue to work.\n */\n\ntype AsyncLocalStorageLike<T> = {\n getStore(): T | undefined;\n run<R>(store: T, callback: () => R): R;\n};\n\ntype AsyncLocalStorageCtor = new <T>() => AsyncLocalStorageLike<T>;\n\nclass StackAsyncLocalStorage<T> implements AsyncLocalStorageLike<T> {\n private readonly stack: T[] = [];\n\n getStore(): T | undefined {\n return this.stack.at(-1);\n }\n\n run<R>(store: T, callback: () => R): R {\n this.stack.push(store);\n try {\n const result = callback();\n const maybePromise = result as unknown as\n | { finally?: (callback: () => void) => unknown }\n | undefined;\n if (maybePromise && typeof maybePromise.finally === \"function\") {\n return maybePromise.finally(() => {\n this.stack.pop();\n }) as R;\n }\n this.stack.pop();\n return result;\n } catch (error) {\n this.stack.pop();\n throw error;\n }\n }\n}\n\nfunction getAsyncLocalStorageCtor(): AsyncLocalStorageCtor | undefined {\n if (\n typeof window !== \"undefined\" ||\n typeof process === \"undefined\" ||\n !process.versions?.node ||\n typeof process.getBuiltinModule !== \"function\"\n ) {\n return undefined;\n }\n return process.getBuiltinModule(\"node:async_hooks\")?.AsyncLocalStorage as\n | AsyncLocalStorageCtor\n | undefined;\n}\n\nconst AsyncLocalStorageCtor = getAsyncLocalStorageCtor();\n\nfunction processEnv(name: string): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n return process.env?.[name];\n}\n\n/**\n * Per-request agent-run state. Lives on `RequestContext.run` so the\n * agent-chat plugin can populate fields as the run progresses (owner,\n * resolved API key, system prompt, engine, model, threadId) without\n * mutating module-scope `let` bindings — those leak across concurrent\n * requests on a single Node.js process.\n *\n * Mutated in-place by `prepareRun`, `onEngineResolved`, `onRunStart` so\n * tool factory closures (automation, fetch, team, builder-browser) read\n * the live per-request value via `getRequestRunContext()`.\n */\nexport interface RequestRunContext {\n /** Request-scoped serverless continuation hook, when the runtime provides one. */\n waitUntil?: (promise: Promise<unknown>) => void;\n /** Origin of the current request (used by the builder-browser tool). */\n requestOrigin?: string;\n /** Stable browser tab id for tab-scoped app-state reads/writes. */\n browserTabId?: string;\n /** Resource scope for the current chat thread, e.g. the active deck. */\n chatScope?: {\n type: string;\n id: string;\n label?: string;\n } | null;\n /** Resolved owner email (set by prepareRun). */\n owner?: string;\n /** Owner's active Anthropic API key (set by prepareRun). */\n userApiKey?: string;\n /** Thread ID for the current run (set by onRunStart). */\n threadId?: string;\n /** System prompt actually sent to the model for this run. */\n systemPrompt?: string;\n /** Engine instance for this run (set by onEngineResolved). */\n engine?: import(\"../agent/engine/types.js\").AgentEngine;\n /** Model name for this run (set by onEngineResolved). */\n model?: string;\n /**\n * True when this run is executing inside the durable background-function\n * worker (the `_process-run` self-dispatch), not the synchronous foreground\n * request. Template `extraContext` / system-prompt builders can read this to\n * skip heavy, hang-prone enrichment (large data-dictionary DB reads, etc.)\n * in the worker so it reliably claims its run within the setup budget.\n */\n isBackgroundWorker?: boolean;\n /** Tool calls made so far in the current agent loop. */\n toolCalls?: Array<{ name: string; input: unknown }>;\n /** Tool results returned so far in the current agent loop. */\n toolResults?: Array<{ name: string; content: string; isError: boolean }>;\n /** Per-run fingerprints for large extension bodies already sent to the LLM. */\n extensionContentReads?: Record<string, string>;\n /** Per-run fingerprints for repeated tool-search calls already sent to the LLM. */\n toolSearchReads?: Record<\n string,\n { totalTools: number; resultNames: string[] }\n >;\n}\n\nexport interface RequestContext {\n userEmail?: string;\n userName?: string;\n orgId?: string;\n /**\n * Narrow authorization capability verified from an embed session. This is\n * deliberately separate from user identity: capability-only sessions must\n * not satisfy account-backed auth or inherit the ticket owner's privileges.\n */\n authCapability?: string;\n timezone?: string;\n /**\n * The caller's browser analytics session id, when the request came from a\n * page. Emitted as PostHog's `$session_id` so agent traces join to session\n * replay; never used for authorization.\n */\n browserSessionId?: string;\n /**\n * Set when code reads authenticated request context. Public SSR shell/data\n * should not depend on this value; user/org-specific reads belong behind\n * client-side actions/API after hydration.\n */\n authContextAccessed?: boolean;\n /**\n * Origin of the inbound request (e.g. `http://127.0.0.1:8100`). Set by the\n * MCP mount from the request headers so actions that build externally\n * fetchable URLs (e.g. design `export-coding-handoff`'s signed raw-code URL)\n * resolve the real local-workspace origin instead of a prod/localhost\n * fallback. Optional — absent on paths that don't populate it.\n */\n requestOrigin?: string;\n /**\n * True when the request's real socket peer is loopback, captured by the\n * action-route handler while the h3 event is still in scope (nothing below\n * that layer can see the event). Derived from `getRequestIP()` WITHOUT\n * `x-forwarded-for`, so a remote client cannot set it via headers.\n *\n * A local-dev gate only. A tunnel or reverse proxy that reaches the dev\n * server over loopback also presents as loopback, so this is necessary but\n * not sufficient on its own — pair it with something that scopes the blast\n * radius (a resource that is itself local-only, NODE_ENV, etc.).\n */\n isLoopbackRequest?: boolean;\n /**\n * True when this request is being processed by an integration-platform\n * webhook (Slack, Telegram, etc.) where the function timeout is the\n * binding constraint. Code that calls slow remote APIs can use this to apply\n * tighter budgets on this path while leaving normal agent-chat callers\n * (5+ min budget) unaffected.\n */\n isIntegrationCaller?: boolean;\n /**\n * Metadata for the currently-processing integration task. This lets tools\n * that start long-running remote work persist a continuation that can update\n * the originating platform thread after the current function budget ends.\n */\n integration?: {\n taskId: string;\n attempts?: number;\n incoming: import(\"../integrations/types.js\").IncomingMessage;\n placeholderRef?: string;\n /** Opaque provider-native progress surface for a durable continuation. */\n progressRef?: import(\"../integrations/types.js\").PlatformRunProgressRef;\n installationId?: string;\n scopeId?: string;\n principalType?: \"user\" | \"service\";\n lineage?: {\n runId?: string;\n parentTaskId?: string;\n source?: {\n kind: string;\n platform?: string;\n id: string;\n url?: string;\n };\n network?: {\n protocol: \"a2a\" | \"mcp\" | \"provider-api\";\n id: string;\n peer?: string;\n };\n };\n };\n /**\n * Mutable per-request agent-run state. Populated by the agent-chat plugin\n * during a run; tool closures dereference it on each invocation.\n */\n run?: RequestRunContext;\n}\n\nconst GLOBAL_KEY = \"__agentNativeRequestContextAls\" as const;\nconst OBSERVERS_KEY = \"__agentNativeRequestContextObservers\" as const;\nconst BOUNDARY_KEY = \"__agentNativeRequestBoundaryInstalled\" as const;\ntype RequestContextObserver = (ctx: RequestContext) => void;\ntype GlobalWithRequestContext = typeof globalThis & {\n [GLOBAL_KEY]?: AsyncLocalStorageLike<RequestContext>;\n [OBSERVERS_KEY]?: RequestContextObserver[];\n [BOUNDARY_KEY]?: boolean;\n};\nconst globalRef = globalThis as GlobalWithRequestContext;\nif (!globalRef[GLOBAL_KEY]) {\n globalRef[GLOBAL_KEY] = AsyncLocalStorageCtor\n ? new AsyncLocalStorageCtor<RequestContext>()\n : new StackAsyncLocalStorage<RequestContext>();\n}\nif (!globalRef[OBSERVERS_KEY]) {\n globalRef[OBSERVERS_KEY] = [];\n}\nconst als = globalRef[GLOBAL_KEY]!;\nconst observers = globalRef[OBSERVERS_KEY]!;\n\n/**\n * Register a callback fired every time `runWithRequestContext` enters a new\n * scope. The hook runs INSIDE the AsyncLocalStorage scope, so observability\n * helpers that read the current isolation scope (e.g. Sentry) attach to the\n * right per-request context.\n *\n * Returned function unregisters the observer. Observers must never throw —\n * any error is swallowed so a misbehaving observer can't break the request\n * path.\n */\nexport function addRequestContextObserver(\n observer: RequestContextObserver,\n): () => void {\n observers.push(observer);\n return () => {\n const i = observers.indexOf(observer);\n if (i !== -1) observers.splice(i, 1);\n };\n}\n\n/**\n * Run a callback within a per-request context. The context is available to all\n * async operations spawned from `fn` via `getRequestUserEmail()` / `getRequestOrgId()`.\n *\n * Any registered `addRequestContextObserver` callbacks fire inside the new\n * scope before `fn` runs, so observability code can pin user/org info onto\n * isolation-scoped backends (Sentry, OpenTelemetry, etc.).\n */\nexport function runWithRequestContext<T>(\n ctx: RequestContext,\n fn: () => T | Promise<T>,\n): T | Promise<T> {\n return als.run(ctx, () => {\n if (observers.length > 0) {\n for (const obs of observers) {\n try {\n obs(ctx);\n } catch {\n // Observers must never break the request path.\n }\n }\n }\n return fn();\n });\n}\n\n/**\n * Return the active request context, if this call chain is running under one.\n *\n * This is intentionally distinct from `getRequestUserEmail()`: callers that\n * have an active context with no authenticated user must not fall through to\n * process-wide CLI fallbacks such as `AGENT_USER_EMAIL` or \"latest session\".\n */\nexport function getRequestContext(): RequestContext | undefined {\n const store = als.getStore();\n markAuthContextAccess(store);\n return store;\n}\n\n/**\n * True when AsyncLocalStorage has an active context for this call chain.\n * Useful for helpers that support both HTTP requests and standalone CLI runs.\n */\nexport function hasRequestContext(): boolean {\n return als.getStore() !== undefined;\n}\n\n/**\n * Record that the framework's request-boundary middleware is installed in this\n * process, so every inbound HTTP request runs inside a `RequestContext`.\n *\n * Once that is true, a request-scoped identity read that finds no store can no\n * longer be an HTTP caller — which is what makes the ambient-identity warning\n * in `getRequestUserEmail()` specific enough to be worth emitting.\n */\nexport function markRequestBoundaryInstalled(): void {\n globalRef[BOUNDARY_KEY] = true;\n}\n\nexport function hasRequestBoundary(): boolean {\n return globalRef[BOUNDARY_KEY] === true;\n}\n\n/**\n * The ambient, process-wide identity configured for this deployment\n * (`AGENT_USER_EMAIL`). Legitimate callers are the ones with no request behind\n * them at all: CLI invocations, cron/scheduled jobs, seed and QA scripts.\n *\n * TRAP: this is not the caller's identity, and a request handler that reads it\n * authorizes whoever the deploy env names rather than whoever signed in — it\n * fails open toward more privilege. Request handlers read\n * `getRequestUserEmail()` and fail closed when it returns undefined.\n */\nexport function getAmbientUserEmail(): string | undefined {\n return processEnv(\"AGENT_USER_EMAIL\");\n}\n\n/** Ambient process-wide org (`AGENT_ORG_ID`). Same trap as `getAmbientUserEmail()`. */\nexport function getAmbientOrgId(): string | undefined {\n return processEnv(\"AGENT_ORG_ID\");\n}\n\nconst warnedAmbientIdentities = new Set<string>();\n\nfunction warnAmbientIdentitySatisfiedRead(email: string): void {\n if (!hasRequestBoundary()) return;\n if (warnedAmbientIdentities.has(email)) return;\n warnedAmbientIdentities.add(email);\n console.warn(\n `[agent-native] getRequestUserEmail() found no request context and answered with the ambient ` +\n `AGENT_USER_EMAIL identity (${email}). This process serves HTTP requests, so a request-scoped ` +\n `read reaching the ambient identity is a bug: it authorizes the deploy env, not the signed-in ` +\n `user. Wrap the caller in runWithRequestContext({ userEmail }), or call getAmbientUserEmail() ` +\n `explicitly if the process identity really is what you mean.`,\n );\n}\n\n/**\n * Get the current request's user email.\n *\n * - If a request context exists (HTTP/A2A path), returns its `userEmail` —\n * even when that value is `undefined`. The env fallback MUST NOT fire here:\n * a stale process-wide `AGENT_USER_EMAIL` from a CLI run or previous bug\n * would leak into an unauthenticated A2A/API call (e.g. unsigned or API-key\n * modes where `runWithRequestContext({ userEmail: undefined })` is used).\n * - Only when there is NO request context (CLI scripts) do we fall back to\n * `process.env.AGENT_USER_EMAIL`. In a process that serves HTTP requests the\n * framework installs a request boundary so that case cannot be a request;\n * if it happens anyway we warn loudly rather than answer silently.\n */\nexport function getRequestUserEmail(): string | undefined {\n const store = als.getStore();\n if (store !== undefined) {\n if (store.userEmail) markAuthContextAccess(store);\n return store.userEmail;\n }\n const ambient = processEnv(\"AGENT_USER_EMAIL\");\n if (ambient) warnAmbientIdentitySatisfiedRead(ambient);\n return ambient;\n}\n\n/**\n * Get the current request's display name, when the auth provider supplied one.\n *\n * The same request-context fallback rules as `getRequestUserEmail()` apply:\n * HTTP/A2A calls only read AsyncLocalStorage, while CLI scripts may opt in via\n * `AGENT_USER_NAME`.\n */\nexport function getRequestUserName(): string | undefined {\n const store = als.getStore();\n if (store !== undefined) {\n if (store.userName) markAuthContextAccess(store);\n return store.userName;\n }\n return processEnv(\"AGENT_USER_NAME\");\n}\n\n/**\n * Get the current request's org ID.\n *\n * Same store-aware semantics as `getRequestUserEmail()` — env fallback is\n * CLI-only, so a request that explicitly has no org doesn't inherit a stale\n * `process.env.AGENT_ORG_ID` from a prior request on the same Lambda instance.\n */\nexport function getRequestOrgId(): string | undefined {\n const store = als.getStore();\n if (store !== undefined) {\n if (store.orgId) markAuthContextAccess(store);\n return store.orgId;\n }\n return processEnv(\"AGENT_ORG_ID\");\n}\n\n/** Return the verified capability for this request, without implying identity. */\nexport function getRequestAuthCapability(): string | undefined {\n const store = als.getStore();\n if (!store) return undefined;\n if (store.authCapability) markAuthContextAccess(store);\n return store.authCapability;\n}\n\n/**\n * Whether the current request came from a loopback socket peer. Fails closed:\n * outside a request store (CLI, background job, agent run) there is no peer to\n * vouch for, so this is `false` rather than inheriting an ambient default.\n */\nexport function getRequestIsLoopback(): boolean {\n return als.getStore()?.isLoopbackRequest === true;\n}\n\nfunction markAuthContextAccess(ctx: RequestContext | undefined) {\n if (!ctx) return;\n if (ctx.userEmail || ctx.userName || ctx.orgId || ctx.authCapability) {\n ctx.authContextAccessed = true;\n }\n}\n\nexport function hasAuthContextAccess(ctx: RequestContext | undefined): boolean {\n return Boolean(ctx?.authContextAccessed);\n}\n\n/**\n * Get the current request's IANA timezone (e.g. \"America/Los_Angeles\").\n * The UI sends this via the `x-user-timezone` header on every action call, and\n * the agent chat plugin propagates it into the request context so that\n * agent-initiated tool calls also see the user's timezone. Falls back to\n * `process.env.AGENT_USER_TIMEZONE` only for CLI scripts (no request context).\n */\nexport function getRequestTimezone(): string | undefined {\n const store = als.getStore();\n if (store !== undefined) return store.timezone;\n return processEnv(\"AGENT_USER_TIMEZONE\");\n}\n\n/**\n * Returns true when this request is on an integration-platform path (Slack,\n * Telegram, etc.) — i.e. we're inside the integration plugin's processor\n * function and the platform's deliver-by deadline plus the host's function\n * timeout are the binding budget. Non-integration callers (CLI, normal\n * agent chat) should treat this as `false`.\n */\nexport function isIntegrationCallerRequest(): boolean {\n return als.getStore()?.isIntegrationCaller === true;\n}\n\nexport function getIntegrationRequestContext():\n | NonNullable<RequestContext[\"integration\"]>\n | undefined {\n return als.getStore()?.integration;\n}\n\n/**\n * Convenience: returns `{ userEmail, orgId }` from the active request context,\n * suitable for passing to `resolveCredential(key, ctx)`. Returns `null` when\n * no user is associated with the call (e.g. an unauthenticated public route).\n *\n * For framework actions auto-mounted at `/_agent-native/actions/...` this is\n * always populated because action-routes wraps every invocation in\n * `runWithRequestContext`. For hand-written `/api/*` routes the calling code\n * is responsible for setting up the context (see `runWithRequestContext`).\n */\nexport function getCredentialContext(): {\n userEmail: string;\n orgId: string | null;\n} | null {\n const userEmail = getRequestUserEmail();\n if (!userEmail) return null;\n return { userEmail, orgId: getRequestOrgId() ?? null };\n}\n\n/**\n * Get the active request's mutable agent-run state. Returns `undefined` when\n * called outside an agent run (e.g. before `prepareRun` or in a non-agent\n * code path). Callers must tolerate the field absence; use the helper\n * `requireRequestRunContext()` if missing context is a programming error.\n */\nexport function getRequestRunContext(): RequestRunContext | undefined {\n const store = als.getStore();\n if (!store) return undefined;\n return store.run;\n}\n\n/**\n * Ensure a `RequestRunContext` exists on the active request store and\n * return it. Used by the agent-chat handler to attach run state once it\n * starts processing a chat request. Returns `undefined` if there is no\n * active request store (caller should not be invoking this outside ALS).\n */\nexport function ensureRequestRunContext(): RequestRunContext | undefined {\n const store = als.getStore();\n if (!store) return undefined;\n if (!store.run) store.run = {};\n return store.run;\n}\n"]}
|