@agent-native/core 0.14.6 → 0.14.7
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/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +54 -0
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +38 -0
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/error-format.d.ts.map +1 -1
- package/dist/client/error-format.js +7 -1
- package/dist/client/error-format.js.map +1 -1
- package/dist/mcp-client/manager.d.ts.map +1 -1
- package/dist/mcp-client/manager.js +10 -0
- package/dist/mcp-client/manager.js.map +1 -1
- package/dist/server/sentry.d.ts.map +1 -1
- package/dist/server/sentry.js +20 -0
- package/dist/server/sentry.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-format.d.ts","sourceRoot":"","sources":["../../src/client/error-format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,0BAA0B,qCAAqC,CAAC;AAY7E,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAYR;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,mBAAmB,
|
|
1
|
+
{"version":3,"file":"error-format.d.ts","sourceRoot":"","sources":["../../src/client/error-format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,0BAA0B,qCAAqC,CAAC;AAY7E,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAYR;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,mBAAmB,CA4C5E"}
|
|
@@ -39,8 +39,14 @@ export function normalizeChatError(errorMessage) {
|
|
|
39
39
|
const looksHtml = /<html[\s>]|<body[\s>]|<head[\s>]/i.test(raw);
|
|
40
40
|
const text = looksHtml ? htmlToText(raw) : raw.trim();
|
|
41
41
|
if (/^Gateway error \(no detail; raw event:/i.test(text)) {
|
|
42
|
+
// The previous copy promised auto-recovery and suggested switching models,
|
|
43
|
+
// but neither helps for this code: the server already retried once and
|
|
44
|
+
// the client deliberately skips auto-continuation
|
|
45
|
+
// (see `builder_gateway_error` in sse-event-processor.ts). The error is
|
|
46
|
+
// almost always upstream, so retrying the same conversation with a
|
|
47
|
+
// different model lands on the same wall.
|
|
42
48
|
return {
|
|
43
|
-
message: "The model gateway
|
|
49
|
+
message: "The model gateway returned no error details and the chat couldn't recover. Wait a moment and retry, or start a new chat if it keeps happening.",
|
|
44
50
|
details: text,
|
|
45
51
|
};
|
|
46
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-format.js","sourceRoot":"","sources":["../../src/client/error-format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,kCAAkC,CAAC;AAE7E,SAAS,gBAAgB,CAAC,GAAW;IACnC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC/C,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,YAAoB,EACpB,UAAmB,EACnB,SAAkB;IAElB,MAAM,UAAU,GAAG,kBAAkB,CAAC,YAAY,CAAC,CAAC;IACpD,IACE,SAAS,KAAK,qBAAqB;QACnC,wCAAwC,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EACjE,CAAC;QACD,OAAO,UAAU,UAAU,CAAC,OAAO,qCAAqC,0BAA0B,GAAG,CAAC;IACxG,CAAC;IACD,IAAI,CAAC,UAAU,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,EAAE,CAAC;QACjD,OAAO,UAAU,UAAU,CAAC,OAAO,EAAE,CAAC;IACxC,CAAC;IACD,OAAO,UAAU,UAAU,CAAC,OAAO,+BAA+B,UAAU,GAAG,CAAC;AAClF,CAAC;AAOD,MAAM,UAAU,kBAAkB,CAAC,YAAoB;IACrD,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,IAAI,eAAe,CAAC,CAAC;IACpD,MAAM,SAAS,GAAG,mCAAmC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChE,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;IAEtD,IAAI,yCAAyC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACzD,OAAO;YACL,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"error-format.js","sourceRoot":"","sources":["../../src/client/error-format.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Append a Builder CTA markdown link to gateway errors that users can fix\n * outside the app. Used by both\n * chat SSE consumers (`sse-event-processor.ts` and `useProductionAgent.ts`)\n * to keep the copy in lockstep.\n *\n * `upgradeUrl` comes from the gateway response body and ends up interpolated\n * into markdown, so we validate it's a plain https URL with no characters\n * that would escape the `[...](url)` link target. Only `)` and whitespace\n * terminate the link target — `(`, `<`, `>` are fine inside it — so the\n * regex stays narrow; `buildUpgradeUrl` emits org-name URLs that may\n * contain `(` (e.g. `Acme%20(staging)`) and we don't want to reject them.\n */\nexport const BUILDER_SPACE_SETTINGS_URL = \"https://builder.io/account/space\";\n\nfunction isSafeUpgradeUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== \"https:\") return false;\n return !/[\\s)]/.test(url);\n } catch {\n return false;\n }\n}\n\nexport function formatChatErrorText(\n errorMessage: string,\n upgradeUrl?: string,\n errorCode?: string,\n): string {\n const normalized = normalizeChatError(errorMessage);\n if (\n errorCode === \"gateway_not_enabled\" ||\n /space has not enabled the LLM gateway/i.test(normalized.message)\n ) {\n return `Error: ${normalized.message}\\n\\n[Open Builder space settings](${BUILDER_SPACE_SETTINGS_URL})`;\n }\n if (!upgradeUrl || !isSafeUpgradeUrl(upgradeUrl)) {\n return `Error: ${normalized.message}`;\n }\n return `Error: ${normalized.message}\\n\\n[Upgrade at builder.io](${upgradeUrl})`;\n}\n\nexport interface NormalizedChatError {\n message: string;\n details?: string;\n}\n\nexport function normalizeChatError(errorMessage: string): NormalizedChatError {\n const raw = String(errorMessage || \"Unknown error\");\n const looksHtml = /<html[\\s>]|<body[\\s>]|<head[\\s>]/i.test(raw);\n const text = looksHtml ? htmlToText(raw) : raw.trim();\n\n if (/^Gateway error \\(no detail; raw event:/i.test(text)) {\n // The previous copy promised auto-recovery and suggested switching models,\n // but neither helps for this code: the server already retried once and\n // the client deliberately skips auto-continuation\n // (see `builder_gateway_error` in sse-event-processor.ts). The error is\n // almost always upstream, so retrying the same conversation with a\n // different model lands on the same wall.\n return {\n message:\n \"The model gateway returned no error details and the chat couldn't recover. Wait a moment and retry, or start a new chat if it keeps happening.\",\n details: text,\n };\n }\n\n if (/inactivity timeout/i.test(text)) {\n return {\n message:\n \"The agent connection timed out before it could finish. You can continue from the partial work or retry.\",\n details: text,\n };\n }\n\n if (/Invalid request body:\\s*tools\\.\\d+\\.input_schema\\.type/i.test(text)) {\n return {\n message:\n \"A tool schema was invalid, so the model rejected the request before it started. The invalid tool can be skipped and the request retried.\",\n details: text,\n };\n }\n\n if (looksHtml) {\n return {\n message:\n text.slice(0, 240) || \"The provider returned an HTML error page.\",\n details: text,\n };\n }\n\n return { message: text };\n}\n\nfunction htmlToText(html: string): string {\n return html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, \" \")\n .replace(/<style[\\s\\S]*?<\\/style>/gi, \" \")\n .replace(/<br\\s*\\/?>/gi, \"\\n\")\n .replace(/<\\/(p|div|h1|h2|h3|li|tr)>/gi, \"\\n\")\n .replace(/<[^>]+>/g, \" \")\n .replace(/ /gi, \" \")\n .replace(/</gi, \"<\")\n .replace(/>/gi, \">\")\n .replace(/&/gi, \"&\")\n .replace(/"/gi, '\"')\n .replace(/'/gi, \"'\")\n .replace(/[ \\t]+/g, \" \")\n .replace(/\\n\\s+/g, \"\\n\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/mcp-client/manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,aAAa,CAAC;AAG9D,eAAO,MAAM,eAAe,UAAU,CAAC;AAEvC,MAAM,WAAW,OAAO;IACtB,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAyBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,GACnB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAS/C;AAED,MAAM,WAAW,uBAAuB;IACtC,iCAAiC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAwDD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAC/D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;IAChC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,GAAG,CAA2B;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;IACxD;6EACyE;IACzE,OAAO,CAAC,gBAAgB,CAAuC;gBAEnD,MAAM,EAAE,SAAS,GAAG,IAAI,EAAE,OAAO,GAAE,uBAA4B;IAK3E,wDAAwD;IACxD,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;+EAC2E;IAC3E,SAAS,IAAI,SAAS,GAAG,IAAI;IAI7B,wEAAwE;IACxE,IAAI,iBAAiB,IAAI,MAAM,EAAE,CAGhC;IAED,2EAA2E;IAC3E,IAAI,gBAAgB,IAAI,MAAM,EAAE,CAI/B;IAED;;;OAGG;YACW,OAAO;IA+CrB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAO1C,OAAO,CAAC,UAAU;IAYlB;;;;;;;OAOG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAQd,aAAa;IAkB3B;;;OAGG;YACW,SAAS;YA6BT,aAAa;
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/mcp-client/manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,aAAa,CAAC;AAG9D,eAAO,MAAM,eAAe,UAAU,CAAC;AAEvC,MAAM,WAAW,OAAO;IACtB,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAyBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,GACnB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAS/C;AAED,MAAM,WAAW,uBAAuB;IACtC,iCAAiC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAwDD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAC/D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;IAChC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,GAAG,CAA2B;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;IACxD;6EACyE;IACzE,OAAO,CAAC,gBAAgB,CAAuC;gBAEnD,MAAM,EAAE,SAAS,GAAG,IAAI,EAAE,OAAO,GAAE,uBAA4B;IAK3E,wDAAwD;IACxD,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;+EAC2E;IAC3E,SAAS,IAAI,SAAS,GAAG,IAAI;IAI7B,wEAAwE;IACxE,IAAI,iBAAiB,IAAI,MAAM,EAAE,CAGhC;IAED,2EAA2E;IAC3E,IAAI,gBAAgB,IAAI,MAAM,EAAE,CAI/B;IAED;;;OAGG;YACW,OAAO;IA+CrB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAO1C,OAAO,CAAC,UAAU;IAYlB;;;;;;;OAOG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAQd,aAAa;IAkB3B;;;OAGG;YACW,SAAS;YA6BT,aAAa;IAuH3B;;;;;;;;;;;OAWG;IACG,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC;QACtD,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,WAAW,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;YAUY,mBAAmB;IAyEjC,wDAAwD;IACxD,QAAQ,IAAI,OAAO,EAAE;IASrB;;;OAGG;IACG,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAiCrE,yDAAyD;IACnD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB3B,+DAA+D;IAC/D,SAAS,IAAI;QACX,iBAAiB,EAAE,MAAM,EAAE,CAAC;QAC5B,gBAAgB,EAAE,MAAM,EAAE,CAAC;QAC3B,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,KAAK,CAAC;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACpE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAChC;CAkBF"}
|
|
@@ -287,6 +287,16 @@ export class McpClientManager {
|
|
|
287
287
|
const restoreClientClose = guardClose(client, recordConnectionError);
|
|
288
288
|
const restoreTransportClose = guardClose(transport, recordConnectionError);
|
|
289
289
|
client.onerror = recordConnectionError;
|
|
290
|
+
// Attach a transport-level error handler before connect() so the SDK's
|
|
291
|
+
// internal fire-and-forget paths (initial SSE stream open, scheduled
|
|
292
|
+
// reconnects, message-handler-triggered reconnects — see processStream()
|
|
293
|
+
// in @modelcontextprotocol/sdk/client/streamableHttp.js) cannot leak as
|
|
294
|
+
// unhandled promise rejections. On AWS Lambda the long-lived socket
|
|
295
|
+
// gets reaped ~60s after the function returns; without this handler the
|
|
296
|
+
// resulting `socket hang up` surfaces as an unhandledRejection and
|
|
297
|
+
// pollutes Sentry. Client.connect() chains its own onerror on top of
|
|
298
|
+
// ours (see protocol.js: const _onerror = transport.onerror; ...).
|
|
299
|
+
transport.onerror = recordConnectionError;
|
|
290
300
|
// If connect or listTools throws, we still need to release the child
|
|
291
301
|
// process (stdio) or pending HTTP session — otherwise repeated failures
|
|
292
302
|
// leak transports. Assign to the entry only after the handshake succeeds.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/mcp-client/manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAC;AA0BvC,SAAS,MAAM;IACb,OAAO,CACL,OAAO,OAAO,KAAK,WAAW;QAC9B,CAAC,CAAE,OAAe,CAAC,QAAQ,EAAE,IAAI;QACjC,OAAQ,OAAe,CAAC,QAAQ,CAAC,IAAI,KAAK,QAAQ,CACnD,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAgB,EAAE,QAAgB;IAC3D,OAAO,GAAG,eAAe,GAAG,QAAQ,KAAK,QAAQ,EAAE,CAAC;AACtD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,YAAoB;IAEpB,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,eAAe,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3D,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IACxD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,GAAG,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACzB,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;QAC5B,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC;KAC9B,CAAC;AACJ,CAAC;AAOD,SAAS,gBAAgB,CAAC,CAAkB,EAAE,CAAkB;IAC9D,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC;IAChC,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC;IAChC,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IAClC,IAAI,KAAK,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC/D,OAAO,CACL,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG;YACf,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CACpE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC3C,OAAO,CACL,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO;YACvB,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;YAC7D,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC;YAC3D,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAChC,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,KAAU,EAAE,WAAuB;IAC5D,IAAI,CAAC;QACH,IAAI,KAAK,EAAE,KAAK;YAAE,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;IACxC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CACjB,KAAU,EACV,WAAsB;IAEtB,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,UAAU;QAAE,OAAO,SAAS,CAAC;IAClE,MAAM,aAAa,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9C,KAAK,CAAC,KAAK,GAAG,KAAK,EAAE,GAAG,IAAe,EAAE,EAAE;QACzC,IAAI,CAAC;YACH,OAAO,MAAM,aAAa,CAAC,GAAG,IAAI,CAAC,CAAC;QACtC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,WAAW,CAAC,GAAG,CAAC,CAAC;YACjB,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;IACF,OAAO,GAAG,EAAE;QACV,KAAK,CAAC,KAAK,GAAG,aAAa,CAAC;IAC9B,CAAC,CAAC;AACJ,CAAC;AAQD,MAAM,OAAO,gBAAgB;IACV,OAAO,GAA6B,IAAI,GAAG,EAAE,CAAC;IAC9C,KAAK,CAAU;IACxB,OAAO,GAAG,KAAK,CAAC;IAChB,MAAM,CAAmB;IACzB,GAAG,GAAsB,IAAI,CAAC;IACrB,SAAS,GAAoB,IAAI,GAAG,EAAE,CAAC;IACxD;6EACyE;IACjE,gBAAgB,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;IAE/D,YAAY,MAAwB,EAAE,UAAmC,EAAE;QACzE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;IAC/B,CAAC;IAED,wDAAwD;IACxD,IAAI,OAAO;QACT,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IACtE,CAAC;IAED;+EAC2E;IAC3E,SAAS;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,wEAAwE;IACxE,IAAI,iBAAiB;QACnB,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,CAAC;QAC5B,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED,2EAA2E;IAC3E,IAAI,gBAAgB;QAClB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;aACrC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;aACnC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACtB,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,OAAO,CAAC,SAAkB;QACtC,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACb,iEAAiE;YACjE,IAAI,SAAS,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,oBAAoB,IAAI,MAAM,EAAE,EAAE,CAAC;gBAC5D,IAAI,CAAC;oBACH,MAAM,QAAQ,GACZ,MAAM,MAAM,CAAC,2CAA2C,CAAC,CAAC;oBAC5D,IAAI,CAAC,GAAG,CAAC,oBAAoB,GAAG,QAAQ,CAAC,oBAAoB,CAAC;gBAChE,CAAC;gBAAC,OAAO,GAAQ,EAAE,CAAC;oBAClB,OAAO,CAAC,IAAI,CACV,gDAAgD,GAAG,EAAE,OAAO,IAAI,GAAG,GAAG,CACvE,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,OAAO,IAAI,CAAC,GAAG,CAAC;QAClB,CAAC;QACD,IAAI,CAAC;YACH,MAAM,SAAS,GACb,MAAM,MAAM,CAAC,2CAA2C,CAAC,CAAC;YAC5D,MAAM,OAAO,GACX,MAAM,MAAM,CAAC,oDAAoD,CAAC,CAAC;YACrE,IAAI,oBAAoB,GAAQ,IAAI,CAAC;YACrC,IAAI,SAAS,IAAI,MAAM,EAAE,EAAE,CAAC;gBAC1B,IAAI,CAAC;oBACH,MAAM,QAAQ,GACZ,MAAM,MAAM,CAAC,2CAA2C,CAAC,CAAC;oBAC5D,oBAAoB,GAAG,QAAQ,CAAC,oBAAoB,CAAC;gBACvD,CAAC;gBAAC,OAAO,GAAQ,EAAE,CAAC;oBAClB,OAAO,CAAC,IAAI,CACV,gDAAgD,GAAG,EAAE,OAAO,IAAI,GAAG,GAAG,CACvE,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,IAAI,CAAC,GAAG,GAAG;gBACT,MAAM,EAAE,SAAS,CAAC,MAAM;gBACxB,oBAAoB;gBACpB,6BAA6B,EAAE,OAAO,CAAC,6BAA6B;aACrE,CAAC;YACF,OAAO,IAAI,CAAC,GAAG,CAAC;QAClB,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,OAAO,CAAC,IAAI,CACV,wCAAwC,GAAG,EAAE,OAAO,IAAI,GAAG,uBAAuB,CACnF,CAAC;YACF,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,QAAQ,CAAC,QAAoB;QAC3B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClC,CAAC,CAAC;IACJ,CAAC;IAEO,UAAU;QAChB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAC/B,IAAI,CAAC;gBACH,CAAC,EAAE,CAAC;YACN,CAAC;YAAC,OAAO,GAAQ,EAAE,CAAC;gBAClB,OAAO,CAAC,IAAI,CACV,yCAAyC,GAAG,EAAE,OAAO,IAAI,GAAG,EAAE,CAC/D,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;QACpE,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE;YACtC,sDAAsD;QACxD,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,KAAK,CAAC,aAAa;QACzB,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAE1B,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CACxD,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,KAAK,OAAO,CAC3C,CAAC;QACF,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC,GAAG;YAAE,OAAO;QAEjB,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAO,CAAC,OAAO,CAAC,CAAC;QACrD,MAAM,OAAO,CAAC,GAAG,CACf,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAC/D,CAAC;QACF,IAAI,CAAC,UAAU,EAAE,CAAC;IACpB,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,SAAS,CACrB,EAAU,EACV,GAAoB,EACpB,GAAe;QAEf,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,IAAI,CACV,qCAAqC,EAAE,uCAAuC,CAC/E,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,GAAgB;YACzB,EAAE;YACF,MAAM,EAAE,GAAG;YACX,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,IAAI;YACf,KAAK,EAAE,EAAE;SACV,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAC5B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YACrC,OAAO,CAAC,GAAG,CACT,6BAA6B,EAAE,KAAK,KAAK,CAAC,KAAK,CAAC,MAAM,QAAQ,CAC/D,CAAC;QACJ,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,KAAK,CAAC,KAAK,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;YACzC,OAAO,CAAC,IAAI,CAAC,qCAAqC,EAAE,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,aAAa,CACzB,KAAkB,EAClB,GAAe;QAEf,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC;QACzB,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;QAEvB,IAAI,SAAc,CAAC;QACnB,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACxB,IAAI,CAAC,GAAG,CAAC,6BAA6B,EAAE,CAAC;gBACvC,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;YAClD,CAAC;YACD,MAAM,WAAW,GAA4B,EAAE,CAAC;YAChD,IAAI,GAAG,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvD,WAAW,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;YACpC,CAAC;YACD,SAAS,GAAG,IAAI,GAAG,CAAC,6BAA6B,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;gBAClE,WAAW;aACZ,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,GAAG,CAAC,oBAAoB,EAAE,CAAC;gBAC9B,MAAM,IAAI,KAAK,CACb,iEAAiE,CAClE,CAAC;YACJ,CAAC;YACD,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC;YAC7C,kEAAkE;YAClE,6DAA6D;YAC7D,+DAA+D;YAC/D,kEAAkE;YAClE,2DAA2D;YAC3D,iEAAiE;YACjE,+DAA+D;YAC/D,6DAA6D;YAC7D,0DAA0D;YAC1D,MAAM,aAAa,GAAG;gBACpB,MAAM;gBACN,MAAM;gBACN,QAAQ;gBACR,MAAM;gBACN,QAAQ;gBACR,MAAM;gBACN,OAAO;aACR,CAAC;YACF,MAAM,QAAQ,GAA2B,EAAE,CAAC;YAC5C,KAAK,MAAM,CAAC,IAAI,aAAa,EAAE,CAAC;gBAC9B,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBACzB,IAAI,OAAO,CAAC,KAAK,QAAQ;oBAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAC7C,CAAC;YACD,MAAM,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;YAC3D,SAAS,GAAG,IAAI,GAAG,CAAC,oBAAoB,CAAC;gBACvC,OAAO;gBACP,IAAI;gBACJ,GAAG,EAAE,SAAmC;gBACxC,GAAG;aACJ,CAAC,CAAC;QACL,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,OAAO,EAAE,EACrD,EAAE,YAAY,EAAE,EAAE,EAAE,CACrB,CAAC;QACF,MAAM,qBAAqB,GAAc,GAAG,EAAE,GAAE,CAAC,CAAC;QAClD,MAAM,kBAAkB,GAAG,UAAU,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;QACrE,MAAM,qBAAqB,GAAG,UAAU,CAAC,SAAS,EAAE,qBAAqB,CAAC,CAAC;QAC3E,MAAM,CAAC,OAAO,GAAG,qBAAqB,CAAC;QAEvC,qEAAqE;QACrE,wEAAwE;QACxE,0EAA0E;QAC1E,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YAChC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC;YACxC,MAAM,QAAQ,GAIT,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE,CAAU,CAAC;YAEpC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;YACtB,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC;YAC5B,KAAK,CAAC,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACjC,MAAM,EAAE,KAAK,CAAC,EAAE;gBAChB,IAAI,EAAE,iBAAiB,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC;gBACzC,YAAY,EAAE,CAAC,CAAC,IAAI;gBACpB,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,IAAI;gBACpC,WAAW,EAAE,CAAC,CAAC,CAAC,WAAW,IAAI;oBAC7B,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE,EAAE;iBACf,CAA4B;aAC9B,CAAC,CAAC,CAAC;YACJ,MAAM,CAAC,OAAO,GAAG,CAAC,KAAc,EAAE,EAAE;gBAClC,KAAK,CAAC,KAAK,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC;gBAC3C,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;oBACf,OAAO,CAAC,IAAI,CACV,mCAAmC,KAAK,CAAC,EAAE,KAAK,KAAK,CAAC,KAAK,EAAE,CAC9D,CAAC;gBACJ,CAAC;YACH,CAAC,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,WAAW,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;YACjD,MAAM,WAAW,CAAC,SAAS,EAAE,qBAAqB,CAAC,CAAC;YACpD,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,kBAAkB,EAAE,EAAE,CAAC;YACvB,qBAAqB,EAAE,EAAE,CAAC;QAC5B,CAAC;IACH,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,WAAW,CAAC,SAA2B;QAM3C,MAAM,IAAI,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,EAAE,CAC3C,IAAI,CAAC,mBAAmB,CAAC,SAAS,CAAC,CACpC,CAAC;QACF,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE;YACtC,sDAAsD;QACxD,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,KAAK,CAAC,mBAAmB,CAAC,SAA2B;QAM3D,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QAExB,MAAM,WAAW,GAAG,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC;QACxC,MAAM,WAAW,GAAG,SAAS,EAAE,OAAO,IAAI,EAAE,CAAC;QAE7C,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,MAAM,WAAW,GAAa,EAAE,CAAC;QAEjC,iDAAiD;QACjD,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YAC1C,IAAI,CAAC,CAAC,EAAE,IAAI,WAAW,CAAC,EAAE,CAAC;gBACzB,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACnB,CAAC;iBAAM,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;gBAC/D,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACvB,CAAC;iBAAM,CAAC;gBACN,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;QACD,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YAC1C,IAAI,CAAC,CAAC,EAAE,IAAI,WAAW,CAAC;gBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3C,CAAC;QAED,MAAM,YAAY,GAAG,CAAC,GAAG,OAAO,EAAE,GAAG,WAAW,CAAC,CAAC;QAClD,MAAM,OAAO,CAAC,GAAG,CACf,YAAY,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;YAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACnC,IAAI,CAAC,KAAK;gBAAE,OAAO;YACnB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACxB,IAAI,CAAC;gBACH,IAAI,KAAK,CAAC,MAAM,EAAE,KAAK;oBAAE,MAAM,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACtD,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YACD,IAAI,CAAC;gBACH,IAAI,KAAK,CAAC,SAAS,EAAE,KAAK;oBAAE,MAAM,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;YAC5D,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;QACH,CAAC,CAAC,CACH,CAAC;QAEF,MAAM,SAAS,GAAG,CAAC,GAAG,KAAK,EAAE,GAAG,WAAW,CAAC,CAAC;QAC7C,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAC9B,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC,KAAK,OAAO,CACtD,CAAC;YACF,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YAC1C,IAAI,GAAG,EAAE,CAAC;gBACR,MAAM,OAAO,CAAC,GAAG,CACf,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,WAAW,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAChE,CAAC;YACJ,CAAC;QACH,CAAC;QAED,2EAA2E;QAC3E,6EAA6E;QAC7E,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,CAAC;QAED,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;IACpD,CAAC;IAED,wDAAwD;IACxD,QAAQ;QACN,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAc,EAAE,CAAC;QAC1B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1C,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK;gBAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjD,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,QAAQ,CAAC,YAAoB,EAAE,IAAa;QAChD,MAAM,MAAM,GAAG,gBAAgB,CAAC,YAAY,CAAC,CAAC;QAC9C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,cAAc,YAAY,mEAAmE,CAC9F,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CACb,eAAe,MAAM,CAAC,QAAQ,qBAC5B,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EACtC,EAAE,CACH,CAAC;QACJ,CAAC;QACD,2EAA2E;QAC3E,iCAAiC;QACjC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC;QAC/D,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CACb,eAAe,MAAM,CAAC,QAAQ,2BAA2B,MAAM,CAAC,QAAQ,GAAG,CAC5E,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;YACzC,IAAI,EAAE,MAAM,CAAC,QAAQ;YACrB,SAAS,EACP,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;gBAC9B,CAAC,CAAE,IAAgC;gBACnC,CAAC,CAAC,EAAE;SACT,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,yDAAyD;IACzD,KAAK,CAAC,IAAI;QACR,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAClD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,MAAM,OAAO,CAAC,GAAG,CACf,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YAC1B,IAAI,CAAC;gBACH,IAAI,KAAK,CAAC,MAAM,EAAE,KAAK;oBAAE,MAAM,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACtD,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YACD,IAAI,CAAC;gBACH,IAAI,KAAK,CAAC,SAAS,EAAE,KAAK;oBAAE,MAAM,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;YAC5D,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;QACH,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;IAED,+DAA+D;IAC/D,SAAS;QAOP,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACxC,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;SAC3B,CAAC,CAAC,CAAC;QACJ,MAAM,MAAM,GAA2B,EAAE,CAAC;QAC1C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1C,IAAI,KAAK,CAAC,KAAK;gBAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC;QAClD,CAAC;QACD,OAAO;YACL,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;YACzC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;YACvC,UAAU,EAAE,KAAK,CAAC,MAAM;YACxB,KAAK;YACL,MAAM;SACP,CAAC;IACJ,CAAC;CACF","sourcesContent":["/**\n * McpClientManager — connects to configured MCP servers (stdio or remote\n * Streamable HTTP), enumerates their tools, and exposes a flat tool registry\n * prefixed with `mcp__<server-id>__` so the agent's tool-use loop can call them.\n *\n * Stdio servers are a strict no-op in non-Node runtimes (Cloudflare Workers,\n * browsers). HTTP servers work in any runtime with `fetch`; `reconfigure()`\n * lets callers add or remove servers at runtime without restarting the process.\n */\n\nimport type { McpConfig, McpServerConfig } from \"./config.js\";\nimport { formatMcpConnectError } from \"./errors.js\";\n\nexport const MCP_TOOL_PREFIX = \"mcp__\";\n\nexport interface McpTool {\n /** Server id the tool belongs to */\n source: string;\n /** Prefixed tool name (e.g. \"mcp__claude-in-chrome__navigate\") */\n name: string;\n /** Original name as reported by the MCP server */\n originalName: string;\n /** Human-readable description */\n description: string;\n /** JSON-Schema input spec forwarded verbatim from the server */\n inputSchema: Record<string, unknown>;\n}\n\ninterface ServerEntry {\n id: string;\n config: McpServerConfig;\n client: any | null;\n transport: any | null;\n tools: McpTool[];\n error?: string;\n}\n\ntype ErrorSink = (error: unknown) => void;\n\nfunction isNode(): boolean {\n return (\n typeof process !== \"undefined\" &&\n !!(process as any).versions?.node &&\n typeof (process as any).versions.node === \"string\"\n );\n}\n\nfunction buildPrefixedName(serverId: string, toolName: string): string {\n return `${MCP_TOOL_PREFIX}${serverId}__${toolName}`;\n}\n\n/**\n * Parse a prefixed tool name back into its server id and original tool name.\n * Returns `null` if the name doesn't match the MCP prefix convention.\n */\nexport function parseMcpToolName(\n prefixedName: string,\n): { serverId: string; toolName: string } | null {\n if (!prefixedName.startsWith(MCP_TOOL_PREFIX)) return null;\n const rest = prefixedName.slice(MCP_TOOL_PREFIX.length);\n const idx = rest.indexOf(\"__\");\n if (idx < 0) return null;\n return {\n serverId: rest.slice(0, idx),\n toolName: rest.slice(idx + 2),\n };\n}\n\nexport interface McpClientManagerOptions {\n /** Emit debug logs on startup */\n debug?: boolean;\n}\n\nfunction sameServerConfig(a: McpServerConfig, b: McpServerConfig): boolean {\n const typeA = a.type ?? \"stdio\";\n const typeB = b.type ?? \"stdio\";\n if (typeA !== typeB) return false;\n if (typeA === \"http\" && b.type === \"http\" && a.type === \"http\") {\n return (\n a.url === b.url &&\n JSON.stringify(a.headers ?? {}) === JSON.stringify(b.headers ?? {})\n );\n }\n if (a.type !== \"http\" && b.type !== \"http\") {\n return (\n a.command === b.command &&\n JSON.stringify(a.args ?? []) === JSON.stringify(b.args ?? []) &&\n JSON.stringify(a.env ?? {}) === JSON.stringify(b.env ?? {}) &&\n (a.cwd ?? \"\") === (b.cwd ?? \"\")\n );\n }\n return false;\n}\n\nasync function safelyClose(value: any, recordError?: ErrorSink): Promise<void> {\n try {\n if (value?.close) await value.close();\n } catch (err) {\n recordError?.(err);\n }\n}\n\nfunction guardClose(\n value: any,\n recordError: ErrorSink,\n): (() => void) | undefined {\n if (!value || typeof value.close !== \"function\") return undefined;\n const originalClose = value.close.bind(value);\n value.close = async (...args: unknown[]) => {\n try {\n return await originalClose(...args);\n } catch (err) {\n recordError(err);\n return undefined;\n }\n };\n return () => {\n value.close = originalClose;\n };\n}\n\ntype SdkModules = {\n Client: any;\n StdioClientTransport: any | null;\n StreamableHTTPClientTransport: any | null;\n};\n\nexport class McpClientManager {\n private readonly servers: Map<string, ServerEntry> = new Map();\n private readonly debug: boolean;\n private started = false;\n private config: McpConfig | null;\n private sdk: SdkModules | null = null;\n private readonly listeners: Set<() => void> = new Set();\n /** Serialises reconfigure()/start() — two concurrent callers would\n * otherwise race on `this.config` and on connect/disconnect ordering. */\n private reconfigureQueue: Promise<unknown> = Promise.resolve();\n\n constructor(config: McpConfig | null, options: McpClientManagerOptions = {}) {\n this.config = config;\n this.debug = !!options.debug;\n }\n\n /** True when the manager has any configured servers. */\n get enabled(): boolean {\n return !!this.config && Object.keys(this.config.servers).length > 0;\n }\n\n /** Return the current config (read-only snapshot for callers that need to\n * merge new servers into the existing set before calling reconfigure). */\n getConfig(): McpConfig | null {\n return this.config;\n }\n\n /** List of configured server ids (whether or not they're connected). */\n get configuredServers(): string[] {\n if (!this.config) return [];\n return Object.keys(this.config.servers);\n }\n\n /** List of server ids that successfully connected and enumerated tools. */\n get connectedServers(): string[] {\n return Array.from(this.servers.values())\n .filter((s) => s.client && !s.error)\n .map((s) => s.id);\n }\n\n /**\n * Load MCP SDK modules lazily so non-Node bundles don't pull them in.\n * Stdio transport is only loaded when a stdio server is actually configured.\n */\n private async loadSdk(needStdio: boolean): Promise<SdkModules | null> {\n if (this.sdk) {\n // If we previously loaded without stdio and now need it, top up.\n if (needStdio && !this.sdk.StdioClientTransport && isNode()) {\n try {\n const stdioMod =\n await import(\"@modelcontextprotocol/sdk/client/stdio.js\");\n this.sdk.StdioClientTransport = stdioMod.StdioClientTransport;\n } catch (err: any) {\n console.warn(\n `[mcp-client] Failed to load stdio transport: ${err?.message ?? err}.`,\n );\n }\n }\n return this.sdk;\n }\n try {\n const clientMod =\n await import(\"@modelcontextprotocol/sdk/client/index.js\");\n const httpMod =\n await import(\"@modelcontextprotocol/sdk/client/streamableHttp.js\");\n let StdioClientTransport: any = null;\n if (needStdio && isNode()) {\n try {\n const stdioMod =\n await import(\"@modelcontextprotocol/sdk/client/stdio.js\");\n StdioClientTransport = stdioMod.StdioClientTransport;\n } catch (err: any) {\n console.warn(\n `[mcp-client] Failed to load stdio transport: ${err?.message ?? err}.`,\n );\n }\n }\n this.sdk = {\n Client: clientMod.Client,\n StdioClientTransport,\n StreamableHTTPClientTransport: httpMod.StreamableHTTPClientTransport,\n };\n return this.sdk;\n } catch (err: any) {\n console.warn(\n `[mcp-client] Failed to load MCP SDK: ${err?.message ?? err}. MCP tools disabled.`,\n );\n return null;\n }\n }\n\n /**\n * Subscribe to tool-set changes (e.g. after `reconfigure()` adds/removes\n * servers). The listener is called *after* connect/disconnect completes.\n * Returns an unsubscribe function.\n */\n onChange(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private emitChange(): void {\n for (const l of this.listeners) {\n try {\n l();\n } catch (err: any) {\n console.warn(\n `[mcp-client] onChange listener threw: ${err?.message ?? err}`,\n );\n }\n }\n }\n\n /**\n * Connect to each configured MCP server (stdio or http) and enumerate tools.\n * Individual server failures are logged and skipped — the manager stays\n * usable with whichever servers did come up.\n *\n * Queued against `reconfigure()` so a `reconfigure` that lands before\n * `start()` finishes can't race on `this.started` / `this.servers`.\n */\n async start(): Promise<void> {\n const task = this.reconfigureQueue.then(() => this.startInternal());\n this.reconfigureQueue = task.catch(() => {\n /* failures surface on the caller, not on the queue */\n });\n return task;\n }\n\n private async startInternal(): Promise<void> {\n if (this.started) return;\n this.started = true;\n if (!this.enabled) return;\n\n const needStdio = Object.values(this.config!.servers).some(\n (cfg) => (cfg.type ?? \"stdio\") === \"stdio\",\n );\n const sdk = await this.loadSdk(needStdio);\n if (!sdk) return;\n\n const entries = Object.entries(this.config!.servers);\n await Promise.all(\n entries.map(async ([id, cfg]) => this.addServer(id, cfg, sdk)),\n );\n this.emitChange();\n }\n\n /**\n * Create a new ServerEntry and attempt to connect. Logs and records errors\n * on the entry rather than throwing — callers iterate many servers.\n */\n private async addServer(\n id: string,\n cfg: McpServerConfig,\n sdk: SdkModules,\n ): Promise<void> {\n if (this.servers.has(id)) {\n console.warn(\n `[mcp-client] Duplicate server ID '${id}' — overwriting previous registration`,\n );\n }\n const entry: ServerEntry = {\n id,\n config: cfg,\n client: null,\n transport: null,\n tools: [],\n };\n this.servers.set(id, entry);\n try {\n await this.connectServer(entry, sdk);\n console.log(\n `[mcp-client] connected to ${id}: ${entry.tools.length} tools`,\n );\n } catch (err: any) {\n entry.error = formatMcpConnectError(err);\n console.warn(`[mcp-client] failed to connect to ${id}: ${entry.error}`);\n }\n }\n\n private async connectServer(\n entry: ServerEntry,\n sdk: SdkModules,\n ): Promise<void> {\n const cfg = entry.config;\n const { Client } = sdk;\n\n let transport: any;\n if (cfg.type === \"http\") {\n if (!sdk.StreamableHTTPClientTransport) {\n throw new Error(\"HTTP transport not available\");\n }\n const requestInit: Record<string, unknown> = {};\n if (cfg.headers && Object.keys(cfg.headers).length > 0) {\n requestInit.headers = cfg.headers;\n }\n transport = new sdk.StreamableHTTPClientTransport(new URL(cfg.url), {\n requestInit,\n });\n } else {\n if (!sdk.StdioClientTransport) {\n throw new Error(\n \"Stdio transport not available (needs Node runtime with MCP SDK)\",\n );\n }\n const { command, args = [], env, cwd } = cfg;\n // SECURITY: stdio MCP servers run as child processes that inherit\n // their environment from us. We previously merged the entire\n // `process.env` into the child, which exposed every deployment\n // secret (A2A_SECRET, ANTHROPIC_API_KEY, BUILDER_PRIVATE_KEY, all\n // database URLs, all platform tokens) to any MCP server in\n // `mcp.config.json` — a malicious npx-fetched server could exfil\n // them by reading its own env. Instead, only forward a minimal\n // baseline plus the keys explicitly listed in `cfg.env`. See\n // finding #10 in /tmp/security-audit/12-mcp-a2a-agent.md.\n const ENV_ALLOWLIST = [\n \"PATH\",\n \"HOME\",\n \"TMPDIR\",\n \"LANG\",\n \"LC_ALL\",\n \"USER\",\n \"SHELL\",\n ];\n const baseline: Record<string, string> = {};\n for (const k of ENV_ALLOWLIST) {\n const v = process.env[k];\n if (typeof v === \"string\") baseline[k] = v;\n }\n const mergedEnv = env ? { ...baseline, ...env } : baseline;\n transport = new sdk.StdioClientTransport({\n command,\n args,\n env: mergedEnv as Record<string, string>,\n cwd,\n });\n }\n\n const client = new Client(\n { name: \"agent-native-mcp-client\", version: \"1.0.0\" },\n { capabilities: {} },\n );\n const recordConnectionError: ErrorSink = () => {};\n const restoreClientClose = guardClose(client, recordConnectionError);\n const restoreTransportClose = guardClose(transport, recordConnectionError);\n client.onerror = recordConnectionError;\n\n // If connect or listTools throws, we still need to release the child\n // process (stdio) or pending HTTP session — otherwise repeated failures\n // leak transports. Assign to the entry only after the handshake succeeds.\n try {\n await client.connect(transport);\n const listed = await client.listTools();\n const rawTools: Array<{\n name: string;\n description?: string;\n inputSchema?: Record<string, unknown>;\n }> = (listed?.tools ?? []) as any[];\n\n entry.client = client;\n entry.transport = transport;\n entry.tools = rawTools.map((t) => ({\n source: entry.id,\n name: buildPrefixedName(entry.id, t.name),\n originalName: t.name,\n description: t.description ?? t.name,\n inputSchema: (t.inputSchema ?? {\n type: \"object\",\n properties: {},\n }) as Record<string, unknown>,\n }));\n client.onerror = (error: unknown) => {\n entry.error = formatMcpConnectError(error);\n if (this.debug) {\n console.warn(\n `[mcp-client] runtime error from ${entry.id}: ${entry.error}`,\n );\n }\n };\n } catch (err) {\n await safelyClose(client, recordConnectionError);\n await safelyClose(transport, recordConnectionError);\n throw err;\n } finally {\n restoreClientClose?.();\n restoreTransportClose?.();\n }\n }\n\n /**\n * Replace the configured server set. Servers that appear in the new config\n * under a different shape are reconnected; unchanged entries stay live;\n * removed entries are disconnected. Safe to call while `start()` is in\n * flight or after it has completed.\n *\n * Serialised against `start()` and any other `reconfigure()` call via the\n * internal queue — two concurrent mutations would otherwise interleave on\n * `this.config` and on connect/disconnect ordering.\n *\n * Returns a summary describing what happened for logging / UI feedback.\n */\n async reconfigure(newConfig: McpConfig | null): Promise<{\n added: string[];\n removed: string[];\n unchanged: string[];\n reconnected: string[];\n }> {\n const task = this.reconfigureQueue.then(() =>\n this.reconfigureInternal(newConfig),\n );\n this.reconfigureQueue = task.catch(() => {\n /* failures surface on the caller, not on the queue */\n });\n return task;\n }\n\n private async reconfigureInternal(newConfig: McpConfig | null): Promise<{\n added: string[];\n removed: string[];\n unchanged: string[];\n reconnected: string[];\n }> {\n const prev = this.config;\n this.config = newConfig;\n\n const prevServers = prev?.servers ?? {};\n const nextServers = newConfig?.servers ?? {};\n\n const added: string[] = [];\n const removed: string[] = [];\n const unchanged: string[] = [];\n const reconnected: string[] = [];\n\n // Remove entries that vanished or changed shape.\n for (const id of Object.keys(prevServers)) {\n if (!(id in nextServers)) {\n removed.push(id);\n } else if (!sameServerConfig(prevServers[id], nextServers[id])) {\n reconnected.push(id);\n } else {\n unchanged.push(id);\n }\n }\n for (const id of Object.keys(nextServers)) {\n if (!(id in prevServers)) added.push(id);\n }\n\n const toDisconnect = [...removed, ...reconnected];\n await Promise.all(\n toDisconnect.map(async (id) => {\n const entry = this.servers.get(id);\n if (!entry) return;\n this.servers.delete(id);\n try {\n if (entry.client?.close) await entry.client.close();\n } catch {\n // ignore\n }\n try {\n if (entry.transport?.close) await entry.transport.close();\n } catch {\n // ignore\n }\n }),\n );\n\n const toConnect = [...added, ...reconnected];\n if (toConnect.length > 0) {\n const needStdio = toConnect.some(\n (id) => (nextServers[id].type ?? \"stdio\") === \"stdio\",\n );\n const sdk = await this.loadSdk(needStdio);\n if (sdk) {\n await Promise.all(\n toConnect.map((id) => this.addServer(id, nextServers[id], sdk)),\n );\n }\n }\n\n // If the manager was never started (e.g. empty initial config) but now has\n // servers, mark it started so subsequent start() calls don't duplicate work.\n if (!this.started && Object.keys(nextServers).length > 0) {\n this.started = true;\n }\n\n this.emitChange();\n return { added, removed, unchanged, reconnected };\n }\n\n /** Flattened tool list across all connected servers. */\n getTools(): McpTool[] {\n if (!this.enabled) return [];\n const out: McpTool[] = [];\n for (const entry of this.servers.values()) {\n for (const tool of entry.tools) out.push(tool);\n }\n return out;\n }\n\n /**\n * Invoke an MCP tool by prefixed name. Routes to the owning server based on\n * the `mcp__<serverId>__` prefix.\n */\n async callTool(prefixedName: string, args: unknown): Promise<unknown> {\n const parsed = parseMcpToolName(prefixedName);\n if (!parsed) {\n throw new Error(\n `Tool name \"${prefixedName}\" does not look like an MCP tool (expected mcp__<server>__<tool>)`,\n );\n }\n const entry = this.servers.get(parsed.serverId);\n if (!entry || !entry.client) {\n throw new Error(\n `MCP server \"${parsed.serverId}\" is not connected${\n entry?.error ? `: ${entry.error}` : \"\"\n }`,\n );\n }\n // Look up the tool so we fail loud for unknown names instead of forwarding\n // garbage through to the server.\n const known = entry.tools.find((t) => t.name === prefixedName);\n if (!known) {\n throw new Error(\n `MCP server \"${parsed.serverId}\" does not expose tool \"${parsed.toolName}\"`,\n );\n }\n const result = await entry.client.callTool({\n name: parsed.toolName,\n arguments:\n args && typeof args === \"object\"\n ? (args as Record<string, unknown>)\n : {},\n });\n return result;\n }\n\n /** Cleanly close all MCP clients and child processes. */\n async stop(): Promise<void> {\n const entries = Array.from(this.servers.values());\n this.servers.clear();\n this.started = false;\n await Promise.all(\n entries.map(async (entry) => {\n try {\n if (entry.client?.close) await entry.client.close();\n } catch {\n // ignore\n }\n try {\n if (entry.transport?.close) await entry.transport.close();\n } catch {\n // ignore\n }\n }),\n );\n }\n\n /** Diagnostic snapshot used by `/_agent-native/mcp/status`. */\n getStatus(): {\n configuredServers: string[];\n connectedServers: string[];\n totalTools: number;\n tools: Array<{ source: string; name: string; description: string }>;\n errors: Record<string, string>;\n } {\n const tools = this.getTools().map((t) => ({\n source: t.source,\n name: t.name,\n description: t.description,\n }));\n const errors: Record<string, string> = {};\n for (const entry of this.servers.values()) {\n if (entry.error) errors[entry.id] = entry.error;\n }\n return {\n configuredServers: this.configuredServers,\n connectedServers: this.connectedServers,\n totalTools: tools.length,\n tools,\n errors,\n };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/mcp-client/manager.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * McpClientManager — connects to configured MCP servers (stdio or remote\n * Streamable HTTP), enumerates their tools, and exposes a flat tool registry\n * prefixed with `mcp__<server-id>__` so the agent's tool-use loop can call them.\n *\n * Stdio servers are a strict no-op in non-Node runtimes (Cloudflare Workers,\n * browsers). HTTP servers work in any runtime with `fetch`; `reconfigure()`\n * lets callers add or remove servers at runtime without restarting the process.\n */\n\nimport type { McpConfig, McpServerConfig } from \"./config.js\";\nimport { formatMcpConnectError } from \"./errors.js\";\n\nexport const MCP_TOOL_PREFIX = \"mcp__\";\n\nexport interface McpTool {\n /** Server id the tool belongs to */\n source: string;\n /** Prefixed tool name (e.g. \"mcp__claude-in-chrome__navigate\") */\n name: string;\n /** Original name as reported by the MCP server */\n originalName: string;\n /** Human-readable description */\n description: string;\n /** JSON-Schema input spec forwarded verbatim from the server */\n inputSchema: Record<string, unknown>;\n}\n\ninterface ServerEntry {\n id: string;\n config: McpServerConfig;\n client: any | null;\n transport: any | null;\n tools: McpTool[];\n error?: string;\n}\n\ntype ErrorSink = (error: unknown) => void;\n\nfunction isNode(): boolean {\n return (\n typeof process !== \"undefined\" &&\n !!(process as any).versions?.node &&\n typeof (process as any).versions.node === \"string\"\n );\n}\n\nfunction buildPrefixedName(serverId: string, toolName: string): string {\n return `${MCP_TOOL_PREFIX}${serverId}__${toolName}`;\n}\n\n/**\n * Parse a prefixed tool name back into its server id and original tool name.\n * Returns `null` if the name doesn't match the MCP prefix convention.\n */\nexport function parseMcpToolName(\n prefixedName: string,\n): { serverId: string; toolName: string } | null {\n if (!prefixedName.startsWith(MCP_TOOL_PREFIX)) return null;\n const rest = prefixedName.slice(MCP_TOOL_PREFIX.length);\n const idx = rest.indexOf(\"__\");\n if (idx < 0) return null;\n return {\n serverId: rest.slice(0, idx),\n toolName: rest.slice(idx + 2),\n };\n}\n\nexport interface McpClientManagerOptions {\n /** Emit debug logs on startup */\n debug?: boolean;\n}\n\nfunction sameServerConfig(a: McpServerConfig, b: McpServerConfig): boolean {\n const typeA = a.type ?? \"stdio\";\n const typeB = b.type ?? \"stdio\";\n if (typeA !== typeB) return false;\n if (typeA === \"http\" && b.type === \"http\" && a.type === \"http\") {\n return (\n a.url === b.url &&\n JSON.stringify(a.headers ?? {}) === JSON.stringify(b.headers ?? {})\n );\n }\n if (a.type !== \"http\" && b.type !== \"http\") {\n return (\n a.command === b.command &&\n JSON.stringify(a.args ?? []) === JSON.stringify(b.args ?? []) &&\n JSON.stringify(a.env ?? {}) === JSON.stringify(b.env ?? {}) &&\n (a.cwd ?? \"\") === (b.cwd ?? \"\")\n );\n }\n return false;\n}\n\nasync function safelyClose(value: any, recordError?: ErrorSink): Promise<void> {\n try {\n if (value?.close) await value.close();\n } catch (err) {\n recordError?.(err);\n }\n}\n\nfunction guardClose(\n value: any,\n recordError: ErrorSink,\n): (() => void) | undefined {\n if (!value || typeof value.close !== \"function\") return undefined;\n const originalClose = value.close.bind(value);\n value.close = async (...args: unknown[]) => {\n try {\n return await originalClose(...args);\n } catch (err) {\n recordError(err);\n return undefined;\n }\n };\n return () => {\n value.close = originalClose;\n };\n}\n\ntype SdkModules = {\n Client: any;\n StdioClientTransport: any | null;\n StreamableHTTPClientTransport: any | null;\n};\n\nexport class McpClientManager {\n private readonly servers: Map<string, ServerEntry> = new Map();\n private readonly debug: boolean;\n private started = false;\n private config: McpConfig | null;\n private sdk: SdkModules | null = null;\n private readonly listeners: Set<() => void> = new Set();\n /** Serialises reconfigure()/start() — two concurrent callers would\n * otherwise race on `this.config` and on connect/disconnect ordering. */\n private reconfigureQueue: Promise<unknown> = Promise.resolve();\n\n constructor(config: McpConfig | null, options: McpClientManagerOptions = {}) {\n this.config = config;\n this.debug = !!options.debug;\n }\n\n /** True when the manager has any configured servers. */\n get enabled(): boolean {\n return !!this.config && Object.keys(this.config.servers).length > 0;\n }\n\n /** Return the current config (read-only snapshot for callers that need to\n * merge new servers into the existing set before calling reconfigure). */\n getConfig(): McpConfig | null {\n return this.config;\n }\n\n /** List of configured server ids (whether or not they're connected). */\n get configuredServers(): string[] {\n if (!this.config) return [];\n return Object.keys(this.config.servers);\n }\n\n /** List of server ids that successfully connected and enumerated tools. */\n get connectedServers(): string[] {\n return Array.from(this.servers.values())\n .filter((s) => s.client && !s.error)\n .map((s) => s.id);\n }\n\n /**\n * Load MCP SDK modules lazily so non-Node bundles don't pull them in.\n * Stdio transport is only loaded when a stdio server is actually configured.\n */\n private async loadSdk(needStdio: boolean): Promise<SdkModules | null> {\n if (this.sdk) {\n // If we previously loaded without stdio and now need it, top up.\n if (needStdio && !this.sdk.StdioClientTransport && isNode()) {\n try {\n const stdioMod =\n await import(\"@modelcontextprotocol/sdk/client/stdio.js\");\n this.sdk.StdioClientTransport = stdioMod.StdioClientTransport;\n } catch (err: any) {\n console.warn(\n `[mcp-client] Failed to load stdio transport: ${err?.message ?? err}.`,\n );\n }\n }\n return this.sdk;\n }\n try {\n const clientMod =\n await import(\"@modelcontextprotocol/sdk/client/index.js\");\n const httpMod =\n await import(\"@modelcontextprotocol/sdk/client/streamableHttp.js\");\n let StdioClientTransport: any = null;\n if (needStdio && isNode()) {\n try {\n const stdioMod =\n await import(\"@modelcontextprotocol/sdk/client/stdio.js\");\n StdioClientTransport = stdioMod.StdioClientTransport;\n } catch (err: any) {\n console.warn(\n `[mcp-client] Failed to load stdio transport: ${err?.message ?? err}.`,\n );\n }\n }\n this.sdk = {\n Client: clientMod.Client,\n StdioClientTransport,\n StreamableHTTPClientTransport: httpMod.StreamableHTTPClientTransport,\n };\n return this.sdk;\n } catch (err: any) {\n console.warn(\n `[mcp-client] Failed to load MCP SDK: ${err?.message ?? err}. MCP tools disabled.`,\n );\n return null;\n }\n }\n\n /**\n * Subscribe to tool-set changes (e.g. after `reconfigure()` adds/removes\n * servers). The listener is called *after* connect/disconnect completes.\n * Returns an unsubscribe function.\n */\n onChange(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private emitChange(): void {\n for (const l of this.listeners) {\n try {\n l();\n } catch (err: any) {\n console.warn(\n `[mcp-client] onChange listener threw: ${err?.message ?? err}`,\n );\n }\n }\n }\n\n /**\n * Connect to each configured MCP server (stdio or http) and enumerate tools.\n * Individual server failures are logged and skipped — the manager stays\n * usable with whichever servers did come up.\n *\n * Queued against `reconfigure()` so a `reconfigure` that lands before\n * `start()` finishes can't race on `this.started` / `this.servers`.\n */\n async start(): Promise<void> {\n const task = this.reconfigureQueue.then(() => this.startInternal());\n this.reconfigureQueue = task.catch(() => {\n /* failures surface on the caller, not on the queue */\n });\n return task;\n }\n\n private async startInternal(): Promise<void> {\n if (this.started) return;\n this.started = true;\n if (!this.enabled) return;\n\n const needStdio = Object.values(this.config!.servers).some(\n (cfg) => (cfg.type ?? \"stdio\") === \"stdio\",\n );\n const sdk = await this.loadSdk(needStdio);\n if (!sdk) return;\n\n const entries = Object.entries(this.config!.servers);\n await Promise.all(\n entries.map(async ([id, cfg]) => this.addServer(id, cfg, sdk)),\n );\n this.emitChange();\n }\n\n /**\n * Create a new ServerEntry and attempt to connect. Logs and records errors\n * on the entry rather than throwing — callers iterate many servers.\n */\n private async addServer(\n id: string,\n cfg: McpServerConfig,\n sdk: SdkModules,\n ): Promise<void> {\n if (this.servers.has(id)) {\n console.warn(\n `[mcp-client] Duplicate server ID '${id}' — overwriting previous registration`,\n );\n }\n const entry: ServerEntry = {\n id,\n config: cfg,\n client: null,\n transport: null,\n tools: [],\n };\n this.servers.set(id, entry);\n try {\n await this.connectServer(entry, sdk);\n console.log(\n `[mcp-client] connected to ${id}: ${entry.tools.length} tools`,\n );\n } catch (err: any) {\n entry.error = formatMcpConnectError(err);\n console.warn(`[mcp-client] failed to connect to ${id}: ${entry.error}`);\n }\n }\n\n private async connectServer(\n entry: ServerEntry,\n sdk: SdkModules,\n ): Promise<void> {\n const cfg = entry.config;\n const { Client } = sdk;\n\n let transport: any;\n if (cfg.type === \"http\") {\n if (!sdk.StreamableHTTPClientTransport) {\n throw new Error(\"HTTP transport not available\");\n }\n const requestInit: Record<string, unknown> = {};\n if (cfg.headers && Object.keys(cfg.headers).length > 0) {\n requestInit.headers = cfg.headers;\n }\n transport = new sdk.StreamableHTTPClientTransport(new URL(cfg.url), {\n requestInit,\n });\n } else {\n if (!sdk.StdioClientTransport) {\n throw new Error(\n \"Stdio transport not available (needs Node runtime with MCP SDK)\",\n );\n }\n const { command, args = [], env, cwd } = cfg;\n // SECURITY: stdio MCP servers run as child processes that inherit\n // their environment from us. We previously merged the entire\n // `process.env` into the child, which exposed every deployment\n // secret (A2A_SECRET, ANTHROPIC_API_KEY, BUILDER_PRIVATE_KEY, all\n // database URLs, all platform tokens) to any MCP server in\n // `mcp.config.json` — a malicious npx-fetched server could exfil\n // them by reading its own env. Instead, only forward a minimal\n // baseline plus the keys explicitly listed in `cfg.env`. See\n // finding #10 in /tmp/security-audit/12-mcp-a2a-agent.md.\n const ENV_ALLOWLIST = [\n \"PATH\",\n \"HOME\",\n \"TMPDIR\",\n \"LANG\",\n \"LC_ALL\",\n \"USER\",\n \"SHELL\",\n ];\n const baseline: Record<string, string> = {};\n for (const k of ENV_ALLOWLIST) {\n const v = process.env[k];\n if (typeof v === \"string\") baseline[k] = v;\n }\n const mergedEnv = env ? { ...baseline, ...env } : baseline;\n transport = new sdk.StdioClientTransport({\n command,\n args,\n env: mergedEnv as Record<string, string>,\n cwd,\n });\n }\n\n const client = new Client(\n { name: \"agent-native-mcp-client\", version: \"1.0.0\" },\n { capabilities: {} },\n );\n const recordConnectionError: ErrorSink = () => {};\n const restoreClientClose = guardClose(client, recordConnectionError);\n const restoreTransportClose = guardClose(transport, recordConnectionError);\n client.onerror = recordConnectionError;\n // Attach a transport-level error handler before connect() so the SDK's\n // internal fire-and-forget paths (initial SSE stream open, scheduled\n // reconnects, message-handler-triggered reconnects — see processStream()\n // in @modelcontextprotocol/sdk/client/streamableHttp.js) cannot leak as\n // unhandled promise rejections. On AWS Lambda the long-lived socket\n // gets reaped ~60s after the function returns; without this handler the\n // resulting `socket hang up` surfaces as an unhandledRejection and\n // pollutes Sentry. Client.connect() chains its own onerror on top of\n // ours (see protocol.js: const _onerror = transport.onerror; ...).\n transport.onerror = recordConnectionError;\n\n // If connect or listTools throws, we still need to release the child\n // process (stdio) or pending HTTP session — otherwise repeated failures\n // leak transports. Assign to the entry only after the handshake succeeds.\n try {\n await client.connect(transport);\n const listed = await client.listTools();\n const rawTools: Array<{\n name: string;\n description?: string;\n inputSchema?: Record<string, unknown>;\n }> = (listed?.tools ?? []) as any[];\n\n entry.client = client;\n entry.transport = transport;\n entry.tools = rawTools.map((t) => ({\n source: entry.id,\n name: buildPrefixedName(entry.id, t.name),\n originalName: t.name,\n description: t.description ?? t.name,\n inputSchema: (t.inputSchema ?? {\n type: \"object\",\n properties: {},\n }) as Record<string, unknown>,\n }));\n client.onerror = (error: unknown) => {\n entry.error = formatMcpConnectError(error);\n if (this.debug) {\n console.warn(\n `[mcp-client] runtime error from ${entry.id}: ${entry.error}`,\n );\n }\n };\n } catch (err) {\n await safelyClose(client, recordConnectionError);\n await safelyClose(transport, recordConnectionError);\n throw err;\n } finally {\n restoreClientClose?.();\n restoreTransportClose?.();\n }\n }\n\n /**\n * Replace the configured server set. Servers that appear in the new config\n * under a different shape are reconnected; unchanged entries stay live;\n * removed entries are disconnected. Safe to call while `start()` is in\n * flight or after it has completed.\n *\n * Serialised against `start()` and any other `reconfigure()` call via the\n * internal queue — two concurrent mutations would otherwise interleave on\n * `this.config` and on connect/disconnect ordering.\n *\n * Returns a summary describing what happened for logging / UI feedback.\n */\n async reconfigure(newConfig: McpConfig | null): Promise<{\n added: string[];\n removed: string[];\n unchanged: string[];\n reconnected: string[];\n }> {\n const task = this.reconfigureQueue.then(() =>\n this.reconfigureInternal(newConfig),\n );\n this.reconfigureQueue = task.catch(() => {\n /* failures surface on the caller, not on the queue */\n });\n return task;\n }\n\n private async reconfigureInternal(newConfig: McpConfig | null): Promise<{\n added: string[];\n removed: string[];\n unchanged: string[];\n reconnected: string[];\n }> {\n const prev = this.config;\n this.config = newConfig;\n\n const prevServers = prev?.servers ?? {};\n const nextServers = newConfig?.servers ?? {};\n\n const added: string[] = [];\n const removed: string[] = [];\n const unchanged: string[] = [];\n const reconnected: string[] = [];\n\n // Remove entries that vanished or changed shape.\n for (const id of Object.keys(prevServers)) {\n if (!(id in nextServers)) {\n removed.push(id);\n } else if (!sameServerConfig(prevServers[id], nextServers[id])) {\n reconnected.push(id);\n } else {\n unchanged.push(id);\n }\n }\n for (const id of Object.keys(nextServers)) {\n if (!(id in prevServers)) added.push(id);\n }\n\n const toDisconnect = [...removed, ...reconnected];\n await Promise.all(\n toDisconnect.map(async (id) => {\n const entry = this.servers.get(id);\n if (!entry) return;\n this.servers.delete(id);\n try {\n if (entry.client?.close) await entry.client.close();\n } catch {\n // ignore\n }\n try {\n if (entry.transport?.close) await entry.transport.close();\n } catch {\n // ignore\n }\n }),\n );\n\n const toConnect = [...added, ...reconnected];\n if (toConnect.length > 0) {\n const needStdio = toConnect.some(\n (id) => (nextServers[id].type ?? \"stdio\") === \"stdio\",\n );\n const sdk = await this.loadSdk(needStdio);\n if (sdk) {\n await Promise.all(\n toConnect.map((id) => this.addServer(id, nextServers[id], sdk)),\n );\n }\n }\n\n // If the manager was never started (e.g. empty initial config) but now has\n // servers, mark it started so subsequent start() calls don't duplicate work.\n if (!this.started && Object.keys(nextServers).length > 0) {\n this.started = true;\n }\n\n this.emitChange();\n return { added, removed, unchanged, reconnected };\n }\n\n /** Flattened tool list across all connected servers. */\n getTools(): McpTool[] {\n if (!this.enabled) return [];\n const out: McpTool[] = [];\n for (const entry of this.servers.values()) {\n for (const tool of entry.tools) out.push(tool);\n }\n return out;\n }\n\n /**\n * Invoke an MCP tool by prefixed name. Routes to the owning server based on\n * the `mcp__<serverId>__` prefix.\n */\n async callTool(prefixedName: string, args: unknown): Promise<unknown> {\n const parsed = parseMcpToolName(prefixedName);\n if (!parsed) {\n throw new Error(\n `Tool name \"${prefixedName}\" does not look like an MCP tool (expected mcp__<server>__<tool>)`,\n );\n }\n const entry = this.servers.get(parsed.serverId);\n if (!entry || !entry.client) {\n throw new Error(\n `MCP server \"${parsed.serverId}\" is not connected${\n entry?.error ? `: ${entry.error}` : \"\"\n }`,\n );\n }\n // Look up the tool so we fail loud for unknown names instead of forwarding\n // garbage through to the server.\n const known = entry.tools.find((t) => t.name === prefixedName);\n if (!known) {\n throw new Error(\n `MCP server \"${parsed.serverId}\" does not expose tool \"${parsed.toolName}\"`,\n );\n }\n const result = await entry.client.callTool({\n name: parsed.toolName,\n arguments:\n args && typeof args === \"object\"\n ? (args as Record<string, unknown>)\n : {},\n });\n return result;\n }\n\n /** Cleanly close all MCP clients and child processes. */\n async stop(): Promise<void> {\n const entries = Array.from(this.servers.values());\n this.servers.clear();\n this.started = false;\n await Promise.all(\n entries.map(async (entry) => {\n try {\n if (entry.client?.close) await entry.client.close();\n } catch {\n // ignore\n }\n try {\n if (entry.transport?.close) await entry.transport.close();\n } catch {\n // ignore\n }\n }),\n );\n }\n\n /** Diagnostic snapshot used by `/_agent-native/mcp/status`. */\n getStatus(): {\n configuredServers: string[];\n connectedServers: string[];\n totalTools: number;\n tools: Array<{ source: string; name: string; description: string }>;\n errors: Record<string, string>;\n } {\n const tools = this.getTools().map((t) => ({\n source: t.source,\n name: t.name,\n description: t.description,\n }));\n const errors: Record<string, string> = {};\n for (const entry of this.servers.values()) {\n if (entry.error) errors[entry.id] = entry.error;\n }\n return {\n configuredServers: this.configuredServers,\n connectedServers: this.connectedServers,\n totalTools: tools.length,\n tools,\n errors,\n };\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sentry.d.ts","sourceRoot":"","sources":["../../src/server/sentry.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAwC7C;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,
|
|
1
|
+
{"version":3,"file":"sentry.d.ts","sourceRoot":"","sources":["../../src/server/sentry.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAwC7C;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CA+J1C;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAsBzE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,IAAI,CAgBP;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,OAAO,EAAE;IAAE,KAAK,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAChE,MAAM,GAAG,SAAS,CAcpB;AAED,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC1C;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACpD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,iBAAsB,GAC9B,MAAM,GAAG,SAAS,CA2BpB"}
|
package/dist/server/sentry.js
CHANGED
|
@@ -106,6 +106,26 @@ export function initServerSentry() {
|
|
|
106
106
|
exceptionType === "UnauthorizedError") {
|
|
107
107
|
return null;
|
|
108
108
|
}
|
|
109
|
+
// Drop "socket hang up" unhandled promise rejections that fire from
|
|
110
|
+
// Lambda freeze cycles. AWS Lambda recycles long-lived sockets (e.g.
|
|
111
|
+
// MCP Streamable HTTP long-polls, keep-alive HTTP agents) ~60s after
|
|
112
|
+
// a function returns 200; the next thaw delivers a socket-end event
|
|
113
|
+
// whose Promise has nobody left to await it. The function itself
|
|
114
|
+
// already returned correctly, so there's no user impact — just
|
|
115
|
+
// ~10k events/day of noise. The narrow shape (unhandled rejection
|
|
116
|
+
// from `Socket.socketOnEnd` in `node:_http_client`) keeps real
|
|
117
|
+
// application-thrown socket errors from being silenced.
|
|
118
|
+
const exceptionValue = event.exception?.values?.[0]?.value ?? "";
|
|
119
|
+
const exceptionMechanism = event.exception?.values?.[0]?.mechanism?.type;
|
|
120
|
+
if (exceptionValue === "socket hang up" &&
|
|
121
|
+
exceptionMechanism === "onunhandledrejection") {
|
|
122
|
+
const frames = event.exception?.values?.[0]?.stacktrace?.frames ?? [];
|
|
123
|
+
const fromHttpClient = frames.some((f) => f?.function === "Socket.socketOnEnd" ||
|
|
124
|
+
f?.filename === "node:_http_client");
|
|
125
|
+
if (fromHttpClient) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
109
129
|
// h3's `createError({ statusCode: 4xx, ... })` produces an
|
|
110
130
|
// `HTTPError` (h3 v2) / `H3Error` (h3 v1). 4xx HTTPErrors are
|
|
111
131
|
// handler-controlled "expected failure" responses (404 not found,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sentry.js","sourceRoot":"","sources":["../../src/server/sentry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Server-side Sentry initialization for Nitro.\n *\n * Errors thrown inside Nitro routes (the framework's own /_agent-native/*\n * handlers, the template's API routes, action handlers, agent-chat streams)\n * never reach the CLI's Sentry init — that only covers the developer's\n * machine. Without server-side Sentry the only signal a 500 ever produces\n * is a server-side console.error that lives and dies with the request.\n *\n * This module is the third Sentry init point in the framework:\n * - cli/index.ts → @sentry/node, hardcoded DSN, \"agent-native-cli\"\n * - client/analytics.ts → @sentry/browser, VITE_SENTRY_CLIENT_DSN / runtime config\n * - server/sentry.ts → @sentry/node, SENTRY_SERVER_DSN / SENTRY_DSN\n *\n * The browser and server can share a Sentry project/DSN. Separate projects\n * are an operational choice for noise, ownership, or quotas; not a runtime\n * requirement.\n */\nimport * as Sentry from \"@sentry/node\";\nimport path from \"node:path\";\nimport fs from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport type { AuthSession } from \"./auth.js\";\nimport {\n resolveSentryEnvironment,\n resolveServerSentryDsn,\n} from \"./sentry-config.js\";\n\nlet _initStarted = false;\nlet _initSucceeded = false;\n\n/**\n * Resolve the agent-native version baked into core's package.json so Sentry\n * \"release\" reflects the running framework version. Mirrors how the CLI\n * computes `_version` — same dist layout, same fallback string. Guarded so\n * a missing/unreadable package.json never crashes server boot.\n */\nfunction resolveServerRelease(): string {\n const explicit = process.env.AGENT_NATIVE_RELEASE;\n if (explicit) return explicit;\n try {\n const here = path.dirname(fileURLToPath(import.meta.url));\n // dist/server/sentry.js → ../../package.json\n const pkgPath = path.resolve(here, \"../../package.json\");\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\")) as {\n version?: string;\n };\n if (pkg?.version) return `agent-native-server@${pkg.version}`;\n } catch {\n // ignore — fall through to \"unknown\"\n }\n return \"agent-native-server@unknown\";\n}\n\nfunction parseTracesSampleRate(): number {\n const raw = process.env.SENTRY_SERVER_TRACES_SAMPLE_RATE;\n if (!raw) return 0;\n const n = Number(raw);\n if (!Number.isFinite(n) || n < 0 || n > 1) return 0;\n return n;\n}\n\n/**\n * Initialize server-side Sentry. Idempotent — safe to call from multiple\n * plugin entrypoints. Returns `true` if initialization actually happened\n * (DSN was set), `false` if Sentry is disabled (no DSN).\n *\n * No DSN is hardcoded: unlike the CLI (a published binary that always wants\n * to phone home crashes), the server runs in customer environments. Operators\n * set `SENTRY_SERVER_DSN` or the common `SENTRY_DSN` when they want their own\n * Sentry project to receive these events; without one the module no-ops.\n */\nexport function initServerSentry(): boolean {\n if (_initStarted) return _initSucceeded;\n _initStarted = true;\n\n const dsn = resolveServerSentryDsn();\n if (!dsn) {\n if (process.env.DEBUG) {\n console.log(\n \"[agent-native] SENTRY_SERVER_DSN/SENTRY_DSN not set — server Sentry disabled.\",\n );\n }\n return false;\n }\n\n Sentry.init({\n dsn,\n environment: resolveSentryEnvironment(),\n release: resolveServerRelease(),\n tracesSampleRate: parseTracesSampleRate(),\n // sendDefaultPii MUST stay false — the framework runs inside customer\n // environments and we never want to silently ship request headers,\n // cookies, or process.env contents to Sentry without explicit consent.\n sendDefaultPii: false,\n beforeSend(event) {\n // Drop expected user-input rejections so they don't pollute Sentry\n // with non-bug noise. Mirrors the CLI's drop list — the framework\n // and CLI both throw `ValidationError` for the same class of input\n // failures, and exception type comes through as the class name.\n const exceptionType = event.exception?.values?.[0]?.type;\n if (\n exceptionType === \"ValidationError\" ||\n event.tags?.handled === \"validation\"\n ) {\n return null;\n }\n // Drop access-control rejections (caller lacks permission, signed\n // out, etc.). These are 4xx user-facing errors that propagated to\n // Nitro's error hook because a route forgot to catch them — fixing\n // the route is the right answer, but in the meantime they bury\n // real bugs and don't represent server failures. Auth-routes use\n // `captureAuthError` directly with `level: warning` so this filter\n // only sees the generic-handler escape path.\n if (\n exceptionType === \"ForbiddenError\" ||\n exceptionType === \"UnauthorizedError\"\n ) {\n return null;\n }\n // h3's `createError({ statusCode: 4xx, ... })` produces an\n // `HTTPError` (h3 v2) / `H3Error` (h3 v1). 4xx HTTPErrors are\n // handler-controlled \"expected failure\" responses (404 not found,\n // 400 bad input) that route through Nitro's error hook just\n // because they bubble out of `defineEventHandler`. They aren't\n // bugs — they're the documented way to return a 4xx in h3.\n // Capture only when the statusCode looks like 5xx (real failure)\n // or is missing (generic Error masquerading as HTTPError).\n if (exceptionType === \"HTTPError\" || exceptionType === \"H3Error\") {\n const statusCode = (event.tags?.statusCode ??\n (\n event.contexts as\n | Record<string, Record<string, unknown>>\n | undefined\n )?.h3?.statusCode) as number | string | undefined;\n const code =\n typeof statusCode === \"string\"\n ? parseInt(statusCode, 10)\n : statusCode;\n if (typeof code === \"number\" && code >= 400 && code < 500) {\n return null;\n }\n // No statusCode in the event payload — fall back to matching the\n // common 4xx messages so handler-thrown 404/400/403/401 don't\n // pollute Sentry. This is a heuristic, but the alternatives\n // (every 4xx becomes a \"real\" issue, or we patch every route to\n // catch+return) are worse.\n const value = event.exception?.values?.[0]?.value ?? \"\";\n if (\n /^Cannot find any route matching/i.test(value) ||\n / not found$/i.test(value) ||\n /Unauthenticated$/i.test(value) ||\n /^No access to /i.test(value)\n ) {\n return null;\n }\n }\n\n // Defense in depth: scrub PII even if some integration auto-attached\n // request metadata despite sendDefaultPii: false.\n if (event.request) {\n if (event.request.headers) {\n const headers = event.request.headers as Record<string, string>;\n for (const k of Object.keys(headers)) {\n const lk = k.toLowerCase();\n if (\n lk === \"cookie\" ||\n lk === \"authorization\" ||\n lk === \"set-cookie\" ||\n lk === \"proxy-authorization\"\n ) {\n delete headers[k];\n }\n }\n }\n // Cookies live in their own field too.\n delete (event.request as Record<string, unknown>).cookies;\n }\n\n // Keep user info that was explicitly set via Sentry.setUser\n // (id/email/username) so we can attribute crashes back to a real\n // operator. Always strip ip_address — auto-collected, no consent.\n if (event.user) {\n const user = event.user as Record<string, unknown>;\n delete user.ip_address;\n const hasIdentity =\n typeof user.id === \"string\" ||\n typeof user.email === \"string\" ||\n typeof user.username === \"string\";\n if (!hasIdentity) {\n delete event.user;\n }\n }\n\n // Sentry's contexts can carry process.env snapshots — strip env-shaped\n // contexts so we don't leak deployment secrets.\n if (event.contexts && typeof event.contexts === \"object\") {\n delete (event.contexts as Record<string, unknown>).runtime_env;\n }\n\n return event;\n },\n });\n\n _initSucceeded = true;\n return true;\n}\n\n/**\n * `true` once `initServerSentry()` has succeeded with a DSN. Plugins that\n * want to skip work when Sentry is disabled can check this before calling\n * the helpers below.\n */\nexport function isServerSentryEnabled(): boolean {\n return _initSucceeded;\n}\n\n/**\n * Attach the current request's user to Sentry's isolation scope so any\n * `captureException` triggered later in the request carries the right\n * `user.id` / `user.email` / `user.username` and `orgId` tag.\n *\n * Sentry node 10 uses Node's AsyncLocalStorage to give each async context\n * its own isolation scope, so setting on `getIsolationScope()` here only\n * affects events emitted while this request's async context is active.\n *\n * No-ops gracefully when Sentry isn't initialized or no session exists —\n * never throws into the request path.\n */\nexport function setSentryUserForRequest(session: AuthSession | null): void {\n if (!_initSucceeded) return;\n try {\n const scope = Sentry.getIsolationScope();\n if (!session) {\n scope.setUser(null);\n scope.setTag(\"orgId\", null);\n return;\n }\n scope.setUser({\n id: session.userId ?? session.email,\n email: session.email,\n username: session.name,\n });\n scope.setTag(\"orgId\", session.orgId ?? null);\n if (session.orgRole) {\n scope.setTag(\"orgRole\", session.orgRole);\n }\n } catch {\n // Sentry scope APIs should never throw, but if they do we'd rather\n // continue serving the request than crash on observability.\n }\n}\n\n/**\n * Pin a user/org onto the current isolation scope from a lighter\n * `RequestContext`-shaped payload. Used by the request-context observer so\n * action handlers, agent-chat runs, and integration webhook processors —\n * all of which already wrap their work in `runWithRequestContext({ userEmail,\n * orgId, ... })` — automatically tag Sentry events with the right user even\n * when the Nitro `request` hook didn't see a cookie (e.g. webhook delivery,\n * A2A calls, internal background runs).\n *\n * Skips overwriting a richer user identity already set by\n * `setSentryUserForRequest` — the cookie-resolved session has\n * userId/username on top of email, which we shouldn't clobber.\n */\nexport function setSentryRequestContext(ctx: {\n userEmail?: string;\n orgId?: string;\n}): void {\n if (!_initSucceeded) return;\n try {\n const scope = Sentry.getIsolationScope();\n if (ctx.userEmail) {\n const existing = scope.getScopeData().user;\n if (!existing?.id && !existing?.email) {\n scope.setUser({ id: ctx.userEmail, email: ctx.userEmail });\n }\n }\n if (ctx.orgId) {\n scope.setTag(\"orgId\", ctx.orgId);\n }\n } catch {\n // never throw\n }\n}\n\n/**\n * Capture an error from one of the auth attempt routes (login / signup)\n * with the email pinned to the event so support can filter by user. Sets\n * Sentry level to `warning` (not `error`) — bad-password attempts aren't\n * actionable, but a sustained spike of warnings on a route IS the signal\n * we care about.\n *\n * Caller should still return their normal HTTP response (401/409/etc.);\n * this just records the error for observability.\n */\nexport function captureAuthError(\n error: unknown,\n context: { route: \"login\" | \"signup\" | \"logout\"; email?: string },\n): string | undefined {\n if (!_initSucceeded) return undefined;\n try {\n return Sentry.withScope((scope) => {\n scope.setLevel(\"warning\");\n scope.setTag(\"auth\", context.route);\n if (context.email) {\n scope.setUser({ id: context.email, email: context.email });\n }\n return Sentry.captureException(error);\n });\n } catch {\n return undefined;\n }\n}\n\nexport interface RouteErrorContext {\n /** The full request path (e.g. `/_agent-native/agent-chat`). */\n route?: string;\n /** HTTP method (e.g. `GET`, `POST`). */\n method?: string;\n /** Caller's `User-Agent` header. */\n userAgent?: string;\n /** Free-form extra tags to add to the event (low-cardinality). */\n tags?: Record<string, string | undefined>;\n /**\n * High-cardinality / structured payload — not searchable but visible in\n * the Sentry event detail (recording IDs, byte counts, compression\n * metadata, response body tails, etc.).\n */\n extra?: Record<string, unknown>;\n /**\n * Grouped contexts shown as separate cards in the Sentry event UI.\n */\n contexts?: Record<string, Record<string, unknown>>;\n}\n\n/**\n * Capture an exception that surfaced in a Nitro route handler with the\n * request's route/method/userAgent attached as searchable Sentry tags.\n *\n * Non-throwing: if Sentry isn't initialized or the underlying capture\n * fails, this is a no-op. Returns the Sentry event ID when capture\n * succeeded, otherwise `undefined`.\n */\nexport function captureRouteError(\n error: unknown,\n context: RouteErrorContext = {},\n): string | undefined {\n if (!_initSucceeded) return undefined;\n try {\n return Sentry.withScope((scope) => {\n if (context.route) scope.setTag(\"route\", context.route);\n if (context.method) scope.setTag(\"method\", context.method);\n if (context.userAgent) scope.setTag(\"userAgent\", context.userAgent);\n if (context.tags) {\n for (const [k, v] of Object.entries(context.tags)) {\n if (typeof v === \"string\") scope.setTag(k, v);\n }\n }\n if (context.extra) {\n for (const [k, v] of Object.entries(context.extra)) {\n if (v !== undefined) scope.setExtra(k, v);\n }\n }\n if (context.contexts) {\n for (const [k, v] of Object.entries(context.contexts)) {\n scope.setContext(k, v);\n }\n }\n return Sentry.captureException(error);\n });\n } catch {\n return undefined;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sentry.js","sourceRoot":"","sources":["../../src/server/sentry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Server-side Sentry initialization for Nitro.\n *\n * Errors thrown inside Nitro routes (the framework's own /_agent-native/*\n * handlers, the template's API routes, action handlers, agent-chat streams)\n * never reach the CLI's Sentry init — that only covers the developer's\n * machine. Without server-side Sentry the only signal a 500 ever produces\n * is a server-side console.error that lives and dies with the request.\n *\n * This module is the third Sentry init point in the framework:\n * - cli/index.ts → @sentry/node, hardcoded DSN, \"agent-native-cli\"\n * - client/analytics.ts → @sentry/browser, VITE_SENTRY_CLIENT_DSN / runtime config\n * - server/sentry.ts → @sentry/node, SENTRY_SERVER_DSN / SENTRY_DSN\n *\n * The browser and server can share a Sentry project/DSN. Separate projects\n * are an operational choice for noise, ownership, or quotas; not a runtime\n * requirement.\n */\nimport * as Sentry from \"@sentry/node\";\nimport path from \"node:path\";\nimport fs from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport type { AuthSession } from \"./auth.js\";\nimport {\n resolveSentryEnvironment,\n resolveServerSentryDsn,\n} from \"./sentry-config.js\";\n\nlet _initStarted = false;\nlet _initSucceeded = false;\n\n/**\n * Resolve the agent-native version baked into core's package.json so Sentry\n * \"release\" reflects the running framework version. Mirrors how the CLI\n * computes `_version` — same dist layout, same fallback string. Guarded so\n * a missing/unreadable package.json never crashes server boot.\n */\nfunction resolveServerRelease(): string {\n const explicit = process.env.AGENT_NATIVE_RELEASE;\n if (explicit) return explicit;\n try {\n const here = path.dirname(fileURLToPath(import.meta.url));\n // dist/server/sentry.js → ../../package.json\n const pkgPath = path.resolve(here, \"../../package.json\");\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\")) as {\n version?: string;\n };\n if (pkg?.version) return `agent-native-server@${pkg.version}`;\n } catch {\n // ignore — fall through to \"unknown\"\n }\n return \"agent-native-server@unknown\";\n}\n\nfunction parseTracesSampleRate(): number {\n const raw = process.env.SENTRY_SERVER_TRACES_SAMPLE_RATE;\n if (!raw) return 0;\n const n = Number(raw);\n if (!Number.isFinite(n) || n < 0 || n > 1) return 0;\n return n;\n}\n\n/**\n * Initialize server-side Sentry. Idempotent — safe to call from multiple\n * plugin entrypoints. Returns `true` if initialization actually happened\n * (DSN was set), `false` if Sentry is disabled (no DSN).\n *\n * No DSN is hardcoded: unlike the CLI (a published binary that always wants\n * to phone home crashes), the server runs in customer environments. Operators\n * set `SENTRY_SERVER_DSN` or the common `SENTRY_DSN` when they want their own\n * Sentry project to receive these events; without one the module no-ops.\n */\nexport function initServerSentry(): boolean {\n if (_initStarted) return _initSucceeded;\n _initStarted = true;\n\n const dsn = resolveServerSentryDsn();\n if (!dsn) {\n if (process.env.DEBUG) {\n console.log(\n \"[agent-native] SENTRY_SERVER_DSN/SENTRY_DSN not set — server Sentry disabled.\",\n );\n }\n return false;\n }\n\n Sentry.init({\n dsn,\n environment: resolveSentryEnvironment(),\n release: resolveServerRelease(),\n tracesSampleRate: parseTracesSampleRate(),\n // sendDefaultPii MUST stay false — the framework runs inside customer\n // environments and we never want to silently ship request headers,\n // cookies, or process.env contents to Sentry without explicit consent.\n sendDefaultPii: false,\n beforeSend(event) {\n // Drop expected user-input rejections so they don't pollute Sentry\n // with non-bug noise. Mirrors the CLI's drop list — the framework\n // and CLI both throw `ValidationError` for the same class of input\n // failures, and exception type comes through as the class name.\n const exceptionType = event.exception?.values?.[0]?.type;\n if (\n exceptionType === \"ValidationError\" ||\n event.tags?.handled === \"validation\"\n ) {\n return null;\n }\n // Drop access-control rejections (caller lacks permission, signed\n // out, etc.). These are 4xx user-facing errors that propagated to\n // Nitro's error hook because a route forgot to catch them — fixing\n // the route is the right answer, but in the meantime they bury\n // real bugs and don't represent server failures. Auth-routes use\n // `captureAuthError` directly with `level: warning` so this filter\n // only sees the generic-handler escape path.\n if (\n exceptionType === \"ForbiddenError\" ||\n exceptionType === \"UnauthorizedError\"\n ) {\n return null;\n }\n // Drop \"socket hang up\" unhandled promise rejections that fire from\n // Lambda freeze cycles. AWS Lambda recycles long-lived sockets (e.g.\n // MCP Streamable HTTP long-polls, keep-alive HTTP agents) ~60s after\n // a function returns 200; the next thaw delivers a socket-end event\n // whose Promise has nobody left to await it. The function itself\n // already returned correctly, so there's no user impact — just\n // ~10k events/day of noise. The narrow shape (unhandled rejection\n // from `Socket.socketOnEnd` in `node:_http_client`) keeps real\n // application-thrown socket errors from being silenced.\n const exceptionValue = event.exception?.values?.[0]?.value ?? \"\";\n const exceptionMechanism = event.exception?.values?.[0]?.mechanism?.type;\n if (\n exceptionValue === \"socket hang up\" &&\n exceptionMechanism === \"onunhandledrejection\"\n ) {\n const frames = event.exception?.values?.[0]?.stacktrace?.frames ?? [];\n const fromHttpClient = frames.some(\n (f) =>\n f?.function === \"Socket.socketOnEnd\" ||\n f?.filename === \"node:_http_client\",\n );\n if (fromHttpClient) {\n return null;\n }\n }\n // h3's `createError({ statusCode: 4xx, ... })` produces an\n // `HTTPError` (h3 v2) / `H3Error` (h3 v1). 4xx HTTPErrors are\n // handler-controlled \"expected failure\" responses (404 not found,\n // 400 bad input) that route through Nitro's error hook just\n // because they bubble out of `defineEventHandler`. They aren't\n // bugs — they're the documented way to return a 4xx in h3.\n // Capture only when the statusCode looks like 5xx (real failure)\n // or is missing (generic Error masquerading as HTTPError).\n if (exceptionType === \"HTTPError\" || exceptionType === \"H3Error\") {\n const statusCode = (event.tags?.statusCode ??\n (\n event.contexts as\n | Record<string, Record<string, unknown>>\n | undefined\n )?.h3?.statusCode) as number | string | undefined;\n const code =\n typeof statusCode === \"string\"\n ? parseInt(statusCode, 10)\n : statusCode;\n if (typeof code === \"number\" && code >= 400 && code < 500) {\n return null;\n }\n // No statusCode in the event payload — fall back to matching the\n // common 4xx messages so handler-thrown 404/400/403/401 don't\n // pollute Sentry. This is a heuristic, but the alternatives\n // (every 4xx becomes a \"real\" issue, or we patch every route to\n // catch+return) are worse.\n const value = event.exception?.values?.[0]?.value ?? \"\";\n if (\n /^Cannot find any route matching/i.test(value) ||\n / not found$/i.test(value) ||\n /Unauthenticated$/i.test(value) ||\n /^No access to /i.test(value)\n ) {\n return null;\n }\n }\n\n // Defense in depth: scrub PII even if some integration auto-attached\n // request metadata despite sendDefaultPii: false.\n if (event.request) {\n if (event.request.headers) {\n const headers = event.request.headers as Record<string, string>;\n for (const k of Object.keys(headers)) {\n const lk = k.toLowerCase();\n if (\n lk === \"cookie\" ||\n lk === \"authorization\" ||\n lk === \"set-cookie\" ||\n lk === \"proxy-authorization\"\n ) {\n delete headers[k];\n }\n }\n }\n // Cookies live in their own field too.\n delete (event.request as Record<string, unknown>).cookies;\n }\n\n // Keep user info that was explicitly set via Sentry.setUser\n // (id/email/username) so we can attribute crashes back to a real\n // operator. Always strip ip_address — auto-collected, no consent.\n if (event.user) {\n const user = event.user as Record<string, unknown>;\n delete user.ip_address;\n const hasIdentity =\n typeof user.id === \"string\" ||\n typeof user.email === \"string\" ||\n typeof user.username === \"string\";\n if (!hasIdentity) {\n delete event.user;\n }\n }\n\n // Sentry's contexts can carry process.env snapshots — strip env-shaped\n // contexts so we don't leak deployment secrets.\n if (event.contexts && typeof event.contexts === \"object\") {\n delete (event.contexts as Record<string, unknown>).runtime_env;\n }\n\n return event;\n },\n });\n\n _initSucceeded = true;\n return true;\n}\n\n/**\n * `true` once `initServerSentry()` has succeeded with a DSN. Plugins that\n * want to skip work when Sentry is disabled can check this before calling\n * the helpers below.\n */\nexport function isServerSentryEnabled(): boolean {\n return _initSucceeded;\n}\n\n/**\n * Attach the current request's user to Sentry's isolation scope so any\n * `captureException` triggered later in the request carries the right\n * `user.id` / `user.email` / `user.username` and `orgId` tag.\n *\n * Sentry node 10 uses Node's AsyncLocalStorage to give each async context\n * its own isolation scope, so setting on `getIsolationScope()` here only\n * affects events emitted while this request's async context is active.\n *\n * No-ops gracefully when Sentry isn't initialized or no session exists —\n * never throws into the request path.\n */\nexport function setSentryUserForRequest(session: AuthSession | null): void {\n if (!_initSucceeded) return;\n try {\n const scope = Sentry.getIsolationScope();\n if (!session) {\n scope.setUser(null);\n scope.setTag(\"orgId\", null);\n return;\n }\n scope.setUser({\n id: session.userId ?? session.email,\n email: session.email,\n username: session.name,\n });\n scope.setTag(\"orgId\", session.orgId ?? null);\n if (session.orgRole) {\n scope.setTag(\"orgRole\", session.orgRole);\n }\n } catch {\n // Sentry scope APIs should never throw, but if they do we'd rather\n // continue serving the request than crash on observability.\n }\n}\n\n/**\n * Pin a user/org onto the current isolation scope from a lighter\n * `RequestContext`-shaped payload. Used by the request-context observer so\n * action handlers, agent-chat runs, and integration webhook processors —\n * all of which already wrap their work in `runWithRequestContext({ userEmail,\n * orgId, ... })` — automatically tag Sentry events with the right user even\n * when the Nitro `request` hook didn't see a cookie (e.g. webhook delivery,\n * A2A calls, internal background runs).\n *\n * Skips overwriting a richer user identity already set by\n * `setSentryUserForRequest` — the cookie-resolved session has\n * userId/username on top of email, which we shouldn't clobber.\n */\nexport function setSentryRequestContext(ctx: {\n userEmail?: string;\n orgId?: string;\n}): void {\n if (!_initSucceeded) return;\n try {\n const scope = Sentry.getIsolationScope();\n if (ctx.userEmail) {\n const existing = scope.getScopeData().user;\n if (!existing?.id && !existing?.email) {\n scope.setUser({ id: ctx.userEmail, email: ctx.userEmail });\n }\n }\n if (ctx.orgId) {\n scope.setTag(\"orgId\", ctx.orgId);\n }\n } catch {\n // never throw\n }\n}\n\n/**\n * Capture an error from one of the auth attempt routes (login / signup)\n * with the email pinned to the event so support can filter by user. Sets\n * Sentry level to `warning` (not `error`) — bad-password attempts aren't\n * actionable, but a sustained spike of warnings on a route IS the signal\n * we care about.\n *\n * Caller should still return their normal HTTP response (401/409/etc.);\n * this just records the error for observability.\n */\nexport function captureAuthError(\n error: unknown,\n context: { route: \"login\" | \"signup\" | \"logout\"; email?: string },\n): string | undefined {\n if (!_initSucceeded) return undefined;\n try {\n return Sentry.withScope((scope) => {\n scope.setLevel(\"warning\");\n scope.setTag(\"auth\", context.route);\n if (context.email) {\n scope.setUser({ id: context.email, email: context.email });\n }\n return Sentry.captureException(error);\n });\n } catch {\n return undefined;\n }\n}\n\nexport interface RouteErrorContext {\n /** The full request path (e.g. `/_agent-native/agent-chat`). */\n route?: string;\n /** HTTP method (e.g. `GET`, `POST`). */\n method?: string;\n /** Caller's `User-Agent` header. */\n userAgent?: string;\n /** Free-form extra tags to add to the event (low-cardinality). */\n tags?: Record<string, string | undefined>;\n /**\n * High-cardinality / structured payload — not searchable but visible in\n * the Sentry event detail (recording IDs, byte counts, compression\n * metadata, response body tails, etc.).\n */\n extra?: Record<string, unknown>;\n /**\n * Grouped contexts shown as separate cards in the Sentry event UI.\n */\n contexts?: Record<string, Record<string, unknown>>;\n}\n\n/**\n * Capture an exception that surfaced in a Nitro route handler with the\n * request's route/method/userAgent attached as searchable Sentry tags.\n *\n * Non-throwing: if Sentry isn't initialized or the underlying capture\n * fails, this is a no-op. Returns the Sentry event ID when capture\n * succeeded, otherwise `undefined`.\n */\nexport function captureRouteError(\n error: unknown,\n context: RouteErrorContext = {},\n): string | undefined {\n if (!_initSucceeded) return undefined;\n try {\n return Sentry.withScope((scope) => {\n if (context.route) scope.setTag(\"route\", context.route);\n if (context.method) scope.setTag(\"method\", context.method);\n if (context.userAgent) scope.setTag(\"userAgent\", context.userAgent);\n if (context.tags) {\n for (const [k, v] of Object.entries(context.tags)) {\n if (typeof v === \"string\") scope.setTag(k, v);\n }\n }\n if (context.extra) {\n for (const [k, v] of Object.entries(context.extra)) {\n if (v !== undefined) scope.setExtra(k, v);\n }\n }\n if (context.contexts) {\n for (const [k, v] of Object.entries(context.contexts)) {\n scope.setContext(k, v);\n }\n }\n return Sentry.captureException(error);\n });\n } catch {\n return undefined;\n }\n}\n"]}
|