From 813334ea58fc33e7f71a693705a178770d72bdae Mon Sep 17 00:00:00 2001 From: socallmebertille Date: Sun, 3 May 2026 18:42:22 +0200 Subject: [PATCH 01/11] first nao mcp version --- apps/backend/package.json | 1 + apps/backend/src/agents/tools/index.ts | 22 +- apps/backend/src/agents/tools/story.ts | 2 +- apps/backend/src/app.ts | 33 +- apps/backend/src/auth.ts | 13 + apps/backend/src/db/abstractSchema.ts | 3 + apps/backend/src/db/pg-schema.ts | 115 +++- apps/backend/src/db/sqlite-schema.ts | 123 +++- apps/backend/src/mcp/auth.ts | 24 + apps/backend/src/mcp/logging.ts | 68 +++ apps/backend/src/mcp/routes.ts | 93 +++ apps/backend/src/mcp/server.ts | 58 ++ apps/backend/src/mcp/tools/agent.ts | 121 ++++ apps/backend/src/mcp/tools/data.ts | 111 ++++ apps/backend/src/mcp/tools/files.ts | 87 +++ apps/backend/src/mcp/tools/stories.ts | 316 +++++++++++ .../src/queries/mcp-endpoint.queries.ts | 52 ++ .../src/queries/shared-story.queries.ts | 2 +- apps/backend/src/queries/story.queries.ts | 528 ++++++++++++------ apps/backend/src/routes/auth.ts | 12 +- apps/backend/src/services/agent.ts | 88 ++- apps/backend/src/services/live-story.ts | 4 +- apps/backend/src/trpc/chat-fork.routes.ts | 50 +- apps/backend/src/trpc/mcp-endpoint.routes.ts | 36 ++ apps/backend/src/trpc/router.ts | 2 + apps/backend/src/trpc/shared-story.routes.ts | 13 +- apps/backend/src/trpc/story.routes.ts | 66 ++- apps/backend/src/types/mcp-endpoint.ts | 13 + apps/backend/tsconfig.json | 1 - apps/frontend/src/components/auth-form.tsx | 6 +- .../src/components/settings-search-index.ts | 33 ++ .../src/components/settings/mcp-endpoint.tsx | 448 +++++++++++++++ .../src/components/sidebar-settings-nav.tsx | 4 + .../src/components/stories-groups.tsx | 96 +++- .../src/components/story-download.tsx | 32 +- .../src/components/tool-calls/mcp.tsx | 78 ++- apps/frontend/src/lib/auth-client.ts | 8 +- apps/frontend/src/lib/stories-page.ts | 54 +- apps/frontend/src/routeTree.gen.ts | 68 +++ .../_sidebar-layout.settings.mcp-endpoint.tsx | 21 + ...r-layout.settings.project.mcp-endpoint.tsx | 15 + .../routes/_sidebar-layout.stories.index.tsx | 45 +- ...sidebar-layout.stories.shared.$shareId.tsx | 26 +- ...bar-layout.stories.standalone.$storyId.tsx | 109 ++++ apps/frontend/src/routes/login.tsx | 19 +- apps/frontend/vite.config.ts | 6 + package-lock.json | 1 + 47 files changed, 2868 insertions(+), 258 deletions(-) create mode 100644 apps/backend/src/mcp/auth.ts create mode 100644 apps/backend/src/mcp/logging.ts create mode 100644 apps/backend/src/mcp/routes.ts create mode 100644 apps/backend/src/mcp/server.ts create mode 100644 apps/backend/src/mcp/tools/agent.ts create mode 100644 apps/backend/src/mcp/tools/data.ts create mode 100644 apps/backend/src/mcp/tools/files.ts create mode 100644 apps/backend/src/mcp/tools/stories.ts create mode 100644 apps/backend/src/queries/mcp-endpoint.queries.ts create mode 100644 apps/backend/src/trpc/mcp-endpoint.routes.ts create mode 100644 apps/backend/src/types/mcp-endpoint.ts create mode 100644 apps/frontend/src/components/settings/mcp-endpoint.tsx create mode 100644 apps/frontend/src/routes/_sidebar-layout.settings.mcp-endpoint.tsx create mode 100644 apps/frontend/src/routes/_sidebar-layout.settings.project.mcp-endpoint.tsx create mode 100644 apps/frontend/src/routes/_sidebar-layout.stories.standalone.$storyId.tsx diff --git a/apps/backend/package.json b/apps/backend/package.json index 44d4800f0..f8e141bfe 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -60,6 +60,7 @@ "@fastify/multipart": "^10.0.0", "@fastify/static": "^8.1.0", "@microsoft/microsoft-graph-client": "^3.0.7", + "@modelcontextprotocol/sdk": "^1.29.0", "@nao/shared": "*", "@openrouter/ai-sdk-provider": "^2.2.3", "@pydantic/monty": "^0.0.7", diff --git a/apps/backend/src/agents/tools/index.ts b/apps/backend/src/agents/tools/index.ts index 138914d64..e9f6a584d 100644 --- a/apps/backend/src/agents/tools/index.ts +++ b/apps/backend/src/agents/tools/index.ts @@ -29,16 +29,24 @@ export const tools = { suggest_follow_ups: suggestFollowUps, }; -export const getTools = (agentSettings: AgentSettings | null, extraTools?: Record) => { +export const getTools = ( + agentSettings: AgentSettings | null, + extraTools?: Record, + excludeTools?: readonly string[], +) => { const mcpTools = mcpService.getMcpTools(); - const { execute_python, execute_sandboxed_code, ...baseTools } = tools; + const include = (key: string) => !excludeTools?.includes(key); return { - ...baseTools, - ...mcpTools, - ...(agentSettings?.experimental?.pythonSandboxing && execute_python && { execute_python }), - ...(agentSettings?.experimental?.sandboxes && execute_sandboxed_code && { execute_sandboxed_code }), - ...extraTools, + ...Object.fromEntries(Object.entries(baseTools).filter(([k]) => include(k))), + ...Object.fromEntries(Object.entries(mcpTools).filter(([k]) => include(k))), + ...(agentSettings?.experimental?.pythonSandboxing && + execute_python && + include('execute_python') && { execute_python }), + ...(agentSettings?.experimental?.sandboxes && + execute_sandboxed_code && + include('execute_sandboxed_code') && { execute_sandboxed_code }), + ...Object.fromEntries(Object.entries(extraTools ?? {}).filter(([k]) => include(k))), }; }; diff --git a/apps/backend/src/agents/tools/story.ts b/apps/backend/src/agents/tools/story.ts index e7b9246b7..cdb95887d 100644 --- a/apps/backend/src/agents/tools/story.ts +++ b/apps/backend/src/agents/tools/story.ts @@ -61,7 +61,7 @@ export default createTool({ }; } - const existing = await storyQueries.getLatestVersion(chatId, input.id); + const existing = await storyQueries.getLatestVersionByChatAndSlug(chatId, input.id); if (!existing) { return fail(`Story "${input.id}" does not exist. Use "create" first.`); } diff --git a/apps/backend/src/app.ts b/apps/backend/src/app.ts index a55e579a1..11f9d86d8 100644 --- a/apps/backend/src/app.ts +++ b/apps/backend/src/app.ts @@ -11,6 +11,7 @@ import { fileURLToPath } from 'url'; import { env, isCloud } from './env'; import { LOG_CLEANUP_JOB_NAME, logCleanupHandler, runLogCleanup } from './handlers/log-cleanup.handler'; +import { mcpServerRoutes } from './mcp/routes'; import { ensureOrganizationSetup } from './queries/organization.queries'; import { agentRoutes } from './routes/agent'; import { authRoutes } from './routes/auth'; @@ -175,6 +176,33 @@ app.register(githubRoutes, { prefix: '/api/github', }); +app.register(mcpServerRoutes, { + prefix: '/mcp', +}); + +async function proxyToBetterAuth(url: string, request: { headers: Record }) { + const auth = await (await import('./auth')).getAuth(); + const headers = (await import('./utils/utils')).convertHeaders(request.headers); + const req = new Request(url, { method: 'GET', headers }); + return auth.handler(req); +} + +app.get('/.well-known/oauth-protected-resource', async (request, reply) => { + const url = new URL('/api/auth/.well-known/oauth-protected-resource', `http://${request.headers.host}`); + const response = await proxyToBetterAuth(url.toString(), request); + reply.status(response.status); + response.headers.forEach((value, key) => reply.header(key, value)); + reply.send(await response.text()); +}); + +app.get('/.well-known/oauth-authorization-server', async (request, reply) => { + const url = new URL('/api/auth/.well-known/oauth-authorization-server', `http://${request.headers.host}`); + const response = await proxyToBetterAuth(url.toString(), request); + reply.status(response.status); + response.headers.forEach((value, key) => reply.header(key, value)); + reply.send(await response.text()); +}); + /** * Tests the API connection */ @@ -201,7 +229,10 @@ const isReservedBackendPath = (url: string) => { pathname === '/c' || pathname.startsWith('/c/') || pathname === '/i' || - pathname.startsWith('/i/') + pathname.startsWith('/i/') || + pathname === '/mcp' || + pathname.startsWith('/mcp/') || + pathname.startsWith('/.well-known/') ); }; diff --git a/apps/backend/src/auth.ts b/apps/backend/src/auth.ts index 083d6482f..92f3e0fcd 100644 --- a/apps/backend/src/auth.ts +++ b/apps/backend/src/auth.ts @@ -1,5 +1,7 @@ import { APIError, betterAuth } from 'better-auth'; import { drizzleAdapter } from 'better-auth/adapters/drizzle'; +import { mcp as mcpPlugin } from 'better-auth/plugins'; +import { bearer } from 'better-auth/plugins/bearer'; import { db } from './db/db'; import dbConfig, { Dialect } from './db/dbConfig'; @@ -71,6 +73,17 @@ async function createAuthInstance(googleConfig: GoogleConfig) { provider: dbConfig.dialect === Dialect.Postgres ? 'pg' : 'sqlite', schema: dbConfig.schema, }), + plugins: [ + bearer(), + mcpPlugin({ + loginPage: '/login', + oidcConfig: { + loginPage: '/login', + accessTokenExpiresIn: 86400, + refreshTokenExpiresIn: 604800, + }, + }), + ], trustedOrigins: env.BETTER_AUTH_URL ? [env.BETTER_AUTH_URL] : undefined, emailAndPassword: { enabled: true, diff --git a/apps/backend/src/db/abstractSchema.ts b/apps/backend/src/db/abstractSchema.ts index 9dca4e313..4125daa0b 100644 --- a/apps/backend/src/db/abstractSchema.ts +++ b/apps/backend/src/db/abstractSchema.ts @@ -82,6 +82,9 @@ export type NewLlmInference = typeof sqliteSchema.llmInference.$inferInsert; export type DBLog = typeof sqliteSchema.log.$inferSelect; export type NewLog = typeof sqliteSchema.log.$inferInsert; +export type DBMcpCallLog = typeof sqliteSchema.mcpCallLog.$inferSelect; +export type NewMcpCallLog = typeof sqliteSchema.mcpCallLog.$inferInsert; + export type DBMessageImage = typeof sqliteSchema.messageImage.$inferSelect; export type NewMessageImage = typeof sqliteSchema.messageImage.$inferInsert; diff --git a/apps/backend/src/db/pg-schema.ts b/apps/backend/src/db/pg-schema.ts index 503dc925a..f8632b87e 100644 --- a/apps/backend/src/db/pg-schema.ts +++ b/apps/backend/src/db/pg-schema.ts @@ -19,6 +19,7 @@ import { AgentSettings } from '../types/agent-settings'; import { ForkMetadata, StopReason, ToolState, UIMessagePartType } from '../types/chat'; import { LLM_INFERENCE_TYPES } from '../types/llm'; import { LOG_LEVELS, LOG_SOURCES } from '../types/log'; +import { McpEndpointSettings } from '../types/mcp-endpoint'; import { MEMORY_CATEGORIES } from '../types/memory'; import { SlackSettings, TeamsSettings, TelegramSettings, WhatsappSettings } from '../types/messaging-provider'; import { ORG_ROLES } from '../types/organization'; @@ -152,6 +153,7 @@ export const project = pgTable( teamsSettings: jsonb('teams_settings').$type(), telegramSettings: jsonb('telegram_settings').$type(), whatsappSettings: jsonb('whatsapp_settings').$type(), + mcpEndpointSettings: jsonb('mcp_endpoint_settings').$type(), createdAt: timestamp('created_at').defaultNow().notNull(), updatedAt: timestamp('updated_at') @@ -492,9 +494,9 @@ export const story = pgTable( id: text('id') .$defaultFn(() => crypto.randomUUID()) .primaryKey(), - chatId: text('chat_id') - .notNull() - .references(() => chat.id, { onDelete: 'cascade' }), + chatId: text('chat_id').references(() => chat.id, { onDelete: 'cascade' }), + projectId: text('project_id').references(() => project.id, { onDelete: 'cascade' }), + userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }), slug: text('slug').notNull(), title: text('title').notNull(), isLive: boolean('is_live').default(false).notNull(), @@ -508,7 +510,11 @@ export const story = pgTable( .$onUpdate(() => new Date()) .notNull(), }, - (t) => [unique('story_chat_slug_unique').on(t.chatId, t.slug), index('story_chatId_idx').on(t.chatId)], + (t) => [ + unique('story_chat_slug_unique').on(t.chatId, t.slug), + index('story_chatId_idx').on(t.chatId), + index('story_userId_idx').on(t.userId), + ], ); export const storyVersion = pgTable( @@ -690,3 +696,104 @@ export const scheduledJob = pgTable( }, (t) => [index('scheduled_job_status_runAt_idx').on(t.status, t.runAt), index('scheduled_job_name_idx').on(t.name)], ); + +export const mcpCallLog = pgTable( + 'mcp_call_log', + { + id: text('id') + .$defaultFn(() => crypto.randomUUID()) + .primaryKey(), + projectId: text('project_id') + .notNull() + .references(() => project.id, { onDelete: 'cascade' }), + userId: text('user_id') + .notNull() + .references(() => user.id, { onDelete: 'cascade' }), + toolName: text('tool_name').notNull(), + durationMs: integer('duration_ms'), + success: boolean('success').notNull(), + toolInput: jsonb('tool_input').$type(), + toolOutput: jsonb('tool_output').$type(), + calledAt: timestamp('called_at').defaultNow().notNull(), + }, + (t) => [ + index('mcp_call_log_projectId_idx').on(t.projectId), + index('mcp_call_log_userId_idx').on(t.userId), + index('mcp_call_log_calledAt_idx').on(t.calledAt), + ], +); + +export const oauthApplication = pgTable( + 'oauth_application', + { + id: text('id') + .$defaultFn(() => crypto.randomUUID()) + .primaryKey(), + name: text('name'), + icon: text('icon'), + metadata: text('metadata'), + clientId: text('client_id').notNull().unique(), + clientSecret: text('client_secret'), + redirectUrls: text('redirect_urls').notNull(), + type: text('type').notNull(), + authenticationScheme: text('authentication_scheme'), + disabled: boolean('disabled').default(false), + userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }), + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at') + .defaultNow() + .$onUpdate(() => new Date()) + .notNull(), + }, + (t) => [index('oauth_application_userId_idx').on(t.userId)], +); + +export const oauthAccessToken = pgTable( + 'oauth_access_token', + { + id: text('id') + .$defaultFn(() => crypto.randomUUID()) + .primaryKey(), + accessToken: text('access_token').notNull().unique(), + refreshToken: text('refresh_token').notNull().unique(), + accessTokenExpiresAt: timestamp('access_token_expires_at').notNull(), + refreshTokenExpiresAt: timestamp('refresh_token_expires_at').notNull(), + clientId: text('client_id') + .notNull() + .references(() => oauthApplication.clientId, { onDelete: 'cascade' }), + userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }), + scopes: text('scopes').notNull(), + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at') + .defaultNow() + .$onUpdate(() => new Date()) + .notNull(), + }, + (t) => [ + index('oauth_access_token_clientId_idx').on(t.clientId), + index('oauth_access_token_userId_idx').on(t.userId), + ], +); + +export const oauthConsent = pgTable( + 'oauth_consent', + { + id: text('id') + .$defaultFn(() => crypto.randomUUID()) + .primaryKey(), + clientId: text('client_id') + .notNull() + .references(() => oauthApplication.clientId, { onDelete: 'cascade' }), + userId: text('user_id') + .notNull() + .references(() => user.id, { onDelete: 'cascade' }), + scopes: text('scopes').notNull(), + consentGiven: boolean('consent_given').notNull(), + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at') + .defaultNow() + .$onUpdate(() => new Date()) + .notNull(), + }, + (t) => [index('oauth_consent_clientId_idx').on(t.clientId), index('oauth_consent_userId_idx').on(t.userId)], +); diff --git a/apps/backend/src/db/sqlite-schema.ts b/apps/backend/src/db/sqlite-schema.ts index 86a15a2fa..18d546dd6 100644 --- a/apps/backend/src/db/sqlite-schema.ts +++ b/apps/backend/src/db/sqlite-schema.ts @@ -8,6 +8,7 @@ import { AgentSettings } from '../types/agent-settings'; import { ForkMetadata, StopReason, ToolState, UIMessagePartType } from '../types/chat'; import { LLM_INFERENCE_TYPES } from '../types/llm'; import { LOG_LEVELS, LOG_SOURCES } from '../types/log'; +import { McpEndpointSettings } from '../types/mcp-endpoint'; import { MEMORY_CATEGORIES } from '../types/memory'; import { SlackSettings, TeamsSettings, TelegramSettings, WhatsappSettings } from '../types/messaging-provider'; import { ORG_ROLES } from '../types/organization'; @@ -157,6 +158,7 @@ export const project = sqliteTable( teamsSettings: text('teams_settings', { mode: 'json' }).$type(), telegramSettings: text('telegram_settings', { mode: 'json' }).$type(), whatsappSettings: text('whatsapp_settings', { mode: 'json' }).$type(), + mcpEndpointSettings: text('mcp_endpoint_settings', { mode: 'json' }).$type(), createdAt: integer('created_at', { mode: 'timestamp_ms' }) .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) @@ -518,9 +520,9 @@ export const story = sqliteTable( id: text('id') .$defaultFn(() => crypto.randomUUID()) .primaryKey(), - chatId: text('chat_id') - .notNull() - .references(() => chat.id, { onDelete: 'cascade' }), + chatId: text('chat_id').references(() => chat.id, { onDelete: 'cascade' }), + projectId: text('project_id').references(() => project.id, { onDelete: 'cascade' }), + userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }), slug: text('slug').notNull(), title: text('title').notNull(), isLive: integer('is_live', { mode: 'boolean' }).default(false).notNull(), @@ -536,7 +538,11 @@ export const story = sqliteTable( .$onUpdate(() => new Date()) .notNull(), }, - (t) => [unique('story_chat_slug_unique').on(t.chatId, t.slug), index('story_chatId_idx').on(t.chatId)], + (t) => [ + unique('story_chat_slug_unique').on(t.chatId, t.slug), + index('story_chatId_idx').on(t.chatId), + index('story_userId_idx').on(t.userId), + ], ); export const storyVersion = sqliteTable( @@ -738,3 +744,112 @@ export const scheduledJob = sqliteTable( }, (t) => [index('scheduled_job_status_runAt_idx').on(t.status, t.runAt), index('scheduled_job_name_idx').on(t.name)], ); + +export const mcpCallLog = sqliteTable( + 'mcp_call_log', + { + id: text('id') + .$defaultFn(() => crypto.randomUUID()) + .primaryKey(), + projectId: text('project_id') + .notNull() + .references(() => project.id, { onDelete: 'cascade' }), + userId: text('user_id') + .notNull() + .references(() => user.id, { onDelete: 'cascade' }), + toolName: text('tool_name').notNull(), + durationMs: integer('duration_ms'), + success: integer('success', { mode: 'boolean' }).notNull(), + toolInput: text('tool_input', { mode: 'json' }).$type(), + toolOutput: text('tool_output', { mode: 'json' }).$type(), + calledAt: integer('called_at', { mode: 'timestamp_ms' }) + .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) + .notNull(), + }, + (t) => [ + index('mcp_call_log_projectId_idx').on(t.projectId), + index('mcp_call_log_userId_idx').on(t.userId), + index('mcp_call_log_calledAt_idx').on(t.calledAt), + ], +); + +export const oauthApplication = sqliteTable( + 'oauth_application', + { + id: text('id') + .$defaultFn(() => crypto.randomUUID()) + .primaryKey(), + name: text('name'), + icon: text('icon'), + metadata: text('metadata'), + clientId: text('client_id').notNull().unique(), + clientSecret: text('client_secret'), + redirectUrls: text('redirect_urls').notNull(), + type: text('type').notNull(), + authenticationScheme: text('authentication_scheme'), + disabled: integer('disabled', { mode: 'boolean' }).default(false), + userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }), + createdAt: integer('created_at', { mode: 'timestamp_ms' }) + .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) + .notNull(), + updatedAt: integer('updated_at', { mode: 'timestamp_ms' }) + .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) + .$onUpdate(() => new Date()) + .notNull(), + }, + (t) => [index('oauth_application_userId_idx').on(t.userId)], +); + +export const oauthAccessToken = sqliteTable( + 'oauth_access_token', + { + id: text('id') + .$defaultFn(() => crypto.randomUUID()) + .primaryKey(), + accessToken: text('access_token').notNull().unique(), + refreshToken: text('refresh_token').notNull().unique(), + accessTokenExpiresAt: integer('access_token_expires_at', { mode: 'timestamp_ms' }).notNull(), + refreshTokenExpiresAt: integer('refresh_token_expires_at', { mode: 'timestamp_ms' }).notNull(), + clientId: text('client_id') + .notNull() + .references(() => oauthApplication.clientId, { onDelete: 'cascade' }), + userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }), + scopes: text('scopes').notNull(), + createdAt: integer('created_at', { mode: 'timestamp_ms' }) + .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) + .notNull(), + updatedAt: integer('updated_at', { mode: 'timestamp_ms' }) + .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) + .$onUpdate(() => new Date()) + .notNull(), + }, + (t) => [ + index('oauth_access_token_clientId_idx').on(t.clientId), + index('oauth_access_token_userId_idx').on(t.userId), + ], +); + +export const oauthConsent = sqliteTable( + 'oauth_consent', + { + id: text('id') + .$defaultFn(() => crypto.randomUUID()) + .primaryKey(), + clientId: text('client_id') + .notNull() + .references(() => oauthApplication.clientId, { onDelete: 'cascade' }), + userId: text('user_id') + .notNull() + .references(() => user.id, { onDelete: 'cascade' }), + scopes: text('scopes').notNull(), + consentGiven: integer('consent_given', { mode: 'boolean' }).notNull(), + createdAt: integer('created_at', { mode: 'timestamp_ms' }) + .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) + .notNull(), + updatedAt: integer('updated_at', { mode: 'timestamp_ms' }) + .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) + .$onUpdate(() => new Date()) + .notNull(), + }, + (t) => [index('oauth_consent_clientId_idx').on(t.clientId), index('oauth_consent_userId_idx').on(t.userId)], +); diff --git a/apps/backend/src/mcp/auth.ts b/apps/backend/src/mcp/auth.ts new file mode 100644 index 000000000..7f462626e --- /dev/null +++ b/apps/backend/src/mcp/auth.ts @@ -0,0 +1,24 @@ +import { getAuth } from '../auth'; +import { convertHeaders } from '../utils/utils'; + +export async function resolveUserId(fastifyRequest: { + headers: Record; + url: string; +}): Promise { + const auth = await getAuth(); + const headers = convertHeaders(fastifyRequest.headers); + + const session = await auth.api.getSession({ headers }); + if (session?.user) { + return session.user.id; + } + + const host = fastifyRequest.headers['host'] ?? 'localhost'; + const request = new Request(`http://${host}${fastifyRequest.url}`, { headers }); + const mcpSession = await auth.api.getMcpSession({ headers, request, asResponse: false }); + if (mcpSession?.userId) { + return mcpSession.userId; + } + + return null; +} diff --git a/apps/backend/src/mcp/logging.ts b/apps/backend/src/mcp/logging.ts new file mode 100644 index 000000000..02df728c4 --- /dev/null +++ b/apps/backend/src/mcp/logging.ts @@ -0,0 +1,68 @@ +import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; +import type { ServerNotification, ServerRequest } from '@modelcontextprotocol/sdk/types.js'; + +import { insertMcpCallLog } from '../queries/mcp-endpoint.queries'; +import type { McpEndpointSettings } from '../types/mcp-endpoint'; + +export interface McpContext { + userId: string; + projectId: string; + settings: McpEndpointSettings; +} + +export type ToolContent = { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }; + +export type ToolResult = { + content: ToolContent[]; + isError?: boolean; + toolOutput?: unknown; +}; + +export type ToolExtra = RequestHandlerExtra; +export type ToolHandler = (args: T, extra: ToolExtra) => Promise; + +export const TOOL_MODE_MAP: Record = { + ask_nao: 'agentModeEnabled', + execute_sql: 'toolsModeEnabled', + grep: 'toolsModeEnabled', + ls: 'toolsModeEnabled', + list_stories: 'objectsModeEnabled', + get_story: 'objectsModeEnabled', + create_story: 'objectsModeEnabled', + update_story: 'objectsModeEnabled', + archive_story: 'objectsModeEnabled', + delete_story: 'objectsModeEnabled', +}; + +export function withLogging(toolName: string, ctx: McpContext, handler: ToolHandler): ToolHandler { + return async (args: T, extra: ToolExtra) => { + const modeKey = TOOL_MODE_MAP[toolName]; + if (modeKey && !ctx.settings[modeKey]) { + return { + content: [{ type: 'text' as const, text: 'This MCP mode is disabled by your admin.' }], + isError: true, + }; + } + + const start = Date.now(); + let success = true; + let result: ToolResult | undefined; + try { + result = await handler(args, extra); + return result; + } catch (error) { + success = false; + throw error; + } finally { + insertMcpCallLog({ + projectId: ctx.projectId, + userId: ctx.userId, + toolName, + durationMs: Date.now() - start, + success, + toolInput: args as unknown, + toolOutput: result?.toolOutput, + }).catch(() => {}); + } + }; +} diff --git a/apps/backend/src/mcp/routes.ts b/apps/backend/src/mcp/routes.ts new file mode 100644 index 000000000..fa7d62594 --- /dev/null +++ b/apps/backend/src/mcp/routes.ts @@ -0,0 +1,93 @@ +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import type { FastifyReply, FastifyRequest } from 'fastify'; + +import type { App } from '../app'; +import { getMcpEndpointSettings } from '../queries/mcp-endpoint.queries'; +import { resolveUserId } from './auth'; +import { createMcpServer, resolveProjectId, sessions } from './server'; + +function replyUnauthorized(request: FastifyRequest, reply: FastifyReply) { + const origin = `${request.protocol}://${request.headers.host ?? request.hostname}`; + const wwwAuth = `Bearer resource_metadata="${origin}/.well-known/oauth-protected-resource"`; + return reply + .status(401) + .header('WWW-Authenticate', wwwAuth) + .header('Access-Control-Expose-Headers', 'WWW-Authenticate') + .send({ error: 'Unauthorized. Provide a valid Bearer token in the Authorization header.' }); +} + +export const mcpServerRoutes = async (app: App) => { + app.post('/', async (request, reply) => { + const userId = await resolveUserId(request); + if (!userId) { + return replyUnauthorized(request, reply); + } + + const existingSessionId = request.headers['mcp-session-id'] as string | undefined; + if (existingSessionId) { + const session = sessions.get(existingSessionId); + if (session) { + session.lastAccess = Date.now(); + await session.transport.handleRequest(request.raw, reply.raw, request.body as Record); + reply.hijack(); + return; + } + return reply.status(404).send({ error: 'Session not found or expired. Please reinitialize.' }); + } + + const projectId = await resolveProjectId(userId); + const settings = await getMcpEndpointSettings(projectId); + if (!settings.enabled) { + return reply.status(503).send({ error: 'MCP is disabled for this workspace.' }); + } + + const server = createMcpServer(userId, projectId, settings); + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => crypto.randomUUID(), + enableJsonResponse: true, + onsessioninitialized: (sessionId) => { + sessions.set(sessionId, { transport, server, userId, lastAccess: Date.now() }); + }, + onsessionclosed: (sessionId) => { + sessions.delete(sessionId); + server.close().catch(() => {}); + }, + }); + + await server.connect(transport); + await transport.handleRequest(request.raw, reply.raw, request.body as Record); + reply.hijack(); + }); + + app.get('/', async (request, reply) => { + const userId = await resolveUserId(request); + if (!userId) { + return replyUnauthorized(request, reply); + } + + const sessionId = request.headers['mcp-session-id'] as string | undefined; + if (!sessionId) { + return reply.status(400).send({ error: 'Missing Mcp-Session-Id header for SSE connection.' }); + } + + const session = sessions.get(sessionId); + if (!session) { + return reply.status(404).send({ error: 'Session not found or expired.' }); + } + + session.lastAccess = Date.now(); + await session.transport.handleRequest(request.raw, reply.raw); + reply.hijack(); + }); + + app.delete('/', async (request, reply) => { + const sessionId = request.headers['mcp-session-id'] as string | undefined; + const session = sessionId ? sessions.get(sessionId) : undefined; + if (!session) { + return reply.status(400).send({ error: 'Invalid or missing session.' }); + } + + await session.transport.handleRequest(request.raw, reply.raw); + reply.hijack(); + }); +}; diff --git a/apps/backend/src/mcp/server.ts b/apps/backend/src/mcp/server.ts new file mode 100644 index 000000000..7aef7813f --- /dev/null +++ b/apps/backend/src/mcp/server.ts @@ -0,0 +1,58 @@ +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; + +import { listUserProjects } from '../queries/project.queries'; +import type { McpEndpointSettings } from '../types/mcp-endpoint'; +import { registerAgentTools } from './tools/agent'; +import { registerDataTools } from './tools/data'; +import { registerFileTools } from './tools/files'; +import { registerStoryTools } from './tools/stories'; + +export interface McpSession { + transport: StreamableHTTPServerTransport; + server: McpServer; + userId: string; + lastAccess: number; +} + +export const sessions = new Map(); + +const SESSION_TTL_MS = 30 * 60 * 1000; + +setInterval( + () => { + const now = Date.now(); + for (const [id, session] of sessions) { + if (now - session.lastAccess > SESSION_TTL_MS) { + session.server.close().catch(() => {}); + sessions.delete(id); + } + } + }, + 5 * 60 * 1000, +).unref(); + +export async function resolveProjectId(userId: string): Promise { + const projects = await listUserProjects(userId); + if (projects.length === 0) { + throw new Error('No projects found for this user. Create or join a project first.'); + } + if (projects.length === 1) { + return projects[0].id; + } + + const listing = projects.map((p) => ` - ${p.name} (${p.id})`).join('\n'); + throw new Error(`MCP only supports single-project workspaces. Multiple projects found for this user:\n${listing}`); +} + +export function createMcpServer(userId: string, projectId: string, settings: McpEndpointSettings): McpServer { + const server = new McpServer({ name: 'nao', version: '0.1.0' }, { capabilities: { tools: {} } }); + const ctx = { userId, projectId, settings }; + + registerAgentTools(server, ctx); + registerDataTools(server, ctx); + registerFileTools(server, ctx); + registerStoryTools(server, ctx); + + return server; +} diff --git a/apps/backend/src/mcp/tools/agent.ts b/apps/backend/src/mcp/tools/agent.ts new file mode 100644 index 000000000..71446eb17 --- /dev/null +++ b/apps/backend/src/mcp/tools/agent.ts @@ -0,0 +1,121 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { z } from 'zod'; + +import * as chatQueries from '../../queries/chat.queries'; +import type { AgentProgressEvent } from '../../services/agent'; +import { agentService } from '../../services/agent'; +import { mcpService } from '../../services/mcp'; +import { skillService } from '../../services/skill'; +import type { UIMessage } from '../../types/chat'; +import { logger } from '../../utils/logger'; +import type { McpContext, ToolExtra } from '../logging'; +import { withLogging } from '../logging'; + +export function registerAgentTools(server: McpServer, ctx: McpContext): void { + server.registerTool( + 'ask_nao', + { + description: 'Ask nao an analytics question in natural language. Creates a chat that is visible in the UI.', + inputSchema: { + question: z.string().describe('The analytics question'), + conversation_id: z + .string() + .optional() + .describe( + 'Existing chat ID to continue a conversation. Omit to start a new chat. ' + + 'Reuse the ID returned by a previous ask_nao call from the same conversation.', + ), + }, + }, + withLogging('ask_nao', ctx, async ({ question, conversation_id }, extra) => { + try { + await mcpService.initializeMcpState(ctx.projectId); + await skillService.initializeSkills(ctx.projectId); + + const { chat, uiMessages } = await buildChatContext( + ctx.projectId, + ctx.userId, + question, + conversation_id, + ); + + const agent = await agentService.create(chat, undefined, ['story', 'suggest_follow_ups']); + + const progressToken = extra._meta?.progressToken; + const result = await agent.streamWithProgress(uiMessages, makeProgressHandler(extra, progressToken)); + + await chatQueries.upsertMessage({ + chatId: chat.id, + role: 'assistant', + parts: result.responseParts, + tokenUsage: result.usage, + }); + + return { + content: [ + { type: 'text' as const, text: result.text }, + { type: 'text' as const, text: `\n\n[conversation_id: ${chat.id}]` }, + ], + toolOutput: { chatId: chat.id, text: result.text }, + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`MCP ask_nao error: ${message}`, { + source: 'tool', + context: { question, userId: ctx.userId }, + }); + return { content: [{ type: 'text' as const, text: `Nao agent error: ${message}` }], isError: true }; + } + }), + ); +} + +async function buildChatContext( + projectId: string, + userId: string, + question: string, + conversationId: string | undefined, +): Promise<{ chat: { id: string; projectId: string; userId: string }; uiMessages: UIMessage[] }> { + const userMessage: UIMessage = { + id: crypto.randomUUID(), + role: 'user', + parts: [{ type: 'text', text: question }], + }; + + if (conversationId) { + const ownerId = await chatQueries.getChatOwnerId(conversationId); + if (ownerId === userId) { + const history = await chatQueries.getChatMessages(conversationId); + await chatQueries.upsertMessage({ ...userMessage, chatId: conversationId }); + return { + chat: { id: conversationId, projectId, userId }, + uiMessages: [...history, userMessage], + }; + } + } + + const chatId = crypto.randomUUID(); + await chatQueries.createChat({ id: chatId, projectId, userId, title: question.slice(0, 80) }, { text: question }); + return { + chat: { id: chatId, projectId, userId }, + uiMessages: [userMessage], + }; +} + +function makeProgressHandler(extra: ToolExtra, rawProgressToken: unknown) { + const progressToken = + typeof rawProgressToken === 'string' || typeof rawProgressToken === 'number' ? rawProgressToken : undefined; + + let chunkIndex = 0; + + return async (event: AgentProgressEvent) => { + if (progressToken === undefined || event.type !== 'tool-call') { + return; + } + + await extra.sendNotification({ + method: 'notifications/progress', + params: { progressToken, progress: ++chunkIndex, message: `[${event.toolName}]` }, + }); + }; +} diff --git a/apps/backend/src/mcp/tools/data.ts b/apps/backend/src/mcp/tools/data.ts new file mode 100644 index 000000000..a088095c2 --- /dev/null +++ b/apps/backend/src/mcp/tools/data.ts @@ -0,0 +1,111 @@ +import crypto from 'node:crypto'; + +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { z } from 'zod'; + +import { executeQuery } from '../../agents/tools/execute-sql'; +import { getEnvVars, retrieveProjectById } from '../../queries/project.queries'; +import { logger } from '../../utils/logger'; +import type { McpContext } from '../logging'; +import { withLogging } from '../logging'; + +export function registerDataTools(server: McpServer, ctx: McpContext): void { + server.registerTool( + 'execute_sql', + { + title: 'Execute SQL', + description: + 'Run a SQL query against the connected data warehouse. Returns rows as JSON. The response includes a `query_id` — pass it to `build_chart` or reference it in story `` blocks. Use ask_nao instead if you want Nao to write the SQL for you.', + inputSchema: { + sql: z.string().describe('The SQL query to execute'), + limit: z.number().optional().default(100).describe('Max rows to return (default 100, max 1000)'), + }, + }, + withLogging('execute_sql', ctx, async ({ sql, limit }) => { + try { + const project = await retrieveProjectById(ctx.projectId); + const envVars = await getEnvVars(ctx.projectId); + const cappedLimit = Math.min(limit, 1000); + + const result = await executeQuery( + { sql_query: sql }, + { + projectFolder: project.path!, + chatId: '', + agentSettings: null, + envVars, + queryResults: new Map(), + }, + ); + + const rows = result.data.slice(0, cappedLimit); + const queryId = `query_${crypto.randomUUID().slice(0, 8)}`; + const output = { query_id: queryId, columns: result.columns, row_count: rows.length, data: rows }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + toolOutput: output, + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`MCP execute_sql error: ${message}`, { + source: 'tool', + context: { sql, userId: ctx.userId }, + }); + return { content: [{ type: 'text' as const, text: `SQL execution error: ${message}` }], isError: true }; + } + }), + ); + + server.registerTool( + 'build_chart', + { + title: 'Build Chart', + description: + 'Generate a Nao-compatible `` block to embed in story content. Always use this tool instead of writing `` blocks manually — it ensures the correct syntax for the Nao UI renderer. Workflow: execute_sql → build_chart → create_story/update_story (pass the returned block in `content` and the SQL rows in `query_data`).', + inputSchema: { + query_id: z.string().describe('The query_id returned by execute_sql.'), + chart_type: z + .enum(['bar', 'stacked_bar', 'line', 'area', 'stacked_area', 'pie', 'kpi_card', 'scatter', 'radar']) + .describe('Type of chart to render.'), + x_axis_key: z.string().describe('Column name for the X-axis / category labels.'), + x_axis_type: z + .enum(['date', 'number', 'category']) + .nullable() + .describe( + 'Use "date" for YYYY-MM-DD values, "category" for labels/periods, "number" for numeric axes. Use null if unsure.', + ), + series: z + .array( + z.object({ + data_key: z.string().describe('Column name from SQL result to plot.'), + color: z.string().optional().describe('CSS color (defaults to theme colors).'), + label: z.string().optional().describe('Label to display in the legend.'), + }), + ) + .min(1) + .describe('Columns to plot. Each entry needs at least a data_key (column name from SQL result).'), + title: z + .string() + .describe('Concise descriptive chart title. Do not include the chart type in the title.'), + }, + }, + withLogging('build_chart', ctx, async ({ query_id, chart_type, x_axis_key, x_axis_type, series, title }) => { + const typedSeries = series as Array<{ data_key: string; color?: string; label?: string }>; + const block = buildChartBlock(query_id, chart_type, x_axis_key, x_axis_type, typedSeries, title); + return { content: [{ type: 'text' as const, text: block }], toolOutput: { block } }; + }), + ); +} + +function buildChartBlock( + queryId: string, + chartType: string, + xAxisKey: string, + xAxisType: string | null, + series: Array<{ data_key: string; color?: string; label?: string }>, + title: string, +): string { + const escapedTitle = title.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + const xAxisTypeAttr = xAxisType ? ` x_axis_type="${xAxisType}"` : ''; + return ``; +} diff --git a/apps/backend/src/mcp/tools/files.ts b/apps/backend/src/mcp/tools/files.ts new file mode 100644 index 000000000..beaea0279 --- /dev/null +++ b/apps/backend/src/mcp/tools/files.ts @@ -0,0 +1,87 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { z } from 'zod'; + +import grepTool from '../../agents/tools/grep'; +import listTool from '../../agents/tools/list'; +import { retrieveProjectById } from '../../queries/project.queries'; +import { logger } from '../../utils/logger'; +import type { McpContext } from '../logging'; +import { withLogging } from '../logging'; + +function makeExecutionOptions(projectFolder: string) { + const context = { projectFolder, chatId: '', agentSettings: null, envVars: {}, queryResults: new Map() }; + return { toolCallId: '', messages: [] as [], experimental_context: context }; +} + +export function registerFileTools(server: McpServer, ctx: McpContext): void { + server.registerTool( + 'grep', + { + title: 'Search Files', + description: + 'Search for text patterns in project files using regex. Respects .gitignore and .naoignore. Returns matching lines with file paths and line numbers.', + inputSchema: { + pattern: z.string().describe('The regex pattern to search for in file contents.'), + path: z.string().optional().describe('File or directory path to search in. Defaults to project root.'), + glob: z.string().optional().describe('Glob pattern to filter files (e.g. "*.ts", "*.{js,jsx}").'), + case_insensitive: z.boolean().optional().describe('Case insensitive search. Defaults to false.'), + context_lines: z.number().optional().describe('Lines of context to show around each match.'), + max_results: z.number().optional().describe('Maximum matches to return (default 100, max 500).'), + }, + }, + withLogging( + 'grep', + ctx, + async ({ pattern, path: searchPath, glob, case_insensitive, context_lines, max_results }) => { + try { + const project = await retrieveProjectById(ctx.projectId); + const result = await grepTool.execute!( + { + pattern, + path: searchPath, + glob, + case_insensitive, + context_lines, + max_results: Math.min(max_results ?? 100, 500), + }, + makeExecutionOptions(project.path!), + ); + return { content: [{ type: 'text' as const, text: JSON.stringify(result) }], toolOutput: result }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`MCP grep error: ${message}`, { + source: 'tool', + context: { pattern, userId: ctx.userId }, + }); + return { content: [{ type: 'text' as const, text: `Search error: ${message}` }], isError: true }; + } + }, + ), + ); + + server.registerTool( + 'ls', + { + title: 'List Files', + description: + 'List files and directories at the specified path within the project. Returns entry names, types, and sizes.', + inputSchema: { + path: z.string().optional().describe('Directory path to list. Defaults to project root ("/").'), + }, + }, + withLogging('ls', ctx, async ({ path: filePath }) => { + try { + const project = await retrieveProjectById(ctx.projectId); + const result = await listTool.execute!({ path: filePath ?? '/' }, makeExecutionOptions(project.path!)); + return { content: [{ type: 'text' as const, text: JSON.stringify(result) }], toolOutput: result }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`MCP ls error: ${message}`, { + source: 'tool', + context: { path: filePath, userId: ctx.userId }, + }); + return { content: [{ type: 'text' as const, text: `List error: ${message}` }], isError: true }; + } + }), + ); +} diff --git a/apps/backend/src/mcp/tools/stories.ts b/apps/backend/src/mcp/tools/stories.ts new file mode 100644 index 000000000..60b95f051 --- /dev/null +++ b/apps/backend/src/mcp/tools/stories.ts @@ -0,0 +1,316 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { z } from 'zod'; + +import type { UserStoryRow } from '../../queries/story.queries'; +import * as storyQueries from '../../queries/story.queries'; +import { logger } from '../../utils/logger'; +import type { McpContext } from '../logging'; +import { withLogging } from '../logging'; + +export function registerStoryTools(server: McpServer, ctx: McpContext): void { + server.registerTool( + 'list_stories', + { + title: 'List Stories', + description: 'List analytics stories (dashboards/reports) in the current project.', + inputSchema: { + limit: z.number().optional().default(20).describe('Max stories to return (default 20, max 100)'), + archived: z.boolean().optional().default(false).describe('Include archived stories'), + }, + }, + withLogging('list_stories', ctx, async ({ limit, archived }) => { + try { + const stories = await storyQueries.listAllUserStoriesInProject(ctx.userId, ctx.projectId, { + archived, + limit, + }); + const result = stories.map(({ id, title, createdAt, updatedAt, archivedAt }) => ({ + id, + title, + createdAt, + updatedAt, + archived: archivedAt !== null, + })); + return { content: [{ type: 'text' as const, text: JSON.stringify(result) }], toolOutput: result }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`MCP list_stories error: ${message}`, { source: 'tool', context: { userId: ctx.userId } }); + return { content: [{ type: 'text' as const, text: `Error: ${message}` }], isError: true }; + } + }), + ); + + server.registerTool( + 'get_story', + { + title: 'Get Story', + description: 'Retrieve a full story including its latest content/code.', + inputSchema: { + story_id: z.string().describe('The story ID to retrieve'), + }, + }, + withLogging('get_story', ctx, async ({ story_id }) => { + try { + const story = await resolveStory(story_id, ctx); + const version = await fetchLatestVersion(story); + + const output = { + id: story.id, + title: story.title, + slug: story.slug, + chatId: story.chatId, + projectId: story.projectId, + code: version?.code ?? null, + version: version?.version ?? null, + isLive: story.isLive, + archived: story.archivedAt !== null, + createdAt: story.createdAt, + updatedAt: story.updatedAt, + }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + toolOutput: output, + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`MCP get_story error: ${message}`, { + source: 'tool', + context: { story_id, userId: ctx.userId }, + }); + return { content: [{ type: 'text' as const, text: `Error: ${message}` }], isError: true }; + } + }), + ); + + server.registerTool( + 'create_story', + { + title: 'Create Story', + description: + 'Create a new analytics story. Stories are markdown documents with embedded chart/table components rendered by the Nao UI.\n\nWorkflow for stories with charts:\n1. execute_sql → get rows + query_id\n2. build_chart → get a `` block string\n3. create_story → embed the block in `content`; pass SQL rows in `query_data`\n\nSupported blocks:\n- Charts: use `build_chart` to generate the correct `` block — do NOT write these manually.\n- Tables: `
`\n- Grids: `...blocks...` (1–4 columns)\n\nOmit `content` to create an empty story.', + inputSchema: { + title: z.string().describe('Story title'), + content: z + .string() + .optional() + .describe( + 'Story content (Nao story markdown with ,
, blocks). Omit to create empty.', + ), + query_data: z + .record( + z.string(), + z.object({ columns: z.array(z.string()), data: z.array(z.record(z.string(), z.unknown())) }), + ) + .optional() + .describe( + 'Query results keyed by query_id (query_id → { columns, data }). Required for stories with or
blocks so the Nao UI can render data.', + ), + }, + }, + withLogging('create_story', ctx, async ({ title, content, query_data }) => { + try { + const slug = generateSlug(title); + const code = content ?? `# ${title}\n`; + + const version = await storyQueries.createStandaloneVersion({ + userId: ctx.userId, + projectId: ctx.projectId, + slug, + title, + code, + action: 'create', + source: 'user', + }); + + const story = await storyQueries.getStandaloneStoryByUserAndSlug(ctx.userId, ctx.projectId, slug); + if (query_data && story) { + await storyQueries.upsertStoryDataCacheByStoryId( + story.id, + query_data as Record, + ); + } + const output = { id: story!.id, title: version.title, createdAt: story!.createdAt }; + return { + content: [{ type: 'text' as const, text: JSON.stringify(output) }], + toolOutput: output, + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`MCP create_story error: ${message}`, { + source: 'tool', + context: { title, userId: ctx.userId }, + }); + return { content: [{ type: 'text' as const, text: `Error: ${message}` }], isError: true }; + } + }), + ); + + server.registerTool( + 'update_story', + { + title: 'Update Story', + description: + 'Update a story title and/or content. Omit fields to keep their current values.\n\nWhen adding or replacing charts, use `build_chart` first to generate the correct `` block, then pass it in `content`. Include the SQL rows for any new query_ids in `query_data`.', + inputSchema: { + story_id: z.string().describe('The story ID to update'), + title: z.string().optional().describe('New title (omit to keep current)'), + content: z.string().optional().describe('New full content (Nao story markdown). Omit to keep current.'), + query_data: z + .record( + z.string(), + z.object({ columns: z.array(z.string()), data: z.array(z.record(z.string(), z.unknown())) }), + ) + .optional() + .describe( + 'Query results keyed by query_id (query_id → { columns, data }). Required for any new or
blocks added in this update.', + ), + }, + }, + withLogging('update_story', ctx, async ({ story_id, title, content, query_data }) => { + try { + const story = await resolveStory(story_id, ctx); + const latestVersion = await fetchLatestVersion(story); + const newTitle = title ?? story.title; + const newCode = content ?? latestVersion?.code ?? `# ${newTitle}\n`; + const updated = await saveNewVersion(story, ctx, newTitle, newCode); + if (query_data && !story.chatId) { + const storyForCache = + (await storyQueries.getStandaloneStoryByUserAndSlug(ctx.userId, ctx.projectId, story.slug)) ?? + story; + const existingCache = await storyQueries.getStoryDataCacheByStoryId(storyForCache.id); + const mergedQueryData = { + ...((existingCache?.queryData as Record< + string, + { data: unknown[]; columns: string[] } + > | null) ?? {}), + ...query_data, + }; + await storyQueries.upsertStoryDataCacheByStoryId( + storyForCache.id, + mergedQueryData as Record, + ); + } + return { content: [{ type: 'text' as const, text: JSON.stringify(updated) }], toolOutput: updated }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`MCP update_story error: ${message}`, { + source: 'tool', + context: { story_id, userId: ctx.userId }, + }); + return { content: [{ type: 'text' as const, text: `Error: ${message}` }], isError: true }; + } + }), + ); + + server.registerTool( + 'archive_story', + { + title: 'Archive Story', + description: 'Soft-delete a story by archiving it. The story can be restored later.', + inputSchema: { + story_id: z.string().describe('The story ID to archive'), + }, + }, + withLogging('archive_story', ctx, async ({ story_id }) => { + try { + const story = await resolveStory(story_id, ctx); + await storyQueries.archiveByStoryId(story.id); + return { + content: [{ type: 'text' as const, text: JSON.stringify({ id: story.id, archived: true }) }], + toolOutput: { id: story.id, archived: true }, + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`MCP archive_story error: ${message}`, { + source: 'tool', + context: { story_id, userId: ctx.userId }, + }); + return { content: [{ type: 'text' as const, text: `Error: ${message}` }], isError: true }; + } + }), + ); + + server.registerTool( + 'delete_story', + { + title: 'Delete Story', + description: + 'Permanently delete a story and all its versions. This cannot be undone. Use archive_story if you want a recoverable soft-delete.', + inputSchema: { + story_id: z.string().describe('The story ID to permanently delete'), + }, + }, + withLogging('delete_story', ctx, async ({ story_id }) => { + try { + const story = await resolveStory(story_id, ctx); + await storyQueries.deleteStory(story.id); + return { + content: [{ type: 'text' as const, text: JSON.stringify({ id: story.id, deleted: true }) }], + toolOutput: { id: story.id, deleted: true }, + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`MCP delete_story error: ${message}`, { + source: 'tool', + context: { story_id, userId: ctx.userId }, + }); + return { content: [{ type: 'text' as const, text: `Error: ${message}` }], isError: true }; + } + }), + ); +} + +export function generateSlug(title: string): string { + return ( + title + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-|-$/g, '') || 'untitled' + ); +} + +async function resolveStory(storyId: string, ctx: McpContext): Promise { + const story = await storyQueries.getStoryByIdForUser(storyId, ctx.userId); + if (!story) { + throw new Error(`Story not found: ${storyId}`); + } + return story; +} + +async function fetchLatestVersion(story: UserStoryRow) { + return story.chatId + ? storyQueries.getLatestVersionByChatAndSlug(story.chatId, story.slug) + : storyQueries.getLatestVersionByStoryId(story.id); +} + +async function saveNewVersion( + story: UserStoryRow, + ctx: McpContext, + title: string, + code: string, +): Promise<{ id: string; title: string; updatedAt: Date }> { + if (story.chatId) { + await storyQueries.createVersion({ + chatId: story.chatId, + slug: story.slug, + title, + code, + action: 'update', + source: 'user', + }); + const updated = await storyQueries.getStoryByChatAndSlug(story.chatId, story.slug); + return { id: updated!.id, title: updated!.title, updatedAt: updated!.updatedAt }; + } + + await storyQueries.createStandaloneVersion({ + userId: ctx.userId, + projectId: ctx.projectId, + slug: story.slug, + title, + code, + action: 'update', + source: 'user', + }); + const updated = await storyQueries.getStandaloneStoryByUserAndSlug(ctx.userId, ctx.projectId, story.slug); + return { id: updated!.id, title: updated!.title, updatedAt: updated!.updatedAt }; +} diff --git a/apps/backend/src/queries/mcp-endpoint.queries.ts b/apps/backend/src/queries/mcp-endpoint.queries.ts new file mode 100644 index 000000000..14d73686b --- /dev/null +++ b/apps/backend/src/queries/mcp-endpoint.queries.ts @@ -0,0 +1,52 @@ +import { desc, eq } from 'drizzle-orm'; + +import type { NewMcpCallLog } from '../db/abstractSchema'; +import s from '../db/abstractSchema'; +import { db } from '../db/db'; +import { DEFAULT_MCP_ENDPOINT_SETTINGS, type McpEndpointSettings } from '../types/mcp-endpoint'; + +export async function getMcpEndpointSettings(projectId: string): Promise { + const [row] = await db + .select({ mcpEndpointSettings: s.project.mcpEndpointSettings }) + .from(s.project) + .where(eq(s.project.id, projectId)) + .limit(1) + .execute(); + + return row?.mcpEndpointSettings ?? DEFAULT_MCP_ENDPOINT_SETTINGS; +} + +export async function updateMcpEndpointSettings( + projectId: string, + settings: Partial, +): Promise { + const current = await getMcpEndpointSettings(projectId); + const merged = { ...current, ...settings }; + + await db.update(s.project).set({ mcpEndpointSettings: merged }).where(eq(s.project.id, projectId)).execute(); + + return merged; +} + +export async function insertMcpCallLog(entry: Omit): Promise { + await db.insert(s.mcpCallLog).values(entry).execute(); +} + +export async function getRecentMcpCallLogs(projectId: string, limit = 50) { + return db + .select({ + id: s.mcpCallLog.id, + userId: s.mcpCallLog.userId, + userName: s.user.name, + toolName: s.mcpCallLog.toolName, + durationMs: s.mcpCallLog.durationMs, + success: s.mcpCallLog.success, + calledAt: s.mcpCallLog.calledAt, + }) + .from(s.mcpCallLog) + .leftJoin(s.user, eq(s.mcpCallLog.userId, s.user.id)) + .where(eq(s.mcpCallLog.projectId, projectId)) + .orderBy(desc(s.mcpCallLog.calledAt)) + .limit(limit) + .execute(); +} diff --git a/apps/backend/src/queries/shared-story.queries.ts b/apps/backend/src/queries/shared-story.queries.ts index 55617aaa8..515f33be2 100644 --- a/apps/backend/src/queries/shared-story.queries.ts +++ b/apps/backend/src/queries/shared-story.queries.ts @@ -5,7 +5,7 @@ import { db } from '../db/db'; export type SharedStoryWithLatest = DBSharedStory & { authorName: string; - chatId: string; + chatId: string | null; slug: string; title: string; code: string; diff --git a/apps/backend/src/queries/story.queries.ts b/apps/backend/src/queries/story.queries.ts index 526679f31..39727c9e9 100644 --- a/apps/backend/src/queries/story.queries.ts +++ b/apps/backend/src/queries/story.queries.ts @@ -1,8 +1,25 @@ -import { and, asc, desc, eq, isNull, max, or, sql } from 'drizzle-orm'; +import { and, asc, desc, eq, isNull, max, or, type SQL, sql } from 'drizzle-orm'; import s, { type DBStory, type DBStoryDataCache, type DBStoryVersion } from '../db/abstractSchema'; import { db } from '../db/db'; +export type UserStoryRow = Pick< + DBStory, + | 'id' + | 'chatId' + | 'projectId' + | 'userId' + | 'slug' + | 'title' + | 'isLive' + | 'isLiveTextDynamic' + | 'cacheSchedule' + | 'cacheScheduleDescription' + | 'archivedAt' + | 'createdAt' + | 'updatedAt' +> & { code: string }; + export async function getStoryByChatAndSlug(chatId: string, slug: string): Promise { const [row] = await db .select() @@ -14,153 +31,96 @@ export async function getStoryByChatAndSlug(chatId: string, slug: string): Promi return row ?? null; } -export async function getOrCreateStory(data: { chatId: string; slug: string; title: string }): Promise { - const existing = await getStoryByChatAndSlug(data.chatId, data.slug); - if (existing) { - return existing; - } - - await db - .insert(s.story) - .values({ chatId: data.chatId, slug: data.slug, title: data.title }) - .onConflictDoNothing({ target: [s.story.chatId, s.story.slug] }) - .execute(); - - const row = await getStoryByChatAndSlug(data.chatId, data.slug); - if (!row) { - throw new Error(`Failed to create or retrieve story: ${data.chatId}/${data.slug}`); - } - return row; -} - -export async function createStoryVersion(data: { - chatId: string; - slug: string; - title: string; - code: string; - action: 'create' | 'update' | 'replace'; - source: 'assistant' | 'user'; -}): Promise { - const story = await getOrCreateStory({ - chatId: data.chatId, - slug: data.slug, - title: data.title, - }); - - if (story.title !== data.title) { - await db.update(s.story).set({ title: data.title }).where(eq(s.story.id, story.id)).execute(); - } - - const nextVersion = db - .select({ v: sql`coalesce(max(${s.storyVersion.version}), 0) + 1` }) - .from(s.storyVersion) - .where(eq(s.storyVersion.storyId, story.id)); - - const [created] = await db - .insert(s.storyVersion) - .values({ - storyId: story.id, - code: data.code, - action: data.action, - source: data.source, - version: sql`(${nextVersion})`, - }) - .returning() - .execute(); - - return { ...created, title: data.title }; -} +export async function getStoryByIdForUser(storyId: string, userId: string): Promise { + const latestVersions = latestVersionsSubquery(); -export async function getLatestVersion( - chatId: string, - slug: string, -): Promise< - | (DBStoryVersion & - Pick< - DBStory, - 'title' | 'isLive' | 'isLiveTextDynamic' | 'cacheSchedule' | 'cacheScheduleDescription' | 'archivedAt' - >) - | null -> { const [row] = await db .select({ - id: s.storyVersion.id, - storyId: s.storyVersion.storyId, - version: s.storyVersion.version, - code: s.storyVersion.code, - action: s.storyVersion.action, - source: s.storyVersion.source, - createdAt: s.storyVersion.createdAt, + id: s.story.id, + chatId: s.story.chatId, + projectId: s.story.projectId, + userId: s.story.userId, + slug: s.story.slug, title: s.story.title, isLive: s.story.isLive, isLiveTextDynamic: s.story.isLiveTextDynamic, cacheSchedule: s.story.cacheSchedule, cacheScheduleDescription: s.story.cacheScheduleDescription, archivedAt: s.story.archivedAt, + createdAt: s.story.createdAt, + updatedAt: s.story.updatedAt, + code: s.storyVersion.code, }) - .from(s.storyVersion) - .innerJoin(s.story, eq(s.storyVersion.storyId, s.story.id)) - .where(and(eq(s.story.chatId, chatId), eq(s.story.slug, slug))) - .orderBy(desc(s.storyVersion.version)) + .from(s.story) + .leftJoin(s.chat, eq(s.story.chatId, s.chat.id)) + .innerJoin(latestVersions, eq(s.story.id, latestVersions.storyId)) + .innerJoin( + s.storyVersion, + and(eq(s.storyVersion.storyId, s.story.id), eq(s.storyVersion.version, latestVersions.maxVersion)), + ) + .where( + and( + eq(s.story.id, storyId), + or(eq(s.chat.userId, userId), and(isNull(s.story.chatId), eq(s.story.userId, userId))), + ), + ) .limit(1) .execute(); return row ?? null; } -export async function getVersionByNumber( - chatId: string, +export async function getStandaloneStoryByUserAndSlug( + userId: string, + projectId: string, slug: string, - versionNumber: number, -): Promise< - | (DBStoryVersion & - Pick< - DBStory, - 'title' | 'isLive' | 'isLiveTextDynamic' | 'cacheSchedule' | 'cacheScheduleDescription' | 'archivedAt' - >) - | null -> { +): Promise { const [row] = await db - .select({ - id: s.storyVersion.id, - storyId: s.storyVersion.storyId, - version: s.storyVersion.version, - code: s.storyVersion.code, - action: s.storyVersion.action, - source: s.storyVersion.source, - createdAt: s.storyVersion.createdAt, - title: s.story.title, - isLive: s.story.isLive, - isLiveTextDynamic: s.story.isLiveTextDynamic, - cacheSchedule: s.story.cacheSchedule, - cacheScheduleDescription: s.story.cacheScheduleDescription, - archivedAt: s.story.archivedAt, - }) - .from(s.storyVersion) - .innerJoin(s.story, eq(s.storyVersion.storyId, s.story.id)) - .where(and(eq(s.story.chatId, chatId), eq(s.story.slug, slug), eq(s.storyVersion.version, versionNumber))) + .select() + .from(s.story) + .where( + and( + eq(s.story.projectId, projectId), + eq(s.story.userId, userId), + eq(s.story.slug, slug), + isNull(s.story.chatId), + ), + ) .limit(1) .execute(); return row ?? null; } -export async function listStoryVersions(chatId: string, slug: string): Promise { - return db - .select({ - id: s.storyVersion.id, - storyId: s.storyVersion.storyId, - version: s.storyVersion.version, - code: s.storyVersion.code, - action: s.storyVersion.action, - source: s.storyVersion.source, - createdAt: s.storyVersion.createdAt, - }) - .from(s.storyVersion) - .innerJoin(s.story, eq(s.storyVersion.storyId, s.story.id)) - .where(and(eq(s.story.chatId, chatId), eq(s.story.slug, slug))) - .orderBy(asc(s.storyVersion.version)) - .execute(); +export function listUserChatStories(userId: string, options?: { archived?: boolean }): Promise { + return queryStoriesWithLatestVersion(eq(s.chat.userId, userId), options); +} + +export function listUserStandaloneStories( + userId: string, + projectId: string, + options?: { archived?: boolean; limit?: number }, +): Promise { + const limit = Math.min(options?.limit ?? 50, 200); + return queryStoriesWithLatestVersion( + and(eq(s.story.projectId, projectId), eq(s.story.userId, userId), isNull(s.story.chatId))!, + { ...options, limit }, + ); +} + +export function listAllUserStoriesInProject( + userId: string, + projectId: string, + options?: { archived?: boolean; limit?: number }, +): Promise { + const limit = Math.min(options?.limit ?? 20, 100); + return queryStoriesWithLatestVersion( + or( + and(eq(s.chat.projectId, projectId), eq(s.chat.userId, userId)), + and(eq(s.story.projectId, projectId), eq(s.story.userId, userId), isNull(s.story.chatId)), + )!, + { ...options, limit }, + ); } export async function listStoriesInChat( @@ -185,39 +145,92 @@ export async function listStoriesInChat( })); } -export async function listUserStories( - userId: string, - options?: { archived?: boolean }, -): Promise<{ slug: string; chatId: string; title: string; code: string; createdAt: Date }[]> { - const latestVersions = db - .select({ - storyId: s.storyVersion.storyId, - maxVersion: max(s.storyVersion.version).as('max_version'), - }) +export async function createStoryVersion(data: { + chatId: string; + slug: string; + title: string; + code: string; + action: 'create' | 'update' | 'replace'; + source: 'assistant' | 'user'; +}): Promise { + const story = await getOrCreateStory({ chatId: data.chatId, slug: data.slug, title: data.title }); + + if (story.title !== data.title) { + await db.update(s.story).set({ title: data.title }).where(eq(s.story.id, story.id)).execute(); + } + + const nextVersion = db + .select({ v: sql`coalesce(max(${s.storyVersion.version}), 0) + 1` }) .from(s.storyVersion) - .groupBy(s.storyVersion.storyId) - .as('latest'); + .where(eq(s.storyVersion.storyId, story.id)); - const archivedFilter = options?.archived ? sql`${s.story.archivedAt} IS NOT NULL` : isNull(s.story.archivedAt); + const [created] = await db + .insert(s.storyVersion) + .values({ + storyId: story.id, + code: data.code, + action: data.action, + source: data.source, + version: sql`(${nextVersion})`, + }) + .returning() + .execute(); - return db - .select({ - slug: s.story.slug, - chatId: s.story.chatId, - title: s.story.title, - code: s.storyVersion.code, - createdAt: s.story.createdAt, + return { ...created, title: data.title }; +} + +export async function createStandaloneVersion(data: { + userId: string; + projectId: string; + slug: string; + title: string; + code: string; + action: 'create' | 'update' | 'replace'; + source: 'assistant' | 'user'; +}): Promise { + const existing = await getStandaloneStoryByUserAndSlug(data.userId, data.projectId, data.slug); + + let story: DBStory; + if (existing) { + story = existing; + if (story.title !== data.title) { + await db.update(s.story).set({ title: data.title }).where(eq(s.story.id, story.id)).execute(); + } + } else { + const [created] = await db + .insert(s.story) + .values({ projectId: data.projectId, userId: data.userId, slug: data.slug, title: data.title }) + .returning() + .execute(); + story = created; + } + + const nextVersion = db + .select({ v: sql`coalesce(max(${s.storyVersion.version}), 0) + 1` }) + .from(s.storyVersion) + .where(eq(s.storyVersion.storyId, story.id)); + + const [created] = await db + .insert(s.storyVersion) + .values({ + storyId: story.id, + code: data.code, + action: data.action, + source: data.source, + version: sql`(${nextVersion})`, }) - .from(s.story) - .innerJoin(s.chat, eq(s.story.chatId, s.chat.id)) - .innerJoin(latestVersions, eq(s.story.id, latestVersions.storyId)) - .innerJoin( - s.storyVersion, - and(eq(s.storyVersion.storyId, s.story.id), eq(s.storyVersion.version, latestVersions.maxVersion)), - ) - .where(and(eq(s.chat.userId, userId), archivedFilter)) - .orderBy(desc(s.story.createdAt)) + .returning() .execute(); + + return { ...created, title: data.title }; +} + +export async function deleteStory(storyId: string): Promise { + await db.delete(s.story).where(eq(s.story.id, storyId)).execute(); +} + +export async function assignChatToStory(storyId: string, chatId: string): Promise { + await db.update(s.story).set({ chatId }).where(eq(s.story.id, storyId)).execute(); } export async function archiveStory(chatId: string, slug: string): Promise { @@ -250,6 +263,14 @@ export async function unarchiveStory(chatId: string, slug: string): Promise { + await db.update(s.story).set({ archivedAt: new Date() }).where(eq(s.story.id, storyId)).execute(); +} + +export async function unarchiveByStoryId(storyId: string): Promise { + await db.update(s.story).set({ archivedAt: null }).where(eq(s.story.id, storyId)).execute(); +} + export async function updateStoryLiveSettings( chatId: string, slug: string, @@ -267,20 +288,67 @@ export async function updateStoryLiveSettings( .execute(); } -export async function getStoryDataCache(chatId: string, slug: string): Promise { - const [row] = await db +type StoryVersionWithStory = DBStoryVersion & + Pick< + DBStory, + 'title' | 'isLive' | 'isLiveTextDynamic' | 'cacheSchedule' | 'cacheScheduleDescription' | 'archivedAt' + >; + +export function getLatestVersionByChatAndSlug(chatId: string, slug: string): Promise { + return getStoryVersion(and(eq(s.story.chatId, chatId), eq(s.story.slug, slug))!, { latest: true }); +} + +export function getLatestVersionByStoryId(storyId: string): Promise { + return getStoryVersion(eq(s.story.id, storyId), { latest: true }); +} + +export function getVersionByNumber( + chatId: string, + slug: string, + versionNumber: number, +): Promise { + return getStoryVersion( + and(eq(s.story.chatId, chatId), eq(s.story.slug, slug), eq(s.storyVersion.version, versionNumber))!, + ); +} + +export async function listStoryVersions(chatId: string, slug: string): Promise { + return db .select({ - storyId: s.storyDataCache.storyId, - queryData: s.storyDataCache.queryData, - analysisResults: s.storyDataCache.analysisResults, - cachedAt: s.storyDataCache.cachedAt, + id: s.storyVersion.id, + storyId: s.storyVersion.storyId, + version: s.storyVersion.version, + code: s.storyVersion.code, + action: s.storyVersion.action, + source: s.storyVersion.source, + createdAt: s.storyVersion.createdAt, }) - .from(s.storyDataCache) - .innerJoin(s.story, eq(s.storyDataCache.storyId, s.story.id)) + .from(s.storyVersion) + .innerJoin(s.story, eq(s.storyVersion.storyId, s.story.id)) .where(and(eq(s.story.chatId, chatId), eq(s.story.slug, slug))) + .orderBy(asc(s.storyVersion.version)) .execute(); +} - return row ?? null; +export async function updateLatestVersionCode(chatId: string, slug: string, code: string): Promise { + const latest = await getLatestVersionByChatAndSlug(chatId, slug); + if (!latest) { + return; + } + + await db + .update(s.storyVersion) + .set({ code }) + .where(and(eq(s.storyVersion.storyId, latest.storyId), eq(s.storyVersion.version, latest.version))) + .execute(); +} + +export function getStoryDataCacheByChatAndSlug(chatId: string, slug: string): Promise { + return getStoryDataCache(and(eq(s.story.chatId, chatId), eq(s.story.slug, slug))!); +} + +export function getStoryDataCacheByStoryId(storyId: string): Promise { + return getStoryDataCache(eq(s.story.id, storyId)); } export async function upsertStoryDataCache( @@ -316,16 +384,17 @@ export async function upsertStoryDataCache( return row; } -export async function updateLatestVersionCode(chatId: string, slug: string, code: string): Promise { - const latest = await getLatestVersion(chatId, slug); - if (!latest) { - return; - } - +export async function upsertStoryDataCacheByStoryId( + storyId: string, + queryData: Record, +): Promise { await db - .update(s.storyVersion) - .set({ code }) - .where(and(eq(s.storyVersion.storyId, latest.storyId), eq(s.storyVersion.version, latest.version))) + .insert(s.storyDataCache) + .values({ storyId, queryData, cachedAt: new Date() }) + .onConflictDoUpdate({ + target: s.storyDataCache.storyId, + set: { queryData, cachedAt: new Date() }, + }) .execute(); } @@ -355,6 +424,64 @@ export async function getSqlQueryById( return result[queryId] ?? null; } +async function queryStoriesWithLatestVersion( + whereCondition: SQL, + options?: { archived?: boolean; limit?: number }, +): Promise { + const latestVersions = latestVersionsSubquery(); + + const query = db + .select({ + id: s.story.id, + chatId: s.story.chatId, + projectId: s.story.projectId, + userId: s.story.userId, + slug: s.story.slug, + title: s.story.title, + isLive: s.story.isLive, + isLiveTextDynamic: s.story.isLiveTextDynamic, + cacheSchedule: s.story.cacheSchedule, + cacheScheduleDescription: s.story.cacheScheduleDescription, + archivedAt: s.story.archivedAt, + createdAt: s.story.createdAt, + updatedAt: s.story.updatedAt, + code: s.storyVersion.code, + }) + .from(s.story) + .leftJoin(s.chat, eq(s.story.chatId, s.chat.id)) + .innerJoin(latestVersions, eq(s.story.id, latestVersions.storyId)) + .innerJoin( + s.storyVersion, + and(eq(s.storyVersion.storyId, s.story.id), eq(s.storyVersion.version, latestVersions.maxVersion)), + ) + .where(and(whereCondition, archivedStoryFilter(options?.archived))) + .orderBy(desc(s.story.createdAt)); + + if (options?.limit !== undefined) { + return query.limit(options.limit).execute(); + } + return query.execute(); +} + +async function getOrCreateStory(data: { chatId: string; slug: string; title: string }): Promise { + const existing = await getStoryByChatAndSlug(data.chatId, data.slug); + if (existing) { + return existing; + } + + await db + .insert(s.story) + .values({ chatId: data.chatId, slug: data.slug, title: data.title }) + .onConflictDoNothing({ target: [s.story.chatId, s.story.slug] }) + .execute(); + + const row = await getStoryByChatAndSlug(data.chatId, data.slug); + if (!row) { + throw new Error(`Failed to create or retrieve story: ${data.chatId}/${data.slug}`); + } + return row; +} + async function getSqlQueriesByIds( chatId: string, queryIds: Set, @@ -380,3 +507,64 @@ async function getSqlQueriesByIds( return queries; } + +async function getStoryVersion( + whereCondition: SQL, + options?: { latest?: boolean }, +): Promise { + const query = db + .select({ + id: s.storyVersion.id, + storyId: s.storyVersion.storyId, + version: s.storyVersion.version, + code: s.storyVersion.code, + action: s.storyVersion.action, + source: s.storyVersion.source, + createdAt: s.storyVersion.createdAt, + title: s.story.title, + isLive: s.story.isLive, + isLiveTextDynamic: s.story.isLiveTextDynamic, + cacheSchedule: s.story.cacheSchedule, + cacheScheduleDescription: s.story.cacheScheduleDescription, + archivedAt: s.story.archivedAt, + }) + .from(s.storyVersion) + .innerJoin(s.story, eq(s.storyVersion.storyId, s.story.id)) + .where(whereCondition); + + const ordered = options?.latest ? query.orderBy(desc(s.storyVersion.version)) : query; + const [row] = await ordered.limit(1).execute(); + + return row ?? null; +} + +async function getStoryDataCache(whereCondition: SQL): Promise { + const [row] = await db + .select({ + storyId: s.storyDataCache.storyId, + queryData: s.storyDataCache.queryData, + analysisResults: s.storyDataCache.analysisResults, + cachedAt: s.storyDataCache.cachedAt, + }) + .from(s.storyDataCache) + .innerJoin(s.story, eq(s.storyDataCache.storyId, s.story.id)) + .where(whereCondition) + .execute(); + + return row ?? null; +} + +function latestVersionsSubquery() { + return db + .select({ + storyId: s.storyVersion.storyId, + maxVersion: max(s.storyVersion.version).as('max_version'), + }) + .from(s.storyVersion) + .groupBy(s.storyVersion.storyId) + .as('latest'); +} + +function archivedStoryFilter(archived: boolean | undefined): SQL { + return archived ? sql`${s.story.archivedAt} IS NOT NULL` : isNull(s.story.archivedAt); +} diff --git a/apps/backend/src/routes/auth.ts b/apps/backend/src/routes/auth.ts index 7674a02cc..fb504d9df 100644 --- a/apps/backend/src/routes/auth.ts +++ b/apps/backend/src/routes/auth.ts @@ -2,6 +2,16 @@ import { App } from '../app'; import { getAuth } from '../auth'; import { convertHeaders } from '../utils/utils'; +function serializeBody(body: unknown, contentType: string | undefined): string | undefined { + if (!body) { + return undefined; + } + if (contentType?.includes('application/x-www-form-urlencoded') && typeof body === 'object') { + return new URLSearchParams(body as Record).toString(); + } + return JSON.stringify(body); +} + export const authRoutes = async (app: App) => { app.route({ method: ['GET', 'POST'], @@ -16,7 +26,7 @@ export const authRoutes = async (app: App) => { const req = new Request(url.toString(), { method: request.method, headers, - body: request.body ? JSON.stringify(request.body) : undefined, + body: serializeBody(request.body, request.headers['content-type']), }); // Process authentication request const auth = await getAuth(); diff --git a/apps/backend/src/services/agent.ts b/apps/backend/src/services/agent.ts index 674c48b5d..ce57fd131 100644 --- a/apps/backend/src/services/agent.ts +++ b/apps/backend/src/services/agent.ts @@ -38,6 +38,7 @@ import { TokenCost, TokenUsage, UIMessage, + UIMessagePart, } from '../types/chat'; import { Provider } from '../types/messaging-provider'; import { ToolContext } from '../types/tools'; @@ -59,6 +60,10 @@ import { memoryService } from './memory'; import { getAzureAccessTokenForUser } from './microsoft-auth.service'; import { skillService } from './skill'; +export type AgentProgressEvent = + | { type: 'tool-call'; toolName: string; toolCallId: string } + | { type: 'tool-result'; toolName: string; toolCallId: string }; + export interface AgentRunResult { text: string; usage: TokenUsage; @@ -73,6 +78,8 @@ export interface AgentRunResult { toolCalls: ReadonlyArray<{ toolName: string; toolCallId: string; input: unknown }>; toolResults: ReadonlyArray<{ toolCallId: string; output?: unknown }>; }>; + /** All message parts (step-starts, tool calls, text) for persisting to the DB */ + responseParts: UIMessagePart[]; } export type AgentChat = Pick & { @@ -87,7 +94,11 @@ export class AgentService { await assertBudgetNotExceeded(projectId, resolved.provider); } - async create(chat: AgentChat, modelSelection?: LlmSelectedModel): Promise { + async create( + chat: AgentChat, + modelSelection?: LlmSelectedModel, + excludeTools?: readonly string[], + ): Promise { this._disposeAgent(chat.id); const resolvedLlmSelectedModel = await this._getResolvedLlmSelectedModel(chat.projectId, modelSelection); await assertBudgetNotExceeded(chat.projectId, resolvedLlmSelectedModel.provider); @@ -95,7 +106,7 @@ export class AgentService { const agentSettings = await projectQueries.getAgentSettings(chat.projectId); const toolContext = await this._getToolContext(chat.projectId, chat.id, chat.userId, agentSettings); const webTools = await this._resolveWebTools(chat.projectId, resolvedLlmSelectedModel.provider, agentSettings); - const agentTools = getTools(agentSettings, webTools ?? undefined); + const agentTools = getTools(agentSettings, webTools ?? undefined, excludeTools); const agent = new AgentManager( chat, modelConfig, @@ -407,10 +418,16 @@ class AgentManager { } try { - const latestVersions = new Map>>(); + const latestVersions = new Map< + string, + Awaited> + >(); await Promise.all( [...lastToolCallByStory.keys()].map(async (storyId) => { - latestVersions.set(storyId, await storyQueries.getLatestVersion(this.chat.id, storyId)); + latestVersions.set( + storyId, + await storyQueries.getLatestVersionByChatAndSlug(this.chat.id, storyId), + ); }), ); @@ -550,6 +567,69 @@ class AgentManager { durationMs, responseMessages: result.response.messages, steps: result.steps as AgentRunResult['steps'], + responseParts: [], + }; + } finally { + this._onDispose(); + } + } + + async streamWithProgress( + uiMessages: UIMessage[], + onEvent: (event: AgentProgressEvent) => Promise, + ): Promise { + const startTime = performance.now(); + const messages = await this._buildModelMessages(uiMessages); + const responseParts: UIMessagePart[] = []; + const pendingToolCalls = new Map(); + + try { + const result = await this._agent.stream({ + messages, + abortSignal: this._abortController.signal, + }); + + for await (const part of result.fullStream) { + if (part.type === 'tool-call') { + pendingToolCalls.set(part.toolCallId, { toolName: part.toolName, args: part.input }); + await onEvent({ type: 'tool-call', toolName: part.toolName, toolCallId: part.toolCallId }); + } else if (part.type === 'tool-result') { + const pending = pendingToolCalls.get(part.toolCallId); + if (pending) { + responseParts.push({ + type: `tool-${pending.toolName}`, + toolName: pending.toolName, + toolCallId: part.toolCallId, + state: 'output-available', + input: pending.args, + output: part.output, + } as unknown as UIMessagePart); + pendingToolCalls.delete(part.toolCallId); + } + await onEvent({ type: 'tool-result', toolName: part.toolName, toolCallId: part.toolCallId }); + } + } + + const text = await result.text; + if (text) { + responseParts.push({ type: 'text', text }); + } + + const durationMs = Math.round(performance.now() - startTime); + const usage = convertToTokenUsage(await result.totalUsage); + const cost = convertToCost(usage, this._modelSelection.provider, this._modelSelection.modelId); + const finishReason = (await result.finishReason) ?? 'stop'; + const steps = await result.steps; + + return { + text, + usage, + cost, + finishReason, + durationMs, + responseMessages: [], + steps: steps as AgentRunResult['steps'], + responseParts, }; } finally { this._onDispose(); diff --git a/apps/backend/src/services/live-story.ts b/apps/backend/src/services/live-story.ts index c7b2c89e3..de6e74e07 100644 --- a/apps/backend/src/services/live-story.ts +++ b/apps/backend/src/services/live-story.ts @@ -42,7 +42,7 @@ export interface RefreshResult { } export async function refreshStoryData(chatId: string, slug: string): Promise { - const version = await storyQueries.getLatestVersion(chatId, slug); + const version = await storyQueries.getLatestVersionByChatAndSlug(chatId, slug); if (!version) { throw new Error('Story not found'); } @@ -100,7 +100,7 @@ export async function getStoryQueryData( return { queryData: await getQueryDataFromCode(chatId, code), cachedAt: null }; } - const cache = await storyQueries.getStoryDataCache(chatId, slug); + const cache = await storyQueries.getStoryDataCacheByChatAndSlug(chatId, slug); if (cache && !isCacheExpired(cache.cachedAt, cacheSchedule)) { return { queryData: cache.queryData, cachedAt: cache.cachedAt }; diff --git a/apps/backend/src/trpc/chat-fork.routes.ts b/apps/backend/src/trpc/chat-fork.routes.ts index bb1a269ca..a24c3a69f 100644 --- a/apps/backend/src/trpc/chat-fork.routes.ts +++ b/apps/backend/src/trpc/chat-fork.routes.ts @@ -8,7 +8,7 @@ import * as sharedStoryQueries from '../queries/shared-story.queries'; import * as storyQueries from '../queries/story.queries'; import { compactionService } from '../services/compaction'; import type { ForkMetadata, UIMessage, UIMessagePart } from '../types/chat'; -import { canSendProcedure, protectedProcedure } from './trpc'; +import { canSendProcedure, projectProtectedProcedure, protectedProcedure } from './trpc'; const shareTypeSchema = z.enum(['chat', 'story']); const selectionSchema = z.object({ start: z.number(), end: z.number(), text: z.string() }); @@ -35,6 +35,34 @@ export const chatForkRoutes = { return forkSharedStoryItem(input.shareId, input.selection, ctx.user.id); }), + openStandalone: projectProtectedProcedure + .input(z.object({ storyId: z.string() })) + .mutation(async ({ input, ctx }): Promise<{ chatId: string }> => { + const story = await storyQueries.getStoryByIdForUser(input.storyId, ctx.user.id); + if (!story) { + throw new TRPCError({ code: 'NOT_FOUND', message: 'Story not found.' }); + } + if (story.chatId) { + return { chatId: story.chatId }; + } + + const cache = await storyQueries.getStoryDataCacheByStoryId(story.id); + const seedMessages = cache?.queryData + ? buildQueryDataMessages(cache.queryData as Record) + : []; + + const chat = await chatQueries.createForkedChat( + { projectId: ctx.project.id, userId: ctx.user.id, title: story.title }, + seedMessages, + ); + + const latestVersion = await storyQueries.getLatestVersionByStoryId(story.id); + await storyQueries.assignChatToStory(story.id, chat.id); + await pinStoryMessageToChat(chat.id, story.slug, story.title, story.code, latestVersion?.version ?? 1); + + return { chatId: chat.id }; + }), + getSelectionForks: protectedProcedure .input(z.object({ shareId: z.string(), type: shareTypeSchema })) .query(async ({ input, ctx }) => { @@ -83,7 +111,7 @@ async function forkSharedStoryItem( : { type: 'story', id: share.storyId, title: share.title, authorName: share.authorName }; if (selection) { - const rawMessages = await chatQueries.getChatMessages(share.chatId); + const rawMessages = await chatQueries.getChatMessages(share.chatId!); const seededMessages = compactionService.useLastCompaction(rawMessages); const messages = [...seededMessages, buildSelectionContextMessage(share.title, selection)]; @@ -92,11 +120,11 @@ async function forkSharedStoryItem( messages, ); - await copyStoriesToFork(share.chatId, chat.id); + await copyStoriesToFork(share.chatId!, chat.id); return { chatId: chat.id }; } - const queryData = await sharedStoryQueries.getQueryDataFromCode(share.chatId, share.code); + const queryData = await sharedStoryQueries.getQueryDataFromCode(share.chatId!, share.code); const messages = buildQueryDataMessages(queryData); const chat = await chatQueries.createForkedChat({ projectId, userId, title: share.title, forkMetadata }, messages); @@ -205,6 +233,16 @@ async function createStoryInFork(chatId: string, slug: string, title: string, co source: 'assistant', }); + await pinStoryMessageToChat(chatId, slug, title, code, version.version); +} + +async function pinStoryMessageToChat( + chatId: string, + slug: string, + title: string, + code: string, + versionNumber: number, +): Promise { await chatQueries.upsertMessage({ chatId, role: 'assistant', @@ -215,7 +253,7 @@ async function createStoryInFork(chatId: string, slug: string, title: string, co toolName: 'story', state: 'output-available', input: { action: 'create', id: slug, title, code }, - output: { _version: '1', success: true, id: slug, version: version.version, code, title }, + output: { _version: '1', success: true, id: slug, version: versionNumber, code, title }, errorText: undefined, providerExecuted: false, } as UIMessagePart, @@ -231,7 +269,7 @@ async function copyStoriesToFork(sourceChatId: string, forkChatId: string): Prom await Promise.all( stories.map(async ({ slug }) => { - const latest = await storyQueries.getLatestVersion(sourceChatId, slug); + const latest = await storyQueries.getLatestVersionByChatAndSlug(sourceChatId, slug); if (!latest) { return; } diff --git a/apps/backend/src/trpc/mcp-endpoint.routes.ts b/apps/backend/src/trpc/mcp-endpoint.routes.ts new file mode 100644 index 000000000..e1791f7dd --- /dev/null +++ b/apps/backend/src/trpc/mcp-endpoint.routes.ts @@ -0,0 +1,36 @@ +import { TRPCError } from '@trpc/server'; +import { z } from 'zod/v4'; + +import * as mcpEndpointQueries from '../queries/mcp-endpoint.queries'; +import { adminProtectedProcedure, projectProtectedProcedure, protectedProcedure, router } from './trpc'; + +export const mcpEndpointRoutes = router({ + getSettings: projectProtectedProcedure.query(async ({ ctx }) => { + return mcpEndpointQueries.getMcpEndpointSettings(ctx.project.id); + }), + + updateSettings: adminProtectedProcedure + .input( + z.object({ + enabled: z.boolean().optional(), + agentModeEnabled: z.boolean().optional(), + toolsModeEnabled: z.boolean().optional(), + objectsModeEnabled: z.boolean().optional(), + }), + ) + .mutation(async ({ ctx, input }) => { + return mcpEndpointQueries.updateMcpEndpointSettings(ctx.project.id, input); + }), + + getCallLogs: adminProtectedProcedure.query(async ({ ctx }) => { + return mcpEndpointQueries.getRecentMcpCallLogs(ctx.project.id); + }), + + getBearerToken: protectedProcedure.query(({ ctx }) => { + const token = ctx.session?.session?.token; + if (!token) { + throw new TRPCError({ code: 'UNAUTHORIZED', message: 'No active session.' }); + } + return { token }; + }), +}); diff --git a/apps/backend/src/trpc/router.ts b/apps/backend/src/trpc/router.ts index a73fa7269..81d48f282 100644 --- a/apps/backend/src/trpc/router.ts +++ b/apps/backend/src/trpc/router.ts @@ -12,6 +12,7 @@ import { githubRoutes } from './github.routes'; import { licenseRoutes } from './license.routes'; import { logRoutes } from './log.routes'; import { mcpRoutes } from './mcp.routes'; +import { mcpEndpointRoutes } from './mcp-endpoint.routes'; import { memoryRoutes } from './memory.routes'; import { organizationRoutes } from './organization.routes'; import { posthogRoutes } from './posthog.routes'; @@ -50,6 +51,7 @@ export const trpcRouter = router({ account: accountRoutes, apiKey: apiKeyRoutes, mcp: mcpRoutes, + mcpEndpoint: mcpEndpointRoutes, system: systemRoutes, skill: skillRoutes, transcribe: transcribeRoutes, diff --git a/apps/backend/src/trpc/shared-story.routes.ts b/apps/backend/src/trpc/shared-story.routes.ts index 7dc404b4b..128fde951 100644 --- a/apps/backend/src/trpc/shared-story.routes.ts +++ b/apps/backend/src/trpc/shared-story.routes.ts @@ -77,15 +77,14 @@ export const sharedStoryRoutes = { get: shareAccessProcedure.input(z.object({ shareId: z.string() })).query(async ({ ctx }) => { const shared = ctx.resource; - - const storyRow = await storyQueries.getStoryByChatAndSlug(shared.chatId, shared.slug); + const storyRow = await storyQueries.getStoryByChatAndSlug(shared.chatId!, shared.slug); const isLive = storyRow?.isLive ?? false; const isLiveTextDynamic = storyRow?.isLiveTextDynamic ?? false; const cacheSchedule = storyRow?.cacheSchedule ?? null; const cacheScheduleDescription = storyRow?.cacheScheduleDescription ?? null; const { queryData, cachedAt } = await getStoryQueryData( - shared.chatId, + shared.chatId!, shared.slug, shared.code, isLive, @@ -113,7 +112,7 @@ export const sharedStoryRoutes = { refreshData: shareAccessProcedure.input(z.object({ shareId: z.string() })).mutation(async ({ ctx }) => { const shared = ctx.resource; - const { queryData } = await refreshStoryData(shared.chatId, shared.slug); + const { queryData } = await refreshStoryData(shared.chatId!, shared.slug); return { queryData, cachedAt: new Date() }; }), @@ -183,14 +182,14 @@ export const sharedStoryRoutes = { const shared = ctx.resource; const version = input.versionNumber - ? await storyQueries.getVersionByNumber(shared.chatId, shared.slug, input.versionNumber) - : await storyQueries.getLatestVersion(shared.chatId, shared.slug); + ? await storyQueries.getVersionByNumber(shared.chatId!, shared.slug, input.versionNumber) + : await storyQueries.getLatestVersionByChatAndSlug(shared.chatId!, shared.slug); if (!version) { throw new TRPCError({ code: 'NOT_FOUND', message: 'Story version not found.' }); } const { queryData } = await getStoryQueryData( - shared.chatId, + shared.chatId!, shared.slug, version.code, version.isLive, diff --git a/apps/backend/src/trpc/story.routes.ts b/apps/backend/src/trpc/story.routes.ts index e82d3959d..49e068bd5 100644 --- a/apps/backend/src/trpc/story.routes.ts +++ b/apps/backend/src/trpc/story.routes.ts @@ -14,7 +14,7 @@ const chatOwnerProcedure = ownedResourceProcedure(chatQueries.getChatOwnerId, 'c export const storyRoutes = { listAll: protectedProcedure.query(async ({ ctx }) => { - const stories = await storyQueries.listUserStories(ctx.user.id); + const stories = await storyQueries.listUserChatStories(ctx.user.id); return stories.map(({ code, ...rest }) => ({ ...rest, storySlug: rest.slug, @@ -23,7 +23,7 @@ export const storyRoutes = { }), listArchived: protectedProcedure.query(async ({ ctx }) => { - const stories = await storyQueries.listUserStories(ctx.user.id, { archived: true }); + const stories = await storyQueries.listUserChatStories(ctx.user.id, { archived: true }); return stories.map(({ code, ...rest }) => ({ ...rest, storySlug: rest.slug, @@ -31,10 +31,37 @@ export const storyRoutes = { })); }), + listStandalone: projectProtectedProcedure.query(async ({ ctx }) => { + const stories = await storyQueries.listUserStandaloneStories(ctx.user.id, ctx.project.id); + return stories.map(({ code, ...rest }) => ({ + ...rest, + storySlug: rest.slug, + summary: extractStorySummary(code), + })); + }), + + listStandaloneArchived: projectProtectedProcedure.query(async ({ ctx }) => { + const stories = await storyQueries.listUserStandaloneStories(ctx.user.id, ctx.project.id, { archived: true }); + return stories.map(({ code, ...rest }) => ({ + ...rest, + storySlug: rest.slug, + summary: extractStorySummary(code), + })); + }), + + getStandalone: projectProtectedProcedure.input(z.object({ storyId: z.string() })).query(async ({ input, ctx }) => { + const story = await storyQueries.getStoryByIdForUser(input.storyId, ctx.user.id); + if (!story) { + throw new TRPCError({ code: 'NOT_FOUND', message: 'Story not found.' }); + } + const cache = await storyQueries.getStoryDataCacheByStoryId(input.storyId); + return { ...story, queryData: cache?.queryData ?? null }; + }), + getLatest: chatOwnerProcedure .input(z.object({ chatId: z.string(), storySlug: z.string() })) .query(async ({ input }) => { - const version = await storyQueries.getLatestVersion(input.chatId, input.storySlug); + const version = await storyQueries.getLatestVersionByChatAndSlug(input.chatId, input.storySlug); if (!version) { throw new TRPCError({ code: 'NOT_FOUND', message: 'Story not found.' }); } @@ -154,6 +181,26 @@ export const storyRoutes = { await storyQueries.unarchiveStory(input.chatId, input.storySlug); }), + archiveStandalone: projectProtectedProcedure + .input(z.object({ storyId: z.string() })) + .mutation(async ({ input, ctx }) => { + const story = await storyQueries.getStoryByIdForUser(input.storyId, ctx.user.id); + if (!story) { + throw new TRPCError({ code: 'NOT_FOUND', message: 'Story not found.' }); + } + await storyQueries.archiveByStoryId(story.id); + }), + + unarchiveStandalone: projectProtectedProcedure + .input(z.object({ storyId: z.string() })) + .mutation(async ({ input, ctx }) => { + const story = await storyQueries.getStoryByIdForUser(input.storyId, ctx.user.id); + if (!story) { + throw new TRPCError({ code: 'NOT_FOUND', message: 'Story not found.' }); + } + await storyQueries.unarchiveByStoryId(story.id); + }), + archiveMany: protectedProcedure .input(z.object({ stories: z.array(z.object({ chatId: z.string(), storySlug: z.string() })).min(1) })) .mutation(async ({ input, ctx }) => { @@ -169,6 +216,17 @@ export const storyRoutes = { await storyQueries.archiveManyStories(input.stories.map((s) => ({ chatId: s.chatId, slug: s.storySlug }))); }), + downloadStandalone: projectProtectedProcedure + .input(z.object({ storyId: z.string(), format: z.enum(DOWNLOAD_FORMATS) })) + .query(async ({ input, ctx }) => { + const story = await storyQueries.getStoryByIdForUser(input.storyId, ctx.user.id); + if (!story) { + throw new TRPCError({ code: 'NOT_FOUND', message: 'Story not found.' }); + } + const cache = await storyQueries.getStoryDataCacheByStoryId(input.storyId); + return buildDownloadResponse(input.format, story.title, story.code, cache?.queryData ?? null); + }), + download: chatOwnerProcedure .input( z.object({ @@ -181,7 +239,7 @@ export const storyRoutes = { .query(async ({ input }) => { const version = input.versionNumber ? await storyQueries.getVersionByNumber(input.chatId, input.storySlug, input.versionNumber) - : await storyQueries.getLatestVersion(input.chatId, input.storySlug); + : await storyQueries.getLatestVersionByChatAndSlug(input.chatId, input.storySlug); if (!version) { throw new TRPCError({ code: 'NOT_FOUND', message: 'Story not found.' }); } diff --git a/apps/backend/src/types/mcp-endpoint.ts b/apps/backend/src/types/mcp-endpoint.ts new file mode 100644 index 000000000..978461e32 --- /dev/null +++ b/apps/backend/src/types/mcp-endpoint.ts @@ -0,0 +1,13 @@ +export interface McpEndpointSettings { + enabled: boolean; + agentModeEnabled: boolean; + toolsModeEnabled: boolean; + objectsModeEnabled: boolean; +} + +export const DEFAULT_MCP_ENDPOINT_SETTINGS: McpEndpointSettings = { + enabled: true, + agentModeEnabled: true, + toolsModeEnabled: true, + objectsModeEnabled: true, +}; diff --git a/apps/backend/tsconfig.json b/apps/backend/tsconfig.json index 2a8cadee4..2a608fe20 100644 --- a/apps/backend/tsconfig.json +++ b/apps/backend/tsconfig.json @@ -6,7 +6,6 @@ "esModuleInterop": true, "strict": true, "skipLibCheck": true, - "declaration": true, "outDir": "./dist", "rootDir": "../..", "types": ["node", "bun-types"], diff --git a/apps/frontend/src/components/auth-form.tsx b/apps/frontend/src/components/auth-form.tsx index d4d48aef6..4405139a4 100644 --- a/apps/frontend/src/components/auth-form.tsx +++ b/apps/frontend/src/components/auth-form.tsx @@ -15,6 +15,7 @@ interface AuthFormProps { children: React.ReactNode; serverError?: string; displaySocialProviders?: boolean; + socialCallbackUrl?: string; footer?: React.ReactNode; } @@ -25,6 +26,7 @@ export function AuthForm({ children, serverError, displaySocialProviders, + socialCallbackUrl, footer, }: AuthFormProps) { const isGoogleSetup = useQuery(trpc.authConfig.google.isSetup.queryOptions()); @@ -49,7 +51,7 @@ export function AuthForm({ type='button' variant='outline' className='w-full h-11' - onClick={handleGoogleSignIn} + onClick={() => handleGoogleSignIn(socialCallbackUrl)} > Continue with Google @@ -60,7 +62,7 @@ export function AuthForm({ type='button' variant='outline' className='w-full h-11' - onClick={handleGithubSignIn} + onClick={() => handleGithubSignIn(socialCallbackUrl)} > Continue with GitHub diff --git a/apps/frontend/src/components/settings-search-index.ts b/apps/frontend/src/components/settings-search-index.ts index d6a52f7f1..d74a13c07 100644 --- a/apps/frontend/src/components/settings-search-index.ts +++ b/apps/frontend/src/components/settings-search-index.ts @@ -189,6 +189,39 @@ export const settingsSearchIndex: SettingsSearchEntry[] = [ adminOnly: true, }, + // ── MCP Endpoint ──────────────────────────────────────── + { + page: '/settings/mcp-endpoint', + pageLabel: 'MCP Endpoint', + title: 'MCP Server Endpoint', + description: 'Allow external AI clients to connect to this workspace via MCP.', + keywords: ['model context protocol', 'claude desktop', 'cursor', 'external', 'api', 'bearer'], + }, + { + page: '/settings/mcp-endpoint', + pageLabel: 'MCP Endpoint', + section: 'MCP Modes', + title: 'Agent mode', + description: 'Let external agents ask analytics questions via ask_nao.', + keywords: ['ask_nao', 'agent', 'analytics'], + }, + { + page: '/settings/mcp-endpoint', + pageLabel: 'MCP Endpoint', + section: 'MCP Modes', + title: 'Tools mode', + description: 'Let external agents run SQL queries directly via execute_sql.', + keywords: ['execute_sql', 'sql', 'query'], + }, + { + page: '/settings/mcp-endpoint', + pageLabel: 'MCP Endpoint', + section: 'MCP Modes', + title: 'Objects mode', + description: 'Let external agents create and manage stories.', + keywords: ['stories', 'dashboard', 'report', 'crud'], + }, + // ── Project > Slack ────────────────────────────────────── { page: '/settings/project/slack', diff --git a/apps/frontend/src/components/settings/mcp-endpoint.tsx b/apps/frontend/src/components/settings/mcp-endpoint.tsx new file mode 100644 index 000000000..90a3cf6e8 --- /dev/null +++ b/apps/frontend/src/components/settings/mcp-endpoint.tsx @@ -0,0 +1,448 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { Check, ChevronLeft, ChevronRight, Copy } from 'lucide-react'; +import { useState } from 'react'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { SettingsCard } from '@/components/ui/settings-card'; +import { SettingsToggleRow } from '@/components/ui/settings-toggle-row'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { trpc } from '@/main'; + +interface ProjectItem { + id: string; + name: string; +} + +interface Props { + isAdmin: boolean; + projects?: ProjectItem[]; +} + +export function McpEndpointSettings({ isAdmin }: Props) { + const queryClient = useQueryClient(); + const settingsQuery = useQuery(trpc.mcpEndpoint.getSettings.queryOptions()); + const callLogsQuery = useQuery({ + ...trpc.mcpEndpoint.getCallLogs.queryOptions(), + refetchInterval: 30_000, + enabled: isAdmin, + }); + + const updateMutation = useMutation( + trpc.mcpEndpoint.updateSettings.mutationOptions({ + onMutate: async (newSettings) => { + const queryKey = trpc.mcpEndpoint.getSettings.queryOptions().queryKey; + await queryClient.cancelQueries({ queryKey }); + const prev = queryClient.getQueryData(queryKey); + if (prev) { + queryClient.setQueryData(queryKey, { ...prev, ...newSettings }); + } + return { prev }; + }, + onError: (_err, _vars, context) => { + if (context?.prev) { + queryClient.setQueryData(trpc.mcpEndpoint.getSettings.queryOptions().queryKey, context.prev); + } + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: trpc.mcpEndpoint.getSettings.queryOptions().queryKey }); + }, + }), + ); + + const settings = settingsQuery.data; + const enabled = settings?.enabled ?? true; + const pending = updateMutation.isPending; + + const toggle = (field: string, value: boolean) => { + updateMutation.mutate({ [field]: value }); + }; + + return ( + <> + + toggle('enabled', v)} + disabled={!isAdmin || pending} + /> + + + + toggle('agentModeEnabled', v)} + disabled={!isAdmin || !enabled || pending} + /> + toggle('toolsModeEnabled', v)} + disabled={!isAdmin || !enabled || pending} + /> + toggle('objectsModeEnabled', v)} + disabled={!isAdmin || !enabled || pending} + /> + + + + + {isAdmin && ( + + )} + + ); +} + +type Method = { + label?: string; + steps: string[]; + config?: string; + configLabel?: string; +}; + +type Provider = { + id: string; + label: string; + methods: Method[]; +}; + +const TOKEN_PLACEHOLDER = ''; + +function ConnectionCard() { + const endpointUrl = `${window.location.origin}/mcp`; + + const cursorConfig = JSON.stringify({ mcpServers: { nao: { type: 'http', url: endpointUrl } } }, null, 2); + + const claudeDesktopConfig = JSON.stringify( + { mcpServers: { nao: { command: 'npx', args: ['-y', 'mcp-remote', endpointUrl] } } }, + null, + 2, + ); + + const manualTokenConfig = JSON.stringify( + { + mcpServers: { + nao: { + type: 'http', + url: endpointUrl, + headers: { Authorization: `Bearer ${TOKEN_PLACEHOLDER}` }, + }, + }, + }, + null, + 2, + ); + + const providers: Provider[] = [ + { + id: 'cursor', + label: 'Cursor', + methods: [ + { + steps: [ + 'Open {Settings > Tools & MCP}.', + 'Click `New MCP Server` and paste the JSON below — or edit `.cursor/mcp.json` manually.', + 'Authenticate in your browser when prompted.', + ], + config: cursorConfig, + configLabel: 'JSON config', + }, + ], + }, + { + id: 'codex', + label: 'Codex', + methods: [ + { + steps: [ + 'Open {Settings > MCP servers > + Add server > Streamable HTTP}.', + 'Paste the URL below into the URL field, then save.', + 'Authenticate in your browser when prompted.', + ], + config: endpointUrl, + configLabel: 'Endpoint URL', + }, + ], + }, + { + id: 'claude-code', + label: 'Claude Code', + methods: [ + { + steps: ['Open `/.claude/settings.local.json`.', 'Paste the config below.'], + config: manualTokenConfig, + configLabel: 'JSON config', + }, + ], + }, + { + id: 'claude-desktop', + label: 'Claude Desktop', + methods: [ + { + label: 'Via Settings UI', + steps: [ + 'Open {Settings > Connectors > Add custom connector}.', + 'Set `Name` to anything, and `Remote MCP Server URL` to the URL below.', + 'Enable the connector and authenticate in your browser.', + ], + config: endpointUrl, + configLabel: 'Endpoint URL', + }, + { + label: 'Via config file', + steps: [ + 'Open `claude_desktop_config.json`, generally located in `~/Library/Application Support/Claude/`.', + 'Add the server using the JSON below.', + 'Restart Claude Desktop and authenticate when prompted.', + ], + config: claudeDesktopConfig, + configLabel: 'JSON config', + }, + ], + }, + { + id: 'cli', + label: 'CLI / Scripts', + methods: [ + { + steps: ['Use the config below in your MCP client.'], + config: manualTokenConfig, + configLabel: 'JSON config', + }, + ], + }, + ]; + + const [active, setActive] = useState(0); + const selected = providers[active]; + + const needsToken = selected.methods.some((m) => m.config?.includes(TOKEN_PLACEHOLDER)); + const tokenQuery = useQuery({ + ...trpc.mcpEndpoint.getBearerToken.queryOptions(), + enabled: needsToken, + staleTime: Infinity, + }); + + const resolveConfig = (config: string) => { + if (!config.includes(TOKEN_PLACEHOLDER)) { + return config; + } + const token = tokenQuery.data?.token; + return token ? config.replaceAll(TOKEN_PLACEHOLDER, token) : config; + }; + + const goPrev = () => setActive((i) => (i === 0 ? providers.length - 1 : i - 1)); + const goNext = () => setActive((i) => (i === providers.length - 1 ? 0 : i + 1)); + + return ( + +
+
+ {providers.map((p, i) => ( + + ))} +
+ + +
+
+ +
+ {selected.methods.map((method, i) => ( +
0 ? 'pt-3' : ''} ${i < selected.methods.length - 1 ? 'pb-3' : ''}`} + > + {method.label && ( +

+ Method {i + 1} — {method.label} +

+ )} + + {method.config && ( + + )} +
+ ))} +
+
+
+ ); +} + +function StepsList({ steps }: { steps: string[] }) { + return ( +
    + {steps.map((step, i) => ( +
  1. {renderInline(step)}
  2. + ))} +
+ ); +} + +function ConfigBlock({ label, text }: { label: string; text: string }) { + return ( +
+
+ {label} + +
+
+				{text}
+			
+
+ ); +} + +/** + * Renders a string with two inline syntaxes: + * - `code` → small code badge (bg, monospace) — for file paths, JSON keys, identifiers + * - {Foo > Bar > Baz} → breadcrumb (no bg, monospace, › separators) — for menu paths + */ +function renderInline(text: string) { + const parts = text.split(/(\{[^}]+\}|`[^`]+`)/g); + return parts.map((part, i) => { + if (part.startsWith('`') && part.endsWith('`')) { + return ( + + {part.slice(1, -1)} + + ); + } + if (part.startsWith('{') && part.endsWith('}')) { + const segments = part.slice(1, -1).split(/\s*>\s*/); + return ( + + {segments.map((seg, j) => ( + + {j > 0 && } + {seg} + + ))} + + ); + } + return {part}; + }); +} + +function CopyButton({ text }: { text: string }) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + await navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + + ); +} + +type CallLog = { + id: string; + userId: string; + userName: string | null; + toolName: string; + durationMs: number | null; + success: boolean; + calledAt: Date; +}; + +function CallLogsCard({ logs, isLoading, isError }: { logs: CallLog[]; isLoading: boolean; isError: boolean }) { + return ( + + {isLoading ? ( +

Loading…

+ ) : isError ? ( +

Failed to load MCP call logs.

+ ) : logs.length === 0 ? ( +

No MCP calls recorded yet.

+ ) : ( +
+ + + Time + User + Tool + Status + + + + {logs.map((log) => ( + + + {formatRelativeTime(log.calledAt)} + + {log.userName ?? 'Unknown'} + + {log.toolName} + + + + {log.success ? 'OK' : 'Error'} + + + + ))} + +
+ )} + + ); +} + +function formatRelativeTime(date: Date): string { + const diff = Date.now() - new Date(date).getTime(); + const minutes = Math.floor(diff / 60_000); + if (minutes < 1) { + return 'now'; + } + if (minutes < 60) { + return `${minutes}m`; + } + const hours = Math.floor(minutes / 60); + if (hours < 24) { + return `${hours}h`; + } + return `${Math.floor(hours / 24)}d`; +} diff --git a/apps/frontend/src/components/sidebar-settings-nav.tsx b/apps/frontend/src/components/sidebar-settings-nav.tsx index 97c07c2b0..511d969b8 100644 --- a/apps/frontend/src/components/sidebar-settings-nav.tsx +++ b/apps/frontend/src/components/sidebar-settings-nav.tsx @@ -46,6 +46,10 @@ const settingsNavItems: NavItem[] = [ to: '/settings/project', visible: ({ isViewer, isInMultipleProjects }) => !isViewer || isInMultipleProjects, }, + { + label: 'MCP Endpoint', + to: '/settings/mcp-endpoint', + }, { label: 'Observability', type: 'divider', diff --git a/apps/frontend/src/components/stories-groups.tsx b/apps/frontend/src/components/stories-groups.tsx index bf326cbd5..3842f0ce0 100644 --- a/apps/frontend/src/components/stories-groups.tsx +++ b/apps/frontend/src/components/stories-groups.tsx @@ -117,7 +117,9 @@ function StoryCard({ displayMode: DisplayMode; showArchived: boolean; }) { - if (item.kind !== 'own' || !item.chatId || !item.storySlug) { + const actionMenu = renderActionMenuForItem(item, displayMode, showArchived); + + if (!actionMenu) { return ( @@ -128,17 +130,29 @@ function StoryCard({ return ( - + ); +} + +function renderActionMenuForItem(item: StoryItem, displayMode: DisplayMode, showArchived: boolean) { + if (item.kind === 'own' && item.chatId && item.storySlug) { + return ( + - - ); + ); + } + if (item.kind === 'own-standalone') { + return ; + } + return null; } -function StoryActionMenu({ +function OwnStoryActionMenu({ chatId, storySlug, displayMode, @@ -168,8 +182,6 @@ function StoryActionMenu({ }), ); - const pending = archiveMutation.isPending || unarchiveMutation.isPending; - function handleSelect() { if (showArchived) { unarchiveMutation.mutate({ chatId, storySlug }); @@ -178,6 +190,74 @@ function StoryActionMenu({ } } + return ( + + ); +} + +function StandaloneStoryActionMenu({ + storyId, + displayMode, + showArchived, +}: { + storyId: string; + displayMode: DisplayMode; + showArchived: boolean; +}) { + const queryClient = useQueryClient(); + + const archiveMutation = useMutation( + trpc.story.archiveStandalone.mutationOptions({ + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: trpc.story.listStandalone.queryKey() }); + queryClient.invalidateQueries({ queryKey: trpc.story.listStandaloneArchived.queryKey() }); + }, + }), + ); + + const unarchiveMutation = useMutation( + trpc.story.unarchiveStandalone.mutationOptions({ + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: trpc.story.listStandalone.queryKey() }); + queryClient.invalidateQueries({ queryKey: trpc.story.listStandaloneArchived.queryKey() }); + }, + }), + ); + + function handleSelect() { + if (showArchived) { + unarchiveMutation.mutate({ storyId }); + } else { + archiveMutation.mutate({ storyId }); + } + } + + return ( + + ); +} + +function ArchiveActionMenu({ + displayMode, + showArchived, + pending, + onSelect, +}: { + displayMode: DisplayMode; + showArchived: boolean; + pending: boolean; + onSelect: () => void; +}) { return ( @@ -198,7 +278,7 @@ function StoryActionMenu({ e.stopPropagation()}> - + {showArchived ? : } {showArchived ? 'Unarchive' : 'Archive'} diff --git a/apps/frontend/src/components/story-download.tsx b/apps/frontend/src/components/story-download.tsx index bd9821261..cef2174c3 100644 --- a/apps/frontend/src/components/story-download.tsx +++ b/apps/frontend/src/components/story-download.tsx @@ -14,17 +14,25 @@ import { import { trpcClient } from '@/main'; interface StoryDownloadOptions { - chatId: string; - storySlug: string; + storyId?: string; + chatId?: string; + storySlug?: string; shareId?: string; isOwner?: boolean; versionNumber?: number; } -function useStoryDownload({ chatId, storySlug, shareId, isOwner = true, versionNumber }: StoryDownloadOptions) { +function useStoryDownload({ + storyId, + chatId, + storySlug, + shareId, + isOwner = true, + versionNumber, +}: StoryDownloadOptions) { const [isDownloading, setIsDownloading] = useState(false); const [error, setError] = useState(null); - const canDownload = isOwner || !!shareId; + const canDownload = isOwner || !!shareId || !!storyId; const handleDownload = async (format: DownloadFormat) => { if (!canDownload) { @@ -33,9 +41,19 @@ function useStoryDownload({ chatId, storySlug, shareId, isOwner = true, versionN setIsDownloading(true); setError(null); try { - const result = isOwner - ? await trpcClient.story.download.query({ chatId, storySlug, format, versionNumber }) - : await trpcClient.storyShare.download.query({ shareId: shareId!, format, versionNumber }); + let result; + if (storyId) { + result = await trpcClient.story.downloadStandalone.query({ storyId, format }); + } else if (isOwner) { + result = await trpcClient.story.download.query({ + chatId: chatId!, + storySlug: storySlug!, + format, + versionNumber, + }); + } else { + result = await trpcClient.storyShare.download.query({ shareId: shareId!, format, versionNumber }); + } const bytes = Uint8Array.from(atob(result.data), (c) => c.charCodeAt(0)); const blob = new Blob([bytes], { type: result.mimeType }); const url = URL.createObjectURL(blob); diff --git a/apps/frontend/src/components/tool-calls/mcp.tsx b/apps/frontend/src/components/tool-calls/mcp.tsx index 1c69b0ded..52e5f7000 100644 --- a/apps/frontend/src/components/tool-calls/mcp.tsx +++ b/apps/frontend/src/components/tool-calls/mcp.tsx @@ -1,12 +1,79 @@ import { Streamdown } from 'streamdown'; -import { ToolCallWrapper } from './tool-call-wrapper'; +import { parseChartBlock } from '@nao/shared/story-segments'; +import { ChartDisplay } from './display-chart'; import { TableDisplay } from './display-table'; +import { ToolCallWrapper } from './tool-call-wrapper'; import type { ToolCallComponentProps } from '.'; +import type { displayChart } from '@nao/shared/tools'; +import type { UIMessage, UIToolPart } from '@nao/backend/chat'; import { getToolName } from '@/lib/ai'; +import { useOptionalAgentContext } from '@/contexts/agent.provider'; import { useToolCallContext } from '@/contexts/tool-call'; +const EMPTY_MESSAGES: UIMessage[] = []; + type McpContent = { type: string; text: string }; +const extractChartBlock = (output: unknown): string | null => { + if (output && typeof output === 'object' && !Array.isArray(output)) { + const block = (output as Record).block; + if (typeof block === 'string' && /^[] | null => { + for (const message of messages) { + for (const part of message.parts) { + const output = (part as UIToolPart).output; + if (output && typeof output === 'object' && !Array.isArray(output)) { + const typed = output as Record; + if (typed.query_id === queryId && Array.isArray(typed.data)) { + return typed.data as Record[]; + } + } + } + } + return null; +}; + +const McpChartOutput = ({ chartBlock }: { chartBlock: string }) => { + const agent = useOptionalAgentContext(); + const messages = agent?.messages ?? EMPTY_MESSAGES; + + const attrString = chartBlock.match(/^$/)?.[1] ?? ''; + const chart = parseChartBlock(attrString); + + if (!chart || chart.series.length === 0) { + return null; + } + + const data = findSqlData(messages, chart.queryId); + + if (!data || data.length === 0) { + return ( +
+ Chart data unavailable +
+ ); + } + + return ( +
+ +
+ ); +}; + const extractText = (output: unknown): string | null => { if (typeof output === 'string') { return output; @@ -81,7 +148,14 @@ const McpOutputContent = ({ text }: { text: string }) => { export const McpToolCall = ({ toolPart }: ToolCallComponentProps) => { const { isSettled } = useToolCallContext(); const toolName = getToolName(toolPart); - const text = isSettled ? extractText(toolPart.output) : null; + if (isSettled) { + const chartBlock = extractChartBlock(toolPart.output); + if (chartBlock) { + return ; + } + } + + const text = isSettled ? extractText(toolPart.output) : null; return {text !== null && }; }; diff --git a/apps/frontend/src/lib/auth-client.ts b/apps/frontend/src/lib/auth-client.ts index fb8cfb596..d8b83bcf8 100644 --- a/apps/frontend/src/lib/auth-client.ts +++ b/apps/frontend/src/lib/auth-client.ts @@ -18,18 +18,18 @@ export const authClient = createAuthClient({ export const { useSession, signIn, signUp, signOut, requestPasswordReset, resetPassword } = authClient; -const handleGoogleSignIn = async () => { +const handleGoogleSignIn = async (callbackURL = '/') => { await authClient.signIn.social({ provider: 'google', - callbackURL: '/', + callbackURL, errorCallbackURL: '/login', }); }; -const handleGithubSignIn = async () => { +const handleGithubSignIn = async (callbackURL = '/') => { await authClient.signIn.social({ provider: 'github', - callbackURL: '/', + callbackURL, errorCallbackURL: '/login', }); }; diff --git a/apps/frontend/src/lib/stories-page.ts b/apps/frontend/src/lib/stories-page.ts index ab4970b68..3928d6a60 100644 --- a/apps/frontend/src/lib/stories-page.ts +++ b/apps/frontend/src/lib/stories-page.ts @@ -17,29 +17,32 @@ export type StoryItem = { title: string; createdAt: Date; author: string; - kind: 'own' | 'shared-with-me' | 'shared-project'; + kind: 'own' | 'own-standalone' | 'shared-with-me' | 'shared-project'; chatId?: string; storySlug?: string; summary: StorySummary; link: | { to: '/stories/preview/$chatId/$storySlug'; params: { chatId: string; storySlug: string } } - | { to: '/stories/shared/$shareId'; params: { shareId: string } }; + | { to: '/stories/shared/$shareId'; params: { shareId: string } } + | { to: '/stories/standalone/$storyId'; params: { storyId: string } }; }; export type StoryGroup = { label: string; items: StoryItem[] }; export type OwnStoryListItem = { - chatId: string; + id?: string; + chatId?: string | null; storySlug: string; title: string; createdAt: Date | string; summary: StorySummary; + isStandalone?: boolean; }; export type SharedStoryListItem = { id: string; userId: string; - chatId: string; + chatId: string | null; storySlug: string; title: string; createdAt: Date | string; @@ -55,18 +58,20 @@ export function getStoredSetting(key: string, allowed: T[], fa export function buildStoryItems({ userStories, + standaloneStories, sharedStories, currentUserId, currentUserName, }: { userStories: OwnStoryListItem[]; + standaloneStories?: OwnStoryListItem[]; sharedStories: SharedStoryListItem[]; currentUserId?: string; currentUserName: string; }): StoryItem[] { const ownShareMap = new Map(); for (const story of sharedStories) { - if (story.userId === currentUserId) { + if (story.userId === currentUserId && story.chatId) { const key = `${story.chatId}-${story.storySlug}`; if (!ownShareMap.has(key)) { ownShareMap.set(key, story.id); @@ -75,25 +80,37 @@ export function buildStoryItems({ } const ownItems: StoryItem[] = userStories.map((story) => { - const shareId = ownShareMap.get(`${story.chatId}-${story.storySlug}`); + const chatId = story.chatId!; + const shareId = ownShareMap.get(`${chatId}-${story.storySlug}`); return { - id: `${story.chatId}-${story.storySlug}`, + id: `${chatId}-${story.storySlug}`, title: story.title, createdAt: new Date(story.createdAt), author: currentUserName, kind: 'own', - chatId: story.chatId, + chatId, storySlug: story.storySlug, summary: story.summary, link: shareId ? { to: '/stories/shared/$shareId', params: { shareId } } : { to: '/stories/preview/$chatId/$storySlug', - params: { chatId: story.chatId, storySlug: story.storySlug }, + params: { chatId, storySlug: story.storySlug }, }, }; }); + const standaloneItems: StoryItem[] = (standaloneStories ?? []).map((story) => ({ + id: story.id ?? story.storySlug, + title: story.title, + createdAt: new Date(story.createdAt), + author: currentUserName, + kind: 'own-standalone', + storySlug: story.storySlug, + summary: story.summary, + link: { to: '/stories/standalone/$storyId', params: { storyId: story.id! } }, + })); + const sharedItems: StoryItem[] = sharedStories .filter((story) => story.userId !== currentUserId) .map((story) => ({ @@ -106,7 +123,7 @@ export function buildStoryItems({ link: { to: '/stories/shared/$shareId', params: { shareId: story.id } }, })); - return [...ownItems, ...sharedItems]; + return [...ownItems, ...standaloneItems, ...sharedItems]; } export function filterStories(items: StoryItem[], query: string): StoryItem[] { @@ -139,24 +156,28 @@ export function groupStories(items: StoryItem[], groupBy: GroupBy): StoryGroup[] } function groupByOwnership(items: StoryItem[]): StoryGroup[] { - const own = items.filter((item) => item.kind === 'own'); + const own = items.filter((item) => item.kind === 'own' || item.kind === 'own-standalone'); const sharedWithMe = items.filter((item) => item.kind === 'shared-with-me'); const sharedProject = items.filter((item) => item.kind === 'shared-project'); const groups: StoryGroup[] = []; if (own.length > 0) { - groups.push({ label: 'My Stories', items: own }); + groups.push({ label: 'My Stories', items: sortByCreatedAtDesc(own) }); } if (sharedWithMe.length > 0) { - groups.push({ label: 'Shared with Me', items: sharedWithMe }); + groups.push({ label: 'Shared with Me', items: sortByCreatedAtDesc(sharedWithMe) }); } if (sharedProject.length > 0) { - groups.push({ label: 'Shared with the Project', items: sharedProject }); + groups.push({ label: 'Shared with the Project', items: sortByCreatedAtDesc(sharedProject) }); } return groups; } +function sortByCreatedAtDesc(items: StoryItem[]): StoryItem[] { + return [...items].sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); +} + function groupByDate(items: StoryItem[]): StoryGroup[] { const now = new Date(); const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()); @@ -205,7 +226,10 @@ function groupByUser(items: StoryItem[]): StoryGroup[] { } } - return [...groupedByAuthor.entries()].map(([label, group]) => ({ label, items: group })); + return [...groupedByAuthor.entries()].map(([label, group]) => ({ + label, + items: sortByCreatedAtDesc(group), + })); } function extractSummaryText(summary: StorySummary): string { diff --git a/apps/frontend/src/routeTree.gen.ts b/apps/frontend/src/routeTree.gen.ts index 68c187945..d7bc84465 100644 --- a/apps/frontend/src/routeTree.gen.ts +++ b/apps/frontend/src/routeTree.gen.ts @@ -24,6 +24,7 @@ import { Route as SidebarLayoutSettingsUsageRouteImport } from './routes/_sideba import { Route as SidebarLayoutSettingsProjectRouteImport } from './routes/_sidebar-layout.settings.project' import { Route as SidebarLayoutSettingsOrganizationRouteImport } from './routes/_sidebar-layout.settings.organization' import { Route as SidebarLayoutSettingsMemoryRouteImport } from './routes/_sidebar-layout.settings.memory' +import { Route as SidebarLayoutSettingsMcpEndpointRouteImport } from './routes/_sidebar-layout.settings.mcp-endpoint' import { Route as SidebarLayoutSettingsLogsRouteImport } from './routes/_sidebar-layout.settings.logs' import { Route as SidebarLayoutSettingsEnterpriseRouteImport } from './routes/_sidebar-layout.settings.enterprise' import { Route as SidebarLayoutSettingsContextExplorerRouteImport } from './routes/_sidebar-layout.settings.context-explorer' @@ -31,6 +32,7 @@ import { Route as SidebarLayoutSettingsChatsReplayRouteImport } from './routes/_ import { Route as SidebarLayoutSettingsAccountRouteImport } from './routes/_sidebar-layout.settings.account' import { Route as SidebarLayoutChatLayoutChatIdRouteImport } from './routes/_sidebar-layout._chat-layout.$chatId' import { Route as SidebarLayoutSettingsProjectIndexRouteImport } from './routes/_sidebar-layout.settings.project.index' +import { Route as SidebarLayoutStoriesStandaloneStoryIdRouteImport } from './routes/_sidebar-layout.stories.standalone.$storyId' import { Route as SidebarLayoutStoriesSharedShareIdRouteImport } from './routes/_sidebar-layout.stories.shared.$shareId' import { Route as SidebarLayoutSettingsProjectWhatsappRouteImport } from './routes/_sidebar-layout.settings.project.whatsapp' import { Route as SidebarLayoutSettingsProjectTelegramRouteImport } from './routes/_sidebar-layout.settings.project.telegram' @@ -39,6 +41,7 @@ import { Route as SidebarLayoutSettingsProjectTeamRouteImport } from './routes/_ import { Route as SidebarLayoutSettingsProjectSlackRouteImport } from './routes/_sidebar-layout.settings.project.slack' import { Route as SidebarLayoutSettingsProjectModelsRouteImport } from './routes/_sidebar-layout.settings.project.models' import { Route as SidebarLayoutSettingsProjectMcpServersRouteImport } from './routes/_sidebar-layout.settings.project.mcp-servers' +import { Route as SidebarLayoutSettingsProjectMcpEndpointRouteImport } from './routes/_sidebar-layout.settings.project.mcp-endpoint' import { Route as SidebarLayoutSettingsProjectBudgetsRouteImport } from './routes/_sidebar-layout.settings.project.budgets' import { Route as SidebarLayoutSettingsProjectAgentRouteImport } from './routes/_sidebar-layout.settings.project.agent' import { Route as SidebarLayoutStoriesPreviewChatIdStorySlugRouteImport } from './routes/_sidebar-layout.stories.preview.$chatId.$storySlug' @@ -124,6 +127,12 @@ const SidebarLayoutSettingsMemoryRoute = path: '/memory', getParentRoute: () => SidebarLayoutSettingsRoute, } as any) +const SidebarLayoutSettingsMcpEndpointRoute = + SidebarLayoutSettingsMcpEndpointRouteImport.update({ + id: '/mcp-endpoint', + path: '/mcp-endpoint', + getParentRoute: () => SidebarLayoutSettingsRoute, + } as any) const SidebarLayoutSettingsLogsRoute = SidebarLayoutSettingsLogsRouteImport.update({ id: '/logs', @@ -166,6 +175,12 @@ const SidebarLayoutSettingsProjectIndexRoute = path: '/', getParentRoute: () => SidebarLayoutSettingsProjectRoute, } as any) +const SidebarLayoutStoriesStandaloneStoryIdRoute = + SidebarLayoutStoriesStandaloneStoryIdRouteImport.update({ + id: '/stories/standalone/$storyId', + path: '/stories/standalone/$storyId', + getParentRoute: () => SidebarLayoutRoute, + } as any) const SidebarLayoutStoriesSharedShareIdRoute = SidebarLayoutStoriesSharedShareIdRouteImport.update({ id: '/stories/shared/$shareId', @@ -214,6 +229,12 @@ const SidebarLayoutSettingsProjectMcpServersRoute = path: '/mcp-servers', getParentRoute: () => SidebarLayoutSettingsProjectRoute, } as any) +const SidebarLayoutSettingsProjectMcpEndpointRoute = + SidebarLayoutSettingsProjectMcpEndpointRouteImport.update({ + id: '/mcp-endpoint', + path: '/mcp-endpoint', + getParentRoute: () => SidebarLayoutSettingsProjectRoute, + } as any) const SidebarLayoutSettingsProjectBudgetsRoute = SidebarLayoutSettingsProjectBudgetsRouteImport.update({ id: '/budgets', @@ -246,6 +267,7 @@ export interface FileRoutesByFullPath { '/settings/context-explorer': typeof SidebarLayoutSettingsContextExplorerRoute '/settings/enterprise': typeof SidebarLayoutSettingsEnterpriseRoute '/settings/logs': typeof SidebarLayoutSettingsLogsRoute + '/settings/mcp-endpoint': typeof SidebarLayoutSettingsMcpEndpointRoute '/settings/memory': typeof SidebarLayoutSettingsMemoryRoute '/settings/organization': typeof SidebarLayoutSettingsOrganizationRoute '/settings/project': typeof SidebarLayoutSettingsProjectRouteWithChildren @@ -255,6 +277,7 @@ export interface FileRoutesByFullPath { '/stories/': typeof SidebarLayoutStoriesIndexRoute '/settings/project/agent': typeof SidebarLayoutSettingsProjectAgentRoute '/settings/project/budgets': typeof SidebarLayoutSettingsProjectBudgetsRoute + '/settings/project/mcp-endpoint': typeof SidebarLayoutSettingsProjectMcpEndpointRoute '/settings/project/mcp-servers': typeof SidebarLayoutSettingsProjectMcpServersRoute '/settings/project/models': typeof SidebarLayoutSettingsProjectModelsRoute '/settings/project/slack': typeof SidebarLayoutSettingsProjectSlackRoute @@ -263,6 +286,7 @@ export interface FileRoutesByFullPath { '/settings/project/telegram': typeof SidebarLayoutSettingsProjectTelegramRoute '/settings/project/whatsapp': typeof SidebarLayoutSettingsProjectWhatsappRoute '/stories/shared/$shareId': typeof SidebarLayoutStoriesSharedShareIdRoute + '/stories/standalone/$storyId': typeof SidebarLayoutStoriesStandaloneStoryIdRoute '/settings/project/': typeof SidebarLayoutSettingsProjectIndexRoute '/stories/preview/$chatId/$storySlug': typeof SidebarLayoutStoriesPreviewChatIdStorySlugRoute } @@ -278,6 +302,7 @@ export interface FileRoutesByTo { '/settings/context-explorer': typeof SidebarLayoutSettingsContextExplorerRoute '/settings/enterprise': typeof SidebarLayoutSettingsEnterpriseRoute '/settings/logs': typeof SidebarLayoutSettingsLogsRoute + '/settings/mcp-endpoint': typeof SidebarLayoutSettingsMcpEndpointRoute '/settings/memory': typeof SidebarLayoutSettingsMemoryRoute '/settings/organization': typeof SidebarLayoutSettingsOrganizationRoute '/settings/usage': typeof SidebarLayoutSettingsUsageRoute @@ -286,6 +311,7 @@ export interface FileRoutesByTo { '/stories': typeof SidebarLayoutStoriesIndexRoute '/settings/project/agent': typeof SidebarLayoutSettingsProjectAgentRoute '/settings/project/budgets': typeof SidebarLayoutSettingsProjectBudgetsRoute + '/settings/project/mcp-endpoint': typeof SidebarLayoutSettingsProjectMcpEndpointRoute '/settings/project/mcp-servers': typeof SidebarLayoutSettingsProjectMcpServersRoute '/settings/project/models': typeof SidebarLayoutSettingsProjectModelsRoute '/settings/project/slack': typeof SidebarLayoutSettingsProjectSlackRoute @@ -294,6 +320,7 @@ export interface FileRoutesByTo { '/settings/project/telegram': typeof SidebarLayoutSettingsProjectTelegramRoute '/settings/project/whatsapp': typeof SidebarLayoutSettingsProjectWhatsappRoute '/stories/shared/$shareId': typeof SidebarLayoutStoriesSharedShareIdRoute + '/stories/standalone/$storyId': typeof SidebarLayoutStoriesStandaloneStoryIdRoute '/settings/project': typeof SidebarLayoutSettingsProjectIndexRoute '/stories/preview/$chatId/$storySlug': typeof SidebarLayoutStoriesPreviewChatIdStorySlugRoute } @@ -312,6 +339,7 @@ export interface FileRoutesById { '/_sidebar-layout/settings/context-explorer': typeof SidebarLayoutSettingsContextExplorerRoute '/_sidebar-layout/settings/enterprise': typeof SidebarLayoutSettingsEnterpriseRoute '/_sidebar-layout/settings/logs': typeof SidebarLayoutSettingsLogsRoute + '/_sidebar-layout/settings/mcp-endpoint': typeof SidebarLayoutSettingsMcpEndpointRoute '/_sidebar-layout/settings/memory': typeof SidebarLayoutSettingsMemoryRoute '/_sidebar-layout/settings/organization': typeof SidebarLayoutSettingsOrganizationRoute '/_sidebar-layout/settings/project': typeof SidebarLayoutSettingsProjectRouteWithChildren @@ -322,6 +350,7 @@ export interface FileRoutesById { '/_sidebar-layout/stories/': typeof SidebarLayoutStoriesIndexRoute '/_sidebar-layout/settings/project/agent': typeof SidebarLayoutSettingsProjectAgentRoute '/_sidebar-layout/settings/project/budgets': typeof SidebarLayoutSettingsProjectBudgetsRoute + '/_sidebar-layout/settings/project/mcp-endpoint': typeof SidebarLayoutSettingsProjectMcpEndpointRoute '/_sidebar-layout/settings/project/mcp-servers': typeof SidebarLayoutSettingsProjectMcpServersRoute '/_sidebar-layout/settings/project/models': typeof SidebarLayoutSettingsProjectModelsRoute '/_sidebar-layout/settings/project/slack': typeof SidebarLayoutSettingsProjectSlackRoute @@ -330,6 +359,7 @@ export interface FileRoutesById { '/_sidebar-layout/settings/project/telegram': typeof SidebarLayoutSettingsProjectTelegramRoute '/_sidebar-layout/settings/project/whatsapp': typeof SidebarLayoutSettingsProjectWhatsappRoute '/_sidebar-layout/stories/shared/$shareId': typeof SidebarLayoutStoriesSharedShareIdRoute + '/_sidebar-layout/stories/standalone/$storyId': typeof SidebarLayoutStoriesStandaloneStoryIdRoute '/_sidebar-layout/settings/project/': typeof SidebarLayoutSettingsProjectIndexRoute '/_sidebar-layout/stories/preview/$chatId/$storySlug': typeof SidebarLayoutStoriesPreviewChatIdStorySlugRoute } @@ -348,6 +378,7 @@ export interface FileRouteTypes { | '/settings/context-explorer' | '/settings/enterprise' | '/settings/logs' + | '/settings/mcp-endpoint' | '/settings/memory' | '/settings/organization' | '/settings/project' @@ -357,6 +388,7 @@ export interface FileRouteTypes { | '/stories/' | '/settings/project/agent' | '/settings/project/budgets' + | '/settings/project/mcp-endpoint' | '/settings/project/mcp-servers' | '/settings/project/models' | '/settings/project/slack' @@ -365,6 +397,7 @@ export interface FileRouteTypes { | '/settings/project/telegram' | '/settings/project/whatsapp' | '/stories/shared/$shareId' + | '/stories/standalone/$storyId' | '/settings/project/' | '/stories/preview/$chatId/$storySlug' fileRoutesByTo: FileRoutesByTo @@ -380,6 +413,7 @@ export interface FileRouteTypes { | '/settings/context-explorer' | '/settings/enterprise' | '/settings/logs' + | '/settings/mcp-endpoint' | '/settings/memory' | '/settings/organization' | '/settings/usage' @@ -388,6 +422,7 @@ export interface FileRouteTypes { | '/stories' | '/settings/project/agent' | '/settings/project/budgets' + | '/settings/project/mcp-endpoint' | '/settings/project/mcp-servers' | '/settings/project/models' | '/settings/project/slack' @@ -396,6 +431,7 @@ export interface FileRouteTypes { | '/settings/project/telegram' | '/settings/project/whatsapp' | '/stories/shared/$shareId' + | '/stories/standalone/$storyId' | '/settings/project' | '/stories/preview/$chatId/$storySlug' id: @@ -413,6 +449,7 @@ export interface FileRouteTypes { | '/_sidebar-layout/settings/context-explorer' | '/_sidebar-layout/settings/enterprise' | '/_sidebar-layout/settings/logs' + | '/_sidebar-layout/settings/mcp-endpoint' | '/_sidebar-layout/settings/memory' | '/_sidebar-layout/settings/organization' | '/_sidebar-layout/settings/project' @@ -423,6 +460,7 @@ export interface FileRouteTypes { | '/_sidebar-layout/stories/' | '/_sidebar-layout/settings/project/agent' | '/_sidebar-layout/settings/project/budgets' + | '/_sidebar-layout/settings/project/mcp-endpoint' | '/_sidebar-layout/settings/project/mcp-servers' | '/_sidebar-layout/settings/project/models' | '/_sidebar-layout/settings/project/slack' @@ -431,6 +469,7 @@ export interface FileRouteTypes { | '/_sidebar-layout/settings/project/telegram' | '/_sidebar-layout/settings/project/whatsapp' | '/_sidebar-layout/stories/shared/$shareId' + | '/_sidebar-layout/stories/standalone/$storyId' | '/_sidebar-layout/settings/project/' | '/_sidebar-layout/stories/preview/$chatId/$storySlug' fileRoutesById: FileRoutesById @@ -550,6 +589,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SidebarLayoutSettingsMemoryRouteImport parentRoute: typeof SidebarLayoutSettingsRoute } + '/_sidebar-layout/settings/mcp-endpoint': { + id: '/_sidebar-layout/settings/mcp-endpoint' + path: '/mcp-endpoint' + fullPath: '/settings/mcp-endpoint' + preLoaderRoute: typeof SidebarLayoutSettingsMcpEndpointRouteImport + parentRoute: typeof SidebarLayoutSettingsRoute + } '/_sidebar-layout/settings/logs': { id: '/_sidebar-layout/settings/logs' path: '/logs' @@ -599,6 +645,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SidebarLayoutSettingsProjectIndexRouteImport parentRoute: typeof SidebarLayoutSettingsProjectRoute } + '/_sidebar-layout/stories/standalone/$storyId': { + id: '/_sidebar-layout/stories/standalone/$storyId' + path: '/stories/standalone/$storyId' + fullPath: '/stories/standalone/$storyId' + preLoaderRoute: typeof SidebarLayoutStoriesStandaloneStoryIdRouteImport + parentRoute: typeof SidebarLayoutRoute + } '/_sidebar-layout/stories/shared/$shareId': { id: '/_sidebar-layout/stories/shared/$shareId' path: '/stories/shared/$shareId' @@ -655,6 +708,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SidebarLayoutSettingsProjectMcpServersRouteImport parentRoute: typeof SidebarLayoutSettingsProjectRoute } + '/_sidebar-layout/settings/project/mcp-endpoint': { + id: '/_sidebar-layout/settings/project/mcp-endpoint' + path: '/mcp-endpoint' + fullPath: '/settings/project/mcp-endpoint' + preLoaderRoute: typeof SidebarLayoutSettingsProjectMcpEndpointRouteImport + parentRoute: typeof SidebarLayoutSettingsProjectRoute + } '/_sidebar-layout/settings/project/budgets': { id: '/_sidebar-layout/settings/project/budgets' path: '/budgets' @@ -698,6 +758,7 @@ const SidebarLayoutChatLayoutRouteWithChildren = interface SidebarLayoutSettingsProjectRouteChildren { SidebarLayoutSettingsProjectAgentRoute: typeof SidebarLayoutSettingsProjectAgentRoute SidebarLayoutSettingsProjectBudgetsRoute: typeof SidebarLayoutSettingsProjectBudgetsRoute + SidebarLayoutSettingsProjectMcpEndpointRoute: typeof SidebarLayoutSettingsProjectMcpEndpointRoute SidebarLayoutSettingsProjectMcpServersRoute: typeof SidebarLayoutSettingsProjectMcpServersRoute SidebarLayoutSettingsProjectModelsRoute: typeof SidebarLayoutSettingsProjectModelsRoute SidebarLayoutSettingsProjectSlackRoute: typeof SidebarLayoutSettingsProjectSlackRoute @@ -714,6 +775,8 @@ const SidebarLayoutSettingsProjectRouteChildren: SidebarLayoutSettingsProjectRou SidebarLayoutSettingsProjectAgentRoute, SidebarLayoutSettingsProjectBudgetsRoute: SidebarLayoutSettingsProjectBudgetsRoute, + SidebarLayoutSettingsProjectMcpEndpointRoute: + SidebarLayoutSettingsProjectMcpEndpointRoute, SidebarLayoutSettingsProjectMcpServersRoute: SidebarLayoutSettingsProjectMcpServersRoute, SidebarLayoutSettingsProjectModelsRoute: @@ -743,6 +806,7 @@ interface SidebarLayoutSettingsRouteChildren { SidebarLayoutSettingsContextExplorerRoute: typeof SidebarLayoutSettingsContextExplorerRoute SidebarLayoutSettingsEnterpriseRoute: typeof SidebarLayoutSettingsEnterpriseRoute SidebarLayoutSettingsLogsRoute: typeof SidebarLayoutSettingsLogsRoute + SidebarLayoutSettingsMcpEndpointRoute: typeof SidebarLayoutSettingsMcpEndpointRoute SidebarLayoutSettingsMemoryRoute: typeof SidebarLayoutSettingsMemoryRoute SidebarLayoutSettingsOrganizationRoute: typeof SidebarLayoutSettingsOrganizationRoute SidebarLayoutSettingsProjectRoute: typeof SidebarLayoutSettingsProjectRouteWithChildren @@ -757,6 +821,7 @@ const SidebarLayoutSettingsRouteChildren: SidebarLayoutSettingsRouteChildren = { SidebarLayoutSettingsContextExplorerRoute, SidebarLayoutSettingsEnterpriseRoute: SidebarLayoutSettingsEnterpriseRoute, SidebarLayoutSettingsLogsRoute: SidebarLayoutSettingsLogsRoute, + SidebarLayoutSettingsMcpEndpointRoute: SidebarLayoutSettingsMcpEndpointRoute, SidebarLayoutSettingsMemoryRoute: SidebarLayoutSettingsMemoryRoute, SidebarLayoutSettingsOrganizationRoute: SidebarLayoutSettingsOrganizationRoute, @@ -777,6 +842,7 @@ interface SidebarLayoutRouteChildren { SidebarLayoutSharedChatShareIdRoute: typeof SidebarLayoutSharedChatShareIdRoute SidebarLayoutStoriesIndexRoute: typeof SidebarLayoutStoriesIndexRoute SidebarLayoutStoriesSharedShareIdRoute: typeof SidebarLayoutStoriesSharedShareIdRoute + SidebarLayoutStoriesStandaloneStoryIdRoute: typeof SidebarLayoutStoriesStandaloneStoryIdRoute SidebarLayoutStoriesPreviewChatIdStorySlugRoute: typeof SidebarLayoutStoriesPreviewChatIdStorySlugRoute } @@ -787,6 +853,8 @@ const SidebarLayoutRouteChildren: SidebarLayoutRouteChildren = { SidebarLayoutStoriesIndexRoute: SidebarLayoutStoriesIndexRoute, SidebarLayoutStoriesSharedShareIdRoute: SidebarLayoutStoriesSharedShareIdRoute, + SidebarLayoutStoriesStandaloneStoryIdRoute: + SidebarLayoutStoriesStandaloneStoryIdRoute, SidebarLayoutStoriesPreviewChatIdStorySlugRoute: SidebarLayoutStoriesPreviewChatIdStorySlugRoute, } diff --git a/apps/frontend/src/routes/_sidebar-layout.settings.mcp-endpoint.tsx b/apps/frontend/src/routes/_sidebar-layout.settings.mcp-endpoint.tsx new file mode 100644 index 000000000..aa91a3472 --- /dev/null +++ b/apps/frontend/src/routes/_sidebar-layout.settings.mcp-endpoint.tsx @@ -0,0 +1,21 @@ +import { createFileRoute } from '@tanstack/react-router'; +import { useQuery } from '@tanstack/react-query'; +import { McpEndpointSettings } from '@/components/settings/mcp-endpoint'; +import { SettingsPageWrapper } from '@/components/ui/settings-card'; +import { trpc } from '@/main'; + +export const Route = createFileRoute('/_sidebar-layout/settings/mcp-endpoint')({ + component: McpEndpointPage, +}); + +function McpEndpointPage() { + const project = useQuery(trpc.project.getCurrent.queryOptions()); + const allProjects = useQuery(trpc.project.listForCurrentUser.queryOptions()); + const isAdmin = project.data?.userRole === 'admin'; + + return ( + + + + ); +} diff --git a/apps/frontend/src/routes/_sidebar-layout.settings.project.mcp-endpoint.tsx b/apps/frontend/src/routes/_sidebar-layout.settings.project.mcp-endpoint.tsx new file mode 100644 index 000000000..5e68c7789 --- /dev/null +++ b/apps/frontend/src/routes/_sidebar-layout.settings.project.mcp-endpoint.tsx @@ -0,0 +1,15 @@ +import { createFileRoute } from '@tanstack/react-router'; +import { useQuery } from '@tanstack/react-query'; +import { McpEndpointSettings } from '@/components/settings/mcp-endpoint'; +import { trpc } from '@/main'; + +export const Route = createFileRoute('/_sidebar-layout/settings/project/mcp-endpoint')({ + component: ProjectMcpEndpointPage, +}); + +function ProjectMcpEndpointPage() { + const project = useQuery(trpc.project.getCurrent.queryOptions()); + const isAdmin = project.data?.userRole === 'admin'; + + return ; +} diff --git a/apps/frontend/src/routes/_sidebar-layout.stories.index.tsx b/apps/frontend/src/routes/_sidebar-layout.stories.index.tsx index e9278fb16..7fc53864f 100644 --- a/apps/frontend/src/routes/_sidebar-layout.stories.index.tsx +++ b/apps/frontend/src/routes/_sidebar-layout.stories.index.tsx @@ -1,7 +1,7 @@ import { useMemo, useState } from 'react'; import { createFileRoute } from '@tanstack/react-router'; import { useQuery } from '@tanstack/react-query'; -import type { DisplayMode, GroupBy } from '@/lib/stories-page'; +import type { DisplayMode, GroupBy, OwnStoryListItem } from '@/lib/stories-page'; import { StoriesEmptyState, StoriesGroups, StoriesNoResults } from '@/components/stories-groups'; import { StoriesToolbarControls } from '@/components/stories-toolbar-controls'; import { MobileHeader } from '@/components/mobile-header'; @@ -32,19 +32,46 @@ function StoriesPage() { const [showArchived, setShowArchived] = useState(false); const userStories = useQuery(trpc.story.listAll.queryOptions()); + const standaloneStories = useQuery(trpc.story.listStandalone.queryOptions()); const sharedStories = useQuery(trpc.storyShare.list.queryOptions()); const archivedStories = useQuery({ ...trpc.story.listArchived.queryOptions(), enabled: showArchived, }); + const archivedStandaloneStories = useQuery({ + ...trpc.story.listStandaloneArchived.queryOptions(), + enabled: showArchived, + }); const currentUserName = session?.user?.name ?? 'Me'; const currentUserId = session?.user?.id; const allItems = useMemo(() => { + const mapStandalone = ( + stories: + | { + id: string; + storySlug: string; + title: string; + createdAt: Date; + summary: OwnStoryListItem['summary']; + }[] + | undefined, + ): OwnStoryListItem[] | undefined => + stories?.map((s) => ({ + id: s.id, + chatId: null, + storySlug: s.storySlug, + title: s.title, + createdAt: s.createdAt, + summary: s.summary, + isStandalone: true, + })); + if (showArchived) { return buildStoryItems({ userStories: archivedStories.data ?? [], + standaloneStories: mapStandalone(archivedStandaloneStories.data), sharedStories: [], currentUserId, currentUserName, @@ -52,11 +79,21 @@ function StoriesPage() { } return buildStoryItems({ userStories: userStories.data ?? [], + standaloneStories: mapStandalone(standaloneStories.data), sharedStories: sharedStories.data ?? [], currentUserId, currentUserName, }); - }, [showArchived, userStories.data, sharedStories.data, archivedStories.data, currentUserId, currentUserName]); + }, [ + showArchived, + userStories.data, + standaloneStories.data, + sharedStories.data, + archivedStories.data, + archivedStandaloneStories.data, + currentUserId, + currentUserName, + ]); const filteredItems = useMemo(() => { return filterStories(allItems, searchQuery); @@ -64,7 +101,9 @@ function StoriesPage() { const groups = useMemo(() => groupStories(filteredItems, groupBy), [filteredItems, groupBy]); - const isLoading = showArchived ? archivedStories.isLoading : userStories.isLoading || sharedStories.isLoading; + const isLoading = showArchived + ? archivedStories.isLoading || archivedStandaloneStories.isLoading + : userStories.isLoading || standaloneStories.isLoading || sharedStories.isLoading; const isEmpty = allItems.length === 0 && !isLoading; function handleDisplayChange(mode: DisplayMode) { diff --git a/apps/frontend/src/routes/_sidebar-layout.stories.shared.$shareId.tsx b/apps/frontend/src/routes/_sidebar-layout.stories.shared.$shareId.tsx index ce471c1a9..12df40c14 100644 --- a/apps/frontend/src/routes/_sidebar-layout.stories.shared.$shareId.tsx +++ b/apps/frontend/src/routes/_sidebar-layout.stories.shared.$shareId.tsx @@ -121,8 +121,13 @@ function SharedStoryPage() { )}
- - {isOwner ? ( + + {isOwner && story.chatId ? ( + ) : isOwner ? ( + ) : ( !isViewer && (
diff --git a/apps/frontend/src/routes/_sidebar-layout.stories.standalone.$storyId.tsx b/apps/frontend/src/routes/_sidebar-layout.stories.standalone.$storyId.tsx new file mode 100644 index 000000000..03c6ae245 --- /dev/null +++ b/apps/frontend/src/routes/_sidebar-layout.stories.standalone.$storyId.tsx @@ -0,0 +1,109 @@ +import { splitCodeIntoSegments } from '@nao/shared/story-segments'; +import { useMutation, useQueryClient, useSuspenseQuery } from '@tanstack/react-query'; +import { createFileRoute, useNavigate } from '@tanstack/react-router'; +import { MessageSquare, Loader2 } from 'lucide-react'; +import { useCallback, useMemo } from 'react'; +import type { ParsedChartBlock, ParsedTableBlock } from '@nao/shared/story-segments'; + +import type { SelectionData } from '@/components/highlight-bubble'; +import type { QueryDataMap } from '@/components/story-embeds'; +import { HighlightBubble } from '@/components/highlight-bubble'; +import { StoryDownload } from '@/components/story-download'; +import { StoryChartEmbed, StoryTableEmbed } from '@/components/story-embeds'; +import { SegmentList } from '@/components/story-rendering'; +import { Button } from '@/components/ui/button'; +import { SelectionProvider } from '@/contexts/text-selection'; +import { chatPendingCitationStore } from '@/stores/chat-pending-citation'; +import { trpc } from '@/main'; + +export const Route = createFileRoute('/_sidebar-layout/stories/standalone/$storyId')({ + component: StandaloneStoryPage, +}); + +function StandaloneStoryPage() { + const { storyId } = Route.useParams(); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + + const { data: story } = useSuspenseQuery(trpc.story.getStandalone.queryOptions({ storyId })); + + const openStandaloneMutation = useMutation( + trpc.chatFork.openStandalone.mutationOptions({ + onSuccess: ({ chatId }) => { + queryClient.invalidateQueries({ queryKey: trpc.story.listAll.queryKey() }); + queryClient.invalidateQueries({ queryKey: trpc.story.listStandalone.queryKey() }); + navigate({ to: '/$chatId', params: { chatId }, state: { openStorySlug: story.slug } }); + }, + }), + ); + + const handleSelectionAsk = useCallback( + (data: SelectionData) => { + if (!story.chatId) { + return; + } + chatPendingCitationStore.set({ chatId: story.chatId, storySlug: story.slug, ...data }); + navigate({ to: '/$chatId', params: { chatId: story.chatId } }); + }, + [navigate, story.chatId, story.slug], + ); + + const handleOpenChat = useCallback(() => { + if (story.chatId) { + navigate({ to: '/$chatId', params: { chatId: story.chatId }, state: { openStorySlug: story.slug } }); + } else { + openStandaloneMutation.mutate({ storyId }); + } + }, [story.chatId, story.slug, storyId, navigate, openStandaloneMutation]); + + return ( +
+
+

{story.title}

+
+ + +
+
+ + + + +
+ ); +} + +function StandaloneStoryContent({ code, queryData }: { code: string; queryData: QueryDataMap | null }) { + const segments = useMemo(() => splitCodeIntoSegments(code), [code]); + + const renderChart = useCallback( + (chart: ParsedChartBlock) => , + [queryData], + ); + + const renderTable = useCallback( + (table: ParsedTableBlock) => , + [queryData], + ); + + return ( +
+
+ +
+
+ ); +} diff --git a/apps/frontend/src/routes/login.tsx b/apps/frontend/src/routes/login.tsx index 57c497697..125b75abe 100644 --- a/apps/frontend/src/routes/login.tsx +++ b/apps/frontend/src/routes/login.tsx @@ -13,6 +13,14 @@ export const Route = createFileRoute('/login')({ component: Login, }); +function buildMcpAuthorizeUrl() { + const params = new URLSearchParams(window.location.search); + if (!params.has('client_id')) { + return null; + } + return `/api/auth/mcp/authorize${window.location.search}`; +} + function Login() { const navigate = useNavigate(); const { error: oauthError } = Route.useSearch(); @@ -21,12 +29,20 @@ function Login() { const config = useQuery(trpc.system.getPublicConfig.queryOptions()); const isCloud = config.data?.naoMode === 'cloud'; + const mcpAuthorizeUrl = buildMcpAuthorizeUrl(); + const form = useForm({ defaultValues: { email: '', password: '' }, onSubmit: async ({ value }) => { setServerError(undefined); await signIn.email(value, { - onSuccess: () => navigate({ to: '/' }), + onSuccess: () => { + if (mcpAuthorizeUrl) { + window.location.href = mcpAuthorizeUrl; + } else { + navigate({ to: '/' }); + } + }, onError: (err) => setServerError(err.error.message), }); }, @@ -39,6 +55,7 @@ function Login() { submitText='Log In' serverError={serverError} displaySocialProviders={true} + socialCallbackUrl={mcpAuthorizeUrl ?? undefined} footer={ isCloud ? ( <> diff --git a/apps/frontend/vite.config.ts b/apps/frontend/vite.config.ts index 8dd41e6d5..9769a02bb 100644 --- a/apps/frontend/vite.config.ts +++ b/apps/frontend/vite.config.ts @@ -36,6 +36,12 @@ export default defineConfig({ '/api': { target: 'http://localhost:5005', }, + '/mcp': { + target: 'http://localhost:5005', + }, + '/.well-known': { + target: 'http://localhost:5005', + }, '/i/': { target: 'http://localhost:5005', }, diff --git a/package-lock.json b/package-lock.json index d6e4c4a84..b4941cde5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "@fastify/multipart": "^10.0.0", "@fastify/static": "^8.1.0", "@microsoft/microsoft-graph-client": "^3.0.7", + "@modelcontextprotocol/sdk": "^1.29.0", "@nao/shared": "*", "@openrouter/ai-sdk-provider": "^2.2.3", "@pydantic/monty": "^0.0.7", From dd79df2a1c2d63a78bad4228aee29445a39f40c5 Mon Sep 17 00:00:00 2001 From: socallmebertille Date: Sun, 3 May 2026 19:08:07 +0200 Subject: [PATCH 02/11] add the hook permission to handle the MCP settings --- apps/backend/src/mcp/tools/data.ts | 6 ++++++ apps/backend/src/mcp/tools/stories.ts | 2 +- apps/frontend/src/components/sidebar-settings-nav.tsx | 1 + .../_sidebar-layout.settings.project.mcp-endpoint.tsx | 8 ++++---- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/backend/src/mcp/tools/data.ts b/apps/backend/src/mcp/tools/data.ts index a088095c2..cc5360f08 100644 --- a/apps/backend/src/mcp/tools/data.ts +++ b/apps/backend/src/mcp/tools/data.ts @@ -5,6 +5,8 @@ import { z } from 'zod'; import { executeQuery } from '../../agents/tools/execute-sql'; import { getEnvVars, retrieveProjectById } from '../../queries/project.queries'; +import { hasFeature, LICENSE_FEATURES } from '../../services/license.service'; +import { getAzureAccessTokenForUser } from '../../services/microsoft-auth.service'; import { logger } from '../../utils/logger'; import type { McpContext } from '../logging'; import { withLogging } from '../logging'; @@ -25,6 +27,9 @@ export function registerDataTools(server: McpServer, ctx: McpContext): void { try { const project = await retrieveProjectById(ctx.projectId); const envVars = await getEnvVars(ctx.projectId); + const azureAccessToken = (await hasFeature(LICENSE_FEATURES.sso)) + ? await getAzureAccessTokenForUser(ctx.userId) + : null; const cappedLimit = Math.min(limit, 1000); const result = await executeQuery( @@ -34,6 +39,7 @@ export function registerDataTools(server: McpServer, ctx: McpContext): void { chatId: '', agentSettings: null, envVars, + azureAccessToken, queryResults: new Map(), }, ); diff --git a/apps/backend/src/mcp/tools/stories.ts b/apps/backend/src/mcp/tools/stories.ts index 60b95f051..b76e05139 100644 --- a/apps/backend/src/mcp/tools/stories.ts +++ b/apps/backend/src/mcp/tools/stories.ts @@ -290,7 +290,7 @@ async function saveNewVersion( code: string, ): Promise<{ id: string; title: string; updatedAt: Date }> { if (story.chatId) { - await storyQueries.createVersion({ + await storyQueries.createStoryVersion({ chatId: story.chatId, slug: story.slug, title, diff --git a/apps/frontend/src/components/sidebar-settings-nav.tsx b/apps/frontend/src/components/sidebar-settings-nav.tsx index 511d969b8..c1d68b114 100644 --- a/apps/frontend/src/components/sidebar-settings-nav.tsx +++ b/apps/frontend/src/components/sidebar-settings-nav.tsx @@ -49,6 +49,7 @@ const settingsNavItems: NavItem[] = [ { label: 'MCP Endpoint', to: '/settings/mcp-endpoint', + visible: ({ isViewer }) => !isViewer, }, { label: 'Observability', diff --git a/apps/frontend/src/routes/_sidebar-layout.settings.project.mcp-endpoint.tsx b/apps/frontend/src/routes/_sidebar-layout.settings.project.mcp-endpoint.tsx index 5e68c7789..1aae58d3f 100644 --- a/apps/frontend/src/routes/_sidebar-layout.settings.project.mcp-endpoint.tsx +++ b/apps/frontend/src/routes/_sidebar-layout.settings.project.mcp-endpoint.tsx @@ -1,15 +1,15 @@ import { createFileRoute } from '@tanstack/react-router'; -import { useQuery } from '@tanstack/react-query'; import { McpEndpointSettings } from '@/components/settings/mcp-endpoint'; -import { trpc } from '@/main'; +import { usePermissions } from '@/hooks/use-permissions'; +import { requireNonViewer } from '@/lib/require-admin'; export const Route = createFileRoute('/_sidebar-layout/settings/project/mcp-endpoint')({ + beforeLoad: requireNonViewer, component: ProjectMcpEndpointPage, }); function ProjectMcpEndpointPage() { - const project = useQuery(trpc.project.getCurrent.queryOptions()); - const isAdmin = project.data?.userRole === 'admin'; + const { isAdmin } = usePermissions(); return ; } From 4ccd69c59420bb5f0ac7442cc300a55b8d9a86ea Mon Sep 17 00:00:00 2001 From: socallmebertille Date: Mon, 4 May 2026 10:09:07 +0200 Subject: [PATCH 03/11] adapt new permissions hook to the PR --- .../src/routes/_sidebar-layout.settings.mcp-endpoint.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/routes/_sidebar-layout.settings.mcp-endpoint.tsx b/apps/frontend/src/routes/_sidebar-layout.settings.mcp-endpoint.tsx index aa91a3472..4de4fd5be 100644 --- a/apps/frontend/src/routes/_sidebar-layout.settings.mcp-endpoint.tsx +++ b/apps/frontend/src/routes/_sidebar-layout.settings.mcp-endpoint.tsx @@ -2,6 +2,7 @@ import { createFileRoute } from '@tanstack/react-router'; import { useQuery } from '@tanstack/react-query'; import { McpEndpointSettings } from '@/components/settings/mcp-endpoint'; import { SettingsPageWrapper } from '@/components/ui/settings-card'; +import { usePermissions } from '@/hooks/use-permissions'; import { trpc } from '@/main'; export const Route = createFileRoute('/_sidebar-layout/settings/mcp-endpoint')({ @@ -9,9 +10,8 @@ export const Route = createFileRoute('/_sidebar-layout/settings/mcp-endpoint')({ }); function McpEndpointPage() { - const project = useQuery(trpc.project.getCurrent.queryOptions()); + const { isAdmin } = usePermissions(); const allProjects = useQuery(trpc.project.listForCurrentUser.queryOptions()); - const isAdmin = project.data?.userRole === 'admin'; return ( From 9f419a64703c9520661a352334450921854c0cb9 Mon Sep 17 00:00:00 2001 From: socallmebertille Date: Mon, 4 May 2026 12:34:37 +0200 Subject: [PATCH 04/11] handle fixes from cubic review and real-time update MCP settings on MCP client side too --- apps/backend/src/db/pg-schema.ts | 5 ++ apps/backend/src/db/sqlite-schema.ts | 6 +- apps/backend/src/mcp/logging.ts | 4 ++ apps/backend/src/mcp/routes.ts | 13 ++-- apps/backend/src/mcp/tools/agent.ts | 5 +- apps/backend/src/mcp/tools/data.ts | 13 ++-- apps/backend/src/mcp/tools/files.ts | 2 +- apps/backend/src/mcp/tools/stories.ts | 15 ++++- apps/backend/src/queries/story.queries.ts | 61 ++++++++++++++----- apps/backend/src/trpc/chat-fork.routes.ts | 2 +- apps/backend/src/trpc/mcp-endpoint.routes.ts | 5 +- apps/backend/src/trpc/story.routes.ts | 29 +++------ apps/frontend/src/components/auth-form.tsx | 2 +- .../src/components/auth-microsoft-button.tsx | 13 +++- apps/frontend/src/lib/microsoft-auth.ts | 4 +- 15 files changed, 125 insertions(+), 54 deletions(-) diff --git a/apps/backend/src/db/pg-schema.ts b/apps/backend/src/db/pg-schema.ts index f8632b87e..569292a37 100644 --- a/apps/backend/src/db/pg-schema.ts +++ b/apps/backend/src/db/pg-schema.ts @@ -13,6 +13,7 @@ import { text, timestamp, unique, + uniqueIndex, } from 'drizzle-orm/pg-core'; import { AgentSettings } from '../types/agent-settings'; @@ -512,7 +513,11 @@ export const story = pgTable( }, (t) => [ unique('story_chat_slug_unique').on(t.chatId, t.slug), + uniqueIndex('story_standalone_slug_unique') + .on(t.projectId, t.userId, t.slug) + .where(sql`${t.chatId} IS NULL`), index('story_chatId_idx').on(t.chatId), + index('story_projectId_idx').on(t.projectId), index('story_userId_idx').on(t.userId), ], ); diff --git a/apps/backend/src/db/sqlite-schema.ts b/apps/backend/src/db/sqlite-schema.ts index 18d546dd6..95607a0d5 100644 --- a/apps/backend/src/db/sqlite-schema.ts +++ b/apps/backend/src/db/sqlite-schema.ts @@ -2,7 +2,7 @@ import type { CitationData, LlmProvider } from '@nao/shared/types'; import { BUDGET_PERIODS, SHARE_VISIBILITY, USER_ROLES } from '@nao/shared/types'; import { type ProviderMetadata } from 'ai'; import { sql } from 'drizzle-orm'; -import { check, index, integer, primaryKey, sqliteTable, text, unique } from 'drizzle-orm/sqlite-core'; +import { check, index, integer, primaryKey, sqliteTable, text, unique, uniqueIndex } from 'drizzle-orm/sqlite-core'; import { AgentSettings } from '../types/agent-settings'; import { ForkMetadata, StopReason, ToolState, UIMessagePartType } from '../types/chat'; @@ -540,7 +540,11 @@ export const story = sqliteTable( }, (t) => [ unique('story_chat_slug_unique').on(t.chatId, t.slug), + uniqueIndex('story_standalone_slug_unique') + .on(t.projectId, t.userId, t.slug) + .where(sql`chat_id IS NULL`), index('story_chatId_idx').on(t.chatId), + index('story_projectId_idx').on(t.projectId), index('story_userId_idx').on(t.userId), ], ); diff --git a/apps/backend/src/mcp/logging.ts b/apps/backend/src/mcp/logging.ts index 02df728c4..e8eb7f3fc 100644 --- a/apps/backend/src/mcp/logging.ts +++ b/apps/backend/src/mcp/logging.ts @@ -24,6 +24,7 @@ export type ToolHandler = (args: T, extra: ToolExtra) => Promise; export const TOOL_MODE_MAP: Record = { ask_nao: 'agentModeEnabled', execute_sql: 'toolsModeEnabled', + build_chart: 'toolsModeEnabled', grep: 'toolsModeEnabled', ls: 'toolsModeEnabled', list_stories: 'objectsModeEnabled', @@ -49,6 +50,9 @@ export function withLogging(toolName: string, ctx: McpContext, handler: ToolH let result: ToolResult | undefined; try { result = await handler(args, extra); + if (result?.isError) { + success = false; + } return result; } catch (error) { success = false; diff --git a/apps/backend/src/mcp/routes.ts b/apps/backend/src/mcp/routes.ts index fa7d62594..94059a4db 100644 --- a/apps/backend/src/mcp/routes.ts +++ b/apps/backend/src/mcp/routes.ts @@ -26,7 +26,7 @@ export const mcpServerRoutes = async (app: App) => { const existingSessionId = request.headers['mcp-session-id'] as string | undefined; if (existingSessionId) { const session = sessions.get(existingSessionId); - if (session) { + if (session && session.userId === userId) { session.lastAccess = Date.now(); await session.transport.handleRequest(request.raw, reply.raw, request.body as Record); reply.hijack(); @@ -46,7 +46,7 @@ export const mcpServerRoutes = async (app: App) => { sessionIdGenerator: () => crypto.randomUUID(), enableJsonResponse: true, onsessioninitialized: (sessionId) => { - sessions.set(sessionId, { transport, server, userId, lastAccess: Date.now() }); + sessions.set(sessionId, { transport, server, userId, projectId, lastAccess: Date.now() }); }, onsessionclosed: (sessionId) => { sessions.delete(sessionId); @@ -71,7 +71,7 @@ export const mcpServerRoutes = async (app: App) => { } const session = sessions.get(sessionId); - if (!session) { + if (!session || session.userId !== userId) { return reply.status(404).send({ error: 'Session not found or expired.' }); } @@ -81,9 +81,14 @@ export const mcpServerRoutes = async (app: App) => { }); app.delete('/', async (request, reply) => { + const userId = await resolveUserId(request); + if (!userId) { + return replyUnauthorized(request, reply); + } + const sessionId = request.headers['mcp-session-id'] as string | undefined; const session = sessionId ? sessions.get(sessionId) : undefined; - if (!session) { + if (!session || session.userId !== userId) { return reply.status(400).send({ error: 'Invalid or missing session.' }); } diff --git a/apps/backend/src/mcp/tools/agent.ts b/apps/backend/src/mcp/tools/agent.ts index 71446eb17..107057e6e 100644 --- a/apps/backend/src/mcp/tools/agent.ts +++ b/apps/backend/src/mcp/tools/agent.ts @@ -64,7 +64,10 @@ export function registerAgentTools(server: McpServer, ctx: McpContext): void { source: 'tool', context: { question, userId: ctx.userId }, }); - return { content: [{ type: 'text' as const, text: `Nao agent error: ${message}` }], isError: true }; + return { + content: [{ type: 'text' as const, text: 'Nao agent failed to process the request.' }], + isError: true, + }; } }), ); diff --git a/apps/backend/src/mcp/tools/data.ts b/apps/backend/src/mcp/tools/data.ts index cc5360f08..8f749f307 100644 --- a/apps/backend/src/mcp/tools/data.ts +++ b/apps/backend/src/mcp/tools/data.ts @@ -20,7 +20,14 @@ export function registerDataTools(server: McpServer, ctx: McpContext): void { 'Run a SQL query against the connected data warehouse. Returns rows as JSON. The response includes a `query_id` — pass it to `build_chart` or reference it in story `` blocks. Use ask_nao instead if you want Nao to write the SQL for you.', inputSchema: { sql: z.string().describe('The SQL query to execute'), - limit: z.number().optional().default(100).describe('Max rows to return (default 100, max 1000)'), + limit: z + .number() + .int() + .min(1) + .max(1000) + .optional() + .default(100) + .describe('Max rows to return (default 100, min 1, max 1000)'), }, }, withLogging('execute_sql', ctx, async ({ sql, limit }) => { @@ -30,8 +37,6 @@ export function registerDataTools(server: McpServer, ctx: McpContext): void { const azureAccessToken = (await hasFeature(LICENSE_FEATURES.sso)) ? await getAzureAccessTokenForUser(ctx.userId) : null; - const cappedLimit = Math.min(limit, 1000); - const result = await executeQuery( { sql_query: sql }, { @@ -44,7 +49,7 @@ export function registerDataTools(server: McpServer, ctx: McpContext): void { }, ); - const rows = result.data.slice(0, cappedLimit); + const rows = result.data.slice(0, limit); const queryId = `query_${crypto.randomUUID().slice(0, 8)}`; const output = { query_id: queryId, columns: result.columns, row_count: rows.length, data: rows }; return { diff --git a/apps/backend/src/mcp/tools/files.ts b/apps/backend/src/mcp/tools/files.ts index beaea0279..2f4023b06 100644 --- a/apps/backend/src/mcp/tools/files.ts +++ b/apps/backend/src/mcp/tools/files.ts @@ -42,7 +42,7 @@ export function registerFileTools(server: McpServer, ctx: McpContext): void { glob, case_insensitive, context_lines, - max_results: Math.min(max_results ?? 100, 500), + max_results: Math.min(Math.max(Math.floor(max_results ?? 100), 1), 500), }, makeExecutionOptions(project.path!), ); diff --git a/apps/backend/src/mcp/tools/stories.ts b/apps/backend/src/mcp/tools/stories.ts index b76e05139..9217778ae 100644 --- a/apps/backend/src/mcp/tools/stories.ts +++ b/apps/backend/src/mcp/tools/stories.ts @@ -109,7 +109,7 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { }, withLogging('create_story', ctx, async ({ title, content, query_data }) => { try { - const slug = generateSlug(title); + const slug = await getUniqueStandaloneSlug(ctx, title); const code = content ?? `# ${title}\n`; const version = await storyQueries.createStandaloneVersion({ @@ -260,7 +260,18 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { ); } -export function generateSlug(title: string): string { +async function getUniqueStandaloneSlug(ctx: McpContext, title: string): Promise { + const baseSlug = generateSlug(title); + let candidate = baseSlug; + let suffix = 2; + while (await storyQueries.getStandaloneStoryByUserAndSlug(ctx.userId, ctx.projectId, candidate)) { + candidate = `${baseSlug}-${suffix}`; + suffix += 1; + } + return candidate; +} + +function generateSlug(title: string): string { return ( title .toLowerCase() diff --git a/apps/backend/src/queries/story.queries.ts b/apps/backend/src/queries/story.queries.ts index 39727c9e9..5651dff82 100644 --- a/apps/backend/src/queries/story.queries.ts +++ b/apps/backend/src/queries/story.queries.ts @@ -31,6 +31,21 @@ export async function getStoryByChatAndSlug(chatId: string, slug: string): Promi return row ?? null; } +export async function getStoryOwnerId(storyId: string): Promise { + const [row] = await db + .select({ + storyUserId: s.story.userId, + chatUserId: s.chat.userId, + }) + .from(s.story) + .leftJoin(s.chat, eq(s.story.chatId, s.chat.id)) + .where(eq(s.story.id, storyId)) + .limit(1) + .execute(); + + return row?.chatUserId ?? row?.storyUserId ?? undefined; +} + export async function getStoryByIdForUser(storyId: string, userId: string): Promise { const latestVersions = latestVersionsSubquery(); @@ -188,21 +203,15 @@ export async function createStandaloneVersion(data: { action: 'create' | 'update' | 'replace'; source: 'assistant' | 'user'; }): Promise { - const existing = await getStandaloneStoryByUserAndSlug(data.userId, data.projectId, data.slug); + const story = await getOrCreateStandaloneStory({ + userId: data.userId, + projectId: data.projectId, + slug: data.slug, + title: data.title, + }); - let story: DBStory; - if (existing) { - story = existing; - if (story.title !== data.title) { - await db.update(s.story).set({ title: data.title }).where(eq(s.story.id, story.id)).execute(); - } - } else { - const [created] = await db - .insert(s.story) - .values({ projectId: data.projectId, userId: data.userId, slug: data.slug, title: data.title }) - .returning() - .execute(); - story = created; + if (story.title !== data.title) { + await db.update(s.story).set({ title: data.title }).where(eq(s.story.id, story.id)).execute(); } const nextVersion = db @@ -482,6 +491,30 @@ async function getOrCreateStory(data: { chatId: string; slug: string; title: str return row; } +async function getOrCreateStandaloneStory(data: { + userId: string; + projectId: string; + slug: string; + title: string; +}): Promise { + const existing = await getStandaloneStoryByUserAndSlug(data.userId, data.projectId, data.slug); + if (existing) { + return existing; + } + + await db + .insert(s.story) + .values({ projectId: data.projectId, userId: data.userId, slug: data.slug, title: data.title }) + .onConflictDoNothing() + .execute(); + + const row = await getStandaloneStoryByUserAndSlug(data.userId, data.projectId, data.slug); + if (!row) { + throw new Error(`Failed to create or retrieve standalone story: ${data.userId}/${data.projectId}/${data.slug}`); + } + return row; +} + async function getSqlQueriesByIds( chatId: string, queryIds: Set, diff --git a/apps/backend/src/trpc/chat-fork.routes.ts b/apps/backend/src/trpc/chat-fork.routes.ts index a24c3a69f..85951eca5 100644 --- a/apps/backend/src/trpc/chat-fork.routes.ts +++ b/apps/backend/src/trpc/chat-fork.routes.ts @@ -39,7 +39,7 @@ export const chatForkRoutes = { .input(z.object({ storyId: z.string() })) .mutation(async ({ input, ctx }): Promise<{ chatId: string }> => { const story = await storyQueries.getStoryByIdForUser(input.storyId, ctx.user.id); - if (!story) { + if (!story || story.projectId !== ctx.project.id) { throw new TRPCError({ code: 'NOT_FOUND', message: 'Story not found.' }); } if (story.chatId) { diff --git a/apps/backend/src/trpc/mcp-endpoint.routes.ts b/apps/backend/src/trpc/mcp-endpoint.routes.ts index e1791f7dd..94b6aeb0a 100644 --- a/apps/backend/src/trpc/mcp-endpoint.routes.ts +++ b/apps/backend/src/trpc/mcp-endpoint.routes.ts @@ -1,6 +1,7 @@ import { TRPCError } from '@trpc/server'; import { z } from 'zod/v4'; +import { closeProjectSessions } from '../mcp/server'; import * as mcpEndpointQueries from '../queries/mcp-endpoint.queries'; import { adminProtectedProcedure, projectProtectedProcedure, protectedProcedure, router } from './trpc'; @@ -19,7 +20,9 @@ export const mcpEndpointRoutes = router({ }), ) .mutation(async ({ ctx, input }) => { - return mcpEndpointQueries.updateMcpEndpointSettings(ctx.project.id, input); + const updated = await mcpEndpointQueries.updateMcpEndpointSettings(ctx.project.id, input); + await closeProjectSessions(ctx.project.id); + return updated; }), getCallLogs: adminProtectedProcedure.query(async ({ ctx }) => { diff --git a/apps/backend/src/trpc/story.routes.ts b/apps/backend/src/trpc/story.routes.ts index 49e068bd5..96ffcb153 100644 --- a/apps/backend/src/trpc/story.routes.ts +++ b/apps/backend/src/trpc/story.routes.ts @@ -11,6 +11,7 @@ import { extractStorySummary } from '../utils/story-summary'; import { ownedResourceProcedure, projectProtectedProcedure, protectedProcedure } from './trpc'; const chatOwnerProcedure = ownedResourceProcedure(chatQueries.getChatOwnerId, 'chat'); +const storyOwnerProcedure = ownedResourceProcedure(storyQueries.getStoryOwnerId, 'story'); export const storyRoutes = { listAll: protectedProcedure.query(async ({ ctx }) => { @@ -49,7 +50,7 @@ export const storyRoutes = { })); }), - getStandalone: projectProtectedProcedure.input(z.object({ storyId: z.string() })).query(async ({ input, ctx }) => { + getStandalone: storyOwnerProcedure.input(z.object({ storyId: z.string() })).query(async ({ input, ctx }) => { const story = await storyQueries.getStoryByIdForUser(input.storyId, ctx.user.id); if (!story) { throw new TRPCError({ code: 'NOT_FOUND', message: 'Story not found.' }); @@ -181,25 +182,13 @@ export const storyRoutes = { await storyQueries.unarchiveStory(input.chatId, input.storySlug); }), - archiveStandalone: projectProtectedProcedure - .input(z.object({ storyId: z.string() })) - .mutation(async ({ input, ctx }) => { - const story = await storyQueries.getStoryByIdForUser(input.storyId, ctx.user.id); - if (!story) { - throw new TRPCError({ code: 'NOT_FOUND', message: 'Story not found.' }); - } - await storyQueries.archiveByStoryId(story.id); - }), + archiveStandalone: storyOwnerProcedure.input(z.object({ storyId: z.string() })).mutation(async ({ input }) => { + await storyQueries.archiveByStoryId(input.storyId); + }), - unarchiveStandalone: projectProtectedProcedure - .input(z.object({ storyId: z.string() })) - .mutation(async ({ input, ctx }) => { - const story = await storyQueries.getStoryByIdForUser(input.storyId, ctx.user.id); - if (!story) { - throw new TRPCError({ code: 'NOT_FOUND', message: 'Story not found.' }); - } - await storyQueries.unarchiveByStoryId(story.id); - }), + unarchiveStandalone: storyOwnerProcedure.input(z.object({ storyId: z.string() })).mutation(async ({ input }) => { + await storyQueries.unarchiveByStoryId(input.storyId); + }), archiveMany: protectedProcedure .input(z.object({ stories: z.array(z.object({ chatId: z.string(), storySlug: z.string() })).min(1) })) @@ -216,7 +205,7 @@ export const storyRoutes = { await storyQueries.archiveManyStories(input.stories.map((s) => ({ chatId: s.chatId, slug: s.storySlug }))); }), - downloadStandalone: projectProtectedProcedure + downloadStandalone: storyOwnerProcedure .input(z.object({ storyId: z.string(), format: z.enum(DOWNLOAD_FORMATS) })) .query(async ({ input, ctx }) => { const story = await storyQueries.getStoryByIdForUser(input.storyId, ctx.user.id); diff --git a/apps/frontend/src/components/auth-form.tsx b/apps/frontend/src/components/auth-form.tsx index 4405139a4..d12a1b4ff 100644 --- a/apps/frontend/src/components/auth-form.tsx +++ b/apps/frontend/src/components/auth-form.tsx @@ -68,7 +68,7 @@ export function AuthForm({ Continue with GitHub )} - {isMicrosoftSetup && } + {isMicrosoftSetup && }
diff --git a/apps/frontend/src/components/auth-microsoft-button.tsx b/apps/frontend/src/components/auth-microsoft-button.tsx index b13cec523..06a1d5650 100644 --- a/apps/frontend/src/components/auth-microsoft-button.tsx +++ b/apps/frontend/src/components/auth-microsoft-button.tsx @@ -12,9 +12,18 @@ export function useIsMicrosoftSetup(): boolean { return Boolean(isMicrosoftSetup.data); } -export function MicrosoftSignInButton() { +interface MicrosoftSignInButtonProps { + callbackUrl?: string; +} + +export function MicrosoftSignInButton({ callbackUrl }: MicrosoftSignInButtonProps = {}) { return ( - diff --git a/apps/frontend/src/lib/microsoft-auth.ts b/apps/frontend/src/lib/microsoft-auth.ts index fd4973315..1e7f01b5f 100644 --- a/apps/frontend/src/lib/microsoft-auth.ts +++ b/apps/frontend/src/lib/microsoft-auth.ts @@ -2,10 +2,10 @@ import { authClient } from './auth-client'; -export async function handleMicrosoftSignIn(): Promise { +export async function handleMicrosoftSignIn(callbackURL = '/'): Promise { await authClient.signIn.social({ provider: 'microsoft', - callbackURL: '/', + callbackURL, errorCallbackURL: '/login', }); } From 6da4112655de8a324d7484d5947816ae5e541b7e Mon Sep 17 00:00:00 2001 From: socallmebertille Date: Mon, 4 May 2026 12:38:49 +0200 Subject: [PATCH 05/11] fix(mcp): expose closeProjectSessions and add projectId to McpSession Required by routes.ts and mcp-endpoint.routes.ts after the real-time MCP settings update; unblocks tsc/lint and the Docker frontend build. Co-authored-by: Cursor --- apps/backend/src/mcp/server.ts | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/apps/backend/src/mcp/server.ts b/apps/backend/src/mcp/server.ts index 7aef7813f..9309058ba 100644 --- a/apps/backend/src/mcp/server.ts +++ b/apps/backend/src/mcp/server.ts @@ -12,6 +12,7 @@ export interface McpSession { transport: StreamableHTTPServerTransport; server: McpServer; userId: string; + projectId: string; lastAccess: number; } @@ -49,10 +50,32 @@ export function createMcpServer(userId: string, projectId: string, settings: Mcp const server = new McpServer({ name: 'nao', version: '0.1.0' }, { capabilities: { tools: {} } }); const ctx = { userId, projectId, settings }; - registerAgentTools(server, ctx); - registerDataTools(server, ctx); - registerFileTools(server, ctx); - registerStoryTools(server, ctx); + if (settings.agentModeEnabled) { + registerAgentTools(server, ctx); + } + if (settings.toolsModeEnabled) { + registerDataTools(server, ctx); + registerFileTools(server, ctx); + } + if (settings.objectsModeEnabled) { + registerStoryTools(server, ctx); + } return server; } + +export async function closeProjectSessions(projectId: string): Promise { + const targets: McpSession[] = []; + for (const [id, session] of sessions) { + if (session.projectId === projectId) { + targets.push(session); + sessions.delete(id); + } + } + await Promise.all( + targets.map(async (session) => { + await session.transport.close().catch(() => {}); + await session.server.close().catch(() => {}); + }), + ); +} From c56ec52a0aa54eeac47a7d012891c78c43985701 Mon Sep 17 00:00:00 2001 From: socallmebertille Date: Mon, 4 May 2026 17:34:25 +0200 Subject: [PATCH 06/11] update some issues and improve the tools --- apps/backend/src/app.ts | 5 +- apps/backend/src/db/pg-schema.ts | 6 +- apps/backend/src/db/sqlite-schema.ts | 6 +- apps/backend/src/mcp/routes.ts | 13 +- apps/backend/src/mcp/tools/agent.ts | 23 +- apps/backend/src/mcp/tools/stories.ts | 203 +++++++++++++----- apps/backend/src/mcp/urls.ts | 22 ++ apps/backend/src/queries/chat.queries.ts | 2 +- apps/backend/src/queries/story.queries.ts | 47 ++++ apps/backend/src/trpc/chat-fork.routes.ts | 56 ++--- apps/backend/src/types/chat.ts | 2 +- apps/backend/src/utils/chat-message-story.ts | 64 ++++++ .../components/chat-messages/user-message.tsx | 2 + .../icons/model-context-protocol.svg | 1 + ...bar-layout.stories.standalone.$storyId.tsx | 29 ++- 15 files changed, 358 insertions(+), 123 deletions(-) create mode 100644 apps/backend/src/mcp/urls.ts create mode 100644 apps/backend/src/utils/chat-message-story.ts create mode 100644 apps/frontend/src/components/icons/model-context-protocol.svg diff --git a/apps/backend/src/app.ts b/apps/backend/src/app.ts index 11f9d86d8..87b642214 100644 --- a/apps/backend/src/app.ts +++ b/apps/backend/src/app.ts @@ -60,6 +60,7 @@ const app = fastify({ : true, bodyLimit: 35 * 1024 * 1024, // ~25 MB audio * 4/3 base64 overhead + JSON envelope routerOptions: { maxParamLength: 2048 }, + trustProxy: true, }).withTypeProvider(); export type App = typeof app; @@ -188,7 +189,7 @@ async function proxyToBetterAuth(url: string, request: { headers: Record { - const url = new URL('/api/auth/.well-known/oauth-protected-resource', `http://${request.headers.host}`); + const url = new URL('/api/auth/.well-known/oauth-protected-resource', env.BETTER_AUTH_URL); const response = await proxyToBetterAuth(url.toString(), request); reply.status(response.status); response.headers.forEach((value, key) => reply.header(key, value)); @@ -196,7 +197,7 @@ app.get('/.well-known/oauth-protected-resource', async (request, reply) => { }); app.get('/.well-known/oauth-authorization-server', async (request, reply) => { - const url = new URL('/api/auth/.well-known/oauth-authorization-server', `http://${request.headers.host}`); + const url = new URL('/api/auth/.well-known/oauth-authorization-server', env.BETTER_AUTH_URL); const response = await proxyToBetterAuth(url.toString(), request); reply.status(response.status); response.headers.forEach((value, key) => reply.header(key, value)); diff --git a/apps/backend/src/db/pg-schema.ts b/apps/backend/src/db/pg-schema.ts index 569292a37..f048b68b5 100644 --- a/apps/backend/src/db/pg-schema.ts +++ b/apps/backend/src/db/pg-schema.ts @@ -241,7 +241,7 @@ export const chatMessage = pgTable( llmProvider: text('llm_provider').$type(), llmModelId: text('llm_model_id'), supersededAt: timestamp('superseded_at'), - source: text('source', { enum: ['slack', 'teams', 'telegram', 'whatsapp', 'web'] }), + source: text('source', { enum: ['slack', 'teams', 'telegram', 'whatsapp', 'web', 'mcp'] }), isForked: boolean('isForked'), citation: jsonb('citation').$type(), createdAt: timestamp('created_at').defaultNow().notNull(), @@ -516,6 +516,10 @@ export const story = pgTable( uniqueIndex('story_standalone_slug_unique') .on(t.projectId, t.userId, t.slug) .where(sql`${t.chatId} IS NULL`), + check( + 'story_owner_required', + sql`${t.chatId} IS NOT NULL OR (${t.projectId} IS NOT NULL AND ${t.userId} IS NOT NULL)`, + ), index('story_chatId_idx').on(t.chatId), index('story_projectId_idx').on(t.projectId), index('story_userId_idx').on(t.userId), diff --git a/apps/backend/src/db/sqlite-schema.ts b/apps/backend/src/db/sqlite-schema.ts index 95607a0d5..e761bfe1d 100644 --- a/apps/backend/src/db/sqlite-schema.ts +++ b/apps/backend/src/db/sqlite-schema.ts @@ -251,7 +251,7 @@ export const chatMessage = sqliteTable( llmProvider: text('llm_provider').$type(), llmModelId: text('llm_model_id'), supersededAt: integer('superseded_at', { mode: 'timestamp_ms' }), - source: text('source', { enum: ['slack', 'teams', 'telegram', 'whatsapp', 'web'] }), + source: text('source', { enum: ['slack', 'teams', 'telegram', 'whatsapp', 'web', 'mcp'] }), isForked: integer('isForked', { mode: 'boolean' }), citation: text('citation', { mode: 'json' }).$type(), createdAt: integer('created_at', { mode: 'timestamp_ms' }) @@ -543,6 +543,10 @@ export const story = sqliteTable( uniqueIndex('story_standalone_slug_unique') .on(t.projectId, t.userId, t.slug) .where(sql`chat_id IS NULL`), + check( + 'story_owner_required', + sql`${t.chatId} IS NOT NULL OR (${t.projectId} IS NOT NULL AND ${t.userId} IS NOT NULL)`, + ), index('story_chatId_idx').on(t.chatId), index('story_projectId_idx').on(t.projectId), index('story_userId_idx').on(t.userId), diff --git a/apps/backend/src/mcp/routes.ts b/apps/backend/src/mcp/routes.ts index 94059a4db..8cd3a8205 100644 --- a/apps/backend/src/mcp/routes.ts +++ b/apps/backend/src/mcp/routes.ts @@ -1,13 +1,14 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; -import type { FastifyReply, FastifyRequest } from 'fastify'; +import type { FastifyReply } from 'fastify'; import type { App } from '../app'; +import { env } from '../env'; import { getMcpEndpointSettings } from '../queries/mcp-endpoint.queries'; import { resolveUserId } from './auth'; import { createMcpServer, resolveProjectId, sessions } from './server'; -function replyUnauthorized(request: FastifyRequest, reply: FastifyReply) { - const origin = `${request.protocol}://${request.headers.host ?? request.hostname}`; +function replyUnauthorized(reply: FastifyReply) { + const origin = env.BETTER_AUTH_URL.replace(/\/+$/, ''); const wwwAuth = `Bearer resource_metadata="${origin}/.well-known/oauth-protected-resource"`; return reply .status(401) @@ -20,7 +21,7 @@ export const mcpServerRoutes = async (app: App) => { app.post('/', async (request, reply) => { const userId = await resolveUserId(request); if (!userId) { - return replyUnauthorized(request, reply); + return replyUnauthorized(reply); } const existingSessionId = request.headers['mcp-session-id'] as string | undefined; @@ -62,7 +63,7 @@ export const mcpServerRoutes = async (app: App) => { app.get('/', async (request, reply) => { const userId = await resolveUserId(request); if (!userId) { - return replyUnauthorized(request, reply); + return replyUnauthorized(reply); } const sessionId = request.headers['mcp-session-id'] as string | undefined; @@ -83,7 +84,7 @@ export const mcpServerRoutes = async (app: App) => { app.delete('/', async (request, reply) => { const userId = await resolveUserId(request); if (!userId) { - return replyUnauthorized(request, reply); + return replyUnauthorized(reply); } const sessionId = request.headers['mcp-session-id'] as string | undefined; diff --git a/apps/backend/src/mcp/tools/agent.ts b/apps/backend/src/mcp/tools/agent.ts index 107057e6e..d789b3990 100644 --- a/apps/backend/src/mcp/tools/agent.ts +++ b/apps/backend/src/mcp/tools/agent.ts @@ -10,12 +10,19 @@ import type { UIMessage } from '../../types/chat'; import { logger } from '../../utils/logger'; import type { McpContext, ToolExtra } from '../logging'; import { withLogging } from '../logging'; +import { chatUrl } from '../urls'; export function registerAgentTools(server: McpServer, ctx: McpContext): void { server.registerTool( 'ask_nao', { - description: 'Ask nao an analytics question in natural language. Creates a chat that is visible in the UI.', + description: + 'Ask nao an analytics question in natural language. Creates a chat that is visible in the UI.\n\n' + + 'Use this for ad-hoc data exploration and Q&A. ' + + 'To create a persistent Nao Story (markdown dashboard with embedded charts/tables), do NOT ask nao in natural language — use `execute_sql` → `build_chart` → `create_story` instead. ' + + 'To browse or update existing stories, use `list_stories` / `get_story` / `update_story`.\n\n' + + 'Returns `chatId` and a `url` that opens the chat in the Nao UI — surface the URL to the user as a clickable link so they can jump to the conversation (and any rendered charts/tables). ' + + 'The returned `chatId` can also be passed to `create_story` as `chat_id` to attach a follow-up story to this conversation.', inputSchema: { question: z.string().describe('The analytics question'), conversation_id: z @@ -23,7 +30,8 @@ export function registerAgentTools(server: McpServer, ctx: McpContext): void { .optional() .describe( 'Existing chat ID to continue a conversation. Omit to start a new chat. ' + - 'Reuse the ID returned by a previous ask_nao call from the same conversation.', + 'Reuse the ID returned by a previous ask_nao call ONLY when the new question clearly builds on the previous nao exchange (same data, same topic). ' + + 'If the topic shifts or the prior nao reply was a refusal / off-topic, omit this to start a fresh chat — otherwise the follow-up inherits the prior context and may repeat the refusal.', ), }, }, @@ -51,12 +59,13 @@ export function registerAgentTools(server: McpServer, ctx: McpContext): void { tokenUsage: result.usage, }); + const url = chatUrl(chat.id); return { content: [ { type: 'text' as const, text: result.text }, - { type: 'text' as const, text: `\n\n[conversation_id: ${chat.id}]` }, + { type: 'text' as const, text: `\n\n[conversation_id: ${chat.id}]\n[chat_url: ${url}]` }, ], - toolOutput: { chatId: chat.id, text: result.text }, + toolOutput: { chatId: chat.id, text: result.text, url }, }; } catch (error) { const message = error instanceof Error ? error.message : String(error); @@ -83,6 +92,7 @@ async function buildChatContext( id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: question }], + source: 'mcp', }; if (conversationId) { @@ -98,7 +108,10 @@ async function buildChatContext( } const chatId = crypto.randomUUID(); - await chatQueries.createChat({ id: chatId, projectId, userId, title: question.slice(0, 80) }, { text: question }); + await chatQueries.createChat( + { id: chatId, projectId, userId, title: question.slice(0, 80) }, + { text: question, source: 'mcp' }, + ); return { chat: { id: chatId, projectId, userId }, uiMessages: [userMessage], diff --git a/apps/backend/src/mcp/tools/stories.ts b/apps/backend/src/mcp/tools/stories.ts index 9217778ae..9eed448e4 100644 --- a/apps/backend/src/mcp/tools/stories.ts +++ b/apps/backend/src/mcp/tools/stories.ts @@ -1,18 +1,22 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; +import * as chatQueries from '../../queries/chat.queries'; import type { UserStoryRow } from '../../queries/story.queries'; import * as storyQueries from '../../queries/story.queries'; +import { pinQueryDataToChat, pinStoryMessageToChat } from '../../utils/chat-message-story'; import { logger } from '../../utils/logger'; import type { McpContext } from '../logging'; import { withLogging } from '../logging'; +import { storyChatUrl, storyUrl } from '../urls'; export function registerStoryTools(server: McpServer, ctx: McpContext): void { server.registerTool( 'list_stories', { title: 'List Stories', - description: 'List analytics stories (dashboards/reports) in the current project.', + description: + 'List analytics stories (dashboards/reports) in the current project. Each story includes a `url` that opens the rendered story in the Nao UI, and a `chatUrl` that opens the underlying chat conversation (null for standalone stories).', inputSchema: { limit: z.number().optional().default(20).describe('Max stories to return (default 20, max 100)'), archived: z.boolean().optional().default(false).describe('Include archived stories'), @@ -24,12 +28,14 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { archived, limit, }); - const result = stories.map(({ id, title, createdAt, updatedAt, archivedAt }) => ({ - id, - title, - createdAt, - updatedAt, - archived: archivedAt !== null, + const result = stories.map((story) => ({ + id: story.id, + title: story.title, + createdAt: story.createdAt, + updatedAt: story.updatedAt, + archived: story.archivedAt !== null, + url: storyUrl(story), + chatUrl: storyChatUrl(story), })); return { content: [{ type: 'text' as const, text: JSON.stringify(result) }], toolOutput: result }; } catch (error) { @@ -44,7 +50,8 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { 'get_story', { title: 'Get Story', - description: 'Retrieve a full story including its latest content/code.', + description: + 'Retrieve a full story including its latest content/code. Returns a `url` that opens the rendered story in the Nao UI, and a `chatUrl` that opens the underlying chat conversation (null for standalone stories).', inputSchema: { story_id: z.string().describe('The story ID to retrieve'), }, @@ -66,6 +73,8 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { archived: story.archivedAt !== null, createdAt: story.createdAt, updatedAt: story.updatedAt, + url: storyUrl(story), + chatUrl: storyChatUrl(story), }; return { content: [{ type: 'text' as const, text: JSON.stringify(output) }], @@ -87,7 +96,7 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { { title: 'Create Story', description: - 'Create a new analytics story. Stories are markdown documents with embedded chart/table components rendered by the Nao UI.\n\nWorkflow for stories with charts:\n1. execute_sql → get rows + query_id\n2. build_chart → get a `` block string\n3. create_story → embed the block in `content`; pass SQL rows in `query_data`\n\nSupported blocks:\n- Charts: use `build_chart` to generate the correct `` block — do NOT write these manually.\n- Tables: `
`\n- Grids: `...blocks...` (1–4 columns)\n\nOmit `content` to create an empty story.', + 'Create a new analytics story. Stories are markdown documents with embedded chart/table components rendered by the Nao UI.\n\nWorkflow for stories with charts:\n1. execute_sql → get rows + query_id\n2. build_chart → get a `` block string\n3. create_story → embed the block in `content`; pass SQL rows in `query_data`\n\nSupported blocks:\n- Charts: use `build_chart` to generate the correct `` block — do NOT write these manually.\n- Tables: `
`\n- Grids: `...blocks...` (1–4 columns)\n\nOmit `content` to create an empty story.\n\nPass `chat_id` to attach the story to an existing chat (e.g. one returned by `ask_nao`). Omit it to create a standalone story listed at the project level.\n\nReturns a `url` that opens the rendered story in the Nao UI and a `chatUrl` that opens the underlying chat (null for standalone stories) — surface the relevant link to the user as a clickable link in your reply.', inputSchema: { title: z.string().describe('Story title'), content: z @@ -105,31 +114,43 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { .describe( 'Query results keyed by query_id (query_id → { columns, data }). Required for stories with or
blocks so the Nao UI can render data.', ), + chat_id: z + .string() + .optional() + .describe( + 'Attach the story to an existing chat (e.g. the chat ID returned by `ask_nao`). ' + + 'Omit to create a standalone story listed at the project level. ' + + 'When provided, the chat must belong to the current user.', + ), }, }, - withLogging('create_story', ctx, async ({ title, content, query_data }) => { + withLogging('create_story', ctx, async ({ title, content, query_data, chat_id }) => { try { - const slug = await getUniqueStandaloneSlug(ctx, title); + const slug = generateSlug(title); const code = content ?? `# ${title}\n`; + const story = chat_id + ? await createChatLinkedStory({ chatId: chat_id, slug, title, code, ctx }) + : await createStandaloneStory({ slug, title, code, ctx }); - const version = await storyQueries.createStandaloneVersion({ - userId: ctx.userId, - projectId: ctx.projectId, - slug, - title, - code, - action: 'create', - source: 'user', - }); + if ('error' in story) { + return { content: [{ type: 'text' as const, text: `Error: ${story.error}` }], isError: true }; + } - const story = await storyQueries.getStandaloneStoryByUserAndSlug(ctx.userId, ctx.projectId, slug); - if (query_data && story) { - await storyQueries.upsertStoryDataCacheByStoryId( - story.id, - query_data as Record, - ); + if (query_data) { + const typedQueryData = query_data as Record; + await storyQueries.upsertStoryDataCacheByStoryId(story.id, typedQueryData); + if (chat_id) { + await pinQueryDataToChat(chat_id, typedQueryData); + } } - const output = { id: story!.id, title: version.title, createdAt: story!.createdAt }; + const storyForUrl = { id: story.id, slug: story.slug, chatId: story.chatId }; + const output = { + id: story.id, + title: story.title, + createdAt: story.createdAt, + url: storyUrl(storyForUrl), + chatUrl: storyChatUrl(storyForUrl), + }; return { content: [{ type: 'text' as const, text: JSON.stringify(output) }], toolOutput: output, @@ -150,7 +171,7 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { { title: 'Update Story', description: - 'Update a story title and/or content. Omit fields to keep their current values.\n\nWhen adding or replacing charts, use `build_chart` first to generate the correct `` block, then pass it in `content`. Include the SQL rows for any new query_ids in `query_data`.', + 'Update a story title and/or content. Omit fields to keep their current values.\n\nWhen adding or replacing charts, use `build_chart` first to generate the correct `` block, then pass it in `content`. Include the SQL rows for any new query_ids in `query_data`.\n\nReturns a `url` that opens the rendered story in the Nao UI and a `chatUrl` that opens the underlying chat (null for standalone stories) — surface the relevant link to the user as a clickable link in your reply.', inputSchema: { story_id: z.string().describe('The story ID to update'), title: z.string().optional().describe('New title (omit to keep current)'), @@ -173,24 +194,30 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { const newTitle = title ?? story.title; const newCode = content ?? latestVersion?.code ?? `# ${newTitle}\n`; const updated = await saveNewVersion(story, ctx, newTitle, newCode); - if (query_data && !story.chatId) { - const storyForCache = - (await storyQueries.getStandaloneStoryByUserAndSlug(ctx.userId, ctx.projectId, story.slug)) ?? - story; - const existingCache = await storyQueries.getStoryDataCacheByStoryId(storyForCache.id); - const mergedQueryData = { - ...((existingCache?.queryData as Record< - string, - { data: unknown[]; columns: string[] } - > | null) ?? {}), - ...query_data, - }; - await storyQueries.upsertStoryDataCacheByStoryId( - storyForCache.id, - mergedQueryData as Record, - ); + const output = { ...updated, url: storyUrl(story), chatUrl: storyChatUrl(story) }; + if (query_data) { + const typedQueryData = query_data as Record; + if (story.chatId) { + await pinQueryDataToChat(story.chatId, typedQueryData); + } else { + const storyForCache = + (await storyQueries.getStandaloneStoryByUserAndSlug( + ctx.userId, + ctx.projectId, + story.slug, + )) ?? story; + const existingCache = await storyQueries.getStoryDataCacheByStoryId(storyForCache.id); + const mergedQueryData = { + ...((existingCache?.queryData as Record< + string, + { data: unknown[]; columns: string[] } + > | null) ?? {}), + ...typedQueryData, + }; + await storyQueries.upsertStoryDataCacheByStoryId(storyForCache.id, mergedQueryData); + } } - return { content: [{ type: 'text' as const, text: JSON.stringify(updated) }], toolOutput: updated }; + return { content: [{ type: 'text' as const, text: JSON.stringify(output) }], toolOutput: output }; } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error(`MCP update_story error: ${message}`, { @@ -260,17 +287,6 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { ); } -async function getUniqueStandaloneSlug(ctx: McpContext, title: string): Promise { - const baseSlug = generateSlug(title); - let candidate = baseSlug; - let suffix = 2; - while (await storyQueries.getStandaloneStoryByUserAndSlug(ctx.userId, ctx.projectId, candidate)) { - candidate = `${baseSlug}-${suffix}`; - suffix += 1; - } - return candidate; -} - function generateSlug(title: string): string { return ( title @@ -280,6 +296,81 @@ function generateSlug(title: string): string { ); } +type CreatedStory = { id: string; title: string; slug: string; chatId: string | null; createdAt: Date }; +type CreateStoryResult = CreatedStory | { error: string }; + +async function createStandaloneStory(args: { + slug: string; + title: string; + code: string; + ctx: McpContext; +}): Promise { + const story = await storyQueries.createStandaloneStory({ + userId: args.ctx.userId, + projectId: args.ctx.projectId, + slug: args.slug, + title: args.title, + code: args.code, + source: 'user', + }); + + if (!story) { + return { + error: `A story with title "${args.title}" already exists. Pick a different title or use update_story to modify the existing one.`, + }; + } + return { ...story, chatId: null }; +} + +async function createChatLinkedStory(args: { + chatId: string; + slug: string; + title: string; + code: string; + ctx: McpContext; +}): Promise { + const ownerId = await chatQueries.getChatOwnerId(args.chatId); + if (ownerId !== args.ctx.userId) { + return { error: `Chat not found: ${args.chatId}` }; + } + + const existing = await storyQueries.getStoryByChatAndSlug(args.chatId, args.slug); + if (existing) { + return { + error: `A story with title "${args.title}" already exists in this chat. Pick a different title or use update_story to modify the existing one.`, + }; + } + + const version = await storyQueries.createStoryVersion({ + chatId: args.chatId, + slug: args.slug, + title: args.title, + code: args.code, + action: 'create', + source: 'assistant', + }); + const created = await storyQueries.getStoryByChatAndSlug(args.chatId, args.slug); + if (!created) { + throw new Error(`Failed to retrieve created story: ${args.chatId}/${args.slug}`); + } + + await pinStoryMessageToChat({ + chatId: args.chatId, + slug: args.slug, + title: args.title, + code: args.code, + version: version.version, + }); + + return { + id: created.id, + title: created.title, + slug: created.slug, + chatId: created.chatId, + createdAt: created.createdAt, + }; +} + async function resolveStory(storyId: string, ctx: McpContext): Promise { const story = await storyQueries.getStoryByIdForUser(storyId, ctx.userId); if (!story) { diff --git a/apps/backend/src/mcp/urls.ts b/apps/backend/src/mcp/urls.ts new file mode 100644 index 000000000..55ab07d5b --- /dev/null +++ b/apps/backend/src/mcp/urls.ts @@ -0,0 +1,22 @@ +import { env } from '../env'; + +export function chatUrl(chatId: string): string { + return appUrl(`/${chatId}`); +} + +export function storyUrl(story: { id: string; chatId: string | null; slug: string }): string { + if (story.chatId) { + return appUrl(`/stories/preview/${story.chatId}/${story.slug}`); + } + return appUrl(`/stories/standalone/${story.id}`); +} + +export function storyChatUrl(story: { chatId: string | null }): string | null { + return story.chatId ? chatUrl(story.chatId) : null; +} + +function appUrl(path: string): string { + const base = env.BETTER_AUTH_URL.replace(/\/+$/, ''); + const suffix = path.startsWith('/') ? path : `/${path}`; + return `${base}${suffix}`; +} diff --git a/apps/backend/src/queries/chat.queries.ts b/apps/backend/src/queries/chat.queries.ts index 41868eb72..92d844e81 100644 --- a/apps/backend/src/queries/chat.queries.ts +++ b/apps/backend/src/queries/chat.queries.ts @@ -262,7 +262,7 @@ export const createChat = async ( newChat: NewChat, newUserMessage: { text: string; - source?: 'slack' | 'teams' | 'telegram' | 'whatsapp' | 'web'; + source?: UIMessage['source']; citation?: CitationData; }, additionalParts: UIMessagePart[] = [], diff --git a/apps/backend/src/queries/story.queries.ts b/apps/backend/src/queries/story.queries.ts index 5651dff82..d4514899d 100644 --- a/apps/backend/src/queries/story.queries.ts +++ b/apps/backend/src/queries/story.queries.ts @@ -234,6 +234,53 @@ export async function createStandaloneVersion(data: { return { ...created, title: data.title }; } +export async function createStandaloneStory(data: { + userId: string; + projectId: string; + slug: string; + title: string; + code: string; + source: 'assistant' | 'user'; +}): Promise<{ id: string; slug: string; title: string; createdAt: Date; version: number } | null> { + return db.transaction(async (tx) => { + const [story] = await tx + .insert(s.story) + .values({ + projectId: data.projectId, + userId: data.userId, + slug: data.slug, + title: data.title, + }) + .onConflictDoNothing() + .returning() + .execute(); + + if (!story) { + return null; + } + + const [version] = await tx + .insert(s.storyVersion) + .values({ + storyId: story.id, + code: data.code, + action: 'create', + source: data.source, + version: 1, + }) + .returning() + .execute(); + + return { + id: story.id, + slug: story.slug, + title: story.title, + createdAt: story.createdAt, + version: version.version, + }; + }); +} + export async function deleteStory(storyId: string): Promise { await db.delete(s.story).where(eq(s.story.id, storyId)).execute(); } diff --git a/apps/backend/src/trpc/chat-fork.routes.ts b/apps/backend/src/trpc/chat-fork.routes.ts index 85951eca5..e6a71f64b 100644 --- a/apps/backend/src/trpc/chat-fork.routes.ts +++ b/apps/backend/src/trpc/chat-fork.routes.ts @@ -7,7 +7,8 @@ import * as sharedChatQueries from '../queries/shared-chat.queries'; import * as sharedStoryQueries from '../queries/shared-story.queries'; import * as storyQueries from '../queries/story.queries'; import { compactionService } from '../services/compaction'; -import type { ForkMetadata, UIMessage, UIMessagePart } from '../types/chat'; +import type { ForkMetadata, UIMessage } from '../types/chat'; +import { buildQueryDataParts, pinStoryMessageToChat } from '../utils/chat-message-story'; import { canSendProcedure, projectProtectedProcedure, protectedProcedure } from './trpc'; const shareTypeSchema = z.enum(['chat', 'story']); @@ -58,7 +59,13 @@ export const chatForkRoutes = { const latestVersion = await storyQueries.getLatestVersionByStoryId(story.id); await storyQueries.assignChatToStory(story.id, chat.id); - await pinStoryMessageToChat(chat.id, story.slug, story.title, story.code, latestVersion?.version ?? 1); + await pinStoryMessageToChat({ + chatId: chat.id, + slug: story.slug, + title: story.title, + code: story.code, + version: latestVersion?.version ?? 1, + }); return { chatId: chat.id }; }), @@ -202,24 +209,10 @@ function buildSelectionContextMessage(sourceTitle: string, selection: SelectionI function buildQueryDataMessages( queryData: Record | null, ): Array> { - if (!queryData || Object.keys(queryData).length === 0) { + const parts = buildQueryDataParts(queryData); + if (parts.length === 0) { return []; } - - const parts: UIMessagePart[] = Object.entries(queryData).map( - ([queryId, { data, columns }]) => - ({ - type: 'tool-execute_sql', - toolName: 'execute_sql', - toolCallId: crypto.randomUUID(), - state: 'output-available', - input: { sql_query: '' }, - output: { id: queryId as `query_${string}`, data, columns, row_count: data.length }, - providerExecuted: false, - errorText: undefined, - }) as unknown as UIMessagePart, - ); - return [{ role: 'assistant', isForked: true, parts }]; } @@ -233,32 +226,7 @@ async function createStoryInFork(chatId: string, slug: string, title: string, co source: 'assistant', }); - await pinStoryMessageToChat(chatId, slug, title, code, version.version); -} - -async function pinStoryMessageToChat( - chatId: string, - slug: string, - title: string, - code: string, - versionNumber: number, -): Promise { - await chatQueries.upsertMessage({ - chatId, - role: 'assistant', - parts: [ - { - type: 'tool-story', - toolCallId: crypto.randomUUID(), - toolName: 'story', - state: 'output-available', - input: { action: 'create', id: slug, title, code }, - output: { _version: '1', success: true, id: slug, version: versionNumber, code, title }, - errorText: undefined, - providerExecuted: false, - } as UIMessagePart, - ], - }); + await pinStoryMessageToChat({ chatId, slug, title, code, version: version.version }); } async function copyStoriesToFork(sourceChatId: string, forkChatId: string): Promise { diff --git a/apps/backend/src/types/chat.ts b/apps/backend/src/types/chat.ts index aa350c5d5..7ca0cf7c6 100644 --- a/apps/backend/src/types/chat.ts +++ b/apps/backend/src/types/chat.ts @@ -45,7 +45,7 @@ export interface ChatListItem { export type UIMessage = UIGenericMessage & { feedback?: MessageFeedback; - source?: 'slack' | 'teams' | 'telegram' | 'whatsapp' | 'web'; + source?: 'slack' | 'teams' | 'telegram' | 'whatsapp' | 'web' | 'mcp'; isForked?: boolean; citation?: CitationData; }; diff --git a/apps/backend/src/utils/chat-message-story.ts b/apps/backend/src/utils/chat-message-story.ts new file mode 100644 index 000000000..d92c3c1c9 --- /dev/null +++ b/apps/backend/src/utils/chat-message-story.ts @@ -0,0 +1,64 @@ +import * as chatQueries from '../queries/chat.queries'; +import type { UIMessagePart } from '../types/chat'; + +export async function pinStoryMessageToChat(args: { + chatId: string; + slug: string; + title: string; + code: string; + version: number; +}): Promise { + const { chatId, slug, title, code, version } = args; + + await chatQueries.upsertMessage({ + chatId, + role: 'assistant', + parts: [ + { + type: 'tool-story', + toolCallId: crypto.randomUUID(), + toolName: 'story', + state: 'output-available', + input: { action: 'create', id: slug, title, code }, + output: { _version: '1', success: true, id: slug, version, code, title }, + errorText: undefined, + providerExecuted: false, + } as UIMessagePart, + ], + }); +} + +export type StoryQueryDataMap = Record; + +export async function pinQueryDataToChat(chatId: string, queryData: StoryQueryDataMap): Promise { + const parts = buildQueryDataParts(queryData); + if (parts.length === 0) { + return; + } + + await chatQueries.upsertMessage({ + chatId, + role: 'assistant', + isForked: true, + parts, + }); +} + +export function buildQueryDataParts(queryData: StoryQueryDataMap | null | undefined): UIMessagePart[] { + if (!queryData) { + return []; + } + return Object.entries(queryData).map( + ([queryId, { data, columns }]) => + ({ + type: 'tool-execute_sql', + toolName: 'execute_sql', + toolCallId: crypto.randomUUID(), + state: 'output-available', + input: { sql_query: '' }, + output: { id: queryId as `query_${string}`, data, columns, row_count: data.length }, + providerExecuted: false, + errorText: undefined, + }) as unknown as UIMessagePart, + ); +} diff --git a/apps/frontend/src/components/chat-messages/user-message.tsx b/apps/frontend/src/components/chat-messages/user-message.tsx index 9ad49da6c..cef430353 100644 --- a/apps/frontend/src/components/chat-messages/user-message.tsx +++ b/apps/frontend/src/components/chat-messages/user-message.tsx @@ -22,6 +22,7 @@ import { STORY_MENTION_ID } from '@/components/chat-input-prompt'; import StoryIcon from '@/components/ui/story-icon'; import SlackIcon from '@/components/icons/slack.svg'; import TeamsIcon from '@/components/icons/microsoft-teams.svg'; +import McpIcon from '@/components/icons/model-context-protocol.svg'; import TelegramIcon from '@/components/icons/telegram.svg'; import WhatsAppIcon from '@/components/icons/whatsapp.svg'; @@ -50,6 +51,7 @@ const MESSAGE_SOURCES = { teams: { icon: , label: 'sent in Teams' }, telegram: { icon: , label: 'sent in Telegram' }, whatsapp: { icon: , label: 'sent in WhatsApp' }, + mcp: { icon: , label: 'sent via MCP' }, } as const; function MessageSourceBadge({ source }: { source: UIMessage['source'] }) { diff --git a/apps/frontend/src/components/icons/model-context-protocol.svg b/apps/frontend/src/components/icons/model-context-protocol.svg new file mode 100644 index 000000000..7aae9e1a3 --- /dev/null +++ b/apps/frontend/src/components/icons/model-context-protocol.svg @@ -0,0 +1 @@ +Model Context Protocol \ No newline at end of file diff --git a/apps/frontend/src/routes/_sidebar-layout.stories.standalone.$storyId.tsx b/apps/frontend/src/routes/_sidebar-layout.stories.standalone.$storyId.tsx index 03c6ae245..9d4b7a837 100644 --- a/apps/frontend/src/routes/_sidebar-layout.stories.standalone.$storyId.tsx +++ b/apps/frontend/src/routes/_sidebar-layout.stories.standalone.$storyId.tsx @@ -1,5 +1,5 @@ import { splitCodeIntoSegments } from '@nao/shared/story-segments'; -import { useMutation, useQueryClient, useSuspenseQuery } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { createFileRoute, useNavigate } from '@tanstack/react-router'; import { MessageSquare, Loader2 } from 'lucide-react'; import { useCallback, useMemo } from 'react'; @@ -12,6 +12,7 @@ import { StoryDownload } from '@/components/story-download'; import { StoryChartEmbed, StoryTableEmbed } from '@/components/story-embeds'; import { SegmentList } from '@/components/story-rendering'; import { Button } from '@/components/ui/button'; +import { Spinner } from '@/components/ui/spinner'; import { SelectionProvider } from '@/contexts/text-selection'; import { chatPendingCitationStore } from '@/stores/chat-pending-citation'; import { trpc } from '@/main'; @@ -25,36 +26,52 @@ function StandaloneStoryPage() { const navigate = useNavigate(); const queryClient = useQueryClient(); - const { data: story } = useSuspenseQuery(trpc.story.getStandalone.queryOptions({ storyId })); + const storyQuery = useQuery(trpc.story.getStandalone.queryOptions({ storyId })); + const story = storyQuery.data; const openStandaloneMutation = useMutation( trpc.chatFork.openStandalone.mutationOptions({ onSuccess: ({ chatId }) => { queryClient.invalidateQueries({ queryKey: trpc.story.listAll.queryKey() }); queryClient.invalidateQueries({ queryKey: trpc.story.listStandalone.queryKey() }); - navigate({ to: '/$chatId', params: { chatId }, state: { openStorySlug: story.slug } }); + navigate({ to: '/$chatId', params: { chatId }, state: { openStorySlug: story?.slug } }); }, }), ); const handleSelectionAsk = useCallback( (data: SelectionData) => { - if (!story.chatId) { + if (!story?.chatId) { return; } chatPendingCitationStore.set({ chatId: story.chatId, storySlug: story.slug, ...data }); navigate({ to: '/$chatId', params: { chatId: story.chatId } }); }, - [navigate, story.chatId, story.slug], + [navigate, story?.chatId, story?.slug], ); const handleOpenChat = useCallback(() => { + if (!story) { + return; + } if (story.chatId) { navigate({ to: '/$chatId', params: { chatId: story.chatId }, state: { openStorySlug: story.slug } }); } else { openStandaloneMutation.mutate({ storyId }); } - }, [story.chatId, story.slug, storyId, navigate, openStandaloneMutation]); + }, [story, storyId, navigate, openStandaloneMutation]); + + if (storyQuery.isLoading) { + return ( +
+ +
+ ); + } + + if (!story) { + return
Not Found
; + } return (
From 63a19af3253085685a8954c6f849823eba5c5c1e Mon Sep 17 00:00:00 2001 From: socallmebertille Date: Tue, 5 May 2026 10:47:39 +0200 Subject: [PATCH 07/11] improve description of tools --- apps/frontend/src/components/settings/mcp-endpoint.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/src/components/settings/mcp-endpoint.tsx b/apps/frontend/src/components/settings/mcp-endpoint.tsx index 90a3cf6e8..4c0b59fbc 100644 --- a/apps/frontend/src/components/settings/mcp-endpoint.tsx +++ b/apps/frontend/src/components/settings/mcp-endpoint.tsx @@ -85,7 +85,7 @@ export function McpEndpointSettings({ isAdmin }: Props) { toggle('toolsModeEnabled', v)} disabled={!isAdmin || !enabled || pending} From 21de9970dacf03b070d9f2c8a674cdaa781b30e2 Mon Sep 17 00:00:00 2001 From: socallmebertille Date: Thu, 7 May 2026 16:00:57 +0200 Subject: [PATCH 08/11] fix from review done for first nao mcp version --- .../migrations-postgres/0039_mcp_endpoint.sql | 127 + .../meta/0039_snapshot.json | 4239 +++++++++++++++++ .../migrations-postgres/meta/_journal.json | 7 + .../migrations-sqlite/0039_mcp_endpoint.sql | 148 + .../migrations-sqlite/meta/0039_snapshot.json | 4020 ++++++++++++++++ .../migrations-sqlite/meta/_journal.json | 7 + apps/backend/package.json | 1 + apps/backend/src/agents/tools/index.ts | 22 +- apps/backend/src/app.ts | 53 +- apps/backend/src/auth.ts | 92 +- apps/backend/src/db/pg-schema.ts | 101 +- apps/backend/src/db/sqlite-schema.ts | 110 +- apps/backend/src/env.ts | 3 + apps/backend/src/mcp/auth.ts | 61 +- apps/backend/src/mcp/logging.ts | 20 +- apps/backend/src/mcp/routes.ts | 152 +- apps/backend/src/mcp/tools/agent.ts | 153 +- apps/backend/src/mcp/tools/data.ts | 131 +- apps/backend/src/mcp/tools/files.ts | 89 +- apps/backend/src/mcp/tools/stories.ts | 20 +- apps/backend/src/mcp/tools/wrap-agent-tool.ts | 67 + apps/backend/src/services/agent.ts | 74 +- apps/frontend/package.json | 1 + apps/frontend/src/lib/auth-client.ts | 2 + apps/frontend/src/routeTree.gen.ts | 21 + apps/frontend/src/routes/consent.tsx | 138 + apps/frontend/src/routes/login.tsx | 12 +- package-lock.json | 20 + 28 files changed, 9357 insertions(+), 534 deletions(-) create mode 100644 apps/backend/migrations-postgres/0039_mcp_endpoint.sql create mode 100644 apps/backend/migrations-postgres/meta/0039_snapshot.json create mode 100644 apps/backend/migrations-sqlite/0039_mcp_endpoint.sql create mode 100644 apps/backend/migrations-sqlite/meta/0039_snapshot.json create mode 100644 apps/backend/src/mcp/tools/wrap-agent-tool.ts create mode 100644 apps/frontend/src/routes/consent.tsx diff --git a/apps/backend/migrations-postgres/0039_mcp_endpoint.sql b/apps/backend/migrations-postgres/0039_mcp_endpoint.sql new file mode 100644 index 000000000..0314e8dc3 --- /dev/null +++ b/apps/backend/migrations-postgres/0039_mcp_endpoint.sql @@ -0,0 +1,127 @@ +CREATE TABLE "jwks" ( + "id" text PRIMARY KEY NOT NULL, + "public_key" text NOT NULL, + "private_key" text NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "expires_at" timestamp +); +--> statement-breakpoint +CREATE TABLE "mcp_call_log" ( + "id" text PRIMARY KEY NOT NULL, + "project_id" text NOT NULL, + "user_id" text NOT NULL, + "tool_name" text NOT NULL, + "duration_ms" integer, + "success" boolean NOT NULL, + "tool_input" jsonb, + "tool_output" jsonb, + "called_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "oauth_access_token" ( + "id" text PRIMARY KEY NOT NULL, + "token" text NOT NULL, + "client_id" text NOT NULL, + "session_id" text, + "user_id" text, + "reference_id" text, + "refresh_id" text, + "expires_at" timestamp NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "scopes" jsonb NOT NULL, + CONSTRAINT "oauth_access_token_token_unique" UNIQUE("token") +); +--> statement-breakpoint +CREATE TABLE "oauth_client" ( + "id" text PRIMARY KEY NOT NULL, + "client_id" text NOT NULL, + "client_secret" text, + "disabled" boolean DEFAULT false, + "skip_consent" boolean, + "enable_end_session" boolean, + "subject_type" text, + "scopes" jsonb, + "user_id" text, + "name" text, + "uri" text, + "icon" text, + "contacts" jsonb, + "tos" text, + "policy" text, + "software_id" text, + "software_version" text, + "software_statement" text, + "redirect_uris" jsonb NOT NULL, + "post_logout_redirect_uris" jsonb, + "token_endpoint_auth_method" text, + "grant_types" jsonb, + "response_types" jsonb, + "public" boolean, + "type" text, + "require_pkce" boolean, + "reference_id" text, + "metadata" jsonb, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "oauth_client_client_id_unique" UNIQUE("client_id") +); +--> statement-breakpoint +CREATE TABLE "oauth_consent" ( + "id" text PRIMARY KEY NOT NULL, + "client_id" text NOT NULL, + "user_id" text, + "reference_id" text, + "scopes" jsonb NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "oauth_refresh_token" ( + "id" text PRIMARY KEY NOT NULL, + "token" text NOT NULL, + "client_id" text NOT NULL, + "session_id" text, + "user_id" text NOT NULL, + "reference_id" text, + "expires_at" timestamp NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "revoked" timestamp, + "auth_time" timestamp, + "scopes" jsonb NOT NULL, + CONSTRAINT "oauth_refresh_token_token_unique" UNIQUE("token") +); +--> statement-breakpoint +ALTER TABLE "story" ALTER COLUMN "chat_id" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "project" ADD COLUMN "mcp_endpoint_settings" jsonb;--> statement-breakpoint +ALTER TABLE "story" ADD COLUMN "project_id" text;--> statement-breakpoint +ALTER TABLE "story" ADD COLUMN "user_id" text;--> statement-breakpoint +ALTER TABLE "mcp_call_log" ADD CONSTRAINT "mcp_call_log_project_id_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."project"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "mcp_call_log" ADD CONSTRAINT "mcp_call_log_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_client_id_oauth_client_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."oauth_client"("client_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_session_id_session_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."session"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_refresh_id_oauth_refresh_token_id_fk" FOREIGN KEY ("refresh_id") REFERENCES "public"."oauth_refresh_token"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_client" ADD CONSTRAINT "oauth_client_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_consent" ADD CONSTRAINT "oauth_consent_client_id_oauth_client_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."oauth_client"("client_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_consent" ADD CONSTRAINT "oauth_consent_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_client_id_oauth_client_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."oauth_client"("client_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_session_id_session_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."session"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "mcp_call_log_projectId_idx" ON "mcp_call_log" USING btree ("project_id");--> statement-breakpoint +CREATE INDEX "mcp_call_log_userId_idx" ON "mcp_call_log" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX "mcp_call_log_calledAt_idx" ON "mcp_call_log" USING btree ("called_at");--> statement-breakpoint +CREATE INDEX "oauth_access_token_clientId_idx" ON "oauth_access_token" USING btree ("client_id");--> statement-breakpoint +CREATE INDEX "oauth_access_token_userId_idx" ON "oauth_access_token" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX "oauth_access_token_refreshId_idx" ON "oauth_access_token" USING btree ("refresh_id");--> statement-breakpoint +CREATE INDEX "oauth_client_userId_idx" ON "oauth_client" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX "oauth_consent_clientId_idx" ON "oauth_consent" USING btree ("client_id");--> statement-breakpoint +CREATE INDEX "oauth_consent_userId_idx" ON "oauth_consent" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX "oauth_refresh_token_clientId_idx" ON "oauth_refresh_token" USING btree ("client_id");--> statement-breakpoint +CREATE INDEX "oauth_refresh_token_userId_idx" ON "oauth_refresh_token" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX "oauth_refresh_token_sessionId_idx" ON "oauth_refresh_token" USING btree ("session_id");--> statement-breakpoint +ALTER TABLE "story" ADD CONSTRAINT "story_project_id_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."project"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "story" ADD CONSTRAINT "story_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "story_standalone_slug_unique" ON "story" USING btree ("project_id","user_id","slug") WHERE "story"."chat_id" IS NULL;--> statement-breakpoint +CREATE INDEX "story_projectId_idx" ON "story" USING btree ("project_id");--> statement-breakpoint +CREATE INDEX "story_userId_idx" ON "story" USING btree ("user_id");--> statement-breakpoint +ALTER TABLE "story" ADD CONSTRAINT "story_owner_required" CHECK ("story"."chat_id" IS NOT NULL OR ("story"."project_id" IS NOT NULL AND "story"."user_id" IS NOT NULL)); \ No newline at end of file diff --git a/apps/backend/migrations-postgres/meta/0039_snapshot.json b/apps/backend/migrations-postgres/meta/0039_snapshot.json new file mode 100644 index 000000000..0d7394678 --- /dev/null +++ b/apps/backend/migrations-postgres/meta/0039_snapshot.json @@ -0,0 +1,4239 @@ +{ + "id": "b6ed1887-ed52-4f88-9341-5e678c3fcb60", + "prevId": "f4ab836b-5ee8-4e8e-87a0-f6edd502cfe5", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_key": { + "name": "api_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_prefix": { + "name": "key_prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "api_key_orgId_idx": { + "name": "api_key_orgId_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_key_org_id_organization_id_fk": { + "name": "api_key_org_id_organization_id_fk", + "tableFrom": "api_key", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_created_by_user_id_fk": { + "name": "api_key_created_by_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_hash_unique": { + "name": "api_key_key_hash_unique", + "nullsNotDistinct": false, + "columns": [ + "key_hash" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat": { + "name": "chat", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'New Conversation'" + }, + "is_starred": { + "name": "is_starred", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "slack_thread_id": { + "name": "slack_thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "teams_thread_id": { + "name": "teams_thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telegram_thread_id": { + "name": "telegram_thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "whatsapp_thread_id": { + "name": "whatsapp_thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fork_metadata": { + "name": "fork_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_userId_idx": { + "name": "chat_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_projectId_idx": { + "name": "chat_projectId_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_slack_thread_idx": { + "name": "chat_slack_thread_idx", + "columns": [ + { + "expression": "slack_thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_teams_thread_idx": { + "name": "chat_teams_thread_idx", + "columns": [ + { + "expression": "teams_thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_telegram_thread_idx": { + "name": "chat_telegram_thread_idx", + "columns": [ + { + "expression": "telegram_thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_whatsapp_thread_idx": { + "name": "chat_whatsapp_thread_idx", + "columns": [ + { + "expression": "whatsapp_thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_user_id_user_id_fk": { + "name": "chat_user_id_user_id_fk", + "tableFrom": "chat", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_project_id_project_id_fk": { + "name": "chat_project_id_project_id_fk", + "tableFrom": "chat", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_message": { + "name": "chat_message", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stop_reason": { + "name": "stop_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "llm_provider": { + "name": "llm_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "llm_model_id": { + "name": "llm_model_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "superseded_at": { + "name": "superseded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isForked": { + "name": "isForked", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "citation": { + "name": "citation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "input_total_tokens": { + "name": "input_total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_no_cache_tokens": { + "name": "input_no_cache_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_cache_read_tokens": { + "name": "input_cache_read_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_cache_write_tokens": { + "name": "input_cache_write_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_total_tokens": { + "name": "output_total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_text_tokens": { + "name": "output_text_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_reasoning_tokens": { + "name": "output_reasoning_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_tokens": { + "name": "total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "chat_message_chatId_idx": { + "name": "chat_message_chatId_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_message_createdAt_idx": { + "name": "chat_message_createdAt_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_message_chat_id_chat_id_fk": { + "name": "chat_message_chat_id_chat_id_fk", + "tableFrom": "chat_message", + "tableTo": "chat", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.jwks": { + "name": "jwks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.llm_inference": { + "name": "llm_inference", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "llm_provider": { + "name": "llm_provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "llm_model_id": { + "name": "llm_model_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_total_tokens": { + "name": "input_total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_no_cache_tokens": { + "name": "input_no_cache_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_cache_read_tokens": { + "name": "input_cache_read_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_cache_write_tokens": { + "name": "input_cache_write_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_total_tokens": { + "name": "output_total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_text_tokens": { + "name": "output_text_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_reasoning_tokens": { + "name": "output_reasoning_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_tokens": { + "name": "total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "llm_inference_projectId_idx": { + "name": "llm_inference_projectId_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "llm_inference_userId_idx": { + "name": "llm_inference_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "llm_inference_type_idx": { + "name": "llm_inference_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "llm_inference_project_id_project_id_fk": { + "name": "llm_inference_project_id_project_id_fk", + "tableFrom": "llm_inference", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "llm_inference_user_id_user_id_fk": { + "name": "llm_inference_user_id_user_id_fk", + "tableFrom": "llm_inference", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "llm_inference_chat_id_chat_id_fk": { + "name": "llm_inference_chat_id_chat_id_fk", + "tableFrom": "llm_inference", + "tableTo": "chat", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.log": { + "name": "log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context": { + "name": "context", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "log_createdAt_idx": { + "name": "log_createdAt_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "log_level_idx": { + "name": "log_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "log_projectId_idx": { + "name": "log_projectId_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "log_project_id_project_id_fk": { + "name": "log_project_id_project_id_fk", + "tableFrom": "log", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_call_log": { + "name": "mcp_call_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "tool_input": { + "name": "tool_input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tool_output": { + "name": "tool_output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "called_at": { + "name": "called_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_call_log_projectId_idx": { + "name": "mcp_call_log_projectId_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_call_log_userId_idx": { + "name": "mcp_call_log_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_call_log_calledAt_idx": { + "name": "mcp_call_log_calledAt_idx", + "columns": [ + { + "expression": "called_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_call_log_project_id_project_id_fk": { + "name": "mcp_call_log_project_id_project_id_fk", + "tableFrom": "mcp_call_log", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_call_log_user_id_user_id_fk": { + "name": "mcp_call_log_user_id_user_id_fk", + "tableFrom": "mcp_call_log", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memories": { + "name": "memories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "superseded_by": { + "name": "superseded_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memories_userId_idx": { + "name": "memories_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memories_chatId_idx": { + "name": "memories_chatId_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memories_supersededBy_idx": { + "name": "memories_supersededBy_idx", + "columns": [ + { + "expression": "superseded_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memories_user_id_user_id_fk": { + "name": "memories_user_id_user_id_fk", + "tableFrom": "memories", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "memories_chat_id_chat_id_fk": { + "name": "memories_chat_id_chat_id_fk", + "tableFrom": "memories", + "tableTo": "chat", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_feedback": { + "name": "message_feedback", + "schema": "", + "columns": { + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "explanation": { + "name": "explanation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "message_feedback_message_id_chat_message_id_fk": { + "name": "message_feedback_message_id_chat_message_id_fk", + "tableFrom": "message_feedback", + "tableTo": "chat_message", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_image": { + "name": "message_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "media_type": { + "name": "media_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_part": { + "name": "message_part", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reasoning_text": { + "name": "reasoning_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_state": { + "name": "tool_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_error_text": { + "name": "tool_error_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_input": { + "name": "tool_input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tool_raw_input": { + "name": "tool_raw_input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tool_output": { + "name": "tool_output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tool_approval_id": { + "name": "tool_approval_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_approval_approved": { + "name": "tool_approval_approved", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "tool_approval_reason": { + "name": "tool_approval_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_provider_metadata": { + "name": "tool_provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "media_type": { + "name": "media_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "image_id": { + "name": "image_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "parts_message_id_idx": { + "name": "parts_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "parts_message_id_order_idx": { + "name": "parts_message_id_order_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "message_part_message_id_chat_message_id_fk": { + "name": "message_part_message_id_chat_message_id_fk", + "tableFrom": "message_part", + "tableTo": "chat_message", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_part_image_id_message_image_id_fk": { + "name": "message_part_image_id_message_image_id_fk", + "tableFrom": "message_part", + "tableTo": "message_image", + "columnsFrom": [ + "image_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "message_part_tool_call_id_unique": { + "name": "message_part_tool_call_id_unique", + "nullsNotDistinct": false, + "columns": [ + "tool_call_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "text_required_if_type_is_text": { + "name": "text_required_if_type_is_text", + "value": "CASE WHEN \"message_part\".\"type\" = 'text' THEN \"message_part\".\"text\" IS NOT NULL ELSE TRUE END" + }, + "reasoning_text_required_if_type_is_reasoning": { + "name": "reasoning_text_required_if_type_is_reasoning", + "value": "CASE WHEN \"message_part\".\"type\" = 'reasoning' THEN \"message_part\".\"reasoning_text\" IS NOT NULL ELSE TRUE END" + }, + "tool_call_fields_required": { + "name": "tool_call_fields_required", + "value": "CASE WHEN \"message_part\".\"type\" LIKE 'tool-%' THEN \"message_part\".\"tool_call_id\" IS NOT NULL AND \"message_part\".\"tool_state\" IS NOT NULL ELSE TRUE END" + }, + "file_fields_required": { + "name": "file_fields_required", + "value": "CASE WHEN \"message_part\".\"type\" = 'file' THEN \"message_part\".\"media_type\" IS NOT NULL AND \"message_part\".\"image_id\" IS NOT NULL ELSE TRUE END" + } + }, + "isRLSEnabled": false + }, + "public.chart_image": { + "name": "chart_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chart_image_tool_call_id_unique": { + "name": "chart_image_tool_call_id_unique", + "nullsNotDistinct": false, + "columns": [ + "tool_call_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_access_token": { + "name": "oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_access_token_clientId_idx": { + "name": "oauth_access_token_clientId_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_userId_idx": { + "name": "oauth_access_token_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_refreshId_idx": { + "name": "oauth_access_token_refreshId_idx", + "columns": [ + { + "expression": "refresh_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_access_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_session_id_session_id_fk": { + "name": "oauth_access_token_session_id_session_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": { + "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_refresh_token", + "columnsFrom": [ + "refresh_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_token_unique": { + "name": "oauth_access_token_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "require_pkce": { + "name": "require_pkce", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "oauth_client_userId_idx": { + "name": "oauth_client_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_client_user_id_user_id_fk": { + "name": "oauth_client_user_id_user_id_fk", + "tableFrom": "oauth_client", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "nullsNotDistinct": false, + "columns": [ + "client_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_consent": { + "name": "oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "oauth_consent_clientId_idx": { + "name": "oauth_consent_clientId_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_consent_userId_idx": { + "name": "oauth_consent_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_consent_client_id_oauth_client_client_id_fk": { + "name": "oauth_consent_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_refresh_token": { + "name": "oauth_refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked": { + "name": "revoked", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "auth_time": { + "name": "auth_time", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_refresh_token_clientId_idx": { + "name": "oauth_refresh_token_clientId_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_refresh_token_userId_idx": { + "name": "oauth_refresh_token_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_refresh_token_sessionId_idx": { + "name": "oauth_refresh_token_sessionId_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_refresh_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_token_session_id_session_id_fk": { + "name": "oauth_refresh_token_session_id_session_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_refresh_token_user_id_user_id_fk": { + "name": "oauth_refresh_token_user_id_user_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_refresh_token_token_unique": { + "name": "oauth_refresh_token_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.org_member": { + "name": "org_member", + "schema": "", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "org_member_userId_idx": { + "name": "org_member_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "org_member_org_id_organization_id_fk": { + "name": "org_member_org_id_organization_id_fk", + "tableFrom": "org_member", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "org_member_user_id_user_id_fk": { + "name": "org_member_user_id_user_id_fk", + "tableFrom": "org_member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "org_member_org_id_user_id_pk": { + "name": "org_member_org_id_user_id_pk", + "columns": [ + "org_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "google_client_id": { + "name": "google_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "google_client_secret": { + "name": "google_client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "google_auth_domains": { + "name": "google_auth_domains", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_settings": { + "name": "agent_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enabled_tools": { + "name": "enabled_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "known_mcp_servers": { + "name": "known_mcp_servers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "env_vars": { + "name": "env_vars", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "slack_settings": { + "name": "slack_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "teams_settings": { + "name": "teams_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "telegram_settings": { + "name": "telegram_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "whatsapp_settings": { + "name": "whatsapp_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "mcp_endpoint_settings": { + "name": "mcp_endpoint_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_orgId_idx": { + "name": "project_orgId_idx", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_org_id_organization_id_fk": { + "name": "project_org_id_organization_id_fk", + "tableFrom": "project", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "local_project_path_required": { + "name": "local_project_path_required", + "value": "CASE WHEN \"type\" = 'local' THEN \"path\" IS NOT NULL ELSE TRUE END" + } + }, + "isRLSEnabled": false + }, + "public.project_llm_config": { + "name": "project_llm_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_key": { + "name": "api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credentials": { + "name": "credentials", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enabled_models": { + "name": "enabled_models", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_llm_config_projectId_idx": { + "name": "project_llm_config_projectId_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_llm_config_project_id_project_id_fk": { + "name": "project_llm_config_project_id_project_id_fk", + "tableFrom": "project_llm_config", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "project_llm_config_project_provider": { + "name": "project_llm_config_project_provider", + "nullsNotDistinct": false, + "columns": [ + "project_id", + "provider" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_member": { + "name": "project_member", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_member_userId_idx": { + "name": "project_member_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_member_project_id_project_id_fk": { + "name": "project_member_project_id_project_id_fk", + "tableFrom": "project_member", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_member_user_id_user_id_fk": { + "name": "project_member_user_id_user_id_fk", + "tableFrom": "project_member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_member_project_id_user_id_pk": { + "name": "project_member_project_id_user_id_pk", + "columns": [ + "project_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_provider_budget": { + "name": "project_provider_budget", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "limit_usd": { + "name": "limit_usd", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "period": { + "name": "period", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "current_period_start": { + "name": "current_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "notified_at": { + "name": "notified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_provider_budget_projectId_idx": { + "name": "project_provider_budget_projectId_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_provider_budget_project_id_project_id_fk": { + "name": "project_provider_budget_project_id_project_id_fk", + "tableFrom": "project_provider_budget", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "project_provider_budget_project_provider": { + "name": "project_provider_budget_project_provider", + "nullsNotDistinct": false, + "columns": [ + "project_id", + "provider" + ] + } + }, + "policies": {}, + "checkConstraints": { + "budget_period_valid": { + "name": "budget_period_valid", + "value": "\"project_provider_budget\".\"period\" IN ('day', 'week', 'month')" + } + }, + "isRLSEnabled": false + }, + "public.project_saved_prompt": { + "name": "project_saved_prompt", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_saved_prompt_projectId_idx": { + "name": "project_saved_prompt_projectId_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_saved_prompt_project_id_project_id_fk": { + "name": "project_saved_prompt_project_id_project_id_fk", + "tableFrom": "project_saved_prompt", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_whatsapp_link": { + "name": "project_whatsapp_link", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "whatsapp_user_id": { + "name": "whatsapp_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_whatsapp_link_userId_idx": { + "name": "project_whatsapp_link_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_whatsapp_link_project_id_project_id_fk": { + "name": "project_whatsapp_link_project_id_project_id_fk", + "tableFrom": "project_whatsapp_link", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_whatsapp_link_user_id_user_id_fk": { + "name": "project_whatsapp_link_user_id_user_id_fk", + "tableFrom": "project_whatsapp_link", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_whatsapp_link_project_id_whatsapp_user_id_pk": { + "name": "project_whatsapp_link_project_id_whatsapp_user_id_pk", + "columns": [ + "project_id", + "whatsapp_user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scheduled_job": { + "name": "scheduled_job", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "run_at": { + "name": "run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "cron": { + "name": "cron", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "locked_by": { + "name": "locked_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unique_key": { + "name": "unique_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scheduled_job_status_runAt_idx": { + "name": "scheduled_job_status_runAt_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scheduled_job_name_idx": { + "name": "scheduled_job_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "scheduled_job_unique_key_unique": { + "name": "scheduled_job_unique_key_unique", + "nullsNotDistinct": false, + "columns": [ + "unique_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_chat": { + "name": "shared_chat", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'project'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shared_chat_chat_id_chat_id_fk": { + "name": "shared_chat_chat_id_chat_id_fk", + "tableFrom": "shared_chat", + "tableTo": "chat", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "shared_chat_chatId_unique": { + "name": "shared_chat_chatId_unique", + "nullsNotDistinct": false, + "columns": [ + "chat_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_chat_access": { + "name": "shared_chat_access", + "schema": "", + "columns": { + "shared_chat_id": { + "name": "shared_chat_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "shared_chat_access_shared_chat_id_shared_chat_id_fk": { + "name": "shared_chat_access_shared_chat_id_shared_chat_id_fk", + "tableFrom": "shared_chat_access", + "tableTo": "shared_chat", + "columnsFrom": [ + "shared_chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_chat_access_user_id_user_id_fk": { + "name": "shared_chat_access_user_id_user_id_fk", + "tableFrom": "shared_chat_access", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_chat_access_shared_chat_id_user_id_pk": { + "name": "shared_chat_access_shared_chat_id_user_id_pk", + "columns": [ + "shared_chat_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_story": { + "name": "shared_story", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "story_id": { + "name": "story_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'project'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "shared_story_projectId_idx": { + "name": "shared_story_projectId_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "shared_story_storyId_idx": { + "name": "shared_story_storyId_idx", + "columns": [ + { + "expression": "story_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_story_story_id_story_id_fk": { + "name": "shared_story_story_id_story_id_fk", + "tableFrom": "shared_story", + "tableTo": "story", + "columnsFrom": [ + "story_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_story_project_id_project_id_fk": { + "name": "shared_story_project_id_project_id_fk", + "tableFrom": "shared_story", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_story_user_id_user_id_fk": { + "name": "shared_story_user_id_user_id_fk", + "tableFrom": "shared_story", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_story_access": { + "name": "shared_story_access", + "schema": "", + "columns": { + "shared_story_id": { + "name": "shared_story_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "shared_story_access_shared_story_id_shared_story_id_fk": { + "name": "shared_story_access_shared_story_id_shared_story_id_fk", + "tableFrom": "shared_story_access", + "tableTo": "shared_story", + "columnsFrom": [ + "shared_story_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_story_access_user_id_user_id_fk": { + "name": "shared_story_access_user_id_user_id_fk", + "tableFrom": "shared_story_access", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_story_access_shared_story_id_user_id_pk": { + "name": "shared_story_access_shared_story_id_user_id_pk", + "columns": [ + "shared_story_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.story": { + "name": "story", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_live": { + "name": "is_live", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_live_text_dynamic": { + "name": "is_live_text_dynamic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cache_schedule": { + "name": "cache_schedule", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_schedule_description": { + "name": "cache_schedule_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "story_standalone_slug_unique": { + "name": "story_standalone_slug_unique", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"story\".\"chat_id\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "story_chatId_idx": { + "name": "story_chatId_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "story_projectId_idx": { + "name": "story_projectId_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "story_userId_idx": { + "name": "story_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "story_chat_id_chat_id_fk": { + "name": "story_chat_id_chat_id_fk", + "tableFrom": "story", + "tableTo": "chat", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "story_project_id_project_id_fk": { + "name": "story_project_id_project_id_fk", + "tableFrom": "story", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "story_user_id_user_id_fk": { + "name": "story_user_id_user_id_fk", + "tableFrom": "story", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "story_chat_slug_unique": { + "name": "story_chat_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "chat_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": { + "story_owner_required": { + "name": "story_owner_required", + "value": "\"story\".\"chat_id\" IS NOT NULL OR (\"story\".\"project_id\" IS NOT NULL AND \"story\".\"user_id\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.story_data_cache": { + "name": "story_data_cache", + "schema": "", + "columns": { + "story_id": { + "name": "story_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "query_data": { + "name": "query_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "analysis_results": { + "name": "analysis_results", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cached_at": { + "name": "cached_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "story_data_cache_story_id_story_id_fk": { + "name": "story_data_cache_story_id_story_id_fk", + "tableFrom": "story_data_cache", + "tableTo": "story", + "columnsFrom": [ + "story_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.story_version": { + "name": "story_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "story_id": { + "name": "story_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "story_version_storyId_idx": { + "name": "story_version_storyId_idx", + "columns": [ + { + "expression": "story_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "story_version_story_id_story_id_fk": { + "name": "story_version_story_id_story_id_fk", + "tableFrom": "story_version", + "tableTo": "story", + "columnsFrom": [ + "story_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "story_version_story_version_unique": { + "name": "story_version_story_version_unique", + "nullsNotDistinct": false, + "columns": [ + "story_id", + "version" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requires_password_reset": { + "name": "requires_password_reset", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "memory_enabled": { + "name": "memory_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "messaging_provider_code": { + "name": "messaging_provider_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_access_token": { + "name": "github_access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "user_messaging_provider_code_unique": { + "name": "user_messaging_provider_code_unique", + "nullsNotDistinct": false, + "columns": [ + "messaging_provider_code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/backend/migrations-postgres/meta/_journal.json b/apps/backend/migrations-postgres/meta/_journal.json index 10f3ab0b9..7cb322f91 100644 --- a/apps/backend/migrations-postgres/meta/_journal.json +++ b/apps/backend/migrations-postgres/meta/_journal.json @@ -274,6 +274,13 @@ "when": 1777983477674, "tag": "0038_scheduling_table", "breakpoints": true + }, + { + "idx": 39, + "version": "7", + "when": 1778071315178, + "tag": "0039_mcp_endpoint", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/backend/migrations-sqlite/0039_mcp_endpoint.sql b/apps/backend/migrations-sqlite/0039_mcp_endpoint.sql new file mode 100644 index 000000000..9197fc1dc --- /dev/null +++ b/apps/backend/migrations-sqlite/0039_mcp_endpoint.sql @@ -0,0 +1,148 @@ +CREATE TABLE `jwks` ( + `id` text PRIMARY KEY NOT NULL, + `public_key` text NOT NULL, + `private_key` text NOT NULL, + `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, + `expires_at` integer +); +--> statement-breakpoint +CREATE TABLE `mcp_call_log` ( + `id` text PRIMARY KEY NOT NULL, + `project_id` text NOT NULL, + `user_id` text NOT NULL, + `tool_name` text NOT NULL, + `duration_ms` integer, + `success` integer NOT NULL, + `tool_input` text, + `tool_output` text, + `called_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, + FOREIGN KEY (`project_id`) REFERENCES `project`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE INDEX `mcp_call_log_projectId_idx` ON `mcp_call_log` (`project_id`);--> statement-breakpoint +CREATE INDEX `mcp_call_log_userId_idx` ON `mcp_call_log` (`user_id`);--> statement-breakpoint +CREATE INDEX `mcp_call_log_calledAt_idx` ON `mcp_call_log` (`called_at`);--> statement-breakpoint +CREATE TABLE `oauth_access_token` ( + `id` text PRIMARY KEY NOT NULL, + `token` text NOT NULL, + `client_id` text NOT NULL, + `session_id` text, + `user_id` text, + `reference_id` text, + `refresh_id` text, + `expires_at` integer NOT NULL, + `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, + `scopes` text NOT NULL, + FOREIGN KEY (`client_id`) REFERENCES `oauth_client`(`client_id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`session_id`) REFERENCES `session`(`id`) ON UPDATE no action ON DELETE set null, + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`refresh_id`) REFERENCES `oauth_refresh_token`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE UNIQUE INDEX `oauth_access_token_token_unique` ON `oauth_access_token` (`token`);--> statement-breakpoint +CREATE INDEX `oauth_access_token_clientId_idx` ON `oauth_access_token` (`client_id`);--> statement-breakpoint +CREATE INDEX `oauth_access_token_userId_idx` ON `oauth_access_token` (`user_id`);--> statement-breakpoint +CREATE INDEX `oauth_access_token_refreshId_idx` ON `oauth_access_token` (`refresh_id`);--> statement-breakpoint +CREATE TABLE `oauth_client` ( + `id` text PRIMARY KEY NOT NULL, + `client_id` text NOT NULL, + `client_secret` text, + `disabled` integer DEFAULT false, + `skip_consent` integer, + `enable_end_session` integer, + `subject_type` text, + `scopes` text, + `user_id` text, + `name` text, + `uri` text, + `icon` text, + `contacts` text, + `tos` text, + `policy` text, + `software_id` text, + `software_version` text, + `software_statement` text, + `redirect_uris` text NOT NULL, + `post_logout_redirect_uris` text, + `token_endpoint_auth_method` text, + `grant_types` text, + `response_types` text, + `public` integer, + `type` text, + `require_pkce` integer, + `reference_id` text, + `metadata` text, + `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, + `updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE UNIQUE INDEX `oauth_client_client_id_unique` ON `oauth_client` (`client_id`);--> statement-breakpoint +CREATE INDEX `oauth_client_userId_idx` ON `oauth_client` (`user_id`);--> statement-breakpoint +CREATE TABLE `oauth_consent` ( + `id` text PRIMARY KEY NOT NULL, + `client_id` text NOT NULL, + `user_id` text, + `reference_id` text, + `scopes` text NOT NULL, + `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, + `updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, + FOREIGN KEY (`client_id`) REFERENCES `oauth_client`(`client_id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE INDEX `oauth_consent_clientId_idx` ON `oauth_consent` (`client_id`);--> statement-breakpoint +CREATE INDEX `oauth_consent_userId_idx` ON `oauth_consent` (`user_id`);--> statement-breakpoint +CREATE TABLE `oauth_refresh_token` ( + `id` text PRIMARY KEY NOT NULL, + `token` text NOT NULL, + `client_id` text NOT NULL, + `session_id` text, + `user_id` text NOT NULL, + `reference_id` text, + `expires_at` integer NOT NULL, + `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, + `revoked` integer, + `auth_time` integer, + `scopes` text NOT NULL, + FOREIGN KEY (`client_id`) REFERENCES `oauth_client`(`client_id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`session_id`) REFERENCES `session`(`id`) ON UPDATE no action ON DELETE set null, + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE UNIQUE INDEX `oauth_refresh_token_token_unique` ON `oauth_refresh_token` (`token`);--> statement-breakpoint +CREATE INDEX `oauth_refresh_token_clientId_idx` ON `oauth_refresh_token` (`client_id`);--> statement-breakpoint +CREATE INDEX `oauth_refresh_token_userId_idx` ON `oauth_refresh_token` (`user_id`);--> statement-breakpoint +CREATE INDEX `oauth_refresh_token_sessionId_idx` ON `oauth_refresh_token` (`session_id`);--> statement-breakpoint +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_story` ( + `id` text PRIMARY KEY NOT NULL, + `chat_id` text, + `project_id` text, + `user_id` text, + `slug` text NOT NULL, + `title` text NOT NULL, + `is_live` integer DEFAULT false NOT NULL, + `is_live_text_dynamic` integer DEFAULT true NOT NULL, + `cache_schedule` text, + `cache_schedule_description` text, + `archived_at` integer, + `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, + `updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, + FOREIGN KEY (`chat_id`) REFERENCES `chat`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`project_id`) REFERENCES `project`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade, + CONSTRAINT "story_owner_required" CHECK(chat_id IS NOT NULL OR (project_id IS NOT NULL AND user_id IS NOT NULL)) +); +--> statement-breakpoint +INSERT INTO `__new_story`("id", "chat_id", "project_id", "user_id", "slug", "title", "is_live", "is_live_text_dynamic", "cache_schedule", "cache_schedule_description", "archived_at", "created_at", "updated_at") SELECT "id", "chat_id", "project_id", "user_id", "slug", "title", "is_live", "is_live_text_dynamic", "cache_schedule", "cache_schedule_description", "archived_at", "created_at", "updated_at" FROM `story`;--> statement-breakpoint +DROP TABLE `story`;--> statement-breakpoint +ALTER TABLE `__new_story` RENAME TO `story`;--> statement-breakpoint +PRAGMA foreign_keys=ON;--> statement-breakpoint +CREATE UNIQUE INDEX `story_standalone_slug_unique` ON `story` (`project_id`,`user_id`,`slug`) WHERE chat_id IS NULL;--> statement-breakpoint +CREATE INDEX `story_chatId_idx` ON `story` (`chat_id`);--> statement-breakpoint +CREATE INDEX `story_projectId_idx` ON `story` (`project_id`);--> statement-breakpoint +CREATE INDEX `story_userId_idx` ON `story` (`user_id`);--> statement-breakpoint +CREATE UNIQUE INDEX `story_chat_slug_unique` ON `story` (`chat_id`,`slug`);--> statement-breakpoint +ALTER TABLE `project` ADD `mcp_endpoint_settings` text; \ No newline at end of file diff --git a/apps/backend/migrations-sqlite/meta/0039_snapshot.json b/apps/backend/migrations-sqlite/meta/0039_snapshot.json new file mode 100644 index 000000000..2a9055fce --- /dev/null +++ b/apps/backend/migrations-sqlite/meta/0039_snapshot.json @@ -0,0 +1,4020 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "04ccf75a-6742-4741-8154-eed49dde2c88", + "prevId": "a4f26907-062e-48df-becb-a5c0f457b9ad", + "tables": { + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "api_key": { + "name": "api_key", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_prefix": { + "name": "key_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "api_key_key_hash_unique": { + "name": "api_key_key_hash_unique", + "columns": [ + "key_hash" + ], + "isUnique": true + }, + "api_key_orgId_idx": { + "name": "api_key_orgId_idx", + "columns": [ + "org_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "api_key_org_id_organization_id_fk": { + "name": "api_key_org_id_organization_id_fk", + "tableFrom": "api_key", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_created_by_user_id_fk": { + "name": "api_key_created_by_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "chat": { + "name": "chat", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'New Conversation'" + }, + "is_starred": { + "name": "is_starred", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "slack_thread_id": { + "name": "slack_thread_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "teams_thread_id": { + "name": "teams_thread_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telegram_thread_id": { + "name": "telegram_thread_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "whatsapp_thread_id": { + "name": "whatsapp_thread_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fork_metadata": { + "name": "fork_metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "chat_userId_idx": { + "name": "chat_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "chat_projectId_idx": { + "name": "chat_projectId_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "chat_slack_thread_idx": { + "name": "chat_slack_thread_idx", + "columns": [ + "slack_thread_id" + ], + "isUnique": false + }, + "chat_teams_thread_idx": { + "name": "chat_teams_thread_idx", + "columns": [ + "teams_thread_id" + ], + "isUnique": false + }, + "chat_telegram_thread_idx": { + "name": "chat_telegram_thread_idx", + "columns": [ + "telegram_thread_id" + ], + "isUnique": false + }, + "chat_whatsapp_thread_idx": { + "name": "chat_whatsapp_thread_idx", + "columns": [ + "whatsapp_thread_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "chat_user_id_user_id_fk": { + "name": "chat_user_id_user_id_fk", + "tableFrom": "chat", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_project_id_project_id_fk": { + "name": "chat_project_id_project_id_fk", + "tableFrom": "chat", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "chat_message": { + "name": "chat_message", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "stop_reason": { + "name": "stop_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "llm_provider": { + "name": "llm_provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "llm_model_id": { + "name": "llm_model_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "superseded_at": { + "name": "superseded_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "isForked": { + "name": "isForked", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "citation": { + "name": "citation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "input_total_tokens": { + "name": "input_total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "input_no_cache_tokens": { + "name": "input_no_cache_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "input_cache_read_tokens": { + "name": "input_cache_read_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "input_cache_write_tokens": { + "name": "input_cache_write_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output_total_tokens": { + "name": "output_total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output_text_tokens": { + "name": "output_text_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output_reasoning_tokens": { + "name": "output_reasoning_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_tokens": { + "name": "total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "chat_message_chatId_idx": { + "name": "chat_message_chatId_idx", + "columns": [ + "chat_id" + ], + "isUnique": false + }, + "chat_message_createdAt_idx": { + "name": "chat_message_createdAt_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "chat_message_chat_id_chat_id_fk": { + "name": "chat_message_chat_id_chat_id_fk", + "tableFrom": "chat_message", + "tableTo": "chat", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "jwks": { + "name": "jwks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "llm_inference": { + "name": "llm_inference", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "llm_provider": { + "name": "llm_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "llm_model_id": { + "name": "llm_model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "input_total_tokens": { + "name": "input_total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "input_no_cache_tokens": { + "name": "input_no_cache_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "input_cache_read_tokens": { + "name": "input_cache_read_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "input_cache_write_tokens": { + "name": "input_cache_write_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output_total_tokens": { + "name": "output_total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output_text_tokens": { + "name": "output_text_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output_reasoning_tokens": { + "name": "output_reasoning_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_tokens": { + "name": "total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "llm_inference_projectId_idx": { + "name": "llm_inference_projectId_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "llm_inference_userId_idx": { + "name": "llm_inference_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "llm_inference_type_idx": { + "name": "llm_inference_type_idx", + "columns": [ + "type" + ], + "isUnique": false + } + }, + "foreignKeys": { + "llm_inference_project_id_project_id_fk": { + "name": "llm_inference_project_id_project_id_fk", + "tableFrom": "llm_inference", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "llm_inference_user_id_user_id_fk": { + "name": "llm_inference_user_id_user_id_fk", + "tableFrom": "llm_inference", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "llm_inference_chat_id_chat_id_fk": { + "name": "llm_inference_chat_id_chat_id_fk", + "tableFrom": "llm_inference", + "tableTo": "chat", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "log": { + "name": "log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "context": { + "name": "context", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "log_createdAt_idx": { + "name": "log_createdAt_idx", + "columns": [ + "created_at" + ], + "isUnique": false + }, + "log_level_idx": { + "name": "log_level_idx", + "columns": [ + "level" + ], + "isUnique": false + }, + "log_projectId_idx": { + "name": "log_projectId_idx", + "columns": [ + "project_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "log_project_id_project_id_fk": { + "name": "log_project_id_project_id_fk", + "tableFrom": "log", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "mcp_call_log": { + "name": "mcp_call_log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "success": { + "name": "success", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tool_input": { + "name": "tool_input", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tool_output": { + "name": "tool_output", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "called_at": { + "name": "called_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "mcp_call_log_projectId_idx": { + "name": "mcp_call_log_projectId_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "mcp_call_log_userId_idx": { + "name": "mcp_call_log_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "mcp_call_log_calledAt_idx": { + "name": "mcp_call_log_calledAt_idx", + "columns": [ + "called_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "mcp_call_log_project_id_project_id_fk": { + "name": "mcp_call_log_project_id_project_id_fk", + "tableFrom": "mcp_call_log", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_call_log_user_id_user_id_fk": { + "name": "mcp_call_log_user_id_user_id_fk", + "tableFrom": "mcp_call_log", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "memories": { + "name": "memories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "superseded_by": { + "name": "superseded_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "memories_userId_idx": { + "name": "memories_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "memories_chatId_idx": { + "name": "memories_chatId_idx", + "columns": [ + "chat_id" + ], + "isUnique": false + }, + "memories_supersededBy_idx": { + "name": "memories_supersededBy_idx", + "columns": [ + "superseded_by" + ], + "isUnique": false + } + }, + "foreignKeys": { + "memories_user_id_user_id_fk": { + "name": "memories_user_id_user_id_fk", + "tableFrom": "memories", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "memories_chat_id_chat_id_fk": { + "name": "memories_chat_id_chat_id_fk", + "tableFrom": "memories", + "tableTo": "chat", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_feedback": { + "name": "message_feedback", + "columns": { + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "explanation": { + "name": "explanation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": {}, + "foreignKeys": { + "message_feedback_message_id_chat_message_id_fk": { + "name": "message_feedback_message_id_chat_message_id_fk", + "tableFrom": "message_feedback", + "tableTo": "chat_message", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_image": { + "name": "message_image", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "media_type": { + "name": "media_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_part": { + "name": "message_part", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reasoning_text": { + "name": "reasoning_text", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tool_state": { + "name": "tool_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tool_error_text": { + "name": "tool_error_text", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tool_input": { + "name": "tool_input", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tool_raw_input": { + "name": "tool_raw_input", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tool_output": { + "name": "tool_output", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tool_approval_id": { + "name": "tool_approval_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tool_approval_approved": { + "name": "tool_approval_approved", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tool_approval_reason": { + "name": "tool_approval_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tool_provider_metadata": { + "name": "tool_provider_metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "media_type": { + "name": "media_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image_id": { + "name": "image_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "message_part_tool_call_id_unique": { + "name": "message_part_tool_call_id_unique", + "columns": [ + "tool_call_id" + ], + "isUnique": true + }, + "parts_message_id_idx": { + "name": "parts_message_id_idx", + "columns": [ + "message_id" + ], + "isUnique": false + }, + "parts_message_id_order_idx": { + "name": "parts_message_id_order_idx", + "columns": [ + "message_id", + "order" + ], + "isUnique": false + } + }, + "foreignKeys": { + "message_part_message_id_chat_message_id_fk": { + "name": "message_part_message_id_chat_message_id_fk", + "tableFrom": "message_part", + "tableTo": "chat_message", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_part_image_id_message_image_id_fk": { + "name": "message_part_image_id_message_image_id_fk", + "tableFrom": "message_part", + "tableTo": "message_image", + "columnsFrom": [ + "image_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "text_required_if_type_is_text": { + "name": "text_required_if_type_is_text", + "value": "CASE WHEN type = 'text' THEN text IS NOT NULL ELSE TRUE END" + }, + "reasoning_text_required_if_type_is_reasoning": { + "name": "reasoning_text_required_if_type_is_reasoning", + "value": "CASE WHEN type = 'reasoning' THEN reasoning_text IS NOT NULL ELSE TRUE END" + }, + "tool_call_fields_required": { + "name": "tool_call_fields_required", + "value": "CASE WHEN type LIKE 'tool-%' THEN tool_call_id IS NOT NULL AND tool_state IS NOT NULL ELSE TRUE END" + }, + "file_fields_required": { + "name": "file_fields_required", + "value": "CASE WHEN type = 'file' THEN media_type IS NOT NULL ELSE TRUE END" + } + } + }, + "chart_image": { + "name": "chart_image", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "chart_image_tool_call_id_unique": { + "name": "chart_image_tool_call_id_unique", + "columns": [ + "tool_call_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_access_token": { + "name": "oauth_access_token", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "oauth_access_token_token_unique": { + "name": "oauth_access_token_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "oauth_access_token_clientId_idx": { + "name": "oauth_access_token_clientId_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauth_access_token_userId_idx": { + "name": "oauth_access_token_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "oauth_access_token_refreshId_idx": { + "name": "oauth_access_token_refreshId_idx", + "columns": [ + "refresh_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauth_access_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_session_id_session_id_fk": { + "name": "oauth_access_token_session_id_session_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": { + "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_refresh_token", + "columnsFrom": [ + "refresh_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_client": { + "name": "oauth_client", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disabled": { + "name": "disabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "contacts": { + "name": "contacts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "grant_types": { + "name": "grant_types", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "response_types": { + "name": "response_types", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public": { + "name": "public", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "require_pkce": { + "name": "require_pkce", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "columns": [ + "client_id" + ], + "isUnique": true + }, + "oauth_client_userId_idx": { + "name": "oauth_client_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauth_client_user_id_user_id_fk": { + "name": "oauth_client_user_id_user_id_fk", + "tableFrom": "oauth_client", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_consent": { + "name": "oauth_consent", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "oauth_consent_clientId_idx": { + "name": "oauth_consent_clientId_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauth_consent_userId_idx": { + "name": "oauth_consent_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauth_consent_client_id_oauth_client_client_id_fk": { + "name": "oauth_consent_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_refresh_token": { + "name": "oauth_refresh_token", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "revoked": { + "name": "revoked", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_time": { + "name": "auth_time", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "oauth_refresh_token_token_unique": { + "name": "oauth_refresh_token_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "oauth_refresh_token_clientId_idx": { + "name": "oauth_refresh_token_clientId_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauth_refresh_token_userId_idx": { + "name": "oauth_refresh_token_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "oauth_refresh_token_sessionId_idx": { + "name": "oauth_refresh_token_sessionId_idx", + "columns": [ + "session_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauth_refresh_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_token_session_id_session_id_fk": { + "name": "oauth_refresh_token_session_id_session_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_refresh_token_user_id_user_id_fk": { + "name": "oauth_refresh_token_user_id_user_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "org_member": { + "name": "org_member", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "org_member_userId_idx": { + "name": "org_member_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "org_member_org_id_organization_id_fk": { + "name": "org_member_org_id_organization_id_fk", + "tableFrom": "org_member", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "org_member_user_id_user_id_fk": { + "name": "org_member_user_id_user_id_fk", + "tableFrom": "org_member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "org_member_org_id_user_id_pk": { + "columns": [ + "org_id", + "user_id" + ], + "name": "org_member_org_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organization": { + "name": "organization", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "google_client_id": { + "name": "google_client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "google_client_secret": { + "name": "google_client_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "google_auth_domains": { + "name": "google_auth_domains", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project": { + "name": "project", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_settings": { + "name": "agent_settings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled_tools": { + "name": "enabled_tools", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "known_mcp_servers": { + "name": "known_mcp_servers", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "env_vars": { + "name": "env_vars", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "slack_settings": { + "name": "slack_settings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "teams_settings": { + "name": "teams_settings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "telegram_settings": { + "name": "telegram_settings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "whatsapp_settings": { + "name": "whatsapp_settings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mcp_endpoint_settings": { + "name": "mcp_endpoint_settings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "project_orgId_idx": { + "name": "project_orgId_idx", + "columns": [ + "org_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "project_org_id_organization_id_fk": { + "name": "project_org_id_organization_id_fk", + "tableFrom": "project", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "local_project_path_required": { + "name": "local_project_path_required", + "value": "CASE WHEN \"type\" = 'local' THEN \"path\" IS NOT NULL ELSE TRUE END" + } + } + }, + "project_llm_config": { + "name": "project_llm_config", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "api_key": { + "name": "api_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credentials": { + "name": "credentials", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled_models": { + "name": "enabled_models", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "project_llm_config_projectId_idx": { + "name": "project_llm_config_projectId_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "project_llm_config_project_provider": { + "name": "project_llm_config_project_provider", + "columns": [ + "project_id", + "provider" + ], + "isUnique": true + } + }, + "foreignKeys": { + "project_llm_config_project_id_project_id_fk": { + "name": "project_llm_config_project_id_project_id_fk", + "tableFrom": "project_llm_config", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_member": { + "name": "project_member", + "columns": { + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "project_member_userId_idx": { + "name": "project_member_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "project_member_project_id_project_id_fk": { + "name": "project_member_project_id_project_id_fk", + "tableFrom": "project_member", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_member_user_id_user_id_fk": { + "name": "project_member_user_id_user_id_fk", + "tableFrom": "project_member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_member_project_id_user_id_pk": { + "columns": [ + "project_id", + "user_id" + ], + "name": "project_member_project_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_provider_budget": { + "name": "project_provider_budget", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "limit_usd": { + "name": "limit_usd", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "period": { + "name": "period", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "current_period_start": { + "name": "current_period_start", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "notified_at": { + "name": "notified_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "project_provider_budget_projectId_idx": { + "name": "project_provider_budget_projectId_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "project_provider_budget_project_provider": { + "name": "project_provider_budget_project_provider", + "columns": [ + "project_id", + "provider" + ], + "isUnique": true + } + }, + "foreignKeys": { + "project_provider_budget_project_id_project_id_fk": { + "name": "project_provider_budget_project_id_project_id_fk", + "tableFrom": "project_provider_budget", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "budget_period_valid": { + "name": "budget_period_valid", + "value": "period IN ('day', 'week', 'month')" + } + } + }, + "project_saved_prompt": { + "name": "project_saved_prompt", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "project_saved_prompt_projectId_idx": { + "name": "project_saved_prompt_projectId_idx", + "columns": [ + "project_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "project_saved_prompt_project_id_project_id_fk": { + "name": "project_saved_prompt_project_id_project_id_fk", + "tableFrom": "project_saved_prompt", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_whatsapp_link": { + "name": "project_whatsapp_link", + "columns": { + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "whatsapp_user_id": { + "name": "whatsapp_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "project_whatsapp_link_userId_idx": { + "name": "project_whatsapp_link_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "project_whatsapp_link_project_id_project_id_fk": { + "name": "project_whatsapp_link_project_id_project_id_fk", + "tableFrom": "project_whatsapp_link", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_whatsapp_link_user_id_user_id_fk": { + "name": "project_whatsapp_link_user_id_user_id_fk", + "tableFrom": "project_whatsapp_link", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_whatsapp_link_project_id_whatsapp_user_id_pk": { + "columns": [ + "project_id", + "whatsapp_user_id" + ], + "name": "project_whatsapp_link_project_id_whatsapp_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "scheduled_job": { + "name": "scheduled_job", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "run_at": { + "name": "run_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cron": { + "name": "cron", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 5 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "locked_at": { + "name": "locked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "locked_by": { + "name": "locked_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unique_key": { + "name": "unique_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "scheduled_job_unique_key_unique": { + "name": "scheduled_job_unique_key_unique", + "columns": [ + "unique_key" + ], + "isUnique": true + }, + "scheduled_job_status_runAt_idx": { + "name": "scheduled_job_status_runAt_idx", + "columns": [ + "status", + "run_at" + ], + "isUnique": false + }, + "scheduled_job_name_idx": { + "name": "scheduled_job_name_idx", + "columns": [ + "name" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_token_unique": { + "name": "session_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_chat": { + "name": "shared_chat", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'project'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "shared_chat_chatId_unique": { + "name": "shared_chat_chatId_unique", + "columns": [ + "chat_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "shared_chat_chat_id_chat_id_fk": { + "name": "shared_chat_chat_id_chat_id_fk", + "tableFrom": "shared_chat", + "tableTo": "chat", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_chat_access": { + "name": "shared_chat_access", + "columns": { + "shared_chat_id": { + "name": "shared_chat_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "shared_chat_access_shared_chat_id_shared_chat_id_fk": { + "name": "shared_chat_access_shared_chat_id_shared_chat_id_fk", + "tableFrom": "shared_chat_access", + "tableTo": "shared_chat", + "columnsFrom": [ + "shared_chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_chat_access_user_id_user_id_fk": { + "name": "shared_chat_access_user_id_user_id_fk", + "tableFrom": "shared_chat_access", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_chat_access_shared_chat_id_user_id_pk": { + "columns": [ + "shared_chat_id", + "user_id" + ], + "name": "shared_chat_access_shared_chat_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_story": { + "name": "shared_story", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "story_id": { + "name": "story_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'project'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "shared_story_projectId_idx": { + "name": "shared_story_projectId_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "shared_story_storyId_idx": { + "name": "shared_story_storyId_idx", + "columns": [ + "story_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "shared_story_story_id_story_id_fk": { + "name": "shared_story_story_id_story_id_fk", + "tableFrom": "shared_story", + "tableTo": "story", + "columnsFrom": [ + "story_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_story_project_id_project_id_fk": { + "name": "shared_story_project_id_project_id_fk", + "tableFrom": "shared_story", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_story_user_id_user_id_fk": { + "name": "shared_story_user_id_user_id_fk", + "tableFrom": "shared_story", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_story_access": { + "name": "shared_story_access", + "columns": { + "shared_story_id": { + "name": "shared_story_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "shared_story_access_shared_story_id_shared_story_id_fk": { + "name": "shared_story_access_shared_story_id_shared_story_id_fk", + "tableFrom": "shared_story_access", + "tableTo": "shared_story", + "columnsFrom": [ + "shared_story_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_story_access_user_id_user_id_fk": { + "name": "shared_story_access_user_id_user_id_fk", + "tableFrom": "shared_story_access", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_story_access_shared_story_id_user_id_pk": { + "columns": [ + "shared_story_id", + "user_id" + ], + "name": "shared_story_access_shared_story_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "story": { + "name": "story", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_live": { + "name": "is_live", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_live_text_dynamic": { + "name": "is_live_text_dynamic", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "cache_schedule": { + "name": "cache_schedule", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_schedule_description": { + "name": "cache_schedule_description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "story_standalone_slug_unique": { + "name": "story_standalone_slug_unique", + "columns": [ + "project_id", + "user_id", + "slug" + ], + "isUnique": true, + "where": "chat_id IS NULL" + }, + "story_chatId_idx": { + "name": "story_chatId_idx", + "columns": [ + "chat_id" + ], + "isUnique": false + }, + "story_projectId_idx": { + "name": "story_projectId_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "story_userId_idx": { + "name": "story_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "story_chat_slug_unique": { + "name": "story_chat_slug_unique", + "columns": [ + "chat_id", + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": { + "story_chat_id_chat_id_fk": { + "name": "story_chat_id_chat_id_fk", + "tableFrom": "story", + "tableTo": "chat", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "story_project_id_project_id_fk": { + "name": "story_project_id_project_id_fk", + "tableFrom": "story", + "tableTo": "project", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "story_user_id_user_id_fk": { + "name": "story_user_id_user_id_fk", + "tableFrom": "story", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "story_owner_required": { + "name": "story_owner_required", + "value": "chat_id IS NOT NULL OR (project_id IS NOT NULL AND user_id IS NOT NULL)" + } + } + }, + "story_data_cache": { + "name": "story_data_cache", + "columns": { + "story_id": { + "name": "story_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "query_data": { + "name": "query_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "analysis_results": { + "name": "analysis_results", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cached_at": { + "name": "cached_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": {}, + "foreignKeys": { + "story_data_cache_story_id_story_id_fk": { + "name": "story_data_cache_story_id_story_id_fk", + "tableFrom": "story_data_cache", + "tableTo": "story", + "columnsFrom": [ + "story_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "story_version": { + "name": "story_version", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "story_id": { + "name": "story_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "story_version_storyId_idx": { + "name": "story_version_storyId_idx", + "columns": [ + "story_id" + ], + "isUnique": false + }, + "story_version_story_version_unique": { + "name": "story_version_story_version_unique", + "columns": [ + "story_id", + "version" + ], + "isUnique": true + } + }, + "foreignKeys": { + "story_version_story_id_story_id_fk": { + "name": "story_version_story_id_story_id_fk", + "tableFrom": "story_version", + "tableTo": "story", + "columnsFrom": [ + "story_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requires_password_reset": { + "name": "requires_password_reset", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "memory_enabled": { + "name": "memory_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "messaging_provider_code": { + "name": "messaging_provider_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_access_token": { + "name": "github_access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "user_messaging_provider_code_unique": { + "name": "user_messaging_provider_code_unique", + "columns": [ + "messaging_provider_code" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification": { + "name": "verification", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + "identifier" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/apps/backend/migrations-sqlite/meta/_journal.json b/apps/backend/migrations-sqlite/meta/_journal.json index 7cff3c9e6..1bb172d9c 100644 --- a/apps/backend/migrations-sqlite/meta/_journal.json +++ b/apps/backend/migrations-sqlite/meta/_journal.json @@ -274,6 +274,13 @@ "when": 1777983465688, "tag": "0038_scheduling_table", "breakpoints": true + }, + { + "idx": 39, + "version": "6", + "when": 1778071303698, + "tag": "0039_mcp_endpoint", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/backend/package.json b/apps/backend/package.json index f8e141bfe..13c264864 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -49,6 +49,7 @@ "@ai-sdk/openai": "^3.0.1", "@aws-sdk/credential-providers": "^3.1030.0", "@azure/identity": "^4.13.0", + "@better-auth/oauth-provider": "^1.6.3", "@chat-adapter/slack": "^4.15.0", "@chat-adapter/state-memory": "^4.15.0", "@chat-adapter/state-redis": "^4.23.0", diff --git a/apps/backend/src/agents/tools/index.ts b/apps/backend/src/agents/tools/index.ts index e9f6a584d..138914d64 100644 --- a/apps/backend/src/agents/tools/index.ts +++ b/apps/backend/src/agents/tools/index.ts @@ -29,24 +29,16 @@ export const tools = { suggest_follow_ups: suggestFollowUps, }; -export const getTools = ( - agentSettings: AgentSettings | null, - extraTools?: Record, - excludeTools?: readonly string[], -) => { +export const getTools = (agentSettings: AgentSettings | null, extraTools?: Record) => { const mcpTools = mcpService.getMcpTools(); + const { execute_python, execute_sandboxed_code, ...baseTools } = tools; - const include = (key: string) => !excludeTools?.includes(key); return { - ...Object.fromEntries(Object.entries(baseTools).filter(([k]) => include(k))), - ...Object.fromEntries(Object.entries(mcpTools).filter(([k]) => include(k))), - ...(agentSettings?.experimental?.pythonSandboxing && - execute_python && - include('execute_python') && { execute_python }), - ...(agentSettings?.experimental?.sandboxes && - execute_sandboxed_code && - include('execute_sandboxed_code') && { execute_sandboxed_code }), - ...Object.fromEntries(Object.entries(extraTools ?? {}).filter(([k]) => include(k))), + ...baseTools, + ...mcpTools, + ...(agentSettings?.experimental?.pythonSandboxing && execute_python && { execute_python }), + ...(agentSettings?.experimental?.sandboxes && execute_sandboxed_code && { execute_sandboxed_code }), + ...extraTools, }; }; diff --git a/apps/backend/src/app.ts b/apps/backend/src/app.ts index 87b642214..e45a403f2 100644 --- a/apps/backend/src/app.ts +++ b/apps/backend/src/app.ts @@ -2,7 +2,7 @@ import formbody from '@fastify/formbody'; import multipart from '@fastify/multipart'; import fastifyStatic from '@fastify/static'; import { fastifyTRPCPlugin, FastifyTRPCPluginOptions } from '@trpc/server/adapters/fastify'; -import fastify from 'fastify'; +import fastify, { FastifyReply } from 'fastify'; import fastifyRawBody from 'fastify-raw-body'; import { serializerCompiler, validatorCompiler, ZodTypeProvider } from 'fastify-type-provider-zod'; import { existsSync } from 'fs'; @@ -181,28 +181,45 @@ app.register(mcpServerRoutes, { prefix: '/mcp', }); -async function proxyToBetterAuth(url: string, request: { headers: Record }) { - const auth = await (await import('./auth')).getAuth(); - const headers = (await import('./utils/utils')).convertHeaders(request.headers); - const req = new Request(url, { method: 'GET', headers }); - return auth.handler(req); -} - -app.get('/.well-known/oauth-protected-resource', async (request, reply) => { - const url = new URL('/api/auth/.well-known/oauth-protected-resource', env.BETTER_AUTH_URL); - const response = await proxyToBetterAuth(url.toString(), request); - reply.status(response.status); - response.headers.forEach((value, key) => reply.header(key, value)); - reply.send(await response.text()); +app.get('/.well-known/oauth-protected-resource', async (_request, reply) => { + const { buildProtectedResourceMetadata } = await import('./auth'); + const { MCP_SERVER_URL } = await import('./env'); + const metadata = await buildProtectedResourceMetadata({ resource: MCP_SERVER_URL }); + reply + .status(200) + .header('Content-Type', 'application/json') + .header('Cache-Control', 'public, max-age=15, stale-while-revalidate=15, stale-if-error=86400') + .send(metadata); }); -app.get('/.well-known/oauth-authorization-server', async (request, reply) => { - const url = new URL('/api/auth/.well-known/oauth-authorization-server', env.BETTER_AUTH_URL); - const response = await proxyToBetterAuth(url.toString(), request); +async function relayWebResponse( + handler: (req: Request) => Promise, + request: { url: string; headers: Record }, + reply: FastifyReply, +) { + const url = new URL(request.url, env.BETTER_AUTH_URL); + const { convertHeaders } = await import('./utils/utils'); + const response = await handler(new Request(url, { method: 'GET', headers: convertHeaders(request.headers) })); reply.status(response.status); response.headers.forEach((value, key) => reply.header(key, value)); reply.send(await response.text()); -}); +} + +async function relayAuthServerMetadata(request: Parameters[1], reply: FastifyReply) { + const { getAuthServerMetadataHandler } = await import('./auth'); + const handler = await getAuthServerMetadataHandler(); + await relayWebResponse(handler, request, reply); +} + +async function relayOpenIdConfigMetadata(request: Parameters[1], reply: FastifyReply) { + const { getOpenIdConfigMetadataHandler } = await import('./auth'); + const handler = await getOpenIdConfigMetadataHandler(); + await relayWebResponse(handler, request, reply); +} + +app.get('/.well-known/oauth-authorization-server/api/auth', relayAuthServerMetadata); +app.get('/.well-known/openid-configuration/api/auth', relayOpenIdConfigMetadata); +app.get('/api/auth/.well-known/openid-configuration', relayOpenIdConfigMetadata); /** * Tests the API connection diff --git a/apps/backend/src/auth.ts b/apps/backend/src/auth.ts index 92f3e0fcd..a7b4bb6ae 100644 --- a/apps/backend/src/auth.ts +++ b/apps/backend/src/auth.ts @@ -1,11 +1,19 @@ +import type { ResourceServerMetadata } from '@better-auth/oauth-provider'; +import { + oauthProvider, + oauthProviderAuthServerMetadata, + oauthProviderOpenIdConfigMetadata, +} from '@better-auth/oauth-provider'; import { APIError, betterAuth } from 'better-auth'; import { drizzleAdapter } from 'better-auth/adapters/drizzle'; -import { mcp as mcpPlugin } from 'better-auth/plugins'; +import { verifyAccessToken } from 'better-auth/oauth2'; +import { jwt } from 'better-auth/plugins'; import { bearer } from 'better-auth/plugins/bearer'; +import type { JWTPayload } from 'jose'; import { db } from './db/db'; import dbConfig, { Dialect } from './db/dbConfig'; -import { env, isCloud } from './env'; +import { env, isCloud, MCP_SERVER_URL } from './env'; import * as orgQueries from './queries/organization.queries'; import { emailService } from './services/email'; import { hasFeature, LICENSE_FEATURES } from './services/license.service'; @@ -18,6 +26,54 @@ import { buildForgotPasswordEmail } from './utils/email-builders'; import { buildGithubAllowlist, isEmailDomainAllowed } from './utils/utils'; type GoogleConfig = Awaited>; +type MetadataHandler = (request: Request) => Promise; + +let authPromise: Promise>> | null = null; +let authServerMetadataPromise: Promise | null = null; +let openIdConfigMetadataPromise: Promise | null = null; + +export const getAuth = () => { + if (!authPromise) { + authPromise = orgQueries.getGoogleConfig().then(createAuthInstance); + } + return authPromise; +}; + +export function updateAuth() { + authPromise = orgQueries.getGoogleConfig().then(createAuthInstance); +} + +export async function verifyOAuthAccessToken(token: string, audience: string): Promise { + const { issuer, jwksUrl } = await getAuthServerEndpoints(); + return verifyAccessToken(token, { + verifyOptions: { audience, issuer }, + jwksUrl, + }); +} + +export async function buildProtectedResourceMetadata( + overrides: ResourceServerMetadata, +): Promise { + const { issuer } = await getAuthServerEndpoints(); + return { + authorization_servers: [issuer], + ...overrides, + }; +} + +export function getAuthServerMetadataHandler(): Promise { + if (!authServerMetadataPromise) { + authServerMetadataPromise = getAuth().then(oauthProviderAuthServerMetadata); + } + return authServerMetadataPromise; +} + +export function getOpenIdConfigMetadataHandler(): Promise { + if (!openIdConfigMetadataPromise) { + openIdConfigMetadataPromise = getAuth().then(oauthProviderOpenIdConfigMetadata); + } + return openIdConfigMetadataPromise; +} async function createAuthInstance(googleConfig: GoogleConfig) { const githubAllowlist = buildGithubAllowlist(env.GITHUB_ALLOWED_USERS); @@ -69,19 +125,23 @@ async function createAuthInstance(googleConfig: GoogleConfig) { return betterAuth({ secret: env.BETTER_AUTH_SECRET, + baseURL: env.BETTER_AUTH_URL, + basePath: '/api/auth', database: drizzleAdapter(db, { provider: dbConfig.dialect === Dialect.Postgres ? 'pg' : 'sqlite', schema: dbConfig.schema, }), plugins: [ bearer(), - mcpPlugin({ + jwt(), + oauthProvider({ loginPage: '/login', - oidcConfig: { - loginPage: '/login', - accessTokenExpiresIn: 86400, - refreshTokenExpiresIn: 604800, - }, + consentPage: '/consent', + accessTokenExpiresIn: 86400, + refreshTokenExpiresIn: 604800, + allowDynamicClientRegistration: true, + allowUnauthenticatedClientRegistration: true, + validAudiences: [env.BETTER_AUTH_URL, MCP_SERVER_URL], }), ], trustedOrigins: env.BETTER_AUTH_URL ? [env.BETTER_AUTH_URL] : undefined, @@ -141,15 +201,9 @@ async function createAuthInstance(googleConfig: GoogleConfig) { }); } -let authPromise: Promise>> | null = null; - -export const getAuth = () => { - if (!authPromise) { - authPromise = orgQueries.getGoogleConfig().then(createAuthInstance); - } - return authPromise; -}; - -export function updateAuth() { - authPromise = orgQueries.getGoogleConfig().then(createAuthInstance); +async function getAuthServerEndpoints(): Promise<{ issuer: string; jwksUrl: string }> { + const auth = await getAuth(); + const context = await auth.$context; + const issuer = context.baseURL; + return { issuer, jwksUrl: `${issuer}/jwks` }; } diff --git a/apps/backend/src/db/pg-schema.ts b/apps/backend/src/db/pg-schema.ts index f048b68b5..4679471ee 100644 --- a/apps/backend/src/db/pg-schema.ts +++ b/apps/backend/src/db/pg-schema.ts @@ -732,29 +732,74 @@ export const mcpCallLog = pgTable( ], ); -export const oauthApplication = pgTable( - 'oauth_application', +export const oauthClient = pgTable( + 'oauth_client', { id: text('id') .$defaultFn(() => crypto.randomUUID()) .primaryKey(), - name: text('name'), - icon: text('icon'), - metadata: text('metadata'), clientId: text('client_id').notNull().unique(), clientSecret: text('client_secret'), - redirectUrls: text('redirect_urls').notNull(), - type: text('type').notNull(), - authenticationScheme: text('authentication_scheme'), disabled: boolean('disabled').default(false), + skipConsent: boolean('skip_consent'), + enableEndSession: boolean('enable_end_session'), + subjectType: text('subject_type'), + scopes: jsonb('scopes').$type(), userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }), + name: text('name'), + uri: text('uri'), + icon: text('icon'), + contacts: jsonb('contacts').$type(), + tos: text('tos'), + policy: text('policy'), + softwareId: text('software_id'), + softwareVersion: text('software_version'), + softwareStatement: text('software_statement'), + redirectUris: jsonb('redirect_uris').$type().notNull(), + postLogoutRedirectUris: jsonb('post_logout_redirect_uris').$type(), + tokenEndpointAuthMethod: text('token_endpoint_auth_method'), + grantTypes: jsonb('grant_types').$type(), + responseTypes: jsonb('response_types').$type(), + public: boolean('public'), + type: text('type'), + requirePKCE: boolean('require_pkce'), + referenceId: text('reference_id'), + metadata: jsonb('metadata').$type>(), createdAt: timestamp('created_at').defaultNow().notNull(), updatedAt: timestamp('updated_at') .defaultNow() .$onUpdate(() => new Date()) .notNull(), }, - (t) => [index('oauth_application_userId_idx').on(t.userId)], + (t) => [index('oauth_client_userId_idx').on(t.userId)], +); + +export const oauthRefreshToken = pgTable( + 'oauth_refresh_token', + { + id: text('id') + .$defaultFn(() => crypto.randomUUID()) + .primaryKey(), + token: text('token').notNull().unique(), + clientId: text('client_id') + .notNull() + .references(() => oauthClient.clientId, { onDelete: 'cascade' }), + sessionId: text('session_id').references(() => session.id, { onDelete: 'set null' }), + userId: text('user_id') + .notNull() + .references(() => user.id, { onDelete: 'cascade' }), + referenceId: text('reference_id'), + expiresAt: timestamp('expires_at').notNull(), + createdAt: timestamp('created_at').defaultNow().notNull(), + revoked: timestamp('revoked'), + authTime: timestamp('auth_time'), + scopes: jsonb('scopes').$type().notNull(), + }, + (t) => [ + index('oauth_refresh_token_clientId_idx').on(t.clientId), + index('oauth_refresh_token_userId_idx').on(t.userId), + index('oauth_refresh_token_sessionId_idx').on(t.sessionId), + ], ); export const oauthAccessToken = pgTable( @@ -763,24 +808,22 @@ export const oauthAccessToken = pgTable( id: text('id') .$defaultFn(() => crypto.randomUUID()) .primaryKey(), - accessToken: text('access_token').notNull().unique(), - refreshToken: text('refresh_token').notNull().unique(), - accessTokenExpiresAt: timestamp('access_token_expires_at').notNull(), - refreshTokenExpiresAt: timestamp('refresh_token_expires_at').notNull(), + token: text('token').notNull().unique(), clientId: text('client_id') .notNull() - .references(() => oauthApplication.clientId, { onDelete: 'cascade' }), + .references(() => oauthClient.clientId, { onDelete: 'cascade' }), + sessionId: text('session_id').references(() => session.id, { onDelete: 'set null' }), userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }), - scopes: text('scopes').notNull(), + referenceId: text('reference_id'), + refreshId: text('refresh_id').references(() => oauthRefreshToken.id, { onDelete: 'cascade' }), + expiresAt: timestamp('expires_at').notNull(), createdAt: timestamp('created_at').defaultNow().notNull(), - updatedAt: timestamp('updated_at') - .defaultNow() - .$onUpdate(() => new Date()) - .notNull(), + scopes: jsonb('scopes').$type().notNull(), }, (t) => [ index('oauth_access_token_clientId_idx').on(t.clientId), index('oauth_access_token_userId_idx').on(t.userId), + index('oauth_access_token_refreshId_idx').on(t.refreshId), ], ); @@ -792,12 +835,10 @@ export const oauthConsent = pgTable( .primaryKey(), clientId: text('client_id') .notNull() - .references(() => oauthApplication.clientId, { onDelete: 'cascade' }), - userId: text('user_id') - .notNull() - .references(() => user.id, { onDelete: 'cascade' }), - scopes: text('scopes').notNull(), - consentGiven: boolean('consent_given').notNull(), + .references(() => oauthClient.clientId, { onDelete: 'cascade' }), + userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }), + referenceId: text('reference_id'), + scopes: jsonb('scopes').$type().notNull(), createdAt: timestamp('created_at').defaultNow().notNull(), updatedAt: timestamp('updated_at') .defaultNow() @@ -806,3 +847,13 @@ export const oauthConsent = pgTable( }, (t) => [index('oauth_consent_clientId_idx').on(t.clientId), index('oauth_consent_userId_idx').on(t.userId)], ); + +export const jwks = pgTable('jwks', { + id: text('id') + .$defaultFn(() => crypto.randomUUID()) + .primaryKey(), + publicKey: text('public_key').notNull(), + privateKey: text('private_key').notNull(), + createdAt: timestamp('created_at').defaultNow().notNull(), + expiresAt: timestamp('expires_at'), +}); diff --git a/apps/backend/src/db/sqlite-schema.ts b/apps/backend/src/db/sqlite-schema.ts index e761bfe1d..abf589b0d 100644 --- a/apps/backend/src/db/sqlite-schema.ts +++ b/apps/backend/src/db/sqlite-schema.ts @@ -543,10 +543,7 @@ export const story = sqliteTable( uniqueIndex('story_standalone_slug_unique') .on(t.projectId, t.userId, t.slug) .where(sql`chat_id IS NULL`), - check( - 'story_owner_required', - sql`${t.chatId} IS NOT NULL OR (${t.projectId} IS NOT NULL AND ${t.userId} IS NOT NULL)`, - ), + check('story_owner_required', sql`chat_id IS NOT NULL OR (project_id IS NOT NULL AND user_id IS NOT NULL)`), index('story_chatId_idx').on(t.chatId), index('story_projectId_idx').on(t.projectId), index('story_userId_idx').on(t.userId), @@ -781,22 +778,39 @@ export const mcpCallLog = sqliteTable( ], ); -export const oauthApplication = sqliteTable( - 'oauth_application', +export const oauthClient = sqliteTable( + 'oauth_client', { id: text('id') .$defaultFn(() => crypto.randomUUID()) .primaryKey(), - name: text('name'), - icon: text('icon'), - metadata: text('metadata'), clientId: text('client_id').notNull().unique(), clientSecret: text('client_secret'), - redirectUrls: text('redirect_urls').notNull(), - type: text('type').notNull(), - authenticationScheme: text('authentication_scheme'), disabled: integer('disabled', { mode: 'boolean' }).default(false), + skipConsent: integer('skip_consent', { mode: 'boolean' }), + enableEndSession: integer('enable_end_session', { mode: 'boolean' }), + subjectType: text('subject_type'), + scopes: text('scopes', { mode: 'json' }).$type(), userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }), + name: text('name'), + uri: text('uri'), + icon: text('icon'), + contacts: text('contacts', { mode: 'json' }).$type(), + tos: text('tos'), + policy: text('policy'), + softwareId: text('software_id'), + softwareVersion: text('software_version'), + softwareStatement: text('software_statement'), + redirectUris: text('redirect_uris', { mode: 'json' }).$type().notNull(), + postLogoutRedirectUris: text('post_logout_redirect_uris', { mode: 'json' }).$type(), + tokenEndpointAuthMethod: text('token_endpoint_auth_method'), + grantTypes: text('grant_types', { mode: 'json' }).$type(), + responseTypes: text('response_types', { mode: 'json' }).$type(), + public: integer('public', { mode: 'boolean' }), + type: text('type'), + requirePKCE: integer('require_pkce', { mode: 'boolean' }), + referenceId: text('reference_id'), + metadata: text('metadata', { mode: 'json' }).$type>(), createdAt: integer('created_at', { mode: 'timestamp_ms' }) .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) .notNull(), @@ -805,7 +819,37 @@ export const oauthApplication = sqliteTable( .$onUpdate(() => new Date()) .notNull(), }, - (t) => [index('oauth_application_userId_idx').on(t.userId)], + (t) => [index('oauth_client_userId_idx').on(t.userId)], +); + +export const oauthRefreshToken = sqliteTable( + 'oauth_refresh_token', + { + id: text('id') + .$defaultFn(() => crypto.randomUUID()) + .primaryKey(), + token: text('token').notNull().unique(), + clientId: text('client_id') + .notNull() + .references(() => oauthClient.clientId, { onDelete: 'cascade' }), + sessionId: text('session_id').references(() => session.id, { onDelete: 'set null' }), + userId: text('user_id') + .notNull() + .references(() => user.id, { onDelete: 'cascade' }), + referenceId: text('reference_id'), + expiresAt: integer('expires_at', { mode: 'timestamp_ms' }).notNull(), + createdAt: integer('created_at', { mode: 'timestamp_ms' }) + .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) + .notNull(), + revoked: integer('revoked', { mode: 'timestamp_ms' }), + authTime: integer('auth_time', { mode: 'timestamp_ms' }), + scopes: text('scopes', { mode: 'json' }).$type().notNull(), + }, + (t) => [ + index('oauth_refresh_token_clientId_idx').on(t.clientId), + index('oauth_refresh_token_userId_idx').on(t.userId), + index('oauth_refresh_token_sessionId_idx').on(t.sessionId), + ], ); export const oauthAccessToken = sqliteTable( @@ -814,26 +858,24 @@ export const oauthAccessToken = sqliteTable( id: text('id') .$defaultFn(() => crypto.randomUUID()) .primaryKey(), - accessToken: text('access_token').notNull().unique(), - refreshToken: text('refresh_token').notNull().unique(), - accessTokenExpiresAt: integer('access_token_expires_at', { mode: 'timestamp_ms' }).notNull(), - refreshTokenExpiresAt: integer('refresh_token_expires_at', { mode: 'timestamp_ms' }).notNull(), + token: text('token').notNull().unique(), clientId: text('client_id') .notNull() - .references(() => oauthApplication.clientId, { onDelete: 'cascade' }), + .references(() => oauthClient.clientId, { onDelete: 'cascade' }), + sessionId: text('session_id').references(() => session.id, { onDelete: 'set null' }), userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }), - scopes: text('scopes').notNull(), + referenceId: text('reference_id'), + refreshId: text('refresh_id').references(() => oauthRefreshToken.id, { onDelete: 'cascade' }), + expiresAt: integer('expires_at', { mode: 'timestamp_ms' }).notNull(), createdAt: integer('created_at', { mode: 'timestamp_ms' }) .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) .notNull(), - updatedAt: integer('updated_at', { mode: 'timestamp_ms' }) - .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) - .$onUpdate(() => new Date()) - .notNull(), + scopes: text('scopes', { mode: 'json' }).$type().notNull(), }, (t) => [ index('oauth_access_token_clientId_idx').on(t.clientId), index('oauth_access_token_userId_idx').on(t.userId), + index('oauth_access_token_refreshId_idx').on(t.refreshId), ], ); @@ -845,12 +887,10 @@ export const oauthConsent = sqliteTable( .primaryKey(), clientId: text('client_id') .notNull() - .references(() => oauthApplication.clientId, { onDelete: 'cascade' }), - userId: text('user_id') - .notNull() - .references(() => user.id, { onDelete: 'cascade' }), - scopes: text('scopes').notNull(), - consentGiven: integer('consent_given', { mode: 'boolean' }).notNull(), + .references(() => oauthClient.clientId, { onDelete: 'cascade' }), + userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }), + referenceId: text('reference_id'), + scopes: text('scopes', { mode: 'json' }).$type().notNull(), createdAt: integer('created_at', { mode: 'timestamp_ms' }) .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) .notNull(), @@ -861,3 +901,15 @@ export const oauthConsent = sqliteTable( }, (t) => [index('oauth_consent_clientId_idx').on(t.clientId), index('oauth_consent_userId_idx').on(t.userId)], ); + +export const jwks = sqliteTable('jwks', { + id: text('id') + .$defaultFn(() => crypto.randomUUID()) + .primaryKey(), + publicKey: text('public_key').notNull(), + privateKey: text('private_key').notNull(), + createdAt: integer('created_at', { mode: 'timestamp_ms' }) + .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) + .notNull(), + expiresAt: integer('expires_at', { mode: 'timestamp_ms' }), +}); diff --git a/apps/backend/src/env.ts b/apps/backend/src/env.ts index a1ccc29be..589790178 100644 --- a/apps/backend/src/env.ts +++ b/apps/backend/src/env.ts @@ -114,6 +114,9 @@ export function __reloadEnvForTesting(): void { export const isCloud = env.NAO_MODE === 'cloud'; export const isSelfHosted = env.NAO_MODE === 'self-hosted'; +const normalizedBaseUrl = env.BETTER_AUTH_URL.replace(/\/+$/, ''); +export const MCP_SERVER_URL = `${normalizedBaseUrl}/mcp`; + export function noProjectMessage(): string { return isCloud ? 'No project configured. Create a project or ask your organization admin to add you to one.' diff --git a/apps/backend/src/mcp/auth.ts b/apps/backend/src/mcp/auth.ts index 7f462626e..be80118f8 100644 --- a/apps/backend/src/mcp/auth.ts +++ b/apps/backend/src/mcp/auth.ts @@ -1,4 +1,12 @@ -import { getAuth } from '../auth'; +import { createHash } from 'node:crypto'; + +import { and, eq, gt } from 'drizzle-orm'; + +import { getAuth, verifyOAuthAccessToken } from '../auth'; +import s from '../db/abstractSchema'; +import { db } from '../db/db'; +import { MCP_SERVER_URL } from '../env'; +import { logger, serializeError } from '../utils/logger'; import { convertHeaders } from '../utils/utils'; export async function resolveUserId(fastifyRequest: { @@ -13,12 +21,51 @@ export async function resolveUserId(fastifyRequest: { return session.user.id; } - const host = fastifyRequest.headers['host'] ?? 'localhost'; - const request = new Request(`http://${host}${fastifyRequest.url}`, { headers }); - const mcpSession = await auth.api.getMcpSession({ headers, request, asResponse: false }); - if (mcpSession?.userId) { - return mcpSession.userId; + return verifyBearerToken(headers.get('authorization')); +} + +async function verifyBearerToken(authorization: string | null): Promise { + if (!authorization?.startsWith('Bearer ')) { + return null; + } + const token = authorization.slice('Bearer '.length).trim(); + if (!token) { + return null; + } + + const userId = (await verifyAsJwt(token)) ?? (await verifyAsOpaqueToken(token)); + if (!userId) { + logger.warn('MCP bearer token rejected', { source: 'http' }); } + return userId; +} + +async function verifyAsJwt(token: string): Promise { + try { + const payload = await verifyOAuthAccessToken(token, MCP_SERVER_URL); + return typeof payload.sub === 'string' ? payload.sub : null; + } catch (error) { + const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error); + logger.warn(`MCP JWT verification failed (audience=${MCP_SERVER_URL}): ${message}`, { + source: 'http', + context: { error: serializeError(error) }, + }); + return null; + } +} + +async function verifyAsOpaqueToken(token: string): Promise { + const hashed = sha256Base64Url(token); + const [row] = await db + .select({ userId: s.oauthAccessToken.userId }) + .from(s.oauthAccessToken) + .where(and(eq(s.oauthAccessToken.token, hashed), gt(s.oauthAccessToken.expiresAt, new Date()))) + .limit(1) + .execute(); + + return row?.userId ?? null; +} - return null; +function sha256Base64Url(value: string): string { + return createHash('sha256').update(value).digest('base64url'); } diff --git a/apps/backend/src/mcp/logging.ts b/apps/backend/src/mcp/logging.ts index e8eb7f3fc..8654f8213 100644 --- a/apps/backend/src/mcp/logging.ts +++ b/apps/backend/src/mcp/logging.ts @@ -15,7 +15,7 @@ export type ToolContent = { type: 'text'; text: string } | { type: 'image'; data export type ToolResult = { content: ToolContent[]; isError?: boolean; - toolOutput?: unknown; + structuredContent?: Record; }; export type ToolExtra = RequestHandlerExtra; @@ -24,7 +24,6 @@ export type ToolHandler = (args: T, extra: ToolExtra) => Promise; export const TOOL_MODE_MAP: Record = { ask_nao: 'agentModeEnabled', execute_sql: 'toolsModeEnabled', - build_chart: 'toolsModeEnabled', grep: 'toolsModeEnabled', ls: 'toolsModeEnabled', list_stories: 'objectsModeEnabled', @@ -65,8 +64,23 @@ export function withLogging(toolName: string, ctx: McpContext, handler: ToolH durationMs: Date.now() - start, success, toolInput: args as unknown, - toolOutput: result?.toolOutput, + toolOutput: extractLoggableOutput(result), }).catch(() => {}); } }; } + +function extractLoggableOutput(result: ToolResult | undefined): unknown { + if (!result) { + return undefined; + } + const text = result.content + .filter((part): part is { type: 'text'; text: string } => part.type === 'text') + .map((part) => part.text) + .join('\n'); + try { + return JSON.parse(text); + } catch { + return text; + } +} diff --git a/apps/backend/src/mcp/routes.ts b/apps/backend/src/mcp/routes.ts index 8cd3a8205..94ef586f5 100644 --- a/apps/backend/src/mcp/routes.ts +++ b/apps/backend/src/mcp/routes.ts @@ -1,5 +1,5 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; -import type { FastifyReply } from 'fastify'; +import type { FastifyReply, FastifyRequest } from 'fastify'; import type { App } from '../app'; import { env } from '../env'; @@ -7,93 +7,97 @@ import { getMcpEndpointSettings } from '../queries/mcp-endpoint.queries'; import { resolveUserId } from './auth'; import { createMcpServer, resolveProjectId, sessions } from './server'; -function replyUnauthorized(reply: FastifyReply) { - const origin = env.BETTER_AUTH_URL.replace(/\/+$/, ''); - const wwwAuth = `Bearer resource_metadata="${origin}/.well-known/oauth-protected-resource"`; - return reply - .status(401) - .header('WWW-Authenticate', wwwAuth) - .header('Access-Control-Expose-Headers', 'WWW-Authenticate') - .send({ error: 'Unauthorized. Provide a valid Bearer token in the Authorization header.' }); +declare module 'fastify' { + interface FastifyRequest { + mcpUserId: string; + } } export const mcpServerRoutes = async (app: App) => { - app.post('/', async (request, reply) => { - const userId = await resolveUserId(request); - if (!userId) { - return replyUnauthorized(reply); - } + app.addHook('preHandler', requireAuthenticatedMcpUser); + app.get('/', (request, reply) => handleExistingSession(request, reply)); + + app.post('/', async (request, reply) => { const existingSessionId = request.headers['mcp-session-id'] as string | undefined; if (existingSessionId) { - const session = sessions.get(existingSessionId); - if (session && session.userId === userId) { - session.lastAccess = Date.now(); - await session.transport.handleRequest(request.raw, reply.raw, request.body as Record); - reply.hijack(); - return; - } - return reply.status(404).send({ error: 'Session not found or expired. Please reinitialize.' }); + return handleExistingSession(request, reply, request.body); } + return initializeSession(request, reply); + }); - const projectId = await resolveProjectId(userId); - const settings = await getMcpEndpointSettings(projectId); - if (!settings.enabled) { - return reply.status(503).send({ error: 'MCP is disabled for this workspace.' }); - } + app.delete('/', (request, reply) => handleExistingSession(request, reply)); +}; - const server = createMcpServer(userId, projectId, settings); - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: () => crypto.randomUUID(), - enableJsonResponse: true, - onsessioninitialized: (sessionId) => { - sessions.set(sessionId, { transport, server, userId, projectId, lastAccess: Date.now() }); - }, - onsessionclosed: (sessionId) => { - sessions.delete(sessionId); - server.close().catch(() => {}); - }, - }); +async function requireAuthenticatedMcpUser(request: FastifyRequest, reply: FastifyReply): Promise { + const userId = await resolveUserId(request); + if (!userId) { + replyUnauthorized(reply); + return; + } + request.mcpUserId = userId; +} - await server.connect(transport); - await transport.handleRequest(request.raw, reply.raw, request.body as Record); - reply.hijack(); - }); +async function initializeSession(request: FastifyRequest, reply: FastifyReply): Promise { + const userId = request.mcpUserId; + const projectId = await resolveProjectId(userId); + const settings = await getMcpEndpointSettings(projectId); + if (!settings.enabled) { + reply.status(503).send({ error: 'MCP is disabled for this workspace.' }); + return; + } - app.get('/', async (request, reply) => { - const userId = await resolveUserId(request); - if (!userId) { - return replyUnauthorized(reply); - } + const server = createMcpServer(userId, projectId, settings); + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => crypto.randomUUID(), + enableJsonResponse: true, + onsessioninitialized: (sessionId) => { + sessions.set(sessionId, { transport, server, userId, projectId, lastAccess: Date.now() }); + }, + onsessionclosed: (sessionId) => { + sessions.delete(sessionId); + server.close().catch(() => {}); + }, + }); - const sessionId = request.headers['mcp-session-id'] as string | undefined; - if (!sessionId) { - return reply.status(400).send({ error: 'Missing Mcp-Session-Id header for SSE connection.' }); - } + await server.connect(transport); + await transport.handleRequest(request.raw, reply.raw, request.body as Record); + reply.hijack(); +} - const session = sessions.get(sessionId); - if (!session || session.userId !== userId) { - return reply.status(404).send({ error: 'Session not found or expired.' }); - } +async function handleExistingSession(request: FastifyRequest, reply: FastifyReply, body?: unknown): Promise { + const sessionId = request.headers['mcp-session-id'] as string | undefined; + if (!sessionId) { + reply.status(400).send({ error: 'Missing Mcp-Session-Id header.' }); + return; + } - session.lastAccess = Date.now(); - await session.transport.handleRequest(request.raw, reply.raw); - reply.hijack(); - }); + const session = sessions.get(sessionId); + if (!session || session.userId !== request.mcpUserId) { + reply.status(404).send({ error: 'Session not found or expired. Please reinitialize.' }); + return; + } - app.delete('/', async (request, reply) => { - const userId = await resolveUserId(request); - if (!userId) { - return replyUnauthorized(reply); - } + const settings = await getMcpEndpointSettings(session.projectId); + if (!settings.enabled) { + sessions.delete(sessionId); + await session.transport.close().catch(() => {}); + await session.server.close().catch(() => {}); + reply.status(503).send({ error: 'MCP is disabled for this workspace.' }); + return; + } - const sessionId = request.headers['mcp-session-id'] as string | undefined; - const session = sessionId ? sessions.get(sessionId) : undefined; - if (!session || session.userId !== userId) { - return reply.status(400).send({ error: 'Invalid or missing session.' }); - } + session.lastAccess = Date.now(); + await session.transport.handleRequest(request.raw, reply.raw, body as Record | undefined); + reply.hijack(); +} - await session.transport.handleRequest(request.raw, reply.raw); - reply.hijack(); - }); -}; +function replyUnauthorized(reply: FastifyReply) { + const origin = env.BETTER_AUTH_URL.replace(/\/+$/, ''); + const wwwAuth = `Bearer resource_metadata="${origin}/.well-known/oauth-protected-resource"`; + return reply + .status(401) + .header('WWW-Authenticate', wwwAuth) + .header('Access-Control-Expose-Headers', 'WWW-Authenticate') + .send({ error: 'Unauthorized. Provide a valid Bearer token in the Authorization header.' }); +} diff --git a/apps/backend/src/mcp/tools/agent.ts b/apps/backend/src/mcp/tools/agent.ts index d789b3990..ac3c25622 100644 --- a/apps/backend/src/mcp/tools/agent.ts +++ b/apps/backend/src/mcp/tools/agent.ts @@ -1,71 +1,68 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { type InferUIMessageChunk, readUIMessageStream } from 'ai'; import { z } from 'zod'; import * as chatQueries from '../../queries/chat.queries'; -import type { AgentProgressEvent } from '../../services/agent'; import { agentService } from '../../services/agent'; import { mcpService } from '../../services/mcp'; import { skillService } from '../../services/skill'; -import type { UIMessage } from '../../types/chat'; +import type { UIMessage, UIMessagePart } from '../../types/chat'; import { logger } from '../../utils/logger'; import type { McpContext, ToolExtra } from '../logging'; import { withLogging } from '../logging'; import { chatUrl } from '../urls'; +const ASK_NAO_DESCRIPTION = + 'Ask nao an analytics question in natural language. Creates a chat that is visible in the UI.\n\n' + + 'Use this for ad-hoc data exploration and Q&A. ' + + 'To create a persistent Nao Story (markdown dashboard with embedded charts/tables), do NOT ask nao in natural language — call `create_story` directly with the SQL results from `execute_sql`. ' + + 'To browse or update existing stories, use `list_stories` / `get_story` / `update_story`.\n\n' + + 'Returns `chatId` and a `url` that opens the chat in the Nao UI — surface the URL to the user as a clickable link so they can jump to the conversation (and any rendered charts/tables). ' + + 'The returned `chatId` can also be passed to `create_story` as `chat_id` to attach a follow-up story to this conversation.'; + export function registerAgentTools(server: McpServer, ctx: McpContext): void { server.registerTool( 'ask_nao', { - description: - 'Ask nao an analytics question in natural language. Creates a chat that is visible in the UI.\n\n' + - 'Use this for ad-hoc data exploration and Q&A. ' + - 'To create a persistent Nao Story (markdown dashboard with embedded charts/tables), do NOT ask nao in natural language — use `execute_sql` → `build_chart` → `create_story` instead. ' + - 'To browse or update existing stories, use `list_stories` / `get_story` / `update_story`.\n\n' + - 'Returns `chatId` and a `url` that opens the chat in the Nao UI — surface the URL to the user as a clickable link so they can jump to the conversation (and any rendered charts/tables). ' + - 'The returned `chatId` can also be passed to `create_story` as `chat_id` to attach a follow-up story to this conversation.', + title: 'Ask Nao', + description: ASK_NAO_DESCRIPTION, inputSchema: { - question: z.string().describe('The analytics question'), - conversation_id: z - .string() + question: z.string().describe('The analytics question.'), + chatId: z + .uuid() .optional() .describe( - 'Existing chat ID to continue a conversation. Omit to start a new chat. ' + - 'Reuse the ID returned by a previous ask_nao call ONLY when the new question clearly builds on the previous nao exchange (same data, same topic). ' + + 'UUID of an existing chat to continue. Omit to start a new chat. ' + + 'Reuse the UUID returned by a previous ask_nao call ONLY when the new question clearly builds on the previous nao exchange (same data, same topic). ' + 'If the topic shifts or the prior nao reply was a refusal / off-topic, omit this to start a fresh chat — otherwise the follow-up inherits the prior context and may repeat the refusal.', ), }, + outputSchema: { + chatId: z.uuid().describe('UUID of the chat in which the question was answered.'), + chatUrl: z.url().describe('URL to open the chat (and rendered charts/tables) in the Nao UI.'), + text: z.string().describe('The assistant final text response.'), + }, }, - withLogging('ask_nao', ctx, async ({ question, conversation_id }, extra) => { + withLogging('ask_nao', ctx, async ({ question, chatId }, extra) => { try { await mcpService.initializeMcpState(ctx.projectId); await skillService.initializeSkills(ctx.projectId); - const { chat, uiMessages } = await buildChatContext( - ctx.projectId, - ctx.userId, - question, - conversation_id, - ); - - const agent = await agentService.create(chat, undefined, ['story', 'suggest_follow_ups']); + const { chat, uiMessages } = await buildChatContext(ctx.projectId, ctx.userId, question, chatId); - const progressToken = extra._meta?.progressToken; - const result = await agent.streamWithProgress(uiMessages, makeProgressHandler(extra, progressToken)); - - await chatQueries.upsertMessage({ - chatId: chat.id, - role: 'assistant', - parts: result.responseParts, - tokenUsage: result.usage, - }); + const agent = await agentService.create(chat); + const stream = agent.stream(uiMessages); + const text = await consumeStreamWithProgress(stream, extra); - const url = chatUrl(chat.id); + const output = { chatId: chat.id, chatUrl: chatUrl(chat.id), text }; return { content: [ - { type: 'text' as const, text: result.text }, - { type: 'text' as const, text: `\n\n[conversation_id: ${chat.id}]\n[chat_url: ${url}]` }, + { + type: 'text' as const, + text: `${text}\n\n[chatId: ${output.chatId}]\n[chatUrl: ${output.chatUrl}]`, + }, ], - toolOutput: { chatId: chat.id, text: result.text, url }, + structuredContent: output, }; } catch (error) { const message = error instanceof Error ? error.message : String(error); @@ -86,7 +83,7 @@ async function buildChatContext( projectId: string, userId: string, question: string, - conversationId: string | undefined, + chatId: string | undefined, ): Promise<{ chat: { id: string; projectId: string; userId: string }; uiMessages: UIMessage[] }> { const userMessage: UIMessage = { id: crypto.randomUUID(), @@ -95,43 +92,83 @@ async function buildChatContext( source: 'mcp', }; - if (conversationId) { - const ownerId = await chatQueries.getChatOwnerId(conversationId); + if (chatId) { + const ownerId = await chatQueries.getChatOwnerId(chatId); if (ownerId === userId) { - const history = await chatQueries.getChatMessages(conversationId); - await chatQueries.upsertMessage({ ...userMessage, chatId: conversationId }); + const history = await chatQueries.getChatMessages(chatId); + await chatQueries.upsertMessage({ ...userMessage, chatId: chatId }); return { - chat: { id: conversationId, projectId, userId }, + chat: { id: chatId, projectId, userId }, uiMessages: [...history, userMessage], }; } } - const chatId = crypto.randomUUID(); + const newChatId = crypto.randomUUID(); await chatQueries.createChat( - { id: chatId, projectId, userId, title: question.slice(0, 80) }, + { id: newChatId, projectId, userId, title: question.slice(0, 80) }, { text: question, source: 'mcp' }, ); return { - chat: { id: chatId, projectId, userId }, + chat: { id: newChatId, projectId, userId }, uiMessages: [userMessage], }; } -function makeProgressHandler(extra: ToolExtra, rawProgressToken: unknown) { - const progressToken = - typeof rawProgressToken === 'string' || typeof rawProgressToken === 'number' ? rawProgressToken : undefined; +async function consumeStreamWithProgress( + stream: ReadableStream>, + extra: ToolExtra, +): Promise { + const progressToken = normalizeProgressToken(extra._meta?.progressToken); + const seenToolCalls = new Set(); + let progress = 0; + let lastMessage: UIMessage | null = null; - let chunkIndex = 0; - - return async (event: AgentProgressEvent) => { - if (progressToken === undefined || event.type !== 'tool-call') { - return; + for await (const message of readUIMessageStream({ stream })) { + lastMessage = message; + if (progressToken === undefined) { + continue; } + for (const part of message.parts) { + if (!isToolPart(part) || seenToolCalls.has(part.toolCallId)) { + continue; + } + if (part.state !== 'input-available' && part.state !== 'output-available') { + continue; + } + seenToolCalls.add(part.toolCallId); + await extra.sendNotification({ + method: 'notifications/progress', + params: { + progressToken, + progress: ++progress, + message: `[${toolNameFromPart(part)}]`, + }, + }); + } + } - await extra.sendNotification({ - method: 'notifications/progress', - params: { progressToken, progress: ++chunkIndex, message: `[${event.toolName}]` }, - }); - }; + return extractFinalText(lastMessage); +} + +function normalizeProgressToken(raw: unknown): string | number | undefined { + return typeof raw === 'string' || typeof raw === 'number' ? raw : undefined; +} + +function isToolPart(part: UIMessagePart): part is Extract { + return typeof part.type === 'string' && part.type.startsWith('tool-') && 'toolCallId' in part && 'state' in part; +} + +function toolNameFromPart(part: { type: string }): string { + return part.type.replace(/^tool-/, ''); +} + +function extractFinalText(message: UIMessage | null): string { + if (!message) { + return ''; + } + return message.parts + .filter((p): p is Extract => p.type === 'text') + .map((p) => p.text) + .join('\n\n'); } diff --git a/apps/backend/src/mcp/tools/data.ts b/apps/backend/src/mcp/tools/data.ts index 8f749f307..b51a9ddae 100644 --- a/apps/backend/src/mcp/tools/data.ts +++ b/apps/backend/src/mcp/tools/data.ts @@ -1,122 +1,21 @@ -import crypto from 'node:crypto'; - import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { z } from 'zod'; -import { executeQuery } from '../../agents/tools/execute-sql'; -import { getEnvVars, retrieveProjectById } from '../../queries/project.queries'; -import { hasFeature, LICENSE_FEATURES } from '../../services/license.service'; -import { getAzureAccessTokenForUser } from '../../services/microsoft-auth.service'; -import { logger } from '../../utils/logger'; +import executeSqlTool from '../../agents/tools/execute-sql'; import type { McpContext } from '../logging'; -import { withLogging } from '../logging'; - -export function registerDataTools(server: McpServer, ctx: McpContext): void { - server.registerTool( - 'execute_sql', - { - title: 'Execute SQL', - description: - 'Run a SQL query against the connected data warehouse. Returns rows as JSON. The response includes a `query_id` — pass it to `build_chart` or reference it in story `
` blocks. Use ask_nao instead if you want Nao to write the SQL for you.', - inputSchema: { - sql: z.string().describe('The SQL query to execute'), - limit: z - .number() - .int() - .min(1) - .max(1000) - .optional() - .default(100) - .describe('Max rows to return (default 100, min 1, max 1000)'), - }, - }, - withLogging('execute_sql', ctx, async ({ sql, limit }) => { - try { - const project = await retrieveProjectById(ctx.projectId); - const envVars = await getEnvVars(ctx.projectId); - const azureAccessToken = (await hasFeature(LICENSE_FEATURES.sso)) - ? await getAzureAccessTokenForUser(ctx.userId) - : null; - const result = await executeQuery( - { sql_query: sql }, - { - projectFolder: project.path!, - chatId: '', - agentSettings: null, - envVars, - azureAccessToken, - queryResults: new Map(), - }, - ); +import { registerAgentToolAsMcp } from './wrap-agent-tool'; - const rows = result.data.slice(0, limit); - const queryId = `query_${crypto.randomUUID().slice(0, 8)}`; - const output = { query_id: queryId, columns: result.columns, row_count: rows.length, data: rows }; - return { - content: [{ type: 'text' as const, text: JSON.stringify(output) }], - toolOutput: output, - }; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - logger.error(`MCP execute_sql error: ${message}`, { - source: 'tool', - context: { sql, userId: ctx.userId }, - }); - return { content: [{ type: 'text' as const, text: `SQL execution error: ${message}` }], isError: true }; - } - }), - ); +const EXECUTE_SQL_DESCRIPTION = + 'Run a SQL query against the connected data warehouse. Returns rows as JSON, including an `id` ' + + '(e.g. "query_a1b2c3d4"). To embed the result in a story, pass that `id` as the `query_id` attribute ' + + 'of a `` or `
` block inside `create_story` / `update_story`. ' + + 'Add a `LIMIT` clause to your SQL if you want to cap the number of rows returned. ' + + 'Use `ask_nao` instead if you want Nao to write the SQL for you.'; - server.registerTool( - 'build_chart', - { - title: 'Build Chart', - description: - 'Generate a Nao-compatible `` block to embed in story content. Always use this tool instead of writing `` blocks manually — it ensures the correct syntax for the Nao UI renderer. Workflow: execute_sql → build_chart → create_story/update_story (pass the returned block in `content` and the SQL rows in `query_data`).', - inputSchema: { - query_id: z.string().describe('The query_id returned by execute_sql.'), - chart_type: z - .enum(['bar', 'stacked_bar', 'line', 'area', 'stacked_area', 'pie', 'kpi_card', 'scatter', 'radar']) - .describe('Type of chart to render.'), - x_axis_key: z.string().describe('Column name for the X-axis / category labels.'), - x_axis_type: z - .enum(['date', 'number', 'category']) - .nullable() - .describe( - 'Use "date" for YYYY-MM-DD values, "category" for labels/periods, "number" for numeric axes. Use null if unsure.', - ), - series: z - .array( - z.object({ - data_key: z.string().describe('Column name from SQL result to plot.'), - color: z.string().optional().describe('CSS color (defaults to theme colors).'), - label: z.string().optional().describe('Label to display in the legend.'), - }), - ) - .min(1) - .describe('Columns to plot. Each entry needs at least a data_key (column name from SQL result).'), - title: z - .string() - .describe('Concise descriptive chart title. Do not include the chart type in the title.'), - }, - }, - withLogging('build_chart', ctx, async ({ query_id, chart_type, x_axis_key, x_axis_type, series, title }) => { - const typedSeries = series as Array<{ data_key: string; color?: string; label?: string }>; - const block = buildChartBlock(query_id, chart_type, x_axis_key, x_axis_type, typedSeries, title); - return { content: [{ type: 'text' as const, text: block }], toolOutput: { block } }; - }), - ); -} - -function buildChartBlock( - queryId: string, - chartType: string, - xAxisKey: string, - xAxisType: string | null, - series: Array<{ data_key: string; color?: string; label?: string }>, - title: string, -): string { - const escapedTitle = title.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); - const xAxisTypeAttr = xAxisType ? ` x_axis_type="${xAxisType}"` : ''; - return ``; +export function registerDataTools(server: McpServer, ctx: McpContext): void { + registerAgentToolAsMcp(server, ctx, { + name: 'execute_sql', + agentTool: executeSqlTool, + title: 'Execute SQL', + description: EXECUTE_SQL_DESCRIPTION, + }); } diff --git a/apps/backend/src/mcp/tools/files.ts b/apps/backend/src/mcp/tools/files.ts index 2f4023b06..09becbd7c 100644 --- a/apps/backend/src/mcp/tools/files.ts +++ b/apps/backend/src/mcp/tools/files.ts @@ -1,87 +1,20 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { z } from 'zod'; import grepTool from '../../agents/tools/grep'; import listTool from '../../agents/tools/list'; -import { retrieveProjectById } from '../../queries/project.queries'; -import { logger } from '../../utils/logger'; import type { McpContext } from '../logging'; -import { withLogging } from '../logging'; - -function makeExecutionOptions(projectFolder: string) { - const context = { projectFolder, chatId: '', agentSettings: null, envVars: {}, queryResults: new Map() }; - return { toolCallId: '', messages: [] as [], experimental_context: context }; -} +import { registerAgentToolAsMcp } from './wrap-agent-tool'; export function registerFileTools(server: McpServer, ctx: McpContext): void { - server.registerTool( - 'grep', - { - title: 'Search Files', - description: - 'Search for text patterns in project files using regex. Respects .gitignore and .naoignore. Returns matching lines with file paths and line numbers.', - inputSchema: { - pattern: z.string().describe('The regex pattern to search for in file contents.'), - path: z.string().optional().describe('File or directory path to search in. Defaults to project root.'), - glob: z.string().optional().describe('Glob pattern to filter files (e.g. "*.ts", "*.{js,jsx}").'), - case_insensitive: z.boolean().optional().describe('Case insensitive search. Defaults to false.'), - context_lines: z.number().optional().describe('Lines of context to show around each match.'), - max_results: z.number().optional().describe('Maximum matches to return (default 100, max 500).'), - }, - }, - withLogging( - 'grep', - ctx, - async ({ pattern, path: searchPath, glob, case_insensitive, context_lines, max_results }) => { - try { - const project = await retrieveProjectById(ctx.projectId); - const result = await grepTool.execute!( - { - pattern, - path: searchPath, - glob, - case_insensitive, - context_lines, - max_results: Math.min(Math.max(Math.floor(max_results ?? 100), 1), 500), - }, - makeExecutionOptions(project.path!), - ); - return { content: [{ type: 'text' as const, text: JSON.stringify(result) }], toolOutput: result }; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - logger.error(`MCP grep error: ${message}`, { - source: 'tool', - context: { pattern, userId: ctx.userId }, - }); - return { content: [{ type: 'text' as const, text: `Search error: ${message}` }], isError: true }; - } - }, - ), - ); + registerAgentToolAsMcp(server, ctx, { + name: 'grep', + agentTool: grepTool, + title: 'Search Files', + }); - server.registerTool( - 'ls', - { - title: 'List Files', - description: - 'List files and directories at the specified path within the project. Returns entry names, types, and sizes.', - inputSchema: { - path: z.string().optional().describe('Directory path to list. Defaults to project root ("/").'), - }, - }, - withLogging('ls', ctx, async ({ path: filePath }) => { - try { - const project = await retrieveProjectById(ctx.projectId); - const result = await listTool.execute!({ path: filePath ?? '/' }, makeExecutionOptions(project.path!)); - return { content: [{ type: 'text' as const, text: JSON.stringify(result) }], toolOutput: result }; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - logger.error(`MCP ls error: ${message}`, { - source: 'tool', - context: { path: filePath, userId: ctx.userId }, - }); - return { content: [{ type: 'text' as const, text: `List error: ${message}` }], isError: true }; - } - }), - ); + registerAgentToolAsMcp(server, ctx, { + name: 'ls', + agentTool: listTool, + title: 'List Files', + }); } diff --git a/apps/backend/src/mcp/tools/stories.ts b/apps/backend/src/mcp/tools/stories.ts index 9eed448e4..9bb50d075 100644 --- a/apps/backend/src/mcp/tools/stories.ts +++ b/apps/backend/src/mcp/tools/stories.ts @@ -37,7 +37,7 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { url: storyUrl(story), chatUrl: storyChatUrl(story), })); - return { content: [{ type: 'text' as const, text: JSON.stringify(result) }], toolOutput: result }; + return { content: [{ type: 'text' as const, text: JSON.stringify(result) }] }; } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error(`MCP list_stories error: ${message}`, { source: 'tool', context: { userId: ctx.userId } }); @@ -76,10 +76,7 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { url: storyUrl(story), chatUrl: storyChatUrl(story), }; - return { - content: [{ type: 'text' as const, text: JSON.stringify(output) }], - toolOutput: output, - }; + return { content: [{ type: 'text' as const, text: JSON.stringify(output) }] }; } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error(`MCP get_story error: ${message}`, { @@ -96,7 +93,7 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { { title: 'Create Story', description: - 'Create a new analytics story. Stories are markdown documents with embedded chart/table components rendered by the Nao UI.\n\nWorkflow for stories with charts:\n1. execute_sql → get rows + query_id\n2. build_chart → get a `` block string\n3. create_story → embed the block in `content`; pass SQL rows in `query_data`\n\nSupported blocks:\n- Charts: use `build_chart` to generate the correct `` block — do NOT write these manually.\n- Tables: `
`\n- Grids: `...blocks...` (1–4 columns)\n\nOmit `content` to create an empty story.\n\nPass `chat_id` to attach the story to an existing chat (e.g. one returned by `ask_nao`). Omit it to create a standalone story listed at the project level.\n\nReturns a `url` that opens the rendered story in the Nao UI and a `chatUrl` that opens the underlying chat (null for standalone stories) — surface the relevant link to the user as a clickable link in your reply.', + 'Create a new analytics story. Stories are markdown documents with embedded chart/table components rendered by the Nao UI.\n\nWorkflow for stories with charts:\n1. `execute_sql` → get rows + `query_id`\n2. `create_story` → embed `` / `
` blocks in `content`; pass the SQL rows keyed by `query_id` in `query_data`\n\nSupported blocks (write them inline in `content`):\n- Charts: `` — `series` is JSON inside single quotes; `x_axis_type` is optional; `kpi_card` and `pie` accept omitted/null `x_axis_type`.\n- Tables: `
`\n- Grids: `...blocks...` (1–4 columns)\n\nOmit `content` to create an empty story.\n\nPass `chat_id` to attach the story to an existing chat (e.g. one returned by `ask_nao`). Omit it to create a standalone story listed at the project level.\n\nReturns a `url` that opens the rendered story in the Nao UI and a `chatUrl` that opens the underlying chat (null for standalone stories) — surface the relevant link to the user as a clickable link in your reply.', inputSchema: { title: z.string().describe('Story title'), content: z @@ -151,10 +148,7 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { url: storyUrl(storyForUrl), chatUrl: storyChatUrl(storyForUrl), }; - return { - content: [{ type: 'text' as const, text: JSON.stringify(output) }], - toolOutput: output, - }; + return { content: [{ type: 'text' as const, text: JSON.stringify(output) }] }; } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error(`MCP create_story error: ${message}`, { @@ -171,7 +165,7 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { { title: 'Update Story', description: - 'Update a story title and/or content. Omit fields to keep their current values.\n\nWhen adding or replacing charts, use `build_chart` first to generate the correct `` block, then pass it in `content`. Include the SQL rows for any new query_ids in `query_data`.\n\nReturns a `url` that opens the rendered story in the Nao UI and a `chatUrl` that opens the underlying chat (null for standalone stories) — surface the relevant link to the user as a clickable link in your reply.', + 'Update a story title and/or content. Omit fields to keep their current values.\n\nWhen adding or replacing charts, write the `` block directly in `content` (see `create_story` for the full chart syntax) and include the SQL rows for any new `query_id`s in `query_data`.\n\nReturns a `url` that opens the rendered story in the Nao UI and a `chatUrl` that opens the underlying chat (null for standalone stories) — surface the relevant link to the user as a clickable link in your reply.', inputSchema: { story_id: z.string().describe('The story ID to update'), title: z.string().optional().describe('New title (omit to keep current)'), @@ -217,7 +211,7 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { await storyQueries.upsertStoryDataCacheByStoryId(storyForCache.id, mergedQueryData); } } - return { content: [{ type: 'text' as const, text: JSON.stringify(output) }], toolOutput: output }; + return { content: [{ type: 'text' as const, text: JSON.stringify(output) }] }; } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error(`MCP update_story error: ${message}`, { @@ -244,7 +238,6 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { await storyQueries.archiveByStoryId(story.id); return { content: [{ type: 'text' as const, text: JSON.stringify({ id: story.id, archived: true }) }], - toolOutput: { id: story.id, archived: true }, }; } catch (error) { const message = error instanceof Error ? error.message : String(error); @@ -273,7 +266,6 @@ export function registerStoryTools(server: McpServer, ctx: McpContext): void { await storyQueries.deleteStory(story.id); return { content: [{ type: 'text' as const, text: JSON.stringify({ id: story.id, deleted: true }) }], - toolOutput: { id: story.id, deleted: true }, }; } catch (error) { const message = error instanceof Error ? error.message : String(error); diff --git a/apps/backend/src/mcp/tools/wrap-agent-tool.ts b/apps/backend/src/mcp/tools/wrap-agent-tool.ts new file mode 100644 index 000000000..172d40243 --- /dev/null +++ b/apps/backend/src/mcp/tools/wrap-agent-tool.ts @@ -0,0 +1,67 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import type { Tool, ToolExecutionOptions } from 'ai'; +import type { AnyZodObject } from 'zod/v3'; + +import { getEnvVars, retrieveProjectById } from '../../queries/project.queries'; +import { hasFeature, LICENSE_FEATURES } from '../../services/license.service'; +import { getAzureAccessTokenForUser } from '../../services/microsoft-auth.service'; +import type { ToolContext } from '../../types/tools'; +import { logger } from '../../utils/logger'; +import type { McpContext, ToolHandler } from '../logging'; +import { withLogging } from '../logging'; + +export interface WrapAgentToolOptions { + name: string; + agentTool: Tool; + title?: string; + description?: string; +} + +export function registerAgentToolAsMcp( + server: McpServer, + ctx: McpContext, + options: WrapAgentToolOptions, +): void { + const { name, agentTool, title, description = agentTool.description } = options; + + const handler: ToolHandler = async (input) => { + try { + const toolContext = await buildMcpToolContext(ctx); + const output = await agentTool.execute!(input, makeExecutionOptions(toolContext)); + return { content: [{ type: 'text' as const, text: JSON.stringify(output) }] }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error(`MCP ${name} error: ${message}`, { + source: 'tool', + context: { input, userId: ctx.userId }, + }); + return { content: [{ type: 'text' as const, text: `${name} error: ${message}` }], isError: true }; + } + }; + + server.registerTool( + name, + { title, description, inputSchema: agentTool.inputSchema as unknown as AnyZodObject }, + withLogging(name, ctx, handler) as Parameters[2], + ); +} + +async function buildMcpToolContext(ctx: McpContext): Promise { + const project = await retrieveProjectById(ctx.projectId); + const envVars = await getEnvVars(ctx.projectId); + const azureAccessToken = (await hasFeature(LICENSE_FEATURES.sso)) + ? await getAzureAccessTokenForUser(ctx.userId) + : null; + return { + projectFolder: project.path ?? '', + chatId: '', + agentSettings: null, + envVars, + azureAccessToken, + queryResults: new Map(), + }; +} + +function makeExecutionOptions(toolContext: ToolContext): ToolExecutionOptions & { experimental_context: ToolContext } { + return { toolCallId: '', messages: [], experimental_context: toolContext }; +} diff --git a/apps/backend/src/services/agent.ts b/apps/backend/src/services/agent.ts index ce57fd131..44115d5b7 100644 --- a/apps/backend/src/services/agent.ts +++ b/apps/backend/src/services/agent.ts @@ -60,10 +60,6 @@ import { memoryService } from './memory'; import { getAzureAccessTokenForUser } from './microsoft-auth.service'; import { skillService } from './skill'; -export type AgentProgressEvent = - | { type: 'tool-call'; toolName: string; toolCallId: string } - | { type: 'tool-result'; toolName: string; toolCallId: string }; - export interface AgentRunResult { text: string; usage: TokenUsage; @@ -94,11 +90,7 @@ export class AgentService { await assertBudgetNotExceeded(projectId, resolved.provider); } - async create( - chat: AgentChat, - modelSelection?: LlmSelectedModel, - excludeTools?: readonly string[], - ): Promise { + async create(chat: AgentChat, modelSelection?: LlmSelectedModel): Promise { this._disposeAgent(chat.id); const resolvedLlmSelectedModel = await this._getResolvedLlmSelectedModel(chat.projectId, modelSelection); await assertBudgetNotExceeded(chat.projectId, resolvedLlmSelectedModel.provider); @@ -106,7 +98,7 @@ export class AgentService { const agentSettings = await projectQueries.getAgentSettings(chat.projectId); const toolContext = await this._getToolContext(chat.projectId, chat.id, chat.userId, agentSettings); const webTools = await this._resolveWebTools(chat.projectId, resolvedLlmSelectedModel.provider, agentSettings); - const agentTools = getTools(agentSettings, webTools ?? undefined, excludeTools); + const agentTools = getTools(agentSettings, webTools ?? undefined); const agent = new AgentManager( chat, modelConfig, @@ -574,68 +566,6 @@ class AgentManager { } } - async streamWithProgress( - uiMessages: UIMessage[], - onEvent: (event: AgentProgressEvent) => Promise, - ): Promise { - const startTime = performance.now(); - const messages = await this._buildModelMessages(uiMessages); - const responseParts: UIMessagePart[] = []; - const pendingToolCalls = new Map(); - - try { - const result = await this._agent.stream({ - messages, - abortSignal: this._abortController.signal, - }); - - for await (const part of result.fullStream) { - if (part.type === 'tool-call') { - pendingToolCalls.set(part.toolCallId, { toolName: part.toolName, args: part.input }); - await onEvent({ type: 'tool-call', toolName: part.toolName, toolCallId: part.toolCallId }); - } else if (part.type === 'tool-result') { - const pending = pendingToolCalls.get(part.toolCallId); - if (pending) { - responseParts.push({ - type: `tool-${pending.toolName}`, - toolName: pending.toolName, - toolCallId: part.toolCallId, - state: 'output-available', - input: pending.args, - output: part.output, - } as unknown as UIMessagePart); - pendingToolCalls.delete(part.toolCallId); - } - await onEvent({ type: 'tool-result', toolName: part.toolName, toolCallId: part.toolCallId }); - } - } - - const text = await result.text; - if (text) { - responseParts.push({ type: 'text', text }); - } - - const durationMs = Math.round(performance.now() - startTime); - const usage = convertToTokenUsage(await result.totalUsage); - const cost = convertToCost(usage, this._modelSelection.provider, this._modelSelection.modelId); - const finishReason = (await result.finishReason) ?? 'stop'; - const steps = await result.steps; - - return { - text, - usage, - cost, - finishReason, - durationMs, - responseMessages: [], - steps: steps as AgentRunResult['steps'], - responseParts, - }; - } finally { - this._onDispose(); - } - } - checkIsUserOwner(userId: string): boolean { return this.chat.userId === userId; } diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 226e6051e..066b1bd96 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@ai-sdk/react": "^3.0.99", + "@better-auth/oauth-provider": "^1.6.3", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", diff --git a/apps/frontend/src/lib/auth-client.ts b/apps/frontend/src/lib/auth-client.ts index d8b83bcf8..754cf6ed0 100644 --- a/apps/frontend/src/lib/auth-client.ts +++ b/apps/frontend/src/lib/auth-client.ts @@ -1,8 +1,10 @@ +import { oauthProviderClient } from '@better-auth/oauth-provider/client'; import { createAuthClient } from 'better-auth/react'; import { inferAdditionalFields } from 'better-auth/client/plugins'; export const authClient = createAuthClient({ plugins: [ + oauthProviderClient(), inferAdditionalFields({ user: { requiresPasswordReset: { diff --git a/apps/frontend/src/routeTree.gen.ts b/apps/frontend/src/routeTree.gen.ts index d7bc84465..48633cb08 100644 --- a/apps/frontend/src/routeTree.gen.ts +++ b/apps/frontend/src/routeTree.gen.ts @@ -13,6 +13,7 @@ import { Route as SignupRouteImport } from './routes/signup' import { Route as ResetPasswordRouteImport } from './routes/reset-password' import { Route as LoginRouteImport } from './routes/login' import { Route as ForgotPasswordRouteImport } from './routes/forgot-password' +import { Route as ConsentRouteImport } from './routes/consent' import { Route as SidebarLayoutRouteImport } from './routes/_sidebar-layout' import { Route as SidebarLayoutSettingsRouteImport } from './routes/_sidebar-layout.settings' import { Route as SidebarLayoutChatLayoutRouteImport } from './routes/_sidebar-layout._chat-layout' @@ -66,6 +67,11 @@ const ForgotPasswordRoute = ForgotPasswordRouteImport.update({ path: '/forgot-password', getParentRoute: () => rootRouteImport, } as any) +const ConsentRoute = ConsentRouteImport.update({ + id: '/consent', + path: '/consent', + getParentRoute: () => rootRouteImport, +} as any) const SidebarLayoutRoute = SidebarLayoutRouteImport.update({ id: '/_sidebar-layout', getParentRoute: () => rootRouteImport, @@ -256,6 +262,7 @@ const SidebarLayoutStoriesPreviewChatIdStorySlugRoute = export interface FileRoutesByFullPath { '/': typeof SidebarLayoutChatLayoutIndexRoute + '/consent': typeof ConsentRoute '/forgot-password': typeof ForgotPasswordRoute '/login': typeof LoginRoute '/reset-password': typeof ResetPasswordRoute @@ -292,6 +299,7 @@ export interface FileRoutesByFullPath { } export interface FileRoutesByTo { '/': typeof SidebarLayoutChatLayoutIndexRoute + '/consent': typeof ConsentRoute '/forgot-password': typeof ForgotPasswordRoute '/login': typeof LoginRoute '/reset-password': typeof ResetPasswordRoute @@ -327,6 +335,7 @@ export interface FileRoutesByTo { export interface FileRoutesById { __root__: typeof rootRouteImport '/_sidebar-layout': typeof SidebarLayoutRouteWithChildren + '/consent': typeof ConsentRoute '/forgot-password': typeof ForgotPasswordRoute '/login': typeof LoginRoute '/reset-password': typeof ResetPasswordRoute @@ -367,6 +376,7 @@ export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/' + | '/consent' | '/forgot-password' | '/login' | '/reset-password' @@ -403,6 +413,7 @@ export interface FileRouteTypes { fileRoutesByTo: FileRoutesByTo to: | '/' + | '/consent' | '/forgot-password' | '/login' | '/reset-password' @@ -437,6 +448,7 @@ export interface FileRouteTypes { id: | '__root__' | '/_sidebar-layout' + | '/consent' | '/forgot-password' | '/login' | '/reset-password' @@ -476,6 +488,7 @@ export interface FileRouteTypes { } export interface RootRouteChildren { SidebarLayoutRoute: typeof SidebarLayoutRouteWithChildren + ConsentRoute: typeof ConsentRoute ForgotPasswordRoute: typeof ForgotPasswordRoute LoginRoute: typeof LoginRoute ResetPasswordRoute: typeof ResetPasswordRoute @@ -512,6 +525,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ForgotPasswordRouteImport parentRoute: typeof rootRouteImport } + '/consent': { + id: '/consent' + path: '/consent' + fullPath: '/consent' + preLoaderRoute: typeof ConsentRouteImport + parentRoute: typeof rootRouteImport + } '/_sidebar-layout': { id: '/_sidebar-layout' path: '' @@ -865,6 +885,7 @@ const SidebarLayoutRouteWithChildren = SidebarLayoutRoute._addFileChildren( const rootRouteChildren: RootRouteChildren = { SidebarLayoutRoute: SidebarLayoutRouteWithChildren, + ConsentRoute: ConsentRoute, ForgotPasswordRoute: ForgotPasswordRoute, LoginRoute: LoginRoute, ResetPasswordRoute: ResetPasswordRoute, diff --git a/apps/frontend/src/routes/consent.tsx b/apps/frontend/src/routes/consent.tsx new file mode 100644 index 000000000..4aa856e8c --- /dev/null +++ b/apps/frontend/src/routes/consent.tsx @@ -0,0 +1,138 @@ +import { createFileRoute } from '@tanstack/react-router'; +import { useMutation, useQuery } from '@tanstack/react-query'; + +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; +import { ErrorMessage } from '@/components/ui/error-message'; +import { Spinner } from '@/components/ui/spinner'; +import NaoLogo from '@/components/icons/nao-full-logo.svg'; +import { authClient } from '@/lib/auth-client'; + +interface PublicClient { + client_id: string; + client_name?: string; + client_uri?: string; + logo_uri?: string; + tos_uri?: string; + policy_uri?: string; +} + +export const Route = createFileRoute('/consent')({ + validateSearch: (search: Record) => ({ + client_id: typeof search.client_id === 'string' ? search.client_id : undefined, + scope: typeof search.scope === 'string' ? search.scope : undefined, + code: typeof search.code === 'string' ? search.code : undefined, + }), + component: Consent, +}); + +function Consent() { + const { client_id, scope } = Route.useSearch(); + + const clientQuery = useQuery({ + queryKey: ['oauth-public-client', client_id], + enabled: Boolean(client_id), + queryFn: async (): Promise => { + const response = await fetch( + `/api/auth/oauth2/public-client?client_id=${encodeURIComponent(client_id ?? '')}`, + { credentials: 'include' }, + ); + if (!response.ok) { + throw new Error('Could not load application details.'); + } + return response.json(); + }, + }); + + const decision = useMutation({ + mutationFn: async (accept: boolean) => { + const result = await authClient.oauth2.consent({ accept }); + const url = result?.data?.url; + if (url) { + window.location.href = url; + } + }, + }); + + if (!client_id) { + return ( + + + + ); + } + + if (clientQuery.isPending) { + return ( + +
+ +
+
+ ); + } + + if (clientQuery.isError) { + return ( + + + + ); + } + + const client = clientQuery.data; + const clientLabel = client.client_name ?? client.client_id; + const requestedScopes = (scope ?? '').split(/\s+/).filter(Boolean); + + return ( + + + + Authorize {clientLabel} + {clientLabel} is requesting access to your nao account. + + + {requestedScopes.length > 0 && ( +
+

The application is requesting:

+
    + {requestedScopes.map((scopeName) => ( +
  • {scopeName}
  • + ))} +
+
+ )} + {client.client_uri && ( +

+ Application website:{' '} + + {client.client_uri} + +

+ )} +
+ + + + +
+
+ ); +} + +function CenteredCard({ children }: { children: React.ReactNode }) { + return ( +
+
+ + +

Authorize

+
+ {children} +
+ ); +} diff --git a/apps/frontend/src/routes/login.tsx b/apps/frontend/src/routes/login.tsx index 125b75abe..c0ba4a424 100644 --- a/apps/frontend/src/routes/login.tsx +++ b/apps/frontend/src/routes/login.tsx @@ -13,12 +13,12 @@ export const Route = createFileRoute('/login')({ component: Login, }); -function buildMcpAuthorizeUrl() { +function buildOAuthAuthorizeUrl() { const params = new URLSearchParams(window.location.search); if (!params.has('client_id')) { return null; } - return `/api/auth/mcp/authorize${window.location.search}`; + return `/api/auth/oauth2/authorize${window.location.search}`; } function Login() { @@ -29,7 +29,7 @@ function Login() { const config = useQuery(trpc.system.getPublicConfig.queryOptions()); const isCloud = config.data?.naoMode === 'cloud'; - const mcpAuthorizeUrl = buildMcpAuthorizeUrl(); + const oauthAuthorizeUrl = buildOAuthAuthorizeUrl(); const form = useForm({ defaultValues: { email: '', password: '' }, @@ -37,8 +37,8 @@ function Login() { setServerError(undefined); await signIn.email(value, { onSuccess: () => { - if (mcpAuthorizeUrl) { - window.location.href = mcpAuthorizeUrl; + if (oauthAuthorizeUrl) { + window.location.href = oauthAuthorizeUrl; } else { navigate({ to: '/' }); } @@ -55,7 +55,7 @@ function Login() { submitText='Log In' serverError={serverError} displaySocialProviders={true} - socialCallbackUrl={mcpAuthorizeUrl ?? undefined} + socialCallbackUrl={oauthAuthorizeUrl ?? undefined} footer={ isCloud ? ( <> diff --git a/package-lock.json b/package-lock.json index b4941cde5..9d5ebc1c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "@ai-sdk/openai": "^3.0.1", "@aws-sdk/credential-providers": "^3.1030.0", "@azure/identity": "^4.13.0", + "@better-auth/oauth-provider": "^1.6.3", "@chat-adapter/slack": "^4.15.0", "@chat-adapter/state-memory": "^4.15.0", "@chat-adapter/state-redis": "^4.23.0", @@ -221,6 +222,7 @@ "name": "@nao/frontend", "dependencies": { "@ai-sdk/react": "^3.0.99", + "@better-auth/oauth-provider": "^1.6.3", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", @@ -1887,6 +1889,23 @@ } } }, + "node_modules/@better-auth/oauth-provider": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@better-auth/oauth-provider/-/oauth-provider-1.6.3.tgz", + "integrity": "sha512-O3yEZ5ZYPIzkoXcNPp4iQkhvk7MS5sxfiY/IHIAEu9GDhiq1O5L0A0FMvDoHN2tXIJyGPzcNMM3PQSUsry9pWA==", + "license": "MIT", + "dependencies": { + "jose": "^6.1.3", + "zod": "^4.3.6" + }, + "peerDependencies": { + "@better-auth/core": "^1.6.3", + "@better-auth/utils": "0.4.0", + "@better-fetch/fetch": "1.1.21", + "better-auth": "^1.6.3", + "better-call": "1.3.5" + } + }, "node_modules/@better-auth/prisma-adapter": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/@better-auth/prisma-adapter/-/prisma-adapter-1.6.3.tgz", @@ -11720,6 +11739,7 @@ "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.6.3.tgz", "integrity": "sha512-jMsoSYQyO8nNRuLEoCP+OUShLyeIGU8ioPYqra0IteLjnS3WNjHj21YE/COSJ/V/f0H5SInZiF+uXcEEHREDMQ==", "license": "MIT", + "peer": true, "dependencies": { "@better-auth/core": "1.6.3", "@better-auth/drizzle-adapter": "1.6.3", From 15ad3d0193c4ccfdf92e21c7ad5063f967071293 Mon Sep 17 00:00:00 2001 From: socallmebertille Date: Thu, 7 May 2026 16:25:05 +0200 Subject: [PATCH 09/11] transitive deps de better-auth on 1.6.3 to match the oauth-provider version --- apps/backend/package.json | 2 +- apps/frontend/package.json | 2 +- bun.lock | 552 +++++++++++++++++++++++++++++++------ package-lock.json | 4 +- 4 files changed, 471 insertions(+), 89 deletions(-) diff --git a/apps/backend/package.json b/apps/backend/package.json index 13c264864..c65b05dca 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -73,7 +73,7 @@ "@vscode/ripgrep": "^1.17.0", "ai": "^6.0.97", "ai-sdk-ollama": "^3.7.1", - "better-auth": "^1.4.10", + "better-auth": "^1.6.3", "better-sqlite3": "^12.5.0", "chat": "^4.15.0", "cheerio": "^1.2.0", diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 066b1bd96..f92b61ed9 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -44,7 +44,7 @@ "@trpc/client": "^11.8.1", "@trpc/tanstack-react-query": "^11.8.1", "ai": "^6.0.97", - "better-auth": "^1.4.10", + "better-auth": "^1.6.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", diff --git a/bun.lock b/bun.lock index f8a0ba6ed..2a5fdd5ff 100644 --- a/bun.lock +++ b/bun.lock @@ -6,6 +6,8 @@ "name": "nao-chat", "dependencies": { "@boxlite-ai/boxlite": "^0.3.0", + "@emnapi/core": "^1.9.2", + "@emnapi/runtime": "^1.9.2", "@pydantic/monty": "^0.0.7", "depd": "^2.0.0", }, @@ -23,11 +25,14 @@ "dependencies": { "@ai-sdk/amazon-bedrock": "^4.0.69", "@ai-sdk/anthropic": "^3.0.15", + "@ai-sdk/azure": "^3.0.49", "@ai-sdk/google": "^3.0.16", "@ai-sdk/google-vertex": "^4.0.80", "@ai-sdk/mistral": "^3.0.13", "@ai-sdk/openai": "^3.0.1", + "@aws-sdk/credential-providers": "^3.1030.0", "@azure/identity": "^4.13.0", + "@better-auth/oauth-provider": "^1.6.3", "@chat-adapter/slack": "^4.15.0", "@chat-adapter/state-memory": "^4.15.0", "@chat-adapter/state-redis": "^4.23.0", @@ -36,19 +41,22 @@ "@chat-adapter/whatsapp": "^4.21.0", "@duckdb/node-api": "^1.4.4-r.1", "@fastify/formbody": "^8.0.2", + "@fastify/multipart": "^10.0.0", "@fastify/static": "^8.1.0", "@microsoft/microsoft-graph-client": "^3.0.7", + "@modelcontextprotocol/sdk": "^1.29.0", "@nao/shared": "*", "@openrouter/ai-sdk-provider": "^2.2.3", "@pydantic/monty": "^0.0.7", "@resvg/resvg-js": "^2.6.2", + "@slack/socket-mode": "^2.0.6", "@slack/web-api": "^7.13.0", "@trpc/server": "^11.8.1", "@types/nodemailer": "^7.0.9", "@vscode/ripgrep": "^1.17.0", "ai": "^6.0.97", "ai-sdk-ollama": "^3.7.1", - "better-auth": "^1.4.10", + "better-auth": "^1.6.3", "better-sqlite3": "^12.5.0", "chat": "^4.15.0", "cheerio": "^1.2.0", @@ -61,6 +69,8 @@ "fastify-raw-body": "^5.0.0", "fastify-type-provider-zod": "^6.1.0", "gray-matter": "^4.0.3", + "jose": "^6.2.2", + "js-yaml": "^4.1.1", "mcporter": "^0.7.3", "minimatch": "^10.1.1", "node-sql-parser": "^5.4.0", @@ -69,6 +79,7 @@ "postgres": "^3.4.8", "posthog-node": "^5.21.2", "prompt-mentions": "^0.0.32", + "puppeteer-core": "^24.40.0", "react": "^19.2.4", "react-dom": "^19.2.4", "superjson": "^2.2.6", @@ -78,6 +89,7 @@ "@eslint/js": "^9.18.0", "@types/better-sqlite3": "^7.6.13", "@types/bun": "^1.3.5", + "@types/js-yaml": "^4.0.9", "@types/node": "^25.0.3", "@types/pg": "^8.16.0", "drizzle-kit": "^0.31.8", @@ -96,6 +108,7 @@ "name": "@nao/frontend", "dependencies": { "@ai-sdk/react": "^3.0.99", + "@better-auth/oauth-provider": "^1.6.3", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", @@ -126,11 +139,12 @@ "@trpc/client": "^11.8.1", "@trpc/tanstack-react-query": "^11.8.1", "ai": "^6.0.97", - "better-auth": "^1.4.10", + "better-auth": "^1.6.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", + "fuse.js": "^7.3.0", "jszip": "^3.10.1", "lucide-react": "^0.562.0", "posthog-js": "^1.336.4", @@ -185,11 +199,13 @@ "packages": { "@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.69", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.50", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.16", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XdsJnMa1DZAQJ4bRuy97JYF48fSJUZWUpjg4rrDrzKY6wUAOIKpHMHmnGF6bCpd3NcR6E2R7c/HhWyIQAHuuUg=="], - "@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.50", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.16" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-BkCUgGTp/iZJuuFBF1wv7GGnrEJg7X7hqbaa+/t4HTBt9dZn3e6NFn5NhPUvo2p5SreUeHEl0As0r2uaVn3K9Q=="], + "@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.66", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yJpQ2x6ACwbXo5D6HsVWd2FFnnWcetfGx4oxkG66P8FawusvrY2vL2qMiiNTruWrxEYDy+YHc3ctv8C769MMJA=="], + + "@ai-sdk/azure": ["@ai-sdk/azure@3.0.63", "", { "dependencies": { "@ai-sdk/openai": "3.0.62", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.26" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-sPGxn5Wliht1o7SSc3agHC4YIluRIKHnQF77FVFQEjep+f/KnZ1VdjhT8LIqAcam8FA4PB/XUMrfuhfnESqHCg=="], "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.59", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.16", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MbtheWHgEFV/8HL1Z6E3hOAsmP73zZlNFg0F0nJAD0Adnjp4J/plqNK00Y896d+dWTw+r0OXzyov9/2wCFjH0Q=="], - "@ai-sdk/google": ["@ai-sdk/google@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "3.0.7", "@ai-sdk/provider-utils": "4.0.13" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bVGsulEr6JiipAFlclo9bjL5WaUV0iCSiiekLt+PY6pwmtJeuU2GaD9DoE3OqR8LN2W779mU13IhVEzlTupf8g=="], + "@ai-sdk/google": ["@ai-sdk/google@3.0.58", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7P7s8g/FoIxesx2y32eK8idAMLOFHN2f4gs5KYi8q2QaScuubXFjgFMFqbjYF5bc92akiOd/C6OG0vIDlV7t2Q=="], "@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.102", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.66", "@ai-sdk/google": "3.0.58", "@ai-sdk/openai-compatible": "2.0.38", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.22", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-3tcZznwjijhORYxrLlhb8Oz78JOTahUmjuQPM47Q44Tay1nLcoyxjIxGeP4UBlkGVAoLAUMmx4o/R8B+XYreuQ=="], @@ -215,9 +231,71 @@ "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], + "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], + + "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], + + "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], + "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], - "@aws-sdk/types": ["@aws-sdk/types@3.973.4", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q=="], + "@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.1044.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.8", "@aws-sdk/credential-provider-node": "^3.972.39", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.38", "@aws-sdk/region-config-resolver": "^3.972.13", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.24", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", "@smithy/middleware-retry": "^4.5.7", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.6.1", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.49", "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.6", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-zwjCKDWInJvQgxMRBgCabzRZ0vQrqjPsakkbZdBIWKPOr2MVQLJmfmGI99iwDTskeSwCsh5lRjAdiQJgqN7VRQ=="], + + "@aws-sdk/core": ["@aws-sdk/core@3.974.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws-sdk/xml-builder": "^3.972.22", "@smithy/core": "^3.23.17", "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.6", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-njR2qoG6ZuB0kvAS2FyICsFZJ6gmCcf2X/7JcD14sUvGDm26wiZ5BrA6LOiUxKFEF+IVe7kdroxyE00YlkiYsw=="], + + "@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.31", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.997.6", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-W5JtzDp3ejzhOOknXlnt+vJsNN2GZdAcBK+hR7HQ1DCacXqS0UpmnIyihIU7CK0IB+XYWeBaN3bBv4pXavp7Vg=="], + + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.34", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-XT0jtf8Fw9JE6ppsQeoNnZRiG+jqRixMT1v1ZR17G60UvVdsQmTG8nbEyHuEPfMxDXEhfdARaM/XiEhca4lGHQ=="], + + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.36", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/types": "^3.973.8", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.6.1", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/util-stream": "^4.5.25", "tslib": "^2.6.2" } }, "sha512-DPoGWfy7J7RKxvbf5kOKIGQkD2ek3dbKgzKIGrnLuvZBz5myU+Im/H6pmc14QcnFbqHMqxvtWSgRDSJW3qXLQg=="], + + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.38", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/credential-provider-env": "^3.972.34", "@aws-sdk/credential-provider-http": "^3.972.36", "@aws-sdk/credential-provider-login": "^3.972.38", "@aws-sdk/credential-provider-process": "^3.972.34", "@aws-sdk/credential-provider-sso": "^3.972.38", "@aws-sdk/credential-provider-web-identity": "^3.972.38", "@aws-sdk/nested-clients": "^3.997.6", "@aws-sdk/types": "^3.973.8", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-oDzUBu2MGJFgoar05sPMCwSrhw44ASyccrHzj66vO69OZqi7I6hZZxXfuPLC8OCzW7C+sU+bI73XHij41yekgQ=="], + + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.38", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/nested-clients": "^3.997.6", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-g1NosS8qe4OF++G2UFCM5ovSkgipC7YYor5KCWatG0UoMSO5YFj9C8muePlyVmOBV/WTI16Jo3/s1NUo/o1Bww=="], + + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.39", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.34", "@aws-sdk/credential-provider-http": "^3.972.36", "@aws-sdk/credential-provider-ini": "^3.972.38", "@aws-sdk/credential-provider-process": "^3.972.34", "@aws-sdk/credential-provider-sso": "^3.972.38", "@aws-sdk/credential-provider-web-identity": "^3.972.38", "@aws-sdk/types": "^3.973.8", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-HEswDQyxUtadoZ/bJsPPENHg7R0Lzym5LuMksJeHvqhCOpP+rtkDLKI4/ZChH4w3cf5kG8n6bZuI8PzajoiqMg=="], + + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.34", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-T3IFs4EVmVi1dVN5RciFnklCANSzvrQd/VuHY9ThHSQmYkTogjcGkoJEr+oNUPQZnso52183088NqysMPji1/Q=="], + + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.38", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/nested-clients": "^3.997.6", "@aws-sdk/token-providers": "3.1041.0", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-5ZxG+t0+3Q3QPh8KEjX6syskhgNf7I0MN7oGioTf6Lm1NTjfP7sIcYGNsthXC2qR8vcD3edNZwCr2ovfSSWuRA=="], + + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.38", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/nested-clients": "^3.997.6", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-lYHFF30DGI20jZcYX8cm6Ns0V7f1dDN6g/MBDLTyD/5iw+bXs3yBr2iAiHDkx4RFU5JgsnZvCHYKiRVPRdmOgw=="], + + "@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.1044.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.1044.0", "@aws-sdk/core": "^3.974.8", "@aws-sdk/credential-provider-cognito-identity": "^3.972.31", "@aws-sdk/credential-provider-env": "^3.972.34", "@aws-sdk/credential-provider-http": "^3.972.36", "@aws-sdk/credential-provider-ini": "^3.972.38", "@aws-sdk/credential-provider-login": "^3.972.38", "@aws-sdk/credential-provider-node": "^3.972.39", "@aws-sdk/credential-provider-process": "^3.972.34", "@aws-sdk/credential-provider-sso": "^3.972.38", "@aws-sdk/credential-provider-web-identity": "^3.972.38", "@aws-sdk/nested-clients": "^3.997.6", "@aws-sdk/types": "^3.973.8", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-iD3cFhifkB6Z3RfaS296PPfKemS/cghofhL/3ae1VLPTKYqUgMMRsSN0Xz9pm3kxa+0QkNEfM+ItCf2n2TKP+Q=="], + + "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg=="], + + "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ=="], + + "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ=="], + + "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.37", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/core": "^3.23.17", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-stream": "^4.5.25", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Km7M+i8DrLArVzrid1gfxeGhYHBd3uxvE77g0s5a52zPSVosxzQBnJ0gwWb6NIp/DOk8gsBMhi7V+cpJG0ndTA=="], + + "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.38", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@smithy/core": "^3.23.17", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-retry": "^4.3.6", "tslib": "^2.6.2" } }, "sha512-iz+B29TXcAZsJpwB+AwG/TTGA5l/VnmMZ2UxtiySOZjI6gCdmviXPwdgzcmuazMy16rXoPY4mYCGe7zdNKfx5A=="], + + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.6", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.8", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.38", "@aws-sdk/region-config-resolver": "^3.972.13", "@aws-sdk/signature-v4-multi-region": "^3.996.25", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.24", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", "@smithy/middleware-retry": "^4.5.7", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.6.1", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.49", "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.6", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-WBDnqatJl+kGObpfmfSxqnXeYTu3Me8wx8WCtvoxX3pfWrrTv8I4WTMSSs7PZqcRcVh8WeUKMgGFjMG+52SR1w=="], + + "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.13", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/config-resolver": "^4.4.17", "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A=="], + + "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.25", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.37", "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-+CMIt3e1VzlklAECmG+DtP1sV8iKq25FuA0OKpnJ4KA0kxUtd7CgClY7/RU6VzJBQwbN4EJ9Ue6plvqx1qGadw=="], + + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1041.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.8", "@aws-sdk/nested-clients": "^3.997.6", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Th7kPI6YPtvJUcdznooXJMy+9rQWjmEF81LxaJssngBzuysK4a/x+l8kjm1zb7nYsUPbndnBdUnwng/3PLvtGw=="], + + "@aws-sdk/types": ["@aws-sdk/types@3.973.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw=="], + + "@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA=="], + + "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-endpoints": "^3.4.2", "tslib": "^2.6.2" } }, "sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g=="], + + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.5", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ=="], + + "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g=="], + + "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.24", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.38", "@aws-sdk/types": "^3.973.8", "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-ZWwlkjcIp7cEL8ZfTpTAPNkwx25p7xol0xlKoWVVf22+nsjwmLcHYtTPjIV1cSpmB/b6DaK4cb1fSkvCXHgRdw=="], + + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.22", "", { "dependencies": { "@nodable/entities": "2.1.0", "@smithy/types": "^4.14.1", "fast-xml-parser": "5.7.2", "tslib": "^2.6.2" } }, "sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA=="], + + "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="], "@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], @@ -287,11 +365,23 @@ "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - "@better-auth/core": ["@better-auth/core@1.4.10", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "zod": "^4.1.12" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "better-call": "1.1.7", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-AThrfb6CpG80wqkanfrbN2/fGOYzhGladHFf3JhaWt/3/Vtf4h084T6PJLrDE7M/vCCGYvDI1DkvP3P1OB2HAg=="], + "@better-auth/core": ["@better-auth/core@1.6.9", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types", "@opentelemetry/api"] }, "sha512-ADFk5pwmLybmc+LvYvXJ6M1x2oY/EyYLkwLuH0x28FUq12DfjL0wnE7g+WRDf3yozDO+qIxTpFGXDGwLKbfz0w=="], + + "@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.6.9", "", { "peerDependencies": { "@better-auth/core": "^1.6.9", "@better-auth/utils": "0.4.0", "drizzle-orm": "^0.45.2" }, "optionalPeers": ["drizzle-orm"] }, "sha512-Lcco5hOGrMgc4XKAkvB6x72eQm4wCcya8IevMg4wBHY9W9GVg8pu23rpRX6VsVQSO4Ux13S7lFwUWtF7/r9aKw=="], + + "@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.6.9", "", { "peerDependencies": { "@better-auth/core": "^1.6.9", "@better-auth/utils": "0.4.0", "kysely": "^0.28.14" }, "optionalPeers": ["kysely"] }, "sha512-gyjuuxJtZ4o9G9z9q4kqn24X2kvMSp7F+KHogYxF03SnXY/2WleAcuj57iC4wP3e9mGDbjPOrnM5K6Kr3Ktdpw=="], + + "@better-auth/memory-adapter": ["@better-auth/memory-adapter@1.6.9", "", { "peerDependencies": { "@better-auth/core": "^1.6.9", "@better-auth/utils": "0.4.0" } }, "sha512-XmIG4tUnOXZ+KEcWjHUjOI9Z5donD09dC2t/AQTXifAUIqx7cySg86w0KTM09ArzAxRx1fCqO36Wkt5nULnrkQ=="], - "@better-auth/telemetry": ["@better-auth/telemetry@1.4.10", "", { "dependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21" }, "peerDependencies": { "@better-auth/core": "1.4.10" } }, "sha512-Dq4XJX6EKsUu0h3jpRagX739p/VMOTcnJYWRrLtDYkqtZFg+sFiFsSWVcfapZoWpRSUGYX9iKwl6nDHn6Ju2oQ=="], + "@better-auth/mongo-adapter": ["@better-auth/mongo-adapter@1.6.9", "", { "peerDependencies": { "@better-auth/core": "^1.6.9", "@better-auth/utils": "0.4.0", "mongodb": "^6.0.0 || ^7.0.0" }, "optionalPeers": ["mongodb"] }, "sha512-h+AiRJ/TsBSi+ZDjySASBpbJ/9QCXBre34PSKgCz7QmTHrFM9Cg2EM4AM7LjR5lPXipEE+2rWPBc9wfnUBjhcw=="], - "@better-auth/utils": ["@better-auth/utils@0.3.0", "", {}, "sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw=="], + "@better-auth/oauth-provider": ["@better-auth/oauth-provider@1.6.9", "", { "dependencies": { "jose": "^6.1.3", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/core": "^1.6.9", "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "better-auth": "^1.6.9", "better-call": "1.3.5" } }, "sha512-GJCRDLu7xOc/HcAuQXaFZ9xZo8l3yLuc+1/vKYB5gh0O+owub+vLH88+AfNm/jMHZ084MSHpgkyxmEnmBXe4iQ=="], + + "@better-auth/prisma-adapter": ["@better-auth/prisma-adapter@1.6.9", "", { "peerDependencies": { "@better-auth/core": "^1.6.9", "@better-auth/utils": "0.4.0", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@prisma/client", "prisma"] }, "sha512-XHks01ntK20orqK/jICq8wmEbJ/zT6dct49Fk8zTQKN9QNGDc+Ix5+7z/Kvui0DXGFf790GfvRozquzaLtXa8Q=="], + + "@better-auth/telemetry": ["@better-auth/telemetry@1.6.9", "", { "peerDependencies": { "@better-auth/core": "^1.6.9", "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21" } }, "sha512-0u5zkhSCAQFoN3DHvUkLHOF6MBbVTDAa6mU8mhPwiysdz1x21vMzhzfaAKN/ZGWaQ09v91/F+2qu42G/bhUV4A=="], + + "@better-auth/utils": ["@better-auth/utils@0.4.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-RpMtLUIQAEWMgdPLNVbIF5ON2mm+CH0U3rCdUCU1VyeAUui4m38DyK7/aXMLZov2YDjG684pS1D0MBllrmgjQA=="], "@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="], @@ -363,11 +453,11 @@ "@duckdb/node-bindings-win32-x64": ["@duckdb/node-bindings-win32-x64@1.4.4-r.1", "", { "os": "win32", "cpu": "x64" }, "sha512-+J+MUYGvYWfX0balWToDIy3CBYg7hHI0KQUQ39+SniinXlMF8+puRW6ebyQ+AXrcrKkwuj4wzJuEBD0AdhHGtw=="], - "@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], + "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], - "@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], + "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], @@ -447,6 +537,10 @@ "@fastify/ajv-compiler": ["@fastify/ajv-compiler@4.0.5", "", { "dependencies": { "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0" } }, "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A=="], + "@fastify/busboy": ["@fastify/busboy@3.2.0", "", {}, "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA=="], + + "@fastify/deepmerge": ["@fastify/deepmerge@3.2.1", "", {}, "sha512-N5Oqvltoa2r9z1tbx4xjky0oRR60v+T47Ic4J1ukoVQcptLOrIdRnCSdTGmOmajZuHVKlTnfcmrjyqsGEW1ztA=="], + "@fastify/error": ["@fastify/error@4.2.0", "", {}, "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ=="], "@fastify/fast-json-stringify-compiler": ["@fastify/fast-json-stringify-compiler@5.0.3", "", { "dependencies": { "fast-json-stringify": "^6.0.0" } }, "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ=="], @@ -457,6 +551,8 @@ "@fastify/merge-json-schemas": ["@fastify/merge-json-schemas@0.2.1", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A=="], + "@fastify/multipart": ["@fastify/multipart@10.0.0", "", { "dependencies": { "@fastify/busboy": "^3.0.0", "@fastify/deepmerge": "^3.0.0", "@fastify/error": "^4.0.0", "fastify-plugin": "^5.0.0", "secure-json-parse": "^4.0.0" } }, "sha512-pUx3Z1QStY7E7kwvDTIvB6P+rF5lzP+iqPgZyJyG3yBJVPvQaZxzDHYbQD89rbY0ciXrMOyGi8ezHDVexLvJDA=="], + "@fastify/proxy-addr": ["@fastify/proxy-addr@5.1.0", "", { "dependencies": { "@fastify/forwarded": "^3.0.0", "ipaddr.js": "^2.1.0" } }, "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw=="], "@fastify/send": ["@fastify/send@4.1.0", "", { "dependencies": { "@lukeed/ms": "^2.0.2", "escape-html": "~1.0.3", "fast-decode-uri-component": "^1.0.1", "http-errors": "^2.0.0", "mime": "^3" } }, "sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw=="], @@ -541,7 +637,7 @@ "@microsoft/microsoft-graph-client": ["@microsoft/microsoft-graph-client@3.0.7", "", { "dependencies": { "@babel/runtime": "^7.12.5", "tslib": "^2.2.0" } }, "sha512-/AazAV/F+HK4LIywF9C+NYHcJo038zEnWkteilcxC1FM/uK/4NVGDKGrxx7nNq1ybspAroRKT4I1FHfxQzxkUw=="], - "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.26.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg=="], + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="], "@monaco-editor/loader": ["@monaco-editor/loader@1.7.0", "", { "dependencies": { "state-local": "^1.0.6" } }, "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA=="], @@ -561,6 +657,8 @@ "@noble/hashes": ["@noble/hashes@2.0.1", "", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="], + "@nodable/entities": ["@nodable/entities@2.1.0", "", {}, "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA=="], + "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.2.3", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-NovC+BaCfEeJwhToDrs8JeDYXXlJdEyz7lcxkjtyePSE4eoAKik872SyDK0MzXKcz8MRkv7XlNhPI6zz4TQp0g=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], @@ -613,6 +711,8 @@ "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], + "@puppeteer/browsers": ["@puppeteer/browsers@2.13.1", "", { "dependencies": { "debug": "^4.4.3", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.7.4", "tar-fs": "^3.1.1", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-zmS4RTK9fbrc++WlAJhxYbfz3IjDeOmkK/CwwbLmk7ydfS9e2CiEeRJHEPvjDVElO/bwXbidwGA37Bsm6LzCnQ=="], + "@pydantic/monty": ["@pydantic/monty@0.0.7", "", { "optionalDependencies": { "@pydantic/monty-darwin-arm64": "0.0.7", "@pydantic/monty-darwin-x64": "0.0.7", "@pydantic/monty-linux-arm64-gnu": "0.0.7", "@pydantic/monty-linux-x64-gnu": "0.0.7", "@pydantic/monty-wasm32-wasi": "0.0.7", "@pydantic/monty-win32-x64-msvc": "0.0.7" } }, "sha512-xC2BbsauHDm6LTfqGHC9HlnUBClW0vJBe9kmSZgTPfqVngV09mPVmdsTDjq5R0ZOxQScZpvInzq+RfgXYQGGGQ=="], "@pydantic/monty-darwin-arm64": ["@pydantic/monty-darwin-arm64@0.0.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+qncYK5TAPys/yn6ZJ6h3Tk1ToBjNYfLmCX1LameHMx9yWDYqk7+uRhJi5Xec5Wz4OARbftkXxEbHhPodr5ehA=="], @@ -859,24 +959,94 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg=="], - "@slack/logger": ["@slack/logger@4.0.0", "", { "dependencies": { "@types/node": ">=18.0.0" } }, "sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA=="], + "@slack/logger": ["@slack/logger@4.0.1", "", { "dependencies": { "@types/node": ">=18" } }, "sha512-6cmdPrV/RYfd2U0mDGiMK8S7OJqpCTm7enMLRR3edccsPX8j7zXTLnaEF4fhxxJJTAIOil6+qZrnUPTuaLvwrQ=="], + + "@slack/socket-mode": ["@slack/socket-mode@2.0.7", "", { "dependencies": { "@slack/logger": "^4.0.1", "@slack/web-api": "^7.15.0", "@types/node": ">=18", "@types/ws": "^8", "eventemitter3": "^5", "ws": "^8" } }, "sha512-qYy07je71WnEHgRwmw12DlAnZLi5HXmdlI2WUzUK2LH/rYXQpP6uEg462S5CwfE8FoCKUdIigHtYnOOfzZH1lQ=="], + + "@slack/types": ["@slack/types@2.20.1", "", {}, "sha512-eWX2mdt1ktpn8+40iiMc404uGrih+2fxiky3zBcPjtXKj6HLRdYlmhrPkJi7JTJm8dpXR6BWVWEDBXtaWMKD6A=="], + + "@slack/web-api": ["@slack/web-api@7.15.0", "", { "dependencies": { "@slack/logger": "^4.0.1", "@slack/types": "^2.20.1", "@types/node": ">=18", "@types/retry": "0.12.0", "axios": "^1.13.5", "eventemitter3": "^5.0.1", "form-data": "^4.0.4", "is-electron": "2.2.2", "is-stream": "^2", "p-queue": "^6", "p-retry": "^4", "retry": "^0.13.1" } }, "sha512-va7zYIt3QHG1x9M/jqXXRPFMoOVlVSSRHC5YH+DzKYsrz5xUKOA3lR4THsu/Zxha9N1jOndbKFKLtr0WOPW1Vw=="], - "@slack/types": ["@slack/types@2.19.0", "", {}, "sha512-7+QZ38HGcNh/b/7MpvPG6jnw7mliV6UmrquJLqgdxkzJgQEYUcEztvFWRU49z0x4vthF0ixL5lTK601AXrS8IA=="], + "@smithy/config-resolver": ["@smithy/config-resolver@4.4.17", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-TzDZcAnhTyAHbXVxWZo7/tEcrIeFq20IBk8So3OLOetWpR8EwY/yEqBMBFaJMeyEiREDq4NfEl+qO3OAUD+vbQ=="], - "@slack/web-api": ["@slack/web-api@7.13.0", "", { "dependencies": { "@slack/logger": "^4.0.0", "@slack/types": "^2.18.0", "@types/node": ">=18.0.0", "@types/retry": "0.12.0", "axios": "^1.11.0", "eventemitter3": "^5.0.1", "form-data": "^4.0.4", "is-electron": "2.2.2", "is-stream": "^2", "p-queue": "^6", "p-retry": "^4", "retry": "^0.13.1" } }, "sha512-ERcExbWrnkDN8ovoWWe6Wgt/usanj1dWUd18dJLpctUI4mlPS0nKt81Joh8VI+OPbNnY1lIilVt9gdMBD9U2ig=="], + "@smithy/core": ["@smithy/core@3.23.17", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-stream": "^4.5.25", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-x7BlLbUFL8NWCGjMF9C+1N5cVCxcPa7g6Tv9B4A2luWx3be3oU8hQ96wIwxe/s7OhIzvoJH73HAUSg5JXVlEtQ=="], + + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.14", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg=="], "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.10", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-A4ynrsFFfSXUHicfTcRehytppFBcY3HQxEGYiyGktPIOye3Ot7fxpiy4VR42WmtGI4Wfo6OXt/c1Ky1nUFxYYQ=="], + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.17", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw=="], + + "@smithy/hash-node": ["@smithy/hash-node@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g=="], + + "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw=="], + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Yfu664Qbf1B4IYIsYgKoABt010daZjkaCRvdU/sPnZG6TtHOB0md0RjNdLGzxe5UIdn9js4ftPICzmkRa9RJ4Q=="], - "@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.14", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw=="], + + "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.32", "", { "dependencies": { "@smithy/core": "^3.23.17", "@smithy/middleware-serde": "^4.2.20", "@smithy/node-config-provider": "^4.3.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-ZZkgyjnJppiZbIm6Qbx92pbXYi1uzenIvGhBSCDlc7NwuAkiqSgS75j1czAD25ZLs2FjMjYy1q7gyRVWG6JA0Q=="], + + "@smithy/middleware-retry": ["@smithy/middleware-retry@4.5.7", "", { "dependencies": { "@smithy/core": "^3.23.17", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/service-error-classification": "^4.3.1", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.6", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-bRt6ZImqVSeTk39Nm81K20ObIiAZ3WefY7G6+iz/0tZjs4dgRRjvRX2sgsH+zi6iDCRR/aQvQofLKxxz4rPBZg=="], + + "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.20", "", { "dependencies": { "@smithy/core": "^3.23.17", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Lx9JMO9vArPtiChE3wbEZ5akMIDQpWQtlu90lhACQmNOXcGXRbaDywMHDzuDZ2OkZzP+9wQfZi3YJT9F67zTQQ=="], + + "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA=="], + + "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.14", "", { "dependencies": { "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg=="], + + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.6.1", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-iB+orM4x3xrr57X3YaXazfKnntl0LHlZB1kcXSGzMV1Tt0+YwEjGlbjk/44qEGtBzXAz6yFDzkYTKSV6Pj2HUg=="], + + "@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@smithy/protocol-http": ["@smithy/protocol-http@5.3.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ=="], + + "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A=="], + + "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw=="], + + "@smithy/service-error-classification": ["@smithy/service-error-classification@4.3.1", "", { "dependencies": { "@smithy/types": "^4.14.1" } }, "sha512-aUQuDGh760ts/8MU+APjIZhlLPKhIIfqyzZaJikLEIMrdxFvxuLYD0WxWzaYWpmLbQlXDe9p7EWM3HsBe0K6Gw=="], + + "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.9", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ=="], + + "@smithy/signature-v4": ["@smithy/signature-v4@5.3.14", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA=="], + + "@smithy/smithy-client": ["@smithy/smithy-client@4.12.13", "", { "dependencies": { "@smithy/core": "^3.23.17", "@smithy/middleware-endpoint": "^4.4.32", "@smithy/middleware-stack": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-stream": "^4.5.25", "tslib": "^2.6.2" } }, "sha512-y/Pcj1V9+qG98gyu1gvftHB7rDpdh+7kIBIggs55yGm3JdtBV8GT8IFF3a1qxZ79QnaJHX9GXzvBG6tAd+czJA=="], + + "@smithy/types": ["@smithy/types@4.14.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg=="], + + "@smithy/url-parser": ["@smithy/url-parser@4.2.14", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ=="], + + "@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="], + + "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ=="], + + "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g=="], "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.1", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-/swhmt1qTiVkaejlmMPPDgZhEaWb/HWMGRBheaxwuVkusp/z+ErJyQxO6kaXumOciZSWlmq6Z5mNylCd33X7Ig=="], + "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ=="], + + "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.49", "", { "dependencies": { "@smithy/property-provider": "^4.2.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-a5bNrdiONYB/qE2BuKegvUMd/+ZDwdg4vsNuuSzYE8qs2EYAdK9CynL+Rzn29PbPiUqoz/cbpRbcLzD5lEevHw=="], + + "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.54", "", { "dependencies": { "@smithy/config-resolver": "^4.4.17", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-g1cvrJvOnzeJgEdf7AE4luI7gp6L8weE0y9a9wQUSGtjb8QRHDbCJYuE4Sy0SD9N8RrnNPFsPltAz/OSoBR9Zw=="], + + "@smithy/util-endpoints": ["@smithy/util-endpoints@3.4.2", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-a55Tr+3OKld4TTtnT+RhKOQHyPxm3j/xL4OR83WBUhLJaKDS9dnJ7arRMOp3t31dcLhApwG9bgvrRXBHlLdIkg=="], + "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-c1hHtkgAWmE35/50gmdKajgGAKV3ePJ7t6UtEmpfCWJmQE9BQAQPz0URUVI89eSkcDqCtzqllxzG28IQoZPvwA=="], + "@smithy/util-middleware": ["@smithy/util-middleware@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw=="], + + "@smithy/util-retry": ["@smithy/util-retry@4.3.8", "", { "dependencies": { "@smithy/service-error-classification": "^4.3.1", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-LUIxbTBi+OpvXpg91poGA6BdyoleMDLnfXjVDqyi2RvZmTveY5loE/FgYUBCR5LU2BThW2SoZRh8dTIIy38IPw=="], + + "@smithy/util-stream": ["@smithy/util-stream@4.5.25", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.6.1", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-/PFpG4k8Ze8Ei+mMKj3oiPICYekthuzePZMgZbCqMiXIHHf4n2aZ4Ps0aSRShycFTGuj/J6XldmC0x0DwednIA=="], + + "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw=="], + "@smithy/util-utf8": ["@smithy/util-utf8@4.2.1", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-DSIwNaWtmzrNQHv8g7DBGR9mulSit65KSj5ymGEIAknmIN8IpbZefEep10LaMG/P/xquwbmJ1h9ectz8z6mV6g=="], + "@smithy/uuid": ["@smithy/uuid@1.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g=="], + "@solid-primitives/event-listener": ["@solid-primitives/event-listener@2.4.3", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg=="], "@solid-primitives/keyboard": ["@solid-primitives/keyboard@1.3.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-9dQHTTgLBqyAI7aavtO+HnpTVJgWQA1ghBSrmLtMu1SMxLPDuLfuNr+Tk5udb4AL4Ojg7h9JrKOGEEDqsJXWJA=="], @@ -1075,6 +1245,8 @@ "@tiptap/y-tiptap": ["@tiptap/y-tiptap@3.0.2", "", { "dependencies": { "lib0": "^0.2.100" }, "peerDependencies": { "prosemirror-model": "^1.7.1", "prosemirror-state": "^1.2.3", "prosemirror-view": "^1.9.10", "y-protocols": "^1.0.1", "yjs": "^13.5.38" } }, "sha512-flMn/YW6zTbc6cvDaUPh/NfLRTXDIqgpBUkYzM74KA1snqQwhOMjnRcnpu4hDFrTnPO6QGzr99vRyXEA7M44WA=="], + "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="], + "@trpc/client": ["@trpc/client@11.8.1", "", { "peerDependencies": { "@trpc/server": "11.8.1", "typescript": ">=5.7.2" } }, "sha512-L/SJFGanr9xGABmuDoeXR4xAdHJmsXsiF9OuH+apecJ+8sUITzVT1EPeqp0ebqA6lBhEl5pPfg3rngVhi/h60Q=="], "@trpc/server": ["@trpc/server@11.8.1", "", { "peerDependencies": { "typescript": ">=5.7.2" } }, "sha512-P4rzZRpEL7zDFgjxK65IdyH0e41FMFfTkQkuq0BA5tKcr7E6v9/v38DEklCpoDN6sPiB1Sigy/PUEzHENhswDA=="], @@ -1173,6 +1345,8 @@ "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + "@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/jsonwebtoken": ["@types/jsonwebtoken@9.0.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw=="], @@ -1211,7 +1385,9 @@ "@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="], - "@types/ws": ["@types/ws@6.0.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg=="], + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.51.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/type-utils": "8.51.0", "@typescript-eslint/utils": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.2.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.51.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og=="], @@ -1355,7 +1531,9 @@ "aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="], - "axios": ["axios@1.13.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA=="], + "axios": ["axios@1.14.0", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ=="], + + "b4a": ["b4a@1.8.1", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw=="], "babel-dead-code-elimination": ["babel-dead-code-elimination@1.0.11", "", { "dependencies": { "@babel/core": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" } }, "sha512-mwq3W3e/pKSI6TG8lXMiDWvEi1VXYlSBlJlB3l+I0bAb5u1RNUl88udos85eOPNK3m5EXK9uO7d2g08pesTySQ=="], @@ -1363,15 +1541,29 @@ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="], + + "bare-fs": ["bare-fs@4.7.1", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw=="], + + "bare-os": ["bare-os@3.9.1", "", {}, "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ=="], + + "bare-path": ["bare-path@3.0.0", "", { "dependencies": { "bare-os": "^3.0.1" } }, "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw=="], + + "bare-stream": ["bare-stream@2.13.1", "", { "dependencies": { "streamx": "^2.25.0", "teex": "^1.0.1" }, "peerDependencies": { "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-abort-controller", "bare-buffer", "bare-events"] }, "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow=="], + + "bare-url": ["bare-url@2.4.3", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], "base64url": ["base64url@3.0.1", "", {}, "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A=="], "baseline-browser-mapping": ["baseline-browser-mapping@2.9.11", "", { "bin": "dist/cli.js" }, "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ=="], - "better-auth": ["better-auth@1.4.10", "", { "dependencies": { "@better-auth/core": "1.4.10", "@better-auth/telemetry": "1.4.10", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "better-call": "1.1.7", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.1.12" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": ">=0.41.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "mongodb", "mysql2", "next", "prisma", "svelte", "vue"] }, "sha512-0kqwEBJLe8eyFzbUspRG/htOriCf9uMLlnpe34dlIJGdmDfPuQISd4shShvUrvIVhPxsY1dSTXdXPLpqISYOYg=="], + "basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="], - "better-call": ["better-call@1.1.7", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.7.10", "set-cookie-parser": "^2.7.1" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-6gaJe1bBIEgVebQu/7q9saahVzvBsGaByEnE8aDVncZEDiJO7sdNB28ot9I6iXSbR25egGmmZ6aIURXyQHRraQ=="], + "better-auth": ["better-auth@1.6.9", "", { "dependencies": { "@better-auth/core": "1.6.9", "@better-auth/drizzle-adapter": "1.6.9", "@better-auth/kysely-adapter": "1.6.9", "@better-auth/memory-adapter": "1.6.9", "@better-auth/mongo-adapter": "1.6.9", "@better-auth/prisma-adapter": "1.6.9", "@better-auth/telemetry": "1.6.9", "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", "better-call": "1.3.5", "defu": "^6.1.4", "jose": "^6.1.3", "kysely": "^0.28.14", "nanostores": "^1.1.1", "zod": "^4.3.6" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": "^0.45.2", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-EBFURtglyiEZxbx4NJBoqUD8J65dX24yC+6I9AUbIXNgUkt76mshzGbHkxZ3n/lB7Dwq3kBC+hHt0hUQsnL7HA=="], + + "better-call": ["better-call@1.3.5", "", { "dependencies": { "@better-auth/utils": "^0.4.0", "@better-fetch/fetch": "^1.1.21", "rou3": "^0.7.12", "set-cookie-parser": "^3.0.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA=="], "better-sqlite3": ["better-sqlite3@12.5.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg=="], @@ -1405,6 +1597,8 @@ "botframework-streaming": ["botframework-streaming@4.23.3", "", { "dependencies": { "@types/ws": "^6.0.3", "uuid": "^10.0.0", "ws": "^7.5.10" } }, "sha512-GMtciQGfZXtAW6syUqFpFJQ2vDyVbpxL3T1DqFzq/GmmkAu7KTZ1zvo7PTww6+IT1kMW0lmL/XZJVq3Rhg4PQA=="], + "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], @@ -1471,12 +1665,16 @@ "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + "chromium-bidi": ["chromium-bidi@14.0.0", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw=="], + "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], "cli-spinners": ["cli-spinners@3.4.0", "", {}, "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw=="], + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], @@ -1509,7 +1707,7 @@ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], "cookie-es": ["cookie-es@2.0.0", "", {}, "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg=="], @@ -1661,6 +1859,8 @@ "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="], + "delaunator": ["delaunator@5.1.0", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ=="], "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], @@ -1677,6 +1877,8 @@ "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + "devtools-protocol": ["devtools-protocol@0.0.1608973", "", {}, "sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ=="], + "diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="], "discontinuous-range": ["discontinuous-range@1.0.0", "", {}, "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ=="], @@ -1715,7 +1917,7 @@ "electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="], - "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], @@ -1755,6 +1957,8 @@ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="], + "eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "bin": "bin/eslint.js" }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="], "eslint-compat-utils": ["eslint-compat-utils@0.5.1", "", { "dependencies": { "semver": "^7.5.4" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q=="], @@ -1795,6 +1999,8 @@ "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], + "events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="], + "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], @@ -1811,6 +2017,8 @@ "extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="], + "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], + "fast-copy": ["fast-copy@4.0.2", "", {}, "sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw=="], "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], @@ -1819,6 +2027,8 @@ "fast-equals": ["fast-equals@5.4.0", "", {}, "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw=="], + "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], "fast-json-stringify": ["fast-json-stringify@6.1.1", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0", "json-schema-ref-resolver": "^3.0.0", "rfdc": "^1.2.0" } }, "sha512-DbgptncYEXZqDUOEl4krff4mUiVrTZZVI7BBrQR/T3BqMj/eM1flTC1Uk2uUoLcWCxjT95xKulV/Lc6hhOZsBQ=="], @@ -1831,6 +2041,10 @@ "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + "fast-xml-builder": ["fast-xml-builder@1.1.9", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-jcyKVSEX13iseJqg7n/KWw+xnu/7fdrZ333Fac54KjHDIELVCfDDJXYIm6DTJ0Su4gSzrhqiK0DzY/wZbF40mw=="], + + "fast-xml-parser": ["fast-xml-parser@5.7.2", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.5", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w=="], + "fastify": ["fastify@5.6.2", "", { "dependencies": { "@fastify/ajv-compiler": "^4.0.0", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^5.0.0", "@fastify/proxy-addr": "^5.0.0", "abstract-logging": "^2.0.1", "avvio": "^9.0.0", "fast-json-stringify": "^6.0.0", "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", "pino": "^10.1.0", "process-warning": "^5.0.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" } }, "sha512-dPugdGnsvYkBlENLhCgX8yhyGCsCPrpA8lFWbTNU428l+YOnLgYHR69hzV8HWPC79n536EqzqQtvhtdaCE0dKg=="], "fastify-plugin": ["fastify-plugin@5.1.0", "", {}, "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw=="], @@ -1901,6 +2115,8 @@ "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], + "fuse.js": ["fuse.js@7.3.0", "", {}, "sha512-plz8RVjfcDedTGfVngWH1jmJvBvAwi1v2jecfDerbEnMcmOYUEEwKFTHbNoCiYyzaK2Ws8lABkTCcRSqCY1q4w=="], + "gaxios": ["gaxios@7.1.4", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA=="], "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], @@ -1909,6 +2125,8 @@ "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], @@ -1917,10 +2135,14 @@ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], + "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], + "get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="], + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], "glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": "dist/esm/bin.mjs" }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], @@ -2007,7 +2229,7 @@ "husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="], - "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], @@ -2133,7 +2355,7 @@ "jiti": ["jiti@2.6.1", "", { "bin": "lib/jiti-cli.mjs" }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], - "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], + "jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="], "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], @@ -2141,7 +2363,7 @@ "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - "js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], "jsdom": ["jsdom@27.0.1", "", { "dependencies": { "@asamuzakjp/dom-selector": "^6.7.2", "cssstyle": "^5.3.1", "data-urls": "^6.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", "parse5": "^8.0.0", "rrweb-cssom": "^0.8.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^15.1.0", "ws": "^8.18.3", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA=="], @@ -2191,7 +2413,7 @@ "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], - "kysely": ["kysely@0.28.9", "", {}, "sha512-3BeXMoiOhpOwu62CiVpO6lxfq4eS6KMYfQdMsN/2kUCRNuF2YiEr7u0HLHaQU+O4Xu8YXE3bHVkwaQ85i72EuA=="], + "kysely": ["kysely@0.28.17", "", {}, "sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q=="], "langium": ["langium@4.2.1", "", { "dependencies": { "chevrotain": "~11.1.1", "chevrotain-allstar": "~0.3.1", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", "vscode-uri": "~3.1.0" } }, "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ=="], @@ -2401,9 +2623,9 @@ "mime": ["mime@3.0.0", "", { "bin": "cli.js" }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], - "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], @@ -2415,6 +2637,8 @@ "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], @@ -2429,7 +2653,7 @@ "nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - "nanostores": ["nanostores@1.1.0", "", {}, "sha512-yJBmDJr18xy47dbNVlHcgdPrulSn1nhSE6Ns9vTG+Nx9VPT6iV1MD6aQFp/t52zpf82FhLLTXAXr30NuCnxvwA=="], + "nanostores": ["nanostores@1.3.0", "", {}, "sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA=="], "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], @@ -2441,6 +2665,8 @@ "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "netmask": ["netmask@2.1.1", "", {}, "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA=="], + "nice-try": ["nice-try@1.0.5", "", {}, "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="], "no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="], @@ -2511,6 +2737,10 @@ "p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="], + "pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="], + + "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="], + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], @@ -2535,6 +2765,8 @@ "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + "path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="], + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], @@ -2629,6 +2861,8 @@ "process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="], + "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], + "promise-limit": ["promise-limit@2.7.0", "", {}, "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw=="], "prompt-mentions": ["prompt-mentions@0.0.32", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-AfKwKekBL8i46r4jKgUE/jSe8esHgwO3zMSxL3KI1z+RMrCZ9wiyPMpwQJxe7q5a3+2EJWbGJEuxhi/TECmlpg=="], @@ -2677,6 +2911,8 @@ "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="], + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], @@ -2685,6 +2921,8 @@ "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], + "puppeteer-core": ["puppeteer-core@24.43.0", "", { "dependencies": { "@puppeteer/browsers": "2.13.1", "chromium-bidi": "14.0.0", "debug": "^4.4.3", "devtools-protocol": "0.0.1608973", "typed-query-selector": "^2.12.2", "webdriver-bidi-protocol": "0.4.1", "ws": "^8.20.0" } }, "sha512-cCRNXsUlhyPoKDz6+TiSpfZpRS3mD6Y1YFKhkdr6ik6TMfuJb7fAtXq9ThUFc4sphxObDk3BuAvdxc1Y6YOnqQ=="], + "qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="], "query-selector-shadow-dom": ["query-selector-shadow-dom@1.0.1", "", {}, "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw=="], @@ -2765,6 +3003,8 @@ "remend": ["remend@1.3.0", "", {}, "sha512-iIhggPkhW3hFImKtB10w0dz4EZbs28mV/dmbcYVonWEJ6UGHHpP+bFZnTh6GNWJONg5m+U56JrL+8IxZRdgWjw=="], + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], @@ -2831,13 +3071,13 @@ "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], - "seroval": ["seroval@1.3.2", "", {}, "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ=="], + "seroval": ["seroval@1.4.2", "", {}, "sha512-N3HEHRCZYn3cQbsC4B5ldj9j+tHdf4JZoYPlcI4rRYu0Xy4qN8MQf1Z08EibzB0WpgRG5BGK08FTrmM66eSzKQ=="], - "seroval-plugins": ["seroval-plugins@1.3.3", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w=="], + "seroval-plugins": ["seroval-plugins@1.4.2", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-X7p4MEDTi+60o2sXZ4bnDBhgsUYDSkQEvzYZuJyFqWg9jcoPsHts5nrg5O956py2wyt28lUrBxk0M0/wU8URpA=="], "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], - "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], + "set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="], "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], @@ -2871,8 +3111,14 @@ "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + "snake-case": ["snake-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg=="], + "socks": ["socks@2.8.8", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog=="], + + "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + "solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="], "sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="], @@ -2915,6 +3161,8 @@ "streamdown": ["streamdown@2.5.0", "", { "dependencies": { "clsx": "^2.1.1", "hast-util-to-jsx-runtime": "^2.3.6", "html-url-attributes": "^3.0.1", "marked": "^17.0.1", "mermaid": "^11.12.2", "rehype-harden": "^1.1.8", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remend": "1.3.0", "tailwind-merge": "^3.4.0", "unified": "^11.0.5", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-/tTnURfIOxZK/pqJAxsfCvETG/XCJHoWnk3jq9xLcuz6CSpnjjuxSRBTTL4PKGhxiZQf0lqPxGhImdpwcZ2XwA=="], + "streamx": ["streamx@2.25.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg=="], + "string-width": ["string-width@8.1.1", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw=="], "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -2943,6 +3191,8 @@ "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], + "strnum": ["strnum@2.3.0", "", {}, "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q=="], + "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], @@ -2975,6 +3225,10 @@ "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + "teex": ["teex@1.0.1", "", { "dependencies": { "streamx": "^2.12.5" } }, "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg=="], + + "text-decoder": ["text-decoder@1.2.7", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ=="], + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], @@ -3049,6 +3303,8 @@ "typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="], + "typed-query-selector": ["typed-query-selector@2.12.2", "", {}, "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "typescript-eslint": ["typescript-eslint@8.51.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.51.0", "@typescript-eslint/parser": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0", "@typescript-eslint/utils": "8.51.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA=="], @@ -3143,6 +3399,8 @@ "web-vitals": ["web-vitals@5.1.0", "", {}, "sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg=="], + "webdriver-bidi-protocol": ["webdriver-bidi-protocol@0.4.1", "", {}, "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw=="], + "webidl-conversions": ["webidl-conversions@8.0.0", "", {}, "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA=="], "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], @@ -3187,10 +3445,16 @@ "y-protocols": ["y-protocols@1.0.7", "", { "dependencies": { "lib0": "^0.2.85" }, "peerDependencies": { "yjs": "^13.0.0" } }, "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw=="], + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "yaml": ["yaml@2.8.2", "", { "bin": "bin.mjs" }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], "yjs": ["yjs@13.6.29", "", { "dependencies": { "lib0": "^0.2.99" } }, "sha512-kHqDPdltoXH+X4w1lVmMtddE3Oeqq48nM40FD5ojTd8xYhQpzIDcfE2keMSU5bAgRPJBe225WTUdyUgj1DtbiQ=="], @@ -3207,13 +3471,17 @@ "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], - "@ai-sdk/google/@ai-sdk/provider": ["@ai-sdk/provider@3.0.7", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-VkPLrutM6VdA924/mG8OS+5frbVTcu6e046D2bgDo00tehBANR1QBJ/mPcZ9tXMFOsVcm6SQArOregxePzTFPw=="], + "@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.50", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.16" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-BkCUgGTp/iZJuuFBF1wv7GGnrEJg7X7hqbaa+/t4HTBt9dZn3e6NFn5NhPUvo2p5SreUeHEl0As0r2uaVn3K9Q=="], - "@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.13", "", { "dependencies": { "@ai-sdk/provider": "3.0.7", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HHG72BN4d+OWTcq2NwTxOm/2qvk1duYsnhCDtsbYwn/h/4zeqURu1S0+Cn0nY2Ysq9a9HGKvrYuMn9bgFhR2Og=="], + "@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.22", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-B2OTFcRw/Pdka9ZTjpXv6T6qZ6RruRuLokyb8HwW+aoW9ndJ3YasA3/mVswyJw7VMBF8ofXgqvcrCt9KYvFifg=="], - "@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.66", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yJpQ2x6ACwbXo5D6HsVWd2FFnnWcetfGx4oxkG66P8FawusvrY2vL2qMiiNTruWrxEYDy+YHc3ctv8C769MMJA=="], + "@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@3.0.62", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.26" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Oy74Bztik2X25wZD9HRd83BAXOKcRvrfgz9gvVGqKj68yegf447NiElPbB6TSVb8zyiY9wv1GSGywMCxnnoF9g=="], - "@ai-sdk/google-vertex/@ai-sdk/google": ["@ai-sdk/google@3.0.58", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7P7s8g/FoIxesx2y32eK8idAMLOFHN2f4gs5KYi8q2QaScuubXFjgFMFqbjYF5bc92akiOd/C6OG0vIDlV7t2Q=="], + "@ai-sdk/azure/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], + + "@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CsKNLKsOpvPujRlIYvoz+Ybw+kGn7J4/fIZa/58+R7iWLLfwn6ifE2G6Yq8K9XvH/I/3bzaDAJ3NhRwEMsLBKQ=="], + + "@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.22", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-B2OTFcRw/Pdka9ZTjpXv6T6qZ6RruRuLokyb8HwW+aoW9ndJ3YasA3/mVswyJw7VMBF8ofXgqvcrCt9KYvFifg=="], "@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.22", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-B2OTFcRw/Pdka9ZTjpXv6T6qZ6RruRuLokyb8HwW+aoW9ndJ3YasA3/mVswyJw7VMBF8ofXgqvcrCt9KYvFifg=="], @@ -3229,15 +3497,31 @@ "@antfu/install-pkg/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], + "@aws-crypto/crc32/@aws-sdk/types": ["@aws-sdk/types@3.973.4", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-crypto/util/@aws-sdk/types": ["@aws-sdk/types@3.973.4", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q=="], + "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@aws-sdk/client-cognito-identity/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/middleware-sdk-s3/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + "@babel/core/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@chat-adapter/slack/@slack/web-api": ["@slack/web-api@7.15.0", "", { "dependencies": { "@slack/logger": "^4.0.1", "@slack/types": "^2.20.1", "@types/node": ">=18", "@types/retry": "0.12.0", "axios": "^1.13.5", "eventemitter3": "^5.0.1", "form-data": "^4.0.4", "is-electron": "2.2.2", "is-stream": "^2", "p-queue": "^6", "p-retry": "^4", "retry": "^0.13.1" } }, "sha512-va7zYIt3QHG1x9M/jqXXRPFMoOVlVSSRHC5YH+DzKYsrz5xUKOA3lR4THsu/Zxha9N1jOndbKFKLtr0WOPW1Vw=="], + "@better-auth/core/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + + "@better-auth/oauth-provider/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "@chevrotain/cst-dts-gen/lodash-es": ["lodash-es@4.17.23", "", {}, "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="], @@ -3251,8 +3535,6 @@ "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], - "@eslint/eslintrc/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], - "@eslint/eslintrc/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "@eslint/eslintrc/strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], @@ -3269,16 +3551,20 @@ "@libsql/hrana-client/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], - "@libsql/isomorphic-ws/@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@modelcontextprotocol/sdk/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + "@modelcontextprotocol/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + "@nao/frontend/@types/node": ["@types/node@22.19.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA=="], "@nao/frontend/prompt-mentions": ["prompt-mentions@0.0.34", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-kIV79vsxdTeGZKDuH8/lgSN40pGvB+hJdg0aE6ugIWbVXW1F3Vu1hX1ks5Lw76KIvjAzeFzoNkrmvI16i2ZZNQ=="], "@nao/frontend/vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@vitest/browser", "@vitest/ui", "happy-dom"], "bin": "vitest.mjs" }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="], + "@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], + + "@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], + "@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="], "@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.5.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ=="], @@ -3289,6 +3575,10 @@ "@opentelemetry/sdk-trace-base/@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="], + "@puppeteer/browsers/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "@puppeteer/browsers/tar-fs": ["tar-fs@3.1.2", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw=="], + "@radix-ui/react-alert-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], @@ -3307,6 +3597,30 @@ "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "@smithy/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/eventstream-codec/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + + "@smithy/hash-node/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@smithy/hash-node/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg=="], + + "@smithy/signature-v4/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@smithy/util-base64/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg=="], + + "@smithy/util-stream/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + "@tanstack/devtools/@tanstack/devtools-client": ["@tanstack/devtools-client@0.0.3", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.3.3" } }, "sha512-kl0r6N5iIL3t9gGDRAv55VRM3UIyMKVH83esRGq7xBjYsRLe/BeCIN2HqrlJkObUXQMKhy7i8ejuGOn+bDqDBw=="], "@tanstack/devtools-vite/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], @@ -3315,10 +3629,6 @@ "@tanstack/router-core/@tanstack/store": ["@tanstack/store@0.8.0", "", {}, "sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ=="], - "@tanstack/router-core/seroval": ["seroval@1.4.2", "", {}, "sha512-N3HEHRCZYn3cQbsC4B5ldj9j+tHdf4JZoYPlcI4rRYu0Xy4qN8MQf1Z08EibzB0WpgRG5BGK08FTrmM66eSzKQ=="], - - "@tanstack/router-core/seroval-plugins": ["seroval-plugins@1.4.2", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-X7p4MEDTi+60o2sXZ4bnDBhgsUYDSkQEvzYZuJyFqWg9jcoPsHts5nrg5O956py2wyt28lUrBxk0M0/wU8URpA=="], - "@tanstack/router-generator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@tanstack/router-plugin/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], @@ -3329,6 +3639,8 @@ "@types/pg/@types/node": ["@types/node@22.19.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA=="], + "@types/ws/@types/node": ["@types/node@22.19.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -3337,22 +3649,20 @@ "@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], - "accepts/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], - "ajv-formats/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "axios/proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + + "better-auth/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + "bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], "bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - "body-parser/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], - "botbuilder/@azure/msal-node": ["@azure/msal-node@2.16.3", "", { "dependencies": { "@azure/msal-common": "14.16.1", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-CO+SE4weOsfJf+C5LM8argzvotrXw252/ZU6SM2Tz63fEblhH1uuVaaO4ISYFuN4Q6BhTo7I3qIdi8ydUQCqhw=="], - "botbuilder/axios": ["axios@1.14.0", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ=="], - "botbuilder/htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="], "botbuilder/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], @@ -3365,14 +3675,14 @@ "botframework-connector/@azure/msal-node": ["@azure/msal-node@2.16.3", "", { "dependencies": { "@azure/msal-common": "14.16.1", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-CO+SE4weOsfJf+C5LM8argzvotrXw252/ZU6SM2Tz63fEblhH1uuVaaO4ISYFuN4Q6BhTo7I3qIdi8ydUQCqhw=="], - "botframework-connector/axios": ["axios@1.14.0", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ=="], - "botframework-connector/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "botframework-schema/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], "botframework-schema/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "botframework-streaming/@types/ws": ["@types/ws@6.0.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg=="], + "botframework-streaming/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], "botframework-streaming/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], @@ -3383,7 +3693,13 @@ "chevrotain/lodash-es": ["lodash-es@4.17.23", "", {}, "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="], - "cosmiconfig/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "chromium-bidi/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "cosmiconfig/parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], @@ -3393,42 +3709,56 @@ "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], + "d3-dsv/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], + "degenerator/ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="], + + "encoding-sniffer/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + "escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "eslint/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "eslint-plugin-n/globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="], "express/content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], - "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - - "express/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], - "fast-json-stringify/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], "fastify-raw-body/secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="], + "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + "get-uri/data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], + + "gray-matter/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], + "htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - "js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], - "jsdom/parse5": ["parse5@8.0.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="], "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], "libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="], + "light-my-request/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + "light-my-request/process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="], + "light-my-request/set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], + + "mcporter/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.26.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg=="], + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], "mermaid/marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], @@ -3469,12 +3799,14 @@ "proxy-addr/ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + "proxy-agent/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], + + "puppeteer-core/ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + "radix-ui/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], "randexp/ret": ["ret@0.1.15", "", {}, "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="], - "raw-body/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], - "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], "readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], @@ -3489,11 +3821,13 @@ "safe-push-apply/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - "send/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + "socks/ip-address": ["ip-address@10.2.0", "", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="], - "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "solid-js/seroval": ["seroval@1.3.2", "", {}, "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ=="], - "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "solid-js/seroval-plugins": ["seroval-plugins@1.3.3", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w=="], + + "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -3511,12 +3845,12 @@ "tsx/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": "bin/esbuild" }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], - "type-is/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], - "vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": "bin/esbuild" }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], "vitest/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], + "whatwg-encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], @@ -3529,13 +3863,25 @@ "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@ai-sdk/azure/@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="], + + "@aws-crypto/crc32/@aws-sdk/types/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@aws-crypto/util/@aws-sdk/types/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="], + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], - "@chat-adapter/slack/@slack/web-api/@slack/logger": ["@slack/logger@4.0.1", "", { "dependencies": { "@types/node": ">=18" } }, "sha512-6cmdPrV/RYfd2U0mDGiMK8S7OJqpCTm7enMLRR3edccsPX8j7zXTLnaEF4fhxxJJTAIOil6+qZrnUPTuaLvwrQ=="], + "@aws-sdk/client-cognito-identity/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@aws-sdk/core/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], - "@chat-adapter/slack/@slack/web-api/@slack/types": ["@slack/types@2.20.1", "", {}, "sha512-eWX2mdt1ktpn8+40iiMc404uGrih+2fxiky3zBcPjtXKj6HLRdYlmhrPkJi7JTJm8dpXR6BWVWEDBXtaWMKD6A=="], + "@aws-sdk/middleware-sdk-s3/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], - "@chat-adapter/slack/@slack/web-api/axios": ["axios@1.14.0", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ=="], + "@aws-sdk/nested-clients/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], @@ -3585,7 +3931,7 @@ "@floating-ui/react/@floating-ui/react-dom/@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="], - "@libsql/isomorphic-ws/@types/ws/@types/node": ["@types/node@22.19.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA=="], + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], @@ -3607,6 +3953,20 @@ "@nao/frontend/vitest/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + "@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@puppeteer/browsers/tar-fs/tar-stream": ["tar-stream@3.2.0", "", { "dependencies": { "b4a": "^1.6.4", "bare-fs": "^4.5.5", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg=="], + + "@smithy/core/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@smithy/hash-node/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@smithy/signature-v4/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + + "@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + "@tanstack/devtools/@tanstack/devtools-client/@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.3.5", "", {}, "sha512-RL1f5ZlfZMpghrCIdzl6mLOFLTuhqmPNblZgBaeKfdtk5rfbjykurv+VfYydOFXj0vxVIoA2d/zT7xfD7Ph8fw=="], "@tanstack/router-plugin/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], @@ -3617,9 +3977,13 @@ "@types/pg/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "@types/ws/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - "accepts/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + "@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], + + "@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], @@ -3629,28 +3993,32 @@ "botbuilder/@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], - "botbuilder/axios/proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], - "botframework-connector/@azure/msal-node/@azure/msal-common": ["@azure/msal-common@14.16.1", "", {}, "sha512-nyxsA6NA4SVKh5YyRpbSXiMr7oQbwark7JU9LMeg6tJYTSPyAGkdx61wPT4gyxZfxlSxMMEyAsWaubBlNyIa1w=="], - "botframework-connector/axios/proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], - "bun-types/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], "chalk/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="], "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], - "express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - "fast-json-stringify/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], + "jsdom/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + "mcporter/@modelcontextprotocol/sdk/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "mcporter/@modelcontextprotocol/sdk/jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], + "node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], "node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], @@ -3667,8 +4035,6 @@ "npm-run-all/cross-spawn/which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": "bin/which" }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="], - "send/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - "tar-stream/readable-stream/string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], "tsup/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], @@ -3775,8 +4141,6 @@ "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], - "type-is/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="], @@ -3831,24 +4195,42 @@ "wrap-ansi-cjs/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "wrap-ansi/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], - "@chat-adapter/slack/@slack/web-api/axios/proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + "@aws-sdk/client-cognito-identity/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@aws-sdk/core/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@aws-sdk/middleware-sdk-s3/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], + + "@aws-sdk/nested-clients/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], "@floating-ui/react/@floating-ui/react-dom/@floating-ui/dom/@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="], - "@libsql/isomorphic-ws/@types/ws/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "@smithy/core/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], "@tanstack/router-plugin/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + "chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "cliui/wrap-ansi/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "mcporter/@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "npm-run-all/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], "npm-run-all/cross-spawn/shebang-command/shebang-regex": ["shebang-regex@1.0.0", "", {}, "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ=="], "wrap-ansi-cjs/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "cliui/wrap-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], } } diff --git a/package-lock.json b/package-lock.json index 9d5ebc1c5..ec72e389e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,7 +60,7 @@ "@vscode/ripgrep": "^1.17.0", "ai": "^6.0.97", "ai-sdk-ollama": "^3.7.1", - "better-auth": "^1.4.10", + "better-auth": "^1.6.3", "better-sqlite3": "^12.5.0", "chat": "^4.15.0", "cheerio": "^1.2.0", @@ -253,7 +253,7 @@ "@trpc/client": "^11.8.1", "@trpc/tanstack-react-query": "^11.8.1", "ai": "^6.0.97", - "better-auth": "^1.4.10", + "better-auth": "^1.6.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", From f1db9ad56c5a11bbf34402dd267704d8d56e0c39 Mon Sep 17 00:00:00 2001 From: socallmebertille Date: Fri, 8 May 2026 17:46:13 +0200 Subject: [PATCH 10/11] give more space to the node modules in the heap memory --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cba3ffde6..c8bd73eba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,7 +37,7 @@ COPY apps/backend ./apps/backend COPY apps/shared ./apps/shared WORKDIR /app/apps/frontend -RUN npm run build +RUN NODE_OPTIONS="--max-old-space-size=6144" npm run build # ============================================================================= # STAGE 4: Python/FastAPI builder From e0ca5af9407371482e3e49fdc345330772ef8f53 Mon Sep 17 00:00:00 2001 From: socallmebertille Date: Fri, 8 May 2026 18:12:34 +0200 Subject: [PATCH 11/11] remove type checker in dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c8bd73eba..b0a9e2dcd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,7 +37,7 @@ COPY apps/backend ./apps/backend COPY apps/shared ./apps/shared WORKDIR /app/apps/frontend -RUN NODE_OPTIONS="--max-old-space-size=6144" npm run build +RUN npx vite build # ============================================================================= # STAGE 4: Python/FastAPI builder