@agent-native/core 0.18.0 → 0.18.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/dist/mcp/build-server.d.ts +20 -3
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +90 -15
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/builtin-tools.d.ts +8 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +115 -13
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +23 -4
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/stdio.d.ts.map +1 -1
- package/dist/mcp/stdio.js +1 -0
- package/dist/mcp/stdio.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +1 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +46 -19
- package/dist/server/auth.js.map +1 -1
- package/dist/server/open-route.d.ts.map +1 -1
- package/dist/server/open-route.js +36 -5
- package/dist/server/open-route.js.map +1 -1
- package/dist/server/request-context.d.ts +8 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"open-route.d.ts","sourceRoot":"","sources":["../../src/server/open-route.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"open-route.d.ts","sourceRoot":"","sources":["../../src/server/open-route.ts"],"names":[],"mappings":"AAyCA,MAAM,WAAW,gBAAgB;IAC/B;;yEAEqE;IACrE,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE;QACzB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAChC,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACjC;AA+BD,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,gBAAqB,2FAyHpE"}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { defineEventHandler, getMethod } from "h3";
|
|
2
2
|
import { getSession, getConfiguredLoginHtml } from "./auth.js";
|
|
3
|
-
import { appStatePut } from "../application-state/store.js";
|
|
3
|
+
import { appStatePut, appStateGet } from "../application-state/store.js";
|
|
4
4
|
/** Query keys that are route control, not navigation payload. */
|
|
5
5
|
const RESERVED = new Set(["app", "view", "to", "compose"]);
|
|
6
6
|
// Control-char guard (NUL..US + DEL). Defined via codepoints so the source
|
|
7
7
|
// file stays plain ASCII.
|
|
8
8
|
const CONTROL_CHARS = new RegExp("[\\u0000-\\u001f\\u007f]");
|
|
9
|
+
// Compose-draft id charset. Mirrors `sanitizeDraftId` in
|
|
10
|
+
// templates/mail/actions/manage-draft.ts so the id we concatenate into the
|
|
11
|
+
// `compose-<id>` application-state key can't escape the key namespace
|
|
12
|
+
// (path-traversal / key injection guard).
|
|
13
|
+
const COMPOSE_ID = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
9
14
|
function getRequestUrl(event) {
|
|
10
15
|
return event.node?.req?.url ?? event.path ?? "/";
|
|
11
16
|
}
|
|
@@ -91,10 +96,36 @@ export function createOpenRouteHandler(options = {}) {
|
|
|
91
96
|
if (compose) {
|
|
92
97
|
try {
|
|
93
98
|
const draft = JSON.parse(decodeBase64Url(compose));
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
// Validate the id before using it as a key segment. An unsafe id
|
|
100
|
+
// could escape the `compose-` namespace and clobber an unrelated
|
|
101
|
+
// application-state key; skip the write (the view still opens),
|
|
102
|
+
// mirroring the malformed-payload branch below.
|
|
103
|
+
if (draft &&
|
|
104
|
+
typeof draft === "object" &&
|
|
105
|
+
typeof draft.id === "string" &&
|
|
106
|
+
COMPOSE_ID.test(draft.id)) {
|
|
107
|
+
const composeKey = `compose-${draft.id}`;
|
|
108
|
+
// A compact deep link may carry only `{ id, subject }` when the
|
|
109
|
+
// full draft was too large to inline in the URL. The complete
|
|
110
|
+
// draft is already persisted at `compose-<id>` by manage-draft
|
|
111
|
+
// on create/update. Never let the truncated stub overwrite that
|
|
112
|
+
// richer saved draft (would silently lose body / recipients /
|
|
113
|
+
// reply metadata). Only write when the payload actually carries
|
|
114
|
+
// content, or when nothing is saved yet (composer still opens).
|
|
115
|
+
const hasContent = (typeof draft.body === "string" && draft.body.length > 0) ||
|
|
116
|
+
!!draft.to ||
|
|
117
|
+
!!draft.cc ||
|
|
118
|
+
!!draft.bcc ||
|
|
119
|
+
!!draft.html ||
|
|
120
|
+
!!draft.replyToThreadId;
|
|
121
|
+
const existing = hasContent
|
|
122
|
+
? null
|
|
123
|
+
: await appStateGet(session.email, composeKey);
|
|
124
|
+
if (hasContent || !existing) {
|
|
125
|
+
await appStatePut(session.email, composeKey, draft, {
|
|
126
|
+
requestSource: "deep-link",
|
|
127
|
+
});
|
|
128
|
+
}
|
|
98
129
|
}
|
|
99
130
|
}
|
|
100
131
|
catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"open-route.js","sourceRoot":"","sources":["../../src/server/open-route.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/_agent-native/open` — the stable deep-link route.\n *\n * An external coding agent (Claude Code / Cowork / Codex) surfaces an\n * \"Open in <app> →\" link (built by an action's `link` builder, see\n * `deep-link.ts`). When the user clicks it in any browser / inline webview,\n * this route:\n * 1. Resolves the *browser* session (NOT the agent token) — so the record\n * always lands where the human is logged in.\n * 2. When unauthenticated, serves the same sign-in form the auth guard\n * would, *at this same URL*. The login form's success handler reloads\n * `window.location.href`, so the now-authenticated request re-enters\n * this route and proceeds. No `?next=` plumbing needed.\n * 3. Writes the existing one-shot `navigate` application-state command (the\n * exact key the UI already drains every 2s — we don't invent a new\n * navigation mechanism, we bridge to it), plus an optional `compose-<id>`\n * draft.\n * 4. 302-redirects to the rendered SPA view so the page loads immediately;\n * the polled `navigate` command then applies record-level focus.\n *\n * The link itself is a pure pointer (view + record ids + filters) and carries\n * no privileged state.\n */\nimport type { H3Event } from \"h3\";\nimport { defineEventHandler, getMethod } from \"h3\";\nimport { getSession, getConfiguredLoginHtml } from \"./auth.js\";\nimport { appStatePut } from \"../application-state/store.js\";\n\n/** Query keys that are route control, not navigation payload. */\nconst RESERVED = new Set([\"app\", \"view\", \"to\", \"compose\"]);\n\n// Control-char guard (NUL..US + DEL). Defined via codepoints so the source\n// file stays plain ASCII.\nconst CONTROL_CHARS = new RegExp(\"[\\\\u0000-\\\\u001f\\\\u007f]\");\n\nexport interface OpenRouteOptions {\n /** Per-template override that turns the parsed deep-link params into the\n * client-side SPA path to redirect to. Return `null` to use the default\n * (`/<view>`). Filter params (`f_*`) are appended automatically. */\n resolveOpenPath?: (params: {\n app?: string;\n view?: string;\n params: Record<string, string>;\n }) => string | null | undefined;\n}\n\nfunction getRequestUrl(event: H3Event): string {\n return (event as any).node?.req?.url ?? (event as any).path ?? \"/\";\n}\n\n/** Decode a base64url string to UTF-8 (Node Buffer; this route is Node-only). */\nfunction decodeBase64Url(input: string): string {\n return Buffer.from(input, \"base64url\").toString(\"utf8\");\n}\n\n/**\n * Normalize a candidate redirect path to a safe, same-origin, leading-slash\n * relative path. Rejects absolute URLs, scheme-relative `//host`, and control\n * chars (open-redirect guard). Returns `null` when unsafe.\n */\nfunction safeRelativePath(raw: string | undefined | null): string | null {\n if (!raw) return null;\n if (CONTROL_CHARS.test(raw)) return null;\n if (!raw.startsWith(\"/\")) return null;\n if (raw.startsWith(\"//\") || raw.startsWith(\"/\\\\\")) return null;\n if (/^\\/[a-z][a-z0-9+.-]*:/i.test(raw)) return null;\n return raw;\n}\n\nfunction redirect(location: string): Response {\n // Native web Response (not h3 v2's reworked sendRedirect) — matches the\n // redirect pattern used elsewhere in auth.ts.\n return new Response(\"\", { status: 302, headers: { Location: location } });\n}\n\nexport function createOpenRouteHandler(options: OpenRouteOptions = {}) {\n return defineEventHandler(async (event: H3Event) => {\n const method = getMethod(event);\n if (method !== \"GET\" && method !== \"HEAD\") {\n return new Response(JSON.stringify({ error: \"Method not allowed\" }), {\n status: 405,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n const rawUrl = getRequestUrl(event);\n let search: URLSearchParams;\n try {\n search = new URL(rawUrl, \"http://an.invalid\").searchParams;\n } catch {\n search = new URLSearchParams();\n }\n\n const app = search.get(\"app\") ?? undefined;\n const view = search.get(\"view\") ?? undefined;\n const toParam = search.get(\"to\") ?? undefined;\n const compose = search.get(\"compose\") ?? undefined;\n\n // Resolve the BROWSER session. When unauthenticated, serve the same login\n // form the guard would — at this URL — so the post-login reload returns\n // here authenticated.\n const session = await getSession(event);\n if (!session?.email) {\n const html = getConfiguredLoginHtml(event);\n if (html) {\n return new Response(html, {\n status: 200,\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n // No auth guard configured (fully open app) — best effort: still send\n // the user to the view; nothing to scope the navigate write to.\n }\n\n // Build the navigation payload from every non-reserved query param\n // (record ids + filters: threadId, eventId, dashboardId, f_*, ...).\n const navParams: Record<string, string> = {};\n for (const [k, v] of search.entries()) {\n if (RESERVED.has(k)) continue;\n navParams[k] = v;\n }\n const navPayload: Record<string, unknown> = { ...navParams };\n if (view) navPayload.view = view;\n\n if (session?.email) {\n try {\n await appStatePut(session.email, \"navigate\", navPayload, {\n requestSource: \"deep-link\",\n });\n if (compose) {\n try {\n const draft = JSON.parse(decodeBase64Url(compose));\n if (draft && typeof draft === \"object\" && draft.id) {\n await appStatePut(session.email, `compose-${draft.id}`, draft, {\n requestSource: \"deep-link\",\n });\n }\n } catch {\n // Malformed compose payload — skip; the view still opens.\n }\n }\n } catch {\n // App-state write failure shouldn't 500 the click; the redirect\n // below still lands the user on the right view.\n }\n }\n\n // Resolve the SPA path to redirect to.\n let target =\n safeRelativePath(toParam) ??\n safeRelativePath(\n options.resolveOpenPath?.({ app, view, params: navParams }) ??\n (view ? `/${view}` : null),\n ) ??\n \"/\";\n\n // Forward filter params (f_*) onto the redirect so dashboards/lists open\n // pre-filtered even before the navigate command is drained.\n const filters = new URLSearchParams();\n for (const [k, v] of search.entries()) {\n if (k.startsWith(\"f_\")) filters.set(k, v);\n }\n const fq = filters.toString();\n if (fq) target += (target.includes(\"?\") ? \"&\" : \"?\") + fq;\n\n return redirect(target);\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"open-route.js","sourceRoot":"","sources":["../../src/server/open-route.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/_agent-native/open` — the stable deep-link route.\n *\n * An external coding agent (Claude Code / Cowork / Codex) surfaces an\n * \"Open in <app> →\" link (built by an action's `link` builder, see\n * `deep-link.ts`). When the user clicks it in any browser / inline webview,\n * this route:\n * 1. Resolves the *browser* session (NOT the agent token) — so the record\n * always lands where the human is logged in.\n * 2. When unauthenticated, serves the same sign-in form the auth guard\n * would, *at this same URL*. The login form's success handler reloads\n * `window.location.href`, so the now-authenticated request re-enters\n * this route and proceeds. No `?next=` plumbing needed.\n * 3. Writes the existing one-shot `navigate` application-state command (the\n * exact key the UI already drains every 2s — we don't invent a new\n * navigation mechanism, we bridge to it), plus an optional `compose-<id>`\n * draft.\n * 4. 302-redirects to the rendered SPA view so the page loads immediately;\n * the polled `navigate` command then applies record-level focus.\n *\n * The link itself is a pure pointer (view + record ids + filters) and carries\n * no privileged state.\n */\nimport type { H3Event } from \"h3\";\nimport { defineEventHandler, getMethod } from \"h3\";\nimport { getSession, getConfiguredLoginHtml } from \"./auth.js\";\nimport { appStatePut, appStateGet } from \"../application-state/store.js\";\n\n/** Query keys that are route control, not navigation payload. */\nconst RESERVED = new Set([\"app\", \"view\", \"to\", \"compose\"]);\n\n// Control-char guard (NUL..US + DEL). Defined via codepoints so the source\n// file stays plain ASCII.\nconst CONTROL_CHARS = new RegExp(\"[\\\\u0000-\\\\u001f\\\\u007f]\");\n\n// Compose-draft id charset. Mirrors `sanitizeDraftId` in\n// templates/mail/actions/manage-draft.ts so the id we concatenate into the\n// `compose-<id>` application-state key can't escape the key namespace\n// (path-traversal / key injection guard).\nconst COMPOSE_ID = /^[a-zA-Z0-9_-]{1,64}$/;\n\nexport interface OpenRouteOptions {\n /** Per-template override that turns the parsed deep-link params into the\n * client-side SPA path to redirect to. Return `null` to use the default\n * (`/<view>`). Filter params (`f_*`) are appended automatically. */\n resolveOpenPath?: (params: {\n app?: string;\n view?: string;\n params: Record<string, string>;\n }) => string | null | undefined;\n}\n\nfunction getRequestUrl(event: H3Event): string {\n return (event as any).node?.req?.url ?? (event as any).path ?? \"/\";\n}\n\n/** Decode a base64url string to UTF-8 (Node Buffer; this route is Node-only). */\nfunction decodeBase64Url(input: string): string {\n return Buffer.from(input, \"base64url\").toString(\"utf8\");\n}\n\n/**\n * Normalize a candidate redirect path to a safe, same-origin, leading-slash\n * relative path. Rejects absolute URLs, scheme-relative `//host`, and control\n * chars (open-redirect guard). Returns `null` when unsafe.\n */\nfunction safeRelativePath(raw: string | undefined | null): string | null {\n if (!raw) return null;\n if (CONTROL_CHARS.test(raw)) return null;\n if (!raw.startsWith(\"/\")) return null;\n if (raw.startsWith(\"//\") || raw.startsWith(\"/\\\\\")) return null;\n if (/^\\/[a-z][a-z0-9+.-]*:/i.test(raw)) return null;\n return raw;\n}\n\nfunction redirect(location: string): Response {\n // Native web Response (not h3 v2's reworked sendRedirect) — matches the\n // redirect pattern used elsewhere in auth.ts.\n return new Response(\"\", { status: 302, headers: { Location: location } });\n}\n\nexport function createOpenRouteHandler(options: OpenRouteOptions = {}) {\n return defineEventHandler(async (event: H3Event) => {\n const method = getMethod(event);\n if (method !== \"GET\" && method !== \"HEAD\") {\n return new Response(JSON.stringify({ error: \"Method not allowed\" }), {\n status: 405,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n const rawUrl = getRequestUrl(event);\n let search: URLSearchParams;\n try {\n search = new URL(rawUrl, \"http://an.invalid\").searchParams;\n } catch {\n search = new URLSearchParams();\n }\n\n const app = search.get(\"app\") ?? undefined;\n const view = search.get(\"view\") ?? undefined;\n const toParam = search.get(\"to\") ?? undefined;\n const compose = search.get(\"compose\") ?? undefined;\n\n // Resolve the BROWSER session. When unauthenticated, serve the same login\n // form the guard would — at this URL — so the post-login reload returns\n // here authenticated.\n const session = await getSession(event);\n if (!session?.email) {\n const html = getConfiguredLoginHtml(event);\n if (html) {\n return new Response(html, {\n status: 200,\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n // No auth guard configured (fully open app) — best effort: still send\n // the user to the view; nothing to scope the navigate write to.\n }\n\n // Build the navigation payload from every non-reserved query param\n // (record ids + filters: threadId, eventId, dashboardId, f_*, ...).\n const navParams: Record<string, string> = {};\n for (const [k, v] of search.entries()) {\n if (RESERVED.has(k)) continue;\n navParams[k] = v;\n }\n const navPayload: Record<string, unknown> = { ...navParams };\n if (view) navPayload.view = view;\n\n if (session?.email) {\n try {\n await appStatePut(session.email, \"navigate\", navPayload, {\n requestSource: \"deep-link\",\n });\n if (compose) {\n try {\n const draft = JSON.parse(decodeBase64Url(compose));\n // Validate the id before using it as a key segment. An unsafe id\n // could escape the `compose-` namespace and clobber an unrelated\n // application-state key; skip the write (the view still opens),\n // mirroring the malformed-payload branch below.\n if (\n draft &&\n typeof draft === \"object\" &&\n typeof draft.id === \"string\" &&\n COMPOSE_ID.test(draft.id)\n ) {\n const composeKey = `compose-${draft.id}`;\n // A compact deep link may carry only `{ id, subject }` when the\n // full draft was too large to inline in the URL. The complete\n // draft is already persisted at `compose-<id>` by manage-draft\n // on create/update. Never let the truncated stub overwrite that\n // richer saved draft (would silently lose body / recipients /\n // reply metadata). Only write when the payload actually carries\n // content, or when nothing is saved yet (composer still opens).\n const hasContent =\n (typeof draft.body === \"string\" && draft.body.length > 0) ||\n !!draft.to ||\n !!draft.cc ||\n !!draft.bcc ||\n !!draft.html ||\n !!draft.replyToThreadId;\n const existing = hasContent\n ? null\n : await appStateGet(session.email, composeKey);\n if (hasContent || !existing) {\n await appStatePut(session.email, composeKey, draft, {\n requestSource: \"deep-link\",\n });\n }\n }\n } catch {\n // Malformed compose payload — skip; the view still opens.\n }\n }\n } catch {\n // App-state write failure shouldn't 500 the click; the redirect\n // below still lands the user on the right view.\n }\n }\n\n // Resolve the SPA path to redirect to.\n let target =\n safeRelativePath(toParam) ??\n safeRelativePath(\n options.resolveOpenPath?.({ app, view, params: navParams }) ??\n (view ? `/${view}` : null),\n ) ??\n \"/\";\n\n // Forward filter params (f_*) onto the redirect so dashboards/lists open\n // pre-filtered even before the navigate command is drained.\n const filters = new URLSearchParams();\n for (const [k, v] of search.entries()) {\n if (k.startsWith(\"f_\")) filters.set(k, v);\n }\n const fq = filters.toString();\n if (fq) target += (target.includes(\"?\") ? \"&\" : \"?\") + fq;\n\n return redirect(target);\n });\n}\n"]}
|
|
@@ -49,6 +49,14 @@ export interface RequestContext {
|
|
|
49
49
|
userName?: string;
|
|
50
50
|
orgId?: string;
|
|
51
51
|
timezone?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Origin of the inbound request (e.g. `http://127.0.0.1:8100`). Set by the
|
|
54
|
+
* MCP mount from the request headers so actions that build externally
|
|
55
|
+
* fetchable URLs (e.g. design `export-coding-handoff`'s signed raw-code URL)
|
|
56
|
+
* resolve the real local-workspace origin instead of a prod/localhost
|
|
57
|
+
* fallback. Optional — absent on paths that don't populate it.
|
|
58
|
+
*/
|
|
59
|
+
requestOrigin?: string;
|
|
52
60
|
/**
|
|
53
61
|
* True when this request is being processed by an integration-platform
|
|
54
62
|
* webhook (Slack, Telegram, etc.) where the function timeout is the
|
|
@@ -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 */\nimport { AsyncLocalStorage } from \"node:async_hooks\";\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 /** 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 /** 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}\n\nexport interface RequestContext {\n userEmail?: string;\n userName?: string;\n orgId?: string;\n timezone?: string;\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 };\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;\ntype RequestContextObserver = (ctx: RequestContext) => void;\ntype GlobalWithRequestContext = typeof globalThis & {\n [GLOBAL_KEY]?: AsyncLocalStorage<RequestContext>;\n [OBSERVERS_KEY]?: RequestContextObserver[];\n};\nconst globalRef = globalThis as GlobalWithRequestContext;\nif (!globalRef[GLOBAL_KEY]) {\n globalRef[GLOBAL_KEY] = new AsyncLocalStorage<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 return als.getStore();\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 * 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`.\n */\nexport function getRequestUserEmail(): string | undefined {\n const store = als.getStore();\n if (store !== undefined) return store.userEmail;\n return process.env.AGENT_USER_EMAIL;\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) return store.userName;\n return process.env.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) return store.orgId;\n return process.env.AGENT_ORG_ID;\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 process.env.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 */\nimport { AsyncLocalStorage } from \"node:async_hooks\";\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 /** 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 /** 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}\n\nexport interface RequestContext {\n userEmail?: string;\n userName?: string;\n orgId?: string;\n timezone?: string;\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 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 };\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;\ntype RequestContextObserver = (ctx: RequestContext) => void;\ntype GlobalWithRequestContext = typeof globalThis & {\n [GLOBAL_KEY]?: AsyncLocalStorage<RequestContext>;\n [OBSERVERS_KEY]?: RequestContextObserver[];\n};\nconst globalRef = globalThis as GlobalWithRequestContext;\nif (!globalRef[GLOBAL_KEY]) {\n globalRef[GLOBAL_KEY] = new AsyncLocalStorage<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 return als.getStore();\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 * 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`.\n */\nexport function getRequestUserEmail(): string | undefined {\n const store = als.getStore();\n if (store !== undefined) return store.userEmail;\n return process.env.AGENT_USER_EMAIL;\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) return store.userName;\n return process.env.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) return store.orgId;\n return process.env.AGENT_ORG_ID;\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 process.env.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"]}
|