@agent-native/core 0.77.8 → 0.77.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +29 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/task-store.ts +25 -3
- package/corpus/core/src/agent/harness/store.ts +89 -55
- package/corpus/core/src/agent/observational-memory/store.ts +40 -14
- package/corpus/core/src/agent/run-store.ts +116 -59
- package/corpus/core/src/application-state/store.ts +36 -2
- package/corpus/core/src/audit/store.ts +37 -6
- package/corpus/core/src/browser-sessions/store.ts +31 -12
- package/corpus/core/src/chat-threads/store.ts +67 -3
- package/corpus/core/src/checkpoints/store.ts +14 -3
- package/corpus/core/src/cli/create.ts +9 -0
- package/corpus/core/src/collab/storage.ts +20 -2
- package/corpus/core/src/db/ddl-guard.ts +112 -0
- package/corpus/core/src/extensions/store.ts +76 -3
- package/corpus/core/src/integrations/a2a-continuations-store.ts +69 -26
- package/corpus/core/src/integrations/config-store.ts +19 -18
- package/corpus/core/src/integrations/pending-tasks-store.ts +40 -16
- package/corpus/core/src/integrations/remote-commands-store.ts +37 -23
- package/corpus/core/src/integrations/remote-devices-store.ts +64 -20
- package/corpus/core/src/integrations/remote-push-store.ts +75 -35
- package/corpus/core/src/integrations/remote-run-events-store.ts +38 -12
- package/corpus/core/src/integrations/thread-mapping-store.ts +11 -2
- package/corpus/core/src/mcp/connect-store.ts +53 -16
- package/corpus/core/src/mcp/oauth-store.ts +32 -7
- package/corpus/core/src/notifications/store.ts +20 -4
- package/corpus/core/src/oauth-tokens/store.ts +45 -2
- package/corpus/core/src/observability/store.ts +154 -47
- package/corpus/core/src/progress/store.ts +36 -21
- package/corpus/core/src/provider-api/corpus-jobs-store.ts +25 -5
- package/corpus/core/src/provider-api/custom-registry.ts +15 -4
- package/corpus/core/src/provider-api/quota-governor.ts +15 -3
- package/corpus/core/src/provider-api/staged-datasets-store.ts +22 -5
- package/corpus/core/src/resources/store.ts +66 -31
- package/corpus/core/src/secrets/storage.ts +18 -22
- package/corpus/core/src/server/agent-teams-run-queue.ts +22 -10
- package/corpus/core/src/server/auth.ts +22 -4
- package/corpus/core/src/server/better-auth-instance.ts +49 -15
- package/corpus/core/src/server/embed-session.ts +19 -4
- package/corpus/core/src/server/identity-sso-store.ts +40 -11
- package/corpus/core/src/server/recap-image-store.ts +28 -9
- package/corpus/core/src/settings/store.ts +14 -19
- package/corpus/core/src/usage/store.ts +51 -16
- package/corpus/core/src/workspace-connections/store.ts +200 -22
- package/corpus/templates/calendar/changelog/2026-06-25-booking-link-previews-now-show-the-content-directly-on-the-g.md +6 -0
- package/corpus/templates/calendar/server/lib/booking-og-image.ts +0 -2
- package/corpus/templates/forms/changelog/2026-06-25-form-previews-now-show-the-content-directly-on-the-grid-back.md +6 -0
- package/corpus/templates/forms/server/lib/form-og-image.ts +0 -2
- package/corpus/templates/plan/app/global.css +6 -11
- package/corpus/templates/plan/app/pages/PlansPage.tsx +12 -10
- package/corpus/templates/plan/changelog/2026-06-25-plan-access-request-pages-stay-visible-during-background-ref.md +6 -0
- package/corpus/templates/plan/changelog/2026-06-25-plan-documents-keep-their-full-reading-width-below-1200px-an.md +6 -0
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +19 -3
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/agent/harness/store.d.ts.map +1 -1
- package/dist/agent/harness/store.js +63 -34
- package/dist/agent/harness/store.js.map +1 -1
- package/dist/agent/observational-memory/store.d.ts.map +1 -1
- package/dist/agent/observational-memory/store.js +30 -8
- package/dist/agent/observational-memory/store.js.map +1 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +103 -46
- package/dist/agent/run-store.js.map +1 -1
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +28 -2
- package/dist/application-state/store.js.map +1 -1
- package/dist/audit/store.d.ts.map +1 -1
- package/dist/audit/store.js +18 -3
- package/dist/audit/store.js.map +1 -1
- package/dist/browser-sessions/store.d.ts.map +1 -1
- package/dist/browser-sessions/store.js +22 -7
- package/dist/browser-sessions/store.js.map +1 -1
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +51 -3
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/checkpoints/store.d.ts.map +1 -1
- package/dist/checkpoints/store.js +12 -3
- package/dist/checkpoints/store.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +9 -0
- package/dist/cli/create.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/collab/storage.d.ts.map +1 -1
- package/dist/collab/storage.js +14 -2
- package/dist/collab/storage.js.map +1 -1
- package/dist/db/ddl-guard.d.ts +63 -0
- package/dist/db/ddl-guard.d.ts.map +1 -1
- package/dist/db/ddl-guard.js +85 -0
- package/dist/db/ddl-guard.js.map +1 -1
- package/dist/extensions/store.d.ts.map +1 -1
- package/dist/extensions/store.js +32 -3
- package/dist/extensions/store.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
- package/dist/integrations/a2a-continuations-store.js +44 -24
- package/dist/integrations/a2a-continuations-store.js.map +1 -1
- package/dist/integrations/config-store.d.ts.map +1 -1
- package/dist/integrations/config-store.js +17 -11
- package/dist/integrations/config-store.js.map +1 -1
- package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
- package/dist/integrations/pending-tasks-store.js +24 -16
- package/dist/integrations/pending-tasks-store.js.map +1 -1
- package/dist/integrations/remote-commands-store.d.ts.map +1 -1
- package/dist/integrations/remote-commands-store.js +29 -21
- package/dist/integrations/remote-commands-store.js.map +1 -1
- package/dist/integrations/remote-devices-store.d.ts.map +1 -1
- package/dist/integrations/remote-devices-store.js +33 -18
- package/dist/integrations/remote-devices-store.js.map +1 -1
- package/dist/integrations/remote-push-store.d.ts.map +1 -1
- package/dist/integrations/remote-push-store.js +53 -31
- package/dist/integrations/remote-push-store.js.map +1 -1
- package/dist/integrations/remote-run-events-store.d.ts.map +1 -1
- package/dist/integrations/remote-run-events-store.js +26 -10
- package/dist/integrations/remote-run-events-store.js.map +1 -1
- package/dist/integrations/thread-mapping-store.d.ts.map +1 -1
- package/dist/integrations/thread-mapping-store.js +10 -2
- package/dist/integrations/thread-mapping-store.js.map +1 -1
- package/dist/mcp/connect-store.d.ts.map +1 -1
- package/dist/mcp/connect-store.js +34 -16
- package/dist/mcp/connect-store.js.map +1 -1
- package/dist/mcp/oauth-store.d.ts.map +1 -1
- package/dist/mcp/oauth-store.js +22 -7
- package/dist/mcp/oauth-store.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/notifications/store.d.ts.map +1 -1
- package/dist/notifications/store.js +15 -3
- package/dist/notifications/store.js.map +1 -1
- package/dist/oauth-tokens/store.d.ts.map +1 -1
- package/dist/oauth-tokens/store.js +33 -2
- package/dist/oauth-tokens/store.js.map +1 -1
- package/dist/observability/routes.d.ts +7 -7
- package/dist/observability/store.d.ts.map +1 -1
- package/dist/observability/store.js +72 -29
- package/dist/observability/store.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/progress/store.d.ts.map +1 -1
- package/dist/progress/store.js +31 -16
- package/dist/progress/store.js.map +1 -1
- package/dist/provider-api/corpus-jobs-store.d.ts.map +1 -1
- package/dist/provider-api/corpus-jobs-store.js +16 -5
- package/dist/provider-api/corpus-jobs-store.js.map +1 -1
- package/dist/provider-api/custom-registry.d.ts.map +1 -1
- package/dist/provider-api/custom-registry.js +14 -4
- package/dist/provider-api/custom-registry.js.map +1 -1
- package/dist/provider-api/quota-governor.d.ts.map +1 -1
- package/dist/provider-api/quota-governor.js +12 -3
- package/dist/provider-api/quota-governor.js.map +1 -1
- package/dist/provider-api/staged-datasets-store.d.ts.map +1 -1
- package/dist/provider-api/staged-datasets-store.js +16 -5
- package/dist/provider-api/staged-datasets-store.js.map +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +56 -25
- package/dist/resources/store.js.map +1 -1
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +10 -14
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-teams-run-queue.d.ts.map +1 -1
- package/dist/server/agent-teams-run-queue.js +15 -4
- package/dist/server/agent-teams-run-queue.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +16 -2
- 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 +55 -21
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/embed-session.d.ts.map +1 -1
- package/dist/server/embed-session.js +15 -4
- package/dist/server/embed-session.js.map +1 -1
- package/dist/server/identity-sso-store.d.ts.map +1 -1
- package/dist/server/identity-sso-store.js +30 -11
- package/dist/server/identity-sso-store.js.map +1 -1
- package/dist/server/recap-image-store.d.ts.map +1 -1
- package/dist/server/recap-image-store.js +21 -7
- package/dist/server/recap-image-store.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/settings/store.d.ts.map +1 -1
- package/dist/settings/store.js +11 -13
- package/dist/settings/store.js.map +1 -1
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +36 -11
- package/dist/usage/store.js.map +1 -1
- package/dist/workspace-connections/store.d.ts.map +1 -1
- package/dist/workspace-connections/store.js +57 -7
- package/dist/workspace-connections/store.js.map +1 -1
- package/package.json +1 -1
|
@@ -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 } from \"../db/client.js\";\nimport { widenIntColumnsToBigInt } from \"../db/widen-columns.js\";\nimport {\n encryptSecretValue,\n decryptSecretValue,\n isEncryptedSecretValue,\n} from \"../secrets/crypto.js\";\n\nlet _initPromise: Promise<void> | undefined;\n\n/**\n * Encrypt the token bundle (AES-256-GCM) before it goes to the `tokens`\n * column. OAuth access/refresh tokens are long-lived, high-value credentials;\n * encrypting at rest means a leaked DB backup / pg_dump / read replica no\n * longer exposes them in plaintext. {@link parseStoredTokens} decrypts\n * transparently on read.\n */\nfunction serializeTokens(tokens: Record<string, unknown>): string {\n return encryptSecretValue(JSON.stringify(tokens));\n}\n\n/**\n * Parse a stored `tokens` value. Encrypted rows are decrypted; rows written\n * before encryption — or mirrored from Better Auth's `account` table — are\n * plaintext JSON and read transparently (the `db-migrate-encrypt-oauth-tokens`\n * script re-encrypts them in place). A row that can't be decrypted (key\n * rotated / corrupt / tampered) is treated as empty rather than throwing into\n * every token lookup.\n */\nfunction parseStoredTokens(\n stored: string | null | undefined,\n): Record<string, unknown> {\n if (!stored) return {};\n if (isEncryptedSecretValue(stored)) {\n try {\n return JSON.parse(decryptSecretValue(stored));\n } catch {\n return {};\n }\n }\n try {\n return JSON.parse(stored);\n } catch {\n return {};\n }\n}\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 // Older deployments have a 32-bit `updated_at`; on Postgres the\n // `Date.now()` written on every token save overflows int4. Widen in place\n // (no-op once done / on fresh DBs). Unqualified name — the helper scopes\n // to the `public` schema.\n await widenIntColumnsToBigInt(\"oauth_tokens\", [\"updated_at\"]);\n })().catch((err) => {\n // Retry init on the next call after a failed startup.\n _initPromise = undefined;\n throw err;\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 parseStoredTokens(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 = parseStoredTokens(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 serializeTokens(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: parseStoredTokens(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: parseStoredTokens(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":"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","sourcesContent":["import { getDbExec, isPostgres, intType } from \"../db/client.js\";\nimport { ensureColumnExists, ensureTableExists } from \"../db/ddl-guard.js\";\nimport { widenIntColumnsToBigInt } from \"../db/widen-columns.js\";\nimport {\n encryptSecretValue,\n decryptSecretValue,\n isEncryptedSecretValue,\n} from \"../secrets/crypto.js\";\n\nlet _initPromise: Promise<void> | undefined;\n\n/**\n * Encrypt the token bundle (AES-256-GCM) before it goes to the `tokens`\n * column. OAuth access/refresh tokens are long-lived, high-value credentials;\n * encrypting at rest means a leaked DB backup / pg_dump / read replica no\n * longer exposes them in plaintext. {@link parseStoredTokens} decrypts\n * transparently on read.\n */\nfunction serializeTokens(tokens: Record<string, unknown>): string {\n return encryptSecretValue(JSON.stringify(tokens));\n}\n\n/**\n * Parse a stored `tokens` value. Encrypted rows are decrypted; rows written\n * before encryption — or mirrored from Better Auth's `account` table — are\n * plaintext JSON and read transparently (the `db-migrate-encrypt-oauth-tokens`\n * script re-encrypts them in place). A row that can't be decrypted (key\n * rotated / corrupt / tampered) is treated as empty rather than throwing into\n * every token lookup.\n */\nfunction parseStoredTokens(\n stored: string | null | undefined,\n): Record<string, unknown> {\n if (!stored) return {};\n if (isEncryptedSecretValue(stored)) {\n try {\n return JSON.parse(decryptSecretValue(stored));\n } catch {\n return {};\n }\n }\n try {\n return JSON.parse(stored);\n } catch {\n return {};\n }\n}\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 const createSql = `\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\n if (isPostgres()) {\n // Hot path: the `oauth_tokens` table and its additive columns are\n // virtually always already present in production. Issuing `CREATE\n // TABLE`/`ALTER TABLE` still takes an ACCESS EXCLUSIVE lock that, in a\n // fresh background-worker process behind a concurrent connection on the\n // shared Neon DB, can block ~indefinitely. The ensure* wrappers probe\n // `information_schema` first (plain reads, no lock) and run DDL ONLY for\n // what is actually missing, bounded by a transaction-scoped\n // `lock_timeout`. If a swallowed lock-timeout leaves the schema still\n // missing they RE-PROBE and THROW rather than letting init memoize\n // success against absent schema. `oauthTokensTable()` is\n // `public.oauth_tokens` on Postgres; the wrappers take the unqualified\n // table name.\n await ensureTableExists(\"oauth_tokens\", createSql);\n // Migration: add owner column to existing tables — guarded so the hot\n // path (column already present) skips the ACCESS EXCLUSIVE ALTER.\n await ensureColumnExists(\n \"oauth_tokens\",\n \"owner\",\n `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS owner TEXT`,\n );\n // Migration: add display_name column\n await ensureColumnExists(\n \"oauth_tokens\",\n \"display_name\",\n `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS display_name TEXT`,\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 // Older deployments have a 32-bit `updated_at`; on Postgres the\n // `Date.now()` written on every token save overflows int4. Widen in place\n // (no-op once done / on fresh DBs). Unqualified name — the helper scopes\n // to the `public` schema.\n await widenIntColumnsToBigInt(\"oauth_tokens\", [\"updated_at\"]);\n return;\n }\n\n // SQLite (local dev): no lock problem — keep the original behaviour.\n await client.execute(createSql);\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 // Older deployments have a 32-bit `updated_at`; on Postgres the\n // `Date.now()` written on every token save overflows int4. Widen in place\n // (no-op once done / on fresh DBs). Unqualified name — the helper scopes\n // to the `public` schema.\n await widenIntColumnsToBigInt(\"oauth_tokens\", [\"updated_at\"]);\n })().catch((err) => {\n // Retry init on the next call after a failed startup.\n _initPromise = undefined;\n throw err;\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 parseStoredTokens(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 = parseStoredTokens(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 serializeTokens(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: parseStoredTokens(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: parseStoredTokens(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"]}
|
|
@@ -41,28 +41,28 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
41
41
|
thumbsUpRate: number;
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
|
+
ok?: undefined;
|
|
44
45
|
summary: import("./types.js").TraceSummary;
|
|
45
46
|
spans: import("./types.js").TraceSpan[];
|
|
46
|
-
id?: undefined;
|
|
47
47
|
error?: undefined;
|
|
48
|
-
|
|
48
|
+
id?: undefined;
|
|
49
49
|
} | {
|
|
50
|
+
ok?: undefined;
|
|
50
51
|
summary?: undefined;
|
|
51
52
|
spans?: undefined;
|
|
52
53
|
id: string;
|
|
53
54
|
error?: undefined;
|
|
54
|
-
ok?: undefined;
|
|
55
55
|
} | {
|
|
56
|
+
ok?: undefined;
|
|
56
57
|
summary?: undefined;
|
|
57
58
|
spans?: undefined;
|
|
58
|
-
id?: undefined;
|
|
59
59
|
error: any;
|
|
60
|
-
|
|
60
|
+
id?: undefined;
|
|
61
61
|
} | {
|
|
62
62
|
summary?: undefined;
|
|
63
63
|
spans?: undefined;
|
|
64
|
-
id?: undefined;
|
|
65
|
-
error?: undefined;
|
|
66
64
|
ok: boolean;
|
|
65
|
+
error?: undefined;
|
|
66
|
+
id?: undefined;
|
|
67
67
|
}>>;
|
|
68
68
|
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/observability/store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/observability/store.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EACV,SAAS,EACT,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,UAAU,EACV,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AA0CpB,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CA0T/D;AAID,wBAAsB,eAAe,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BpE;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA8E7E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAyBD;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7B,OAAO,CAAC,SAAS,EAAE,CAAC,CAStB;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAkB1B;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAU9B;AAID,wBAAsB,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBxE;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CA6B3B;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7B,OAAO,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC,CAAC,CA2BD;AAID,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,IAAI,CAAC,CAsDf;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAyB/B;AAID,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBxE;AAED,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7B,OAAO,CAAC,UAAU,EAAE,CAAC,CASvB;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7B,OAAO,CAAC;IACT,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1E,CAAC,CA+BD;AAID,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB3E;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAO/D;AAED,wBAAsB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAS5E;AAED,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAC,CAAC,GACtE,OAAO,CAAC,IAAI,CAAC,CAwBf;AAID,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBrE;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,OAAO,CACd,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC,CACzE,GACA,OAAO,CAAC,IAAI,CAAC,CAmCf;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAO7D;AAED,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAS1E;AAID,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,oBAAoB,GAC/B,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAgBtC;AAID,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED,wBAAsB,oBAAoB,CACxC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAUnC;AAID,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7B,OAAO,CAAC;IACT,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC,CA6DD"}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* rather than Drizzle ORM (which is for template-level schemas).
|
|
8
8
|
*/
|
|
9
9
|
import { getDbExec, intType, isPostgres, retryOnDdlRace, } from "../db/client.js";
|
|
10
|
+
import { ensureTableExists, ensureColumnExists, ensureIndexExists, } from "../db/ddl-guard.js";
|
|
10
11
|
import { isDuplicateColumnError } from "../db/migrations.js";
|
|
11
12
|
function safeJsonParse(value, fallback) {
|
|
12
13
|
if (!value)
|
|
@@ -47,7 +48,7 @@ export async function ensureObservabilityTables() {
|
|
|
47
48
|
if (!_initPromise) {
|
|
48
49
|
_initPromise = (async () => {
|
|
49
50
|
const client = getDbExec();
|
|
50
|
-
|
|
51
|
+
const traceSpansCreateSql = `
|
|
51
52
|
CREATE TABLE IF NOT EXISTS agent_trace_spans (
|
|
52
53
|
id TEXT PRIMARY KEY,
|
|
53
54
|
run_id TEXT NOT NULL,
|
|
@@ -67,8 +68,8 @@ export async function ensureObservabilityTables() {
|
|
|
67
68
|
metadata TEXT,
|
|
68
69
|
created_at ${intType()} NOT NULL
|
|
69
70
|
)
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
`;
|
|
72
|
+
const traceSummariesCreateSql = `
|
|
72
73
|
CREATE TABLE IF NOT EXISTS agent_trace_summaries (
|
|
73
74
|
run_id TEXT PRIMARY KEY,
|
|
74
75
|
thread_id TEXT,
|
|
@@ -85,8 +86,8 @@ export async function ensureObservabilityTables() {
|
|
|
85
86
|
model TEXT NOT NULL DEFAULT '',
|
|
86
87
|
created_at ${intType()} NOT NULL
|
|
87
88
|
)
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
`;
|
|
90
|
+
const feedbackCreateSql = `
|
|
90
91
|
CREATE TABLE IF NOT EXISTS agent_feedback (
|
|
91
92
|
id TEXT PRIMARY KEY,
|
|
92
93
|
run_id TEXT,
|
|
@@ -97,8 +98,8 @@ export async function ensureObservabilityTables() {
|
|
|
97
98
|
user_id TEXT,
|
|
98
99
|
created_at ${intType()} NOT NULL
|
|
99
100
|
)
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
`;
|
|
102
|
+
const satisfactionScoresCreateSql = `
|
|
102
103
|
CREATE TABLE IF NOT EXISTS agent_satisfaction_scores (
|
|
103
104
|
id TEXT PRIMARY KEY,
|
|
104
105
|
thread_id TEXT NOT NULL,
|
|
@@ -110,8 +111,8 @@ export async function ensureObservabilityTables() {
|
|
|
110
111
|
length_trend_score REAL NOT NULL DEFAULT 0,
|
|
111
112
|
computed_at ${intType()} NOT NULL
|
|
112
113
|
)
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
`;
|
|
115
|
+
const evalsCreateSql = `
|
|
115
116
|
CREATE TABLE IF NOT EXISTS agent_evals (
|
|
116
117
|
id TEXT PRIMARY KEY,
|
|
117
118
|
run_id TEXT NOT NULL,
|
|
@@ -124,8 +125,8 @@ export async function ensureObservabilityTables() {
|
|
|
124
125
|
metadata TEXT,
|
|
125
126
|
created_at ${intType()} NOT NULL
|
|
126
127
|
)
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
`;
|
|
129
|
+
const evalDatasetsCreateSql = `
|
|
129
130
|
CREATE TABLE IF NOT EXISTS agent_eval_datasets (
|
|
130
131
|
id TEXT PRIMARY KEY,
|
|
131
132
|
name TEXT NOT NULL,
|
|
@@ -134,8 +135,8 @@ export async function ensureObservabilityTables() {
|
|
|
134
135
|
created_at ${intType()} NOT NULL,
|
|
135
136
|
updated_at ${intType()} NOT NULL
|
|
136
137
|
)
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
`;
|
|
139
|
+
const experimentsCreateSql = `
|
|
139
140
|
CREATE TABLE IF NOT EXISTS agent_experiments (
|
|
140
141
|
id TEXT PRIMARY KEY,
|
|
141
142
|
name TEXT NOT NULL,
|
|
@@ -148,19 +149,8 @@ export async function ensureObservabilityTables() {
|
|
|
148
149
|
created_at ${intType()} NOT NULL,
|
|
149
150
|
owner_email TEXT
|
|
150
151
|
)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// (any pre-existing rows have NULL owner — see `updateExperiment` for
|
|
154
|
-
// the migration semantics). Mutations on those rows fall back to the
|
|
155
|
-
// standard authentication gate but cannot enforce per-owner scoping
|
|
156
|
-
// until they're re-saved.
|
|
157
|
-
try {
|
|
158
|
-
await client.execute(`ALTER TABLE agent_experiments ADD COLUMN owner_email TEXT`);
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
// Column already exists — expected after first run.
|
|
162
|
-
}
|
|
163
|
-
await retryOnDdlRace(() => client.execute(`
|
|
152
|
+
`;
|
|
153
|
+
const experimentAssignmentsCreateSql = `
|
|
164
154
|
CREATE TABLE IF NOT EXISTS agent_experiment_assignments (
|
|
165
155
|
experiment_id TEXT NOT NULL,
|
|
166
156
|
user_id TEXT NOT NULL,
|
|
@@ -168,8 +158,8 @@ export async function ensureObservabilityTables() {
|
|
|
168
158
|
assigned_at ${intType()} NOT NULL,
|
|
169
159
|
PRIMARY KEY (experiment_id, user_id)
|
|
170
160
|
)
|
|
171
|
-
|
|
172
|
-
|
|
161
|
+
`;
|
|
162
|
+
const experimentResultsCreateSql = `
|
|
173
163
|
CREATE TABLE IF NOT EXISTS agent_experiment_results (
|
|
174
164
|
id TEXT PRIMARY KEY,
|
|
175
165
|
experiment_id TEXT NOT NULL,
|
|
@@ -181,7 +171,60 @@ export async function ensureObservabilityTables() {
|
|
|
181
171
|
confidence_high REAL NOT NULL DEFAULT 0,
|
|
182
172
|
computed_at ${intType()} NOT NULL
|
|
183
173
|
)
|
|
184
|
-
|
|
174
|
+
`;
|
|
175
|
+
if (isPostgres()) {
|
|
176
|
+
// PG guard: probe → guarded DDL → re-probe; skips lock on already-migrated path
|
|
177
|
+
await ensureTableExists("agent_trace_spans", traceSpansCreateSql);
|
|
178
|
+
await ensureTableExists("agent_trace_summaries", traceSummariesCreateSql);
|
|
179
|
+
await ensureTableExists("agent_feedback", feedbackCreateSql);
|
|
180
|
+
await ensureTableExists("agent_satisfaction_scores", satisfactionScoresCreateSql);
|
|
181
|
+
await ensureTableExists("agent_evals", evalsCreateSql);
|
|
182
|
+
await ensureTableExists("agent_eval_datasets", evalDatasetsCreateSql);
|
|
183
|
+
await ensureTableExists("agent_experiments", experimentsCreateSql);
|
|
184
|
+
await ensureTableExists("agent_experiment_assignments", experimentAssignmentsCreateSql);
|
|
185
|
+
await ensureTableExists("agent_experiment_results", experimentResultsCreateSql);
|
|
186
|
+
await ensureColumnExists("agent_experiments", "owner_email", `ALTER TABLE agent_experiments ADD COLUMN IF NOT EXISTS owner_email TEXT`);
|
|
187
|
+
for (const table of USER_SCOPED_TABLES) {
|
|
188
|
+
await ensureColumnExists(table, "user_id", `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS user_id TEXT`);
|
|
189
|
+
}
|
|
190
|
+
await ensureIndexExists("idx_trace_spans_run", `CREATE INDEX IF NOT EXISTS idx_trace_spans_run ON agent_trace_spans (run_id)`);
|
|
191
|
+
await ensureIndexExists("idx_trace_spans_thread", `CREATE INDEX IF NOT EXISTS idx_trace_spans_thread ON agent_trace_spans (thread_id)`);
|
|
192
|
+
await ensureIndexExists("idx_trace_spans_created", `CREATE INDEX IF NOT EXISTS idx_trace_spans_created ON agent_trace_spans (created_at)`);
|
|
193
|
+
await ensureIndexExists("idx_trace_summaries_created", `CREATE INDEX IF NOT EXISTS idx_trace_summaries_created ON agent_trace_summaries (created_at)`);
|
|
194
|
+
await ensureIndexExists("idx_trace_summaries_user", `CREATE INDEX IF NOT EXISTS idx_trace_summaries_user ON agent_trace_summaries (user_id, created_at)`);
|
|
195
|
+
await ensureIndexExists("idx_trace_spans_user", `CREATE INDEX IF NOT EXISTS idx_trace_spans_user ON agent_trace_spans (user_id)`);
|
|
196
|
+
await ensureIndexExists("idx_feedback_thread", `CREATE INDEX IF NOT EXISTS idx_feedback_thread ON agent_feedback (thread_id)`);
|
|
197
|
+
await ensureIndexExists("idx_feedback_created", `CREATE INDEX IF NOT EXISTS idx_feedback_created ON agent_feedback (created_at)`);
|
|
198
|
+
await ensureIndexExists("idx_feedback_user", `CREATE INDEX IF NOT EXISTS idx_feedback_user ON agent_feedback (user_id, created_at)`);
|
|
199
|
+
await ensureIndexExists("idx_satisfaction_thread", `CREATE INDEX IF NOT EXISTS idx_satisfaction_thread ON agent_satisfaction_scores (thread_id)`);
|
|
200
|
+
await ensureIndexExists("idx_satisfaction_user", `CREATE INDEX IF NOT EXISTS idx_satisfaction_user ON agent_satisfaction_scores (user_id, computed_at)`);
|
|
201
|
+
await ensureIndexExists("idx_evals_run", `CREATE INDEX IF NOT EXISTS idx_evals_run ON agent_evals (run_id)`);
|
|
202
|
+
await ensureIndexExists("idx_evals_created", `CREATE INDEX IF NOT EXISTS idx_evals_created ON agent_evals (created_at)`);
|
|
203
|
+
await ensureIndexExists("idx_evals_user", `CREATE INDEX IF NOT EXISTS idx_evals_user ON agent_evals (user_id, created_at)`);
|
|
204
|
+
await ensureIndexExists("idx_experiment_results_exp", `CREATE INDEX IF NOT EXISTS idx_experiment_results_exp ON agent_experiment_results (experiment_id)`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
208
|
+
await retryOnDdlRace(() => client.execute(traceSpansCreateSql));
|
|
209
|
+
await retryOnDdlRace(() => client.execute(traceSummariesCreateSql));
|
|
210
|
+
await retryOnDdlRace(() => client.execute(feedbackCreateSql));
|
|
211
|
+
await retryOnDdlRace(() => client.execute(satisfactionScoresCreateSql));
|
|
212
|
+
await retryOnDdlRace(() => client.execute(evalsCreateSql));
|
|
213
|
+
await retryOnDdlRace(() => client.execute(evalDatasetsCreateSql));
|
|
214
|
+
await retryOnDdlRace(() => client.execute(experimentsCreateSql));
|
|
215
|
+
// Additive migration for DBs created before the owner column shipped
|
|
216
|
+
// (any pre-existing rows have NULL owner — see `updateExperiment` for
|
|
217
|
+
// the migration semantics). Mutations on those rows fall back to the
|
|
218
|
+
// standard authentication gate but cannot enforce per-owner scoping
|
|
219
|
+
// until they're re-saved.
|
|
220
|
+
try {
|
|
221
|
+
await client.execute(`ALTER TABLE agent_experiments ADD COLUMN owner_email TEXT`);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// Column already exists — expected after first run.
|
|
225
|
+
}
|
|
226
|
+
await retryOnDdlRace(() => client.execute(experimentAssignmentsCreateSql));
|
|
227
|
+
await retryOnDdlRace(() => client.execute(experimentResultsCreateSql));
|
|
185
228
|
// Idempotent column upgrades for DBs created before per-user
|
|
186
229
|
// isolation. SQLite has no `ADD COLUMN IF NOT EXISTS`; Postgres
|
|
187
230
|
// surfaces "column ... already exists". `isDuplicateColumnError`
|