diff --git a/docs/openapi.json b/docs/openapi.json index e9f43b04c7..42096981bb 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.0", "info": { "title": "Archestra", - "version": "1.1.3" + "version": "1.1.4" }, "components": { "schemas": { diff --git a/docs/pages/platform-deployment.md b/docs/pages/platform-deployment.md index 23e4ee2383..a158837f5d 100644 --- a/docs/pages/platform-deployment.md +++ b/docs/pages/platform-deployment.md @@ -558,7 +558,7 @@ The [Knowledge Base](/docs/platform-knowledge-bases) enterprise feature requires **Cloud-managed databases:** -- **AWS RDS** — pgvector is available as a [trusted extension](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_PostgreSQL.html#PostgreSQL.Concepts.General.Extensions). Enable it via `CREATE EXTENSION vector` without superuser. +- **AWS RDS** — pgvector is available but is [not a trusted extension](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/PostgreSQL.Concepts.General.FeatureSupport.Extensions.html#PostgreSQL.Concepts.General.Extensions.Trusted), so it must be installed by a user with the `rds_superuser` role. Connect as the RDS master user and run `CREATE EXTENSION vector`. - **Google Cloud SQL** — pgvector is [supported natively](https://cloud.google.com/sql/docs/postgres/extensions#pgvector). Enable it via the Cloud SQL console or `CREATE EXTENSION vector`. - **Azure Database for PostgreSQL** — pgvector is [available as an extension](https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-extensions). Allow-list it in server parameters, then run `CREATE EXTENSION vector`. diff --git a/docs/pages/platform-knowledge-bases.md b/docs/pages/platform-knowledge-bases.md index e61638a1dd..1253f9a1af 100644 --- a/docs/pages/platform-knowledge-bases.md +++ b/docs/pages/platform-knowledge-bases.md @@ -11,7 +11,7 @@ Check ../docs_writer_prompt.md before changing this file. --> -Knowledge bases provide built-in retrieval augmented generation (RAG) powered by PostgreSQL and pgvector. Connectors sync data from external tools into knowledge bases, where documents are chunked, embedded, and indexed for hybrid search. Agents query their assigned knowledge bases at runtime via the `query_knowledge_base` tool. +Knowledge bases provide built-in retrieval augmented generation (RAG) powered by PostgreSQL and pgvector. Connectors sync data from external tools into knowledge bases, where documents are chunked, embedded, and indexed for hybrid search. Agents automatically query their assigned knowledge sources at runtime. > **Enterprise feature.** Knowledge bases require an enterprise license. Contact sales@archestra.ai for licensing information. @@ -33,11 +33,11 @@ flowchart LR ### Querying -At runtime, the `query_knowledge_base` tool embeds the query, runs vector and optional full-text search in parallel, then fuses, reranks, and filters results. +At runtime, the agent's query is embedded, then vector and optional full-text search run in parallel. Results are fused, reranked, and filtered before being returned. ```mermaid flowchart LR - Q[query_knowledge_base] -->|OpenAI API| QE[Query Embedding] + Q[Agent Query] -->|OpenAI API| QE[Query Embedding] QE --> VS[Vector Search] QE --> FTS["Full-Text Search (configurable)"] VS --> RRF[Reciprocal Rank Fusion] diff --git a/platform/CLAUDE.md b/platform/CLAUDE.md index 20c4c84951..856ce2723b 100644 --- a/platform/CLAUDE.md +++ b/platform/CLAUDE.md @@ -85,6 +85,12 @@ drizzle-kit check # Check consistency of generated SQL migrations history # IMPORTANT: Never create manually-named migration files - Drizzle tracks migrations # via the meta/_journal.json file which references the generated file names. +# Custom Data-Only Migrations (no schema changes) +# For pure data migrations (UPDATE, INSERT) with no schema changes, use: +# cd backend && npx drizzle-kit generate --custom --name= +# This creates an empty SQL file tracked by Drizzle's journal. Add your SQL, then run: +# npx drizzle-kit check + # Database Connection # PostgreSQL is running in Kubernetes (managed by Tilt) # Connect to database: diff --git a/platform/backend/src/archestra-mcp-server.test.ts b/platform/backend/src/archestra-mcp-server.test.ts index ba76cf364c..6189c03d5b 100644 --- a/platform/backend/src/archestra-mcp-server.test.ts +++ b/platform/backend/src/archestra-mcp-server.test.ts @@ -126,25 +126,19 @@ describe("getArchestraMcpTools", () => { expect(tool?.title).toBe("Get LLM Proxy Token Usage"); }); - test("should have query_knowledge_base tool", () => { + test("should have query_knowledge_sources tool", () => { const tools = getArchestraMcpTools(); - const tool = tools.find((t) => t.name.endsWith("query_knowledge_base")); + const tool = tools.find((t) => t.name.endsWith("query_knowledge_sources")); expect(tool).toBeDefined(); - expect(tool?.title).toBe("Query Knowledge Base"); + expect(tool?.title).toBe("Query Knowledge Sources"); expect(tool?.inputSchema).toEqual({ type: "object", properties: { query: { type: "string", description: - "A natural language query about the content stored in the knowledge base. Ask about topics, concepts, or information — not about source systems (e.g. ask 'what tasks are in progress' rather than 'get jira data').", - }, - mode: { - type: "string", - enum: ["local", "global", "hybrid", "naive"], - description: - "Query mode: 'local' uses only local context, 'global' uses global context across all documents, 'hybrid' combines both (recommended), 'naive' uses simple RAG without graph-based retrieval. Defaults to 'hybrid'.", + "A natural language query about the content you are looking for. Ask about topics, concepts, or information rather than about source systems.", }, }, required: ["query"], @@ -720,8 +714,8 @@ describe("executeArchestraTool", () => { }); }); - describe("query_knowledge_base tool", () => { - const toolName = `${ARCHESTRA_MCP_SERVER_NAME}${MCP_SERVER_TOOL_NAME_SEPARATOR}query_knowledge_base`; + describe("query_knowledge_sources tool", () => { + const toolName = `${ARCHESTRA_MCP_SERVER_NAME}${MCP_SERVER_TOOL_NAME_SEPARATOR}query_knowledge_sources`; test("should return error when query param is missing", async () => { const result = await executeArchestraTool(toolName, {}, mockContext); diff --git a/platform/backend/src/archestra-mcp-server.ts b/platform/backend/src/archestra-mcp-server.ts index 88975f2aeb..04abd26866 100644 --- a/platform/backend/src/archestra-mcp-server.ts +++ b/platform/backend/src/archestra-mcp-server.ts @@ -5,7 +5,7 @@ import { MCP_SERVER_TOOL_NAME_SEPARATOR, TOOL_ARTIFACT_WRITE_FULL_NAME, TOOL_CREATE_MCP_SERVER_INSTALLATION_REQUEST_FULL_NAME, - TOOL_QUERY_KNOWLEDGE_BASE_FULL_NAME, + TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME, TOOL_TODO_WRITE_FULL_NAME, } from "@shared"; import { executeA2AMessage } from "@/agents/a2a-executor"; @@ -1782,10 +1782,10 @@ export async function executeArchestraTool( } } - if (toolName === TOOL_QUERY_KNOWLEDGE_BASE_FULL_NAME) { + if (toolName === TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME) { logger.info( { agentId: contextAgent.id, queryArgs: args }, - "query_knowledge_base tool called", + "query_knowledge_sources tool called", ); try { @@ -1910,7 +1910,7 @@ export async function executeArchestraTool( agentId: contextAgent.id, error: error instanceof Error ? error.message : String(error), }, - "query_knowledge_base failed", + "query_knowledge_sources failed", ); return { content: [ @@ -2946,23 +2946,17 @@ export function getArchestraMcpTools(): Tool[] { _meta: {}, }, { - name: TOOL_QUERY_KNOWLEDGE_BASE_FULL_NAME, - title: "Query Knowledge Base", + name: TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME, + title: "Query Knowledge Sources", description: - "Query the organization's knowledge base to retrieve information from ingested documents (uploaded files, Jira issues, Confluence pages, etc.). Uses graph-based retrieval augmented generation (GraphRAG) for accurate and contextual results. IMPORTANT: formulate queries about the actual content you are looking for, not about the source system. For example, instead of 'get information from jira', ask 'what tasks or issues are being tracked' or 'what are the open bugs'.", + "Query the organization's knowledge sources to retrieve relevant information. Use this tool when the user asks a question you cannot answer from your training data alone, or when they explicitly ask you to search internal documents and data sources. Formulate queries about the actual content you are looking for — ask about topics, concepts, or information rather than about source systems.", inputSchema: { type: "object", properties: { query: { type: "string", description: - "A natural language query about the content stored in the knowledge base. Ask about topics, concepts, or information — not about source systems (e.g. ask 'what tasks are in progress' rather than 'get jira data').", - }, - mode: { - type: "string", - enum: ["local", "global", "hybrid", "naive"], - description: - "Query mode: 'local' uses only local context, 'global' uses global context across all documents, 'hybrid' combines both (recommended), 'naive' uses simple RAG without graph-based retrieval. Defaults to 'hybrid'.", + "A natural language query about the content you are looking for. Ask about topics, concepts, or information rather than about source systems.", }, }, required: ["query"], diff --git a/platform/backend/src/database/migrations/0175_rename-query-knowledge-base-to-query-knowledge-sources.sql b/platform/backend/src/database/migrations/0175_rename-query-knowledge-base-to-query-knowledge-sources.sql new file mode 100644 index 0000000000..ad8f4ca82e --- /dev/null +++ b/platform/backend/src/database/migrations/0175_rename-query-knowledge-base-to-query-knowledge-sources.sql @@ -0,0 +1,4 @@ +-- Rename legacy tool: query_knowledge_base → query_knowledge_sources +UPDATE "tools" +SET "name" = 'archestra__query_knowledge_sources' +WHERE "name" = 'archestra__query_knowledge_base'; diff --git a/platform/backend/src/database/migrations/meta/0175_snapshot.json b/platform/backend/src/database/migrations/meta/0175_snapshot.json new file mode 100644 index 0000000000..dfad784137 --- /dev/null +++ b/platform/backend/src/database/migrations/meta/0175_snapshot.json @@ -0,0 +1,7616 @@ +{ + "id": "85e95fe1-c93f-46f7-9f17-d8e9f67d0e2c", + "prevId": "c792aa2c-4c0e-41b2-a320-fa5b44e4bc6d", + "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": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_connector_assignment": { + "name": "agent_connector_assignment", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_connector_assignment_agent_idx": { + "name": "agent_connector_assignment_agent_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "agent_connector_assignment_connector_idx": { + "name": "agent_connector_assignment_connector_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "agent_connector_assignment_agent_id_agents_id_fk": { + "name": "agent_connector_assignment_agent_id_agents_id_fk", + "tableFrom": "agent_connector_assignment", + "columnsFrom": [ + "agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "agent_connector_assignment_connector_id_knowledge_base_connectors_id_fk": { + "name": "agent_connector_assignment_connector_id_knowledge_base_connectors_id_fk", + "tableFrom": "agent_connector_assignment", + "columnsFrom": [ + "connector_id" + ], + "tableTo": "knowledge_base_connectors", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "agent_connector_assignment_agent_id_connector_id_pk": { + "name": "agent_connector_assignment_agent_id_connector_id_pk", + "columns": [ + "agent_id", + "connector_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_knowledge_base": { + "name": "agent_knowledge_base", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_knowledge_base_agent_idx": { + "name": "agent_knowledge_base_agent_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "agent_knowledge_base_kb_idx": { + "name": "agent_knowledge_base_kb_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "agent_knowledge_base_agent_id_agents_id_fk": { + "name": "agent_knowledge_base_agent_id_agents_id_fk", + "tableFrom": "agent_knowledge_base", + "columnsFrom": [ + "agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "agent_knowledge_base_knowledge_base_id_knowledge_bases_id_fk": { + "name": "agent_knowledge_base_knowledge_base_id_knowledge_bases_id_fk", + "tableFrom": "agent_knowledge_base", + "columnsFrom": [ + "knowledge_base_id" + ], + "tableTo": "knowledge_bases", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "agent_knowledge_base_agent_id_knowledge_base_id_pk": { + "name": "agent_knowledge_base_agent_id_knowledge_base_id_pk", + "columns": [ + "agent_id", + "knowledge_base_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_labels": { + "name": "agent_labels", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "value_id": { + "name": "value_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agent_labels_agent_id_agents_id_fk": { + "name": "agent_labels_agent_id_agents_id_fk", + "tableFrom": "agent_labels", + "columnsFrom": [ + "agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "agent_labels_key_id_label_keys_id_fk": { + "name": "agent_labels_key_id_label_keys_id_fk", + "tableFrom": "agent_labels", + "columnsFrom": [ + "key_id" + ], + "tableTo": "label_keys", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "agent_labels_value_id_label_values_id_fk": { + "name": "agent_labels_value_id_label_values_id_fk", + "tableFrom": "agent_labels", + "columnsFrom": [ + "value_id" + ], + "tableTo": "label_values", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "agent_labels_agent_id_key_id_pk": { + "name": "agent_labels_agent_id_key_id_pk", + "columns": [ + "agent_id", + "key_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_team": { + "name": "agent_team", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agent_team_agent_id_agents_id_fk": { + "name": "agent_team_agent_id_agents_id_fk", + "tableFrom": "agent_team", + "columnsFrom": [ + "agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "agent_team_team_id_team_id_fk": { + "name": "agent_team_team_id_team_id_fk", + "tableFrom": "agent_team", + "columnsFrom": [ + "team_id" + ], + "tableTo": "team", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "agent_team_agent_id_team_id_pk": { + "name": "agent_team_agent_id_team_id_pk", + "columns": [ + "agent_id", + "team_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_tools": { + "name": "agent_tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "response_modifier_template": { + "name": "response_modifier_template", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_source_mcp_server_id": { + "name": "credential_source_mcp_server_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_source_mcp_server_id": { + "name": "execution_source_mcp_server_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "use_dynamic_team_credential": { + "name": "use_dynamic_team_credential", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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": { + "agent_tools_agent_id_agents_id_fk": { + "name": "agent_tools_agent_id_agents_id_fk", + "tableFrom": "agent_tools", + "columnsFrom": [ + "agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "agent_tools_tool_id_tools_id_fk": { + "name": "agent_tools_tool_id_tools_id_fk", + "tableFrom": "agent_tools", + "columnsFrom": [ + "tool_id" + ], + "tableTo": "tools", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "agent_tools_credential_source_mcp_server_id_mcp_server_id_fk": { + "name": "agent_tools_credential_source_mcp_server_id_mcp_server_id_fk", + "tableFrom": "agent_tools", + "columnsFrom": [ + "credential_source_mcp_server_id" + ], + "tableTo": "mcp_server", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "agent_tools_execution_source_mcp_server_id_mcp_server_id_fk": { + "name": "agent_tools_execution_source_mcp_server_id_mcp_server_id_fk", + "tableFrom": "agent_tools", + "columnsFrom": [ + "execution_source_mcp_server_id" + ], + "tableTo": "mcp_server", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agent_tools_agent_id_tool_id_unique": { + "name": "agent_tools_agent_id_tool_id_unique", + "columns": [ + "agent_id", + "tool_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "agent_scope", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_demo": { + "name": "is_demo", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "consider_context_untrusted": { + "name": "consider_context_untrusted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "agent_type": { + "name": "agent_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'mcp_gateway'" + }, + "system_prompt": { + "name": "system_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_prompt": { + "name": "user_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt_version": { + "name": "prompt_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "prompt_history": { + "name": "prompt_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "incoming_email_enabled": { + "name": "incoming_email_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "incoming_email_security_mode": { + "name": "incoming_email_security_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'private'" + }, + "incoming_email_allowed_domain": { + "name": "incoming_email_allowed_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "llm_api_key_id": { + "name": "llm_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "llm_model": { + "name": "llm_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "identity_provider_id": { + "name": "identity_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "built_in_agent_config": { + "name": "built_in_agent_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "built_in": { + "name": "built_in", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "generated": { + "type": "stored", + "as": "\"agents\".\"built_in_agent_config\" IS NOT NULL" + } + }, + "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": { + "agents_organization_id_idx": { + "name": "agents_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "agents_agent_type_idx": { + "name": "agents_agent_type_idx", + "columns": [ + { + "expression": "agent_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "agents_identity_provider_id_idx": { + "name": "agents_identity_provider_id_idx", + "columns": [ + { + "expression": "identity_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "agents_author_id_idx": { + "name": "agents_author_id_idx", + "columns": [ + { + "expression": "author_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "agents_scope_idx": { + "name": "agents_scope_idx", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "agents_author_id_user_id_fk": { + "name": "agents_author_id_user_id_fk", + "tableFrom": "agents", + "columnsFrom": [ + "author_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "agents_llm_api_key_id_chat_api_keys_id_fk": { + "name": "agents_llm_api_key_id_chat_api_keys_id_fk", + "tableFrom": "agents", + "columnsFrom": [ + "llm_api_key_id" + ], + "tableTo": "chat_api_keys", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "agents_identity_provider_id_identity_provider_id_fk": { + "name": "agents_identity_provider_id_identity_provider_id_fk", + "tableFrom": "agents", + "columnsFrom": [ + "identity_provider_id" + ], + "tableTo": "identity_provider", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_key_models": { + "name": "api_key_models", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "api_key_id": { + "name": "api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "model_id": { + "name": "model_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "is_fastest": { + "name": "is_fastest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_best": { + "name": "is_best", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "api_key_models_api_key_id_idx": { + "name": "api_key_models_api_key_id_idx", + "columns": [ + { + "expression": "api_key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "api_key_models_model_id_idx": { + "name": "api_key_models_model_id_idx", + "columns": [ + { + "expression": "model_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "api_key_models_api_key_id_chat_api_keys_id_fk": { + "name": "api_key_models_api_key_id_chat_api_keys_id_fk", + "tableFrom": "api_key_models", + "columnsFrom": [ + "api_key_id" + ], + "tableTo": "chat_api_keys", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "api_key_models_model_id_models_id_fk": { + "name": "api_key_models_model_id_models_id_fk", + "tableFrom": "api_key_models", + "columnsFrom": [ + "model_id" + ], + "tableTo": "models", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_models_unique": { + "name": "api_key_models_unique", + "columns": [ + "api_key_id", + "model_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.apikey": { + "name": "apikey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 86400000 + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_request": { + "name": "last_request", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "apikey_user_id_user_id_fk": { + "name": "apikey_user_id_user_id_fk", + "tableFrom": "apikey", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.browser_tab_states": { + "name": "browser_tab_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "isolation_key": { + "name": "isolation_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tab_index": { + "name": "tab_index", + "type": "integer", + "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": { + "browser_tab_states_agent_user_isolation_idx": { + "name": "browser_tab_states_agent_user_isolation_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "isolation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "browser_tab_states_user_id_idx": { + "name": "browser_tab_states_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "browser_tab_states_agent_id_agents_id_fk": { + "name": "browser_tab_states_agent_id_agents_id_fk", + "tableFrom": "browser_tab_states", + "columnsFrom": [ + "agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "browser_tab_states_user_id_user_id_fk": { + "name": "browser_tab_states_user_id_user_id_fk", + "tableFrom": "browser_tab_states", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_api_keys": { + "name": "chat_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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_api_keys_organization_id_idx": { + "name": "chat_api_keys_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "chat_api_keys_org_provider_idx": { + "name": "chat_api_keys_org_provider_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "chat_api_keys_system_unique": { + "name": "chat_api_keys_system_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"chat_api_keys\".\"is_system\" = true", + "concurrently": false + }, + "chat_api_keys_primary_personal_unique": { + "name": "chat_api_keys_primary_personal_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"chat_api_keys\".\"is_primary\" = true AND \"chat_api_keys\".\"scope\" = 'personal'", + "concurrently": false + }, + "chat_api_keys_primary_team_unique": { + "name": "chat_api_keys_primary_team_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"chat_api_keys\".\"is_primary\" = true AND \"chat_api_keys\".\"scope\" = 'team'", + "concurrently": false + }, + "chat_api_keys_primary_org_wide_unique": { + "name": "chat_api_keys_primary_org_wide_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"chat_api_keys\".\"is_primary\" = true AND \"chat_api_keys\".\"scope\" = 'org_wide'", + "concurrently": false + } + }, + "foreignKeys": { + "chat_api_keys_secret_id_secret_id_fk": { + "name": "chat_api_keys_secret_id_secret_id_fk", + "tableFrom": "chat_api_keys", + "columnsFrom": [ + "secret_id" + ], + "tableTo": "secret", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "chat_api_keys_user_id_user_id_fk": { + "name": "chat_api_keys_user_id_user_id_fk", + "tableFrom": "chat_api_keys", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "chat_api_keys_team_id_team_id_fk": { + "name": "chat_api_keys_team_id_team_id_fk", + "tableFrom": "chat_api_keys", + "columnsFrom": [ + "team_id" + ], + "tableTo": "team", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chatops_channel_binding": { + "name": "chatops_channel_binding", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "channel_name": { + "name": "channel_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "workspace_name": { + "name": "workspace_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "is_dm": { + "name": "is_dm", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dm_owner_email": { + "name": "dm_owner_email", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "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": { + "chatops_channel_binding_provider_channel_workspace_idx": { + "name": "chatops_channel_binding_provider_channel_workspace_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "chatops_channel_binding_organization_id_idx": { + "name": "chatops_channel_binding_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "chatops_channel_binding_agent_id_idx": { + "name": "chatops_channel_binding_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "chatops_channel_binding_agent_id_agents_id_fk": { + "name": "chatops_channel_binding_agent_id_agents_id_fk", + "tableFrom": "chatops_channel_binding", + "columnsFrom": [ + "agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chatops_processed_message": { + "name": "chatops_processed_message", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "message_id": { + "name": "message_id", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chatops_processed_message_processed_at_idx": { + "name": "chatops_processed_message_processed_at_idx", + "columns": [ + { + "expression": "processed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chatops_processed_message_message_id_unique": { + "name": "chatops_processed_message_message_id_unique", + "columns": [ + "message_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connector_runs": { + "name": "connector_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "connector_id": { + "name": "connector_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "documents_processed": { + "name": "documents_processed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "documents_ingested": { + "name": "documents_ingested", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "total_items": { + "name": "total_items", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_batches": { + "name": "total_batches", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "completed_batches": { + "name": "completed_batches", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "item_errors": { + "name": "item_errors", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logs": { + "name": "logs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkpoint": { + "name": "checkpoint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connector_runs_connector_id_idx": { + "name": "connector_runs_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "connector_runs_connector_id_knowledge_base_connectors_id_fk": { + "name": "connector_runs_connector_id_knowledge_base_connectors_id_fk", + "tableFrom": "connector_runs", + "columnsFrom": [ + "connector_id" + ], + "tableTo": "knowledge_base_connectors", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.conversation_enabled_tools": { + "name": "conversation_enabled_tools", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "conversation_enabled_tools_conversation_id_conversations_id_fk": { + "name": "conversation_enabled_tools_conversation_id_conversations_id_fk", + "tableFrom": "conversation_enabled_tools", + "columnsFrom": [ + "conversation_id" + ], + "tableTo": "conversations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "conversation_enabled_tools_tool_id_tools_id_fk": { + "name": "conversation_enabled_tools_tool_id_tools_id_fk", + "tableFrom": "conversation_enabled_tools", + "columnsFrom": [ + "tool_id" + ], + "tableTo": "tools", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "conversation_enabled_tools_conversation_id_tool_id_pk": { + "name": "conversation_enabled_tools_conversation_id_tool_id_pk", + "columns": [ + "conversation_id", + "tool_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.conversation_shares": { + "name": "conversation_shares", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visibility": { + "name": "visibility", + "type": "conversation_share_visibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'organization'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "conversation_shares_conversation_id_conversations_id_fk": { + "name": "conversation_shares_conversation_id_conversations_id_fk", + "tableFrom": "conversation_shares", + "columnsFrom": [ + "conversation_id" + ], + "tableTo": "conversations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "conversation_shares_conversation_id_unique": { + "name": "conversation_shares_conversation_id_unique", + "columns": [ + "conversation_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.conversations": { + "name": "conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_api_key_id": { + "name": "chat_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "selected_model": { + "name": "selected_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gpt-4o'" + }, + "selected_provider": { + "name": "selected_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_custom_tool_selection": { + "name": "has_custom_tool_selection", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "todo_list": { + "name": "todo_list", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "artifact": { + "name": "artifact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pinned_at": { + "name": "pinned_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": {}, + "foreignKeys": { + "conversations_agent_id_agents_id_fk": { + "name": "conversations_agent_id_agents_id_fk", + "tableFrom": "conversations", + "columnsFrom": [ + "agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "conversations_chat_api_key_id_chat_api_keys_id_fk": { + "name": "conversations_chat_api_key_id_chat_api_keys_id_fk", + "tableFrom": "conversations", + "columnsFrom": [ + "chat_api_key_id" + ], + "tableTo": "chat_api_keys", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dual_llm_config": { + "name": "dual_llm_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "main_agent_prompt": { + "name": "main_agent_prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "quarantined_agent_prompt": { + "name": "quarantined_agent_prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary_prompt": { + "name": "summary_prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "max_rounds": { + "name": "max_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "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": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dual_llm_results": { + "name": "dual_llm_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversations": { + "name": "conversations", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dual_llm_results_agent_id_idx": { + "name": "dual_llm_results_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "dual_llm_results_agent_id_agents_id_fk": { + "name": "dual_llm_results_agent_id_agents_id_fk", + "tableFrom": "dual_llm_results", + "columnsFrom": [ + "agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.identity_provider": { + "name": "identity_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role_mapping": { + "name": "role_mapping", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_sync_config": { + "name": "team_sync_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain_verified": { + "name": "domain_verified", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "identity_provider_user_id_user_id_fk": { + "name": "identity_provider_user_id_user_id_fk", + "tableFrom": "identity_provider", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "identity_provider_provider_id_unique": { + "name": "identity_provider_provider_id_unique", + "columns": [ + "provider_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.incoming_email_subscription": { + "name": "incoming_email_subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subscription_id": { + "name": "subscription_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "webhook_url": { + "name": "webhook_url", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": true + }, + "client_state": { + "name": "client_state", + "type": "varchar(256)", + "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": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interactions": { + "name": "interactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "external_agent_id": { + "name": "external_agent_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "session_source": { + "name": "session_source", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "request": { + "name": "request", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "processed_request": { + "name": "processed_request", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "response": { + "name": "response", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "baseline_model": { + "name": "baseline_model", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "baseline_cost": { + "name": "baseline_cost", + "type": "numeric(13, 10)", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "numeric(13, 10)", + "primaryKey": false, + "notNull": false + }, + "toon_tokens_before": { + "name": "toon_tokens_before", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "toon_tokens_after": { + "name": "toon_tokens_after", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "toon_cost_savings": { + "name": "toon_cost_savings", + "type": "numeric(13, 10)", + "primaryKey": false, + "notNull": false + }, + "toon_skip_reason": { + "name": "toon_skip_reason", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "interactions_agent_id_idx": { + "name": "interactions_agent_id_idx", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "interactions_external_agent_id_idx": { + "name": "interactions_external_agent_id_idx", + "columns": [ + { + "expression": "external_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "interactions_execution_id_idx": { + "name": "interactions_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "interactions_user_id_idx": { + "name": "interactions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "interactions_session_id_idx": { + "name": "interactions_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "interactions_created_at_idx": { + "name": "interactions_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "interactions_profile_created_at_idx": { + "name": "interactions_profile_created_at_idx", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "interactions_session_created_at_idx": { + "name": "interactions_session_created_at_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "interactions_profile_id_agents_id_fk": { + "name": "interactions_profile_id_agents_id_fk", + "tableFrom": "interactions", + "columnsFrom": [ + "profile_id" + ], + "tableTo": "agents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "interactions_user_id_user_id_fk": { + "name": "interactions_user_id_user_id_fk", + "tableFrom": "interactions", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.internal_mcp_catalog": { + "name": "internal_mcp_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "instructions": { + "name": "instructions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installation_command": { + "name": "installation_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requires_auth": { + "name": "requires_auth", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "auth_description": { + "name": "auth_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_fields": { + "name": "auth_fields", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "server_type": { + "name": "server_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "server_url": { + "name": "server_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "docs_url": { + "name": "docs_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_secret_id": { + "name": "client_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "local_config_secret_id": { + "name": "local_config_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "local_config": { + "name": "local_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "deployment_spec_yaml": { + "name": "deployment_spec_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_config": { + "name": "user_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "oauth_config": { + "name": "oauth_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "mcp_catalog_scope", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'org'" + }, + "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": { + "internal_mcp_catalog_organization_id_idx": { + "name": "internal_mcp_catalog_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "internal_mcp_catalog_author_id_idx": { + "name": "internal_mcp_catalog_author_id_idx", + "columns": [ + { + "expression": "author_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "internal_mcp_catalog_scope_idx": { + "name": "internal_mcp_catalog_scope_idx", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "internal_mcp_catalog_client_secret_id_secret_id_fk": { + "name": "internal_mcp_catalog_client_secret_id_secret_id_fk", + "tableFrom": "internal_mcp_catalog", + "columnsFrom": [ + "client_secret_id" + ], + "tableTo": "secret", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "internal_mcp_catalog_local_config_secret_id_secret_id_fk": { + "name": "internal_mcp_catalog_local_config_secret_id_secret_id_fk", + "tableFrom": "internal_mcp_catalog", + "columnsFrom": [ + "local_config_secret_id" + ], + "tableTo": "secret", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "internal_mcp_catalog_author_id_user_id_fk": { + "name": "internal_mcp_catalog_author_id_user_id_fk", + "tableFrom": "internal_mcp_catalog", + "columnsFrom": [ + "author_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "columnsFrom": [ + "inviter_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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.kb_chunks": { + "name": "kb_chunks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_index": { + "name": "chunk_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": false + }, + "acl": { + "name": "acl", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_chunks_document_id_idx": { + "name": "kb_chunks_document_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "kb_chunks_document_id_kb_documents_id_fk": { + "name": "kb_chunks_document_id_kb_documents_id_fk", + "tableFrom": "kb_chunks", + "columnsFrom": [ + "document_id" + ], + "tableTo": "kb_documents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kb_documents": { + "name": "kb_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connector_id": { + "name": "connector_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acl": { + "name": "acl", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "embedding_status": { + "name": "embedding_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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": { + "kb_documents_org_id_idx": { + "name": "kb_documents_org_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kb_documents_source_idx": { + "name": "kb_documents_source_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "kb_documents_connector_id_knowledge_base_connectors_id_fk": { + "name": "kb_documents_connector_id_knowledge_base_connectors_id_fk", + "tableFrom": "kb_documents", + "columnsFrom": [ + "connector_id" + ], + "tableTo": "knowledge_base_connectors", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base_connector_assignment": { + "name": "knowledge_base_connector_assignment", + "schema": "", + "columns": { + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_connector_assignment_kb_id_idx": { + "name": "kb_connector_assignment_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kb_connector_assignment_connector_id_idx": { + "name": "kb_connector_assignment_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_base_connector_assignment_knowledge_base_id_knowledge_bases_id_fk": { + "name": "knowledge_base_connector_assignment_knowledge_base_id_knowledge_bases_id_fk", + "tableFrom": "knowledge_base_connector_assignment", + "columnsFrom": [ + "knowledge_base_id" + ], + "tableTo": "knowledge_bases", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_base_connector_assignment_connector_id_knowledge_base_connectors_id_fk": { + "name": "knowledge_base_connector_assignment_connector_id_knowledge_base_connectors_id_fk", + "tableFrom": "knowledge_base_connector_assignment", + "columnsFrom": [ + "connector_id" + ], + "tableTo": "knowledge_base_connectors", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base_connectors": { + "name": "knowledge_base_connectors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_type": { + "name": "connector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'0 */6 * * *'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_sync_status": { + "name": "last_sync_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_error": { + "name": "last_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkpoint": { + "name": "checkpoint", + "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": { + "knowledge_base_connectors_organization_id_idx": { + "name": "knowledge_base_connectors_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_base_connectors_secret_id_secret_id_fk": { + "name": "knowledge_base_connectors_secret_id_secret_id_fk", + "tableFrom": "knowledge_base_connectors", + "columnsFrom": [ + "secret_id" + ], + "tableTo": "secret", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_bases": { + "name": "knowledge_bases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'org-wide'" + }, + "team_ids": { + "name": "team_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "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": { + "knowledge_bases_organization_id_idx": { + "name": "knowledge_bases_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.label_keys": { + "name": "label_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "key": { + "name": "key", + "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": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "label_keys_key_unique": { + "name": "label_keys_key_unique", + "columns": [ + "key" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.label_values": { + "name": "label_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "value": { + "name": "value", + "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": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "label_values_value_unique": { + "name": "label_values_value_unique", + "columns": [ + "value" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.limit_model_usage": { + "name": "limit_model_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "limit_id": { + "name": "limit_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "current_usage_tokens_in": { + "name": "current_usage_tokens_in", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "current_usage_tokens_out": { + "name": "current_usage_tokens_out", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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": { + "limit_model_usage_limit_id_idx": { + "name": "limit_model_usage_limit_id_idx", + "columns": [ + { + "expression": "limit_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "limit_model_usage_limit_model_idx": { + "name": "limit_model_usage_limit_model_idx", + "columns": [ + { + "expression": "limit_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "limit_model_usage_limit_id_limits_id_fk": { + "name": "limit_model_usage_limit_id_limits_id_fk", + "tableFrom": "limit_model_usage", + "columnsFrom": [ + "limit_id" + ], + "tableTo": "limits", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.limits": { + "name": "limits", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "entity_type": { + "name": "entity_type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "limit_type": { + "name": "limit_type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "limit_value": { + "name": "limit_value", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mcp_server_name": { + "name": "mcp_server_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_cleanup": { + "name": "last_cleanup", + "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": { + "limits_entity_idx": { + "name": "limits_entity_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "limits_type_idx": { + "name": "limits_type_idx", + "columns": [ + { + "expression": "limit_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_catalog_labels": { + "name": "mcp_catalog_labels", + "schema": "", + "columns": { + "catalog_id": { + "name": "catalog_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "value_id": { + "name": "value_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_catalog_labels_catalog_id_internal_mcp_catalog_id_fk": { + "name": "mcp_catalog_labels_catalog_id_internal_mcp_catalog_id_fk", + "tableFrom": "mcp_catalog_labels", + "columnsFrom": [ + "catalog_id" + ], + "tableTo": "internal_mcp_catalog", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "mcp_catalog_labels_key_id_label_keys_id_fk": { + "name": "mcp_catalog_labels_key_id_label_keys_id_fk", + "tableFrom": "mcp_catalog_labels", + "columnsFrom": [ + "key_id" + ], + "tableTo": "label_keys", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "mcp_catalog_labels_value_id_label_values_id_fk": { + "name": "mcp_catalog_labels_value_id_label_values_id_fk", + "tableFrom": "mcp_catalog_labels", + "columnsFrom": [ + "value_id" + ], + "tableTo": "label_values", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "mcp_catalog_labels_catalog_id_key_id_pk": { + "name": "mcp_catalog_labels_catalog_id_key_id_pk", + "columns": [ + "catalog_id", + "key_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_catalog_team": { + "name": "mcp_catalog_team", + "schema": "", + "columns": { + "catalog_id": { + "name": "catalog_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_catalog_team_catalog_id_internal_mcp_catalog_id_fk": { + "name": "mcp_catalog_team_catalog_id_internal_mcp_catalog_id_fk", + "tableFrom": "mcp_catalog_team", + "columnsFrom": [ + "catalog_id" + ], + "tableTo": "internal_mcp_catalog", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "mcp_catalog_team_team_id_team_id_fk": { + "name": "mcp_catalog_team_team_id_team_id_fk", + "tableFrom": "mcp_catalog_team", + "columnsFrom": [ + "team_id" + ], + "tableTo": "team", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "mcp_catalog_team_catalog_id_team_id_pk": { + "name": "mcp_catalog_team_catalog_id_team_id_pk", + "columns": [ + "catalog_id", + "team_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_http_sessions": { + "name": "mcp_http_sessions", + "schema": "", + "columns": { + "connection_key": { + "name": "connection_key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_endpoint_url": { + "name": "session_endpoint_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_endpoint_pod_name": { + "name": "session_endpoint_pod_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_server_installation_request": { + "name": "mcp_server_installation_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "external_catalog_id": { + "name": "external_catalog_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "request_reason": { + "name": "request_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "custom_server_config": { + "name": "custom_server_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "admin_response": { + "name": "admin_response", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reviewed_by": { + "name": "reviewed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reviewed_at": { + "name": "reviewed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "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": { + "mcp_server_installation_request_requested_by_user_id_fk": { + "name": "mcp_server_installation_request_requested_by_user_id_fk", + "tableFrom": "mcp_server_installation_request", + "columnsFrom": [ + "requested_by" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "mcp_server_installation_request_reviewed_by_user_id_fk": { + "name": "mcp_server_installation_request_reviewed_by_user_id_fk", + "tableFrom": "mcp_server_installation_request", + "columnsFrom": [ + "reviewed_by" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_server_user": { + "name": "mcp_server_user", + "schema": "", + "columns": { + "mcp_server_id": { + "name": "mcp_server_id", + "type": "uuid", + "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()" + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_server_user_mcp_server_id_mcp_server_id_fk": { + "name": "mcp_server_user_mcp_server_id_mcp_server_id_fk", + "tableFrom": "mcp_server_user", + "columnsFrom": [ + "mcp_server_id" + ], + "tableTo": "mcp_server", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "mcp_server_user_user_id_user_id_fk": { + "name": "mcp_server_user_user_id_user_id_fk", + "tableFrom": "mcp_server_user", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "mcp_server_user_mcp_server_id_user_id_pk": { + "name": "mcp_server_user_mcp_server_id_user_id_pk", + "columns": [ + "mcp_server_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_server": { + "name": "mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "catalog_id": { + "name": "catalog_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "server_type": { + "name": "server_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reinstall_required": { + "name": "reinstall_required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "local_installation_status": { + "name": "local_installation_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "local_installation_error": { + "name": "local_installation_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_refresh_error": { + "name": "oauth_refresh_error", + "type": "oauth_refresh_error_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "oauth_refresh_failed_at": { + "name": "oauth_refresh_failed_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": {}, + "foreignKeys": { + "mcp_server_catalog_id_internal_mcp_catalog_id_fk": { + "name": "mcp_server_catalog_id_internal_mcp_catalog_id_fk", + "tableFrom": "mcp_server", + "columnsFrom": [ + "catalog_id" + ], + "tableTo": "internal_mcp_catalog", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "mcp_server_secret_id_secret_id_fk": { + "name": "mcp_server_secret_id_secret_id_fk", + "tableFrom": "mcp_server", + "columnsFrom": [ + "secret_id" + ], + "tableTo": "secret", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "mcp_server_owner_id_user_id_fk": { + "name": "mcp_server_owner_id_user_id_fk", + "tableFrom": "mcp_server", + "columnsFrom": [ + "owner_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "mcp_server_team_id_team_id_fk": { + "name": "mcp_server_team_id_team_id_fk", + "tableFrom": "mcp_server", + "columnsFrom": [ + "team_id" + ], + "tableTo": "team", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_tool_calls": { + "name": "mcp_tool_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mcp_server_name": { + "name": "mcp_server_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "tool_call": { + "name": "tool_call", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tool_result": { + "name": "tool_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_method": { + "name": "auth_method", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_tool_calls_agent_id_idx": { + "name": "mcp_tool_calls_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "mcp_tool_calls_created_at_idx": { + "name": "mcp_tool_calls_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "mcp_tool_calls_agent_id_agents_id_fk": { + "name": "mcp_tool_calls_agent_id_agents_id_fk", + "tableFrom": "mcp_tool_calls", + "columnsFrom": [ + "agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "mcp_tool_calls_user_id_user_id_fk": { + "name": "mcp_tool_calls_user_id_user_id_fk", + "tableFrom": "mcp_tool_calls", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_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, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "default_agent_id": { + "name": "default_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "member_default_agent_id_agents_id_fk": { + "name": "member_default_agent_id_agents_id_fk", + "tableFrom": "member", + "columnsFrom": [ + "default_agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "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": false + } + }, + "indexes": { + "messages_conversation_id_idx": { + "name": "messages_conversation_id_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "messages_conversation_id_conversations_id_fk": { + "name": "messages_conversation_id_conversations_id_fk", + "tableFrom": "messages", + "columnsFrom": [ + "conversation_id" + ], + "tableTo": "conversations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.models": { + "name": "models", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context_length": { + "name": "context_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_modalities": { + "name": "input_modalities", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "output_modalities": { + "name": "output_modalities", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "supports_tool_calling": { + "name": "supports_tool_calling", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "prompt_price_per_token": { + "name": "prompt_price_per_token", + "type": "numeric(20, 12)", + "primaryKey": false, + "notNull": false + }, + "completion_price_per_token": { + "name": "completion_price_per_token", + "type": "numeric(20, 12)", + "primaryKey": false, + "notNull": false + }, + "custom_price_per_million_input": { + "name": "custom_price_per_million_input", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "custom_price_per_million_output": { + "name": "custom_price_per_million_output", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "discovered_via_llm_proxy": { + "name": "discovered_via_llm_proxy", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "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": { + "models_provider_model_idx": { + "name": "models_provider_model_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "models_external_id_idx": { + "name": "models_external_id_idx", + "columns": [ + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "models_provider_model_unique": { + "name": "models_provider_model_unique", + "columns": [ + "provider", + "model_id" + ], + "nullsNotDistinct": false + } + }, + "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": "text[]", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "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", + "columnsFrom": [ + "client_id" + ], + "tableTo": "oauth_client", + "columnsTo": [ + "client_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_access_token_session_id_session_id_fk": { + "name": "oauth_access_token_session_id_session_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "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", + "columnsFrom": [ + "refresh_id" + ], + "tableTo": "oauth_refresh_token", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_token_unique": { + "name": "oauth_access_token_token_unique", + "columns": [ + "token" + ], + "nullsNotDistinct": false + } + }, + "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 + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "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 + }, + "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 + }, + "scopes": { + "name": "scopes", + "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 + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "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": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "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": {}, + "foreignKeys": { + "oauth_client_user_id_user_id_fk": { + "name": "oauth_client_user_id_user_id_fk", + "tableFrom": "oauth_client", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "columns": [ + "client_id" + ], + "nullsNotDistinct": false + } + }, + "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": "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": { + "oauth_consent_client_id_oauth_client_client_id_fk": { + "name": "oauth_consent_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_consent", + "columnsFrom": [ + "client_id" + ], + "tableTo": "oauth_client", + "columnsTo": [ + "client_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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 + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "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", + "columnsFrom": [ + "client_id" + ], + "tableTo": "oauth_client", + "columnsTo": [ + "client_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_refresh_token_session_id_session_id_fk": { + "name": "oauth_refresh_token_session_id_session_id_fk", + "tableFrom": "oauth_refresh_token", + "columnsFrom": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "oauth_refresh_token_user_id_user_id_fk": { + "name": "oauth_refresh_token_user_id_user_id_fk", + "tableFrom": "oauth_refresh_token", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.optimization_rules": { + "name": "optimization_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "entity_type": { + "name": "entity_type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_model": { + "name": "target_model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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": { + "optimization_rules_entity_idx": { + "name": "optimization_rules_entity_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_role": { + "name": "organization_role", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "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": false + } + }, + "indexes": {}, + "foreignKeys": { + "organization_role_organization_id_organization_id_fk": { + "name": "organization_role_organization_id_organization_id_fk", + "tableFrom": "organization_role", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_role_organization_id_role_unique": { + "name": "organization_role_organization_id_role_unique", + "columns": [ + "organization_id", + "role" + ], + "nullsNotDistinct": false + } + }, + "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 + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logo_dark": { + "name": "logo_dark", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "limit_cleanup_interval": { + "name": "limit_cleanup_interval", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'1h'" + }, + "onboarding_complete": { + "name": "onboarding_complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'cosmic-night'" + }, + "custom_font": { + "name": "custom_font", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'lato'" + }, + "convert_tool_results_to_toon": { + "name": "convert_tool_results_to_toon", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "compression_scope": { + "name": "compression_scope", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'organization'" + }, + "global_tool_policy": { + "name": "global_tool_policy", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'permissive'" + }, + "allow_chat_file_uploads": { + "name": "allow_chat_file_uploads", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "embedding_chat_api_key_id": { + "name": "embedding_chat_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reranker_chat_api_key_id": { + "name": "reranker_chat_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reranker_model": { + "name": "reranker_model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "columns": [ + "slug" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.processed_email": { + "name": "processed_email", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "message_id": { + "name": "message_id", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "processed_email_processed_at_idx": { + "name": "processed_email_processed_at_idx", + "columns": [ + { + "expression": "processed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "processed_email_message_id_unique": { + "name": "processed_email_message_id_unique", + "columns": [ + "message_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret": { + "name": "secret", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true, + "default": "'secret'" + }, + "secret": { + "name": "secret", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "is_vault": { + "name": "is_vault", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_byos_vault": { + "name": "is_byos_vault", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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": {}, + "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 + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "columns": [ + "token" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tasks": { + "name": "tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "task_type": { + "name": "task_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "scheduled_for": { + "name": "scheduled_for", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "periodic": { + "name": "periodic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tasks_dequeue_idx": { + "name": "tasks_dequeue_idx", + "columns": [ + { + "expression": "task_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduled_for", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "tasks_unique_periodic_idx": { + "name": "tasks_unique_periodic_idx", + "columns": [ + { + "expression": "task_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"tasks\".\"periodic\" = true AND \"tasks\".\"status\" IN ('pending', 'processing')", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_external_group": { + "name": "team_external_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_identifier": { + "name": "group_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_external_group_team_id_team_id_fk": { + "name": "team_external_group_team_id_team_id_fk", + "tableFrom": "team_external_group", + "columnsFrom": [ + "team_id" + ], + "tableTo": "team", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_external_group_team_group_unique": { + "name": "team_external_group_team_group_unique", + "columns": [ + "team_id", + "group_identifier" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_member": { + "name": "team_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "team_id": { + "name": "team_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, + "default": "'member'" + }, + "synced_from_sso": { + "name": "synced_from_sso", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "team_member_team_id_team_id_fk": { + "name": "team_member_team_id_team_id_fk", + "tableFrom": "team_member", + "columnsFrom": [ + "team_id" + ], + "tableTo": "team", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "team_member_user_id_user_id_fk": { + "name": "team_member_user_id_user_id_fk", + "tableFrom": "team_member", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_token": { + "name": "team_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_organization_token": { + "name": "is_organization_token", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_start": { + "name": "token_start", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_team_token_org_id": { + "name": "idx_team_token_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_team_token_team_id": { + "name": "idx_team_token_team_id", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "team_token_organization_id_organization_id_fk": { + "name": "team_token_organization_id_organization_id_fk", + "tableFrom": "team_token", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "team_token_team_id_team_id_fk": { + "name": "team_token_team_id_team_id_fk", + "tableFrom": "team_token", + "columnsFrom": [ + "team_id" + ], + "tableTo": "team", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "team_token_secret_id_secret_id_fk": { + "name": "team_token_secret_id_secret_id_fk", + "tableFrom": "team_token", + "columnsFrom": [ + "secret_id" + ], + "tableTo": "secret", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_token_organization_id_team_id_unique": { + "name": "team_token_organization_id_team_id_unique", + "columns": [ + "organization_id", + "team_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_vault_folder": { + "name": "team_vault_folder", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vault_path": { + "name": "vault_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "team_vault_folder_team_id_team_id_fk": { + "name": "team_vault_folder_team_id_team_id_fk", + "tableFrom": "team_vault_folder", + "columnsFrom": [ + "team_id" + ], + "tableTo": "team", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_vault_folder_team_id_unique": { + "name": "team_vault_folder_team_id_unique", + "columns": [ + "team_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team": { + "name": "team", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "convert_tool_results_to_toon": { + "name": "convert_tool_results_to_toon", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_organization_id_organization_id_fk": { + "name": "team_organization_id_organization_id_fk", + "tableFrom": "team", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "team_created_by_user_id_fk": { + "name": "team_created_by_user_id_fk", + "tableFrom": "team", + "columnsFrom": [ + "created_by" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_invocation_policies": { + "name": "tool_invocation_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "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": { + "tool_invocation_policies_tool_id_tools_id_fk": { + "name": "tool_invocation_policies_tool_id_tools_id_fk", + "tableFrom": "tool_invocation_policies", + "columnsFrom": [ + "tool_id" + ], + "tableTo": "tools", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tools": { + "name": "tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_id": { + "name": "catalog_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mcp_server_id": { + "name": "mcp_server_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "delegate_to_agent_id": { + "name": "delegate_to_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parameters": { + "name": "parameters", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policies_auto_configured_at": { + "name": "policies_auto_configured_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "policies_auto_configuring_started_at": { + "name": "policies_auto_configuring_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "policies_auto_configured_reasoning": { + "name": "policies_auto_configured_reasoning", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policies_auto_configured_model": { + "name": "policies_auto_configured_model", + "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": { + "tools_delegate_to_agent_id_idx": { + "name": "tools_delegate_to_agent_id_idx", + "columns": [ + { + "expression": "delegate_to_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "tools_agent_id_agents_id_fk": { + "name": "tools_agent_id_agents_id_fk", + "tableFrom": "tools", + "columnsFrom": [ + "agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "tools_catalog_id_internal_mcp_catalog_id_fk": { + "name": "tools_catalog_id_internal_mcp_catalog_id_fk", + "tableFrom": "tools", + "columnsFrom": [ + "catalog_id" + ], + "tableTo": "internal_mcp_catalog", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "tools_mcp_server_id_mcp_server_id_fk": { + "name": "tools_mcp_server_id_mcp_server_id_fk", + "tableFrom": "tools", + "columnsFrom": [ + "mcp_server_id" + ], + "tableTo": "mcp_server", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "tools_delegate_to_agent_id_agents_id_fk": { + "name": "tools_delegate_to_agent_id_agents_id_fk", + "tableFrom": "tools", + "columnsFrom": [ + "delegate_to_agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tools_catalog_id_name_agent_id_delegate_to_agent_id_unique": { + "name": "tools_catalog_id_name_agent_id_delegate_to_agent_id_unique", + "columns": [ + "catalog_id", + "name", + "agent_id", + "delegate_to_agent_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.trusted_data_policies": { + "name": "trusted_data_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tool_id": { + "name": "tool_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'mark_as_trusted'" + }, + "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": { + "trusted_data_policies_tool_id_tools_id_fk": { + "name": "trusted_data_policies_tool_id_tools_id_fk", + "tableFrom": "trusted_data_policies", + "columnsFrom": [ + "tool_id" + ], + "tableTo": "tools", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "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": {}, + "foreignKeys": { + "two_factor_user_id_user_id_fk": { + "name": "two_factor_user_id_user_id_fk", + "tableFrom": "two_factor", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_token": { + "name": "user_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_start": { + "name": "token_start", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_user_token_org_id": { + "name": "idx_user_token_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_user_token_user_id": { + "name": "idx_user_token_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "user_token_organization_id_organization_id_fk": { + "name": "user_token_organization_id_organization_id_fk", + "tableFrom": "user_token", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "user_token_user_id_user_id_fk": { + "name": "user_token_user_id_user_id_fk", + "tableFrom": "user_token", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "user_token_secret_id_secret_id_fk": { + "name": "user_token_secret_id_secret_id_fk", + "tableFrom": "user_token", + "columnsFrom": [ + "secret_id" + ], + "tableTo": "secret", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_token_organization_id_user_id_unique": { + "name": "user_token_organization_id_user_id_unique", + "columns": [ + "organization_id", + "user_id" + ], + "nullsNotDistinct": false + } + }, + "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 + }, + "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()" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "nullsNotDistinct": false + } + }, + "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": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.virtual_api_keys": { + "name": "virtual_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_api_key_id": { + "name": "chat_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_start": { + "name": "token_start", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_virtual_api_key_chat_api_key_id": { + "name": "idx_virtual_api_key_chat_api_key_id", + "columns": [ + { + "expression": "chat_api_key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_virtual_api_key_token_start": { + "name": "idx_virtual_api_key_token_start", + "columns": [ + { + "expression": "token_start", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "virtual_api_keys_chat_api_key_id_chat_api_keys_id_fk": { + "name": "virtual_api_keys_chat_api_key_id_chat_api_keys_id_fk", + "tableFrom": "virtual_api_keys", + "columnsFrom": [ + "chat_api_key_id" + ], + "tableTo": "chat_api_keys", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "virtual_api_keys_secret_id_secret_id_fk": { + "name": "virtual_api_keys_secret_id_secret_id_fk", + "tableFrom": "virtual_api_keys", + "columnsFrom": [ + "secret_id" + ], + "tableTo": "secret", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.agent_scope": { + "name": "agent_scope", + "schema": "public", + "values": [ + "personal", + "team", + "org" + ] + }, + "public.conversation_share_visibility": { + "name": "conversation_share_visibility", + "schema": "public", + "values": [ + "organization" + ] + }, + "public.mcp_catalog_scope": { + "name": "mcp_catalog_scope", + "schema": "public", + "values": [ + "personal", + "team", + "org" + ] + }, + "public.oauth_refresh_error_enum": { + "name": "oauth_refresh_error_enum", + "schema": "public", + "values": [ + "refresh_failed", + "no_refresh_token" + ] + } + }, + "schemas": {}, + "views": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/platform/backend/src/database/migrations/meta/_journal.json b/platform/backend/src/database/migrations/meta/_journal.json index 59113fd13e..1e6f343fdb 100644 --- a/platform/backend/src/database/migrations/meta/_journal.json +++ b/platform/backend/src/database/migrations/meta/_journal.json @@ -1226,6 +1226,13 @@ "when": 1773082514382, "tag": "0174_unusual_ricochet", "breakpoints": true + }, + { + "idx": 175, + "version": "7", + "when": 1773197577794, + "tag": "0175_rename-query-knowledge-base-to-query-knowledge-sources", + "breakpoints": true } ] } \ No newline at end of file diff --git a/platform/backend/src/knowledge-base/connector-sync.ts b/platform/backend/src/knowledge-base/connector-sync.ts index 75b15a41f9..86b50f328a 100644 --- a/platform/backend/src/knowledge-base/connector-sync.ts +++ b/platform/backend/src/knowledge-base/connector-sync.ts @@ -55,8 +55,13 @@ class ConnectorSyncService { await ConnectorRunModel.interruptActiveRuns(connectorId); if (interrupted > 0) { log.info( - { connectorId, interrupted }, - "[ConnectorSync] Interrupted stale running runs", + { + connectorId, + connectorName: connector.name, + connectorType: connector.connectorType, + interrupted, + }, + "Interrupted stale running runs", ); } @@ -69,8 +74,13 @@ class ConnectorSyncService { documentsIngested: 0, }); - // Bind runId to logger so every log line in this sync includes it - const runLog = log.child({ runId: run.id, connectorId }); + // Bind runId, connectorName, and connectorType to logger so every log line in this sync includes them + const runLog = log.child({ + runId: run.id, + connectorId, + connectorName: connector.name, + connectorType: connector.connectorType, + }); // Propagate the run-scoped logger into the connector implementation if (connectorImpl instanceof BaseConnector) { @@ -92,14 +102,14 @@ class ConnectorSyncService { if (totalItems !== null && totalItems > 0) { await ConnectorRunModel.update(run.id, { totalItems }); - runLog.info({ totalItems }, "[ConnectorSync] Estimated total items"); + runLog.info({ totalItems }, "Estimated total items"); } } catch (error) { runLog.warn( { error: extractErrorMessage(error), }, - "[ConnectorSync] Failed to estimate total items, continuing without", + "Failed to estimate total items, continuing without", ); } @@ -141,7 +151,7 @@ class ConnectorSyncService { documentId: doc.id, error: extractErrorMessage(docError), }, - "[ConnectorSync] Failed to ingest document", + "Failed to ingest document", ); } } @@ -187,7 +197,7 @@ class ConnectorSyncService { maxDurationMs: options.maxDurationMs, documentsProcessed, }, - "[ConnectorSync] Time budget exceeded, stopping early for continuation", + "Time budget exceeded, stopping early for continuation", ); break; } @@ -215,7 +225,7 @@ class ConnectorSyncService { runLog.info( { documentsProcessed, documentsIngested }, - "[ConnectorSync] Partial sync completed, continuation needed", + "Partial sync completed, continuation needed", ); return { runId: run.id, status: "partial" }; @@ -272,7 +282,7 @@ class ConnectorSyncService { documentsIngested, batchCount, }, - "[ConnectorSync] Sync completed successfully", + "Sync completed successfully", ); return { runId: run.id, status: "success" }; @@ -294,7 +304,7 @@ class ConnectorSyncService { lastSyncAt: new Date(), }); - runLog.error({ error: errorMessage }, "[ConnectorSync] Sync failed"); + runLog.error({ error: errorMessage }, "Sync failed"); return { runId: run.id, status: "failed" }; } @@ -330,7 +340,7 @@ class ConnectorSyncService { documentId: doc.id, existingDocId: existing.id, }, - "[ConnectorSync] Document unchanged, skipping", + "Document unchanged, skipping", ); return { ingested: false, documentId: null }; } @@ -363,7 +373,7 @@ class ConnectorSyncService { documentId: doc.id, kbDocumentId: existing.id, }, - "[ConnectorSync] Updated existing document with new content", + "Updated existing document with new content", ); return { ingested: true, documentId: existing.id }; } @@ -396,7 +406,7 @@ class ConnectorSyncService { { documentId: doc.id, }, - "[ConnectorSync] Document ingested into kb_documents", + "Document ingested into kb_documents", ); return { ingested: true, documentId: created.id }; } @@ -425,7 +435,7 @@ class ConnectorSyncService { log.debug( { documentId, chunkCount: chunks.length }, - "[ConnectorSync] Document chunked and stored", + "Document chunked and stored", ); } @@ -442,7 +452,7 @@ class ConnectorSyncService { throw new Error(`Secret not found: ${secretId}`); } - log.debug({ secretId }, "[ConnectorSync] Credentials loaded"); + log.debug({ secretId }, "Credentials loaded"); const data = secret.secret as Record; return { diff --git a/platform/backend/src/knowledge-base/connectors/base-connector.ts b/platform/backend/src/knowledge-base/connectors/base-connector.ts index 1fe93f25fe..fd7249da1f 100644 --- a/platform/backend/src/knowledge-base/connectors/base-connector.ts +++ b/platform/backend/src/knowledge-base/connectors/base-connector.ts @@ -120,7 +120,7 @@ export abstract class BaseConnector implements Connector { status: response.status, delayMs: Math.round(delay), }, - "[Connector] Retryable HTTP error, will retry", + "Retryable HTTP error, will retry", ); await sleep(delay); continue; @@ -143,7 +143,7 @@ export abstract class BaseConnector implements Connector { error: lastError.message, delayMs: Math.round(delay), }, - "[Connector] Transient error, will retry", + "Transient error, will retry", ); await sleep(delay); continue; @@ -173,7 +173,7 @@ export abstract class BaseConnector implements Connector { resource: params.resource, error: message, }, - "[Connector] Failed to fetch sub-resource for item, using fallback", + "Failed to fetch sub-resource for item, using fallback", ); this.itemFailures.push({ itemId: params.itemId, diff --git a/platform/backend/src/knowledge-base/connectors/confluence/confluence-connector.ts b/platform/backend/src/knowledge-base/connectors/confluence/confluence-connector.ts index 0f80c01e9f..568b2c5a85 100644 --- a/platform/backend/src/knowledge-base/connectors/confluence/confluence-connector.ts +++ b/platform/backend/src/knowledge-base/connectors/confluence/confluence-connector.ts @@ -52,7 +52,7 @@ export class ConfluenceConnector extends BaseConnector { this.log.debug( { baseUrl: parsed.confluenceUrl, isCloud: parsed.isCloud }, - "[ConfluenceConnector] Testing connection", + "Testing connection", ); try { @@ -62,14 +62,11 @@ export class ConfluenceConnector extends BaseConnector { this.log, ); await client.space.getSpaces({ limit: 1 }); - this.log.debug("[ConfluenceConnector] Connection test successful"); + this.log.debug("Connection test successful"); return { success: true }; } catch (error) { const message = extractErrorMessage(error); - this.log.error( - { error: message }, - "[ConfluenceConnector] Connection test failed", - ); + this.log.error({ error: message }, "Connection test failed"); return { success: false, error: `Connection failed: ${message}` }; } } @@ -88,7 +85,7 @@ export class ConfluenceConnector extends BaseConnector { }; const cql = buildCql(parsed, checkpoint); - this.log.debug({ cql }, "[ConfluenceConnector] Estimating total items"); + this.log.debug({ cql }, "Estimating total items"); const client = createConfluenceClient( parsed, @@ -107,7 +104,7 @@ export class ConfluenceConnector extends BaseConnector { } catch (error) { this.log.warn( { error: extractErrorMessage(error) }, - "[ConfluenceConnector] Failed to estimate total items", + "Failed to estimate total items", ); return null; } @@ -140,7 +137,7 @@ export class ConfluenceConnector extends BaseConnector { cql, checkpoint, }, - "[ConfluenceConnector] Starting sync", + "Starting sync", ); let cursor: string | undefined; @@ -151,10 +148,7 @@ export class ConfluenceConnector extends BaseConnector { await this.rateLimit(); try { - this.log.debug( - { batchIndex, cursor }, - "[ConfluenceConnector] Fetching batch", - ); + this.log.debug({ batchIndex, cursor }, "Fetching batch"); const searchResult = await client.content.searchContentByCQL({ cql, @@ -198,7 +192,7 @@ export class ConfluenceConnector extends BaseConnector { documentCount: documents.length, hasMore, }, - "[ConfluenceConnector] Batch fetched", + "Batch fetched", ); batchIndex++; @@ -218,7 +212,7 @@ export class ConfluenceConnector extends BaseConnector { } catch (error) { this.log.error( { batchIndex, error: extractErrorMessage(error) }, - "[ConfluenceConnector] Batch fetch failed", + "Batch fetch failed", ); throw error; } @@ -252,7 +246,7 @@ function createConfluenceClient( url: err?.config?.url, error: err?.message ?? String(error), }, - "[ConfluenceConnector] HTTP error", + "HTTP error", ); }, onResponse: (response: unknown) => { @@ -264,7 +258,7 @@ function createConfluenceClient( method: res?.config?.method?.toUpperCase(), url: res?.config?.url, }, - "[ConfluenceConnector] HTTP response", + "HTTP response", ); }, }, diff --git a/platform/backend/src/knowledge-base/connectors/github/github-connector.ts b/platform/backend/src/knowledge-base/connectors/github/github-connector.ts index 7feed47a81..deca947a02 100644 --- a/platform/backend/src/knowledge-base/connectors/github/github-connector.ts +++ b/platform/backend/src/knowledge-base/connectors/github/github-connector.ts @@ -53,20 +53,17 @@ export class GithubConnector extends BaseConnector { this.log.debug( { baseUrl: parsed.githubUrl, owner: parsed.owner }, - "[GithubConnector] Testing connection", + "Testing connection", ); try { const octokit = createOctokit(parsed, params.credentials, this.log); await octokit.rest.users.getAuthenticated(); - this.log.debug("[GithubConnector] Connection test successful"); + this.log.debug("Connection test successful"); return { success: true }; } catch (error) { const message = error instanceof Error ? error.message : String(error); - this.log.error( - { error: message }, - "[GithubConnector] Connection test failed", - ); + this.log.error({ error: message }, "Connection test failed"); return { success: false, error: `Connection failed: ${message}` }; } } @@ -81,7 +78,7 @@ export class GithubConnector extends BaseConnector { this.log.debug( { owner: parsed.owner, repos: parsed.repos }, - "[GithubConnector] Estimating total items", + "Estimating total items", ); try { @@ -113,7 +110,7 @@ export class GithubConnector extends BaseConnector { } catch (error) { this.log.warn( { error: extractErrorMessage(error) }, - "[GithubConnector] Failed to estimate total items", + "Failed to estimate total items", ); return null; } @@ -146,7 +143,7 @@ export class GithubConnector extends BaseConnector { includePullRequests: parsed.includePullRequests, checkpoint, }, - "[GithubConnector] Starting sync", + "Starting sync", ); for (let repoIdx = 0; repoIdx < repos.length; repoIdx++) { @@ -193,7 +190,7 @@ export class GithubConnector extends BaseConnector { this.log.debug( { repo: `${repo.owner}/${repo.name}`, kind }, - "[GithubConnector] Syncing repo items", + "Syncing repo items", ); while (pageHasMore) { @@ -203,7 +200,7 @@ export class GithubConnector extends BaseConnector { try { this.log.debug( { repo: `${repo.owner}/${repo.name}`, kind, page }, - "[GithubConnector] Fetching batch", + "Fetching batch", ); response = await octokit.rest.issues.listForRepo({ @@ -226,7 +223,7 @@ export class GithubConnector extends BaseConnector { ) { this.log.debug( { repo: `${repo.owner}/${repo.name}`, kind }, - "[GithubConnector] Repo not found or issues disabled, skipping", + "Repo not found or issues disabled, skipping", ); break; } @@ -237,7 +234,7 @@ export class GithubConnector extends BaseConnector { page, error: extractErrorMessage(err), }, - "[GithubConnector] Batch fetch failed", + "Batch fetch failed", ); throw err; } @@ -272,7 +269,7 @@ export class GithubConnector extends BaseConnector { documentCount: documents.length, hasMore: pageHasMore || !isLastGroup, }, - "[GithubConnector] Batch fetched", + "Batch fetched", ); const lastItem = items.length > 0 ? items[items.length - 1] : null; @@ -304,13 +301,12 @@ function createOctokit( baseUrl: config.githubUrl.replace(/\/+$/, ""), log: { debug: (message: string) => - log.debug({ sdkMessage: message }, "[GithubConnector] SDK debug"), - info: (message: string) => - log.debug({ sdkMessage: message }, "[GithubConnector] SDK info"), + log.debug({ sdkMessage: message }, "SDK debug"), + info: (message: string) => log.debug({ sdkMessage: message }, "SDK info"), warn: (message: string) => - log.warn({ sdkMessage: message }, "[GithubConnector] SDK warning"), + log.warn({ sdkMessage: message }, "SDK warning"), error: (message: string) => - log.error({ sdkMessage: message }, "[GithubConnector] SDK error"), + log.error({ sdkMessage: message }, "SDK error"), }, request: { fetch: (url: string | URL | Request, init?: RequestInit) => diff --git a/platform/backend/src/knowledge-base/connectors/gitlab/gitlab-connector.ts b/platform/backend/src/knowledge-base/connectors/gitlab/gitlab-connector.ts index ab3586f93c..0667f4fb95 100644 --- a/platform/backend/src/knowledge-base/connectors/gitlab/gitlab-connector.ts +++ b/platform/backend/src/knowledge-base/connectors/gitlab/gitlab-connector.ts @@ -48,22 +48,16 @@ export class GitlabConnector extends BaseConnector { return { success: false, error: "Invalid GitLab configuration" }; } - this.log.debug( - { baseUrl: parsed.gitlabUrl }, - "[GitlabConnector] Testing connection", - ); + this.log.debug({ baseUrl: parsed.gitlabUrl }, "Testing connection"); try { const client = createGitlabClient(parsed, params.credentials); await client.Users.showCurrentUser(); - this.log.debug("[GitlabConnector] Connection test successful"); + this.log.debug("Connection test successful"); return { success: true }; } catch (error) { const message = error instanceof Error ? error.message : String(error); - this.log.error( - { error: message }, - "[GitlabConnector] Connection test failed", - ); + this.log.error({ error: message }, "Connection test failed"); return { success: false, error: `Connection failed: ${message}` }; } } @@ -78,7 +72,7 @@ export class GitlabConnector extends BaseConnector { this.log.debug( { projectIds: parsed.projectIds, groupId: parsed.groupId }, - "[GitlabConnector] Estimating total items", + "Estimating total items", ); try { @@ -116,7 +110,7 @@ export class GitlabConnector extends BaseConnector { } catch (error) { this.log.warn( { error: extractErrorMessage(error) }, - "[GitlabConnector] Failed to estimate total items", + "Failed to estimate total items", ); return null; } @@ -148,7 +142,7 @@ export class GitlabConnector extends BaseConnector { includeMergeRequests: parsed.includeMergeRequests, checkpoint, }, - "[GitlabConnector] Starting sync", + "Starting sync", ); for (let projIdx = 0; projIdx < projects.length; projIdx++) { @@ -192,7 +186,7 @@ export class GitlabConnector extends BaseConnector { this.log.debug( { project: project.pathWithNamespace }, - "[GitlabConnector] Syncing project issues", + "Syncing project issues", ); while (pageHasMore) { @@ -201,7 +195,7 @@ export class GitlabConnector extends BaseConnector { try { this.log.debug( { project: project.pathWithNamespace, page }, - "[GitlabConnector] Fetching issues batch", + "Fetching issues batch", ); // biome-ignore lint/suspicious/noExplicitAny: Gitbeaker Camelize types @@ -243,7 +237,7 @@ export class GitlabConnector extends BaseConnector { documentCount: documents.length, hasMore: pageHasMore || !isLastGroup, }, - "[GitlabConnector] Issues batch fetched", + "Issues batch fetched", ); const lastIssue = @@ -265,7 +259,7 @@ export class GitlabConnector extends BaseConnector { page, error: extractErrorMessage(error), }, - "[GitlabConnector] Issues batch fetch failed", + "Issues batch fetch failed", ); throw error; } @@ -285,7 +279,7 @@ export class GitlabConnector extends BaseConnector { this.log.debug( { project: project.pathWithNamespace }, - "[GitlabConnector] Syncing project merge requests", + "Syncing project merge requests", ); while (pageHasMore) { @@ -294,7 +288,7 @@ export class GitlabConnector extends BaseConnector { try { this.log.debug( { project: project.pathWithNamespace, page }, - "[GitlabConnector] Fetching merge requests batch", + "Fetching merge requests batch", ); // biome-ignore lint/suspicious/noExplicitAny: Gitbeaker Camelize types @@ -336,7 +330,7 @@ export class GitlabConnector extends BaseConnector { documentCount: documents.length, hasMore: pageHasMore || !isLastGroup, }, - "[GitlabConnector] Merge requests batch fetched", + "Merge requests batch fetched", ); const lastMr = @@ -358,7 +352,7 @@ export class GitlabConnector extends BaseConnector { page, error: extractErrorMessage(error), }, - "[GitlabConnector] Merge requests batch fetch failed", + "Merge requests batch fetch failed", ); throw error; } diff --git a/platform/backend/src/knowledge-base/connectors/jira/jira-connector.ts b/platform/backend/src/knowledge-base/connectors/jira/jira-connector.ts index e69c833b84..202a0e3011 100644 --- a/platform/backend/src/knowledge-base/connectors/jira/jira-connector.ts +++ b/platform/backend/src/knowledge-base/connectors/jira/jira-connector.ts @@ -66,7 +66,7 @@ export class JiraConnector extends BaseConnector { this.log.info( { baseUrl: parsed.jiraBaseUrl, isCloud: parsed.isCloud }, - "[JiraConnector] Testing connection", + "Testing connection", ); try { @@ -77,13 +77,13 @@ export class JiraConnector extends BaseConnector { const client = createV2Client(parsed, params.credentials, this.log); await client.myself.getCurrentUser(); } - this.log.info("[JiraConnector] Connection test successful"); + this.log.info("Connection test successful"); return { success: true }; } catch (error) { const message = error instanceof Error ? error.message : String(error); this.log.error( { error: message, ...extractJiraErrorDetails(error) }, - "[JiraConnector] Connection test failed", + "Connection test failed", ); return { success: false, error: `Connection failed: ${message}` }; } @@ -103,7 +103,7 @@ export class JiraConnector extends BaseConnector { }; const jql = buildJql(parsed, checkpoint); - this.log.info({ jql }, "[JiraConnector] Estimating total items"); + this.log.info({ jql }, "Estimating total items"); // Use classic JQL search with maxResults=0 to get total without fetching issues if (parsed.isCloud) { @@ -129,7 +129,7 @@ export class JiraConnector extends BaseConnector { error: extractErrorMessage(error), ...extractJiraErrorDetails(error), }, - "[JiraConnector] Failed to estimate total items", + "Failed to estimate total items", ); return null; } @@ -160,7 +160,7 @@ export class JiraConnector extends BaseConnector { jql, checkpoint, }, - "[JiraConnector] Starting sync", + "Starting sync", ); if (parsed.isCloud) { @@ -187,10 +187,7 @@ export class JiraConnector extends BaseConnector { await this.rateLimit(); try { - this.log.debug( - { batchIndex, nextPageToken }, - "[JiraConnector] Fetching cloud batch", - ); + this.log.debug({ batchIndex, nextPageToken }, "Fetching cloud batch"); const searchResult = await client.issueSearch.searchForIssuesUsingJqlEnhancedSearchPost({ @@ -213,7 +210,7 @@ export class JiraConnector extends BaseConnector { documentCount: documents.length, hasMore, }, - "[JiraConnector] Cloud batch fetched", + "Cloud batch fetched", ); batchIndex++; @@ -226,7 +223,7 @@ export class JiraConnector extends BaseConnector { error: extractErrorMessage(error), ...extractJiraErrorDetails(error), }, - "[JiraConnector] Cloud batch fetch failed", + "Cloud batch fetch failed", ); throw error; } @@ -248,10 +245,7 @@ export class JiraConnector extends BaseConnector { await this.rateLimit(); try { - this.log.debug( - { batchIndex, startAt }, - "[JiraConnector] Fetching server batch", - ); + this.log.debug({ batchIndex, startAt }, "Fetching server batch"); const searchResult = await client.issueSearch.searchForIssuesUsingJqlPost({ @@ -277,7 +271,7 @@ export class JiraConnector extends BaseConnector { total: searchResult.total, hasMore, }, - "[JiraConnector] Server batch fetched", + "Server batch fetched", ); batchIndex++; @@ -290,7 +284,7 @@ export class JiraConnector extends BaseConnector { error: extractErrorMessage(error), ...extractJiraErrorDetails(error), }, - "[JiraConnector] Server batch fetch failed", + "Server batch fetch failed", ); throw error; } @@ -345,7 +339,7 @@ function buildJiraMiddlewares(log: pino.Logger) { url: err?.config?.url, error: err?.message ?? String(error), }, - "[JiraConnector] HTTP error", + "HTTP error", ); }, onResponse: (response: unknown) => { @@ -357,7 +351,7 @@ function buildJiraMiddlewares(log: pino.Logger) { method: res?.config?.method?.toUpperCase(), url: res?.config?.url, }, - "[JiraConnector] HTTP response", + "HTTP response", ); }, }; diff --git a/platform/backend/src/models/agent-tool.test.ts b/platform/backend/src/models/agent-tool.test.ts index 8dc494c227..a628f274c8 100644 --- a/platform/backend/src/models/agent-tool.test.ts +++ b/platform/backend/src/models/agent-tool.test.ts @@ -1,4 +1,8 @@ -import { BUILT_IN_AGENT_IDS, BUILT_IN_AGENT_NAMES } from "@shared"; +import { + BUILT_IN_AGENT_IDS, + BUILT_IN_AGENT_NAMES, + TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME, +} from "@shared"; import { vi } from "vitest"; import { describe, expect, test } from "@/test"; import AgentModel from "./agent"; @@ -1147,4 +1151,29 @@ describe("AgentToolModel.findAll", () => { vi.doUnmock("@/agents/subagents/policy-configuration"); }); }); + + describe("Knowledge sources tool filtering", () => { + test("findAll excludes query_knowledge_sources tool", async ({ + makeAgent, + makeTool, + makeAgentTool, + }) => { + const agent = await makeAgent(); + const regularTool = await makeTool({ name: "regular-tool" }); + const kbTool = await makeTool({ + name: TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME, + }); + await makeAgentTool(agent.id, regularTool.id); + await makeAgentTool(agent.id, kbTool.id); + + const result = await AgentToolModel.findAll({ + filters: { agentId: agent.id, excludeArchestraTools: true }, + skipPagination: true, + }); + + const toolNames = result.data.map((at) => at.tool.name); + expect(toolNames).toContain("regular-tool"); + expect(toolNames).not.toContain(TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME); + }); + }); }); diff --git a/platform/backend/src/models/agent-tool.ts b/platform/backend/src/models/agent-tool.ts index a01987d9bc..673240ccf7 100644 --- a/platform/backend/src/models/agent-tool.ts +++ b/platform/backend/src/models/agent-tool.ts @@ -1,4 +1,7 @@ -import { BUILT_IN_AGENT_IDS } from "@shared"; +import { + BUILT_IN_AGENT_IDS, + TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME, +} from "@shared"; import { and, asc, @@ -8,6 +11,7 @@ import { getTableColumns, inArray, isNotNull, + ne, or, type SQL, sql, @@ -800,6 +804,11 @@ class AgentToolModel { ); } + // Always exclude the knowledge sources tool (auto-injected, not user-assignable) + whereConditions.push( + ne(schema.toolsTable.name, TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME), + ); + const whereClause = whereConditions.length > 0 ? and(...whereConditions) : undefined; diff --git a/platform/backend/src/models/knowledge-base-connector.test.ts b/platform/backend/src/models/knowledge-base-connector.test.ts index d207c6f399..b50da5b49b 100644 --- a/platform/backend/src/models/knowledge-base-connector.test.ts +++ b/platform/backend/src/models/knowledge-base-connector.test.ts @@ -626,6 +626,120 @@ describe("KnowledgeBaseConnectorModel", () => { }); }); + describe("checkpoint management", () => { + test("checkpoint can be set and persisted", async ({ + makeOrganization, + makeKnowledgeBase, + makeKnowledgeBaseConnector, + }) => { + const org = await makeOrganization(); + const kb = await makeKnowledgeBase(org.id); + const connector = await makeKnowledgeBaseConnector(kb.id, org.id); + + const checkpoint = { + type: "jira" as const, + lastSyncedAt: "2026-03-10T15:30:00.000Z", + lastIssueKey: "PROJ-123", + }; + + const updated = await KnowledgeBaseConnectorModel.update(connector.id, { + checkpoint, + }); + + expect(updated?.checkpoint).toEqual(checkpoint); + }); + + test("checkpoint is reset to null when config is updated with checkpoint: null", async ({ + makeOrganization, + makeKnowledgeBase, + makeKnowledgeBaseConnector, + }) => { + const org = await makeOrganization(); + const kb = await makeKnowledgeBase(org.id); + const connector = await makeKnowledgeBaseConnector(kb.id, org.id); + + // Set a checkpoint + await KnowledgeBaseConnectorModel.update(connector.id, { + checkpoint: { + type: "jira", + lastSyncedAt: "2026-03-10T15:30:00.000Z", + lastIssueKey: "PROJ-123", + }, + }); + + // Update config + reset checkpoint (as the route handler does) + const updated = await KnowledgeBaseConnectorModel.update(connector.id, { + config: { + type: "jira", + jiraBaseUrl: "https://new-instance.atlassian.net", + isCloud: true, + projectKey: "NEW", + }, + checkpoint: null, + }); + + expect(updated?.checkpoint).toBeNull(); + expect((updated?.config as Record).jiraBaseUrl).toBe( + "https://new-instance.atlassian.net", + ); + }); + + test("checkpoint is preserved when only non-config fields are updated", async ({ + makeOrganization, + makeKnowledgeBase, + makeKnowledgeBaseConnector, + }) => { + const org = await makeOrganization(); + const kb = await makeKnowledgeBase(org.id); + const connector = await makeKnowledgeBaseConnector(kb.id, org.id); + + const checkpoint = { + type: "jira" as const, + lastSyncedAt: "2026-03-10T15:30:00.000Z", + lastIssueKey: "PROJ-123", + }; + + await KnowledgeBaseConnectorModel.update(connector.id, { checkpoint }); + + // Update only name - checkpoint should be preserved + const updated = await KnowledgeBaseConnectorModel.update(connector.id, { + name: "Renamed Connector", + }); + + expect(updated?.name).toBe("Renamed Connector"); + expect(updated?.checkpoint).toEqual(checkpoint); + }); + + test("resetCheckpointsByOrganization resets all connectors in the org", async ({ + makeOrganization, + makeKnowledgeBase, + makeKnowledgeBaseConnector, + }) => { + const org1 = await makeOrganization(); + const org2 = await makeOrganization(); + const kb1 = await makeKnowledgeBase(org1.id); + const kb2 = await makeKnowledgeBase(org2.id); + const c1 = await makeKnowledgeBaseConnector(kb1.id, org1.id); + const c2 = await makeKnowledgeBaseConnector(kb2.id, org2.id); + + const checkpoint = { + type: "jira" as const, + lastSyncedAt: "2026-03-10T15:30:00.000Z", + }; + + await KnowledgeBaseConnectorModel.update(c1.id, { checkpoint }); + await KnowledgeBaseConnectorModel.update(c2.id, { checkpoint }); + + await KnowledgeBaseConnectorModel.resetCheckpointsByOrganization(org1.id); + + const after1 = await KnowledgeBaseConnectorModel.findById(c1.id); + const after2 = await KnowledgeBaseConnectorModel.findById(c2.id); + + expect(after1?.checkpoint).toBeNull(); + expect(after2?.checkpoint).toEqual(checkpoint); + }); + }); + describe("getKnowledgeBaseIds", () => { test("returns knowledge base IDs for a connector", async ({ makeOrganization, diff --git a/platform/backend/src/models/knowledge-base.ts b/platform/backend/src/models/knowledge-base.ts index b997bb5e56..a73ca01cd4 100644 --- a/platform/backend/src/models/knowledge-base.ts +++ b/platform/backend/src/models/knowledge-base.ts @@ -1,4 +1,4 @@ -import { count, desc, eq } from "drizzle-orm"; +import { count, desc, eq, inArray } from "drizzle-orm"; import db, { schema } from "@/database"; import type { InsertKnowledgeBase, @@ -40,6 +40,14 @@ class KnowledgeBaseModel { return result ?? null; } + static async findByIds(ids: string[]): Promise { + if (ids.length === 0) return []; + return await db + .select() + .from(schema.knowledgeBasesTable) + .where(inArray(schema.knowledgeBasesTable.id, ids)); + } + static async create(data: InsertKnowledgeBase): Promise { const [result] = await db .insert(schema.knowledgeBasesTable) diff --git a/platform/backend/src/models/tool.test.ts b/platform/backend/src/models/tool.test.ts index 2ed1725e62..bde4e32dd2 100644 --- a/platform/backend/src/models/tool.test.ts +++ b/platform/backend/src/models/tool.test.ts @@ -1,7 +1,7 @@ import { MCP_SERVER_TOOL_NAME_SEPARATOR, TOOL_ARTIFACT_WRITE_FULL_NAME, - TOOL_QUERY_KNOWLEDGE_BASE_FULL_NAME, + TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME, TOOL_TODO_WRITE_FULL_NAME, } from "@shared"; import db, { schema } from "@/database"; @@ -1349,7 +1349,7 @@ describe("ToolModel", () => { }); describe("assignDefaultArchestraToolsToAgent", () => { - test("assigns all default tools including query_knowledge_base, but getMcpToolsByAgent filters it out when agent has no KG", async ({ + test("assigns all default tools including query_knowledge_sources, but getMcpToolsByAgent filters it out when agent has no knowledge sources", async ({ makeAgent, seedAndAssignArchestraTools, }) => { @@ -1360,14 +1360,14 @@ describe("ToolModel", () => { // Create a new agent WITHOUT a knowledgeBaseId const agent = await makeAgent({ name: "Test Agent" }); - // Assign default tools (always includes query_knowledge_base now) + // Assign default tools (always includes query_knowledge_sources now) await ToolModel.assignDefaultArchestraToolsToAgent(agent.id); // Verify the tool was assigned in the junction table const assignedToolIds = await AgentToolModel.findToolIdsByAgent(agent.id); expect(assignedToolIds.length).toBeGreaterThanOrEqual(3); - // But getMcpToolsByAgent filters it out because the agent has no KG + // But getMcpToolsByAgent filters it out because the agent has no knowledge sources const mcpTools = await ToolModel.getMcpToolsByAgent(agent.id); const toolNames = mcpTools.map((t) => t.name); @@ -1375,11 +1375,11 @@ describe("ToolModel", () => { expect(toolNames).toContain(TOOL_ARTIFACT_WRITE_FULL_NAME); expect(toolNames).toContain(TOOL_TODO_WRITE_FULL_NAME); - // query_knowledge_base is filtered out at query time because agent has no KG - expect(toolNames).not.toContain(TOOL_QUERY_KNOWLEDGE_BASE_FULL_NAME); + // query_knowledge_sources is filtered out at query time because agent has no knowledge sources + expect(toolNames).not.toContain(TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME); }); - test("includes query_knowledge_base when agent has a knowledge base assigned", async ({ + test("includes query_knowledge_sources when agent has a knowledge base assigned", async ({ makeAgent, makeOrganization, makeKnowledgeBase, @@ -1393,9 +1393,9 @@ describe("ToolModel", () => { const org = await makeOrganization(); const kg = await makeKnowledgeBase(org.id); - // Create a new agent and assign the KG + // Create a new agent and assign the knowledge base const agent = await makeAgent({ - name: "KG Enabled Agent", + name: "Knowledge Base Enabled Agent", organizationId: org.id, }); await db @@ -1409,10 +1409,10 @@ describe("ToolModel", () => { const mcpTools = await ToolModel.getMcpToolsByAgent(agent.id); const toolNames = mcpTools.map((t) => t.name); - // Should have all three default tools including query_knowledge_base + // Should have all three default tools including query_knowledge_sources expect(toolNames).toContain(TOOL_ARTIFACT_WRITE_FULL_NAME); expect(toolNames).toContain(TOOL_TODO_WRITE_FULL_NAME); - expect(toolNames).toContain(TOOL_QUERY_KNOWLEDGE_BASE_FULL_NAME); + expect(toolNames).toContain(TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME); }); test("is idempotent - does not create duplicates", async ({ @@ -1450,7 +1450,7 @@ describe("ToolModel", () => { }); describe("knowledge base tool visibility", () => { - test("getMcpToolsByAgent excludes query_knowledge_base when agent has no KG assigned", async ({ + test("getMcpToolsByAgent excludes query_knowledge_sources when agent has no knowledge base assigned", async ({ makeAgent, seedAndAssignArchestraTools, }) => { @@ -1461,13 +1461,13 @@ describe("ToolModel", () => { const tools = await ToolModel.getMcpToolsByAgent(agent.id); const toolNames = tools.map((t) => t.name); - expect(toolNames).not.toContain(TOOL_QUERY_KNOWLEDGE_BASE_FULL_NAME); + expect(toolNames).not.toContain(TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME); // Other Archestra tools should still be present expect(toolNames).toContain(TOOL_ARTIFACT_WRITE_FULL_NAME); expect(toolNames).toContain(TOOL_TODO_WRITE_FULL_NAME); }); - test("getMcpToolsByAgent includes query_knowledge_base when agent has a KG assigned", async ({ + test("getMcpToolsByAgent includes query_knowledge_sources when agent has a knowledge base assigned", async ({ makeAgent, makeOrganization, makeKnowledgeBase, @@ -1477,7 +1477,7 @@ describe("ToolModel", () => { const org = await makeOrganization(); const kg = await makeKnowledgeBase(org.id); - // Create agent and assign the KG + // Create agent and assign the knowledge base const agent = await makeAgent({ organizationId: org.id }); await db .insert(schema.agentKnowledgeBasesTable) @@ -1488,10 +1488,10 @@ describe("ToolModel", () => { const tools = await ToolModel.getMcpToolsByAgent(agent.id); const toolNames = tools.map((t) => t.name); - expect(toolNames).toContain(TOOL_QUERY_KNOWLEDGE_BASE_FULL_NAME); + expect(toolNames).toContain(TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME); }); - test("findByCatalogId includes query_knowledge_base (always visible in catalog)", async ({ + test("findByCatalogId excludes query_knowledge_sources (auto-injected, not user-assignable)", async ({ makeAgent, seedAndAssignArchestraTools, }) => { @@ -1502,11 +1502,11 @@ describe("ToolModel", () => { const tools = await ToolModel.findByCatalogId(ARCHESTRA_MCP_CATALOG_ID); const toolNames = tools.map((t) => t.name); - expect(toolNames).toContain(TOOL_QUERY_KNOWLEDGE_BASE_FULL_NAME); + expect(toolNames).not.toContain(TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME); expect(toolNames).toContain(TOOL_ARTIFACT_WRITE_FULL_NAME); }); - test("assignArchestraToolsToAgent always assigns query_knowledge_base (filtered at query time)", async ({ + test("assignArchestraToolsToAgent always assigns query_knowledge_sources (filtered at query time)", async ({ makeAgent, seedAndAssignArchestraTools, }) => { @@ -1523,11 +1523,11 @@ describe("ToolModel", () => { ); // Tool is assigned (in junction table) but filtered out by getMcpToolsByAgent - // since the agent has no KG assigned + // since the agent has no knowledge base assigned const tools = await ToolModel.getMcpToolsByAgent(agent.id); const toolNames = tools.map((t) => t.name); - expect(toolNames).not.toContain(TOOL_QUERY_KNOWLEDGE_BASE_FULL_NAME); + expect(toolNames).not.toContain(TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME); expect(toolNames).toContain(TOOL_ARTIFACT_WRITE_FULL_NAME); }); }); diff --git a/platform/backend/src/models/tool.ts b/platform/backend/src/models/tool.ts index 08d5f8b92c..714bd04ee7 100644 --- a/platform/backend/src/models/tool.ts +++ b/platform/backend/src/models/tool.ts @@ -5,7 +5,7 @@ import { MCP_SERVER_TOOL_NAME_SEPARATOR, parseFullToolName, slugify, - TOOL_QUERY_KNOWLEDGE_BASE_FULL_NAME, + TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME, } from "@shared"; import { and, @@ -375,10 +375,7 @@ class ToolModel { * All tools are linked via the agent_tools junction table. */ static async getToolsByAgent(agentId: string): Promise { - const [assignedToolIds, hasKnowledgeBase] = await Promise.all([ - AgentToolModel.findToolIdsByAgent(agentId), - ToolModel.getAgentHasKnowledgeBase(agentId), - ]); + const assignedToolIds = await AgentToolModel.findToolIdsByAgent(agentId); if (assignedToolIds.length === 0) { return []; @@ -387,10 +384,16 @@ class ToolModel { const tools = await db .select() .from(schema.toolsTable) - .where(inArray(schema.toolsTable.id, assignedToolIds)) + .where( + and( + inArray(schema.toolsTable.id, assignedToolIds), + // Always hide query_knowledge_sources from UI — it's auto-injected behind the scenes + ne(schema.toolsTable.name, TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME), + ), + ) .orderBy(desc(schema.toolsTable.createdAt)); - return ToolModel.filterUnavailableTools(tools, hasKnowledgeBase); + return tools; } /** @@ -433,15 +436,15 @@ class ToolModel { .orderBy(desc(schema.toolsTable.createdAt)) : []; - // Auto-inject query_knowledge_base when the agent has a knowledge base assigned, + // Auto-inject query_knowledge_sources when the agent has a knowledge base assigned, // regardless of whether the tool was manually assigned if (hasKnowledgeBase) { const hasKbTool = tools.some( - (t) => t.name === TOOL_QUERY_KNOWLEDGE_BASE_FULL_NAME, + (t) => t.name === TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME, ); if (!hasKbTool) { const kbTool = await ToolModel.findByName( - TOOL_QUERY_KNOWLEDGE_BASE_FULL_NAME, + TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME, ); if (kbTool) { tools.push(kbTool as (typeof tools)[number]); @@ -668,9 +671,9 @@ class ToolModel { * Default tools are those listed in {@link DEFAULT_ARCHESTRA_TOOL_NAMES}: * - artifact_write: for artifact management * - todo_write: for task tracking - * - query_knowledge_base: for querying the knowledge base + * - query_knowledge_sources: for querying the knowledge base * - * All default tools are always assigned. The query_knowledge_base tool + * All default tools are always assigned. The query_knowledge_sources tool * is filtered out at query time if the agent has no knowledge base assigned. * * Only tools that have already been seeded (via {@link seedArchestraTools}) @@ -807,7 +810,12 @@ class ToolModel { createdAt: schema.toolsTable.createdAt, }) .from(schema.toolsTable) - .where(eq(schema.toolsTable.catalogId, catalogId)) + .where( + and( + eq(schema.toolsTable.catalogId, catalogId), + ne(schema.toolsTable.name, TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME), + ), + ) .orderBy(desc(schema.toolsTable.createdAt)); const toolIds = allTools.map((tool) => tool.id); @@ -1552,9 +1560,9 @@ class ToolModel { } // Hide knowledge base tool in global tool listings (no agent context). - // The tool is only visible when queried per-agent and the agent has a KG assigned. + // The tool is only visible when queried per-agent and the agent has a knowledge base assigned. toolWhereConditions.push( - ne(schema.toolsTable.name, TOOL_QUERY_KNOWLEDGE_BASE_FULL_NAME), + ne(schema.toolsTable.name, TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME), ); // Apply access control filtering for users that are not agent admins @@ -1821,7 +1829,7 @@ class ToolModel { /** * Filter out tools that should not be visible based on current configuration. - * Filters out the query_knowledge_base tool when the agent has no knowledge base assigned. + * Filters out the query_knowledge_sources tool when the agent has no knowledge base assigned. */ private static filterUnavailableTools( tools: T[], @@ -1830,7 +1838,9 @@ class ToolModel { if (hasKnowledgeBase) { return tools; } - return tools.filter((t) => t.name !== TOOL_QUERY_KNOWLEDGE_BASE_FULL_NAME); + return tools.filter( + (t) => t.name !== TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME, + ); } } diff --git a/platform/backend/src/routes/knowledge-base.ts b/platform/backend/src/routes/knowledge-base.ts index ddb09a5547..803cad2b36 100644 --- a/platform/backend/src/routes/knowledge-base.ts +++ b/platform/backend/src/routes/knowledge-base.ts @@ -449,7 +449,17 @@ const knowledgeBaseRoutes: FastifyPluginAsyncZod = async (fastify) => { } } - return reply.send(connector); + // Auto-trigger initial sync + await taskQueueService.enqueue({ + taskType: "connector_sync", + payload: { connectorId: connector.id }, + }); + const updatedConnector = await KnowledgeBaseConnectorModel.update( + connector.id, + { lastSyncStatus: "running" }, + ); + + return reply.send(updatedConnector ?? connector); }, ); @@ -505,7 +515,12 @@ const knowledgeBaseRoutes: FastifyPluginAsyncZod = async (fastify) => { } const { credentials: _, ...updateData } = body; - const updated = await KnowledgeBaseConnectorModel.update(id, updateData); + // Reset checkpoint when config changes to force a full re-sync + // (filters, queries, inclusion/exclusion criteria affect which items get synced) + const updated = await KnowledgeBaseConnectorModel.update(id, { + ...updateData, + ...(updateData.config ? { checkpoint: null } : {}), + }); if (!updated) { throw new ApiError(404, "Connector not found"); } diff --git a/platform/backend/src/routes/mcp-gateway.utils.test.ts b/platform/backend/src/routes/mcp-gateway.utils.test.ts index 64464dd475..35b2d89752 100644 --- a/platform/backend/src/routes/mcp-gateway.utils.test.ts +++ b/platform/backend/src/routes/mcp-gateway.utils.test.ts @@ -28,6 +28,7 @@ const { validateMCPGatewayToken, validateOAuthToken, validateExternalIdpToken, + buildKnowledgeSourcesDescription, } = await import("./mcp-gateway.utils"); describe("validateMCPGatewayToken", () => { @@ -767,3 +768,159 @@ describe("validateExternalIdpToken", () => { expect(result?.teamId).toBeNull(); }); }); + +describe("buildKnowledgeSourcesDescription", () => { + test("returns null when agent has no knowledge bases", async ({ + makeAgent, + }) => { + const agent = await makeAgent(); + const result = await buildKnowledgeSourcesDescription(agent.id); + expect(result).toBeNull(); + }); + + test("returns null for non-existent agent id", async () => { + const result = await buildKnowledgeSourcesDescription(crypto.randomUUID()); + expect(result).toBeNull(); + }); + + test("includes knowledge base name in description", async ({ + makeAgent, + makeOrganization, + makeKnowledgeBase, + }) => { + const { AgentKnowledgeBaseModel } = await import("@/models"); + const org = await makeOrganization(); + const agent = await makeAgent({ organizationId: org.id }); + const kb = await makeKnowledgeBase(org.id, { name: "Engineering Docs" }); + await AgentKnowledgeBaseModel.assign(agent.id, kb.id); + + const result = await buildKnowledgeSourcesDescription(agent.id); + + expect(result).not.toBeNull(); + expect(result).toContain("Engineering Docs"); + expect(result).toContain("Available knowledge bases:"); + }); + + test("includes connector types in description", async ({ + makeAgent, + makeOrganization, + makeKnowledgeBase, + makeKnowledgeBaseConnector, + }) => { + const { AgentKnowledgeBaseModel } = await import("@/models"); + const org = await makeOrganization(); + const agent = await makeAgent({ organizationId: org.id }); + const kb = await makeKnowledgeBase(org.id); + await AgentKnowledgeBaseModel.assign(agent.id, kb.id); + await makeKnowledgeBaseConnector(kb.id, org.id, { connectorType: "jira" }); + + const result = await buildKnowledgeSourcesDescription(agent.id); + + expect(result).not.toBeNull(); + expect(result).toContain("jira"); + expect(result).toContain("Connected sources:"); + }); + + test("includes multiple knowledge base names", async ({ + makeAgent, + makeOrganization, + makeKnowledgeBase, + }) => { + const { AgentKnowledgeBaseModel } = await import("@/models"); + const org = await makeOrganization(); + const agent = await makeAgent({ organizationId: org.id }); + const kb1 = await makeKnowledgeBase(org.id, { name: "Product KB" }); + const kb2 = await makeKnowledgeBase(org.id, { name: "Support KB" }); + await AgentKnowledgeBaseModel.assign(agent.id, kb1.id); + await AgentKnowledgeBaseModel.assign(agent.id, kb2.id); + + const result = await buildKnowledgeSourcesDescription(agent.id); + + expect(result).not.toBeNull(); + expect(result).toContain("Product KB"); + expect(result).toContain("Support KB"); + }); + + test("deduplicates connector types", async ({ + makeAgent, + makeOrganization, + makeKnowledgeBase, + makeKnowledgeBaseConnector, + }) => { + const { AgentKnowledgeBaseModel } = await import("@/models"); + const org = await makeOrganization(); + const agent = await makeAgent({ organizationId: org.id }); + const kb = await makeKnowledgeBase(org.id); + await AgentKnowledgeBaseModel.assign(agent.id, kb.id); + await makeKnowledgeBaseConnector(kb.id, org.id, { connectorType: "jira" }); + await makeKnowledgeBaseConnector(kb.id, org.id, { connectorType: "jira" }); + + const result = await buildKnowledgeSourcesDescription(agent.id); + + expect(result).not.toBeNull(); + // "jira" should appear once in "Connected sources: jira." + const match = result?.match(/Connected sources: (.+?)\./); + expect(match).not.toBeNull(); + expect(match?.[1]).toBe("jira"); + }); + + test("includes multiple distinct connector types", async ({ + makeAgent, + makeOrganization, + makeKnowledgeBase, + makeKnowledgeBaseConnector, + }) => { + const { AgentKnowledgeBaseModel } = await import("@/models"); + const org = await makeOrganization(); + const agent = await makeAgent({ organizationId: org.id }); + const kb = await makeKnowledgeBase(org.id); + await AgentKnowledgeBaseModel.assign(agent.id, kb.id); + await makeKnowledgeBaseConnector(kb.id, org.id, { connectorType: "jira" }); + await makeKnowledgeBaseConnector(kb.id, org.id, { + connectorType: "confluence", + }); + + const result = await buildKnowledgeSourcesDescription(agent.id); + + expect(result).not.toBeNull(); + expect(result).toContain("jira"); + expect(result).toContain("confluence"); + }); + + test("includes base instruction text", async ({ + makeAgent, + makeOrganization, + makeKnowledgeBase, + }) => { + const { AgentKnowledgeBaseModel } = await import("@/models"); + const org = await makeOrganization(); + const agent = await makeAgent({ organizationId: org.id }); + const kb = await makeKnowledgeBase(org.id); + await AgentKnowledgeBaseModel.assign(agent.id, kb.id); + + const result = await buildKnowledgeSourcesDescription(agent.id); + + expect(result).not.toBeNull(); + expect(result).toContain( + "Query the organization's knowledge sources to retrieve relevant information", + ); + expect(result).toContain("Formulate queries about the actual content"); + }); + + test("omits 'Connected sources' when no connectors exist", async ({ + makeAgent, + makeOrganization, + makeKnowledgeBase, + }) => { + const { AgentKnowledgeBaseModel } = await import("@/models"); + const org = await makeOrganization(); + const agent = await makeAgent({ organizationId: org.id }); + const kb = await makeKnowledgeBase(org.id); + await AgentKnowledgeBaseModel.assign(agent.id, kb.id); + + const result = await buildKnowledgeSourcesDescription(agent.id); + + expect(result).not.toBeNull(); + expect(result).not.toContain("Connected sources:"); + }); +}); diff --git a/platform/backend/src/routes/mcp-gateway.utils.ts b/platform/backend/src/routes/mcp-gateway.utils.ts index 9f37430e97..1d86f89fd3 100644 --- a/platform/backend/src/routes/mcp-gateway.utils.ts +++ b/platform/backend/src/routes/mcp-gateway.utils.ts @@ -13,6 +13,7 @@ import { MCP_SERVER_TOOL_NAME_SEPARATOR, OAUTH_TOKEN_ID_PREFIX, parseFullToolName, + TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME, } from "@shared"; import { eq } from "drizzle-orm"; import type { FastifyRequest } from "fastify"; @@ -26,8 +27,11 @@ import config from "@/config"; import db, { schema as dbSchema } from "@/database"; import logger from "@/logging"; import { + AgentKnowledgeBaseModel, AgentModel, AgentTeamModel, + KnowledgeBaseConnectorModel, + KnowledgeBaseModel, McpToolCallModel, MemberModel, OAuthAccessTokenModel, @@ -113,10 +117,17 @@ export async function createAgentServer( // Fetch fresh on every request to ensure we get newly assigned tools const mcpTools = await ToolModel.getMcpToolsByAgent(agentId); + // Dynamically enrich the knowledge sources tool description with + // the agent's actual knowledge base names and connector types + const kbToolDescription = await buildKnowledgeSourcesDescription(agentId); + const toolsList = mcpTools.map(({ name, description, parameters }) => ({ name, title: archestraToolTitles.get(name) || name, - description, + description: + name === TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME && kbToolDescription + ? kbToolDescription + : description, inputSchema: parameters, annotations: {}, _meta: {}, @@ -136,7 +147,7 @@ export async function createAgentServer( }); logger.info( { agentId, toolsCount: toolsList.length }, - "✅ Saved tools/list request", + "Saved tools/list request", ); } catch (dbError) { logger.warn({ err: dbError }, "Failed to persist tools/list request:"); @@ -966,3 +977,69 @@ async function fetchOidcJwksUrl(issuerUrl: string): Promise { return null; } } + +/** + * TTL cache for buildKnowledgeSourcesDescription to avoid repeated DB queries + * on every tools/list request. Invalidated after 30 seconds. + */ +const kbDescriptionCache = new Map< + string, + { description: string | null; expiresAt: number } +>(); +const KB_DESCRIPTION_CACHE_TTL_MS = 30_000; + +/** + * Build a dynamic description for the query_knowledge_sources tool that includes + * the agent's actual knowledge base names and connector sources. + * Results are cached per agentId with a 30s TTL. + */ +export async function buildKnowledgeSourcesDescription( + agentId: string, +): Promise { + const cached = kbDescriptionCache.get(agentId); + if (cached && cached.expiresAt > Date.now()) { + return cached.description; + } + + const assignments = await AgentKnowledgeBaseModel.findByAgent(agentId); + if (assignments.length === 0) { + kbDescriptionCache.set(agentId, { + description: null, + expiresAt: Date.now() + KB_DESCRIPTION_CACHE_TTL_MS, + }); + return null; + } + + const kbIds = assignments.map((a) => a.knowledgeBaseId); + + const [knowledgeBases, connectors] = await Promise.all([ + KnowledgeBaseModel.findByIds(kbIds), + KnowledgeBaseConnectorModel.findByKnowledgeBaseIds(kbIds), + ]); + + const kbNames = knowledgeBases.map((kb) => kb.name); + const connectorTypes = [...new Set(connectors.map((c) => c.connectorType))]; + + let description = + "Query the organization's knowledge sources to retrieve relevant information. " + + "Use this tool when the user asks a question you cannot answer from your training data alone, " + + "or when they explicitly ask you to search internal documents and data sources."; + + if (kbNames.length > 0) { + description += ` Available knowledge bases: ${kbNames.join(", ")}.`; + } + if (connectorTypes.length > 0) { + description += ` Connected sources: ${connectorTypes.join(", ")}.`; + } + + description += + " Formulate queries about the actual content you are looking for — " + + "ask about topics, concepts, or information rather than about source systems."; + + kbDescriptionCache.set(agentId, { + description, + expiresAt: Date.now() + KB_DESCRIPTION_CACHE_TTL_MS, + }); + + return description; +} diff --git a/platform/backend/src/task-queue/handlers/check-due-connectors-handler.ts b/platform/backend/src/task-queue/handlers/check-due-connectors-handler.ts index 4ff6ffaf15..3c463c0884 100644 --- a/platform/backend/src/task-queue/handlers/check-due-connectors-handler.ts +++ b/platform/backend/src/task-queue/handlers/check-due-connectors-handler.ts @@ -28,8 +28,12 @@ export async function handleCheckDueConnectors(): Promise { payload: { connectorId: connector.id }, }); logger.info( - { connectorId: connector.id }, - "[TaskQueue] Enqueued scheduled connector sync", + { + connectorId: connector.id, + connectorName: connector.name, + connectorType: connector.connectorType, + }, + "Enqueued scheduled connector sync", ); } } @@ -37,10 +41,12 @@ export async function handleCheckDueConnectors(): Promise { logger.warn( { connectorId: connector.id, + connectorName: connector.name, + connectorType: connector.connectorType, schedule: connector.schedule, error: error instanceof Error ? error.message : String(error), }, - "[TaskQueue] Failed to evaluate connector schedule", + "Failed to evaluate connector schedule", ); } } @@ -68,16 +74,22 @@ async function cleanupOrphanedRunningStatuses(): Promise { lastSyncError: "Sync task was lost", }); logger.warn( - { connectorId: connector.id }, - "[TaskQueue] Reset orphaned running status to failed", + { + connectorId: connector.id, + connectorName: connector.name, + connectorType: connector.connectorType, + }, + "Reset orphaned running status to failed", ); } catch (error) { logger.warn( { connectorId: connector.id, + connectorName: connector.name, + connectorType: connector.connectorType, error: error instanceof Error ? error.message : String(error), }, - "[TaskQueue] Failed to cleanup orphaned running status", + "Failed to cleanup orphaned running status", ); } } diff --git a/platform/backend/src/task-queue/handlers/connector-sync-handler.test.ts b/platform/backend/src/task-queue/handlers/connector-sync-handler.test.ts index 16d7b95eed..2de6180873 100644 --- a/platform/backend/src/task-queue/handlers/connector-sync-handler.test.ts +++ b/platform/backend/src/task-queue/handlers/connector-sync-handler.test.ts @@ -1,4 +1,6 @@ -import { beforeEach, describe, expect, test, vi } from "vitest"; +import { randomUUID } from "node:crypto"; +import { vi } from "vitest"; +import { beforeEach, describe, expect, test } from "@/test"; const mockExecuteSync = vi.hoisted(() => vi.fn()); vi.mock("@/knowledge-base", () => ({ @@ -24,37 +26,23 @@ vi.mock("@/entrypoints/_shared/log-capture", () => ({ }), })); -vi.mock("@/logging", () => ({ - default: { - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - }, -})); - -vi.mock("@/config", () => ({ - default: { - kb: { - connectorSyncMaxDurationSeconds: undefined, - }, - }, -})); - import { handleConnectorSync } from "./connector-sync-handler"; describe("handleConnectorSync", () => { + let connectorId: string; + beforeEach(() => { + connectorId = randomUUID(); vi.clearAllMocks(); }); test("calls executeSync with the connector ID", async () => { mockExecuteSync.mockResolvedValue({ status: "complete" }); - await handleConnectorSync({ connectorId: "conn-1" }); + await handleConnectorSync({ connectorId }); expect(mockExecuteSync).toHaveBeenCalledWith( - "conn-1", + connectorId, expect.objectContaining({ logger: expect.any(Object), getLogOutput: expect.any(Function), @@ -65,12 +53,12 @@ describe("handleConnectorSync", () => { test("enqueues continuation with incremented count on partial result", async () => { mockExecuteSync.mockResolvedValue({ status: "partial" }); - await handleConnectorSync({ connectorId: "conn-1", continuationCount: 3 }); + await handleConnectorSync({ connectorId, continuationCount: 3 }); expect(mockEnqueue).toHaveBeenCalledWith({ taskType: "connector_sync", payload: { - connectorId: "conn-1", + connectorId, continuationCount: 4, }, }); @@ -79,7 +67,7 @@ describe("handleConnectorSync", () => { test("does not enqueue when continuation count >= 50", async () => { mockExecuteSync.mockResolvedValue({ status: "partial" }); - await handleConnectorSync({ connectorId: "conn-1", continuationCount: 50 }); + await handleConnectorSync({ connectorId, continuationCount: 50 }); expect(mockEnqueue).not.toHaveBeenCalled(); }); diff --git a/platform/backend/src/task-queue/handlers/connector-sync-handler.ts b/platform/backend/src/task-queue/handlers/connector-sync-handler.ts index 3a7ece29b6..17087ddafe 100644 --- a/platform/backend/src/task-queue/handlers/connector-sync-handler.ts +++ b/platform/backend/src/task-queue/handlers/connector-sync-handler.ts @@ -2,6 +2,7 @@ import config from "@/config"; import { createCapturingLogger } from "@/entrypoints/_shared/log-capture"; import { connectorSyncService } from "@/knowledge-base"; import logger from "@/logging"; +import { KnowledgeBaseConnectorModel } from "@/models"; import { taskQueueService } from "@/task-queue"; const MAX_CONTINUATIONS = 50; @@ -16,6 +17,11 @@ export async function handleConnectorSync( throw new Error("Missing connectorId in connector_sync payload"); } + // Load connector metadata for structured logging + const connector = await KnowledgeBaseConnectorModel.findById(connectorId); + const connectorName = connector?.name; + const connectorType = connector?.connectorType; + const { logger: capturingLogger, getLogOutput } = createCapturingLogger(); const maxDurationMs = config.kb.connectorSyncMaxDurationSeconds @@ -41,19 +47,23 @@ export async function handleConnectorSync( logger.info( { connectorId, + connectorName, + connectorType, runId: result.runId, continuationCount: continuationCount + 1, }, - "[ConnectorSyncHandler] Enqueued continuation", + "Enqueued sync continuation", ); } else { logger.warn( { connectorId, + connectorName, + connectorType, runId: result.runId, maxContinuations: MAX_CONTINUATIONS, }, - "[ConnectorSyncHandler] Max continuations reached", + "Max sync continuations reached", ); } } diff --git a/platform/backend/src/test/fixtures.ts b/platform/backend/src/test/fixtures.ts index cc27817cb3..3f6e8e7e36 100644 --- a/platform/backend/src/test/fixtures.ts +++ b/platform/backend/src/test/fixtures.ts @@ -857,7 +857,7 @@ async function makeKnowledgeBase( .insert(schema.knowledgeBasesTable) .values({ organizationId, - name: `Test KG ${crypto.randomUUID().substring(0, 8)}`, + name: `Test Knowledge Base ${crypto.randomUUID().substring(0, 8)}`, ...overrides, }) .returning(); diff --git a/platform/e2e-tests/tests/api/agent-tools.spec.ts b/platform/e2e-tests/tests/api/agent-tools.spec.ts index a68647703a..31d53c606b 100644 --- a/platform/e2e-tests/tests/api/agent-tools.spec.ts +++ b/platform/e2e-tests/tests/api/agent-tools.spec.ts @@ -94,8 +94,10 @@ test.describe("Agent Tools API", () => { expect(result.pagination.hasNext).toBe(false); // All data should be returned expect(result.pagination.total).toBe(result.data.length); - // Verify we have the tools we assigned - expect(result.data.length).toBe(assignedTools.length); + // assignArchestraToolsToProfile assigns all archestra tools including + // query_knowledge_sources, but the API always excludes it (auto-injected). + expect(result.data.length).toBeGreaterThan(0); + expect(result.data.length).toBe(assignedTools.length - 1); }); test("skipPagination respects other filters like agentId", async ({ diff --git a/platform/e2e-tests/tests/api/fixtures.ts b/platform/e2e-tests/tests/api/fixtures.ts index 37f905038d..b64e1698d2 100644 --- a/platform/e2e-tests/tests/api/fixtures.ts +++ b/platform/e2e-tests/tests/api/fixtures.ts @@ -995,7 +995,7 @@ const createKnowledgeBase = async (request: APIRequestContext, name?: string) => method: "post", urlSuffix: "/api/knowledge-bases", data: { - name: name ?? `Test KG ${crypto.randomUUID().slice(0, 8)}`, + name: name ?? `Test Knowledge Base ${crypto.randomUUID().slice(0, 8)}`, }, }); diff --git a/platform/e2e-tests/tests/api/knowledge-bases.spec.ts b/platform/e2e-tests/tests/api/knowledge-bases.spec.ts index f4f8630e2b..fef7605cb1 100644 --- a/platform/e2e-tests/tests/api/knowledge-bases.spec.ts +++ b/platform/e2e-tests/tests/api/knowledge-bases.spec.ts @@ -8,7 +8,7 @@ test.describe("Knowledge Bases API", () => { deleteKnowledgeBase, }) => { const uniqueSuffix = crypto.randomUUID().slice(0, 8); - const name = `E2E KG Create ${uniqueSuffix}`; + const name = `E2E Knowledge Base Create ${uniqueSuffix}`; const response = await createKnowledgeBase(request, name); const kg = await response.json(); @@ -29,7 +29,7 @@ test.describe("Knowledge Bases API", () => { deleteKnowledgeBase, }) => { const uniqueSuffix = crypto.randomUUID().slice(0, 8); - const name = `E2E KG Get ${uniqueSuffix}`; + const name = `E2E Knowledge Base Get ${uniqueSuffix}`; const createResponse = await createKnowledgeBase(request, name); const created = await createResponse.json(); @@ -55,8 +55,8 @@ test.describe("Knowledge Bases API", () => { deleteKnowledgeBase, }) => { const uniqueSuffix = crypto.randomUUID().slice(0, 8); - const name1 = `E2E KG List A ${uniqueSuffix}`; - const name2 = `E2E KG List B ${uniqueSuffix}`; + const name1 = `E2E Knowledge Base List A ${uniqueSuffix}`; + const name2 = `E2E Knowledge Base List B ${uniqueSuffix}`; const res1 = await createKnowledgeBase(request, name1); const kg1 = await res1.json(); @@ -97,11 +97,11 @@ test.describe("Knowledge Bases API", () => { const uniqueSuffix = crypto.randomUUID().slice(0, 8); const createResponse = await createKnowledgeBase( request, - `E2E KG Update ${uniqueSuffix}`, + `E2E Knowledge Base Update ${uniqueSuffix}`, ); const created = await createResponse.json(); - const updatedName = `E2E KG Updated ${uniqueSuffix}`; + const updatedName = `E2E Knowledge Base Updated ${uniqueSuffix}`; const updateResponse = await makeApiRequest({ request, @@ -135,7 +135,7 @@ test.describe("Knowledge Bases API", () => { const uniqueSuffix = crypto.randomUUID().slice(0, 8); const createResponse = await createKnowledgeBase( request, - `E2E KG Delete ${uniqueSuffix}`, + `E2E Knowledge Base Delete ${uniqueSuffix}`, ); const created = await createResponse.json(); @@ -212,7 +212,7 @@ test.describe("Knowledge Bases API", () => { method: "post", urlSuffix: "/api/knowledge-bases", data: { - name: "Member KG Attempt", + name: "Member Knowledge Base Attempt", }, ignoreStatusCheck: true, }); @@ -229,7 +229,7 @@ test.describe("Knowledge Bases API", () => { // Create as admin const createResponse = await createKnowledgeBase( request, - `E2E KG RBAC Delete ${crypto.randomUUID().slice(0, 8)}`, + `E2E Knowledge Base RBAC Delete ${crypto.randomUUID().slice(0, 8)}`, ); const kg = await createResponse.json(); @@ -257,7 +257,7 @@ test.describe("Knowledge Bases API", () => { const uniqueSuffix = crypto.randomUUID().slice(0, 8); const kgRes = await createKnowledgeBase( request, - `E2E KG Connector Create ${uniqueSuffix}`, + `E2E Knowledge Base Connector Create ${uniqueSuffix}`, ); const kg = await kgRes.json(); @@ -288,7 +288,7 @@ test.describe("Knowledge Bases API", () => { const uniqueSuffix = crypto.randomUUID().slice(0, 8); const kgRes = await createKnowledgeBase( request, - `E2E KG Connector Get ${uniqueSuffix}`, + `E2E Knowledge Base Connector Get ${uniqueSuffix}`, ); const kg = await kgRes.json(); @@ -321,7 +321,7 @@ test.describe("Knowledge Bases API", () => { const uniqueSuffix = crypto.randomUUID().slice(0, 8); const kgRes = await createKnowledgeBase( request, - `E2E KG Connector List ${uniqueSuffix}`, + `E2E Knowledge Base Connector List ${uniqueSuffix}`, ); const kg = await kgRes.json(); @@ -367,7 +367,7 @@ test.describe("Knowledge Bases API", () => { const uniqueSuffix = crypto.randomUUID().slice(0, 8); const kgRes = await createKnowledgeBase( request, - `E2E KG Connector Update ${uniqueSuffix}`, + `E2E Knowledge Base Connector Update ${uniqueSuffix}`, ); const kg = await kgRes.json(); @@ -420,7 +420,7 @@ test.describe("Knowledge Bases API", () => { const uniqueSuffix = crypto.randomUUID().slice(0, 8); const kgRes = await createKnowledgeBase( request, - `E2E KG Connector Delete ${uniqueSuffix}`, + `E2E Knowledge Base Connector Delete ${uniqueSuffix}`, ); const kg = await kgRes.json(); @@ -461,7 +461,7 @@ test.describe("Knowledge Bases API", () => { const uniqueSuffix = crypto.randomUUID().slice(0, 8); const kgRes = await createKnowledgeBase( request, - `E2E KG Connector Invalid ${uniqueSuffix}`, + `E2E Knowledge Base Connector Invalid ${uniqueSuffix}`, ); const kg = await kgRes.json(); @@ -484,7 +484,7 @@ test.describe("Knowledge Bases API", () => { await deleteKnowledgeBase(request, kg.id); }); - test.skip("connectors are cascade-deleted when KG is deleted", async ({ + test.skip("connectors are cascade-deleted when knowledge base is deleted", async ({ request, makeApiRequest, createKnowledgeBase, @@ -493,7 +493,7 @@ test.describe("Knowledge Bases API", () => { const uniqueSuffix = crypto.randomUUID().slice(0, 8); const kgRes = await createKnowledgeBase( request, - `E2E KG Cascade ${uniqueSuffix}`, + `E2E Knowledge Base Cascade ${uniqueSuffix}`, ); const kg = await kgRes.json(); @@ -504,7 +504,7 @@ test.describe("Knowledge Bases API", () => { ); const connector = await connRes.json(); - // Delete the KG + // Delete the knowledge base const deleteResponse = await makeApiRequest({ request, method: "delete", @@ -513,7 +513,7 @@ test.describe("Knowledge Bases API", () => { const result = await deleteResponse.json(); expect(result.success).toBe(true); - // Verify connector is gone (KG cascade-deleted the connector) + // Verify connector is gone (knowledge base cascade-deleted the connector) const getResponse = await makeApiRequest({ request, method: "get", @@ -535,7 +535,7 @@ test.describe("Knowledge Bases API", () => { const uniqueSuffix = crypto.randomUUID().slice(0, 8); const kgRes = await createKnowledgeBase( request, - `E2E KG Runs ${uniqueSuffix}`, + `E2E Knowledge Base Runs ${uniqueSuffix}`, ); const kg = await kgRes.json(); diff --git a/platform/e2e-tests/tests/api/mcp-gateway.spec.ts b/platform/e2e-tests/tests/api/mcp-gateway.spec.ts index 4696943919..a764d23d24 100644 --- a/platform/e2e-tests/tests/api/mcp-gateway.spec.ts +++ b/platform/e2e-tests/tests/api/mcp-gateway.spec.ts @@ -1,5 +1,8 @@ import crypto from "node:crypto"; -import { OAUTH_ENDPOINTS } from "@shared"; +import { + OAUTH_ENDPOINTS, + TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME, +} from "@shared"; import { API_BASE_URL, MCP_GATEWAY_URL_SUFFIX, @@ -1765,3 +1768,114 @@ test.describe("MCP Gateway - CIMD (Client ID Metadata Documents)", () => { expect(body.client_id_metadata_document_supported).toBe(true); }); }); + +test.describe("MCP Gateway - Knowledge Sources Tool Description", () => { + let profileId: string; + let archestraToken: string; + let knowledgeBaseId: string; + + test.beforeAll(async ({ request, createKnowledgeBase, createConnector }) => { + const uniqueSuffix = crypto.randomUUID().slice(0, 8); + + // Create a knowledge base + const kbResponse = await createKnowledgeBase( + request, + `E2E KB Dynamic Desc ${uniqueSuffix}`, + ); + const kb = await kbResponse.json(); + knowledgeBaseId = kb.id; + + // Create a connector assigned to the knowledge base + await createConnector( + request, + knowledgeBaseId, + `E2E Jira Conn ${uniqueSuffix}`, + ); + + // Create an agent with the knowledge base assigned + const agentResponse = await makeApiRequest({ + request, + method: "post", + urlSuffix: "/api/agents", + data: { + name: `MCP Gateway KB Test ${uniqueSuffix}`, + teams: [], + scope: "personal", + knowledgeBaseIds: [knowledgeBaseId], + }, + }); + const agent = await agentResponse.json(); + profileId = agent.id; + + // Assign Archestra tools to the profile + await assignArchestraToolsToProfile(request, profileId); + + // Get org token + archestraToken = await getOrgTokenForProfile(request); + }); + + test.afterAll(async ({ request, deleteAgent, deleteKnowledgeBase }) => { + await deleteAgent(request, profileId); + await deleteKnowledgeBase(request, knowledgeBaseId); + }); + + test("query_knowledge_sources tool has dynamic description with KB name and connector type", async ({ + request, + }) => { + // Initialize MCP session + await makeApiRequest({ + request, + method: "post", + urlSuffix: `${MCP_GATEWAY_URL_SUFFIX}/${profileId}`, + headers: { + Authorization: `Bearer ${archestraToken}`, + "Content-Type": "application/json", + Accept: "application/json, text/event-stream", + }, + data: { + jsonrpc: "2.0", + id: 1, + method: "initialize", + params: { + protocolVersion: "2024-11-05", + capabilities: { tools: {} }, + clientInfo: { name: "test-client", version: "1.0.0" }, + }, + }, + }); + + // List tools + const listToolsResponse = await makeApiRequest({ + request, + method: "post", + urlSuffix: `${MCP_GATEWAY_URL_SUFFIX}/${profileId}`, + headers: { + Authorization: `Bearer ${archestraToken}`, + "Content-Type": "application/json", + Accept: "application/json, text/event-stream", + }, + data: { + jsonrpc: "2.0", + id: 2, + method: "tools/list", + params: {}, + }, + }); + + expect(listToolsResponse.status()).toBe(200); + const listResult = await listToolsResponse.json(); + const tools = listResult.result.tools; + + // Find the knowledge sources tool + const kbTool = tools.find( + // biome-ignore lint/suspicious/noExplicitAny: e2e test + (t: any) => t.name === TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME, + ); + + expect(kbTool).toBeDefined(); + + // Verify dynamic description includes the KB name and connector type + expect(kbTool.description).toContain("E2E KB Dynamic Desc"); + expect(kbTool.description).toContain("jira"); + }); +}); diff --git a/platform/frontend/package.json b/platform/frontend/package.json index 6da749876b..26be60f67e 100644 --- a/platform/frontend/package.json +++ b/platform/frontend/package.json @@ -89,7 +89,7 @@ "require-in-the-middle": "8.0.1", "simple-icons": "^16.10.0", "sonner": "^2.0.7", - "streamdown": "^2.0.1", + "streamdown": "^2.4.0", "tailwind-merge": "^3.4.0", "use-stick-to-bottom": "^1.1.1", "zod": "^4.3.4" diff --git a/platform/frontend/src/app/chat/page.tsx b/platform/frontend/src/app/chat/page.tsx index c6ea06dd2b..9d326128a7 100644 --- a/platform/frontend/src/app/chat/page.tsx +++ b/platform/frontend/src/app/chat/page.tsx @@ -114,16 +114,20 @@ import { getPendingActions, } from "@/lib/pending-tool-state"; import { useTeams } from "@/lib/team.query"; +import { + clearSavedModel, + getSavedAgent, + getSavedModel, + saveAgent, + saveModel, +} from "@/lib/use-chat-preferences"; import { useIsMobile } from "@/lib/use-mobile.hook"; import { cn } from "@/lib/utils"; import ArchestraPromptInput from "./prompt-input"; const CONVERSATION_QUERY_PARAM = "conversation"; -const LocalStorageKeys = { - browserOpen: "archestra-chat-browser-open", - selectedChatModel: "archestra-chat-selected-chat-model", -} as const; +const BROWSER_OPEN_KEY = "archestra-chat-browser-open"; export default function ChatPage() { const queryClient = useQueryClient(); @@ -195,7 +199,7 @@ export default function ChatPage() { // State for browser panel - initialize from localStorage const [isBrowserPanelOpen, setIsBrowserPanelOpen] = useState(() => { if (typeof window !== "undefined") { - return localStorage.getItem(LocalStorageKeys.browserOpen) === "true"; + return localStorage.getItem(BROWSER_OPEN_KEY) === "true"; } return false; }); @@ -246,7 +250,7 @@ export default function ChatPage() { // Try to restore from localStorage, then member's default agent, then first internal agent if (!initialAgentId) { - const savedAgentId = localStorage.getItem("selected-chat-agent"); + const savedAgentId = getSavedAgent(); const savedAgent = internalAgents.find((a) => a.id === savedAgentId); if (savedAgent) { setInitialAgentId(savedAgentId); @@ -290,9 +294,7 @@ export default function ChatPage() { }; // 1. User's explicit selection from localStorage takes priority - const savedModelId = localStorage.getItem( - LocalStorageKeys.selectedChatModel, - ); + const savedModelId = getSavedModel(); if (savedModelId) { // Wait for models to load so we can validate the saved model still exists if (allModels.length === 0) return; @@ -309,7 +311,7 @@ export default function ChatPage() { return; } // Saved model no longer available — clear stale value and fall through - localStorage.removeItem(LocalStorageKeys.selectedChatModel); + clearSavedModel(); } // 2. Agent-configured model as fallback @@ -347,7 +349,7 @@ export default function ChatPage() { // Save model to localStorage when changed const handleInitialModelChange = useCallback((modelId: string) => { setInitialModel(modelId); - localStorage.setItem(LocalStorageKeys.selectedChatModel, modelId); + saveModel(modelId); }, []); // Handle provider change from API key selector - auto-select a model from new provider @@ -358,7 +360,7 @@ export default function ChatPage() { // Fall back to first model for this provider const firstModel = providerModels[0]; setInitialModel(firstModel.id); - localStorage.setItem(LocalStorageKeys.selectedChatModel, firstModel.id); + saveModel(firstModel.id); } }, [modelsByProvider], @@ -515,7 +517,7 @@ export default function ChatPage() { }); // Persist to localStorage so it's restored on next visit - localStorage.setItem(LocalStorageKeys.selectedChatModel, model); + saveModel(model); }, [conversation, chatModels, updateConversationMutation], ); @@ -536,7 +538,7 @@ export default function ChatPage() { }); // Persist to localStorage so it's restored on next visit - localStorage.setItem(LocalStorageKeys.selectedChatModel, firstModel.id); + saveModel(firstModel.id); } }, [conversation, modelsByProvider, updateConversationMutation], @@ -918,13 +920,13 @@ export default function ChatPage() { const toggleBrowserPanel = useCallback(() => { const newValue = !isBrowserPanelOpen; setIsBrowserPanelOpen(newValue); - localStorage.setItem(LocalStorageKeys.browserOpen, String(newValue)); + localStorage.setItem(BROWSER_OPEN_KEY, String(newValue)); }, [isBrowserPanelOpen]); // Close browser panel handler (also persists to localStorage) const closeBrowserPanel = useCallback(() => { setIsBrowserPanelOpen(false); - localStorage.setItem(LocalStorageKeys.browserOpen, "false"); + localStorage.setItem(BROWSER_OPEN_KEY, "false"); }, []); // Handle creating conversation from browser URL input (when no conversation exists) @@ -978,7 +980,7 @@ export default function ChatPage() { const handleInitialAgentChange = useCallback( (agentId: string) => { setInitialAgentId(agentId); - localStorage.setItem("selected-chat-agent", agentId); + saveAgent(agentId); // Apply agent's LLM config if present const selectedAgent = internalAgents.find((a) => a.id === agentId); diff --git a/platform/frontend/src/app/knowledge/connectors/[id]/page.client.tsx b/platform/frontend/src/app/knowledge/connectors/[id]/page.client.tsx index cd591892e5..da2deff14d 100644 --- a/platform/frontend/src/app/knowledge/connectors/[id]/page.client.tsx +++ b/platform/frontend/src/app/knowledge/connectors/[id]/page.client.tsx @@ -34,6 +34,11 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { useAssignConnectorToKnowledgeBases, useConnector, @@ -215,21 +220,31 @@ function ConnectorDetail({ connectorId }: { connectorId: string }) { description="" actionButton={
- + + + + + + + {connector.lastSyncStatus === "running" && ( + Sync run in progress + )} + @@ -415,7 +412,7 @@ export function CreateConnectorDialog({ ? "Creating..." : "Create Connector"} - + )} diff --git a/platform/frontend/src/app/knowledge/knowledge-bases/_parts/edit-connector-dialog.tsx b/platform/frontend/src/app/knowledge/knowledge-bases/_parts/edit-connector-dialog.tsx index dbbee92e38..b20c01e821 100644 --- a/platform/frontend/src/app/knowledge/knowledge-bases/_parts/edit-connector-dialog.tsx +++ b/platform/frontend/src/app/knowledge/knowledge-bases/_parts/edit-connector-dialog.tsx @@ -14,8 +14,8 @@ import { Dialog, DialogContent, DialogDescription, - DialogFooter, DialogHeader, + DialogStickyFooter, DialogTitle, } from "@/components/ui/dialog"; import { @@ -118,7 +118,7 @@ export function EditConnectorDialog({ return ( - +
@@ -132,11 +132,8 @@ export function EditConnectorDialog({
- -
+ +
- + - + diff --git a/platform/frontend/src/app/knowledge/knowledge-bases/_parts/gitlab-config-fields.tsx b/platform/frontend/src/app/knowledge/knowledge-bases/_parts/gitlab-config-fields.tsx index 28441d5ce8..0e4ce5269f 100644 --- a/platform/frontend/src/app/knowledge/knowledge-bases/_parts/gitlab-config-fields.tsx +++ b/platform/frontend/src/app/knowledge/knowledge-bases/_parts/gitlab-config-fields.tsx @@ -51,13 +51,14 @@ export function GitlabConfigFields({ name={`${prefix}.groupId`} render={({ field }) => ( - Group (optional) + Group Path (optional) - + - GitLab group ID or path. Leave blank to sync all accessible - projects. + The path of the GitLab group to sync from. Found in the group URL + (e.g. gitlab.com/groups/my-org/my-subgroup). Leave blank to sync + all accessible projects. diff --git a/platform/frontend/src/app/llm/providers/provider-settings-api-keys.tsx b/platform/frontend/src/app/llm/providers/provider-settings-api-keys.tsx index 961e26c9b4..a971834546 100644 --- a/platform/frontend/src/app/llm/providers/provider-settings-api-keys.tsx +++ b/platform/frontend/src/app/llm/providers/provider-settings-api-keys.tsx @@ -44,6 +44,7 @@ import { DialogFooter, DialogForm, DialogHeader, + DialogStickyFooter, DialogTitle, } from "@/components/ui/dialog"; import { InlineTag } from "@/components/ui/inline-tag"; @@ -488,7 +489,7 @@ export function ProviderSettingsApiKeys() { geminiVertexAiEnabled={geminiVertexAiEnabled} />
- +
@@ -532,7 +533,7 @@ export function ProviderSettingsApiKeys() { /> )}
- + - + ); const catalogButton = ( diff --git a/platform/frontend/src/app/mcp/registry/_parts/custom-server-request-dialog.tsx b/platform/frontend/src/app/mcp/registry/_parts/custom-server-request-dialog.tsx index 98b277efff..4fdd5a5cfa 100644 --- a/platform/frontend/src/app/mcp/registry/_parts/custom-server-request-dialog.tsx +++ b/platform/frontend/src/app/mcp/registry/_parts/custom-server-request-dialog.tsx @@ -12,9 +12,9 @@ import { Dialog, DialogContent, DialogDescription, - DialogFooter, DialogForm, DialogHeader, + DialogStickyFooter, DialogTitle, } from "@/components/ui/dialog"; import { @@ -321,7 +321,7 @@ export function CustomServerRequestDialog({ /> - + @@ -331,7 +331,7 @@ export function CustomServerRequestDialog({ )} Submit Request - + diff --git a/platform/frontend/src/app/mcp/registry/_parts/edit-catalog-dialog.tsx b/platform/frontend/src/app/mcp/registry/_parts/edit-catalog-dialog.tsx index 2115420b18..f3988b8af7 100644 --- a/platform/frontend/src/app/mcp/registry/_parts/edit-catalog-dialog.tsx +++ b/platform/frontend/src/app/mcp/registry/_parts/edit-catalog-dialog.tsx @@ -1,6 +1,11 @@ import { type archestraApiTypes, isPlaywrightCatalogItem } from "@shared"; import { Button } from "@/components/ui/button"; -import { Dialog, DialogContent, DialogFooter } from "@/components/ui/dialog"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogStickyFooter, +} from "@/components/ui/dialog"; import { useUpdateInternalMcpCatalogItem } from "@/lib/internal-mcp-catalog.query"; import { McpCatalogForm } from "./mcp-catalog-form"; import type { McpCatalogFormValues } from "./mcp-catalog-form.types"; @@ -62,15 +67,11 @@ export function EditCatalogContent({ nameDisabled={isPlaywrightCatalogItem(item.id)} onDirtyChange={onDirtyChange} submitRef={submitRef} - footer={({ isDirty, onReset }) => - keepOpenOnSave && !isDirty ? null : ( - + footer={({ isDirty, onReset }) => { + if (keepOpenOnSave && !isDirty) return null; + const Footer = keepOpenOnSave ? DialogStickyFooter : DialogFooter; + return ( +
{keepOpenOnSave ? ( - - ) - } +
+ ); + }} /> ); } diff --git a/platform/frontend/src/app/mcp/registry/_parts/local-server-install-dialog.tsx b/platform/frontend/src/app/mcp/registry/_parts/local-server-install-dialog.tsx index 61a8b6a893..12c852a60b 100644 --- a/platform/frontend/src/app/mcp/registry/_parts/local-server-install-dialog.tsx +++ b/platform/frontend/src/app/mcp/registry/_parts/local-server-install-dialog.tsx @@ -21,9 +21,9 @@ import { Dialog, DialogContent, DialogDescription, - DialogFooter, DialogForm, DialogHeader, + DialogStickyFooter, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; @@ -642,7 +642,7 @@ export function LocalServerInstallDialog({ )} - + {canInstall && ( - + )} ); diff --git a/platform/frontend/src/app/mcp/registry/_parts/reinstall-confirmation-dialog.tsx b/platform/frontend/src/app/mcp/registry/_parts/reinstall-confirmation-dialog.tsx index c78378784b..a777ee3ea5 100644 --- a/platform/frontend/src/app/mcp/registry/_parts/reinstall-confirmation-dialog.tsx +++ b/platform/frontend/src/app/mcp/registry/_parts/reinstall-confirmation-dialog.tsx @@ -6,9 +6,9 @@ import { Dialog, DialogContent, DialogDescription, - DialogFooter, DialogForm, DialogHeader, + DialogStickyFooter, DialogTitle, } from "@/components/ui/dialog"; @@ -43,7 +43,7 @@ export function ReinstallConfirmationDialog({ - + - + diff --git a/platform/frontend/src/app/mcp/registry/_parts/yaml-config-dialog.tsx b/platform/frontend/src/app/mcp/registry/_parts/yaml-config-dialog.tsx index 2afa6dea30..df65055fcc 100644 --- a/platform/frontend/src/app/mcp/registry/_parts/yaml-config-dialog.tsx +++ b/platform/frontend/src/app/mcp/registry/_parts/yaml-config-dialog.tsx @@ -15,6 +15,7 @@ import { DialogFooter, DialogForm, DialogHeader, + DialogStickyFooter, DialogTitle, } from "@/components/ui/dialog"; import { @@ -212,25 +213,23 @@ export function YamlConfigContent({ /> ))} - {(!hideHeader || hasYamlChanged) && ( - - - - - )} + {(!hideHeader || hasYamlChanged) && + (() => { + const Footer = hideHeader ? DialogStickyFooter : DialogFooter; + return ( +
+ + +
+ ); + })()} ); diff --git a/platform/frontend/src/app/settings/identity-providers/_parts/create-identity-provider-dialog.ee.tsx b/platform/frontend/src/app/settings/identity-providers/_parts/create-identity-provider-dialog.ee.tsx index 5f36027eb3..0f7da7fe42 100644 --- a/platform/frontend/src/app/settings/identity-providers/_parts/create-identity-provider-dialog.ee.tsx +++ b/platform/frontend/src/app/settings/identity-providers/_parts/create-identity-provider-dialog.ee.tsx @@ -12,8 +12,8 @@ import { Dialog, DialogContent, DialogDescription, - DialogFooter, DialogHeader, + DialogStickyFooter, DialogTitle, } from "@/components/ui/dialog"; import { Form } from "@/components/ui/form"; @@ -100,7 +100,7 @@ export function CreateIdentityProviderDialog({ return ( - + {providerName @@ -115,23 +115,18 @@ export function CreateIdentityProviderDialog({
- -
- {currentProviderType === "saml" ? ( - - ) : ( - - )} -
+ + {currentProviderType === "saml" ? ( + + ) : ( + + )} - + @@ -144,7 +139,7 @@ export function CreateIdentityProviderDialog({ ? "Creating..." : "Create Provider"} - +
diff --git a/platform/frontend/src/app/settings/identity-providers/_parts/edit-identity-provider-dialog.ee.tsx b/platform/frontend/src/app/settings/identity-providers/_parts/edit-identity-provider-dialog.ee.tsx index e88cf0e76d..1f678b9d77 100644 --- a/platform/frontend/src/app/settings/identity-providers/_parts/edit-identity-provider-dialog.ee.tsx +++ b/platform/frontend/src/app/settings/identity-providers/_parts/edit-identity-provider-dialog.ee.tsx @@ -22,8 +22,8 @@ import { Dialog, DialogContent, DialogDescription, - DialogFooter, DialogHeader, + DialogStickyFooter, DialogTitle, } from "@/components/ui/dialog"; import { Form } from "@/components/ui/form"; @@ -160,7 +160,7 @@ export function EditIdentityProviderDialog({ return ( - + Edit Identity Provider @@ -169,19 +169,14 @@ export function EditIdentityProviderDialog({
- -
- {providerType === "saml" ? ( - - ) : ( - - )} -
+ + {providerType === "saml" ? ( + + ) : ( + + )} - +
-
+
diff --git a/platform/frontend/src/components/agent-dialog.tsx b/platform/frontend/src/components/agent-dialog.tsx index 02a00099b1..879a735b01 100644 --- a/platform/frontend/src/components/agent-dialog.tsx +++ b/platform/frontend/src/components/agent-dialog.tsx @@ -63,9 +63,9 @@ import { import { Dialog, DialogContent, - DialogFooter, DialogForm, DialogHeader, + DialogStickyFooter, DialogTitle, } from "@/components/ui/dialog"; import { ExpandableText } from "@/components/ui/expandable-text"; @@ -1079,7 +1079,7 @@ export function AgentDialog({ return ( e.preventDefault()} > @@ -1104,11 +1104,8 @@ export function AgentDialog({ )} - -
+ +
{agentType === "profile" && ( @@ -1837,7 +1834,7 @@ export function AgentDialog({ )}
- + @@ -1856,7 +1853,7 @@ export function AgentDialog({ )} {agent ? "Update" : "Create"} - +
diff --git a/platform/frontend/src/components/agent-tools-editor.tsx b/platform/frontend/src/components/agent-tools-editor.tsx index 25b43051f8..d27add8306 100644 --- a/platform/frontend/src/components/agent-tools-editor.tsx +++ b/platform/frontend/src/components/agent-tools-editor.tsx @@ -987,12 +987,20 @@ export function ToolChecklist({ const [searchQuery, setSearchQuery] = useState(""); // Snapshot the initial selection for sort order so tools don't jump - // around as the user toggles checkboxes. Re-sorts only when the - // component remounts (e.g. popover re-opens) or search query changes. + // around as the user toggles checkboxes. Updates when the selection + // transitions from empty to populated (async data load), then stays + // frozen until remount or search query changes. const initialSelectedRef = useRef(selectedToolIds); + const hasSelection = selectedToolIds.size > 0; + useEffect(() => { + if (initialSelectedRef.current.size === 0 && hasSelection) { + initialSelectedRef.current = selectedToolIds; + } + }, [selectedToolIds, hasSelection]); + // biome-ignore lint/correctness/useExhaustiveDependencies: re-sort when initial selection loads (empty → populated) const filteredTools = useMemo( () => sortAndFilterTools(tools, initialSelectedRef.current, searchQuery), - [tools, searchQuery], + [tools, searchQuery, hasSelection], ); const allSelected = filteredTools.every((tool) => diff --git a/platform/frontend/src/components/chat/chat-api-key-selector.tsx b/platform/frontend/src/components/chat/chat-api-key-selector.tsx index c57538d2d0..22bbf151c9 100644 --- a/platform/frontend/src/components/chat/chat-api-key-selector.tsx +++ b/platform/frontend/src/components/chat/chat-api-key-selector.tsx @@ -34,6 +34,7 @@ import { type ChatApiKeyScope, useAvailableChatApiKeys, } from "@/lib/chat-settings.query"; +import { getSavedApiKey, saveApiKey } from "@/lib/use-chat-preferences"; interface ChatApiKeySelectorProps { /** Conversation ID for persisting selection (optional for initial chat) */ @@ -67,7 +68,6 @@ const SCOPE_ICONS: Record = { // Note: This stores the API key's database ID (UUID), NOT the actual API key secret. // The actual API key value is never exposed to the frontend - it's stored securely on the server. // This ID is just a reference to select which key configuration to use, similar to a userId. -const LOCAL_STORAGE_KEY = "selected-chat-api-key-id"; /** * API Key selector for chat - allows users to select which API key to use for the conversation. @@ -183,10 +183,9 @@ export function ChatApiKeySelector({ : []; // Try to find key from localStorage (per-provider key) - const localStorageKey = currentProvider - ? `${LOCAL_STORAGE_KEY}-${currentProvider}` - : LOCAL_STORAGE_KEY; - const keyIdFromLocalStorage = localStorage.getItem(localStorageKey); + const keyIdFromLocalStorage = currentProvider + ? getSavedApiKey(currentProvider) + : null; const keyFromLocalStorage = keyIdFromLocalStorage ? providerKeys.find((k) => k.id === keyIdFromLocalStorage) : null; @@ -266,10 +265,7 @@ export function ChatApiKeySelector({ // Save to localStorage for the selected key's provider if (selectedKeyProvider) { - localStorage.setItem( - `${LOCAL_STORAGE_KEY}-${selectedKeyProvider}`, - keyId, - ); + saveApiKey(selectedKeyProvider, keyId); } // If the selected key has a different provider, notify parent to switch model diff --git a/platform/frontend/src/components/chat/chat-messages.tsx b/platform/frontend/src/components/chat/chat-messages.tsx index 5ba891cf92..1482370d8c 100644 --- a/platform/frontend/src/components/chat/chat-messages.tsx +++ b/platform/frontend/src/components/chat/chat-messages.tsx @@ -541,11 +541,38 @@ export function ChatMessages({ isLastAssistantInSequence && isLastTextPart && status !== "streaming"; - const citationParts = + // Show citations on the last text part of the last + // assistant message, only after streaming completes + // to avoid citations jumping between messages. + let citationParts: typeof message.parts | undefined; + if ( + isLastAssistantInSequence && isLastTextPart && - hasKnowledgeBaseToolCall(message.parts) - ? message.parts - : undefined; + !isResponseInProgress + ) { + if (hasKnowledgeBaseToolCall(message.parts ?? [])) { + citationParts = message.parts; + } else { + // Search backwards for KB tool calls within the same + // assistant turn — stop at the next user message to + // avoid showing stale citations from prior turns. + for ( + let prevIdx = idx - 1; + prevIdx >= 0; + prevIdx-- + ) { + const prev = messages[prevIdx]; + if (prev.role === "user") break; + if ( + prev.role === "assistant" && + hasKnowledgeBaseToolCall(prev.parts ?? []) + ) { + citationParts = prev.parts; + break; + } + } + } + } // Check for tags (used by Qwen and similar models) if (hasThinkingTags(part.text)) { diff --git a/platform/frontend/src/components/chat/knowledge-base-upload-indicator.tsx b/platform/frontend/src/components/chat/knowledge-base-upload-indicator.tsx index ae4ac08d24..2fd3495ab7 100644 --- a/platform/frontend/src/components/chat/knowledge-base-upload-indicator.tsx +++ b/platform/frontend/src/components/chat/knowledge-base-upload-indicator.tsx @@ -32,7 +32,7 @@ export function KnowledgeBaseUploadIndicator({
- KG Upload + Knowledge Base Upload
diff --git a/platform/frontend/src/components/chat/knowledge-graph-citations.test.ts b/platform/frontend/src/components/chat/knowledge-graph-citations.test.ts index caa090ed98..fce0c5ec76 100644 --- a/platform/frontend/src/components/chat/knowledge-graph-citations.test.ts +++ b/platform/frontend/src/components/chat/knowledge-graph-citations.test.ts @@ -5,15 +5,15 @@ import { } from "./knowledge-graph-citations"; describe("hasKnowledgeBaseToolCall", () => { - it("returns true when a part has toolName ending with query_knowledge_base", () => { + it("returns true when a part has toolName ending with query_knowledge_sources", () => { const parts = [ - { type: "dynamic-tool", toolName: "archestra__query_knowledge_base" }, + { type: "dynamic-tool", toolName: "archestra__query_knowledge_sources" }, ]; expect(hasKnowledgeBaseToolCall(parts)).toBe(true); }); - it("returns true for legacy tool parts with type ending in query_knowledge_base", () => { - const parts = [{ type: "tool-archestra__query_knowledge_base" }]; + it("returns true for legacy tool parts with type ending in query_knowledge_sources", () => { + const parts = [{ type: "tool-archestra__query_knowledge_sources" }]; expect(hasKnowledgeBaseToolCall(parts)).toBe(true); }); @@ -33,17 +33,39 @@ describe("hasKnowledgeBaseToolCall", () => { const parts = [ { type: "text" }, { type: "dynamic-tool", toolName: "web_search" }, - { type: "dynamic-tool", toolName: "archestra__query_knowledge_base" }, + { type: "dynamic-tool", toolName: "archestra__query_knowledge_sources" }, { type: "text" }, ]; expect(hasKnowledgeBaseToolCall(parts)).toBe(true); }); + + it("should be used to determine citation placement: citations belong on the NEXT message, not the tool-call message", () => { + // Message that initiates the tool call — has both text and tool parts + const toolCallMessageParts = [ + { type: "text" }, + { + type: "dynamic-tool", + toolName: "archestra__query_knowledge_sources", + }, + ]; + // The follow-up message that uses the results — text only + const followUpMessageParts = [{ type: "text" }]; + + // The tool-call message contains the KB tool call + expect(hasKnowledgeBaseToolCall(toolCallMessageParts)).toBe(true); + // The follow-up message does NOT contain a KB tool call + expect(hasKnowledgeBaseToolCall(followUpMessageParts)).toBe(false); + + // Citation placement rule: show citations on the follow-up message + // (where hasKnowledgeBaseToolCall is false for current message, + // but true for a previous message) + }); }); describe("extractCitations", () => { const makeKbPart = (output: unknown) => ({ type: "dynamic-tool", - toolName: "archestra__query_knowledge_base", + toolName: "archestra__query_knowledge_sources", state: "output-available" as const, output, }); @@ -126,7 +148,7 @@ describe("extractCitations", () => { it("ignores KB parts that are not in output-available state", () => { const part = { type: "dynamic-tool", - toolName: "archestra__query_knowledge_base", + toolName: "archestra__query_knowledge_sources", state: "input-available", output: undefined, }; @@ -200,7 +222,7 @@ describe("extractCitations", () => { it("extracts citations from legacy tool type parts", () => { const part = { - type: "tool-archestra__query_knowledge_base", + type: "tool-archestra__query_knowledge_sources", state: "output-available" as const, output: { results: [ @@ -234,7 +256,7 @@ describe("extractCitations", () => { }, ], }); - const toolResult = `name: archestra__query_knowledge_base\ncontent: "${innerJson.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`; + const toolResult = `name: archestra__query_knowledge_sources\ncontent: "${innerJson.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`; const output = { tool_result: toolResult }; const citations = extractCitations([makeKbPart(output)]); expect(citations).toHaveLength(1); @@ -246,6 +268,55 @@ describe("extractCitations", () => { }); }); + it("does not extract citations when tool call is pending (no output yet)", () => { + const part = { + type: "dynamic-tool", + toolName: "archestra__query_knowledge_sources", + state: "call" as const, + output: undefined, + }; + expect(extractCitations([part])).toEqual([]); + }); + + it("does not extract citations when tool is still running", () => { + const part = { + type: "dynamic-tool", + toolName: "archestra__query_knowledge_sources", + state: "running" as const, + output: undefined, + }; + expect(extractCitations([part])).toEqual([]); + }); + + it("only returns citations from parts with output-available state, ignoring pending ones", () => { + const pendingPart = { + type: "dynamic-tool", + toolName: "archestra__query_knowledge_sources", + state: "call" as const, + output: undefined, + }; + const completedPart = { + type: "dynamic-tool", + toolName: "archestra__query_knowledge_sources", + state: "output-available" as const, + output: { + results: [ + { + citation: { + title: "Completed Doc", + sourceUrl: null, + connectorType: null, + documentId: "doc-completed", + }, + }, + ], + }, + }; + const citations = extractCitations([pendingPart, completedPart]); + expect(citations).toHaveLength(1); + expect(citations[0].documentId).toBe("doc-completed"); + }); + it("extracts citations across multiple KB tool parts", () => { const part1 = makeKbPart({ results: [ diff --git a/platform/frontend/src/components/chat/knowledge-graph-citations.tsx b/platform/frontend/src/components/chat/knowledge-graph-citations.tsx index c12a81d3ff..bfc6a9d5f5 100644 --- a/platform/frontend/src/components/chat/knowledge-graph-citations.tsx +++ b/platform/frontend/src/components/chat/knowledge-graph-citations.tsx @@ -8,7 +8,7 @@ import { } from "@/app/knowledge/knowledge-bases/_parts/connector-icons"; import { Button } from "@/components/ui/button"; -const KNOWLEDGE_BASE_TOOL_SUFFIX = "query_knowledge_base"; +const KNOWLEDGE_BASE_TOOL_SUFFIX = "query_knowledge_sources"; export function hasKnowledgeBaseToolCall( parts: Array<{ type: string; toolName?: string }>, @@ -21,7 +21,7 @@ export function hasKnowledgeBaseToolCall( ) { return true; } - // Legacy tool parts have type like "tool-archestra__query_knowledge_base" + // Legacy tool parts have type like "tool-archestra__query_knowledge_sources" if ( typeof part.type === "string" && part.type.endsWith(KNOWLEDGE_BASE_TOOL_SUFFIX) diff --git a/platform/frontend/src/components/message-thread.tsx b/platform/frontend/src/components/message-thread.tsx index 1fee8e3a11..4b6bf9342b 100644 --- a/platform/frontend/src/components/message-thread.tsx +++ b/platform/frontend/src/components/message-thread.tsx @@ -9,7 +9,7 @@ import { ShieldCheck, TriangleAlert, } from "lucide-react"; -import { Fragment, useMemo } from "react"; +import { Fragment } from "react"; import { Action, Actions } from "@/components/ai-elements/actions"; import { Conversation, @@ -66,7 +66,6 @@ const MessageThread = ({ profileId?: string; }) => { const status: ChatStatus = "streaming" as ChatStatus; - const allParts = useMemo(() => messages.flatMap((m) => m.parts), [messages]); return (
p.type !== "text"); - const showCitations = - isLastTextPartInMessage && - hasKnowledgeBaseToolCall(allParts); + // Show citations on the last text part of the last + // assistant message, scoped to the current assistant turn + // (stop at the next user message to avoid stale citations). + let citationParts: typeof message.parts | undefined; + if (isLastTextPartInMessage) { + if (hasKnowledgeBaseToolCall(message.parts ?? [])) { + citationParts = message.parts; + } else { + for ( + let prevIdx = idx - 1; + prevIdx >= 0; + prevIdx-- + ) { + const prev = messages[prevIdx]; + if (prev.role === "user") break; + if ( + prev.role === "assistant" && + hasKnowledgeBaseToolCall(prev.parts ?? []) + ) { + citationParts = prev.parts; + break; + } + } + } + } return ( @@ -191,8 +212,10 @@ const MessageThread = ({ ? preserveNewlines(part.text) : part.text} - {showCitations && ( - + {citationParts && ( + )} diff --git a/platform/frontend/src/components/ui/dialog.tsx b/platform/frontend/src/components/ui/dialog.tsx index 95c59aa755..e3de92578b 100644 --- a/platform/frontend/src/components/ui/dialog.tsx +++ b/platform/frontend/src/components/ui/dialog.tsx @@ -130,6 +130,23 @@ function DialogDescription({ ); } +function DialogStickyFooter({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ); +} + /** * A form wrapper for dialog content that enables Enter key submission. * @@ -181,6 +198,7 @@ export { DialogHeader, DialogOverlay, DialogPortal, + DialogStickyFooter, DialogTitle, DialogTrigger, }; diff --git a/platform/frontend/src/lib/use-chat-preferences.test.ts b/platform/frontend/src/lib/use-chat-preferences.test.ts new file mode 100644 index 0000000000..957a937ef1 --- /dev/null +++ b/platform/frontend/src/lib/use-chat-preferences.test.ts @@ -0,0 +1,181 @@ +import { afterEach, beforeEach, describe, expect, test } from "vitest"; +import { + CHAT_STORAGE_KEYS, + clearSavedModel, + getApiKeyStorageKey, + getSavedAgent, + getSavedApiKey, + getSavedModel, + resolveInitialModel, + saveAgent, + saveApiKey, + saveModel, +} from "./use-chat-preferences"; + +beforeEach(() => { + localStorage.clear(); +}); + +afterEach(() => { + localStorage.clear(); +}); + +describe("CHAT_STORAGE_KEYS", () => { + test("has correct key values", () => { + expect(CHAT_STORAGE_KEYS.selectedModel).toBe( + "archestra-chat-selected-chat-model", + ); + expect(CHAT_STORAGE_KEYS.selectedAgent).toBe("selected-chat-agent"); + expect(CHAT_STORAGE_KEYS.selectedApiKeyPrefix).toBe( + "selected-chat-api-key-id", + ); + }); +}); + +describe("getApiKeyStorageKey", () => { + test("returns provider-specific key", () => { + expect(getApiKeyStorageKey("openai")).toBe( + "selected-chat-api-key-id-openai", + ); + expect(getApiKeyStorageKey("anthropic")).toBe( + "selected-chat-api-key-id-anthropic", + ); + }); +}); + +describe("model persistence", () => { + test("saveModel and getSavedModel round-trip", () => { + expect(getSavedModel()).toBeNull(); + saveModel("gpt-4o"); + expect(getSavedModel()).toBe("gpt-4o"); + }); + + test("clearSavedModel removes the saved model", () => { + saveModel("gpt-4o"); + clearSavedModel(); + expect(getSavedModel()).toBeNull(); + }); +}); + +describe("agent persistence", () => { + test("saveAgent and getSavedAgent round-trip", () => { + expect(getSavedAgent()).toBeNull(); + saveAgent("agent-123"); + expect(getSavedAgent()).toBe("agent-123"); + }); +}); + +describe("API key persistence", () => { + test("saveApiKey and getSavedApiKey round-trip per provider", () => { + saveApiKey("openai", "key-1"); + saveApiKey("anthropic", "key-2"); + expect(getSavedApiKey("openai")).toBe("key-1"); + expect(getSavedApiKey("anthropic")).toBe("key-2"); + expect(getSavedApiKey("gemini")).toBeNull(); + }); +}); + +describe("resolveInitialModel", () => { + const baseModels = { + openai: [{ id: "gpt-4o" }, { id: "gpt-4o-mini" }], + anthropic: [{ id: "claude-3-5-sonnet" }], + }; + + const baseChatApiKeys = [ + { id: "key-openai", provider: "openai" }, + { id: "key-anthropic", provider: "anthropic" }, + ]; + + test("returns null when no models available", () => { + const result = resolveInitialModel({ + modelsByProvider: {}, + agent: null, + chatApiKeys: [], + }); + expect(result).toBeNull(); + }); + + test("prefers localStorage model when valid", () => { + saveModel("claude-3-5-sonnet"); + const result = resolveInitialModel({ + modelsByProvider: baseModels, + agent: null, + chatApiKeys: baseChatApiKeys, + }); + expect(result).toEqual({ + modelId: "claude-3-5-sonnet", + apiKeyId: "key-anthropic", + source: "localStorage", + }); + }); + + test("clears stale localStorage model and falls through", () => { + saveModel("deleted-model"); + const result = resolveInitialModel({ + modelsByProvider: baseModels, + agent: null, + chatApiKeys: baseChatApiKeys, + }); + // Should have cleared the stale value + expect(getSavedModel()).toBeNull(); + // Should fall through to first available + expect(result?.source).toBe("fallback"); + expect(result?.modelId).toBe("gpt-4o"); + }); + + test("uses agent model when no localStorage", () => { + const result = resolveInitialModel({ + modelsByProvider: baseModels, + agent: { llmModel: "claude-3-5-sonnet", llmApiKeyId: "agent-key" }, + chatApiKeys: baseChatApiKeys, + }); + expect(result).toEqual({ + modelId: "claude-3-5-sonnet", + apiKeyId: "agent-key", + source: "agent", + }); + }); + + test("skips agent model when model is not in available models", () => { + const result = resolveInitialModel({ + modelsByProvider: baseModels, + agent: { llmModel: "deleted-model", llmApiKeyId: "agent-key" }, + chatApiKeys: baseChatApiKeys, + }); + expect(result?.source).toBe("fallback"); + }); + + test("falls back to first available model", () => { + const result = resolveInitialModel({ + modelsByProvider: baseModels, + agent: null, + chatApiKeys: baseChatApiKeys, + }); + expect(result).toEqual({ + modelId: "gpt-4o", + apiKeyId: "key-openai", + source: "fallback", + }); + }); + + test("returns null apiKeyId when no matching key for provider", () => { + const result = resolveInitialModel({ + modelsByProvider: baseModels, + agent: null, + chatApiKeys: [], // No keys at all + }); + expect(result?.modelId).toBe("gpt-4o"); + expect(result?.apiKeyId).toBeNull(); + }); + + test("localStorage takes priority over agent config", () => { + saveModel("gpt-4o"); + const result = resolveInitialModel({ + modelsByProvider: baseModels, + agent: { llmModel: "claude-3-5-sonnet", llmApiKeyId: "agent-key" }, + chatApiKeys: baseChatApiKeys, + }); + expect(result?.source).toBe("localStorage"); + expect(result?.modelId).toBe("gpt-4o"); + }); +}); diff --git a/platform/frontend/src/lib/use-chat-preferences.ts b/platform/frontend/src/lib/use-chat-preferences.ts new file mode 100644 index 0000000000..1745c1f93b --- /dev/null +++ b/platform/frontend/src/lib/use-chat-preferences.ts @@ -0,0 +1,162 @@ +// ===== LocalStorage Keys ===== + +export const CHAT_STORAGE_KEYS = { + selectedModel: "archestra-chat-selected-chat-model", + selectedAgent: "selected-chat-agent", + selectedApiKeyPrefix: "selected-chat-api-key-id", +} as const; + +// ===== Pure functions (testable without React) ===== + +/** + * Get the localStorage key for a provider-specific API key selection. + */ +export function getApiKeyStorageKey(provider: string): string { + return `${CHAT_STORAGE_KEYS.selectedApiKeyPrefix}-${provider}`; +} + +/** + * Read the saved model ID from localStorage. + * Returns null if not set or if running on the server. + */ +export function getSavedModel(): string | null { + if (typeof window === "undefined") return null; + return localStorage.getItem(CHAT_STORAGE_KEYS.selectedModel); +} + +/** + * Save the selected model ID to localStorage. + */ +export function saveModel(modelId: string): void { + if (typeof window === "undefined") return; + localStorage.setItem(CHAT_STORAGE_KEYS.selectedModel, modelId); +} + +/** + * Clear the saved model from localStorage (e.g., when it becomes stale). + */ +export function clearSavedModel(): void { + if (typeof window === "undefined") return; + localStorage.removeItem(CHAT_STORAGE_KEYS.selectedModel); +} + +/** + * Read the saved agent ID from localStorage. + */ +export function getSavedAgent(): string | null { + if (typeof window === "undefined") return null; + return localStorage.getItem(CHAT_STORAGE_KEYS.selectedAgent); +} + +/** + * Save the selected agent ID to localStorage. + */ +export function saveAgent(agentId: string): void { + if (typeof window === "undefined") return; + localStorage.setItem(CHAT_STORAGE_KEYS.selectedAgent, agentId); +} + +/** + * Read the saved API key ID for a specific provider from localStorage. + */ +export function getSavedApiKey(provider: string): string | null { + if (typeof window === "undefined") return null; + return localStorage.getItem(getApiKeyStorageKey(provider)); +} + +/** + * Save the selected API key ID for a specific provider to localStorage. + */ +export function saveApiKey(provider: string, keyId: string): void { + if (typeof window === "undefined") return; + localStorage.setItem(getApiKeyStorageKey(provider), keyId); +} + +// ===== Model resolution logic ===== + +interface ModelInfo { + id: string; +} + +interface AgentInfo { + llmModel?: string | null; + llmApiKeyId?: string | null; +} + +interface ResolveInitialModelParams { + modelsByProvider: Record; + agent: AgentInfo | null; + chatApiKeys: Array<{ id: string; provider: string }>; +} + +interface ResolvedModel { + modelId: string; + apiKeyId: string | null; + source: "localStorage" | "agent" | "fallback"; +} + +/** + * Resolve which model to use on initial chat load. + * Priority: localStorage > agent config > first available model. + * Returns null if no model can be resolved (e.g., no models available). + */ +export function resolveInitialModel( + params: ResolveInitialModelParams, +): ResolvedModel | null { + const { modelsByProvider, agent, chatApiKeys } = params; + const allModels = Object.values(modelsByProvider).flat(); + if (allModels.length === 0) return null; + + const findKeyForProvider = (provider: string): string | null => { + const key = chatApiKeys.find((k) => k.provider === provider); + return key?.id ?? null; + }; + + const findProviderForModel = (modelId: string): string | null => { + for (const [provider, models] of Object.entries(modelsByProvider)) { + if (models.some((m) => m.id === modelId)) return provider; + } + return null; + }; + + // 1. User's explicit selection from localStorage + const savedModelId = getSavedModel(); + if (savedModelId && allModels.some((m) => m.id === savedModelId)) { + const provider = findProviderForModel(savedModelId); + return { + modelId: savedModelId, + apiKeyId: provider ? findKeyForProvider(provider) : null, + source: "localStorage", + }; + } + + // Clear stale localStorage value if it existed but model no longer available + if (savedModelId) { + clearSavedModel(); + } + + // 2. Agent-configured model + if (agent?.llmModel && allModels.some((m) => m.id === agent.llmModel)) { + return { + modelId: agent.llmModel, + apiKeyId: agent.llmApiKeyId ?? null, + source: "agent", + }; + } + + // 3. First available model + const providers = Object.keys(modelsByProvider); + if (providers.length > 0) { + const firstProvider = providers[0]; + const models = modelsByProvider[firstProvider]; + if (models && models.length > 0) { + return { + modelId: models[0].id, + apiKeyId: findKeyForProvider(firstProvider), + source: "fallback", + }; + } + } + + return null; +} diff --git a/platform/pnpm-lock.yaml b/platform/pnpm-lock.yaml index e7e4d088d0..911c1c57b8 100644 --- a/platform/pnpm-lock.yaml +++ b/platform/pnpm-lock.yaml @@ -620,8 +620,8 @@ importers: specifier: ^2.0.7 version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) streamdown: - specifier: ^2.0.1 - version: 2.1.0(react@19.2.4) + specifier: ^2.4.0 + version: 2.4.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) tailwind-merge: specifier: ^3.4.0 version: 3.4.0 @@ -4684,8 +4684,8 @@ packages: '@types/node@25.2.3': resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==} - '@types/node@25.3.3': - resolution: {integrity: sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==} + '@types/node@25.4.0': + resolution: {integrity: sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw==} '@types/oracledb@6.5.2': resolution: {integrity: sha512-kK1eBS/Adeyis+3OlBDMeQQuasIDLUYXsi2T15ccNJ0iyUpQ4xDF7svFu3+bGVrI0CMBUclPciz+lsQR3JX3TQ==} @@ -7807,8 +7807,8 @@ packages: refractor@5.0.0: resolution: {integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==} - rehype-harden@1.1.7: - resolution: {integrity: sha512-j5DY0YSK2YavvNGV+qBHma15J9m0WZmRe8posT5AtKDS6TNWtMVTo6RiqF8SidfcASYz8f3k2J/1RWmq5zTXUw==} + rehype-harden@1.1.8: + resolution: {integrity: sha512-Qn7vR1xrf6fZCrkm9TDWi/AB4ylrHy+jqsNm1EHOAmbARYA6gsnVJBq/sdBh6kmT4NEZxH5vgIjrscefJAOXcw==} rehype-highlight@7.0.2: resolution: {integrity: sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==} @@ -7834,8 +7834,8 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} - remend@1.1.0: - resolution: {integrity: sha512-JENGyuIhTwzUfCarW43X4r9cehoqTo9QyYxfNDZSud2AmqeuWjZ5pfybasTa4q0dxTJAj5m8NB+wR+YueAFpxQ==} + remend@1.2.2: + resolution: {integrity: sha512-4ZJgIB9EG9fQE41mOJCRHMmnxDTKHWawQoJWZyUbZuj680wVyogu2ihnj8Edqm7vh2mo/TWHyEZpn2kqeDvS7w==} require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} @@ -8128,10 +8128,11 @@ packages: stream-length@1.0.2: resolution: {integrity: sha512-aI+qKFiwoDV4rsXiS7WRoCt+v2RX1nUj17+KJC5r2gfh5xoSJIfP6Y3Do/HtvesFcTSWthIuJ3l1cvKQY/+nZg==} - streamdown@2.1.0: - resolution: {integrity: sha512-u9gWd0AmjKg1d+74P44XaPlGrMeC21oDOSIhjGNEYMAttDMzCzlJO6lpTyJ9JkSinQQF65YcK4eOd3q9iTvULw==} + streamdown@2.4.0: + resolution: {integrity: sha512-fRk4HEYNznRLmxoVeT8wsGBwHF6/Yrdey6k+ZrE1Qtp4NyKwm7G/6e2Iw8penY4yLx31TlAHWT5Bsg1weZ9FZg==} peerDependencies: react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 streamx@2.23.0: resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} @@ -13785,7 +13786,7 @@ snapshots: dependencies: undici-types: 7.16.0 - '@types/node@25.3.3': + '@types/node@25.4.0': dependencies: undici-types: 7.18.2 @@ -15851,7 +15852,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 25.3.3 + '@types/node': 25.4.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -17335,7 +17336,7 @@ snapshots: hastscript: 9.0.1 parse-entities: 4.0.2 - rehype-harden@1.1.7: + rehype-harden@1.1.8: dependencies: unist-util-visit: 5.1.0 @@ -17398,7 +17399,7 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 - remend@1.1.0: {} + remend@1.2.2: {} require-directory@2.1.1: {} @@ -17774,23 +17775,25 @@ snapshots: dependencies: bluebird: 2.11.0 - streamdown@2.1.0(react@19.2.4): + streamdown@2.4.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: clsx: 2.1.1 hast-util-to-jsx-runtime: 2.3.6 html-url-attributes: 3.0.1 marked: 17.0.1 react: 19.2.4 - rehype-harden: 1.1.7 + react-dom: 19.2.4(react@19.2.4) + 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.1.0 + remend: 1.2.2 tailwind-merge: 3.4.0 unified: 11.0.5 unist-util-visit: 5.1.0 + unist-util-visit-parents: 6.0.2 transitivePeerDependencies: - supports-color diff --git a/platform/shared/consts.ts b/platform/shared/consts.ts index 6fa84bd4cd..09d7736643 100644 --- a/platform/shared/consts.ts +++ b/platform/shared/consts.ts @@ -168,12 +168,12 @@ export const AGENT_TOOL_PREFIX = `agent${MCP_SERVER_TOOL_NAME_SEPARATOR}`; export const TOOL_CREATE_MCP_SERVER_INSTALLATION_REQUEST_FULL_NAME = `${ARCHESTRA_MCP_SERVER_NAME}${MCP_SERVER_TOOL_NAME_SEPARATOR}create_mcp_server_installation_request`; export const TOOL_ARTIFACT_WRITE_FULL_NAME = `${ARCHESTRA_MCP_SERVER_NAME}${MCP_SERVER_TOOL_NAME_SEPARATOR}artifact_write`; export const TOOL_TODO_WRITE_FULL_NAME = `${ARCHESTRA_MCP_SERVER_NAME}${MCP_SERVER_TOOL_NAME_SEPARATOR}todo_write`; -export const TOOL_QUERY_KNOWLEDGE_BASE_FULL_NAME = `${ARCHESTRA_MCP_SERVER_NAME}${MCP_SERVER_TOOL_NAME_SEPARATOR}query_knowledge_base`; +export const TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME = `${ARCHESTRA_MCP_SERVER_NAME}${MCP_SERVER_TOOL_NAME_SEPARATOR}query_knowledge_sources`; export const DEFAULT_ARCHESTRA_TOOL_NAMES = [ TOOL_ARTIFACT_WRITE_FULL_NAME, TOOL_TODO_WRITE_FULL_NAME, - TOOL_QUERY_KNOWLEDGE_BASE_FULL_NAME, + TOOL_QUERY_KNOWLEDGE_SOURCES_FULL_NAME, ]; export const MCP_CATALOG_API_BASE_URL =