@agent-native/core 0.30.6 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a2a/client.d.ts +2 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +6 -4
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/handlers.d.ts.map +1 -1
- package/dist/a2a/handlers.js +3 -0
- package/dist/a2a/handlers.js.map +1 -1
- package/dist/a2a/server.d.ts.map +1 -1
- package/dist/a2a/server.js.map +1 -1
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
- package/dist/agent/engine/anthropic-engine.js +0 -7
- package/dist/agent/engine/anthropic-engine.js.map +1 -1
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/agent/engine/translate-ai-sdk.d.ts.map +1 -1
- package/dist/agent/engine/translate-ai-sdk.js +5 -3
- package/dist/agent/engine/translate-ai-sdk.js.map +1 -1
- package/dist/agent/production-agent.js +7 -3
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +14 -6
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js.map +1 -1
- package/dist/brand-kit/brand-signals.d.ts +31 -0
- package/dist/brand-kit/brand-signals.d.ts.map +1 -0
- package/dist/brand-kit/brand-signals.js +101 -0
- package/dist/brand-kit/brand-signals.js.map +1 -0
- package/dist/brand-kit/index.d.ts +21 -0
- package/dist/brand-kit/index.d.ts.map +1 -0
- package/dist/brand-kit/index.js +34 -0
- package/dist/brand-kit/index.js.map +1 -0
- package/dist/brand-kit/types.d.ts +103 -0
- package/dist/brand-kit/types.d.ts.map +1 -0
- package/dist/brand-kit/types.js +17 -0
- package/dist/brand-kit/types.js.map +1 -0
- package/dist/cli/code-agent-executor.d.ts.map +1 -1
- package/dist/cli/code-agent-executor.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +0 -1
- package/dist/cli/create.js.map +1 -1
- package/dist/client/AgentNative.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +0 -2
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +69 -17
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/IframeEmbed.d.ts.map +1 -1
- package/dist/client/IframeEmbed.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +1 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/builder-mark.d.ts.map +1 -1
- package/dist/client/builder-mark.js.map +1 -1
- package/dist/client/components/MissingKeyCard.d.ts.map +1 -1
- package/dist/client/components/MissingKeyCard.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +6 -3
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +5 -0
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/VoiceButton.d.ts.map +1 -1
- package/dist/client/composer/VoiceButton.js +9 -0
- package/dist/client/composer/VoiceButton.js.map +1 -1
- package/dist/client/composer/extensions/FileReference.d.ts.map +1 -1
- package/dist/client/composer/extensions/FileReference.js.map +1 -1
- package/dist/client/composer/extensions/MentionReference.d.ts.map +1 -1
- package/dist/client/composer/extensions/MentionReference.js.map +1 -1
- package/dist/client/composer/extensions/SkillReference.d.ts.map +1 -1
- package/dist/client/composer/extensions/SkillReference.js.map +1 -1
- package/dist/client/conversation/AgentConversation.js +8 -6
- package/dist/client/conversation/AgentConversation.js.map +1 -1
- package/dist/client/conversation/use-near-bottom-autoscroll.d.ts.map +1 -1
- package/dist/client/conversation/use-near-bottom-autoscroll.js +133 -35
- package/dist/client/conversation/use-near-bottom-autoscroll.js.map +1 -1
- package/dist/client/db-admin/DbAdminPage.js.map +1 -1
- package/dist/client/dev-overlay/DevOverlay.d.ts.map +1 -1
- package/dist/client/dev-overlay/DevOverlay.js +0 -1
- package/dist/client/dev-overlay/DevOverlay.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +19 -0
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +11 -3
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/integrations/IntegrationsPanel.d.ts.map +1 -1
- package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
- package/dist/client/mcp-app-host.d.ts.map +1 -1
- package/dist/client/mcp-app-host.js +6 -1
- package/dist/client/mcp-app-host.js.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.js +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/client/notifications/NotificationsBell.js.map +1 -1
- package/dist/client/onboarding/SetupButton.d.ts.map +1 -1
- package/dist/client/onboarding/SetupButton.js +6 -0
- package/dist/client/onboarding/SetupButton.js.map +1 -1
- package/dist/client/progress/RunsTray.js.map +1 -1
- package/dist/client/resources/McpServerDetail.d.ts.map +1 -1
- package/dist/client/resources/McpServerDetail.js.map +1 -1
- package/dist/client/settings/AgentsSection.d.ts.map +1 -1
- package/dist/client/settings/AgentsSection.js +1 -1
- package/dist/client/settings/AgentsSection.js.map +1 -1
- package/dist/client/settings/AutomationsSection.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +0 -4
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/terminal/AgentTerminal.d.ts.map +1 -1
- package/dist/client/terminal/AgentTerminal.js +1 -1
- package/dist/client/terminal/AgentTerminal.js.map +1 -1
- package/dist/client/use-agent-chat.d.ts.map +1 -1
- package/dist/client/use-agent-chat.js +20 -4
- package/dist/client/use-agent-chat.js.map +1 -1
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js +39 -25
- package/dist/client/use-chat-threads.js.map +1 -1
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +8 -0
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/client/use-dev-mode.d.ts.map +1 -1
- package/dist/client/use-dev-mode.js +25 -9
- package/dist/client/use-dev-mode.js.map +1 -1
- package/dist/client/useProductionAgent.d.ts.map +1 -1
- package/dist/client/useProductionAgent.js +6 -2
- package/dist/client/useProductionAgent.js.map +1 -1
- package/dist/collab/agent-presence.d.ts.map +1 -1
- package/dist/collab/agent-presence.js +1 -1
- package/dist/collab/agent-presence.js.map +1 -1
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/awareness.js +8 -0
- package/dist/collab/awareness.js.map +1 -1
- package/dist/collab/client-struct.js.map +1 -1
- package/dist/deploy/build.js +0 -5
- package/dist/deploy/build.js.map +1 -1
- package/dist/extensions/fetch-tool.d.ts.map +1 -1
- package/dist/extensions/fetch-tool.js +4 -1
- package/dist/extensions/fetch-tool.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts.map +1 -1
- package/dist/file-upload/actions/upload-image.js +39 -4
- package/dist/file-upload/actions/upload-image.js.map +1 -1
- package/dist/integrations/adapters/slack.d.ts.map +1 -1
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/google-docs-poller.d.ts.map +1 -1
- package/dist/integrations/google-docs-poller.js +14 -1
- package/dist/integrations/google-docs-poller.js.map +1 -1
- package/dist/integrations/plugin.d.ts.map +1 -1
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +10 -1
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/jobs/scheduler.d.ts.map +1 -1
- package/dist/jobs/scheduler.js.map +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +20 -8
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/connect-store.d.ts.map +1 -1
- package/dist/mcp/connect-store.js +1 -1
- package/dist/mcp/connect-store.js.map +1 -1
- package/dist/notifications/channels.d.ts.map +1 -1
- package/dist/notifications/channels.js +3 -2
- package/dist/notifications/channels.js.map +1 -1
- package/dist/oauth-tokens/store.js.map +1 -1
- package/dist/observability/evals.d.ts.map +1 -1
- package/dist/observability/evals.js +7 -7
- package/dist/observability/evals.js.map +1 -1
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +15 -5
- package/dist/observability/traces.js.map +1 -1
- package/dist/org/handlers.d.ts.map +1 -1
- package/dist/org/handlers.js +3 -2
- package/dist/org/handlers.js.map +1 -1
- package/dist/resources/handlers.d.ts +5 -5
- package/dist/resources/handlers.d.ts.map +1 -1
- package/dist/resources/handlers.js.map +1 -1
- package/dist/scripts/db/schema.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +0 -6
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +0 -3
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +1 -2
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/create-server.d.ts.map +1 -1
- package/dist/server/create-server.js +0 -23
- package/dist/server/create-server.js.map +1 -1
- package/dist/server/google-oauth.d.ts.map +1 -1
- package/dist/server/google-oauth.js +0 -3
- package/dist/server/google-oauth.js.map +1 -1
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +49 -18
- package/dist/server/poll.js.map +1 -1
- package/dist/settings/store.js.map +1 -1
- package/dist/sharing/access.d.ts.map +1 -1
- package/dist/sharing/access.js +25 -4
- package/dist/sharing/access.js.map +1 -1
- package/dist/triggers/dispatcher.d.ts.map +1 -1
- package/dist/triggers/dispatcher.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +2 -0
- package/dist/vite/client.js.map +1 -1
- package/package.json +3 -2
- package/dist/client/conversation/AgentConversation.spec.d.ts +0 -2
- package/dist/client/conversation/AgentConversation.spec.d.ts.map +0 -1
- package/dist/client/conversation/AgentConversation.spec.js +0 -69
- package/dist/client/conversation/AgentConversation.spec.js.map +0 -1
- package/dist/client/extensions/AgentNativeExtensionFrame.e2e-host.d.ts +0 -2
- package/dist/client/extensions/AgentNativeExtensionFrame.e2e-host.d.ts.map +0 -1
- package/dist/client/extensions/AgentNativeExtensionFrame.e2e-host.js +0 -110
- package/dist/client/extensions/AgentNativeExtensionFrame.e2e-host.js.map +0 -1
- package/dist/client/extensions/AgentNativeExtensionFrame.spec.d.ts +0 -2
- package/dist/client/extensions/AgentNativeExtensionFrame.spec.d.ts.map +0 -1
- package/dist/client/extensions/AgentNativeExtensionFrame.spec.js +0 -68
- package/dist/client/extensions/AgentNativeExtensionFrame.spec.js.map +0 -1
- package/dist/client/extensions/ExtensionViewer.spec.d.ts +0 -2
- package/dist/client/extensions/ExtensionViewer.spec.d.ts.map +0 -1
- package/dist/client/extensions/ExtensionViewer.spec.js +0 -94
- package/dist/client/extensions/ExtensionViewer.spec.js.map +0 -1
- package/dist/client/guided-questions.flow.spec.d.ts +0 -2
- package/dist/client/guided-questions.flow.spec.d.ts.map +0 -1
- package/dist/client/guided-questions.flow.spec.js +0 -147
- package/dist/client/guided-questions.flow.spec.js.map +0 -1
- package/dist/client/settings/useBuilderStatus.spec.d.ts +0 -2
- package/dist/client/settings/useBuilderStatus.spec.d.ts.map +0 -1
- package/dist/client/settings/useBuilderStatus.spec.js +0 -487
- package/dist/client/settings/useBuilderStatus.spec.js.map +0 -1
- package/dist/client/sharing/ShareButton.spec.d.ts +0 -2
- package/dist/client/sharing/ShareButton.spec.d.ts.map +0 -1
- package/dist/client/sharing/ShareButton.spec.js +0 -196
- package/dist/client/sharing/ShareButton.spec.js.map +0 -1
- package/dist/client/use-chat-models.spec.d.ts +0 -2
- package/dist/client/use-chat-models.spec.d.ts.map +0 -1
- package/dist/client/use-chat-models.spec.js +0 -39
- package/dist/client/use-chat-models.spec.js.map +0 -1
- package/dist/client/use-chat-threads.spec.d.ts +0 -2
- package/dist/client/use-chat-threads.spec.d.ts.map +0 -1
- package/dist/client/use-chat-threads.spec.js +0 -760
- package/dist/client/use-chat-threads.spec.js.map +0 -1
- package/dist/client/use-db-sync.spec.d.ts +0 -2
- package/dist/client/use-db-sync.spec.d.ts.map +0 -1
- package/dist/client/use-db-sync.spec.js +0 -107
- package/dist/client/use-db-sync.spec.js.map +0 -1
- package/dist/server/script-discovery.d.ts +0 -6
- package/dist/server/script-discovery.d.ts.map +0 -1
- package/dist/server/script-discovery.js +0 -6
- package/dist/server/script-discovery.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect-store.js","sourceRoot":"","sources":["../../src/mcp/connect-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Framework-table store for the \"connect external agents\" feature.\n *\n * Two additive, dialect-agnostic tables back the browser **Connect** page and\n * the OAuth-style **device-code flow** a CLI drives:\n *\n * - `mcp_connect_tokens` — one row per minted MCP token. We never store the\n * token value (it's a signed JWT); only its `jti` so revocation is a\n * SQL lookup. Revoking sets `revoked_at`; the row is never deleted.\n * - `mcp_device_codes` — short-lived (10 min) device/user code pairs for\n * the OAuth 2.0 device-authorization-style CLI flow. Single-use\n * (`consumed_at`), rate-limited at creation.\n *\n * Mirrors `application-state/store.ts`: lazy `ensureTable()`, `getDbExec()`,\n * `isPostgres()` dialect branching for upserts, `isConnectionError()` swallow\n * so a transient Neon WS drop never 500s. `CREATE TABLE IF NOT EXISTS` only —\n * strictly additive, never DROP / ALTER (shared prod DB rule).\n */\n\nimport {\n getDbExec,\n isConnectionError,\n isPostgres,\n intType,\n} from \"../db/client.js\";\nimport { randomBytes, randomUUID } from \"node:crypto\";\n\nlet _initPromise: Promise<void> | undefined;\n\n/**\n * Scope claim that marks a connect-minted token (vs. an ordinary A2A\n * delegation JWT). Only tokens carrying this scope go through the revoke\n * lookup in `verifyAuth` — defined here so both `connect-route.ts` and\n * `build-server.ts` import it from the leaf store without a cycle.\n */\nexport const MCP_CONNECT_SCOPE = \"mcp-connect\";\n\n/**\n * Client id used when connect/device flows have to mint a standard MCP OAuth\n * access token instead of an A2A JWT (for deployments without A2A_SECRET).\n */\nexport const MCP_CONNECT_OAUTH_CLIENT_ID = \"agent-native-connect\";\n\n/** Device codes are valid for 10 minutes. */\nexport const DEVICE_CODE_TTL_MS = 10 * 60_000;\n\n/** Default minted-token lifetime. Configurable per-request 1–365 days. */\nexport const DEFAULT_TOKEN_TTL_DAYS = 365;\nexport const MIN_TOKEN_TTL_DAYS = 1;\nexport const MAX_TOKEN_TTL_DAYS = 365;\n\n/**\n * Rate limit for `device/start`: at most this many device codes may be created\n * within `DEVICE_START_WINDOW_MS`. Unauthenticated endpoint — keep it tight so\n * a hostile client can't flood the table or brute-force user codes.\n */\nexport const DEVICE_START_MAX = 20;\nexport const DEVICE_START_WINDOW_MS = 60_000;\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n // Additive only. Never DROP / ALTER — this DB is shared across every\n // deploy context (preview/branch/prod) for hosted templates.\n await client.execute(`\n CREATE TABLE IF NOT EXISTS mcp_connect_tokens (\n id TEXT PRIMARY KEY,\n jti TEXT UNIQUE NOT NULL,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n label TEXT,\n created_at ${intType()},\n last_used_at ${intType()},\n revoked_at ${intType()}\n )\n `);\n await client.execute(`\n CREATE TABLE IF NOT EXISTS mcp_device_codes (\n device_code TEXT PRIMARY KEY,\n user_code TEXT NOT NULL,\n owner_email TEXT,\n org_id TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n token_jti TEXT,\n created_at ${intType()},\n expires_at ${intType()},\n consumed_at ${intType()}\n )\n `);\n })().catch((err) => {\n // Don't cache a rejected init. A transient DB blip should let the next\n // connect/mint/revoke call retry rather than wedging the process.\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\n// ---------------------------------------------------------------------------\n// Minted-token records\n// ---------------------------------------------------------------------------\n\nexport interface MintedTokenRow {\n id: string;\n jti: string;\n ownerEmail: string;\n orgId: string | null;\n label: string | null;\n createdAt: number | null;\n lastUsedAt: number | null;\n revokedAt: number | null;\n}\n\n/**\n * Persist a record of a minted token. The token value itself (a signed JWT)\n * is NEVER stored — only its `jti`, so revocation is a cheap SQL lookup.\n */\nexport async function recordMintedToken(params: {\n jti: string;\n ownerEmail: string;\n orgId?: string | null;\n label?: string | null;\n}): Promise<string> {\n await ensureTable();\n const client = getDbExec();\n const id = randomUUID();\n await client.execute({\n sql: `INSERT INTO mcp_connect_tokens (id, jti, owner_email, org_id, label, created_at, last_used_at, revoked_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n id,\n params.jti,\n params.ownerEmail,\n params.orgId ?? null,\n params.label ?? null,\n Date.now(),\n null,\n null,\n ],\n });\n return id;\n}\n\n/**\n * Returns true when the given `jti` corresponds to a token that has been\n * revoked. Fails OPEN on a store/DB error: a transient Neon WS drop must not\n * lock every connected agent out. Signature verification is unaffected — this\n * is only the post-verify revoke check (see `verifyAuth` in build-server.ts).\n */\nexport async function isJtiRevoked(jti: string): Promise<boolean> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT revoked_at FROM mcp_connect_tokens WHERE jti = ?`,\n args: [jti],\n });\n if (rows.length === 0) return false;\n const revokedAt = rows[0].revoked_at ?? rows[0].revokedAt;\n return revokedAt != null;\n } catch (err) {\n // Fail open: a DB blip must not turn every minted token into a 401.\n // (Signature checks already passed; this only gates explicit revokes.)\n if (isConnectionError(err)) return false;\n return false;\n }\n}\n\nexport async function listTokens(\n ownerEmail: string,\n): Promise<MintedTokenRow[]> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, jti, owner_email, org_id, label, created_at, last_used_at, revoked_at FROM mcp_connect_tokens WHERE owner_email = ? ORDER BY created_at DESC`,\n args: [ownerEmail],\n });\n return rows.map((r: any) => ({\n id: r.id as string,\n jti: r.jti as string,\n ownerEmail: (r.owner_email ?? r.ownerEmail) as string,\n orgId: (r.org_id ?? r.orgId ?? null) as string | null,\n label: (r.label ?? null) as string | null,\n createdAt: numOrNull(r.created_at ?? r.createdAt),\n lastUsedAt: numOrNull(r.last_used_at ?? r.lastUsedAt),\n revokedAt: numOrNull(r.revoked_at ?? r.revokedAt),\n }));\n } catch (err) {\n if (isConnectionError(err)) return [];\n throw err;\n }\n}\n\n/**\n * Revoke a token, but ONLY if it is owned by `ownerEmail` (the caller). The\n * `owner_email = ?` predicate is the access scope — a caller can never revoke\n * another user's token. Idempotent: re-revoking keeps the first timestamp.\n * Returns true when a row was actually transitioned to revoked.\n */\nexport async function revokeToken(\n ownerEmail: string,\n id: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const result = await client.execute({\n sql: `UPDATE mcp_connect_tokens SET revoked_at = ? WHERE id = ? AND owner_email = ? AND revoked_at IS NULL`,\n args: [Date.now(), id, ownerEmail],\n });\n return result.rowsAffected > 0;\n}\n\n/**\n * Best-effort: stamp `last_used_at` for a token. Swallows all errors — this is\n * pure telemetry and must never affect the auth path.\n */\nexport async function touchTokenUsed(jti: string): Promise<void> {\n try {\n await ensureTable();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE mcp_connect_tokens SET last_used_at = ? WHERE jti = ?`,\n args: [Date.now(), jti],\n });\n } catch {\n // last_used_at is informational only — never throw from the hot path.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Device-code flow (OAuth 2.0 device-authorization style)\n// ---------------------------------------------------------------------------\n\nexport interface DeviceCodeRow {\n deviceCode: string;\n userCode: string;\n ownerEmail: string | null;\n orgId: string | null;\n status: \"pending\" | \"approved\" | \"minting\" | \"consumed\" | \"expired\";\n tokenJti: string | null;\n createdAt: number | null;\n expiresAt: number | null;\n consumedAt: number | null;\n}\n\nconst USER_CODE_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\"; // Crockford-ish base32, no 0/1/O/I\n\n/** Crypto-random short human-typable code, formatted `XXXX-XXXX`. */\nfunction generateUserCode(): string {\n const bytes = randomBytes(8);\n let out = \"\";\n for (let i = 0; i < 8; i++) {\n out += USER_CODE_ALPHABET[bytes[i] % USER_CODE_ALPHABET.length];\n if (i === 3) out += \"-\";\n }\n return out;\n}\n\nfunction generateDeviceCode(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\n/**\n * Create a new device+user code pair. Rate-limited: at most\n * `DEVICE_START_MAX` codes within `DEVICE_START_WINDOW_MS`. The window count\n * is a coarse global cap (this endpoint is unauthenticated) — enough to stop\n * table flooding / user-code brute force without per-IP plumbing.\n *\n * Throws `RATE_LIMITED` when the cap is exceeded so the route can map it to a\n * 429.\n */\nexport async function createDeviceCode(): Promise<DeviceCodeRow> {\n await ensureTable();\n const client = getDbExec();\n\n const now = Date.now();\n try {\n const { rows } = await client.execute({\n sql: `SELECT COUNT(*) AS n FROM mcp_device_codes WHERE created_at > ?`,\n args: [now - DEVICE_START_WINDOW_MS],\n });\n const n = Number(rows[0]?.n ?? rows[0]?.[\"COUNT(*)\"] ?? 0);\n if (Number.isFinite(n) && n >= DEVICE_START_MAX) {\n throw new Error(\"RATE_LIMITED\");\n }\n } catch (err: any) {\n if (err?.message === \"RATE_LIMITED\") throw err;\n // A read failure here should not block legitimate device starts — the\n // single-use + short-TTL design is the primary protection. Continue.\n }\n\n const deviceCode = generateDeviceCode();\n const userCode = generateUserCode();\n const expiresAt = now + DEVICE_CODE_TTL_MS;\n await client.execute({\n sql: `INSERT INTO mcp_device_codes (device_code, user_code, owner_email, org_id, status, token_jti, created_at, expires_at, consumed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n deviceCode,\n userCode,\n null,\n null,\n \"pending\",\n null,\n now,\n expiresAt,\n null,\n ],\n });\n return {\n deviceCode,\n userCode,\n ownerEmail: null,\n orgId: null,\n status: \"pending\",\n tokenJti: null,\n createdAt: now,\n expiresAt,\n consumedAt: null,\n };\n}\n\nfunction mapDeviceRow(r: any): DeviceCodeRow {\n return {\n deviceCode: (r.device_code ?? r.deviceCode) as string,\n userCode: (r.user_code ?? r.userCode) as string,\n ownerEmail: (r.owner_email ?? r.ownerEmail ?? null) as string | null,\n orgId: (r.org_id ?? r.orgId ?? null) as string | null,\n status: (r.status ?? \"pending\") as DeviceCodeRow[\"status\"],\n tokenJti: (r.token_jti ?? r.tokenJti ?? null) as string | null,\n createdAt: numOrNull(r.created_at ?? r.createdAt),\n expiresAt: numOrNull(r.expires_at ?? r.expiresAt),\n consumedAt: numOrNull(r.consumed_at ?? r.consumedAt),\n };\n}\n\nexport async function getDeviceCode(\n deviceCode: string,\n): Promise<DeviceCodeRow | null> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_device_codes WHERE device_code = ?`,\n args: [deviceCode],\n });\n if (rows.length === 0) return null;\n return mapDeviceRow(rows[0]);\n } catch (err) {\n if (isConnectionError(err)) return null;\n throw err;\n }\n}\n\nexport async function getDeviceCodeByUserCode(\n userCode: string,\n): Promise<DeviceCodeRow | null> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_device_codes WHERE user_code = ?`,\n args: [userCode],\n });\n if (rows.length === 0) return null;\n return mapDeviceRow(rows[0]);\n } catch (err) {\n if (isConnectionError(err)) return null;\n throw err;\n }\n}\n\n/**\n * Bind the logged-in user (email + org) to a pending device code, identified\n * by its human-typable `user_code`. Only transitions a non-expired, still\n * `pending` row. Returns the bound row, or a string error code:\n * - `not_found` — no such user_code\n * - `expired` — past its TTL\n * - `already` — already approved/consumed (not re-bindable)\n */\nexport async function approveDeviceCode(\n userCode: string,\n ownerEmail: string,\n orgId: string | null,\n): Promise<DeviceCodeRow | \"not_found\" | \"expired\" | \"already\"> {\n await ensureTable();\n const client = getDbExec();\n const row = await getDeviceCodeByUserCode(userCode);\n if (!row) return \"not_found\";\n if ((row.expiresAt ?? 0) < Date.now()) return \"expired\";\n if (row.status !== \"pending\") return \"already\";\n\n const result = await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'approved', owner_email = ?, org_id = ? WHERE user_code = ? AND status = 'pending'`,\n args: [ownerEmail, orgId, userCode],\n });\n if (result.rowsAffected === 0) {\n // Lost a race with another approve — re-read to report the real state.\n const fresh = await getDeviceCodeByUserCode(userCode);\n return fresh && fresh.status !== \"pending\" ? \"already\" : \"not_found\";\n }\n return {\n ...row,\n status: \"approved\",\n ownerEmail,\n orgId,\n };\n}\n\n/**\n * Atomically transition an approved device code to consumed and stamp the\n * minted token's jti. Single-use: only succeeds when the row is currently\n * `approved` (not already consumed). Returns the pre-consume row on success,\n * or null when it could not be consumed (already consumed / not approved /\n * gone). The caller mints the token only after this returns a row.\n */\nexport async function consumeDeviceCode(\n deviceCode: string,\n tokenJti: string,\n): Promise<DeviceCodeRow | null> {\n await ensureTable();\n const client = getDbExec();\n const row = await getDeviceCode(deviceCode);\n if (!row) return null;\n if (row.status !== \"approved\") return null;\n const result = await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'consumed', token_jti = ?, consumed_at = ? WHERE device_code = ? AND status = 'approved'`,\n args: [tokenJti, Date.now(), deviceCode],\n });\n if (result.rowsAffected === 0) return null; // lost the single-use race\n return row;\n}\n\n/**\n * Claim an approved device code for token minting without making it terminal.\n * If signing or token recording fails, callers release this back to approved\n * so the CLI can retry the poll instead of being stuck at \"consumed\".\n */\nexport async function claimDeviceCodeForMint(\n deviceCode: string,\n tokenJti: string,\n): Promise<DeviceCodeRow | null> {\n await ensureTable();\n const client = getDbExec();\n const row = await getDeviceCode(deviceCode);\n if (!row || row.status !== \"approved\") return null;\n const result = await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'minting', token_jti = ?, consumed_at = ? WHERE device_code = ? AND status = 'approved'`,\n args: [tokenJti, Date.now(), deviceCode],\n });\n if (result.rowsAffected === 0) return null;\n return row;\n}\n\nexport async function finishDeviceCodeMint(\n deviceCode: string,\n tokenJti: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const result = await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'consumed' WHERE device_code = ? AND status = 'minting' AND token_jti = ?`,\n args: [deviceCode, tokenJti],\n });\n return result.rowsAffected > 0;\n}\n\nexport async function releaseDeviceCodeMint(\n deviceCode: string,\n tokenJti: string,\n): Promise<void> {\n try {\n await ensureTable();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'approved', token_jti = NULL, consumed_at = NULL WHERE device_code = ? AND status = 'minting' AND token_jti = ?`,\n args: [deviceCode, tokenJti],\n });\n } catch {\n // The next poll will keep returning pending for a minting row until a\n // later cleanup/retry path can observe or repair it. Do not throw here.\n }\n}\n\n/**\n * Best-effort: flip an expired, still-pending/approved row to `expired` so\n * the poll endpoint can report a clean terminal state. Swallows errors.\n */\nexport async function expireDeviceCode(deviceCode: string): Promise<void> {\n try {\n await ensureTable();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'expired' WHERE device_code = ? AND status IN ('pending','approved')`,\n args: [deviceCode],\n });\n } catch {\n // The poll handler already treats past-TTL rows as expired regardless of\n // whether this housekeeping write lands.\n }\n}\n\nfunction numOrNull(v: unknown): number | null {\n if (v == null) return null;\n const n = Number(v);\n return Number.isFinite(n) ? n : null;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"connect-store.js","sourceRoot":"","sources":["../../src/mcp/connect-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Framework-table store for the \"connect external agents\" feature.\n *\n * Two additive, dialect-agnostic tables back the browser **Connect** page and\n * the OAuth-style **device-code flow** a CLI drives:\n *\n * - `mcp_connect_tokens` — one row per minted MCP token. We never store the\n * token value (it's a signed JWT); only its `jti` so revocation is a\n * SQL lookup. Revoking sets `revoked_at`; the row is never deleted.\n * - `mcp_device_codes` — short-lived (10 min) device/user code pairs for\n * the OAuth 2.0 device-authorization-style CLI flow. Single-use\n * (`consumed_at`), rate-limited at creation.\n *\n * Mirrors `application-state/store.ts`: lazy `ensureTable()`, `getDbExec()`,\n * `isPostgres()` dialect branching for upserts, `isConnectionError()` swallow\n * so a transient Neon WS drop never 500s. `CREATE TABLE IF NOT EXISTS` only —\n * strictly additive, never DROP / ALTER (shared prod DB rule).\n */\n\nimport { getDbExec, isConnectionError, intType } from \"../db/client.js\";\nimport { randomBytes, randomUUID } from \"node:crypto\";\n\nlet _initPromise: Promise<void> | undefined;\n\n/**\n * Scope claim that marks a connect-minted token (vs. an ordinary A2A\n * delegation JWT). Only tokens carrying this scope go through the revoke\n * lookup in `verifyAuth` — defined here so both `connect-route.ts` and\n * `build-server.ts` import it from the leaf store without a cycle.\n */\nexport const MCP_CONNECT_SCOPE = \"mcp-connect\";\n\n/**\n * Client id used when connect/device flows have to mint a standard MCP OAuth\n * access token instead of an A2A JWT (for deployments without A2A_SECRET).\n */\nexport const MCP_CONNECT_OAUTH_CLIENT_ID = \"agent-native-connect\";\n\n/** Device codes are valid for 10 minutes. */\nexport const DEVICE_CODE_TTL_MS = 10 * 60_000;\n\n/** Default minted-token lifetime. Configurable per-request 1–365 days. */\nexport const DEFAULT_TOKEN_TTL_DAYS = 365;\nexport const MIN_TOKEN_TTL_DAYS = 1;\nexport const MAX_TOKEN_TTL_DAYS = 365;\n\n/**\n * Rate limit for `device/start`: at most this many device codes may be created\n * within `DEVICE_START_WINDOW_MS`. Unauthenticated endpoint — keep it tight so\n * a hostile client can't flood the table or brute-force user codes.\n */\nexport const DEVICE_START_MAX = 20;\nexport const DEVICE_START_WINDOW_MS = 60_000;\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n // Additive only. Never DROP / ALTER — this DB is shared across every\n // deploy context (preview/branch/prod) for hosted templates.\n await client.execute(`\n CREATE TABLE IF NOT EXISTS mcp_connect_tokens (\n id TEXT PRIMARY KEY,\n jti TEXT UNIQUE NOT NULL,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n label TEXT,\n created_at ${intType()},\n last_used_at ${intType()},\n revoked_at ${intType()}\n )\n `);\n await client.execute(`\n CREATE TABLE IF NOT EXISTS mcp_device_codes (\n device_code TEXT PRIMARY KEY,\n user_code TEXT NOT NULL,\n owner_email TEXT,\n org_id TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n token_jti TEXT,\n created_at ${intType()},\n expires_at ${intType()},\n consumed_at ${intType()}\n )\n `);\n })().catch((err) => {\n // Don't cache a rejected init. A transient DB blip should let the next\n // connect/mint/revoke call retry rather than wedging the process.\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\n// ---------------------------------------------------------------------------\n// Minted-token records\n// ---------------------------------------------------------------------------\n\nexport interface MintedTokenRow {\n id: string;\n jti: string;\n ownerEmail: string;\n orgId: string | null;\n label: string | null;\n createdAt: number | null;\n lastUsedAt: number | null;\n revokedAt: number | null;\n}\n\n/**\n * Persist a record of a minted token. The token value itself (a signed JWT)\n * is NEVER stored — only its `jti`, so revocation is a cheap SQL lookup.\n */\nexport async function recordMintedToken(params: {\n jti: string;\n ownerEmail: string;\n orgId?: string | null;\n label?: string | null;\n}): Promise<string> {\n await ensureTable();\n const client = getDbExec();\n const id = randomUUID();\n await client.execute({\n sql: `INSERT INTO mcp_connect_tokens (id, jti, owner_email, org_id, label, created_at, last_used_at, revoked_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n id,\n params.jti,\n params.ownerEmail,\n params.orgId ?? null,\n params.label ?? null,\n Date.now(),\n null,\n null,\n ],\n });\n return id;\n}\n\n/**\n * Returns true when the given `jti` corresponds to a token that has been\n * revoked. Fails OPEN on a store/DB error: a transient Neon WS drop must not\n * lock every connected agent out. Signature verification is unaffected — this\n * is only the post-verify revoke check (see `verifyAuth` in build-server.ts).\n */\nexport async function isJtiRevoked(jti: string): Promise<boolean> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT revoked_at FROM mcp_connect_tokens WHERE jti = ?`,\n args: [jti],\n });\n if (rows.length === 0) return false;\n const revokedAt = rows[0].revoked_at ?? rows[0].revokedAt;\n return revokedAt != null;\n } catch (err) {\n // Fail open: a DB blip must not turn every minted token into a 401.\n // (Signature checks already passed; this only gates explicit revokes.)\n if (isConnectionError(err)) return false;\n return false;\n }\n}\n\nexport async function listTokens(\n ownerEmail: string,\n): Promise<MintedTokenRow[]> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, jti, owner_email, org_id, label, created_at, last_used_at, revoked_at FROM mcp_connect_tokens WHERE owner_email = ? ORDER BY created_at DESC`,\n args: [ownerEmail],\n });\n return rows.map((r: any) => ({\n id: r.id as string,\n jti: r.jti as string,\n ownerEmail: (r.owner_email ?? r.ownerEmail) as string,\n orgId: (r.org_id ?? r.orgId ?? null) as string | null,\n label: (r.label ?? null) as string | null,\n createdAt: numOrNull(r.created_at ?? r.createdAt),\n lastUsedAt: numOrNull(r.last_used_at ?? r.lastUsedAt),\n revokedAt: numOrNull(r.revoked_at ?? r.revokedAt),\n }));\n } catch (err) {\n if (isConnectionError(err)) return [];\n throw err;\n }\n}\n\n/**\n * Revoke a token, but ONLY if it is owned by `ownerEmail` (the caller). The\n * `owner_email = ?` predicate is the access scope — a caller can never revoke\n * another user's token. Idempotent: re-revoking keeps the first timestamp.\n * Returns true when a row was actually transitioned to revoked.\n */\nexport async function revokeToken(\n ownerEmail: string,\n id: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const result = await client.execute({\n sql: `UPDATE mcp_connect_tokens SET revoked_at = ? WHERE id = ? AND owner_email = ? AND revoked_at IS NULL`,\n args: [Date.now(), id, ownerEmail],\n });\n return result.rowsAffected > 0;\n}\n\n/**\n * Best-effort: stamp `last_used_at` for a token. Swallows all errors — this is\n * pure telemetry and must never affect the auth path.\n */\nexport async function touchTokenUsed(jti: string): Promise<void> {\n try {\n await ensureTable();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE mcp_connect_tokens SET last_used_at = ? WHERE jti = ?`,\n args: [Date.now(), jti],\n });\n } catch {\n // last_used_at is informational only — never throw from the hot path.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Device-code flow (OAuth 2.0 device-authorization style)\n// ---------------------------------------------------------------------------\n\nexport interface DeviceCodeRow {\n deviceCode: string;\n userCode: string;\n ownerEmail: string | null;\n orgId: string | null;\n status: \"pending\" | \"approved\" | \"minting\" | \"consumed\" | \"expired\";\n tokenJti: string | null;\n createdAt: number | null;\n expiresAt: number | null;\n consumedAt: number | null;\n}\n\nconst USER_CODE_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\"; // Crockford-ish base32, no 0/1/O/I\n\n/** Crypto-random short human-typable code, formatted `XXXX-XXXX`. */\nfunction generateUserCode(): string {\n const bytes = randomBytes(8);\n let out = \"\";\n for (let i = 0; i < 8; i++) {\n out += USER_CODE_ALPHABET[bytes[i] % USER_CODE_ALPHABET.length];\n if (i === 3) out += \"-\";\n }\n return out;\n}\n\nfunction generateDeviceCode(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\n/**\n * Create a new device+user code pair. Rate-limited: at most\n * `DEVICE_START_MAX` codes within `DEVICE_START_WINDOW_MS`. The window count\n * is a coarse global cap (this endpoint is unauthenticated) — enough to stop\n * table flooding / user-code brute force without per-IP plumbing.\n *\n * Throws `RATE_LIMITED` when the cap is exceeded so the route can map it to a\n * 429.\n */\nexport async function createDeviceCode(): Promise<DeviceCodeRow> {\n await ensureTable();\n const client = getDbExec();\n\n const now = Date.now();\n try {\n const { rows } = await client.execute({\n sql: `SELECT COUNT(*) AS n FROM mcp_device_codes WHERE created_at > ?`,\n args: [now - DEVICE_START_WINDOW_MS],\n });\n const n = Number(rows[0]?.n ?? rows[0]?.[\"COUNT(*)\"] ?? 0);\n if (Number.isFinite(n) && n >= DEVICE_START_MAX) {\n throw new Error(\"RATE_LIMITED\");\n }\n } catch (err: any) {\n if (err?.message === \"RATE_LIMITED\") throw err;\n // A read failure here should not block legitimate device starts — the\n // single-use + short-TTL design is the primary protection. Continue.\n }\n\n const deviceCode = generateDeviceCode();\n const userCode = generateUserCode();\n const expiresAt = now + DEVICE_CODE_TTL_MS;\n await client.execute({\n sql: `INSERT INTO mcp_device_codes (device_code, user_code, owner_email, org_id, status, token_jti, created_at, expires_at, consumed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n deviceCode,\n userCode,\n null,\n null,\n \"pending\",\n null,\n now,\n expiresAt,\n null,\n ],\n });\n return {\n deviceCode,\n userCode,\n ownerEmail: null,\n orgId: null,\n status: \"pending\",\n tokenJti: null,\n createdAt: now,\n expiresAt,\n consumedAt: null,\n };\n}\n\nfunction mapDeviceRow(r: any): DeviceCodeRow {\n return {\n deviceCode: (r.device_code ?? r.deviceCode) as string,\n userCode: (r.user_code ?? r.userCode) as string,\n ownerEmail: (r.owner_email ?? r.ownerEmail ?? null) as string | null,\n orgId: (r.org_id ?? r.orgId ?? null) as string | null,\n status: (r.status ?? \"pending\") as DeviceCodeRow[\"status\"],\n tokenJti: (r.token_jti ?? r.tokenJti ?? null) as string | null,\n createdAt: numOrNull(r.created_at ?? r.createdAt),\n expiresAt: numOrNull(r.expires_at ?? r.expiresAt),\n consumedAt: numOrNull(r.consumed_at ?? r.consumedAt),\n };\n}\n\nexport async function getDeviceCode(\n deviceCode: string,\n): Promise<DeviceCodeRow | null> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_device_codes WHERE device_code = ?`,\n args: [deviceCode],\n });\n if (rows.length === 0) return null;\n return mapDeviceRow(rows[0]);\n } catch (err) {\n if (isConnectionError(err)) return null;\n throw err;\n }\n}\n\nexport async function getDeviceCodeByUserCode(\n userCode: string,\n): Promise<DeviceCodeRow | null> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_device_codes WHERE user_code = ?`,\n args: [userCode],\n });\n if (rows.length === 0) return null;\n return mapDeviceRow(rows[0]);\n } catch (err) {\n if (isConnectionError(err)) return null;\n throw err;\n }\n}\n\n/**\n * Bind the logged-in user (email + org) to a pending device code, identified\n * by its human-typable `user_code`. Only transitions a non-expired, still\n * `pending` row. Returns the bound row, or a string error code:\n * - `not_found` — no such user_code\n * - `expired` — past its TTL\n * - `already` — already approved/consumed (not re-bindable)\n */\nexport async function approveDeviceCode(\n userCode: string,\n ownerEmail: string,\n orgId: string | null,\n): Promise<DeviceCodeRow | \"not_found\" | \"expired\" | \"already\"> {\n await ensureTable();\n const client = getDbExec();\n const row = await getDeviceCodeByUserCode(userCode);\n if (!row) return \"not_found\";\n if ((row.expiresAt ?? 0) < Date.now()) return \"expired\";\n if (row.status !== \"pending\") return \"already\";\n\n const result = await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'approved', owner_email = ?, org_id = ? WHERE user_code = ? AND status = 'pending'`,\n args: [ownerEmail, orgId, userCode],\n });\n if (result.rowsAffected === 0) {\n // Lost a race with another approve — re-read to report the real state.\n const fresh = await getDeviceCodeByUserCode(userCode);\n return fresh && fresh.status !== \"pending\" ? \"already\" : \"not_found\";\n }\n return {\n ...row,\n status: \"approved\",\n ownerEmail,\n orgId,\n };\n}\n\n/**\n * Atomically transition an approved device code to consumed and stamp the\n * minted token's jti. Single-use: only succeeds when the row is currently\n * `approved` (not already consumed). Returns the pre-consume row on success,\n * or null when it could not be consumed (already consumed / not approved /\n * gone). The caller mints the token only after this returns a row.\n */\nexport async function consumeDeviceCode(\n deviceCode: string,\n tokenJti: string,\n): Promise<DeviceCodeRow | null> {\n await ensureTable();\n const client = getDbExec();\n const row = await getDeviceCode(deviceCode);\n if (!row) return null;\n if (row.status !== \"approved\") return null;\n const result = await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'consumed', token_jti = ?, consumed_at = ? WHERE device_code = ? AND status = 'approved'`,\n args: [tokenJti, Date.now(), deviceCode],\n });\n if (result.rowsAffected === 0) return null; // lost the single-use race\n return row;\n}\n\n/**\n * Claim an approved device code for token minting without making it terminal.\n * If signing or token recording fails, callers release this back to approved\n * so the CLI can retry the poll instead of being stuck at \"consumed\".\n */\nexport async function claimDeviceCodeForMint(\n deviceCode: string,\n tokenJti: string,\n): Promise<DeviceCodeRow | null> {\n await ensureTable();\n const client = getDbExec();\n const row = await getDeviceCode(deviceCode);\n if (!row || row.status !== \"approved\") return null;\n const result = await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'minting', token_jti = ?, consumed_at = ? WHERE device_code = ? AND status = 'approved'`,\n args: [tokenJti, Date.now(), deviceCode],\n });\n if (result.rowsAffected === 0) return null;\n return row;\n}\n\nexport async function finishDeviceCodeMint(\n deviceCode: string,\n tokenJti: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const result = await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'consumed' WHERE device_code = ? AND status = 'minting' AND token_jti = ?`,\n args: [deviceCode, tokenJti],\n });\n return result.rowsAffected > 0;\n}\n\nexport async function releaseDeviceCodeMint(\n deviceCode: string,\n tokenJti: string,\n): Promise<void> {\n try {\n await ensureTable();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'approved', token_jti = NULL, consumed_at = NULL WHERE device_code = ? AND status = 'minting' AND token_jti = ?`,\n args: [deviceCode, tokenJti],\n });\n } catch {\n // The next poll will keep returning pending for a minting row until a\n // later cleanup/retry path can observe or repair it. Do not throw here.\n }\n}\n\n/**\n * Best-effort: flip an expired, still-pending/approved row to `expired` so\n * the poll endpoint can report a clean terminal state. Swallows errors.\n */\nexport async function expireDeviceCode(deviceCode: string): Promise<void> {\n try {\n await ensureTable();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'expired' WHERE device_code = ? AND status IN ('pending','approved')`,\n args: [deviceCode],\n });\n } catch {\n // The poll handler already treats past-TTL rows as expired regardless of\n // whether this housekeeping write lands.\n }\n}\n\nfunction numOrNull(v: unknown): number | null {\n if (v == null) return null;\n const n = Number(v);\n return Number.isFinite(n) ? n : null;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channels.d.ts","sourceRoot":"","sources":["../../src/notifications/channels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"channels.d.ts","sourceRoot":"","sources":["../../src/notifications/channels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAaH,wBAAgB,mCAAmC,IAAI,IAAI,CAQ1D"}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { registerNotificationChannel } from "./registry.js";
|
|
15
15
|
import { resolveKeyReferences, validateUrlAllowlist, getKeyAllowlist, } from "../secrets/substitution.js";
|
|
16
|
+
import { ssrfSafeFetch } from "../extensions/url-safety.js";
|
|
16
17
|
let _registered = false;
|
|
17
18
|
export function registerBuiltinNotificationChannels() {
|
|
18
19
|
if (_registered)
|
|
@@ -48,7 +49,7 @@ function createWebhookChannel(urlTemplate) {
|
|
|
48
49
|
throw new Error(`[notifications] webhook URL ${new URL(url).origin} is not in the allowlist for key "${name}"`);
|
|
49
50
|
}
|
|
50
51
|
});
|
|
51
|
-
const res = await
|
|
52
|
+
const res = await ssrfSafeFetch(url, {
|
|
52
53
|
method: "POST",
|
|
53
54
|
headers,
|
|
54
55
|
body: JSON.stringify({
|
|
@@ -59,7 +60,7 @@ function createWebhookChannel(urlTemplate) {
|
|
|
59
60
|
owner: meta.owner,
|
|
60
61
|
emittedAt: new Date().toISOString(),
|
|
61
62
|
}),
|
|
62
|
-
});
|
|
63
|
+
}, { maxRedirects: 3 });
|
|
63
64
|
if (!res.ok) {
|
|
64
65
|
throw new Error(`[notifications] webhook ${new URL(url).origin} returned ${res.status}${(await readErrorSnippet(res)) || ""}`);
|
|
65
66
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channels.js","sourceRoot":"","sources":["../../src/notifications/channels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAE5D,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,GAChB,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"channels.js","sourceRoot":"","sources":["../../src/notifications/channels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAE5D,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,GAChB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAE5D,IAAI,WAAW,GAAG,KAAK,CAAC;AAExB,MAAM,UAAU,mCAAmC;IACjD,IAAI,WAAW;QAAE,OAAO;IACxB,WAAW,GAAG,IAAI,CAAC;IAEnB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC;IAClD,IAAI,GAAG,EAAE,CAAC;QACR,2BAA2B,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC;IACzD,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB,CAAC,WAAmB;IAC/C,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC;IAC5D,OAAO;QACL,IAAI,EAAE,SAAS;QACf,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI;YACvB,mEAAmE;YACnE,mEAAmE;YACnE,gEAAgE;YAChE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,MAAM,oBAAoB,CAClD,WAAW,EACX,MAAM,EACN,IAAI,CAAC,KAAK,CACX,CAAC;YACF,MAAM,OAAO,GAA2B;gBACtC,cAAc,EAAE,kBAAkB;aACnC,CAAC;YACF,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,MAAM,oBAAoB,CACnD,YAAY,EACZ,MAAM,EACN,IAAI,CAAC,KAAK,CACX,CAAC;gBACF,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;YAC/B,CAAC;YAED,sEAAsE;YACtE,oEAAoE;YACpE,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CACzB,IAAI,GAAG,CACL,KAAK,CAAC,IAAI,CACR,WAAW,CAAC,QAAQ,CAAC,+BAA+B,CAAC,EACrD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CACZ,CACF,CACF,CAAC;YACF,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,GAAG,CAClC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAClE,CAAC;YACF,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;gBAC3B,IAAI,CAAC,oBAAoB,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC9C,MAAM,IAAI,KAAK,CACb,+BAA+B,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,qCAAqC,IAAI,GAAG,CAC/F,CAAC;gBACJ,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,MAAM,GAAG,GAAG,MAAM,aAAa,CAC7B,GAAG,EACH;gBACE,MAAM,EAAE,MAAM;gBACd,OAAO;gBACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,QAAQ,EAAE,KAAK,CAAC,QAAQ;oBACxB,KAAK,EAAE,KAAK,CAAC,KAAK;oBAClB,IAAI,EAAE,KAAK,CAAC,IAAI;oBAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;oBACxB,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;iBACpC,CAAC;aACH,EACD,EAAE,YAAY,EAAE,CAAC,EAAE,CACpB,CAAC;YACF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CACb,2BAA2B,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,aAAa,GAAG,CAAC,MAAM,GACnE,CAAC,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC,IAAI,EACnC,EAAE,CACH,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,gBAAgB,CAAC,GAAa;IAC3C,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC;IACrC,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACvB,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,MAAM,GAAG,GAAG,IAAI,CAAC;IACjB,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YACxB,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,IAAI;gBAAE,MAAM;YAChB,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,CAAC;QACD,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,OAAO,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AAClC,CAAC","sourcesContent":["/**\n * Built-in notification channels.\n *\n * Set environment variables to auto-register the webhook channel at startup.\n * Extra channels can be registered at any time via\n * `registerNotificationChannel()` from a server plugin.\n *\n * NOTIFICATIONS_WEBHOOK_URL → POST notifications as JSON to this URL.\n * Supports `${keys.NAME}` substitution — the raw\n * value never enters the agent context.\n * NOTIFICATIONS_WEBHOOK_AUTH → optional `Authorization` header value (also\n * supports `${keys.NAME}`).\n */\n\nimport { registerNotificationChannel } from \"./registry.js\";\nimport type { NotificationChannel } from \"./types.js\";\nimport {\n resolveKeyReferences,\n validateUrlAllowlist,\n getKeyAllowlist,\n} from \"../secrets/substitution.js\";\nimport { ssrfSafeFetch } from \"../extensions/url-safety.js\";\n\nlet _registered = false;\n\nexport function registerBuiltinNotificationChannels(): void {\n if (_registered) return;\n _registered = true;\n\n const url = process.env.NOTIFICATIONS_WEBHOOK_URL;\n if (url) {\n registerNotificationChannel(createWebhookChannel(url));\n }\n}\n\nfunction createWebhookChannel(urlTemplate: string): NotificationChannel {\n const authTemplate = process.env.NOTIFICATIONS_WEBHOOK_AUTH;\n return {\n name: \"webhook\",\n async deliver(input, meta) {\n // Resolve `${keys.NAME}` references against the owner's user-scope\n // secrets. Missing keys throw — the error surfaces in logs and the\n // channel is marked un-delivered, but other channels still run.\n const { resolved: url } = await resolveKeyReferences(\n urlTemplate,\n \"user\",\n meta.owner,\n );\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n if (authTemplate) {\n const { resolved: auth } = await resolveKeyReferences(\n authTemplate,\n \"user\",\n meta.owner,\n );\n headers.Authorization = auth;\n }\n\n // If the user set an allowlist on a referenced key, enforce it here —\n // origin-level check, same rule the automations fetch-tool applies.\n const keyNames = Array.from(\n new Set(\n Array.from(\n urlTemplate.matchAll(/\\$\\{keys\\.([A-Za-z0-9_-]+)\\}/g),\n (m) => m[1],\n ),\n ),\n );\n const allowlists = await Promise.all(\n keyNames.map((name) => getKeyAllowlist(name, \"user\", meta.owner)),\n );\n keyNames.forEach((name, i) => {\n if (!validateUrlAllowlist(url, allowlists[i])) {\n throw new Error(\n `[notifications] webhook URL ${new URL(url).origin} is not in the allowlist for key \"${name}\"`,\n );\n }\n });\n\n const res = await ssrfSafeFetch(\n url,\n {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n severity: input.severity,\n title: input.title,\n body: input.body,\n metadata: input.metadata,\n owner: meta.owner,\n emittedAt: new Date().toISOString(),\n }),\n },\n { maxRedirects: 3 },\n );\n if (!res.ok) {\n throw new Error(\n `[notifications] webhook ${new URL(url).origin} returned ${res.status}${\n (await readErrorSnippet(res)) || \"\"\n }`,\n );\n }\n },\n };\n}\n\n/**\n * Read up to ~1 KB from the body for error context. Streams chunks so a\n * misbehaving endpoint returning a large error page doesn't pin that whole\n * payload in memory per failed webhook.\n */\nasync function readErrorSnippet(res: Response): Promise<string> {\n const reader = res.body?.getReader();\n if (!reader) return \"\";\n const decoder = new TextDecoder();\n const MAX = 1024;\n let buf = \"\";\n try {\n while (buf.length < MAX) {\n const { value, done } = await reader.read();\n if (done) break;\n buf += decoder.decode(value, { stream: true });\n }\n reader.cancel().catch(() => {});\n } catch {\n return \"\";\n }\n if (!buf) return \"\";\n return `: ${buf.slice(0, 200)}`;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/oauth-tokens/store.ts"],"names":[],"mappings":"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","sourcesContent":["import { getDbExec, isPostgres, intType, type DbExec } from \"../db/client.js\";\n\nlet _initPromise: Promise<void> | undefined;\n\nfunction oauthTokensTable(): string {\n return isPostgres() ? \"public.oauth_tokens\" : \"oauth_tokens\";\n}\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n const table = oauthTokensTable();\n await client.execute(`\n CREATE TABLE IF NOT EXISTS ${table} (\n provider TEXT NOT NULL,\n account_id TEXT NOT NULL,\n owner TEXT,\n tokens TEXT NOT NULL,\n updated_at ${intType()} NOT NULL,\n PRIMARY KEY (provider, account_id)\n )\n `);\n // Migration: add owner column to existing tables\n try {\n await client.execute(`ALTER TABLE ${table} ADD COLUMN owner TEXT`);\n } catch {\n // Column already exists\n }\n // Migration: add display_name column\n try {\n await client.execute(\n `ALTER TABLE ${table} ADD COLUMN display_name TEXT`,\n );\n } catch {\n // Column already exists\n }\n // Backfill: set owner = account_id for existing rows without an owner\n await client.execute(\n `UPDATE ${table} SET owner = account_id WHERE owner IS NULL`,\n );\n })();\n }\n return _initPromise;\n}\n\nexport async function getOAuthTokens(\n provider: string,\n accountId: string,\n): Promise<Record<string, unknown> | null> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n const { rows } = await client.execute({\n sql: `SELECT tokens FROM ${table} WHERE provider = ? AND account_id = ?`,\n args: [provider, accountId],\n });\n if (rows.length === 0) return null;\n return JSON.parse(rows[0].tokens as string);\n}\n\n/**\n * Thrown when an OAuth save would re-bind an `(provider, account_id)` row\n * to a different owner than already holds it. Callers should catch this and\n * surface a clean \"this account is already linked to another user\" message\n * to the requester rather than letting it propagate as a 500.\n *\n * Carries `statusCode = 409` so route handlers using h3's `createError` can\n * pass it straight through.\n */\nexport class OAuthAccountOwnedByOtherUserError extends Error {\n readonly statusCode = 409;\n readonly provider: string;\n readonly accountId: string;\n readonly existingOwner: string;\n readonly attemptedOwner: string;\n constructor(opts: {\n provider: string;\n accountId: string;\n existingOwner: string;\n attemptedOwner: string;\n }) {\n super(\n `OAuth account ${opts.provider}:${opts.accountId} is already linked to another user — refusing to overwrite the owner.`,\n );\n this.name = \"OAuthAccountOwnedByOtherUserError\";\n this.provider = opts.provider;\n this.accountId = opts.accountId;\n this.existingOwner = opts.existingOwner;\n this.attemptedOwner = opts.attemptedOwner;\n }\n}\n\n/**\n * Save OAuth tokens. The `owner` parameter specifies which user owns this\n * account — defaults to `accountId` (the account itself is the owner).\n * For multi-account support, pass the logged-in user's email as owner.\n *\n * If the account already exists and is owned by a different user, throws\n * `OAuthAccountOwnedByOtherUserError` (statusCode 409) to prevent silently\n * stealing another user's linked account.\n *\n * Read + write happen as a single linearised batch (Postgres) or paired\n * statements (SQLite). On both backends the per-row PK serialises concurrent\n * writes for the same `(provider, account_id)` so the owner check cannot be\n * raced by an attacker calling saveOAuthTokens twice in flight — the second\n * caller sees the first caller's owner row and raises 409.\n */\nexport async function saveOAuthTokens(\n provider: string,\n accountId: string,\n tokens: Record<string, unknown>,\n owner?: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n\n // Read the current row before deciding what to write. We use this to\n // (a) preserve owner / display_name when this is a token refresh (no\n // owner argument), and (b) reject the write when the caller is trying\n // to overwrite a row owned by someone else.\n let resolvedOwner = owner ?? accountId;\n let existingDisplayName: string | null = null;\n let existingOwner: string | null = null;\n let existingTokens: Record<string, unknown> | null = null;\n const { rows: existing } = await client.execute({\n sql: `SELECT owner, display_name, tokens FROM ${table} WHERE provider = ? AND account_id = ?`,\n args: [provider, accountId],\n });\n if (existing.length > 0) {\n existingOwner = (existing[0].owner as string) ?? null;\n existingDisplayName = (existing[0].display_name as string) ?? null;\n existingTokens = JSON.parse((existing[0].tokens as string) ?? \"{}\");\n }\n\n if (!owner) {\n // Token-refresh path: keep the existing owner/displayName unchanged.\n if (existingOwner) resolvedOwner = existingOwner;\n } else if (existingOwner && owner && existingOwner !== owner) {\n // Refuse to silently re-bind an account from one user to another.\n // This is the case the docstring promised but the previous\n // implementation didn't enforce — `ON CONFLICT DO UPDATE SET\n // owner=EXCLUDED.owner` would have overwritten the prior owner.\n throw new OAuthAccountOwnedByOtherUserError({\n provider,\n accountId,\n existingOwner,\n attemptedOwner: owner,\n });\n }\n\n const cleanedIncomingTokens = Object.fromEntries(\n Object.entries(tokens).filter(([, value]) => value !== undefined),\n );\n const tokensToStore = {\n ...(existingTokens ?? {}),\n ...cleanedIncomingTokens,\n };\n\n await client.execute({\n sql: isPostgres()\n ? `INSERT INTO ${table} (provider, account_id, owner, display_name, tokens, updated_at) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (provider, account_id) DO UPDATE SET owner=EXCLUDED.owner, display_name=COALESCE(EXCLUDED.display_name, ${table}.display_name), tokens=EXCLUDED.tokens, updated_at=EXCLUDED.updated_at`\n : `INSERT OR REPLACE INTO ${table} (provider, account_id, owner, display_name, tokens, updated_at) VALUES (?, ?, ?, ?, ?, ?)`,\n args: [\n provider,\n accountId,\n resolvedOwner,\n existingDisplayName,\n JSON.stringify(tokensToStore),\n Date.now(),\n ],\n });\n}\n\nexport async function deleteOAuthTokens(\n provider: string,\n accountId?: string,\n): Promise<number> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n if (accountId) {\n const result = await client.execute({\n sql: `DELETE FROM ${table} WHERE provider = ? AND account_id = ?`,\n args: [provider, accountId],\n });\n return result.rowsAffected;\n }\n const result = await client.execute({\n sql: `DELETE FROM ${table} WHERE provider = ?`,\n args: [provider],\n });\n return result.rowsAffected;\n}\n\nexport async function listOAuthAccounts(provider: string): Promise<\n Array<{\n accountId: string;\n owner: string | null;\n tokens: Record<string, unknown>;\n }>\n> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n const { rows } = await client.execute({\n sql: `SELECT account_id, owner, tokens FROM ${table} WHERE provider = ?`,\n args: [provider],\n });\n return rows.map((row) => ({\n accountId: row.account_id as string,\n owner: (row.owner as string) ?? null,\n tokens: JSON.parse(row.tokens as string),\n }));\n}\n\n/**\n * List all OAuth accounts owned by a specific user.\n * In multi-account mode, a user may have connected multiple Google accounts.\n */\nexport async function listOAuthAccountsByOwner(\n provider: string,\n owner: string,\n): Promise<\n Array<{\n accountId: string;\n displayName: string | null;\n tokens: Record<string, unknown>;\n }>\n> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n const { rows } = await client.execute({\n sql: `SELECT account_id, display_name, tokens FROM ${table} WHERE provider = ? AND owner = ?`,\n args: [provider, owner],\n });\n return rows.map((row) => ({\n accountId: row.account_id as string,\n displayName: (row.display_name as string) ?? null,\n tokens: JSON.parse(row.tokens as string),\n }));\n}\n\n/**\n * Set the display name for an OAuth account (e.g. Google profile name).\n */\nexport async function setOAuthDisplayName(\n provider: string,\n accountId: string,\n displayName: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n await client.execute({\n sql: `UPDATE ${table} SET display_name = ? WHERE provider = ? AND account_id = ?`,\n args: [displayName, provider, accountId],\n });\n}\n\n/**\n * Check whether a specific user has tokens for a provider.\n *\n * `owner` is REQUIRED. The previous unscoped form leaked information\n * across users — the onboarding banner would mark the OAuth secret as\n * \"set\" for user B as soon as ANY user in the deployment connected the\n * provider, and user B would never see the prompt to connect.\n */\nexport async function hasOAuthTokens(\n provider: string,\n owner: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n const { rows } = await client.execute({\n sql: `SELECT 1 FROM ${table} WHERE provider = ? AND owner = ? LIMIT 1`,\n args: [provider, owner],\n });\n return rows.length > 0;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/oauth-tokens/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAEjE,IAAI,YAAuC,CAAC;AAE5C,SAAS,gBAAgB;IACvB,OAAO,UAAU,EAAE,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,cAAc,CAAC;AAC/D,CAAC;AAED,KAAK,UAAU,WAAW;IACxB,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,YAAY,GAAG,CAAC,KAAK,IAAI,EAAE;YACzB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;YACjC,MAAM,MAAM,CAAC,OAAO,CAAC;qCACU,KAAK;;;;;uBAKnB,OAAO,EAAE;;;OAGzB,CAAC,CAAC;YACH,iDAAiD;YACjD,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,OAAO,CAAC,eAAe,KAAK,wBAAwB,CAAC,CAAC;YACrE,CAAC;YAAC,MAAM,CAAC;gBACP,wBAAwB;YAC1B,CAAC;YACD,qCAAqC;YACrC,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,OAAO,CAClB,eAAe,KAAK,+BAA+B,CACpD,CAAC;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,wBAAwB;YAC1B,CAAC;YACD,sEAAsE;YACtE,MAAM,MAAM,CAAC,OAAO,CAClB,UAAU,KAAK,6CAA6C,CAC7D,CAAC;QACJ,CAAC,CAAC,EAAE,CAAC;IACP,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAAgB,EAChB,SAAiB;IAEjB,MAAM,WAAW,EAAE,CAAC;IACpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;IACjC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;QACpC,GAAG,EAAE,sBAAsB,KAAK,wCAAwC;QACxE,IAAI,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC;KAC5B,CAAC,CAAC;IACH,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAgB,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,OAAO,iCAAkC,SAAQ,KAAK;IACjD,UAAU,GAAG,GAAG,CAAC;IACjB,QAAQ,CAAS;IACjB,SAAS,CAAS;IAClB,aAAa,CAAS;IACtB,cAAc,CAAS;IAChC,YAAY,IAKX;QACC,KAAK,CACH,iBAAiB,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,SAAS,uEAAuE,CACxH,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,mCAAmC,CAAC;QAChD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QAChC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC;QACxC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC;IAC5C,CAAC;CACF;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,SAAiB,EACjB,MAA+B,EAC/B,KAAc;IAEd,MAAM,WAAW,EAAE,CAAC;IACpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;IAEjC,qEAAqE;IACrE,qEAAqE;IACrE,sEAAsE;IACtE,4CAA4C;IAC5C,IAAI,aAAa,GAAG,KAAK,IAAI,SAAS,CAAC;IACvC,IAAI,mBAAmB,GAAkB,IAAI,CAAC;IAC9C,IAAI,aAAa,GAAkB,IAAI,CAAC;IACxC,IAAI,cAAc,GAAmC,IAAI,CAAC;IAC1D,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;QAC9C,GAAG,EAAE,2CAA2C,KAAK,wCAAwC;QAC7F,IAAI,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC;KAC5B,CAAC,CAAC;IACH,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,aAAa,GAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAgB,IAAI,IAAI,CAAC;QACtD,mBAAmB,GAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,YAAuB,IAAI,IAAI,CAAC;QACnE,cAAc,GAAG,IAAI,CAAC,KAAK,CAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAiB,IAAI,IAAI,CAAC,CAAC;IACtE,CAAC;IAED,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,qEAAqE;QACrE,IAAI,aAAa;YAAE,aAAa,GAAG,aAAa,CAAC;IACnD,CAAC;SAAM,IAAI,aAAa,IAAI,KAAK,IAAI,aAAa,KAAK,KAAK,EAAE,CAAC;QAC7D,kEAAkE;QAClE,2DAA2D;QAC3D,6DAA6D;QAC7D,gEAAgE;QAChE,MAAM,IAAI,iCAAiC,CAAC;YAC1C,QAAQ;YACR,SAAS;YACT,aAAa;YACb,cAAc,EAAE,KAAK;SACtB,CAAC,CAAC;IACL,CAAC;IAED,MAAM,qBAAqB,GAAG,MAAM,CAAC,WAAW,CAC9C,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAClE,CAAC;IACF,MAAM,aAAa,GAAG;QACpB,GAAG,CAAC,cAAc,IAAI,EAAE,CAAC;QACzB,GAAG,qBAAqB;KACzB,CAAC;IAEF,MAAM,MAAM,CAAC,OAAO,CAAC;QACnB,GAAG,EAAE,UAAU,EAAE;YACf,CAAC,CAAC,eAAe,KAAK,kNAAkN,KAAK,wEAAwE;YACrT,CAAC,CAAC,0BAA0B,KAAK,4FAA4F;QAC/H,IAAI,EAAE;YACJ,QAAQ;YACR,SAAS;YACT,aAAa;YACb,mBAAmB;YACnB,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC;YAC7B,IAAI,CAAC,GAAG,EAAE;SACX;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAgB,EAChB,SAAkB;IAElB,MAAM,WAAW,EAAE,CAAC;IACpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;IACjC,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;YAClC,GAAG,EAAE,eAAe,KAAK,wCAAwC;YACjE,IAAI,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC;SAC5B,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,YAAY,CAAC;IAC7B,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;QAClC,GAAG,EAAE,eAAe,KAAK,qBAAqB;QAC9C,IAAI,EAAE,CAAC,QAAQ,CAAC;KACjB,CAAC,CAAC;IACH,OAAO,MAAM,CAAC,YAAY,CAAC;AAC7B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,QAAgB;IAOtD,MAAM,WAAW,EAAE,CAAC;IACpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;IACjC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;QACpC,GAAG,EAAE,yCAAyC,KAAK,qBAAqB;QACxE,IAAI,EAAE,CAAC,QAAQ,CAAC;KACjB,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACxB,SAAS,EAAE,GAAG,CAAC,UAAoB;QACnC,KAAK,EAAG,GAAG,CAAC,KAAgB,IAAI,IAAI;QACpC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAgB,CAAC;KACzC,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,QAAgB,EAChB,KAAa;IAQb,MAAM,WAAW,EAAE,CAAC;IACpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;IACjC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;QACpC,GAAG,EAAE,gDAAgD,KAAK,mCAAmC;QAC7F,IAAI,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC;KACxB,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACxB,SAAS,EAAE,GAAG,CAAC,UAAoB;QACnC,WAAW,EAAG,GAAG,CAAC,YAAuB,IAAI,IAAI;QACjD,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAgB,CAAC;KACzC,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,QAAgB,EAChB,SAAiB,EACjB,WAAmB;IAEnB,MAAM,WAAW,EAAE,CAAC;IACpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;IACjC,MAAM,MAAM,CAAC,OAAO,CAAC;QACnB,GAAG,EAAE,UAAU,KAAK,6DAA6D;QACjF,IAAI,EAAE,CAAC,WAAW,EAAE,QAAQ,EAAE,SAAS,CAAC;KACzC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAAgB,EAChB,KAAa;IAEb,MAAM,WAAW,EAAE,CAAC;IACpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;IACjC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;QACpC,GAAG,EAAE,iBAAiB,KAAK,2CAA2C;QACtE,IAAI,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC;KACxB,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;AACzB,CAAC","sourcesContent":["import { getDbExec, isPostgres, intType } from \"../db/client.js\";\n\nlet _initPromise: Promise<void> | undefined;\n\nfunction oauthTokensTable(): string {\n return isPostgres() ? \"public.oauth_tokens\" : \"oauth_tokens\";\n}\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n const table = oauthTokensTable();\n await client.execute(`\n CREATE TABLE IF NOT EXISTS ${table} (\n provider TEXT NOT NULL,\n account_id TEXT NOT NULL,\n owner TEXT,\n tokens TEXT NOT NULL,\n updated_at ${intType()} NOT NULL,\n PRIMARY KEY (provider, account_id)\n )\n `);\n // Migration: add owner column to existing tables\n try {\n await client.execute(`ALTER TABLE ${table} ADD COLUMN owner TEXT`);\n } catch {\n // Column already exists\n }\n // Migration: add display_name column\n try {\n await client.execute(\n `ALTER TABLE ${table} ADD COLUMN display_name TEXT`,\n );\n } catch {\n // Column already exists\n }\n // Backfill: set owner = account_id for existing rows without an owner\n await client.execute(\n `UPDATE ${table} SET owner = account_id WHERE owner IS NULL`,\n );\n })();\n }\n return _initPromise;\n}\n\nexport async function getOAuthTokens(\n provider: string,\n accountId: string,\n): Promise<Record<string, unknown> | null> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n const { rows } = await client.execute({\n sql: `SELECT tokens FROM ${table} WHERE provider = ? AND account_id = ?`,\n args: [provider, accountId],\n });\n if (rows.length === 0) return null;\n return JSON.parse(rows[0].tokens as string);\n}\n\n/**\n * Thrown when an OAuth save would re-bind an `(provider, account_id)` row\n * to a different owner than already holds it. Callers should catch this and\n * surface a clean \"this account is already linked to another user\" message\n * to the requester rather than letting it propagate as a 500.\n *\n * Carries `statusCode = 409` so route handlers using h3's `createError` can\n * pass it straight through.\n */\nexport class OAuthAccountOwnedByOtherUserError extends Error {\n readonly statusCode = 409;\n readonly provider: string;\n readonly accountId: string;\n readonly existingOwner: string;\n readonly attemptedOwner: string;\n constructor(opts: {\n provider: string;\n accountId: string;\n existingOwner: string;\n attemptedOwner: string;\n }) {\n super(\n `OAuth account ${opts.provider}:${opts.accountId} is already linked to another user — refusing to overwrite the owner.`,\n );\n this.name = \"OAuthAccountOwnedByOtherUserError\";\n this.provider = opts.provider;\n this.accountId = opts.accountId;\n this.existingOwner = opts.existingOwner;\n this.attemptedOwner = opts.attemptedOwner;\n }\n}\n\n/**\n * Save OAuth tokens. The `owner` parameter specifies which user owns this\n * account — defaults to `accountId` (the account itself is the owner).\n * For multi-account support, pass the logged-in user's email as owner.\n *\n * If the account already exists and is owned by a different user, throws\n * `OAuthAccountOwnedByOtherUserError` (statusCode 409) to prevent silently\n * stealing another user's linked account.\n *\n * Read + write happen as a single linearised batch (Postgres) or paired\n * statements (SQLite). On both backends the per-row PK serialises concurrent\n * writes for the same `(provider, account_id)` so the owner check cannot be\n * raced by an attacker calling saveOAuthTokens twice in flight — the second\n * caller sees the first caller's owner row and raises 409.\n */\nexport async function saveOAuthTokens(\n provider: string,\n accountId: string,\n tokens: Record<string, unknown>,\n owner?: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n\n // Read the current row before deciding what to write. We use this to\n // (a) preserve owner / display_name when this is a token refresh (no\n // owner argument), and (b) reject the write when the caller is trying\n // to overwrite a row owned by someone else.\n let resolvedOwner = owner ?? accountId;\n let existingDisplayName: string | null = null;\n let existingOwner: string | null = null;\n let existingTokens: Record<string, unknown> | null = null;\n const { rows: existing } = await client.execute({\n sql: `SELECT owner, display_name, tokens FROM ${table} WHERE provider = ? AND account_id = ?`,\n args: [provider, accountId],\n });\n if (existing.length > 0) {\n existingOwner = (existing[0].owner as string) ?? null;\n existingDisplayName = (existing[0].display_name as string) ?? null;\n existingTokens = JSON.parse((existing[0].tokens as string) ?? \"{}\");\n }\n\n if (!owner) {\n // Token-refresh path: keep the existing owner/displayName unchanged.\n if (existingOwner) resolvedOwner = existingOwner;\n } else if (existingOwner && owner && existingOwner !== owner) {\n // Refuse to silently re-bind an account from one user to another.\n // This is the case the docstring promised but the previous\n // implementation didn't enforce — `ON CONFLICT DO UPDATE SET\n // owner=EXCLUDED.owner` would have overwritten the prior owner.\n throw new OAuthAccountOwnedByOtherUserError({\n provider,\n accountId,\n existingOwner,\n attemptedOwner: owner,\n });\n }\n\n const cleanedIncomingTokens = Object.fromEntries(\n Object.entries(tokens).filter(([, value]) => value !== undefined),\n );\n const tokensToStore = {\n ...(existingTokens ?? {}),\n ...cleanedIncomingTokens,\n };\n\n await client.execute({\n sql: isPostgres()\n ? `INSERT INTO ${table} (provider, account_id, owner, display_name, tokens, updated_at) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (provider, account_id) DO UPDATE SET owner=EXCLUDED.owner, display_name=COALESCE(EXCLUDED.display_name, ${table}.display_name), tokens=EXCLUDED.tokens, updated_at=EXCLUDED.updated_at`\n : `INSERT OR REPLACE INTO ${table} (provider, account_id, owner, display_name, tokens, updated_at) VALUES (?, ?, ?, ?, ?, ?)`,\n args: [\n provider,\n accountId,\n resolvedOwner,\n existingDisplayName,\n JSON.stringify(tokensToStore),\n Date.now(),\n ],\n });\n}\n\nexport async function deleteOAuthTokens(\n provider: string,\n accountId?: string,\n): Promise<number> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n if (accountId) {\n const result = await client.execute({\n sql: `DELETE FROM ${table} WHERE provider = ? AND account_id = ?`,\n args: [provider, accountId],\n });\n return result.rowsAffected;\n }\n const result = await client.execute({\n sql: `DELETE FROM ${table} WHERE provider = ?`,\n args: [provider],\n });\n return result.rowsAffected;\n}\n\nexport async function listOAuthAccounts(provider: string): Promise<\n Array<{\n accountId: string;\n owner: string | null;\n tokens: Record<string, unknown>;\n }>\n> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n const { rows } = await client.execute({\n sql: `SELECT account_id, owner, tokens FROM ${table} WHERE provider = ?`,\n args: [provider],\n });\n return rows.map((row) => ({\n accountId: row.account_id as string,\n owner: (row.owner as string) ?? null,\n tokens: JSON.parse(row.tokens as string),\n }));\n}\n\n/**\n * List all OAuth accounts owned by a specific user.\n * In multi-account mode, a user may have connected multiple Google accounts.\n */\nexport async function listOAuthAccountsByOwner(\n provider: string,\n owner: string,\n): Promise<\n Array<{\n accountId: string;\n displayName: string | null;\n tokens: Record<string, unknown>;\n }>\n> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n const { rows } = await client.execute({\n sql: `SELECT account_id, display_name, tokens FROM ${table} WHERE provider = ? AND owner = ?`,\n args: [provider, owner],\n });\n return rows.map((row) => ({\n accountId: row.account_id as string,\n displayName: (row.display_name as string) ?? null,\n tokens: JSON.parse(row.tokens as string),\n }));\n}\n\n/**\n * Set the display name for an OAuth account (e.g. Google profile name).\n */\nexport async function setOAuthDisplayName(\n provider: string,\n accountId: string,\n displayName: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n await client.execute({\n sql: `UPDATE ${table} SET display_name = ? WHERE provider = ? AND account_id = ?`,\n args: [displayName, provider, accountId],\n });\n}\n\n/**\n * Check whether a specific user has tokens for a provider.\n *\n * `owner` is REQUIRED. The previous unscoped form leaked information\n * across users — the onboarding banner would mark the OAuth secret as\n * \"set\" for user B as soon as ANY user in the deployment connected the\n * provider, and user B would never see the prompt to connect.\n */\nexport async function hasOAuthTokens(\n provider: string,\n owner: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const table = oauthTokensTable();\n const { rows } = await client.execute({\n sql: `SELECT 1 FROM ${table} WHERE provider = ? AND owner = ? LIMIT 1`,\n args: [provider, owner],\n });\n return rows.length > 0;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evals.d.ts","sourceRoot":"","sources":["../../src/observability/evals.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,YAAY,EAGb,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AA0I5D,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAsB5E;
|
|
1
|
+
{"version":3,"file":"evals.d.ts","sourceRoot":"","sources":["../../src/observability/evals.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,YAAY,EAGb,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AA0I5D,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAsB5E;AAyDD,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,YAAY,EACtB,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACtE,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CA6E5B;AAID,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACzE,OAAO,CAAC;IACT,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB,CAAC,CAkDD;AAuGD,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7B,OAAO,CAAC,UAAU,EAAE,CAAC,CA6BvB"}
|
|
@@ -133,14 +133,14 @@ function buildConversationTranscript(events) {
|
|
|
133
133
|
else if (event.type === "text-delta" || event.type === "text") {
|
|
134
134
|
lines.push(`[Agent]: ${event.text}`);
|
|
135
135
|
}
|
|
136
|
-
else if (event.type === "
|
|
137
|
-
lines.push(`[Tool Call: ${event.
|
|
136
|
+
else if (event.type === "tool_start") {
|
|
137
|
+
lines.push(`[Tool Call: ${event.tool}] ${JSON.stringify(event.input)}`);
|
|
138
138
|
}
|
|
139
|
-
else if (event.type === "
|
|
140
|
-
const snippet = typeof event.
|
|
141
|
-
? event.
|
|
142
|
-
: JSON.stringify(event.
|
|
143
|
-
lines.push(`[Tool Result
|
|
139
|
+
else if (event.type === "tool_done") {
|
|
140
|
+
const snippet = typeof event.result === "string"
|
|
141
|
+
? event.result.slice(0, 500)
|
|
142
|
+
: JSON.stringify(event.result).slice(0, 500);
|
|
143
|
+
lines.push(`[Tool Result]: ${snippet}`);
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evals.js","sourceRoot":"","sources":["../../src/observability/evals.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n EvalResult,\n EvalCriteria,\n TraceSummary,\n EvalTestCase,\n} from \"./types.js\";\nimport { getTraceSummary, insertEvalResult, getEvalDataset } from \"./store.js\";\nimport { getRunById, getRunEventsSince } from \"../agent/run-store.js\";\nimport type { AgentEngine } from \"../agent/engine/types.js\";\nimport {\n resolveEngine,\n getStoredModelForEngine,\n} from \"../agent/engine/index.js\";\n\nconst LATENCY_BASELINE_PER_TOOL_MS = 10_000;\nconst COST_BASELINE_PER_TOOL_CX100 = 50;\nconst LLM_JUDGE_TIMEOUT_MS = 30_000;\n\ninterface MakeEvalResultOpts {\n runId: string;\n threadId: string | null;\n userId: string | null;\n evalType: EvalResult[\"evalType\"];\n criteria: string;\n score: number;\n reasoning?: string | null;\n metadata?: Record<string, unknown> | null;\n}\n\nfunction makeEvalResult(opts: MakeEvalResultOpts): EvalResult {\n return {\n id: crypto.randomUUID(),\n runId: opts.runId,\n threadId: opts.threadId,\n userId: opts.userId,\n evalType: opts.evalType,\n criteria: opts.criteria,\n score: Math.max(0, Math.min(1, opts.score)),\n reasoning: opts.reasoning ?? null,\n metadata: opts.metadata ?? null,\n createdAt: Date.now(),\n };\n}\n\n/** Lift the (runId, threadId, userId) triple off a TraceSummary —\n * every automated scorer pulls these together. */\nfunction fromSummary(summary: TraceSummary): {\n runId: string;\n threadId: string | null;\n userId: string | null;\n} {\n return {\n runId: summary.runId,\n threadId: summary.threadId,\n userId: summary.userId,\n };\n}\n\n// ─── Layer 1: Automated deterministic scorers ────────────────────────\n\nfunction scoreToolSuccessRate(summary: TraceSummary): EvalResult {\n const total = summary.toolCalls;\n const score = total > 0 ? summary.successfulTools / total : 1.0;\n return makeEvalResult({\n ...fromSummary(summary),\n evalType: \"automated\",\n criteria: \"tool_success_rate\",\n score,\n metadata: {\n totalTools: total,\n successfulTools: summary.successfulTools,\n failedTools: summary.failedTools,\n },\n });\n}\n\nfunction scoreStepEfficiency(summary: TraceSummary): EvalResult {\n // No tool calls = simple Q&A, maximally efficient.\n // With tools: penalize excessive LLM iterations relative to tool calls.\n const score =\n summary.toolCalls === 0\n ? 1.0\n : summary.llmCalls > 0\n ? Math.min(1, summary.toolCalls / summary.llmCalls)\n : 1.0;\n return makeEvalResult({\n ...fromSummary(summary),\n evalType: \"automated\",\n criteria: \"step_efficiency\",\n score,\n metadata: { llmCalls: summary.llmCalls, toolCalls: summary.toolCalls },\n });\n}\n\nfunction scoreLatency(summary: TraceSummary): EvalResult {\n const expectedMs = Math.max(\n LATENCY_BASELINE_PER_TOOL_MS,\n summary.toolCalls * LATENCY_BASELINE_PER_TOOL_MS,\n );\n const score = Math.max(0, 1 - summary.totalDurationMs / expectedMs);\n return makeEvalResult({\n ...fromSummary(summary),\n evalType: \"automated\",\n criteria: \"latency_score\",\n score,\n metadata: { actualMs: summary.totalDurationMs, expectedMs },\n });\n}\n\nfunction scoreCostEfficiency(summary: TraceSummary): EvalResult {\n const expectedCx100 = Math.max(\n COST_BASELINE_PER_TOOL_CX100,\n summary.toolCalls * COST_BASELINE_PER_TOOL_CX100,\n );\n const score = Math.max(0, 1 - summary.totalCostCentsX100 / expectedCx100);\n return makeEvalResult({\n ...fromSummary(summary),\n evalType: \"automated\",\n criteria: \"cost_efficiency\",\n score,\n metadata: { actualCx100: summary.totalCostCentsX100, expectedCx100 },\n });\n}\n\nfunction scoreErrorRecovery(\n summary: TraceSummary,\n runStatus: string,\n): EvalResult {\n const hadErrors = summary.failedTools > 0;\n let score: number;\n if (!hadErrors) {\n score = 1.0;\n } else if (runStatus === \"completed\") {\n score = 1.0;\n } else {\n score = 0;\n }\n return makeEvalResult({\n ...fromSummary(summary),\n evalType: \"automated\",\n criteria: \"error_recovery\",\n score,\n metadata: { hadErrors, runStatus },\n });\n}\n\nexport async function runAutomatedEvals(runId: string): Promise<EvalResult[]> {\n const [summary, run] = await Promise.all([\n getTraceSummary(runId),\n getRunById(runId),\n ]);\n\n if (!summary) return [];\n\n const runStatus = run?.status ?? \"unknown\";\n const results = [\n scoreToolSuccessRate(summary),\n scoreStepEfficiency(summary),\n scoreLatency(summary),\n scoreCostEfficiency(summary),\n scoreErrorRecovery(summary, runStatus),\n ];\n\n for (const result of results) {\n insertEvalResult(result).catch(() => {});\n }\n\n return results;\n}\n\n// ─── Layer 2: LLM-as-Judge ───────────────────────────────────────────\n\nfunction buildConversationTranscript(\n events: Array<{ seq: number; eventData: string }>,\n): string {\n const lines: string[] = [];\n for (const { eventData } of events) {\n try {\n const event = JSON.parse(eventData);\n if (event.type === \"user-message\") {\n lines.push(`[User]: ${event.text ?? JSON.stringify(event.content)}`);\n } else if (event.type === \"text-delta\" || event.type === \"text\") {\n lines.push(`[Agent]: ${event.text}`);\n } else if (event.type === \"tool-call\") {\n lines.push(`[Tool Call: ${event.name}] ${JSON.stringify(event.input)}`);\n } else if (event.type === \"tool-result\") {\n const snippet =\n typeof event.content === \"string\"\n ? event.content.slice(0, 500)\n : JSON.stringify(event.content).slice(0, 500);\n lines.push(\n `[Tool Result${event.isError ? \" (ERROR)\" : \"\"}]: ${snippet}`,\n );\n }\n } catch {\n // Skip unparseable events\n }\n }\n return lines.join(\"\\n\");\n}\n\nfunction buildJudgePrompt(transcript: string, criteria: EvalCriteria): string {\n let prompt = `You are an expert evaluator. Assess the following agent conversation against the given criteria.\n\n## Criteria\nName: ${criteria.name}\nDescription: ${criteria.description}`;\n\n if (criteria.rubric) {\n prompt += `\\nRubric: ${criteria.rubric}`;\n }\n\n const min = criteria.scoreRange?.min ?? 0;\n const max = criteria.scoreRange?.max ?? 1;\n\n prompt += `\n\n## Conversation Transcript\n${transcript}\n\n## Instructions\nEvaluate the conversation and respond with ONLY a JSON object (no markdown, no explanation outside the JSON):\n{\"score\": <number between ${min} and ${max}>, \"reasoning\": \"<brief explanation>\"}`;\n\n return prompt;\n}\n\nexport async function runLlmJudgeEval(\n runId: string,\n criteria: EvalCriteria,\n opts?: { engine?: AgentEngine; model?: string; userId?: string | null },\n): Promise<EvalResult | null> {\n try {\n const [events, run] = await Promise.all([\n getRunEventsSince(runId, 0),\n getRunById(runId),\n ]);\n\n if (events.length === 0) return null;\n\n const transcript = buildConversationTranscript(events);\n if (!transcript.trim()) return null;\n\n const engine =\n opts?.engine ?? (await resolveEngine({ engineOption: undefined }));\n const model =\n opts?.model ??\n (await getStoredModelForEngine(engine)) ??\n engine.defaultModel;\n\n const judgePrompt = buildJudgePrompt(transcript, criteria);\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), LLM_JUDGE_TIMEOUT_MS);\n\n let responseText = \"\";\n try {\n const stream = engine.stream({\n model,\n systemPrompt:\n \"You are an evaluation judge. Respond only with valid JSON.\",\n messages: [\n { role: \"user\", content: [{ type: \"text\", text: judgePrompt }] },\n ],\n tools: [],\n abortSignal: controller.signal,\n maxOutputTokens: 512,\n temperature: 0,\n });\n\n for await (const event of stream) {\n if (event.type === \"text-delta\") {\n responseText += event.text;\n }\n }\n } finally {\n clearTimeout(timeout);\n }\n\n const jsonMatch = responseText.match(/\\{[\\s\\S]*\\}/);\n if (!jsonMatch) return null;\n\n const parsed = JSON.parse(jsonMatch[0]) as {\n score: number;\n reasoning: string;\n };\n\n const min = criteria.scoreRange?.min ?? 0;\n const max = criteria.scoreRange?.max ?? 1;\n const normalizedScore =\n max > min ? (parsed.score - min) / (max - min) : parsed.score;\n\n const result = makeEvalResult({\n runId,\n threadId: run?.threadId ?? null,\n userId: opts?.userId ?? null,\n evalType: \"llm_judge\",\n criteria: criteria.name,\n score: normalizedScore,\n reasoning: parsed.reasoning,\n metadata: { model, rawScore: parsed.score, scoreRange: { min, max } },\n });\n\n insertEvalResult(result).catch(() => {});\n return result;\n } catch {\n return null;\n }\n}\n\n// ─── Layer 3: Dataset evaluation ─────────────────────────────────────\n\nexport async function runDatasetEval(\n datasetId: string,\n opts?: { criteria?: EvalCriteria[]; engine?: AgentEngine; model?: string },\n): Promise<{\n datasetId: string;\n totalCases: number;\n avgScore: number;\n results: EvalResult[];\n}> {\n const dataset = await getEvalDataset(datasetId);\n if (!dataset) {\n return { datasetId, totalCases: 0, avgScore: 0, results: [] };\n }\n\n const engine =\n opts?.engine ?? (await resolveEngine({ engineOption: undefined }));\n const model =\n opts?.model ??\n (await getStoredModelForEngine(engine)) ??\n engine.defaultModel;\n\n const criteria = opts?.criteria ?? [\n {\n name: \"response_quality\",\n description:\n \"How well the agent's response addresses the user's input, considering accuracy, completeness, and helpfulness.\",\n },\n ];\n\n const allResults: EvalResult[] = [];\n\n for (const testCase of dataset.entries) {\n const transcript = buildTestCaseTranscript(testCase, engine, model);\n\n for (const c of criteria) {\n const result = await evaluateTestCase(\n datasetId,\n testCase,\n transcript,\n c,\n engine,\n model,\n );\n if (result) allResults.push(result);\n }\n }\n\n const avgScore =\n allResults.length > 0\n ? allResults.reduce((sum, r) => sum + r.score, 0) / allResults.length\n : 0;\n\n return {\n datasetId,\n totalCases: dataset.entries.length,\n avgScore,\n results: allResults,\n };\n}\n\nfunction buildTestCaseTranscript(\n testCase: EvalTestCase,\n _engine: AgentEngine,\n _model: string,\n): string {\n let transcript = `[User]: ${testCase.input}`;\n if (testCase.expectedOutput) {\n transcript += `\\n[Expected Output]: ${testCase.expectedOutput}`;\n }\n if (testCase.context) {\n transcript += `\\n[Context]: ${JSON.stringify(testCase.context)}`;\n }\n return transcript;\n}\n\nasync function evaluateTestCase(\n datasetId: string,\n testCase: EvalTestCase,\n transcript: string,\n criteria: EvalCriteria,\n engine: AgentEngine,\n model: string,\n): Promise<EvalResult | null> {\n try {\n const judgePrompt = buildJudgePrompt(transcript, criteria);\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), LLM_JUDGE_TIMEOUT_MS);\n\n let responseText = \"\";\n try {\n const stream = engine.stream({\n model,\n systemPrompt:\n \"You are an evaluation judge. Respond only with valid JSON.\",\n messages: [\n { role: \"user\", content: [{ type: \"text\", text: judgePrompt }] },\n ],\n tools: [],\n abortSignal: controller.signal,\n maxOutputTokens: 512,\n temperature: 0,\n });\n\n for await (const event of stream) {\n if (event.type === \"text-delta\") {\n responseText += event.text;\n }\n }\n } finally {\n clearTimeout(timeout);\n }\n\n const jsonMatch = responseText.match(/\\{[\\s\\S]*\\}/);\n if (!jsonMatch) return null;\n\n const parsed = JSON.parse(jsonMatch[0]) as {\n score: number;\n reasoning: string;\n };\n\n const min = criteria.scoreRange?.min ?? 0;\n const max = criteria.scoreRange?.max ?? 1;\n const normalizedScore =\n max > min ? (parsed.score - min) / (max - min) : parsed.score;\n\n // Dataset evals use a synthetic runId since there's no real run\n const syntheticRunId = `dataset:${datasetId}:${crypto.randomUUID()}`;\n\n // Dataset evals are administrative — there's no per-user runId, so\n // we leave userId null. Per-user reads filter null rows out, which\n // is the right default; admins can fetch dataset evals via the\n // unfiltered call path.\n const result = makeEvalResult({\n runId: syntheticRunId,\n threadId: null,\n userId: null,\n evalType: \"llm_judge\",\n criteria: criteria.name,\n score: normalizedScore,\n reasoning: parsed.reasoning,\n metadata: {\n datasetId,\n model,\n testCaseInput: testCase.input,\n expectedOutput: testCase.expectedOutput ?? null,\n tags: testCase.tags ?? [],\n rawScore: parsed.score,\n scoreRange: { min, max },\n },\n });\n\n insertEvalResult(result).catch(() => {});\n return result;\n } catch {\n return null;\n }\n}\n\n// ─── Orchestrator ────────────────────────────────────────────────────\n\nexport async function evaluateRun(\n runId: string,\n opts?: { sampleRate?: number },\n): Promise<EvalResult[]> {\n const results = await runAutomatedEvals(runId);\n const userId = results[0]?.userId ?? null;\n\n const sampleRate = opts?.sampleRate ?? 0;\n if (sampleRate > 0 && Math.random() < sampleRate) {\n const defaultCriteria: EvalCriteria[] = [\n {\n name: \"overall_quality\",\n description:\n \"Overall quality of the agent's response, considering helpfulness, accuracy, and appropriate tool usage.\",\n },\n {\n name: \"task_completion\",\n description:\n \"Whether the agent successfully completed the user's requested task.\",\n },\n ];\n\n const judgeResults = await Promise.all(\n defaultCriteria.map((c) => runLlmJudgeEval(runId, c, { userId })),\n );\n\n for (const r of judgeResults) {\n if (r) results.push(r);\n }\n }\n\n return results;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"evals.js","sourceRoot":"","sources":["../../src/observability/evals.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n EvalResult,\n EvalCriteria,\n TraceSummary,\n EvalTestCase,\n} from \"./types.js\";\nimport { getTraceSummary, insertEvalResult, getEvalDataset } from \"./store.js\";\nimport { getRunById, getRunEventsSince } from \"../agent/run-store.js\";\nimport type { AgentEngine } from \"../agent/engine/types.js\";\nimport {\n resolveEngine,\n getStoredModelForEngine,\n} from \"../agent/engine/index.js\";\n\nconst LATENCY_BASELINE_PER_TOOL_MS = 10_000;\nconst COST_BASELINE_PER_TOOL_CX100 = 50;\nconst LLM_JUDGE_TIMEOUT_MS = 30_000;\n\ninterface MakeEvalResultOpts {\n runId: string;\n threadId: string | null;\n userId: string | null;\n evalType: EvalResult[\"evalType\"];\n criteria: string;\n score: number;\n reasoning?: string | null;\n metadata?: Record<string, unknown> | null;\n}\n\nfunction makeEvalResult(opts: MakeEvalResultOpts): EvalResult {\n return {\n id: crypto.randomUUID(),\n runId: opts.runId,\n threadId: opts.threadId,\n userId: opts.userId,\n evalType: opts.evalType,\n criteria: opts.criteria,\n score: Math.max(0, Math.min(1, opts.score)),\n reasoning: opts.reasoning ?? null,\n metadata: opts.metadata ?? null,\n createdAt: Date.now(),\n };\n}\n\n/** Lift the (runId, threadId, userId) triple off a TraceSummary —\n * every automated scorer pulls these together. */\nfunction fromSummary(summary: TraceSummary): {\n runId: string;\n threadId: string | null;\n userId: string | null;\n} {\n return {\n runId: summary.runId,\n threadId: summary.threadId,\n userId: summary.userId,\n };\n}\n\n// ─── Layer 1: Automated deterministic scorers ────────────────────────\n\nfunction scoreToolSuccessRate(summary: TraceSummary): EvalResult {\n const total = summary.toolCalls;\n const score = total > 0 ? summary.successfulTools / total : 1.0;\n return makeEvalResult({\n ...fromSummary(summary),\n evalType: \"automated\",\n criteria: \"tool_success_rate\",\n score,\n metadata: {\n totalTools: total,\n successfulTools: summary.successfulTools,\n failedTools: summary.failedTools,\n },\n });\n}\n\nfunction scoreStepEfficiency(summary: TraceSummary): EvalResult {\n // No tool calls = simple Q&A, maximally efficient.\n // With tools: penalize excessive LLM iterations relative to tool calls.\n const score =\n summary.toolCalls === 0\n ? 1.0\n : summary.llmCalls > 0\n ? Math.min(1, summary.toolCalls / summary.llmCalls)\n : 1.0;\n return makeEvalResult({\n ...fromSummary(summary),\n evalType: \"automated\",\n criteria: \"step_efficiency\",\n score,\n metadata: { llmCalls: summary.llmCalls, toolCalls: summary.toolCalls },\n });\n}\n\nfunction scoreLatency(summary: TraceSummary): EvalResult {\n const expectedMs = Math.max(\n LATENCY_BASELINE_PER_TOOL_MS,\n summary.toolCalls * LATENCY_BASELINE_PER_TOOL_MS,\n );\n const score = Math.max(0, 1 - summary.totalDurationMs / expectedMs);\n return makeEvalResult({\n ...fromSummary(summary),\n evalType: \"automated\",\n criteria: \"latency_score\",\n score,\n metadata: { actualMs: summary.totalDurationMs, expectedMs },\n });\n}\n\nfunction scoreCostEfficiency(summary: TraceSummary): EvalResult {\n const expectedCx100 = Math.max(\n COST_BASELINE_PER_TOOL_CX100,\n summary.toolCalls * COST_BASELINE_PER_TOOL_CX100,\n );\n const score = Math.max(0, 1 - summary.totalCostCentsX100 / expectedCx100);\n return makeEvalResult({\n ...fromSummary(summary),\n evalType: \"automated\",\n criteria: \"cost_efficiency\",\n score,\n metadata: { actualCx100: summary.totalCostCentsX100, expectedCx100 },\n });\n}\n\nfunction scoreErrorRecovery(\n summary: TraceSummary,\n runStatus: string,\n): EvalResult {\n const hadErrors = summary.failedTools > 0;\n let score: number;\n if (!hadErrors) {\n score = 1.0;\n } else if (runStatus === \"completed\") {\n score = 1.0;\n } else {\n score = 0;\n }\n return makeEvalResult({\n ...fromSummary(summary),\n evalType: \"automated\",\n criteria: \"error_recovery\",\n score,\n metadata: { hadErrors, runStatus },\n });\n}\n\nexport async function runAutomatedEvals(runId: string): Promise<EvalResult[]> {\n const [summary, run] = await Promise.all([\n getTraceSummary(runId),\n getRunById(runId),\n ]);\n\n if (!summary) return [];\n\n const runStatus = run?.status ?? \"unknown\";\n const results = [\n scoreToolSuccessRate(summary),\n scoreStepEfficiency(summary),\n scoreLatency(summary),\n scoreCostEfficiency(summary),\n scoreErrorRecovery(summary, runStatus),\n ];\n\n for (const result of results) {\n insertEvalResult(result).catch(() => {});\n }\n\n return results;\n}\n\n// ─── Layer 2: LLM-as-Judge ───────────────────────────────────────────\n\nfunction buildConversationTranscript(\n events: Array<{ seq: number; eventData: string }>,\n): string {\n const lines: string[] = [];\n for (const { eventData } of events) {\n try {\n const event = JSON.parse(eventData);\n if (event.type === \"user-message\") {\n lines.push(`[User]: ${event.text ?? JSON.stringify(event.content)}`);\n } else if (event.type === \"text-delta\" || event.type === \"text\") {\n lines.push(`[Agent]: ${event.text}`);\n } else if (event.type === \"tool_start\") {\n lines.push(`[Tool Call: ${event.tool}] ${JSON.stringify(event.input)}`);\n } else if (event.type === \"tool_done\") {\n const snippet =\n typeof event.result === \"string\"\n ? event.result.slice(0, 500)\n : JSON.stringify(event.result).slice(0, 500);\n lines.push(`[Tool Result]: ${snippet}`);\n }\n } catch {\n // Skip unparseable events\n }\n }\n return lines.join(\"\\n\");\n}\n\nfunction buildJudgePrompt(transcript: string, criteria: EvalCriteria): string {\n let prompt = `You are an expert evaluator. Assess the following agent conversation against the given criteria.\n\n## Criteria\nName: ${criteria.name}\nDescription: ${criteria.description}`;\n\n if (criteria.rubric) {\n prompt += `\\nRubric: ${criteria.rubric}`;\n }\n\n const min = criteria.scoreRange?.min ?? 0;\n const max = criteria.scoreRange?.max ?? 1;\n\n prompt += `\n\n## Conversation Transcript\n${transcript}\n\n## Instructions\nEvaluate the conversation and respond with ONLY a JSON object (no markdown, no explanation outside the JSON):\n{\"score\": <number between ${min} and ${max}>, \"reasoning\": \"<brief explanation>\"}`;\n\n return prompt;\n}\n\nexport async function runLlmJudgeEval(\n runId: string,\n criteria: EvalCriteria,\n opts?: { engine?: AgentEngine; model?: string; userId?: string | null },\n): Promise<EvalResult | null> {\n try {\n const [events, run] = await Promise.all([\n getRunEventsSince(runId, 0),\n getRunById(runId),\n ]);\n\n if (events.length === 0) return null;\n\n const transcript = buildConversationTranscript(events);\n if (!transcript.trim()) return null;\n\n const engine =\n opts?.engine ?? (await resolveEngine({ engineOption: undefined }));\n const model =\n opts?.model ??\n (await getStoredModelForEngine(engine)) ??\n engine.defaultModel;\n\n const judgePrompt = buildJudgePrompt(transcript, criteria);\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), LLM_JUDGE_TIMEOUT_MS);\n\n let responseText = \"\";\n try {\n const stream = engine.stream({\n model,\n systemPrompt:\n \"You are an evaluation judge. Respond only with valid JSON.\",\n messages: [\n { role: \"user\", content: [{ type: \"text\", text: judgePrompt }] },\n ],\n tools: [],\n abortSignal: controller.signal,\n maxOutputTokens: 512,\n temperature: 0,\n });\n\n for await (const event of stream) {\n if (event.type === \"text-delta\") {\n responseText += event.text;\n }\n }\n } finally {\n clearTimeout(timeout);\n }\n\n const jsonMatch = responseText.match(/\\{[\\s\\S]*\\}/);\n if (!jsonMatch) return null;\n\n const parsed = JSON.parse(jsonMatch[0]) as {\n score: number;\n reasoning: string;\n };\n\n const min = criteria.scoreRange?.min ?? 0;\n const max = criteria.scoreRange?.max ?? 1;\n const normalizedScore =\n max > min ? (parsed.score - min) / (max - min) : parsed.score;\n\n const result = makeEvalResult({\n runId,\n threadId: run?.threadId ?? null,\n userId: opts?.userId ?? null,\n evalType: \"llm_judge\",\n criteria: criteria.name,\n score: normalizedScore,\n reasoning: parsed.reasoning,\n metadata: { model, rawScore: parsed.score, scoreRange: { min, max } },\n });\n\n insertEvalResult(result).catch(() => {});\n return result;\n } catch {\n return null;\n }\n}\n\n// ─── Layer 3: Dataset evaluation ─────────────────────────────────────\n\nexport async function runDatasetEval(\n datasetId: string,\n opts?: { criteria?: EvalCriteria[]; engine?: AgentEngine; model?: string },\n): Promise<{\n datasetId: string;\n totalCases: number;\n avgScore: number;\n results: EvalResult[];\n}> {\n const dataset = await getEvalDataset(datasetId);\n if (!dataset) {\n return { datasetId, totalCases: 0, avgScore: 0, results: [] };\n }\n\n const engine =\n opts?.engine ?? (await resolveEngine({ engineOption: undefined }));\n const model =\n opts?.model ??\n (await getStoredModelForEngine(engine)) ??\n engine.defaultModel;\n\n const criteria = opts?.criteria ?? [\n {\n name: \"response_quality\",\n description:\n \"How well the agent's response addresses the user's input, considering accuracy, completeness, and helpfulness.\",\n },\n ];\n\n const allResults: EvalResult[] = [];\n\n for (const testCase of dataset.entries) {\n const transcript = buildTestCaseTranscript(testCase, engine, model);\n\n for (const c of criteria) {\n const result = await evaluateTestCase(\n datasetId,\n testCase,\n transcript,\n c,\n engine,\n model,\n );\n if (result) allResults.push(result);\n }\n }\n\n const avgScore =\n allResults.length > 0\n ? allResults.reduce((sum, r) => sum + r.score, 0) / allResults.length\n : 0;\n\n return {\n datasetId,\n totalCases: dataset.entries.length,\n avgScore,\n results: allResults,\n };\n}\n\nfunction buildTestCaseTranscript(\n testCase: EvalTestCase,\n _engine: AgentEngine,\n _model: string,\n): string {\n let transcript = `[User]: ${testCase.input}`;\n if (testCase.expectedOutput) {\n transcript += `\\n[Expected Output]: ${testCase.expectedOutput}`;\n }\n if (testCase.context) {\n transcript += `\\n[Context]: ${JSON.stringify(testCase.context)}`;\n }\n return transcript;\n}\n\nasync function evaluateTestCase(\n datasetId: string,\n testCase: EvalTestCase,\n transcript: string,\n criteria: EvalCriteria,\n engine: AgentEngine,\n model: string,\n): Promise<EvalResult | null> {\n try {\n const judgePrompt = buildJudgePrompt(transcript, criteria);\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), LLM_JUDGE_TIMEOUT_MS);\n\n let responseText = \"\";\n try {\n const stream = engine.stream({\n model,\n systemPrompt:\n \"You are an evaluation judge. Respond only with valid JSON.\",\n messages: [\n { role: \"user\", content: [{ type: \"text\", text: judgePrompt }] },\n ],\n tools: [],\n abortSignal: controller.signal,\n maxOutputTokens: 512,\n temperature: 0,\n });\n\n for await (const event of stream) {\n if (event.type === \"text-delta\") {\n responseText += event.text;\n }\n }\n } finally {\n clearTimeout(timeout);\n }\n\n const jsonMatch = responseText.match(/\\{[\\s\\S]*\\}/);\n if (!jsonMatch) return null;\n\n const parsed = JSON.parse(jsonMatch[0]) as {\n score: number;\n reasoning: string;\n };\n\n const min = criteria.scoreRange?.min ?? 0;\n const max = criteria.scoreRange?.max ?? 1;\n const normalizedScore =\n max > min ? (parsed.score - min) / (max - min) : parsed.score;\n\n // Dataset evals use a synthetic runId since there's no real run\n const syntheticRunId = `dataset:${datasetId}:${crypto.randomUUID()}`;\n\n // Dataset evals are administrative — there's no per-user runId, so\n // we leave userId null. Per-user reads filter null rows out, which\n // is the right default; admins can fetch dataset evals via the\n // unfiltered call path.\n const result = makeEvalResult({\n runId: syntheticRunId,\n threadId: null,\n userId: null,\n evalType: \"llm_judge\",\n criteria: criteria.name,\n score: normalizedScore,\n reasoning: parsed.reasoning,\n metadata: {\n datasetId,\n model,\n testCaseInput: testCase.input,\n expectedOutput: testCase.expectedOutput ?? null,\n tags: testCase.tags ?? [],\n rawScore: parsed.score,\n scoreRange: { min, max },\n },\n });\n\n insertEvalResult(result).catch(() => {});\n return result;\n } catch {\n return null;\n }\n}\n\n// ─── Orchestrator ────────────────────────────────────────────────────\n\nexport async function evaluateRun(\n runId: string,\n opts?: { sampleRate?: number },\n): Promise<EvalResult[]> {\n const results = await runAutomatedEvals(runId);\n const userId = results[0]?.userId ?? null;\n\n const sampleRate = opts?.sampleRate ?? 0;\n if (sampleRate > 0 && Math.random() < sampleRate) {\n const defaultCriteria: EvalCriteria[] = [\n {\n name: \"overall_quality\",\n description:\n \"Overall quality of the agent's response, considering helpfulness, accuracy, and appropriate tool usage.\",\n },\n {\n name: \"task_completion\",\n description:\n \"Whether the agent successfully completed the user's requested task.\",\n },\n ];\n\n const judgeResults = await Promise.all(\n defaultCriteria.map((c) => runLlmJudgeEval(runId, c, { userId })),\n );\n\n for (const r of judgeResults) {\n if (r) results.push(r);\n }\n }\n\n return results;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"traces.d.ts","sourceRoot":"","sources":["../../src/observability/traces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAA2B,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAiB/E;;;wEAGwE;AACxE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE7D;AAoBD,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAY3E;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,YAAY,EAAE,CAAC,QAAQ,EAAE;QACvB,MAAM,EAAE,GAAG,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,YAAY,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE,GAAG,EAAE,CAAC;QACb,QAAQ,EAAE,GAAG,EAAE,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC7B,IAAI,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;QACtC,MAAM,EAAE,WAAW,CAAC;QACpB,eAAe,CAAC,EAAE,GAAG,CAAC;KACvB,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAC9B,QAAQ,EAAE;QACR,MAAM,EAAE,GAAG,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,YAAY,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE,GAAG,EAAE,CAAC;QACb,QAAQ,EAAE,GAAG,EAAE,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC7B,IAAI,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;QACtC,MAAM,EAAE,WAAW,CAAC;QACpB,eAAe,CAAC,EAAE,GAAG,CAAC;KACvB,CAAC;IACF,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB;;;iBAGa;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,mBAAmB,CAAC;CAC7B,GAAG,OAAO,CAAC,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"traces.d.ts","sourceRoot":"","sources":["../../src/observability/traces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAA2B,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAiB/E;;;wEAGwE;AACxE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE7D;AAoBD,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAY3E;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,YAAY,EAAE,CAAC,QAAQ,EAAE;QACvB,MAAM,EAAE,GAAG,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,YAAY,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE,GAAG,EAAE,CAAC;QACb,QAAQ,EAAE,GAAG,EAAE,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC7B,IAAI,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;QACtC,MAAM,EAAE,WAAW,CAAC;QACpB,eAAe,CAAC,EAAE,GAAG,CAAC;KACvB,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAC9B,QAAQ,EAAE;QACR,MAAM,EAAE,GAAG,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,YAAY,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE,GAAG,EAAE,CAAC;QACb,QAAQ,EAAE,GAAG,EAAE,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC7B,IAAI,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;QACtC,MAAM,EAAE,WAAW,CAAC;QACpB,eAAe,CAAC,EAAE,GAAG,CAAC;KACvB,CAAC;IACF,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB;;;iBAGa;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,mBAAmB,CAAC;CAC7B,GAAG,OAAO,CAAC,cAAc,CAAC,CAiM1B"}
|
|
@@ -59,8 +59,12 @@ export async function instrumentAgentLoop(opts) {
|
|
|
59
59
|
let toolInvocationCounter = 0;
|
|
60
60
|
// Keyed by counter to handle concurrent calls to the same tool name
|
|
61
61
|
const pendingTools = new Map();
|
|
62
|
-
// Secondary index: tool name →
|
|
63
|
-
|
|
62
|
+
// Secondary index: tool name → FIFO queue of pending invocation counters.
|
|
63
|
+
// tool_start/tool_done events carry only the tool name (no call id), so to
|
|
64
|
+
// pair starts and dones correctly when the agent runs concurrent calls to the
|
|
65
|
+
// same tool name (read-only / parallelSafe batches via Promise.all), we keep a
|
|
66
|
+
// queue per name and match each done to the OLDEST still-pending start.
|
|
67
|
+
const toolNameToCounters = new Map();
|
|
64
68
|
let toolCallCount = 0;
|
|
65
69
|
let successfulTools = 0;
|
|
66
70
|
let failedTools = 0;
|
|
@@ -75,14 +79,20 @@ export async function instrumentAgentLoop(opts) {
|
|
|
75
79
|
toolName: event.tool,
|
|
76
80
|
input: event.input,
|
|
77
81
|
});
|
|
78
|
-
|
|
82
|
+
const queue = toolNameToCounters.get(event.tool);
|
|
83
|
+
if (queue)
|
|
84
|
+
queue.push(counter);
|
|
85
|
+
else
|
|
86
|
+
toolNameToCounters.set(event.tool, [counter]);
|
|
79
87
|
}
|
|
80
88
|
else if (event.type === "tool_done") {
|
|
81
|
-
const
|
|
89
|
+
const queue = toolNameToCounters.get(event.tool);
|
|
90
|
+
const counter = queue?.shift();
|
|
82
91
|
const pending = counter !== undefined ? pendingTools.get(counter) : undefined;
|
|
83
92
|
if (counter !== undefined) {
|
|
84
93
|
pendingTools.delete(counter);
|
|
85
|
-
|
|
94
|
+
if (queue && queue.length === 0)
|
|
95
|
+
toolNameToCounters.delete(event.tool);
|
|
86
96
|
}
|
|
87
97
|
toolCallCount++;
|
|
88
98
|
const isError = typeof event.result === "string" &&
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"traces.js","sourceRoot":"","sources":["../../src/observability/traces.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentChatEvent } from \"../agent/types.js\";\nimport type { AgentLoopUsage } from \"../agent/production-agent.js\";\nimport type { TraceSpan, TraceSummary, ObservabilityConfig } from \"./types.js\";\nimport { DEFAULT_OBSERVABILITY_CONFIG } from \"./types.js\";\n\nfunction spanId(): string {\n return `span-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\n/** Keys whose values are stripped from persisted tool inputs when\n * `captureToolArgs` is enabled. Matched case-insensitively and tolerant\n * of `_` / `-` separators. M14 in the MCP/A2A audit: tool calls\n * routinely receive credentials verbatim (db-exec INSERTs, fetchTool\n * Authorization headers, ad-hoc bearer tokens) — keeping those values\n * out of agent_trace_spans.metadata avoids long-term storage of\n * short-lived secrets. */\nconst SENSITIVE_FIELD_PATTERN =\n /^(authorization|cookie|api[_-]?key|password|secret|token|access[_-]?token|refresh[_-]?token|bearer)$/i;\n\n/** Recursively walk a structured value and replace sensitive field\n * values with the literal string \"[REDACTED]\". Pure (returns a copy);\n * the original input is never mutated. Cycles are tolerated via a\n * small WeakSet seen-tracker that returns \"[Circular]\" for repeats. */\nexport function redactSensitiveFields(value: unknown): unknown {\n return redactWalk(value, new WeakSet<object>());\n}\n\nfunction redactWalk(value: unknown, seen: WeakSet<object>): unknown {\n if (value === null || typeof value !== \"object\") return value;\n if (seen.has(value as object)) return \"[Circular]\";\n seen.add(value as object);\n if (Array.isArray(value)) {\n return value.map((v) => redactWalk(v, seen));\n }\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (SENSITIVE_FIELD_PATTERN.test(k)) {\n out[k] = \"[REDACTED]\";\n } else {\n out[k] = redactWalk(v, seen);\n }\n }\n return out;\n}\n\nexport async function getObservabilityConfig(): Promise<ObservabilityConfig> {\n try {\n const { getSetting } = await import(\"../settings/store.js\");\n const stored = await getSetting(\"observability-config\");\n if (stored) {\n return {\n ...DEFAULT_OBSERVABILITY_CONFIG,\n ...stored,\n } as ObservabilityConfig;\n }\n } catch {}\n return DEFAULT_OBSERVABILITY_CONFIG;\n}\n\nexport async function instrumentAgentLoop(opts: {\n runAgentLoop: (loopOpts: {\n engine: any;\n model: string;\n systemPrompt: string;\n tools: any[];\n messages: any[];\n actions: Record<string, any>;\n send: (event: AgentChatEvent) => void;\n signal: AbortSignal;\n providerOptions?: any;\n }) => Promise<AgentLoopUsage>;\n loopOpts: {\n engine: any;\n model: string;\n systemPrompt: string;\n tools: any[];\n messages: any[];\n actions: Record<string, any>;\n send: (event: AgentChatEvent) => void;\n signal: AbortSignal;\n providerOptions?: any;\n };\n runId: string;\n threadId: string | null;\n /** Owner of this run; persisted on every span + summary so dashboard\n * reads can filter to a single user. Null for unauthenticated callers\n * (background tasks, etc.) — those rows aren't returned by per-user\n * reads. */\n userId: string | null;\n config: ObservabilityConfig;\n}): Promise<AgentLoopUsage> {\n const { runAgentLoop, loopOpts, runId, threadId, userId, config } = opts;\n const runStart = Date.now();\n const parentSpanId = spanId();\n\n const spans: TraceSpan[] = [];\n let toolInvocationCounter = 0;\n // Keyed by counter to handle concurrent calls to the same tool name\n const pendingTools = new Map<\n number,\n {\n spanId: string;\n startMs: number;\n toolName: string;\n input: Record<string, string>;\n }\n >();\n // Secondary index: tool name → latest invocation counter (for tool_done matching)\n const toolNameToCounter = new Map<string, number>();\n\n let toolCallCount = 0;\n let successfulTools = 0;\n let failedTools = 0;\n\n const instrumentedSend = (event: AgentChatEvent): void => {\n try {\n if (event.type === \"tool_start\") {\n const counter = toolInvocationCounter++;\n const sid = spanId();\n pendingTools.set(counter, {\n spanId: sid,\n startMs: Date.now(),\n toolName: event.tool,\n input: event.input,\n });\n toolNameToCounter.set(event.tool, counter);\n } else if (event.type === \"tool_done\") {\n const counter = toolNameToCounter.get(event.tool);\n const pending =\n counter !== undefined ? pendingTools.get(counter) : undefined;\n if (counter !== undefined) {\n pendingTools.delete(counter);\n toolNameToCounter.delete(event.tool);\n }\n toolCallCount++;\n\n const isError =\n typeof event.result === \"string\" &&\n (event.result.startsWith(\"Error\") ||\n event.result.startsWith(\"Error running \"));\n if (isError) failedTools++;\n else successfulTools++;\n\n const span: TraceSpan = {\n id: pending?.spanId ?? spanId(),\n runId,\n threadId,\n userId,\n parentSpanId,\n spanType: \"tool_call\",\n name: event.tool,\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n costCentsX100: 0,\n durationMs: pending ? Date.now() - pending.startMs : 0,\n status: isError ? \"error\" : \"success\",\n errorMessage: isError ? event.result : null,\n metadata:\n config.captureToolArgs && pending\n ? // Strip Authorization/api-key/token-shaped values before\n // persisting (M14 in the MCP/A2A audit). Tool-runtime\n // execution still sees the unredacted input — only the\n // long-lived span row is sanitized.\n {\n input: redactSensitiveFields(pending.input) as Record<\n string,\n string\n >,\n }\n : null,\n createdAt: Date.now(),\n };\n spans.push(span);\n }\n } catch {}\n\n loopOpts.send(event);\n };\n\n let usage: AgentLoopUsage | undefined;\n let runStatus: \"success\" | \"error\" = \"success\";\n let errorMessage: string | null = null;\n try {\n usage = await runAgentLoop({ ...loopOpts, send: instrumentedSend });\n } catch (err: any) {\n runStatus = \"error\";\n errorMessage = err?.message ?? String(err);\n throw err;\n } finally {\n const runEnd = Date.now();\n const totalDurationMs = runEnd - runStart;\n\n let costCentsX100 = 0;\n try {\n const { calculateCost } = await import(\"../usage/store.js\");\n if (usage) {\n costCentsX100 = calculateCost(\n usage.inputTokens,\n usage.outputTokens,\n usage.model,\n usage.cacheReadTokens,\n usage.cacheWriteTokens,\n );\n }\n } catch {}\n\n let llmCallCount = 0;\n if (usage) {\n llmCallCount = 1;\n const llmSpan: TraceSpan = {\n id: spanId(),\n runId,\n threadId,\n userId,\n parentSpanId,\n spanType: \"llm_call\",\n name: usage.model,\n inputTokens: usage.inputTokens,\n outputTokens: usage.outputTokens,\n cacheReadTokens: usage.cacheReadTokens,\n cacheWriteTokens: usage.cacheWriteTokens,\n costCentsX100,\n durationMs: totalDurationMs,\n status: runStatus,\n errorMessage,\n metadata: null,\n createdAt: runStart,\n };\n spans.push(llmSpan);\n }\n\n const parentSpan: TraceSpan = {\n id: parentSpanId,\n runId,\n threadId,\n userId,\n parentSpanId: null,\n spanType: \"agent_run\",\n name: \"agent_run\",\n inputTokens: usage?.inputTokens ?? 0,\n outputTokens: usage?.outputTokens ?? 0,\n cacheReadTokens: usage?.cacheReadTokens ?? 0,\n cacheWriteTokens: usage?.cacheWriteTokens ?? 0,\n costCentsX100,\n durationMs: totalDurationMs,\n status: runStatus,\n errorMessage,\n metadata: null,\n createdAt: runStart,\n };\n spans.push(parentSpan);\n\n const summary: TraceSummary = {\n runId,\n threadId,\n userId,\n totalSpans: spans.length,\n llmCalls: llmCallCount,\n toolCalls: toolCallCount,\n successfulTools,\n failedTools,\n totalDurationMs,\n totalCostCentsX100: costCentsX100,\n totalInputTokens: usage?.inputTokens ?? 0,\n totalOutputTokens: usage?.outputTokens ?? 0,\n model: usage?.model ?? loopOpts.model,\n createdAt: runStart,\n };\n\n writeTraceData(spans, summary, runId, config).catch(() => {});\n }\n\n return usage!;\n}\n\nasync function writeTraceData(\n spans: TraceSpan[],\n summary: TraceSummary,\n runId: string,\n config: ObservabilityConfig,\n): Promise<void> {\n const { insertTraceSpan, upsertTraceSummary } = await import(\"./store.js\");\n await Promise.all(spans.map((s) => insertTraceSpan(s).catch(() => {})));\n await upsertTraceSummary(summary).catch(() => {});\n\n // Fire automated evals after trace data is persisted\n try {\n const { evaluateRun } = await import(\"./evals.js\");\n await evaluateRun(runId, { sampleRate: config.evalSampleRate });\n } catch {}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"traces.js","sourceRoot":"","sources":["../../src/observability/traces.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentChatEvent } from \"../agent/types.js\";\nimport type { AgentLoopUsage } from \"../agent/production-agent.js\";\nimport type { TraceSpan, TraceSummary, ObservabilityConfig } from \"./types.js\";\nimport { DEFAULT_OBSERVABILITY_CONFIG } from \"./types.js\";\n\nfunction spanId(): string {\n return `span-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\n/** Keys whose values are stripped from persisted tool inputs when\n * `captureToolArgs` is enabled. Matched case-insensitively and tolerant\n * of `_` / `-` separators. M14 in the MCP/A2A audit: tool calls\n * routinely receive credentials verbatim (db-exec INSERTs, fetchTool\n * Authorization headers, ad-hoc bearer tokens) — keeping those values\n * out of agent_trace_spans.metadata avoids long-term storage of\n * short-lived secrets. */\nconst SENSITIVE_FIELD_PATTERN =\n /^(authorization|cookie|api[_-]?key|password|secret|token|access[_-]?token|refresh[_-]?token|bearer)$/i;\n\n/** Recursively walk a structured value and replace sensitive field\n * values with the literal string \"[REDACTED]\". Pure (returns a copy);\n * the original input is never mutated. Cycles are tolerated via a\n * small WeakSet seen-tracker that returns \"[Circular]\" for repeats. */\nexport function redactSensitiveFields(value: unknown): unknown {\n return redactWalk(value, new WeakSet<object>());\n}\n\nfunction redactWalk(value: unknown, seen: WeakSet<object>): unknown {\n if (value === null || typeof value !== \"object\") return value;\n if (seen.has(value as object)) return \"[Circular]\";\n seen.add(value as object);\n if (Array.isArray(value)) {\n return value.map((v) => redactWalk(v, seen));\n }\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (SENSITIVE_FIELD_PATTERN.test(k)) {\n out[k] = \"[REDACTED]\";\n } else {\n out[k] = redactWalk(v, seen);\n }\n }\n return out;\n}\n\nexport async function getObservabilityConfig(): Promise<ObservabilityConfig> {\n try {\n const { getSetting } = await import(\"../settings/store.js\");\n const stored = await getSetting(\"observability-config\");\n if (stored) {\n return {\n ...DEFAULT_OBSERVABILITY_CONFIG,\n ...stored,\n } as ObservabilityConfig;\n }\n } catch {}\n return DEFAULT_OBSERVABILITY_CONFIG;\n}\n\nexport async function instrumentAgentLoop(opts: {\n runAgentLoop: (loopOpts: {\n engine: any;\n model: string;\n systemPrompt: string;\n tools: any[];\n messages: any[];\n actions: Record<string, any>;\n send: (event: AgentChatEvent) => void;\n signal: AbortSignal;\n providerOptions?: any;\n }) => Promise<AgentLoopUsage>;\n loopOpts: {\n engine: any;\n model: string;\n systemPrompt: string;\n tools: any[];\n messages: any[];\n actions: Record<string, any>;\n send: (event: AgentChatEvent) => void;\n signal: AbortSignal;\n providerOptions?: any;\n };\n runId: string;\n threadId: string | null;\n /** Owner of this run; persisted on every span + summary so dashboard\n * reads can filter to a single user. Null for unauthenticated callers\n * (background tasks, etc.) — those rows aren't returned by per-user\n * reads. */\n userId: string | null;\n config: ObservabilityConfig;\n}): Promise<AgentLoopUsage> {\n const { runAgentLoop, loopOpts, runId, threadId, userId, config } = opts;\n const runStart = Date.now();\n const parentSpanId = spanId();\n\n const spans: TraceSpan[] = [];\n let toolInvocationCounter = 0;\n // Keyed by counter to handle concurrent calls to the same tool name\n const pendingTools = new Map<\n number,\n {\n spanId: string;\n startMs: number;\n toolName: string;\n input: Record<string, string>;\n }\n >();\n // Secondary index: tool name → FIFO queue of pending invocation counters.\n // tool_start/tool_done events carry only the tool name (no call id), so to\n // pair starts and dones correctly when the agent runs concurrent calls to the\n // same tool name (read-only / parallelSafe batches via Promise.all), we keep a\n // queue per name and match each done to the OLDEST still-pending start.\n const toolNameToCounters = new Map<string, number[]>();\n\n let toolCallCount = 0;\n let successfulTools = 0;\n let failedTools = 0;\n\n const instrumentedSend = (event: AgentChatEvent): void => {\n try {\n if (event.type === \"tool_start\") {\n const counter = toolInvocationCounter++;\n const sid = spanId();\n pendingTools.set(counter, {\n spanId: sid,\n startMs: Date.now(),\n toolName: event.tool,\n input: event.input,\n });\n const queue = toolNameToCounters.get(event.tool);\n if (queue) queue.push(counter);\n else toolNameToCounters.set(event.tool, [counter]);\n } else if (event.type === \"tool_done\") {\n const queue = toolNameToCounters.get(event.tool);\n const counter = queue?.shift();\n const pending =\n counter !== undefined ? pendingTools.get(counter) : undefined;\n if (counter !== undefined) {\n pendingTools.delete(counter);\n if (queue && queue.length === 0)\n toolNameToCounters.delete(event.tool);\n }\n toolCallCount++;\n\n const isError =\n typeof event.result === \"string\" &&\n (event.result.startsWith(\"Error\") ||\n event.result.startsWith(\"Error running \"));\n if (isError) failedTools++;\n else successfulTools++;\n\n const span: TraceSpan = {\n id: pending?.spanId ?? spanId(),\n runId,\n threadId,\n userId,\n parentSpanId,\n spanType: \"tool_call\",\n name: event.tool,\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n costCentsX100: 0,\n durationMs: pending ? Date.now() - pending.startMs : 0,\n status: isError ? \"error\" : \"success\",\n errorMessage: isError ? event.result : null,\n metadata:\n config.captureToolArgs && pending\n ? // Strip Authorization/api-key/token-shaped values before\n // persisting (M14 in the MCP/A2A audit). Tool-runtime\n // execution still sees the unredacted input — only the\n // long-lived span row is sanitized.\n {\n input: redactSensitiveFields(pending.input) as Record<\n string,\n string\n >,\n }\n : null,\n createdAt: Date.now(),\n };\n spans.push(span);\n }\n } catch {}\n\n loopOpts.send(event);\n };\n\n let usage: AgentLoopUsage | undefined;\n let runStatus: \"success\" | \"error\" = \"success\";\n let errorMessage: string | null = null;\n try {\n usage = await runAgentLoop({ ...loopOpts, send: instrumentedSend });\n } catch (err: any) {\n runStatus = \"error\";\n errorMessage = err?.message ?? String(err);\n throw err;\n } finally {\n const runEnd = Date.now();\n const totalDurationMs = runEnd - runStart;\n\n let costCentsX100 = 0;\n try {\n const { calculateCost } = await import(\"../usage/store.js\");\n if (usage) {\n costCentsX100 = calculateCost(\n usage.inputTokens,\n usage.outputTokens,\n usage.model,\n usage.cacheReadTokens,\n usage.cacheWriteTokens,\n );\n }\n } catch {}\n\n let llmCallCount = 0;\n if (usage) {\n llmCallCount = 1;\n const llmSpan: TraceSpan = {\n id: spanId(),\n runId,\n threadId,\n userId,\n parentSpanId,\n spanType: \"llm_call\",\n name: usage.model,\n inputTokens: usage.inputTokens,\n outputTokens: usage.outputTokens,\n cacheReadTokens: usage.cacheReadTokens,\n cacheWriteTokens: usage.cacheWriteTokens,\n costCentsX100,\n durationMs: totalDurationMs,\n status: runStatus,\n errorMessage,\n metadata: null,\n createdAt: runStart,\n };\n spans.push(llmSpan);\n }\n\n const parentSpan: TraceSpan = {\n id: parentSpanId,\n runId,\n threadId,\n userId,\n parentSpanId: null,\n spanType: \"agent_run\",\n name: \"agent_run\",\n inputTokens: usage?.inputTokens ?? 0,\n outputTokens: usage?.outputTokens ?? 0,\n cacheReadTokens: usage?.cacheReadTokens ?? 0,\n cacheWriteTokens: usage?.cacheWriteTokens ?? 0,\n costCentsX100,\n durationMs: totalDurationMs,\n status: runStatus,\n errorMessage,\n metadata: null,\n createdAt: runStart,\n };\n spans.push(parentSpan);\n\n const summary: TraceSummary = {\n runId,\n threadId,\n userId,\n totalSpans: spans.length,\n llmCalls: llmCallCount,\n toolCalls: toolCallCount,\n successfulTools,\n failedTools,\n totalDurationMs,\n totalCostCentsX100: costCentsX100,\n totalInputTokens: usage?.inputTokens ?? 0,\n totalOutputTokens: usage?.outputTokens ?? 0,\n model: usage?.model ?? loopOpts.model,\n createdAt: runStart,\n };\n\n writeTraceData(spans, summary, runId, config).catch(() => {});\n }\n\n return usage!;\n}\n\nasync function writeTraceData(\n spans: TraceSpan[],\n summary: TraceSummary,\n runId: string,\n config: ObservabilityConfig,\n): Promise<void> {\n const { insertTraceSpan, upsertTraceSummary } = await import(\"./store.js\");\n await Promise.all(spans.map((s) => insertTraceSpan(s).catch(() => {})));\n await upsertTraceSummary(summary).catch(() => {});\n\n // Fire automated evals after trace data is persisted\n try {\n const { evaluateRun } = await import(\"./evals.js\");\n await evaluateRun(runId, { sampleRate: config.evalSampleRate });\n } catch {}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/org/handlers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/org/handlers.ts"],"names":[],"mappings":"AAgDA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AA2B1C,2FAA2F;AAC3F,eAAO,MAAM,eAAe;;;;;;;cAaA,OAAO;;;;;;;;;;eAIC,MAAM;iBAAW,MAAM;;;;GA0EzD,CAAC;AAEH,0DAA0D;AAC1D,eAAO,MAAM,gBAAgB;;;;GAe3B,CAAC;AAEH,wDAAwD;AACxD,eAAO,MAAM,kBAAkB;;;cA2CH,OAAO;;;;;GAQjC,CAAC;AAqBH,UAAU,kBAAkB;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC;IACzB,MAAM,EAAE,SAAS,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,mBAAmB;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAsED,8EAA8E;AAC9E,eAAO,MAAM,uBAAuB;;;;GAuEnC,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,sBAAsB;;;;;;;;;GAyBlC,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,uBAAuB;;;UAoD8B,OAAO;GAkBxE,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,mBAAmB;;GA2D/B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB;;;GAqEnC,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,gBAAgB;;;GA4B3B,CAAC;AAEH,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB;;;UAkCC,OAAO;GAEnC,CAAC;AAEH,mGAAmG;AACnG,eAAO,MAAM,mBAAmB;;;UAqDR,OAAO;GAG9B,CAAC;AAEF,+FAA+F;AAC/F,eAAO,MAAM,gBAAgB;;GAsE3B,CAAC;AAEH,oGAAoG;AACpG,eAAO,MAAM,mBAAmB;;;GA0C/B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,oBAAoB;;;;;YA2DvB,MAAM;cACJ,MAAM;aACP,MAAM;YACP,OAAO;iBACF,MAAM;gBACP,MAAM;;GA6DnB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,uBAAuB;;;GAgGnC,CAAC"}
|