From 52763bb08214cc0abeeae9ca72fd9f058d429c32 Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 12:18:16 -0300 Subject: [PATCH 01/19] Record who may give an app to a Bot, and whether its owner or an admin gave it --- server/drizzle/0063_owner_app_grants.sql | 4 + server/drizzle/meta/0063_snapshot.json | 10204 +++++++++++++++++++++ server/drizzle/meta/_journal.json | 7 + server/src/db/schema/plugins.ts | 37 + 4 files changed, 10252 insertions(+) create mode 100644 server/drizzle/0063_owner_app_grants.sql create mode 100644 server/drizzle/meta/0063_snapshot.json diff --git a/server/drizzle/0063_owner_app_grants.sql b/server/drizzle/0063_owner_app_grants.sql new file mode 100644 index 0000000000..3c3bb5bd28 --- /dev/null +++ b/server/drizzle/0063_owner_app_grants.sql @@ -0,0 +1,4 @@ +ALTER TABLE "mcp_servers" ADD COLUMN "owner_grants" text;--> statement-breakpoint +ALTER TABLE "plugin_grants" ADD COLUMN "granted_as" text;--> statement-breakpoint +ALTER TABLE "mcp_servers" ADD CONSTRAINT "mcp_servers_owner_grants_check" CHECK ("mcp_servers"."owner_grants" IS NULL OR "mcp_servers"."owner_grants" IN ('any', 'admin'));--> statement-breakpoint +ALTER TABLE "plugin_grants" ADD CONSTRAINT "plugin_grants_granted_as_check" CHECK ("plugin_grants"."granted_as" IS NULL OR "plugin_grants"."granted_as" IN ('admin', 'owner')); \ No newline at end of file diff --git a/server/drizzle/meta/0063_snapshot.json b/server/drizzle/meta/0063_snapshot.json new file mode 100644 index 0000000000..8a8c84c6b9 --- /dev/null +++ b/server/drizzle/meta/0063_snapshot.json @@ -0,0 +1,10204 @@ +{ + "id": "59e401eb-8e91-4157-9ffd-c9e3aeb2dfab", + "prevId": "20465172-4c5a-4639-919a-d1f8d832e6ed", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.accounts": { + "name": "accounts", + "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 + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "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 with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "accounts_provider_account_idx": { + "name": "accounts_provider_account_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "agent_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "configuration": { + "name": "configuration", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "package_id": { + "name": "package_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "override": { + "name": "override", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agents_package_id_deployment_packages_id_fk": { + "name": "agents_package_id_deployment_packages_id_fk", + "tableFrom": "agents", + "tableTo": "deployment_packages", + "columnsFrom": [ + "package_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.attachments": { + "name": "attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "bytes": { + "name": "bytes", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "attached_at": { + "name": "attached_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "upload_group": { + "name": "upload_group", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "attachments_channel_idx": { + "name": "attachments_channel_idx", + "columns": [ + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "attachments_uploaded_by_idx": { + "name": "attachments_uploaded_by_idx", + "columns": [ + { + "expression": "uploaded_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "attachments_upload_group_idx": { + "name": "attachments_upload_group_idx", + "columns": [ + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "uploaded_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "upload_group", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"attachments\".\"attached_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "attachments_staged_idx": { + "name": "attachments_staged_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"attachments\".\"attached_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "attachments_channel_id_channels_id_fk": { + "name": "attachments_channel_id_channels_id_fk", + "tableFrom": "attachments", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "attachments_uploaded_by_users_id_fk": { + "name": "attachments_uploaded_by_users_id_fk", + "tableFrom": "attachments", + "tableTo": "users", + "columnsFrom": [ + "uploaded_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_events": { + "name": "audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "initiator_kind": { + "name": "initiator_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'person'" + }, + "initiator_id": { + "name": "initiator_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_events_created_at_idx": { + "name": "audit_events_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_events_type_time_idx": { + "name": "audit_events_type_time_idx", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_events_actor_time_idx": { + "name": "audit_events_actor_time_idx", + "columns": [ + { + "expression": "actor_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_events_target_time_idx": { + "name": "audit_events_target_time_idx", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_events_initiator_time_idx": { + "name": "audit_events_initiator_time_idx", + "columns": [ + { + "expression": "initiator_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.channel_agents": { + "name": "channel_agents", + "schema": "", + "columns": { + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "channel_agents_channel_id_channels_id_fk": { + "name": "channel_agents_channel_id_channels_id_fk", + "tableFrom": "channel_agents", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channel_agents_agent_id_agents_id_fk": { + "name": "channel_agents_agent_id_agents_id_fk", + "tableFrom": "channel_agents", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "channel_agents_channel_id_agent_id_pk": { + "name": "channel_agents_channel_id_agent_id_pk", + "columns": [ + "channel_id", + "agent_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.channel_memberships": { + "name": "channel_memberships", + "schema": "", + "columns": { + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pinned_at": { + "name": "pinned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "channel_memberships_channel_id_channels_id_fk": { + "name": "channel_memberships_channel_id_channels_id_fk", + "tableFrom": "channel_memberships", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channel_memberships_user_id_users_id_fk": { + "name": "channel_memberships_user_id_users_id_fk", + "tableFrom": "channel_memberships", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "channel_memberships_channel_id_user_id_pk": { + "name": "channel_memberships_channel_id_user_id_pk", + "columns": [ + "channel_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.channels": { + "name": "channels", + "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": true + }, + "suggested_prompts": { + "name": "suggested_prompts", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "allowed_groups": { + "name": "allowed_groups", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "package_id": { + "name": "package_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "override": { + "name": "override", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary_at": { + "name": "summary_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_message": { + "name": "last_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_message_source_id": { + "name": "last_message_source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_message_at": { + "name": "last_message_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_message_agent_id": { + "name": "last_message_agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "channels_recent_activity_idx": { + "name": "channels_recent_activity_idx", + "columns": [ + { + "expression": "COALESCE(\"last_message_at\", \"created_at\") DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "channels_awaiting_summary_idx": { + "name": "channels_awaiting_summary_idx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"channels\".\"summary\" is null and \"channels\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "channels_package_id_deployment_packages_id_fk": { + "name": "channels_package_id_deployment_packages_id_fk", + "tableFrom": "channels", + "tableTo": "deployment_packages", + "columnsFrom": [ + "package_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "channels_last_message_agent_id_agents_id_fk": { + "name": "channels_last_message_agent_id_agents_id_fk", + "tableFrom": "channels", + "tableTo": "agents", + "columnsFrom": [ + "last_message_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credentials": { + "name": "credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "kind": { + "name": "kind", + "type": "credential_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_value": { + "name": "encrypted_value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credentials_active_key_idx": { + "name": "credentials_active_key_idx", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credentials\".\"revoked_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment_packages": { + "name": "deployment_packages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_path": { + "name": "source_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "loaded_at": { + "name": "loaded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "deployment_packages_tenant_id_unique": { + "name": "deployment_packages_tenant_id_unique", + "nullsNotDistinct": false, + "columns": [ + "tenant_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.intelligence_channel_mappings": { + "name": "intelligence_channel_mappings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "intelligence_channel_mappings_thread_idx": { + "name": "intelligence_channel_mappings_thread_idx", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "intelligence_channel_mappings_user_id_users_id_fk": { + "name": "intelligence_channel_mappings_user_id_users_id_fk", + "tableFrom": "intelligence_channel_mappings", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "intelligence_channel_mappings_channel_id_channels_id_fk": { + "name": "intelligence_channel_mappings_channel_id_channels_id_fk", + "tableFrom": "intelligence_channel_mappings", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "intelligence_channel_mappings_user_id_channel_id_pk": { + "name": "intelligence_channel_mappings_user_id_channel_id_pk", + "columns": [ + "user_id", + "channel_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.revoked_access": { + "name": "revoked_access", + "schema": "", + "columns": { + "email": { + "name": "email", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_by": { + "name": "revoked_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_token_unique": { + "name": "sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_providers": { + "name": "sso_providers", + "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 + }, + "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 + } + }, + "indexes": {}, + "foreignKeys": { + "sso_providers_user_id_users_id_fk": { + "name": "sso_providers_user_id_users_id_fk", + "tableFrom": "sso_providers", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sso_providers_provider_id_unique": { + "name": "sso_providers_provider_id_unique", + "nullsNotDistinct": false, + "columns": [ + "provider_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_instructions": { + "name": "user_instructions", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instructions": { + "name": "instructions", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_instructions_user_id_users_id_fk": { + "name": "user_instructions_user_id_users_id_fk", + "tableFrom": "user_instructions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_roles_user_id_role_pk": { + "name": "user_roles_user_id_role_pk", + "columns": [ + "user_id", + "role" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "preferences": { + "name": "preferences", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "groups": { + "name": "groups", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "onboarding_step": { + "name": "onboarding_step", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "onboarding_completed_at": { + "name": "onboarding_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_signed_in_at": { + "name": "last_signed_in_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verifications": { + "name": "verifications", + "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 with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.action_policy": { + "name": "action_policy", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deny": { + "name": "deny", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "allow": { + "name": "allow", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "labels": { + "name": "labels", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.computer_page_frame": { + "name": "computer_page_frame", + "schema": "", + "columns": { + "computer_id": { + "name": "computer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "frame": { + "name": "frame", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "computer_page_frame_captured_idx": { + "name": "computer_page_frame_captured_idx", + "columns": [ + { + "expression": "captured_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "computer_page_frame_computer_id_tool_call_id_pk": { + "name": "computer_page_frame_computer_id_tool_call_id_pk", + "columns": [ + "computer_id", + "tool_call_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.computer_snapshot": { + "name": "computer_snapshot", + "schema": "", + "columns": { + "computer_id": { + "name": "computer_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "snapshot_id": { + "name": "snapshot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "elements": { + "name": "elements", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "taken_at": { + "name": "taken_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "session": { + "name": "session", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_preferences": { + "name": "agent_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hidden_at": { + "name": "hidden_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "pinned_at": { + "name": "pinned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_preferences_user_id_users_id_fk": { + "name": "agent_preferences_user_id_users_id_fk", + "tableFrom": "agent_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_preferences_agent_id_agents_id_fk": { + "name": "agent_preferences_agent_id_agents_id_fk", + "tableFrom": "agent_preferences", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "agent_preferences_user_id_agent_id_pk": { + "name": "agent_preferences_user_id_agent_id_pk", + "columns": [ + "user_id", + "agent_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_profiles": { + "name": "agent_profiles", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_description": { + "name": "role_description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "avatar_seed": { + "name": "avatar_seed", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visibility": { + "name": "visibility", + "type": "agent_visibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "callback_token_hash": { + "name": "callback_token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "callback_token_issued_at": { + "name": "callback_token_issued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_profiles_visibility_deleted_idx": { + "name": "agent_profiles_visibility_deleted_idx", + "columns": [ + { + "expression": "visibility", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_profiles_agent_id_agents_id_fk": { + "name": "agent_profiles_agent_id_agents_id_fk", + "tableFrom": "agent_profiles", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_profiles_owner_user_id_users_id_fk": { + "name": "agent_profiles_owner_user_id_users_id_fk", + "tableFrom": "agent_profiles", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "routine_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "waiting": { + "name": "waiting", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'schedule'" + } + }, + "indexes": { + "routine_runs_by_routine_idx": { + "name": "routine_runs_by_routine_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_sweeps": { + "name": "routine_sweeps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "swept_at": { + "name": "swept_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instruction": { + "name": "instruction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cron": { + "name": "cron", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "enabled_at": { + "name": "enabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routines_due_idx": { + "name": "routines_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_by_owner_idx": { + "name": "routines_by_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_owner_user_id_users_id_fk": { + "name": "routines_owner_user_id_users_id_fk", + "tableFrom": "routines", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_agent_id_agents_id_fk": { + "name": "routines_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.component_exclusions": { + "name": "component_exclusions", + "schema": "", + "columns": { + "component_name": { + "name": "component_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "withheld_by": { + "name": "withheld_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "component_exclusions_component_name_components_name_fk": { + "name": "component_exclusions_component_name_components_name_fk", + "tableFrom": "component_exclusions", + "tableTo": "components", + "columnsFrom": [ + "component_name" + ], + "columnsTo": [ + "name" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "component_exclusions_agent_id_agents_id_fk": { + "name": "component_exclusions_agent_id_agents_id_fk", + "tableFrom": "component_exclusions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "component_exclusions_component_name_agent_id_pk": { + "name": "component_exclusions_component_name_agent_id_pk", + "columns": [ + "component_name", + "agent_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.component_functions": { + "name": "component_functions", + "schema": "", + "columns": { + "component_name": { + "name": "component_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "function_name": { + "name": "function_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "component_functions_component_name_components_name_fk": { + "name": "component_functions_component_name_components_name_fk", + "tableFrom": "component_functions", + "tableTo": "components", + "columnsFrom": [ + "component_name" + ], + "columnsTo": [ + "name" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "component_functions_component_name_function_name_pk": { + "name": "component_functions_component_name_function_name_pk", + "columns": [ + "component_name", + "function_name" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.components": { + "name": "components", + "schema": "", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "draft_description": { + "name": "draft_description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "published_description": { + "name": "published_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.app_access_requests": { + "name": "app_access_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "app_key": { + "name": "app_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app_title": { + "name": "app_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_by": { + "name": "resolved_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "app_access_requests_pending_idx": { + "name": "app_access_requests_pending_idx", + "columns": [ + { + "expression": "requested_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "app_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"agent_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"app_access_requests\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "app_access_requests_status_idx": { + "name": "app_access_requests_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "app_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "app_access_requests_requested_by_users_id_fk": { + "name": "app_access_requests_requested_by_users_id_fk", + "tableFrom": "app_access_requests", + "tableTo": "users", + "columnsFrom": [ + "requested_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "app_access_requests_agent_id_agents_id_fk": { + "name": "app_access_requests_agent_id_agents_id_fk", + "tableFrom": "app_access_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "app_access_requests_kind_check": { + "name": "app_access_requests_kind_check", + "value": "\"app_access_requests\".\"kind\" IN ('enable', 'grant', 'connect_shared')" + }, + "app_access_requests_status_check": { + "name": "app_access_requests_status_check", + "value": "\"app_access_requests\".\"status\" IN ('pending', 'approved', 'dismissed', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.brokered_connections": { + "name": "brokered_connections", + "schema": "", + "columns": { + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app": { + "name": "app", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "holder": { + "name": "holder", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vendor_user_id": { + "name": "vendor_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connected_by": { + "name": "connected_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "probe_action": { + "name": "probe_action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "brokered_connections_person_idx": { + "name": "brokered_connections_person_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "app", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"brokered_connections\".\"holder\" = 'person'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "brokered_connections_deployment_idx": { + "name": "brokered_connections_deployment_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "app", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"brokered_connections\".\"holder\" = 'deployment'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "brokered_connections_user_idx": { + "name": "brokered_connections_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "brokered_connections_holder_check": { + "name": "brokered_connections_holder_check", + "value": "(\"brokered_connections\".\"holder\" = 'person' AND \"brokered_connections\".\"user_id\" IS NOT NULL) OR (\"brokered_connections\".\"holder\" = 'deployment' AND \"brokered_connections\".\"user_id\" IS NULL)" + }, + "brokered_connections_provider_check": { + "name": "brokered_connections_provider_check", + "value": "\"brokered_connections\".\"provider\" IN ('composio', 'arcade')" + } + }, + "isRLSEnabled": false + }, + "public.composio_connections": { + "name": "composio_connections", + "schema": "", + "columns": { + "toolkit": { + "name": "toolkit", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "probe_action": { + "name": "probe_action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "composio_connections_user_idx": { + "name": "composio_connections_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "composio_connections_toolkit_user_id_pk": { + "name": "composio_connections_toolkit_user_id_pk", + "columns": [ + "toolkit", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_servers": { + "name": "mcp_servers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vendor": { + "name": "vendor", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provenance": { + "name": "provenance", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'first-party'" + }, + "credential_id": { + "name": "credential_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "auth_scheme": { + "name": "auth_scheme", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_mode": { + "name": "account_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_vendor_user_id": { + "name": "shared_vendor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_grants": { + "name": "owner_grants", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tools_refreshed_at": { + "name": "tools_refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "skipped_tools": { + "name": "skipped_tools", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_servers_credential_id_credentials_id_fk": { + "name": "mcp_servers_credential_id_credentials_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_servers_account_mode_check": { + "name": "mcp_servers_account_mode_check", + "value": "\"mcp_servers\".\"account_mode\" IS NULL OR \"mcp_servers\".\"account_mode\" IN ('personal', 'shared')" + }, + "mcp_servers_owner_grants_check": { + "name": "mcp_servers_owner_grants_check", + "value": "\"mcp_servers\".\"owner_grants\" IS NULL OR \"mcp_servers\".\"owner_grants\" IN ('any', 'admin')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_tools": { + "name": "mcp_tools", + "schema": "", + "columns": { + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vendor_name": { + "name": "vendor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "input_schema": { + "name": "input_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "destructive": { + "name": "destructive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_tools_server_id_mcp_servers_id_fk": { + "name": "mcp_tools_server_id_mcp_servers_id_fk", + "tableFrom": "mcp_tools", + "tableTo": "mcp_servers", + "columnsFrom": [ + "server_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "mcp_tools_server_id_name_pk": { + "name": "mcp_tools_server_id_name_pk", + "columns": [ + "server_id", + "name" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_user_credentials": { + "name": "mcp_user_credentials", + "schema": "", + "columns": { + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_user_credentials_user_idx": { + "name": "mcp_user_credentials_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_user_credentials_server_id_mcp_servers_id_fk": { + "name": "mcp_user_credentials_server_id_mcp_servers_id_fk", + "tableFrom": "mcp_user_credentials", + "tableTo": "mcp_servers", + "columnsFrom": [ + "server_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_user_credentials_user_id_users_id_fk": { + "name": "mcp_user_credentials_user_id_users_id_fk", + "tableFrom": "mcp_user_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_user_credentials_credential_id_credentials_id_fk": { + "name": "mcp_user_credentials_credential_id_credentials_id_fk", + "tableFrom": "mcp_user_credentials", + "tableTo": "credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "mcp_user_credentials_server_id_user_id_pk": { + "name": "mcp_user_credentials_server_id_user_id_pk", + "columns": [ + "server_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_authorizations": { + "name": "pending_authorizations", + "schema": "", + "columns": { + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "holder": { + "name": "holder", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vendor_user_id": { + "name": "vendor_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_id": { + "name": "auth_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "pending_authorizations_server_idx": { + "name": "pending_authorizations_server_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pending_authorizations_actor_id_users_id_fk": { + "name": "pending_authorizations_actor_id_users_id_fk", + "tableFrom": "pending_authorizations", + "tableTo": "users", + "columnsFrom": [ + "actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_authorizations_server_id_mcp_servers_id_fk": { + "name": "pending_authorizations_server_id_mcp_servers_id_fk", + "tableFrom": "pending_authorizations", + "tableTo": "mcp_servers", + "columnsFrom": [ + "server_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pending_authorizations_holder_check": { + "name": "pending_authorizations_holder_check", + "value": "(\"pending_authorizations\".\"holder\" = 'person' AND \"pending_authorizations\".\"user_id\" IS NOT NULL) OR (\"pending_authorizations\".\"holder\" = 'deployment' AND \"pending_authorizations\".\"user_id\" IS NULL)" + }, + "pending_authorizations_outcome_check": { + "name": "pending_authorizations_outcome_check", + "value": "\"pending_authorizations\".\"outcome\" IS NULL OR \"pending_authorizations\".\"outcome\" IN ('completed', 'failed', 'unreconciled', 'withdrawn')" + }, + "pending_authorizations_completion_check": { + "name": "pending_authorizations_completion_check", + "value": "(\"pending_authorizations\".\"completed_at\" IS NULL) = (\"pending_authorizations\".\"outcome\" IS NULL)" + }, + "pending_authorizations_user_is_actor_check": { + "name": "pending_authorizations_user_is_actor_check", + "value": "\"pending_authorizations\".\"user_id\" IS NULL OR \"pending_authorizations\".\"user_id\" = \"pending_authorizations\".\"actor_id\"" + } + }, + "isRLSEnabled": false + }, + "public.plugin_grants": { + "name": "plugin_grants", + "schema": "", + "columns": { + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ref": { + "name": "ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "granted_as": { + "name": "granted_as", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_grants_agent_idx": { + "name": "plugin_grants_agent_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_grants_agent_id_agents_id_fk": { + "name": "plugin_grants_agent_id_agents_id_fk", + "tableFrom": "plugin_grants", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_grants_kind_ref_agent_id_pk": { + "name": "plugin_grants_kind_ref_agent_id_pk", + "columns": [ + "kind", + "ref", + "agent_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "plugin_grants_granted_as_check": { + "name": "plugin_grants_granted_as_check", + "value": "\"plugin_grants\".\"granted_as\" IS NULL OR \"plugin_grants\".\"granted_as\" IN ('admin', 'owner')" + } + }, + "isRLSEnabled": false + }, + "public.sandboxed_components": { + "name": "sandboxed_components", + "schema": "", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "draft_description": { + "name": "draft_description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "draft_html": { + "name": "draft_html", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "draft_css": { + "name": "draft_css", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "draft_js_functions": { + "name": "draft_js_functions", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "draft_argument_schema": { + "name": "draft_argument_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "published_description": { + "name": "published_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published_html": { + "name": "published_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published_css": { + "name": "published_css", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published_js_functions": { + "name": "published_js_functions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published_argument_schema": { + "name": "published_argument_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "sample_arguments": { + "name": "sample_arguments", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "authored_by": { + "name": "authored_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skill_tools": { + "name": "skill_tools", + "schema": "", + "columns": { + "skill_id": { + "name": "skill_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ref": { + "name": "ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "declared_by": { + "name": "declared_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skill_tools_ref_idx": { + "name": "skill_tools_ref_idx", + "columns": [ + { + "expression": "ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skill_tools_skill_id_skills_id_fk": { + "name": "skill_tools_skill_id_skills_id_fk", + "tableFrom": "skill_tools", + "tableTo": "skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "skill_tools_skill_id_ref_pk": { + "name": "skill_tools_skill_id_ref_pk", + "columns": [ + "skill_id", + "ref" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skills": { + "name": "skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instructions": { + "name": "instructions", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'yours'" + }, + "installed_by": { + "name": "installed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skills_slug_key": { + "name": "skills_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "skills_owner_idx": { + "name": "skills_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skills_owner_user_id_users_id_fk": { + "name": "skills_owner_user_id_users_id_fk", + "tableFrom": "skills", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.work_items": { + "name": "work_items", + "schema": "", + "columns": { + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_at": { + "name": "run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_by": { + "name": "claimed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_until": { + "name": "lease_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "work_items_claimable_idx": { + "name": "work_items_claimable_idx", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "work_items_kind_key_pk": { + "name": "work_items_kind_key_pk", + "columns": [ + "kind", + "key" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.voice_sessions": { + "name": "voice_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_message_id": { + "name": "anchor_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "transcript": { + "name": "transcript", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary_status": { + "name": "summary_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'failed'" + } + }, + "indexes": { + "voice_sessions_channel_started_idx": { + "name": "voice_sessions_channel_started_idx", + "columns": [ + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "voice_sessions_channel_id_channels_id_fk": { + "name": "voice_sessions_channel_id_channels_id_fk", + "tableFrom": "voice_sessions", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "voice_sessions_user_id_users_id_fk": { + "name": "voice_sessions_user_id_users_id_fk", + "tableFrom": "voice_sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "voice_sessions_duration_check": { + "name": "voice_sessions_duration_check", + "value": "\"voice_sessions\".\"duration_seconds\" >= 0" + }, + "voice_sessions_summary_check": { + "name": "voice_sessions_summary_check", + "value": "(\"voice_sessions\".\"summary_status\" = 'ready' AND \"voice_sessions\".\"summary\" IS NOT NULL) OR (\"voice_sessions\".\"summary_status\" = 'failed' AND \"voice_sessions\".\"summary\" IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.learning_settings": { + "name": "learning_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.learning_thread_bindings": { + "name": "learning_thread_bindings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "container_id": { + "name": "container_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "learning_thread_bindings_user_id_thread_id_pk": { + "name": "learning_thread_bindings_user_id_thread_id_pk", + "columns": [ + "user_id", + "thread_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.responsibilities": { + "name": "responsibilities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instruction": { + "name": "instruction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "success_criteria": { + "name": "success_criteria", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "responsibility_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "progress": { + "name": "progress", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subscriptions": { + "name": "subscriptions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "responsibilities_owner_status_idx": { + "name": "responsibilities_owner_status_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "responsibilities_owner_user_id_users_id_fk": { + "name": "responsibilities_owner_user_id_users_id_fk", + "tableFrom": "responsibilities", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "responsibilities_agent_id_agents_id_fk": { + "name": "responsibilities_agent_id_agents_id_fk", + "tableFrom": "responsibilities", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.responsibility_events": { + "name": "responsibility_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "responsibility_events_source_id_idx": { + "name": "responsibility_events_source_id_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "responsibility_events_owner_user_id_users_id_fk": { + "name": "responsibility_events_owner_user_id_users_id_fk", + "tableFrom": "responsibility_events", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.responsibility_runs": { + "name": "responsibility_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "responsibility_id": { + "name": "responsibility_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "responsibility_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "reply_text": { + "name": "reply_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "waiting": { + "name": "waiting", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "continuation": { + "name": "continuation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "responsibility_runs_event_idx": { + "name": "responsibility_runs_event_idx", + "columns": [ + { + "expression": "responsibility_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "responsibility_runs_status_idx": { + "name": "responsibility_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "responsibility_runs_responsibility_id_responsibilities_id_fk": { + "name": "responsibility_runs_responsibility_id_responsibilities_id_fk", + "tableFrom": "responsibility_runs", + "tableTo": "responsibilities", + "columnsFrom": [ + "responsibility_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "responsibility_runs_event_id_responsibility_events_id_fk": { + "name": "responsibility_runs_event_id_responsibility_events_id_fk", + "tableFrom": "responsibility_runs", + "tableTo": "responsibility_events", + "columnsFrom": [ + "event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.responsibility_source_bindings": { + "name": "responsibility_source_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "responsibility_source_bindings_owner_idx": { + "name": "responsibility_source_bindings_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "responsibility_source_bindings_owner_user_id_users_id_fk": { + "name": "responsibility_source_bindings_owner_user_id_users_id_fk", + "tableFrom": "responsibility_source_bindings", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "responsibility_source_bindings_credential_id_credentials_id_fk": { + "name": "responsibility_source_bindings_credential_id_credentials_id_fk", + "tableFrom": "responsibility_source_bindings", + "tableTo": "credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.responsibility_triggers": { + "name": "responsibility_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsibility_id": { + "name": "responsibility_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "responsibility_triggers_owner_idx": { + "name": "responsibility_triggers_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "responsibility_triggers_responsibility_idx": { + "name": "responsibility_triggers_responsibility_idx", + "columns": [ + { + "expression": "responsibility_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "responsibility_triggers_kind_idx": { + "name": "responsibility_triggers_kind_idx", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "responsibility_triggers_owner_user_id_users_id_fk": { + "name": "responsibility_triggers_owner_user_id_users_id_fk", + "tableFrom": "responsibility_triggers", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "responsibility_triggers_responsibility_id_responsibilities_id_fk": { + "name": "responsibility_triggers_responsibility_id_responsibilities_id_fk", + "tableFrom": "responsibility_triggers", + "tableTo": "responsibilities", + "columnsFrom": [ + "responsibility_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "responsibility_triggers_credential_id_credentials_id_fk": { + "name": "responsibility_triggers_credential_id_credentials_id_fk", + "tableFrom": "responsibility_triggers", + "tableTo": "credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approval_preferences": { + "name": "approval_preferences", + "schema": "", + "columns": { + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "auto_review": { + "name": "auto_review", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "host_commands": { + "name": "host_commands", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ask'" + } + }, + "indexes": {}, + "foreignKeys": { + "approval_preferences_owner_user_id_users_id_fk": { + "name": "approval_preferences_owner_user_id_users_id_fk", + "tableFrom": "approval_preferences", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "approval_preferences_host_commands_check": { + "name": "approval_preferences_host_commands_check", + "value": "\"approval_preferences\".\"host_commands\" IN ('ask', 'allow', 'never')" + } + }, + "isRLSEnabled": false + }, + "public.approval_requests": { + "name": "approval_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action_digest": { + "name": "action_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "approval_requests_action_idx": { + "name": "approval_requests_action_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_requests_inbox_idx": { + "name": "approval_requests_inbox_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_requests_owner_user_id_users_id_fk": { + "name": "approval_requests_owner_user_id_users_id_fk", + "tableFrom": "approval_requests", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "approval_status_check": { + "name": "approval_status_check", + "value": "\"approval_requests\".\"status\" IN ('pending', 'approved', 'denied', 'consumed', 'completed')" + }, + "approval_decision_check": { + "name": "approval_decision_check", + "value": "\"approval_requests\".\"decision\" IN ('allow_once', 'allow_always', 'deny', 'handled')" + } + }, + "isRLSEnabled": false + }, + "public.approval_rules": { + "name": "approval_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_ref": { + "name": "tool_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "behaviour": { + "name": "behaviour", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'allow'" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_rules_owner_idx": { + "name": "approval_rules_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_rules_owner_user_id_users_id_fk": { + "name": "approval_rules_owner_user_id_users_id_fk", + "tableFrom": "approval_rules", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "approval_rules_behaviour_check": { + "name": "approval_rules_behaviour_check", + "value": "\"approval_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')" + } + }, + "isRLSEnabled": false + }, + "public.approval_team_rules": { + "name": "approval_team_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_ref": { + "name": "tool_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "behaviour": { + "name": "behaviour", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "approval_team_rules_created_by_users_id_fk": { + "name": "approval_team_rules_created_by_users_id_fk", + "tableFrom": "approval_team_rules", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "approval_team_rules_behaviour_check": { + "name": "approval_team_rules_behaviour_check", + "value": "\"approval_team_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')" + } + }, + "isRLSEnabled": false + }, + "public.approval_team_settings": { + "name": "approval_team_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "enforce_auto_review": { + "name": "enforce_auto_review", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "custom_rules_enabled": { + "name": "custom_rules_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "host_commands_cap": { + "name": "host_commands_cap", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'allow'" + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "approval_team_settings_updated_by_users_id_fk": { + "name": "approval_team_settings_updated_by_users_id_fk", + "tableFrom": "approval_team_settings", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "approval_team_settings_host_commands_cap_check": { + "name": "approval_team_settings_host_commands_cap_check", + "value": "\"approval_team_settings\".\"host_commands_cap\" IN ('ask', 'allow', 'never')" + } + }, + "isRLSEnabled": false + }, + "public.memory_sources": { + "name": "memory_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_ref": { + "name": "tool_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "sync_status": { + "name": "sync_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "sync_error": { + "name": "sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_sync_at": { + "name": "next_sync_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "memory_sources_owner_idx": { + "name": "memory_sources_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_sources_due_idx": { + "name": "memory_sources_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_sources_owner_user_id_users_id_fk": { + "name": "memory_sources_owner_user_id_users_id_fk", + "tableFrom": "memory_sources", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "memory_sources_agent_id_agents_id_fk": { + "name": "memory_sources_agent_id_agents_id_fk", + "tableFrom": "memory_sources", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.personal_memories": { + "name": "personal_memories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "import_digest": { + "name": "import_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provenance": { + "name": "provenance", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unreviewed'" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "formed_by": { + "name": "formed_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'person'" + }, + "formed_by_agent_id": { + "name": "formed_by_agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_app": { + "name": "source_app", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_link": { + "name": "source_link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "observed_at": { + "name": "observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "intelligence_memory_id": { + "name": "intelligence_memory_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "intelligence_digest": { + "name": "intelligence_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "personal_memories_owner_idx": { + "name": "personal_memories_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "personal_memories_intelligence_idx": { + "name": "personal_memories_intelligence_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "intelligence_memory_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "personal_memories_formed_idx": { + "name": "personal_memories_formed_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "formed_by_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "personal_memories_import_idx": { + "name": "personal_memories_import_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "personal_memories_formed_digest_idx": { + "name": "personal_memories_formed_digest_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "import_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"personal_memories\".\"formed_by\" = 'bot'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "personal_memories_owner_user_id_users_id_fk": { + "name": "personal_memories_owner_user_id_users_id_fk", + "tableFrom": "personal_memories", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "personal_memories_source_id_memory_sources_id_fk": { + "name": "personal_memories_source_id_memory_sources_id_fk", + "tableFrom": "personal_memories", + "tableTo": "memory_sources", + "columnsFrom": [ + "source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "personal_memories_formed_by_agent_id_agents_id_fk": { + "name": "personal_memories_formed_by_agent_id_agents_id_fk", + "tableFrom": "personal_memories", + "tableTo": "agents", + "columnsFrom": [ + "formed_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.default_opt_outs": { + "name": "default_opt_outs", + "schema": "", + "columns": { + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "feature": { + "name": "feature", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "default_opt_outs_owner_user_id_users_id_fk": { + "name": "default_opt_outs_owner_user_id_users_id_fk", + "tableFrom": "default_opt_outs", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "default_opt_outs_agent_id_agents_id_fk": { + "name": "default_opt_outs_agent_id_agents_id_fk", + "tableFrom": "default_opt_outs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "default_opt_outs_owner_user_id_agent_id_feature_scope_pk": { + "name": "default_opt_outs_owner_user_id_agent_id_feature_scope_pk", + "columns": [ + "owner_user_id", + "agent_id", + "feature", + "scope" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "default_opt_outs_feature_check": { + "name": "default_opt_outs_feature_check", + "value": "\"default_opt_outs\".\"feature\" IN ('memory_source', 'proactive_research')" + } + }, + "isRLSEnabled": false + }, + "public.proactive_settings": { + "name": "proactive_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "focus": { + "name": "focus", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "interval_minutes": { + "name": "interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 240 + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_status": { + "name": "last_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "proactive_settings_owner_agent_idx": { + "name": "proactive_settings_owner_agent_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "proactive_settings_due_idx": { + "name": "proactive_settings_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "proactive_settings_owner_user_id_users_id_fk": { + "name": "proactive_settings_owner_user_id_users_id_fk", + "tableFrom": "proactive_settings", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "proactive_settings_agent_id_agents_id_fk": { + "name": "proactive_settings_agent_id_agents_id_fk", + "tableFrom": "proactive_settings", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "proactive_settings_interval_check": { + "name": "proactive_settings_interval_check", + "value": "\"proactive_settings\".\"interval_minutes\" >= 60 AND \"proactive_settings\".\"interval_minutes\" <= 1440" + }, + "proactive_settings_status_check": { + "name": "proactive_settings_status_check", + "value": "\"proactive_settings\".\"last_status\" IN ('idle', 'running', 'succeeded', 'error')" + } + }, + "isRLSEnabled": false + }, + "public.proactive_suggestions": { + "name": "proactive_suggestions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "setting_id": { + "name": "setting_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_app": { + "name": "source_app", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_link": { + "name": "source_link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "proactive_suggestions_owner_idx": { + "name": "proactive_suggestions_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "proactive_suggestions_run_idx": { + "name": "proactive_suggestions_run_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "proactive_suggestions_owner_user_id_users_id_fk": { + "name": "proactive_suggestions_owner_user_id_users_id_fk", + "tableFrom": "proactive_suggestions", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "proactive_suggestions_agent_id_agents_id_fk": { + "name": "proactive_suggestions_agent_id_agents_id_fk", + "tableFrom": "proactive_suggestions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "proactive_suggestions_setting_id_proactive_settings_id_fk": { + "name": "proactive_suggestions_setting_id_proactive_settings_id_fk", + "tableFrom": "proactive_suggestions", + "tableTo": "proactive_settings", + "columnsFrom": [ + "setting_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "proactive_suggestions_status_check": { + "name": "proactive_suggestions_status_check", + "value": "\"proactive_suggestions\".\"status\" IN ('open', 'dismissed', 'started', 'superseded')" + } + }, + "isRLSEnabled": false + }, + "public.demonstrations": { + "name": "demonstrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'recording'" + }, + "actions": { + "name": "actions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "draft": { + "name": "draft", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "skill_slug": { + "name": "skill_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "demonstrations_owner_idx": { + "name": "demonstrations_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "demonstrations_active_bot_idx": { + "name": "demonstrations_active_bot_idx", + "columns": [ + { + "expression": "bot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"demonstrations\".\"status\" = 'recording'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "demonstrations_owner_user_id_users_id_fk": { + "name": "demonstrations_owner_user_id_users_id_fk", + "tableFrom": "demonstrations", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "demonstrations_bot_id_agents_id_fk": { + "name": "demonstrations_bot_id_agents_id_fk", + "tableFrom": "demonstrations", + "tableTo": "agents", + "columnsFrom": [ + "bot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.delivery_bindings": { + "name": "delivery_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identity": { + "name": "identity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "realm": { + "name": "realm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mention_id": { + "name": "mention_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "opted_out_at": { + "name": "opted_out_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "delivery_binding_identity_idx": { + "name": "delivery_binding_identity_idx", + "columns": [ + { + "expression": "transport", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "realm", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "identity", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "delivery_bindings_owner_idx": { + "name": "delivery_bindings_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "delivery_bindings_owner_user_id_users_id_fk": { + "name": "delivery_bindings_owner_user_id_users_id_fk", + "tableFrom": "delivery_bindings", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "delivery_bindings_channel_id_channels_id_fk": { + "name": "delivery_bindings_channel_id_channels_id_fk", + "tableFrom": "delivery_bindings", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "delivery_bindings_agent_id_agents_id_fk": { + "name": "delivery_bindings_agent_id_agents_id_fk", + "tableFrom": "delivery_bindings", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.delivery_challenges": { + "name": "delivery_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "delivery_challenges_owner_user_id_users_id_fk": { + "name": "delivery_challenges_owner_user_id_users_id_fk", + "tableFrom": "delivery_challenges", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "delivery_challenges_channel_id_channels_id_fk": { + "name": "delivery_challenges_channel_id_channels_id_fk", + "tableFrom": "delivery_challenges", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "delivery_challenges_agent_id_agents_id_fk": { + "name": "delivery_challenges_agent_id_agents_id_fk", + "tableFrom": "delivery_challenges", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.delivery_inbox": { + "name": "delivery_inbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "binding_id": { + "name": "binding_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "realm": { + "name": "realm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "delivery_inbox_dedupe_idx": { + "name": "delivery_inbox_dedupe_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "realm", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "delivery_inbox_owner_idx": { + "name": "delivery_inbox_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "delivery_inbox_owner_user_id_users_id_fk": { + "name": "delivery_inbox_owner_user_id_users_id_fk", + "tableFrom": "delivery_inbox", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "delivery_inbox_channel_id_channels_id_fk": { + "name": "delivery_inbox_channel_id_channels_id_fk", + "tableFrom": "delivery_inbox", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "delivery_inbox_agent_id_agents_id_fk": { + "name": "delivery_inbox_agent_id_agents_id_fk", + "tableFrom": "delivery_inbox", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.delivery_outbox": { + "name": "delivery_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "binding_id": { + "name": "binding_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "delivery_outbox_owner_idx": { + "name": "delivery_outbox_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "delivery_outbox_provider_idx": { + "name": "delivery_outbox_provider_idx", + "columns": [ + { + "expression": "transport", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "delivery_outbox_owner_user_id_users_id_fk": { + "name": "delivery_outbox_owner_user_id_users_id_fk", + "tableFrom": "delivery_outbox", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "delivery_outbox_channel_id_channels_id_fk": { + "name": "delivery_outbox_channel_id_channels_id_fk", + "tableFrom": "delivery_outbox", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "delivery_outbox_agent_id_agents_id_fk": { + "name": "delivery_outbox_agent_id_agents_id_fk", + "tableFrom": "delivery_outbox", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "delivery_outbox_dedupe_key_unique": { + "name": "delivery_outbox_dedupe_key_unique", + "nullsNotDistinct": false, + "columns": [ + "dedupe_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.push_devices": { + "name": "push_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "push_devices_owner_idx": { + "name": "push_devices_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "push_devices_owner_user_id_users_id_fk": { + "name": "push_devices_owner_user_id_users_id_fk", + "tableFrom": "push_devices", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "push_devices_token_unique": { + "name": "push_devices_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.group_bot_threads": { + "name": "group_bot_threads", + "schema": "", + "columns": { + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "group_bot_threads_thread_idx": { + "name": "group_bot_threads_thread_idx", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "group_bot_threads_owner_user_id_users_id_fk": { + "name": "group_bot_threads_owner_user_id_users_id_fk", + "tableFrom": "group_bot_threads", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "group_bot_threads_channel_id_channels_id_fk": { + "name": "group_bot_threads_channel_id_channels_id_fk", + "tableFrom": "group_bot_threads", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "group_bot_threads_owner_user_id_channel_id_agent_id_pk": { + "name": "group_bot_threads_owner_user_id_channel_id_agent_id_pk", + "columns": [ + "owner_user_id", + "channel_id", + "agent_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.group_messages": { + "name": "group_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'completed'" + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "group_messages_channel_idx": { + "name": "group_messages_channel_idx", + "columns": [ + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "group_messages_channel_id_channels_id_fk": { + "name": "group_messages_channel_id_channels_id_fk", + "tableFrom": "group_messages", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "group_messages_owner_user_id_users_id_fk": { + "name": "group_messages_owner_user_id_users_id_fk", + "tableFrom": "group_messages", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "group_messages_status_check": { + "name": "group_messages_status_check", + "value": "\"group_messages\".\"status\" IN ('queued', 'running', 'waiting', 'completed', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.action_records": { + "name": "action_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "surface": { + "name": "surface", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "action_records_created_idx": { + "name": "action_records_created_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "action_records_bot_idx": { + "name": "action_records_bot_idx", + "columns": [ + { + "expression": "bot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "action_records_surface_check": { + "name": "action_records_surface_check", + "value": "\"action_records\".\"surface\" IN ('cloud', 'local')" + } + }, + "isRLSEnabled": false + }, + "public.capability_settings": { + "name": "capability_settings", + "schema": "", + "columns": { + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "capability": { + "name": "capability", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed": { + "name": "allowed", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "capability_settings_scope_kind_scope_id_capability_pk": { + "name": "capability_settings_scope_kind_scope_id_capability_pk", + "columns": [ + "scope_kind", + "scope_id", + "capability" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "capability_settings_scope_kind_check": { + "name": "capability_settings_scope_kind_check", + "value": "\"capability_settings\".\"scope_kind\" IN ('organization', 'role', 'group')" + } + }, + "isRLSEnabled": false + }, + "public.enterprise_settings": { + "name": "enterprise_settings", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_usage": { + "name": "model_usage", + "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": false + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed": { + "name": "allowed", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "model_usage_created_idx": { + "name": "model_usage_created_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "model_usage_model_idx": { + "name": "model_usage_model_idx", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "model_usage_source_check": { + "name": "model_usage_source_check", + "value": "\"model_usage\".\"source\" IN ('configured', 'observed', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.network_policies": { + "name": "network_policies", + "schema": "", + "columns": { + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rules": { + "name": "rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"entries\":[]}'::jsonb" + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "network_policies_scope_kind_scope_id_pk": { + "name": "network_policies_scope_kind_scope_id_pk", + "columns": [ + "scope_kind", + "scope_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "network_policies_scope_kind_check": { + "name": "network_policies_scope_kind_check", + "value": "\"network_policies\".\"scope_kind\" IN ('organization', 'group')" + }, + "network_policies_mode_check": { + "name": "network_policies_mode_check", + "value": "\"network_policies\".\"mode\" IN ('allow_all', 'defaults_plus_allowlist', 'allowlist_only')" + } + }, + "isRLSEnabled": false + }, + "public.scim_connection_bindings": { + "name": "scim_connection_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_key": { + "name": "connection_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provisioning_domain_id": { + "name": "provisioning_domain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "decommissioned_at": { + "name": "decommissioned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "decommission_status": { + "name": "decommission_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "decommission_cursor_user_id": { + "name": "decommission_cursor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decommission_reconciled_user_count": { + "name": "decommission_reconciled_user_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "decommission_batch_count": { + "name": "decommission_batch_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "decommission_revision": { + "name": "decommission_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "decommission_completed_at": { + "name": "decommission_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "decommission_lease_id": { + "name": "decommission_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decommission_lease_expires_at": { + "name": "decommission_lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "scim_connection_bindings_connection_idx": { + "name": "scim_connection_bindings_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "scim_connection_bindings_connection_key_unique": { + "name": "scim_connection_bindings_connection_key_unique", + "nullsNotDistinct": false, + "columns": [ + "connection_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_group_members": { + "name": "scim_group_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scim_user_id": { + "name": "scim_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "membership_key": { + "name": "membership_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "scim_group_members_connection_idx": { + "name": "scim_group_members_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_members_group_idx": { + "name": "scim_group_members_group_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_members_scim_user_idx": { + "name": "scim_group_members_scim_user_idx", + "columns": [ + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_group_members_group_id_scim_groups_id_fk": { + "name": "scim_group_members_group_id_scim_groups_id_fk", + "tableFrom": "scim_group_members", + "tableTo": "scim_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_group_members_scim_user_id_scim_users_id_fk": { + "name": "scim_group_members_scim_user_id_scim_users_id_fk", + "tableFrom": "scim_group_members", + "tableTo": "scim_users", + "columnsFrom": [ + "scim_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "scim_group_members_membership_key_unique": { + "name": "scim_group_members_membership_key_unique", + "nullsNotDistinct": false, + "columns": [ + "membership_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_groups": { + "name": "scim_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provisioning_domain_id": { + "name": "provisioning_domain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name_key": { + "name": "display_name_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id_key": { + "name": "external_id_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "order_key": { + "name": "order_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "scim_groups_connection_idx": { + "name": "scim_groups_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_groups_domain_idx": { + "name": "scim_groups_domain_idx", + "columns": [ + { + "expression": "provisioning_domain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "scim_groups_display_name_key_unique": { + "name": "scim_groups_display_name_key_unique", + "nullsNotDistinct": false, + "columns": [ + "display_name_key" + ] + }, + "scim_groups_external_id_key_unique": { + "name": "scim_groups_external_id_key_unique", + "nullsNotDistinct": false, + "columns": [ + "external_id_key" + ] + }, + "scim_groups_order_key_unique": { + "name": "scim_groups_order_key_unique", + "nullsNotDistinct": false, + "columns": [ + "order_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_identity_tombstones": { + "name": "scim_identity_tombstones", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provisioning_domain_id": { + "name": "provisioning_domain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id_key": { + "name": "external_id_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile": { + "name": "profile", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "scim_identity_tombstones_connection_idx": { + "name": "scim_identity_tombstones_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_identity_tombstones_domain_idx": { + "name": "scim_identity_tombstones_domain_idx", + "columns": [ + { + "expression": "provisioning_domain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_identity_tombstones_user_idx": { + "name": "scim_identity_tombstones_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_identity_tombstones_user_id_users_id_fk": { + "name": "scim_identity_tombstones_user_id_users_id_fk", + "tableFrom": "scim_identity_tombstones", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "scim_identity_tombstones_external_id_key_unique": { + "name": "scim_identity_tombstones_external_id_key_unique", + "nullsNotDistinct": false, + "columns": [ + "external_id_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_projection_grants": { + "name": "scim_projection_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provisioning_domain_id": { + "name": "provisioning_domain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scim_user_id": { + "name": "scim_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_value": { + "name": "source_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant_key": { + "name": "grant_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "scim_projection_grants_connection_idx": { + "name": "scim_projection_grants_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_projection_grants_domain_idx": { + "name": "scim_projection_grants_domain_idx", + "columns": [ + { + "expression": "provisioning_domain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_projection_grants_scim_user_idx": { + "name": "scim_projection_grants_scim_user_idx", + "columns": [ + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_projection_grants_user_idx": { + "name": "scim_projection_grants_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_projection_grants_scim_user_id_scim_users_id_fk": { + "name": "scim_projection_grants_scim_user_id_scim_users_id_fk", + "tableFrom": "scim_projection_grants", + "tableTo": "scim_users", + "columnsFrom": [ + "scim_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_projection_grants_user_id_users_id_fk": { + "name": "scim_projection_grants_user_id_users_id_fk", + "tableFrom": "scim_projection_grants", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "scim_projection_grants_grant_key_unique": { + "name": "scim_projection_grants_grant_key_unique", + "nullsNotDistinct": false, + "columns": [ + "grant_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_subjects": { + "name": "scim_subjects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile_source_id": { + "name": "profile_source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "scim_subjects_profile_source_idx": { + "name": "scim_subjects_profile_source_idx", + "columns": [ + { + "expression": "profile_source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_subjects_user_id_users_id_fk": { + "name": "scim_subjects_user_id_users_id_fk", + "tableFrom": "scim_subjects", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "scim_subjects_user_id_unique": { + "name": "scim_subjects_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_users": { + "name": "scim_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provisioning_domain_id": { + "name": "provisioning_domain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_user_key": { + "name": "connection_user_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_name_key": { + "name": "user_name_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "primary_email": { + "name": "primary_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "work_email_value_index": { + "name": "work_email_value_index", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_value_index": { + "name": "email_value_index", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "formatted_name": { + "name": "formatted_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "given_name": { + "name": "given_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "family_name": { + "name": "family_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serialized_emails": { + "name": "serialized_emails", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serialized_attributes": { + "name": "serialized_attributes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id_key": { + "name": "external_id_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "order_key": { + "name": "order_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "scim_users_connection_idx": { + "name": "scim_users_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_users_domain_idx": { + "name": "scim_users_domain_idx", + "columns": [ + { + "expression": "provisioning_domain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_users_user_idx": { + "name": "scim_users_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_users_user_id_users_id_fk": { + "name": "scim_users_user_id_users_id_fk", + "tableFrom": "scim_users", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "scim_users_connection_user_key_unique": { + "name": "scim_users_connection_user_key_unique", + "nullsNotDistinct": false, + "columns": [ + "connection_user_key" + ] + }, + "scim_users_user_name_key_unique": { + "name": "scim_users_user_name_key_unique", + "nullsNotDistinct": false, + "columns": [ + "user_name_key" + ] + }, + "scim_users_external_id_key_unique": { + "name": "scim_users_external_id_key_unique", + "nullsNotDistinct": false, + "columns": [ + "external_id_key" + ] + }, + "scim_users_order_key_unique": { + "name": "scim_users_order_key_unique", + "nullsNotDistinct": false, + "columns": [ + "order_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_lifecycle": { + "name": "bot_lifecycle", + "schema": "", + "columns": { + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "notify": { + "name": "notify", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "bot_lifecycle_owner_user_id_users_id_fk": { + "name": "bot_lifecycle_owner_user_id_users_id_fk", + "tableFrom": "bot_lifecycle", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_lifecycle_agent_id_agents_id_fk": { + "name": "bot_lifecycle_agent_id_agents_id_fk", + "tableFrom": "bot_lifecycle", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "bot_lifecycle_owner_user_id_agent_id_pk": { + "name": "bot_lifecycle_owner_user_id_agent_id_pk", + "columns": [ + "owner_user_id", + "agent_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.update_routing_preferences": { + "name": "update_routing_preferences", + "schema": "", + "columns": { + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "update_routing_preferences_owner_user_id_users_id_fk": { + "name": "update_routing_preferences_owner_user_id_users_id_fk", + "tableFrom": "update_routing_preferences", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "update_routing_preferences_owner_user_id_kind_pk": { + "name": "update_routing_preferences_owner_user_id_kind_pk", + "columns": [ + "owner_user_id", + "kind" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.saved_logins": { + "name": "saved_logins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "saved_logins_owner_origin_username_idx": { + "name": "saved_logins_owner_origin_username_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "saved_logins_owner_idx": { + "name": "saved_logins_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "saved_logins_owner_user_id_users_id_fk": { + "name": "saved_logins_owner_user_id_users_id_fk", + "tableFrom": "saved_logins", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sign_in_requests": { + "name": "sign_in_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "control_request_id": { + "name": "control_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "continuation": { + "name": "continuation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "filling_until": { + "name": "filling_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sign_in_requests_owner_idx": { + "name": "sign_in_requests_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sign_in_requests_owner_user_id_users_id_fk": { + "name": "sign_in_requests_owner_user_id_users_id_fk", + "tableFrom": "sign_in_requests", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "sign_in_requests_status_check": { + "name": "sign_in_requests_status_check", + "value": "\"sign_in_requests\".\"status\" IN ('pending', 'filling', 'code_required', 'taken_over', 'signed_in', 'failed', 'cancelled', 'expired')" + }, + "sign_in_requests_method_check": { + "name": "sign_in_requests_method_check", + "value": "\"sign_in_requests\".\"method\" IS NULL OR \"sign_in_requests\".\"method\" IN ('typed', 'saved', 'takeover')" + } + }, + "isRLSEnabled": false + }, + "public.team_bot_assignments": { + "name": "team_bot_assignments", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_bot_assignments_agent_id_agents_id_fk": { + "name": "team_bot_assignments_agent_id_agents_id_fk", + "tableFrom": "team_bot_assignments", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_bot_assignments_assigned_by_users_id_fk": { + "name": "team_bot_assignments_assigned_by_users_id_fk", + "tableFrom": "team_bot_assignments", + "tableTo": "users", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "team_bot_assignments_agent_id_group_name_pk": { + "name": "team_bot_assignments_agent_id_group_name_pk", + "columns": [ + "agent_id", + "group_name" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_bot_audience": { + "name": "team_bot_audience", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_bot_audience_agent_id_agents_id_fk": { + "name": "team_bot_audience_agent_id_agents_id_fk", + "tableFrom": "team_bot_audience", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "team_bot_audience_agent_id_kind_value_pk": { + "name": "team_bot_audience_agent_id_kind_value_pk", + "columns": [ + "agent_id", + "kind", + "value" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "team_bot_audience_kind_check": { + "name": "team_bot_audience_kind_check", + "value": "\"team_bot_audience\".\"kind\" IN ('user', 'group')" + } + }, + "isRLSEnabled": false + }, + "public.team_bot_consent_defaults": { + "name": "team_bot_consent_defaults", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_bot_consent_defaults_user_id_users_id_fk": { + "name": "team_bot_consent_defaults_user_id_users_id_fk", + "tableFrom": "team_bot_consent_defaults", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "team_bot_consent_defaults_user_id_server_id_pk": { + "name": "team_bot_consent_defaults_user_id_server_id_pk", + "columns": [ + "user_id", + "server_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_bot_consents": { + "name": "team_bot_consents", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_bot_consents_user_id_users_id_fk": { + "name": "team_bot_consents_user_id_users_id_fk", + "tableFrom": "team_bot_consents", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_bot_consents_agent_id_agents_id_fk": { + "name": "team_bot_consents_agent_id_agents_id_fk", + "tableFrom": "team_bot_consents", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "team_bot_consents_user_id_agent_id_server_id_pk": { + "name": "team_bot_consents_user_id_agent_id_server_id_pk", + "columns": [ + "user_id", + "agent_id", + "server_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "team_bot_consents_decision_check": { + "name": "team_bot_consents_decision_check", + "value": "\"team_bot_consents\".\"decision\" IN ('always', 'once')" + } + }, + "isRLSEnabled": false + }, + "public.team_bot_publications": { + "name": "team_bot_publications", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "published_by": { + "name": "published_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "audience": { + "name": "audience", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_bot_publications_agent_id_agents_id_fk": { + "name": "team_bot_publications_agent_id_agents_id_fk", + "tableFrom": "team_bot_publications", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_bot_publications_published_by_users_id_fk": { + "name": "team_bot_publications_published_by_users_id_fk", + "tableFrom": "team_bot_publications", + "tableTo": "users", + "columnsFrom": [ + "published_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "team_bot_publications_audience_check": { + "name": "team_bot_publications_audience_check", + "value": "\"team_bot_publications\".\"audience\" IN ('team', 'people')" + } + }, + "isRLSEnabled": false + }, + "public.shared_use_approval_members": { + "name": "shared_use_approval_members", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "shared_use_approval_members_approval_fk": { + "name": "shared_use_approval_members_approval_fk", + "tableFrom": "shared_use_approval_members", + "tableTo": "shared_use_approvals", + "columnsFrom": [ + "agent_id", + "server_id" + ], + "columnsTo": [ + "agent_id", + "server_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_use_approval_members_agent_id_server_id_kind_value_pk": { + "name": "shared_use_approval_members_agent_id_server_id_kind_value_pk", + "columns": [ + "agent_id", + "server_id", + "kind", + "value" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "shared_use_approval_members_kind_check": { + "name": "shared_use_approval_members_kind_check", + "value": "\"shared_use_approval_members\".\"kind\" IN ('user', 'group')" + } + }, + "isRLSEnabled": false + }, + "public.shared_use_approvals": { + "name": "shared_use_approvals", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "audience": { + "name": "audience", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outside_input": { + "name": "outside_input", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "approved_by": { + "name": "approved_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shared_use_approvals_agent_id_agents_id_fk": { + "name": "shared_use_approvals_agent_id_agents_id_fk", + "tableFrom": "shared_use_approvals", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_use_approvals_server_id_mcp_servers_id_fk": { + "name": "shared_use_approvals_server_id_mcp_servers_id_fk", + "tableFrom": "shared_use_approvals", + "tableTo": "mcp_servers", + "columnsFrom": [ + "server_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_use_approvals_agent_id_server_id_pk": { + "name": "shared_use_approvals_agent_id_server_id_pk", + "columns": [ + "agent_id", + "server_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "shared_use_approvals_audience_check": { + "name": "shared_use_approvals_audience_check", + "value": "\"shared_use_approvals\".\"audience\" IN ('owner', 'people', 'team')" + } + }, + "isRLSEnabled": false + }, + "public.shared_use_requests": { + "name": "shared_use_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "proposed_audience": { + "name": "proposed_audience", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "proposed_outside_input": { + "name": "proposed_outside_input", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "proposed_members": { + "name": "proposed_members", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "decided_by": { + "name": "decided_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "shared_use_requests_pending_idx": { + "name": "shared_use_requests_pending_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"shared_use_requests\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "shared_use_requests_status_idx": { + "name": "shared_use_requests_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_use_requests_agent_id_agents_id_fk": { + "name": "shared_use_requests_agent_id_agents_id_fk", + "tableFrom": "shared_use_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_use_requests_server_id_mcp_servers_id_fk": { + "name": "shared_use_requests_server_id_mcp_servers_id_fk", + "tableFrom": "shared_use_requests", + "tableTo": "mcp_servers", + "columnsFrom": [ + "server_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "shared_use_requests_audience_check": { + "name": "shared_use_requests_audience_check", + "value": "\"shared_use_requests\".\"proposed_audience\" IN ('owner', 'people', 'team')" + }, + "shared_use_requests_reason_check": { + "name": "shared_use_requests_reason_check", + "value": "\"shared_use_requests\".\"reason\" IN ('publish', 'trigger', 'grant', 'refused_call')" + }, + "shared_use_requests_status_check": { + "name": "shared_use_requests_status_check", + "value": "\"shared_use_requests\".\"status\" IN ('pending', 'approved', 'declined', 'superseded')" + } + }, + "isRLSEnabled": false + }, + "public.identity_link_challenges": { + "name": "identity_link_challenges", + "schema": "", + "columns": { + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "identity_link_challenges_expiry_idx": { + "name": "identity_link_challenges_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "identity_link_challenges_user_provider_idx": { + "name": "identity_link_challenges_user_provider_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.identity_links": { + "name": "identity_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "realm": { + "name": "realm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified_by": { + "name": "verified_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "identity_links_identity_idx": { + "name": "identity_links_identity_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "realm", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "identity_links_user_realm_idx": { + "name": "identity_links_user_realm_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "realm", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "identity_links_credential_idx": { + "name": "identity_links_credential_idx", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"identity_links\".\"credential_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "identity_links_credential_id_credentials_id_fk": { + "name": "identity_links_credential_id_credentials_id_fk", + "tableFrom": "identity_links", + "tableTo": "credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "identity_links_verified_by_check": { + "name": "identity_links_verified_by_check", + "value": "\"identity_links\".\"verified_by\" IN ('challenge', 'oauth')" + }, + "identity_links_status_check": { + "name": "identity_links_status_check", + "value": "\"identity_links\".\"status\" IN ('active', 'needs_reconnect')" + } + }, + "isRLSEnabled": false + } + }, + "enums": { + "public.agent_type": { + "name": "agent_type", + "schema": "public", + "values": [ + "built_in", + "remote_ag_ui", + "remote_mastra" + ] + }, + "public.credential_kind": { + "name": "credential_kind", + "schema": "public", + "values": [ + "model", + "connector", + "agent", + "mcp", + "mcp_oauth_client", + "mcp_user_token" + ] + }, + "public.role": { + "name": "role", + "schema": "public", + "values": [ + "admin", + "user" + ] + }, + "public.agent_visibility": { + "name": "agent_visibility", + "schema": "public", + "values": [ + "public", + "private" + ] + }, + "public.routine_run_status": { + "name": "routine_run_status", + "schema": "public", + "values": [ + "succeeded", + "failed", + "skipped", + "waiting" + ] + }, + "public.responsibility_run_status": { + "name": "responsibility_run_status", + "schema": "public", + "values": [ + "queued", + "running", + "waiting", + "succeeded", + "failed", + "skipped" + ] + }, + "public.responsibility_status": { + "name": "responsibility_status", + "schema": "public", + "values": [ + "active", + "paused", + "completed" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/server/drizzle/meta/_journal.json b/server/drizzle/meta/_journal.json index a3b2138466..74a928ec93 100644 --- a/server/drizzle/meta/_journal.json +++ b/server/drizzle/meta/_journal.json @@ -442,6 +442,13 @@ "when": 1793016088183, "tag": "0062_action_policy_labels", "breakpoints": true + }, + { + "idx": 63, + "version": "7", + "when": 1793027892885, + "tag": "0063_owner_app_grants", + "breakpoints": true } ] } \ No newline at end of file diff --git a/server/src/db/schema/plugins.ts b/server/src/db/schema/plugins.ts index 3c64f186f4..4eaa9e44a4 100644 --- a/server/src/db/schema/plugins.ts +++ b/server/src/db/schema/plugins.ts @@ -133,6 +133,21 @@ export const mcpServers = pgTable( * any connection row exists, so this is where `authorize` and `confirm` both read it from. */ sharedVendorUserId: text("shared_vendor_user_id"), + /** + * Who may give this app to a Bot: any Bot's owner, for their own Bot (`any`), or only an + * administrator (`admin`). + * + * NULL MEANS THE DEFAULT FOR WHOSE ACCOUNT THE APP RUNS ON, and that is a statement rather than + * a gap. `ownerGrantsOf` in `plugins/app-access.ts` reads it: an app every person connects for + * themselves, and the built-in Routines, default to `any`; an app on the deployment's token, a + * Shared team account or no account at all defaults to `admin`. Stored only once an + * administrator chooses, so an app switched from Personal to Shared falls back to `admin` by + * itself instead of keeping a default somebody chose for the other mode. + * + * Nullable for the reason `docs/development.md` gives under Migrations: a replica from before + * this column writes rows without it. + */ + ownerGrants: text("owner_grants").$type<"any" | "admin">(), /** What the deployment last heard back from it. `null` until the first successful listing. */ toolsRefreshedAt: timestamp("tools_refreshed_at", { withTimezone: true }), /** The last failure, kept so the Plugins page can say why a server has no tools. */ @@ -154,6 +169,10 @@ export const mcpServers = pgTable( "mcp_servers_account_mode_check", sql`${table.accountMode} IS NULL OR ${table.accountMode} IN ('personal', 'shared')`, ), + check( + "mcp_servers_owner_grants_check", + sql`${table.ownerGrants} IS NULL OR ${table.ownerGrants} IN ('any', 'admin')`, + ), ], ); @@ -802,12 +821,30 @@ export const pluginGrants = pgTable( .notNull() .references(() => agents.id, { onDelete: "cascade" }), grantedBy: text("granted_by"), + /** + * Whether an administrator made this grant (`admin`, or null on every row from before this + * column) or the Bot's owner gave one of their own apps to their own Bot (`owner`). + * + * NOT BOOKKEEPING: IT DECIDES WHOSE ACCOUNT A CALL USES. On a published Team Bot, an + * administrator's grant of a personal app goes out on the owner's account wherever the owner + * connected it — `credentialActorFor` in `team-bots/team-bots.ts`. An owner's grant never does: + * it always uses the asking person's own account. Otherwise publishing a Bot would hand every + * teammate the owner's mailbox, which nobody decided except by giving the Bot their own Gmail. + * + * Nullable, null read as `admin`, for the rolling deploy `docs/development.md` describes: a + * replica from before this column only ever wrote an administrator's grant. + */ + grantedAs: text("granted_as").$type<"admin" | "owner">(), grantedAt: createdAt(), updatedAt: updatedAt(), }, (table) => [ primaryKey({ columns: [table.kind, table.ref, table.agentId] }), index("plugin_grants_agent_idx").on(table.agentId), + check( + "plugin_grants_granted_as_check", + sql`${table.grantedAs} IS NULL OR ${table.grantedAs} IN ('admin', 'owner')`, + ), ], ); From dcfbd0be602fdce71fceb285b885f3e57b14d74b Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 12:27:22 -0300 Subject: [PATCH 02/19] Tell a Bot's owner when an app is theirs to give it, and let an admin decide which apps are --- app/src/lib/enterprise/queries.ts | 3 +- server/src/admin/capabilities.ts | 7 + server/src/audit.ts | 8 + server/src/plugins/app-access-tool.ts | 5 + server/src/plugins/app-access.ts | 67 +++++- server/src/plugins/owner-grants.ts | 59 +++++ server/src/plugins/plugin-store/app-access.ts | 213 ++++++++++++++++-- server/src/plugins/plugin-store/grants.ts | 66 +++++- server/src/plugins/plugin-store/model.ts | 7 + server/src/plugins/plugin-store/servers.ts | 13 ++ .../app-access-routes.integration.test.ts | 28 ++- server/tests/app-access-state.test.ts | 59 +++++ server/tests/app-access-tool.test.ts | 10 +- server/tests/plugin-routes.test.ts | 2 + 14 files changed, 512 insertions(+), 35 deletions(-) create mode 100644 server/src/plugins/owner-grants.ts diff --git a/app/src/lib/enterprise/queries.ts b/app/src/lib/enterprise/queries.ts index 7e58b53313..4078cee654 100644 --- a/app/src/lib/enterprise/queries.ts +++ b/app/src/lib/enterprise/queries.ts @@ -10,7 +10,8 @@ export type CapabilityKey = | "customRules" | "passwordManager" | "slackTeams" - | "teamBots"; + | "teamBots" + | "ownerApps"; /** One switch as the server stored it at each scope. `null` means no row: the scope inherits. */ export type CapabilitySetting = { diff --git a/server/src/admin/capabilities.ts b/server/src/admin/capabilities.ts index 8205d15dce..3af70aff9b 100644 --- a/server/src/admin/capabilities.ts +++ b/server/src/admin/capabilities.ts @@ -25,6 +25,7 @@ export const CAPABILITIES = [ "passwordManager", "slackTeams", "teamBots", + "ownerApps", ] as const; export type Capability = (typeof CAPABILITIES)[number]; @@ -80,6 +81,11 @@ export const CAPABILITY_LABELS: Record< title: "Team Bots", description: "People may publish a Bot for the whole team to use.", }, + ownerApps: { + title: "Give apps to their own Bots", + description: + "People may let Bots they own use apps they connected themselves.", + }, }; /** @@ -97,6 +103,7 @@ export const DEFAULT_CAPABILITIES: Record = { passwordManager: true, slackTeams: true, teamBots: true, + ownerApps: true, }; export type CapabilityRow = { diff --git a/server/src/audit.ts b/server/src/audit.ts index 441c2f5902..44939b23be 100644 --- a/server/src/audit.ts +++ b/server/src/audit.ts @@ -280,6 +280,14 @@ export const auditEventTypes = [ "app_access.approved", "app_access.dismissed", "app_access.refused", + /* + * A Bot's owner giving one of their own apps to their own Bot, or taking it back, without an + * administrator (`plugins/owner-grants.ts`). Target the app key; payload `app`, `bot`, and the + * refs given or taken. Each ref also has its own `plugin_granted` / `plugin_revoked` row marked + * `grantedAs: owner`. + */ + "app_access.owner_granted", + "app_access.owner_revoked", // Every action a Bot takes on its computer, allowed or refused. Both, always: a trail that records // only what was permitted cannot answer whether the Bot tried. "computer.action_allowed", diff --git a/server/src/plugins/app-access-tool.ts b/server/src/plugins/app-access-tool.ts index 3c9a60fdf9..6f06b8b666 100644 --- a/server/src/plugins/app-access-tool.ts +++ b/server/src/plugins/app-access-tool.ts @@ -5,6 +5,7 @@ import { import type { GrantedTool } from "@openbot/toolkit/granted-tool"; import { z } from "zod"; import type { AppAccess, AppAccessState } from "./app-access"; +import { ownerAppsAllowed } from "./owner-grants"; import type { AppDirectory, SharedAudienceCheck, @@ -74,6 +75,9 @@ export function appAccessTools(options: { ...(options.sharedAudience ? { sharedAudience: options.sharedAudience } : {}), + ownerGrantsAllowed: options.actorId + ? await ownerAppsAllowed(options.actorId) + : false, }); /* * No card for an app nobody can name. A card about it would have nothing to ask the server @@ -116,6 +120,7 @@ function toldTheModel(access: AppAccess): string { shared_not_approved: `${name} uses one shared account for this deployment, and you reach more people than an administrator approved for it, so its calls are refused. Only an administrator can approve that; the card in front of the person says so.`, disabled: `${name} is switched off on this deployment. The card in front of the person lets them ask an administrator to switch it on.`, not_granted: `${name} is switched on, and you have not been granted it. The card in front of the person lets them ask an administrator to grant it to you.`, + owner_grantable: `${name} is switched on, and you have not been given it. This person owns you and can give it to you themselves: the card in front of them asks whether to let you use their ${name}. Ask them to try again once they have.`, }; return `${said[access.state]} Do not browse to its website or ask them to sign in there.`; } diff --git a/server/src/plugins/app-access.ts b/server/src/plugins/app-access.ts index 749fe5a826..276eb659b0 100644 --- a/server/src/plugins/app-access.ts +++ b/server/src/plugins/app-access.ts @@ -25,7 +25,7 @@ import type { CatalogueEntry } from "./catalogue"; */ /** - * The seven things a person can be told about one app on one Bot. + * The eight things a person can be told about one app on one Bot. * * `ready` — usable now, as far as this deployment knows. * `not_connected` — enabled and held, and this app is used under each person's own account, which @@ -40,6 +40,9 @@ import type { CatalogueEntry } from "./catalogue"; * `disabled` — switched off at the deployment: no row, the broker not configured, or an app that * still needs something only an administrator can supply. * `not_granted` — enabled, and this Bot holds none of its tools. Only an administrator can grant. + * `owner_grantable` — enabled, this Bot holds none of its tools, and the person looking owns the Bot + * and may give it the app themselves, because the app's rule ({@link ownerGrantsOf}) lets owners + * decide. Theirs to do, so nothing is filed. */ export const APP_ACCESS_STATES = [ "ready", @@ -49,6 +52,7 @@ export const APP_ACCESS_STATES = [ "shared_not_approved", "disabled", "not_granted", + "owner_grantable", ] as const; export type AppAccessState = (typeof APP_ACCESS_STATES)[number]; @@ -74,8 +78,15 @@ export const REQUEST_KIND_FOR: Record< shared_not_approved: null, disabled: "enable", not_granted: "grant", + owner_grantable: null, }; +/** + * Who may give an app to a Bot: the Bot's owner, for their own Bot (`any`), or only an + * administrator (`admin`). See `mcp_servers.owner_grants`. + */ +export type OwnerGrants = "any" | "admin"; + /** Whose account the app's calls land in. */ export type AppAccount = /** Each person's own, which each person connects. */ @@ -136,6 +147,19 @@ export type AppAccess = { asksFirst: boolean; /** Whether this Bot holds any of the app's tools. */ granted: boolean; + /** Who may give the app to a Bot, with the default for its account filled in. */ + ownerGrants: OwnerGrants; + /** + * Whether every grant this Bot holds on the app was made by the Bot's owner, and so may be taken + * back by them too. False where the Bot holds nothing, or where an administrator gave any of it. + */ + givenByOwner: boolean; + /** + * Whether giving the app to the Bot (`owner_grantable`) has to start with connecting the person's + * own account, because the app runs on it and they have none, or one the vendor refused. False in + * every other state. + */ + needsConnect: boolean; /** * When the account this app runs in for this person was connected — their own, or the team's for * a Shared app — and null where none is, or where no account is involved. @@ -174,6 +198,14 @@ export type AppAccessFacts = { /** Whether the row holds a credential: the OAuth client, or the deployment's token. */ hasCredential: boolean; granted: boolean; + /** The app's `owner_grants` column, null where an administrator never chose. */ + ownerGrants: OwnerGrants | null; + /** + * Whether the person asking may give their own apps to this Bot: they own it (a Bot with no owner + * — a package's public Bot — is nobody's to give to), and no administrator has switched the + * feature off for them. Whether THIS app may be given is {@link ownerGrantsOf}'s answer, not this. + */ + mayGiveOwn: boolean; /** This person's own connection, or null where they have none. */ personal: { usable: boolean } | null; /** The deployment's account at a Shared app, or null where none is connected. */ @@ -185,8 +217,14 @@ export type AppAccessFacts = { sharedAdmitted: boolean | null; }; +/** The facts that say whose account an app runs on, and so what its owner rule defaults to. */ +type AccountFacts = Pick< + AppAccessFacts, + "provider" | "authScheme" | "accountMode" | "catalogueAuth" +>; + /** Whose account, from the same facts `accessFor` resolves a call with. */ -export function accountOf(facts: AppAccessFacts): AppAccount { +export function accountOf(facts: AccountFacts): AppAccount { if (brokeredProvenance(facts.provider)) { if (schemeKind(facts.authScheme) === "none") return "deployment"; return facts.accountMode === "shared" ? "shared" : "personal"; @@ -198,6 +236,26 @@ export function accountOf(facts: AppAccessFacts): AppAccount { return "deployment"; } +/** + * Who may give this app to a Bot: what an administrator chose, or the default for its account. + * + * OWNERS DECIDE BY DEFAULT ONLY WHERE THE APP RUNS ON THE ASKER'S OWN ACCOUNT. A Bot given somebody's + * Gmail by its owner reads, for anybody else who talks to it, that person's own Gmail and nothing + * more — so letting the owner decide hands nobody anything they did not already have. The built-in + * Routines have no account at all and run as whoever scheduled them, the same shape, and "every + * morning…" has to work on a Bot made five minutes ago. Everything else reaches one account that is + * not the asker's — the deployment's token, a Shared team account, a server that needs no credential + * and so answers everybody alike — and giving a Bot that is an administrator's decision until an + * administrator says otherwise. + */ +export function ownerGrantsOf( + facts: AccountFacts & { ownerGrants: OwnerGrants | null }, +): OwnerGrants { + if (facts.ownerGrants) return facts.ownerGrants; + if (facts.catalogueAuth?.kind === "builtin") return "any"; + return accountOf(facts) === "personal" ? "any" : "admin"; +} + export function connectOf(facts: AppAccessFacts): AppConnect { if (brokeredProvenance(facts.provider)) { const kind = schemeKind(facts.authScheme); @@ -256,7 +314,10 @@ export function unavailableReason(facts: AppAccessFacts): Unavailable | null { */ export function appAccessState(facts: AppAccessFacts): AppAccessState { if (unavailableReason(facts) !== null) return "disabled"; - if (!facts.granted) return "not_granted"; + if (!facts.granted) + return facts.mayGiveOwn && ownerGrantsOf(facts) === "any" + ? "owner_grantable" + : "not_granted"; const account = accountOf(facts); if (account === "deployment") return "ready"; if (account === "shared") { diff --git a/server/src/plugins/owner-grants.ts b/server/src/plugins/owner-grants.ts new file mode 100644 index 0000000000..4852f4ac38 --- /dev/null +++ b/server/src/plugins/owner-grants.ts @@ -0,0 +1,59 @@ +import { capabilityRefusal } from "../admin/capabilities"; +import { enterpriseControls } from "../admin/controls"; +import type { AppInspection } from "./plugin-store/app-access"; +import type { PluginStore } from "./store"; + +/** + * A Bot's owner giving one of their own apps to their own Bot, without an administrator. + * + * WHY THIS IS NOT THE ADMINISTRATOR'S GRANT WITH THE CHECK TAKEN OFF. Three things differ, and each + * is the reason the other two are safe: + * + * - WHICH APPS. Only one an administrator has added, switched on and opened to owners + * (`mcp_servers.owner_grants`, read through `ownerGrantsOf`). By default that is an app every + * person connects for themselves, and the built-in Routines. An owner never adds or switches on an + * app; that stays the administrator's. + * - WHICH BOTS. Only one the person owns. A Bot nobody owns — a package's public Bot — reaches + * everybody on behalf of nobody, and stays an administrator's to equip. + * - WHOSE ACCOUNT. The grant is marked `granted_as = owner`, and a call made under it always uses + * the asking person's own account, even on the owner's published Team Bot. See + * `credentialActorFor`. Publishing a Bot must not hand teammates the owner's mailbox. + * + * And an administrator can switch the whole thing off for anybody through the `ownerApps` + * capability, which every check here reads. + */ + +/** Whether an administrator left this person able to give their own apps to their own Bots. */ +export async function ownerAppsAllowed(userId: string): Promise { + return ( + (await enterpriseControls()?.capabilityFor(userId, "ownerApps")) ?? true + ); +} + +/** + * Why this person may not give this app to this Bot themselves, or null when they may. The sentence + * is the one they read. + * + * ASKED OF THE INSPECTION, NOT RE-DERIVED. Whether the app is switched on and who it is open to are + * the same answers the card and the Bot page drew; the Bot's owner is read here again because a 403 + * is decided on what is true now, not on what the page loaded a minute ago. + */ +export async function ownerGrantRefusal( + store: Pick, + actorId: string, + agentId: string, + inspected: AppInspection, +): Promise { + const name = inspected.access.displayName; + if ( + !(await store.agentIsRegistered(agentId)) || + (await store.agentOwner(agentId)) !== actorId + ) + return "You can only give apps to Bots you own."; + if (!(await ownerAppsAllowed(actorId))) return capabilityRefusal("ownerApps"); + if (inspected.unavailable !== null || !inspected.answeringId) + return `${name} isn't turned on yet. Only an admin can turn it on.`; + if (inspected.access.ownerGrants !== "any") + return `Only an admin can give ${name} to a Bot.`; + return null; +} diff --git a/server/src/plugins/plugin-store/app-access.ts b/server/src/plugins/plugin-store/app-access.ts index 3ab22b6005..dc7a9435fd 100644 --- a/server/src/plugins/plugin-store/app-access.ts +++ b/server/src/plugins/plugin-store/app-access.ts @@ -35,12 +35,19 @@ import { accountOf, appAccessState, connectOf, + type OwnerGrants, + ownerGrantsOf, REQUEST_KIND_FOR, type Unavailable, unavailableReason, } from "../app-access"; import type { BrokerApp } from "../broker"; -import { CATALOGUE, type CatalogueEntry, catalogueEntry } from "../catalogue"; +import { + CATALOGUE, + type CatalogueEntry, + catalogueEntry, + classifyTool, +} from "../catalogue"; import type { StoreContext } from "./context"; import { iso } from "./model"; @@ -66,6 +73,8 @@ type AppCandidate = { authScheme: string | null; accountMode: "personal" | "shared" | null; hasCredential: boolean; + /** The answering row's `owner_grants`, null where nobody chose or there is no row. */ + ownerGrants: OwnerGrants | null; /** * Whether anything but the caller's own word says this app exists: a row, a catalogue entry, or * the vendor's directory. False only for an id in the broker's namespace (`composio-`, @@ -84,8 +93,15 @@ type ServerRow = { authScheme: string | null; accountMode: "personal" | "shared" | null; credentialId: string | null; + ownerGrants: OwnerGrants | null; }; +/** An app a person could give their own Bots. See `giveableApps`. */ +export type GiveableApp = Pick< + AppAccess, + "key" | "displayName" | "logo" | "account" +>; + /** The directory, asked only to name an app this deployment has no row for. */ export type AppDirectory = () => Promise; @@ -96,6 +112,8 @@ export type AppInspection = { unavailable: Unavailable | null; /** The row a grant is written against, and null for an app with no row. */ answeringId: string | null; + /** Every row that is this app, which is where the grants an owner takes back may be. */ + rowIds: string[]; /** The app at the broker, for a brokered one. */ toolkit: string | null; /** The catalogue entry, for an app this deployment ships. */ @@ -189,6 +207,7 @@ export function appAccessMethods(context: StoreContext) { authScheme: mcpServers.authScheme, accountMode: mcpServers.accountMode, credentialId: mcpServers.credentialId, + ownerGrants: mcpServers.ownerGrants, }) .from(mcpServers) .orderBy(asc(mcpServers.id)); @@ -240,6 +259,7 @@ export function appAccessMethods(context: StoreContext) { authScheme: toolkit ? (head?.authScheme ?? null) : source.authScheme, accountMode: toolkit ? (head?.accountMode ?? null) : null, hasCredential: source.credentialId !== null, + ownerGrants: source.ownerGrants, vouched: true, }; apps.set(key, app); @@ -279,6 +299,7 @@ export function appAccessMethods(context: StoreContext) { authScheme: null, accountMode: null, hasCredential: false, + ownerGrants: null, vouched: input.vouched ?? true, }; } @@ -405,17 +426,49 @@ export function appAccessMethods(context: StoreContext) { return absentFor(isBrokeredId(lower) ? lower : asked, undefined, listed); } - /** The server halves of a Bot's MCP grants. */ - async function grantedServerIds(agentId: string): Promise> { + /** + * The server halves of a Bot's MCP grants, each with whether any of its grants an administrator + * made. A server whose every grant is the owner's reads `false`: see `plugin_grants.granted_as`. + */ + async function grantsByServer( + agentId: string, + ): Promise> { const grants = await database - .select({ ref: pluginGrants.ref }) + .select({ ref: pluginGrants.ref, grantedAs: pluginGrants.grantedAs }) .from(pluginGrants) .where( and(eq(pluginGrants.kind, "mcp"), eq(pluginGrants.agentId, agentId)), ); - return new Set( - grants.map((grant) => grant.ref.split("/")[0] ?? "").filter(Boolean), - ); + const servers = new Map(); + for (const grant of grants) { + const serverId = grant.ref.split("/")[0] ?? ""; + if (!serverId) continue; + const byAdmin = grant.grantedAs !== "owner"; + servers.set(serverId, { + byAdmin: byAdmin || (servers.get(serverId)?.byAdmin ?? false), + }); + } + return servers; + } + + /** The server halves of a Bot's MCP grants. */ + async function grantedServerIds(agentId: string): Promise> { + return new Set((await grantsByServer(agentId)).keys()); + } + + /** Who owns a Bot that still stands: a person's id, or null for one nobody owns or none at all. */ + async function botOwnerOf(agentId: string): Promise { + const [row] = await database + .select({ ownerUserId: agentProfiles.ownerUserId }) + .from(agentProfiles) + .where( + and( + eq(agentProfiles.agentId, agentId), + isNull(agentProfiles.deletedAt), + ), + ) + .limit(1); + return row?.ownerUserId ?? null; } /** @@ -605,6 +658,13 @@ export function appAccessMethods(context: StoreContext) { * otherwise on the person's own, after they allow it. So the owner's connection decides the state * there, and a teammate is never offered a Connect that changes nothing, nor told `ready` over a * call that will stop to ask them first without saying so. + * + * EXCEPT WHERE THE OWNER GAVE THE APP THEMSELVES. A grant the Bot's owner made always runs on the + * asking person's own account (see `plugin_grants.granted_as`), so where every grant this Bot holds + * on the app is the owner's, the owner's connection is not asked about and the person's own is. + * + * `ownerGrantsAllowed` is whether an administrator left this person able to give their own apps + * to their own Bots at all — the `ownerApps` capability — and true where nobody asked. */ async function describe( apps: AppCandidate[], @@ -613,13 +673,18 @@ export function appAccessMethods(context: StoreContext) { agentId: string; composioConfigured: boolean; sharedAudience?: SharedAudienceCheck; + ownerGrantsAllowed?: boolean; }, ): Promise { const { userId } = input; const owner = userId ? await teamBotOwnerFor(input.agentId, userId) : null; - const [granted, connections, ownerConnections, consented, pending] = + const mayGiveOwn = + userId !== null && + input.ownerGrantsAllowed !== false && + (await botOwnerOf(input.agentId)) === userId; + const [grants, connections, ownerConnections, consented, pending] = await Promise.all([ - grantedServerIds(input.agentId), + grantsByServer(input.agentId), connectionFacts(apps, userId), owner ? connectionFacts(apps, owner) : null, owner && userId @@ -647,14 +712,21 @@ export function appAccessMethods(context: StoreContext) { return Promise.all( apps.map(async (app) => { const ids = [app.key, ...app.rowIds]; + const held = ids.flatMap((id) => { + const grant = grants.get(id); + return grant ? [grant] : []; + }); + const givenByOwner = + held.length > 0 && held.every((grant) => !grant.byAdmin); const teamAccount = app.toolkit ? connections.deploymentBrokered.get(app.toolkit) : undefined; const sharedUsable = teamAccount?.usable === true; const own = personalIn(connections, app); - const owners = ownerConnections - ? personalIn(ownerConnections, app) - : undefined; + const owners = + ownerConnections && !givenByOwner + ? personalIn(ownerConnections, app) + : undefined; const personal = owners ?? own; const known: AppAccessFacts = { provider: app.provider, @@ -664,7 +736,9 @@ export function appAccessMethods(context: StoreContext) { accountMode: app.accountMode, catalogueAuth: app.entry?.auth ?? null, hasCredential: app.hasCredential, - granted: ids.some((id) => granted.has(id)), + granted: held.length > 0, + ownerGrants: app.ownerGrants, + mayGiveOwn, personal: personal === undefined ? null : { usable: personal.usable }, shared: teamAccount ? { usable: sharedUsable } : null, sharedAdmitted: null, @@ -722,6 +796,12 @@ export function appAccessMethods(context: StoreContext) { owner !== null && !ids.some((id) => consented.has(id)), granted: facts.granted, + ownerGrants: ownerGrantsOf(facts), + givenByOwner, + needsConnect: + state === "owner_grantable" && + account === "personal" && + !facts.personal?.usable, connectedAt: account === "shared" ? (teamAccount?.connectedAt ?? null) @@ -739,6 +819,7 @@ export function appAccessMethods(context: StoreContext) { }, unavailable: unavailableReason(facts), answeringId: app.answeringId, + rowIds: app.rowIds, toolkit: app.toolkit, entry: app.entry, sharedUsable, @@ -836,6 +917,7 @@ export function appAccessMethods(context: StoreContext) { composioConfigured: boolean; directory?: AppDirectory; sharedAudience?: SharedAudienceCheck; + ownerGrantsAllowed?: boolean; }): Promise { const granted = await grantedServerIds(input.agentId); if (granted.size === 0) return []; @@ -878,6 +960,7 @@ export function appAccessMethods(context: StoreContext) { composioConfigured: boolean; directory?: AppDirectory; sharedAudience?: SharedAudienceCheck; + ownerGrantsAllowed?: boolean; }): Promise { const candidate = await candidateFor( input.app, @@ -889,11 +972,104 @@ export function appAccessMethods(context: StoreContext) { return described ?? null; }, + /** + * The apps this person could give their own Bots, as the New Bot sheet and a Bot's page offer + * them: switched on and usable, opened to owners ({@link ownerGrantsOf}), and — for an app that + * runs on each person's own account — connected by this person with a key nobody refused. + * + * NOT ABOUT ANY ONE BOT. Whether a given Bot is theirs to give to is asked where it is given; this + * is the list of what they hold. Sorted by name. + */ + async giveableApps(input: { + userId: string; + composioConfigured: boolean; + }): Promise { + const apps = [...new Set((await appsByRow(await allRows())).values())]; + const connections = await connectionFacts(apps, input.userId); + const giveable: GiveableApp[] = []; + for (const app of apps) { + const own = + brokeredProvenance(app.provider) && app.toolkit + ? connections.personalBrokered.get(app.toolkit) + : app.provider === "catalogue" + ? app.rowIds + .map((id) => connections.personalHeld.get(id)) + .find((value) => value !== undefined) + : undefined; + const facts: AppAccessFacts = { + provider: app.provider, + present: app.present, + composioConfigured: input.composioConfigured, + authScheme: app.authScheme, + accountMode: app.accountMode, + catalogueAuth: app.entry?.auth ?? null, + hasCredential: app.hasCredential, + granted: false, + ownerGrants: app.ownerGrants, + mayGiveOwn: true, + personal: own ? { usable: own.usable } : null, + shared: null, + sharedAdmitted: null, + }; + if (unavailableReason(facts) !== null) continue; + if (ownerGrantsOf(facts) !== "any") continue; + const account = accountOf(facts); + if (account === "personal" && !own?.usable) continue; + giveable.push({ + key: app.key, + displayName: app.title, + logo: app.logo, + account, + }); + } + return giveable.sort((left, right) => + left.displayName.localeCompare(right.displayName), + ); + }, + + /** + * Who may give an app to a Bot, as an administrator chose it, or null to put the default for its + * account back. Written on every row at the app's url, so a duplicate row answers the same, and + * recorded in the trail. Answers false for an app with no row. + */ + async setOwnerGrants(input: { + serverId: string; + ownerGrants: OwnerGrants | null; + by: string; + }): Promise { + const [row] = await database + .select({ url: mcpServers.url, ownerGrants: mcpServers.ownerGrants }) + .from(mcpServers) + .where(eq(mcpServers.id, input.serverId)) + .limit(1); + if (!row) return false; + await database + .update(mcpServers) + .set({ ownerGrants: input.ownerGrants, updatedAt: new Date() }) + .where(eq(mcpServers.url, row.url)); + await recordAuditEvent(auditStore, { + eventType: "configuration.changed", + targetType: "mcp_server", + targetId: input.serverId, + payload: { + actor: input.by, + change: "owner_grants_changed", + from: row.ownerGrants, + to: input.ownerGrants, + }, + }); + return true; + }, + /** * Every tool the app's answering row advertises, as grant refs, and whether each is a read. * - * `read` IS THE EXACT WORD AND NOTHING ELSE, as `classifyTool` reads `effect`: a null or an - * unrecognised value is a write, so a tool nobody classified is never handed out as a read. + * A READ IS WHAT `classifyTool` CALLS ONE, the same answer the call gate and the Plugins page + * give. For a brokered or custom app that is the exact word `read` in `effect` and nothing + * else, so a tool nobody classified is never handed out as a read. For an app this deployment + * ships it is also anything advertised that the reviewed entry does not list as a write — which + * this used to miss, reading `effect` alone, so a one-press approval of a built-in app whose + * listing records no effect found no reads at all and refused. */ async appTools( serverId: string, @@ -903,9 +1079,10 @@ export function appAccessMethods(context: StoreContext) { .from(mcpTools) .where(eq(mcpTools.serverId, serverId)) .orderBy(asc(mcpTools.name)); + const entry = catalogueEntry(serverId); return rows.map((row) => ({ ref: `${serverId}/${row.name}`, - read: row.effect === "read", + read: classifyTool(entry, row.name, true, row.effect) === "read", })); }, @@ -1079,7 +1256,9 @@ export function appAccessMethods(context: StoreContext) { | "app_access.cancelled" | "app_access.approved" | "app_access.dismissed" - | "app_access.refused"; + | "app_access.refused" + | "app_access.owner_granted" + | "app_access.owner_revoked"; }, ): Promise { await recordAuditEvent(auditStore, { diff --git a/server/src/plugins/plugin-store/grants.ts b/server/src/plugins/plugin-store/grants.ts index 86e8f6fd39..bb6b285a3a 100644 --- a/server/src/plugins/plugin-store/grants.ts +++ b/server/src/plugins/plugin-store/grants.ts @@ -13,18 +13,30 @@ import { export function grantMethods(context: StoreContext) { const { database, auditStore, toolsDeclaredBy } = context; return { + /** + * Give a Bot one thing, as an administrator or — `as: "owner"` — as the Bot's owner giving it one + * of their own apps. See `plugin_grants.granted_as` for what the difference decides. + * + * AN OWNER'S GRANT NEVER OVERWRITES AN ADMINISTRATOR'S. An administrator's grant of the same + * action already stands for a reason the owner did not give, and turning it into the owner's + * would also change whose account it uses. So the owner's insert does nothing on a conflict. An + * administrator granting over an owner's row makes it theirs: that is an administrator deciding. + */ async grant( kind: PluginKind, ref: string, agentId: string, by: string, + as: "admin" | "owner" = "admin", ): Promise { - await database + const insert = database .insert(pluginGrants) - .values({ kind, ref, agentId, grantedBy: by }) - .onConflictDoUpdate({ + .values({ kind, ref, agentId, grantedBy: by, grantedAs: as }); + if (as === "owner") await insert.onConflictDoNothing(); + else + await insert.onConflictDoUpdate({ target: [pluginGrants.kind, pluginGrants.ref, pluginGrants.agentId], - set: { grantedBy: by, updatedAt: new Date() }, + set: { grantedBy: by, grantedAs: "admin", updatedAt: new Date() }, }); await recordAuditEvent(auditStore, { @@ -37,10 +49,56 @@ export function grantMethods(context: StoreContext) { kind, ref, bot: agentId, + ...(as === "owner" ? { grantedAs: "owner" } : {}), }, }); }, + /** + * Take back what the Bot's owner gave it on one app: every grant on these server rows that the + * owner made, and none an administrator made. Answers the refs taken, each with its own trail + * row, as {@link revoke} writes one. + */ + async revokeOwnerGrants( + serverIds: string[], + agentId: string, + by: string, + ): Promise { + if (serverIds.length === 0) return []; + const owned = and( + eq(pluginGrants.kind, "mcp"), + eq(pluginGrants.agentId, agentId), + eq(pluginGrants.grantedAs, "owner"), + ); + const refs = ( + await database + .select({ ref: pluginGrants.ref }) + .from(pluginGrants) + .where(owned) + ) + .map((row) => row.ref) + .filter((ref) => serverIds.includes(ref.split("/")[0] ?? "")); + if (refs.length === 0) return []; + await database + .delete(pluginGrants) + .where(and(owned, inArray(pluginGrants.ref, refs))); + for (const ref of refs) + await recordAuditEvent(auditStore, { + eventType: "configuration.changed", + targetType: grantTargetType("mcp"), + targetId: ref, + payload: { + actor: by, + change: "plugin_revoked", + kind: "mcp", + ref, + bot: agentId, + grantedAs: "owner", + }, + }); + return refs; + }, + async revoke( kind: PluginKind, ref: string, diff --git a/server/src/plugins/plugin-store/model.ts b/server/src/plugins/plugin-store/model.ts index b961c4712a..1231729e7d 100644 --- a/server/src/plugins/plugin-store/model.ts +++ b/server/src/plugins/plugin-store/model.ts @@ -159,6 +159,13 @@ export type ServerRecord = { * connected would tell the page something that changes nothing a call does. */ sharedVendorConnected: boolean; + /** + * Who may give this app to a Bot, with the default for its account filled in: any Bot's owner + * (`any`), or only an administrator (`admin`). See `ownerGrantsOf`. + */ + ownerGrants: "any" | "admin"; + /** What an administrator chose, or null where the default above is in force. */ + ownerGrantsChosen: "any" | "admin" | null; tools: ToolRecord[]; /** * Grants on tools this server no longer advertises. diff --git a/server/src/plugins/plugin-store/servers.ts b/server/src/plugins/plugin-store/servers.ts index 521b934ec9..3532ea7094 100644 --- a/server/src/plugins/plugin-store/servers.ts +++ b/server/src/plugins/plugin-store/servers.ts @@ -14,6 +14,7 @@ import { pluginGrants, } from "../../db/schema"; import { BROKERED_PROFILE, brokeredProvenance, toolkitOfFor } from "../access"; +import { ownerGrantsOf } from "../app-access"; import { type BrokerConnection, BrokerUnconfiguredError, @@ -2004,6 +2005,18 @@ export function serverMethods(context: StoreContext) { toolkit !== null && brokeredApps.get(row.url)?.accountMode === "shared" && deploymentHeld.has(toolkit), + ownerGrants: ownerGrantsOf({ + provider: toolkit ? brokerProvider : entry ? "catalogue" : "custom", + authScheme: toolkit + ? (brokeredApps.get(row.url)?.authScheme ?? null) + : row.authScheme, + accountMode: toolkit + ? (brokeredApps.get(row.url)?.accountMode ?? null) + : row.accountMode, + catalogueAuth: entry?.auth ?? null, + ownerGrants: row.ownerGrants, + }), + ownerGrantsChosen: row.ownerGrants, tools: tools .filter((tool) => tool.serverId === row.id) .map((tool) => { diff --git a/server/tests/app-access-routes.integration.test.ts b/server/tests/app-access-routes.integration.test.ts index 7870c44db7..feedeaf099 100644 --- a/server/tests/app-access-routes.integration.test.ts +++ b/server/tests/app-access-routes.integration.test.ts @@ -61,6 +61,13 @@ const id = (name: string) => `${name}-${suite}`; const person = id("person"); const other = id("other"); const admin = id("admin"); +/** + * Who owns the suite's Bots: nobody the requests below are filed as. A Bot's owner may give it one + * of their own apps without asking anybody (`owner_grantable`), so a person asking an administrator + * for a grant is, here, asking about somebody else's Bot. `owner-app-grants.integration.test.ts` + * covers the owner's side. + */ +const maker = id("maker"); const bot = id("bot"); const hidden = id("hidden"); @@ -327,7 +334,7 @@ async function clearApps() { } beforeAll(async () => { - for (const user of [person, other, admin]) + for (const user of [person, other, admin, maker]) await database.insert(users).values({ id: user, email: `${user}@example.test`, @@ -343,8 +350,9 @@ beforeAll(async () => { }); await database.insert(agentProfiles).values({ agentId: agent, - ownerUserId: person, - visibility: "private", + ownerUserId: maker, + // Public, so the people asking below are in the audience a Shared app's approval covers. + visibility: agent === bot ? "public" : "private", title: "Helper", roleDescription: "Helper", avatarSeed: "x", @@ -362,7 +370,9 @@ beforeEach(async () => { afterAll(async () => { await clearApps(); await database.delete(agents).where(inArray(agents.id, [bot, hidden])); - await database.delete(users).where(inArray(users.id, [person, other, admin])); + await database + .delete(users) + .where(inArray(users.id, [person, other, admin, maker])); }); describe("what each app a Bot holds is, for the person asking", () => { @@ -1577,7 +1587,7 @@ describe("a teammate on somebody else's Team Bot", () => { beforeEach(async () => { await database .insert(teamBotPublications) - .values({ agentId: bot, publishedBy: person, audience: "team" }); + .values({ agentId: bot, publishedBy: maker, audience: "team" }); await seedApp({ key: GMAIL, title: "Gmail" }); }); const connect = ( @@ -1601,7 +1611,7 @@ describe("a teammate on somebody else's Team Bot", () => { ).app; test("the owner's connection is the one their calls use", async () => { - await connect(person); + await connect(maker); expect(await asTeammate()).toMatchObject({ state: "ready", accountHolder: "owner", @@ -1610,7 +1620,7 @@ describe("a teammate on somebody else's Team Bot", () => { }); test("the owner's refused connection is theirs to reconnect, not the teammate's", async () => { - await connect(person, { verified: false, probeAction: "GMAIL_LIST" }); + await connect(maker, { verified: false, probeAction: "GMAIL_LIST" }); await connect(other); expect(await asTeammate()).toMatchObject({ state: "reconnect", @@ -1640,9 +1650,9 @@ describe("a teammate on somebody else's Team Bot", () => { }); test("the owner reads their own account, and is never asked", async () => { - await connect(person); + await connect(maker); const { app } = await json<{ app: AppAccess }>( - await appAs(person).request(`/api/plugins/for/${bot}/apps/${GMAIL}`), + await appAs(maker).request(`/api/plugins/for/${bot}/apps/${GMAIL}`), ); expect(app).toMatchObject({ state: "ready", diff --git a/server/tests/app-access-state.test.ts b/server/tests/app-access-state.test.ts index e2e4428b7a..06b4c34646 100644 --- a/server/tests/app-access-state.test.ts +++ b/server/tests/app-access-state.test.ts @@ -5,6 +5,7 @@ import { accountOf, appAccessState, connectOf, + ownerGrantsOf, REQUEST_KIND_FOR, unavailableReason, } from "../src/plugins/app-access"; @@ -27,6 +28,8 @@ const composio = (overrides: Partial = {}): AppAccessFacts => ({ catalogueAuth: null, hasCredential: false, granted: true, + ownerGrants: null, + mayGiveOwn: false, personal: null, shared: null, sharedAdmitted: null, @@ -51,6 +54,8 @@ const catalogue = ( catalogueAuth: auth, hasCredential: true, granted: true, + ownerGrants: null, + mayGiveOwn: false, personal: null, shared: null, sharedAdmitted: null, @@ -192,5 +197,59 @@ test("only the three states an administrator can fix file a request", () => { shared_not_approved: null, disabled: "enable", not_granted: "grant", + // The Bot's owner gives it the app themselves; there is nobody to ask. + owner_grantable: null, + }); +}); + +describe("who may give an app to a Bot", () => { + const routines = catalogueEntry("routines"); + if (!routines) throw new Error("The Routines entry is missing."); + + test("owners decide by default where the app runs on the asker's own account, and for Routines", () => { + expect(ownerGrantsOf(composio())).toBe("any"); + expect(ownerGrantsOf(catalogue(drive.auth))).toBe("any"); + expect(ownerGrantsOf(catalogue(routines.auth))).toBe("any"); + }); + + test("an admin decides by default for a team account, a deployment token and no account", () => { + expect(ownerGrantsOf(composio({ accountMode: "shared" }))).toBe("admin"); + expect( + ownerGrantsOf(composio({ authScheme: "NO_AUTH", accountMode: null })), + ).toBe("admin"); + expect(ownerGrantsOf(catalogue(parallel.auth))).toBe("admin"); + expect(ownerGrantsOf({ ...catalogue(null), provider: "custom" })).toBe( + "admin", + ); + }); + + test("what an administrator chose wins over the default, either way", () => { + expect(ownerGrantsOf(composio({ ownerGrants: "admin" }))).toBe("admin"); + expect( + ownerGrantsOf(composio({ accountMode: "shared", ownerGrants: "any" })), + ).toBe("any"); + }); + + test("an app the Bot does not hold is the owner's to give only where both say so", () => { + const notHeld = { granted: false }; + expect(appAccessState(composio({ ...notHeld, mayGiveOwn: true }))).toBe( + "owner_grantable", + ); + // Not their Bot, or the feature switched off for them. + expect(appAccessState(composio({ ...notHeld, mayGiveOwn: false }))).toBe( + "not_granted", + ); + // Their Bot, and an app an administrator keeps for themselves. + expect( + appAccessState( + composio({ ...notHeld, mayGiveOwn: true, ownerGrants: "admin" }), + ), + ).toBe("not_granted"); + // Switched off is still switched off: an owner never turns an app on. + expect( + appAccessState( + composio({ ...notHeld, mayGiveOwn: true, present: false }), + ), + ).toBe("disabled"); }); }); diff --git a/server/tests/app-access-tool.test.ts b/server/tests/app-access-tool.test.ts index d3a31f7a10..38b6c28fc1 100644 --- a/server/tests/app-access-tool.test.ts +++ b/server/tests/app-access-tool.test.ts @@ -32,7 +32,13 @@ function access(state: AppAccessState): AppAccess { unavailable: state === "disabled" ? "absent" : null, accountHolder: "you", asksFirst: false, - granted: state !== "not_granted" && state !== "disabled", + granted: + state !== "not_granted" && + state !== "disabled" && + state !== "owner_grantable", + ownerGrants: "any", + givenByOwner: false, + needsConnect: false, connectedAt: null, requestKind: null, request: null, @@ -72,6 +78,7 @@ describe("show_app_access", () => { agentId: "expenses", userId: "usr_7", composioConfigured: true, + ownerGrantsAllowed: true, }, ]); }); @@ -83,6 +90,7 @@ describe("show_app_access", () => { ["shared_not_connected", /ask an administrator for it/], ["disabled", /switched off on this deployment/], ["not_granted", /you have not been granted it/], + ["owner_grantable", /can give it to you themselves/], ] as const)( "answers %s with the card marker and the state in words", async (state, said) => { diff --git a/server/tests/plugin-routes.test.ts b/server/tests/plugin-routes.test.ts index 94bdb0e23d..c468d29b8f 100644 --- a/server/tests/plugin-routes.test.ts +++ b/server/tests/plugin-routes.test.ts @@ -140,6 +140,8 @@ function serverRecord(row: { id: string; url: string }): ServerRecord { authScheme: null, accountMode: null, sharedVendorConnected: false, + ownerGrants: "admin", + ownerGrantsChosen: null, tools: [], withdrawn: [], }; From 919c7f095ce5cb794ce4a8ff560826e3839bc85a Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 12:27:25 -0300 Subject: [PATCH 03/19] Let a Bot's owner give it their own apps, and take back only what they gave --- server/src/plugins/route-groups/app-access.ts | 191 +++++++++++++++++- server/src/plugins/route-groups/grants.ts | 66 +++++- 2 files changed, 252 insertions(+), 5 deletions(-) diff --git a/server/src/plugins/route-groups/app-access.ts b/server/src/plugins/route-groups/app-access.ts index bb99c6e35c..f957345155 100644 --- a/server/src/plugins/route-groups/app-access.ts +++ b/server/src/plugins/route-groups/app-access.ts @@ -4,9 +4,11 @@ import { BROKERED_PROFILE, type BrokerProvider } from "../access"; import type { AppAccess, AppAccessRequestKind, + OwnerGrants, Unavailable, } from "../app-access"; import type { CatalogueEntry } from "../catalogue"; +import { ownerAppsAllowed, ownerGrantRefusal } from "../owner-grants"; import type { AppAccessRequestRecord, AppDirectory, @@ -368,6 +370,7 @@ export function mountAppAccessRoutes({ composioConfigured, directory, sharedAudience, + ownerGrantsAllowed: await ownerAppsAllowed(context.var.actor.id), }), }); }); @@ -391,6 +394,7 @@ export function mountAppAccessRoutes({ composioConfigured, directory, sharedAudience, + ownerGrantsAllowed: await ownerAppsAllowed(context.var.actor.id), }); if (!inspected) return context.json({ error: `OpenBot has no app called ${app}.` }, 404); @@ -437,6 +441,7 @@ export function mountAppAccessRoutes({ composioConfigured, directory, sharedAudience, + ownerGrantsAllowed: await ownerAppsAllowed(context.var.actor.id), }); const inspected = found && (found.vouched || found.access.granted) ? found : null; @@ -457,7 +462,9 @@ export function mountAppAccessRoutes({ error: access.state === "ready" ? `${access.displayName} is ready to use. There's nothing to ask an admin for.` - : `${access.displayName} needs your own account, not an admin. Connect it yourself.`, + : access.state === "owner_grantable" + ? `This is your Bot, so you can give it ${access.displayName} yourself.` + : `${access.displayName} needs your own account, not an admin. Connect it yourself.`, status: 409, }); return { agentId, access, requestKind: access.requestKind }; @@ -580,6 +587,188 @@ export function mountAppAccessRoutes({ }, ); + /** + * The Bot and the app an owner's give or take-back names, inspected for this person: the Bot one + * they may use, the app one this deployment can name. The refusal otherwise. + */ + const ownersApp = async (context: RouteContext) => { + const agentId = context.req.param("agentId")?.trim() ?? ""; + const app = context.req.param("app")?.trim() ?? ""; + if ( + !agentId || + !app || + agentId.length > MAX_NAME || + app.length > MAX_NAME || + !(await canUseBot(context.var.actor, agentId)) + ) + return context.json({ error: "There is no such Bot." }, 404); + const found = await store.inspectApp({ + app, + agentId, + userId: context.var.actor.id, + composioConfigured, + directory, + sharedAudience, + ownerGrantsAllowed: await ownerAppsAllowed(context.var.actor.id), + }); + if (!found || !(found.vouched || found.access.granted)) + return context.json({ error: `OpenBot has no app called ${app}.` }, 404); + return { agentId, inspected: found }; + }; + + /** + * A Bot's owner giving it one of their own apps, as one press: the card's "Let Research Bot use + * your Gmail?", a switch on the Bot's page, a box left ticked on the New Bot sheet. + * + * WHAT MAY BE GIVEN IS {@link ownerGrantRefusal}'S ANSWER: their own Bot, an app an administrator + * switched on and opened to owners, and the `ownerApps` capability. Refused 403 with the sentence + * otherwise, and the refusal is in the trail. + * + * WHAT IS GIVEN IS WHAT AN ADMINISTRATOR'S ONE PRESS GIVES: the app's read-only actions, through + * the same `appTools` reading, so an owner never gets more by asking themselves than an + * administrator's approval would have given. Anything that sends or writes is still chosen action by + * action by an administrator. ONE EXCEPTION, the built-in Routines: every action it has keeps, + * changes or drops the asking person's own standing instructions and nothing else, and "every + * morning, …" is a write — given only its reads, a Bot could list routines and never make one. + * + * Every grant is `granted_as = owner`, which keeps each call on the asking person's own account + * (`credentialActorFor`). A Bot that already holds any of the app is answered with nothing given. + */ + routes.post("/for/:agentId/apps/:app/give", requireUser, async (context) => { + const found = await ownersApp(context); + if (found instanceof Response) return found; + const { agentId, inspected } = found; + const { access } = inspected; + const deny = (reason: string, error: string, status: 403 | 409) => + refused(context, { + app: access.key, + bot: agentId, + reason, + error, + status, + }); + const refusal = await ownerGrantRefusal( + store, + context.var.actor.id, + agentId, + inspected, + ); + if (refusal) return deny("owner_grant_refused", refusal, 403); + if (access.granted) return context.json({ granted: [] }); + const serverId = inspected.answeringId ?? ""; + const tools = await store.appTools(serverId); + if (tools.length === 0) + return deny( + "no_tools", + `${access.displayName} has nothing to give yet. Ask an admin to refresh it.`, + 409, + ); + const builtin = inspected.entry?.auth.kind === "builtin"; + const refs = tools + .filter((tool) => builtin || tool.read) + .map((tool) => tool.ref); + if (refs.length === 0) + return deny( + "no_read_only_tools", + `${access.displayName} has nothing a Bot can only read with. Ask an admin to choose what this Bot can do with it.`, + 409, + ); + for (const ref of refs) + await store.grant("mcp", ref, agentId, actorEmail(context), "owner"); + await store.recordAppAccessEvent({ + eventType: "app_access.owner_granted", + targetId: access.key, + actorUserId: context.var.actor.id, + payload: { app: access.key, bot: agentId, refs }, + }); + return context.json({ granted: refs }); + }); + + /** + * A Bot's owner taking back what they gave it on one app. Only the owner's own grants go: one an + * administrator made stays, because it was not the owner's to give. Still asks that the Bot is + * theirs; it does not ask whether the app is still open to owners, since taking away is always + * allowed (see `enablementRefusal`'s note on `intent`). + */ + routes.delete( + "/for/:agentId/apps/:app/give", + requireUser, + async (context) => { + const found = await ownersApp(context); + if (found instanceof Response) return found; + const { agentId, inspected } = found; + if ((await store.agentOwner(agentId)) !== context.var.actor.id) + return refused(context, { + app: inspected.access.key, + bot: agentId, + reason: "owner_revoke_refused", + error: "You can only change apps on Bots you own.", + status: 403, + }); + const ids = [ + ...new Set([ + inspected.access.key, + ...inspected.rowIds, + ...(inspected.answeringId ? [inspected.answeringId] : []), + ]), + ]; + const refs = await store.revokeOwnerGrants( + ids, + agentId, + actorEmail(context), + ); + if (refs.length > 0) + await store.recordAppAccessEvent({ + eventType: "app_access.owner_revoked", + targetId: inspected.access.key, + actorUserId: context.var.actor.id, + payload: { app: inspected.access.key, bot: agentId, refs }, + }); + return context.json({ revoked: refs }); + }, + ); + + /** + * The apps this person could give their own Bots: what the New Bot sheet offers ticked, and the + * switches on a Bot's page. Empty where an administrator switched the feature off for them. + */ + routes.get("/giveable", requireUser, async (context) => { + if (!(await ownerAppsAllowed(context.var.actor.id))) + return context.json({ apps: [] }); + return context.json({ + apps: await store.giveableApps({ + userId: context.var.actor.id, + composioConfigured, + }), + }); + }); + + /** + * Who may give an app to a Bot: any Bot's owner (`any`), only an administrator (`admin`), or the + * default for the app's account (`null`). Admin only, and in the trail. + */ + routes.put("/servers/:id/owner-grants", requireUser, async (context) => { + const forbidden = requireAdmin(context); + if (forbidden) return forbidden; + const body = (await context.req.json().catch(() => null)) as { + ownerGrants?: unknown; + } | null; + const value = body?.ownerGrants; + if (value !== null && value !== "any" && value !== "admin") + return context.json( + { error: "Choose any, admin, or null for the default." }, + 400, + ); + const changed = await store.setOwnerGrants({ + serverId: context.req.param("id"), + ownerGrants: value as OwnerGrants | null, + by: actorEmail(context), + }); + if (!changed) + return context.json({ error: "That app hasn't been added." }, 404); + return context.json({ ok: true }); + }); + /** The person's own pending requests, newest first. */ routes.get("/access-requests/mine", requireUser, async (context) => context.json({ diff --git a/server/src/plugins/route-groups/grants.ts b/server/src/plugins/route-groups/grants.ts index 94dd4011f8..61ebc11e98 100644 --- a/server/src/plugins/route-groups/grants.ts +++ b/server/src/plugins/route-groups/grants.ts @@ -5,6 +5,7 @@ import { exposureOf, type SharedUseApproval, } from "../shared-use"; +import { ownerGrantRefusal } from "../owner-grants"; import { CatalogueEntryUnknownError, isDeploymentFault, @@ -60,6 +61,7 @@ export function mountGrantRoutes({ requireUser, canUseBot, shared, + composio, actorEmail, skillActor, }: PluginRouteContext) { @@ -83,10 +85,42 @@ export function mountGrantRoutes({ ? (value as PluginKind) : null; + /** + * May this Bot's owner, who is not an administrator, give it this one action of an app? + * + * The Bot is asked about first, so somebody who does not own it learns nothing about the app from + * the answer. The ref has to name a row that is the app, not merely a word an app answers to. + */ + async function ownerMcpRefusal( + context: { var: AppVariables }, + ref: string, + agentId: string, + ): Promise { + const actorId = context.var.actor.id; + if ( + !(await store.agentIsRegistered(agentId)) || + (await store.agentOwner(agentId)) !== actorId + ) + return "Only an admin can choose which Bots use a tool."; + const [serverId] = ref.split("/"); + const inspected = serverId + ? await store.inspectApp({ + app: serverId, + agentId, + userId: actorId, + composioConfigured: Boolean(composio), + }) + : null; + if (!inspected?.rowIds.includes(serverId ?? "")) + return `${serverId} hasn't been added yet. Only an admin can add it.`; + return ownerGrantRefusal(store, actorId, agentId, inspected); + } + /** * May this person put this on that Bot? * - * MCP is an administrator's, always: it reaches another company's system with a stored credential. + * MCP is an administrator's, except one of the owner's own apps on their own Bot (see + * {@link ownerMcpRefusal}): it reaches another company's system with a stored credential. * A skill is an instruction, so somebody may put their own skill on a Bot they own, and neither * half alone is enough. Both are checked here rather than in the store, because this is the only * place that knows who is asking. @@ -111,7 +145,20 @@ export function mountGrantRoutes({ if (kind === "mcp") { if (!actor.isAdmin) { - return "Only an admin can choose which Bots use a tool."; + /* + * A BOT'S OWNER MAY GIVE IT ONE OF THEIR OWN APPS, and nothing else changes for anybody who + * is not an administrator. The owner rule — their Bot, an app an administrator switched on + * and opened to owners, the `ownerApps` capability — is {@link ownerGrantRefusal}'s, the + * same one the card's one press asks. The grant this allows is written `granted_as = owner` + * by the route below, which is what keeps it on the asker's own account. + * + * TAKING AWAY IS NOT RELAXED HERE. An owner takes back what they gave through + * `DELETE /for/:agentId/apps/:app/give`, which removes only the owner's own grants; this + * route's revoke would remove an administrator's as readily, so it stays theirs. + */ + if (intent === "revoke") + return "Only an admin can choose which Bots use a tool."; + return ownerMcpRefusal(context, ref, agentId); } // Taking something away is always allowed: see the note on `intent`. It matters more here // than anywhere else, because the rows this check exists to prevent are the same shape as the @@ -278,8 +325,13 @@ export function mountGrantRoutes({ * A SHARED APP'S ACTION IS GRANTED WITH WHO MAY USE IT, in the one request. See * {@link sharedGrantApproval} for what is recorded when none is sent. */ + /* + * An owner's grant records no Shared approval: who may reach a team account through a Bot is an + * administrator's decision, and the shared-use gate refuses the calls and files their request. + */ + const asOwner = kind === "mcp" && !skillActor(context).isAdmin; const decided = - kind === "mcp" + kind === "mcp" && !asOwner ? await sharedGrantApproval( { store, shared }, grantRef, @@ -293,7 +345,13 @@ export function mountGrantRoutes({ 400, ); } - await store.grant(kind, grantRef, grantAgentId, actorEmail(context)); + await store.grant( + kind, + grantRef, + grantAgentId, + actorEmail(context), + asOwner ? "owner" : "admin", + ); if (decided?.approval && shared) { await shared.use.setApproval({ botId: grantAgentId, From b87271e11c1b4d7c543979f7d75a347eca747dbe Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 12:27:25 -0300 Subject: [PATCH 04/19] Keep a Team Bot on each teammate's own account for apps its owner gave it --- server/src/team-bots/team-bots.ts | 28 +- .../owner-app-grants.integration.test.ts | 453 ++++++++++++++++++ 2 files changed, 479 insertions(+), 2 deletions(-) create mode 100644 server/tests/owner-app-grants.integration.test.ts diff --git a/server/src/team-bots/team-bots.ts b/server/src/team-bots/team-bots.ts index 40761ce329..f5fa91bab9 100644 --- a/server/src/team-bots/team-bots.ts +++ b/server/src/team-bots/team-bots.ts @@ -10,7 +10,8 @@ * membership-scoped, so the owner cannot read it and it cannot read theirs. Nothing here changes that. * * WHOSE ACCOUNTS. A Team Bot reaches a connected app as its OWNER when the owner has connected it, - * the same for everyone. A teammate's OWN account on a server the owner has not connected is used + * the same for everyone — for an app an ADMINISTRATOR gave it. An app the owner gave it themselves + * (`plugin_grants.granted_as = owner`) always runs on the asking teammate's own account. A teammate's OWN account on a server the owner has not connected is used * only with their consent: Allow once, Always allow for this Bot, or Skip. Everything else about the * call (grant, policy, approvals, audit) is still decided as the teammate who is asking. */ @@ -21,6 +22,7 @@ import type { AgentActor } from "../agents/profile-types"; import type { Database } from "../db/client"; import { agents, users } from "../db/schema/core"; import { agentProfiles } from "../db/schema/coworker"; +import { pluginGrants } from "../db/schema/plugins"; import { teamBotAssignments, teamBotAudience, @@ -153,6 +155,13 @@ export function createTeamBots(options: { * personal account on (a shared credential reaches the same place whoever asks). The owner, where * the owner connected it. The asker's own account only with a standing or one-time consent; * without one the call is refused with the card's marker, and nothing is sent. + * + * NEVER THE OWNER'S FOR A GRANT THE OWNER MADE. Where the owner gave the Bot this action of one of + * their own apps (`granted_as = owner`), the owner's connection is not asked about: the call goes + * on as though the owner had none, which is the asker's own account — with their consent, or + * refused to connect. An owner giving their Bot their Gmail decided about their own mailbox for + * their own use; publishing the Bot afterwards must not turn that into every teammate's access to + * it. Only an administrator's grant can put a Team Bot on its owner's account. */ credentialActorFor(actorId: string, botId: string) { return async (ref: string): Promise => { @@ -160,7 +169,22 @@ export function createTeamBots(options: { const owner = published?.ownerUserId; if (!owner || owner === actorId) return actorId; const serverId = ref.split("/")[0] ?? ""; - if ((await connectedServers(owner)).has(serverId)) return owner; + const [grant] = await database + .select({ grantedAs: pluginGrants.grantedAs }) + .from(pluginGrants) + .where( + and( + eq(pluginGrants.kind, "mcp"), + eq(pluginGrants.ref, ref), + eq(pluginGrants.agentId, botId), + ), + ) + .limit(1); + if ( + grant?.grantedAs !== "owner" && + (await connectedServers(owner)).has(serverId) + ) + return owner; if (!(await connectedServers(actorId)).has(serverId)) return actorId; const [consent] = await database .select({ decision: teamBotConsents.decision }) diff --git a/server/tests/owner-app-grants.integration.test.ts b/server/tests/owner-app-grants.integration.test.ts new file mode 100644 index 0000000000..b1319c9455 --- /dev/null +++ b/server/tests/owner-app-grants.integration.test.ts @@ -0,0 +1,453 @@ +// server/tests/owner-app-grants.integration.test.ts +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, +} from "bun:test"; +import { randomUUID } from "node:crypto"; +import { and, eq, inArray, like } from "drizzle-orm"; +import { Hono, type MiddlewareHandler } from "hono"; +import { createAgentProfileStore } from "../src/agents/profile-store"; +import type { AgentActor } from "../src/agents/profile-types"; +import type { AuditEventInput, AuditStore } from "../src/audit"; +import type { AppVariables } from "../src/auth/guards"; +import { createDatabase } from "../src/db/client"; +import { + agentProfiles, + agents, + brokeredConnections, + mcpServers, + mcpTools, + pluginGrants, + teamBotConsents, + teamBotPublications, + users, +} from "../src/db/schema"; +import type { AppAccess } from "../src/plugins/app-access"; +import type { GiveableApp } from "../src/plugins/plugin-store/app-access"; +import { createPluginRoutes } from "../src/plugins/routes"; +import { createSharedUseStore } from "../src/plugins/shared-use-store"; +import { createPluginStore, PluginRefusedError } from "../src/plugins/store"; +import { + createTeamBots, + TEAM_BOT_CONSENT_MARKER, +} from "../src/team-bots/team-bots"; +import { TEST_POOL, testDatabaseUrl } from "./support/database"; +import { stubBroker } from "./support/stub-broker"; + +/** + * A Bot's owner giving one of their own apps to their own Bot — and the one rule that makes that + * safe to publish: a grant the owner made runs on the ASKING person's own account, never the + * owner's, even on the owner's published Team Bot. + * + * Real tables, real routes, the real Team Bot credential rule. The broker is the one thing faked, + * and nothing here reaches it past the directory. + */ + +const database = createDatabase(testDatabaseUrl(), TEST_POOL); +const suite = randomUUID().slice(0, 8); +const id = (name: string) => `${name}-${suite}`; + +const owner = id("owner"); +const teammate = id("teammate"); +const admin = id("admin"); +const people = [owner, teammate, admin]; + +const slug = (name: string) => `${name}_${suite}`; +const GMAIL = `composio-${slug("gmail")}`; +const SLACK = `composio-${slug("slack")}`; + +const events: AuditEventInput[] = []; +const audit: AuditStore = { + insert: async (event) => { + events.push(event); + }, +}; + +const stub = stubBroker({ + grantIsLocal: true, + authorizations: null, + listApps: async () => [], +}); +afterEach(() => { + stub.expectNothingUnprovidedReached(); +}); + +const store = createPluginStore({ + brokerProvider: "composio", + database, + auditStore: audit, + credentials: {} as never, + encryptionKey: "x".repeat(44), + policy: () => ({ mode: "enforce", deny: [], allow: ["true"] }), + broker: stub.broker, + deploymentId: `acme-${suite}`, +}); + +const profiles = createAgentProfileStore( + database, + new URL("https://managed.example.test/ag-ui"), +); + +/** Which servers each person holds their own account on, as `index.ts` reads it for Team Bots. */ +async function connectedServers(userId: string): Promise> { + const rows = await database + .select({ app: brokeredConnections.app }) + .from(brokeredConnections) + .where( + and( + eq(brokeredConnections.holder, "person"), + eq(brokeredConnections.userId, userId), + ), + ); + return new Set(rows.map((row) => `composio-${row.app}`)); +} +const teamBots = createTeamBots({ database, connectedServers }); + +function appAs(actorId: string, role: "admin" | "user" = "user") { + const signedIn: MiddlewareHandler<{ Variables: AppVariables }> = async ( + context, + next, + ) => { + context.set("actor", { + id: actorId, + email: `${actorId}@example.test`, + role, + } as never); + await next(); + }; + return new Hono().route( + "/api/plugins", + createPluginRoutes( + store, + signedIn, + async () => true, + undefined, + { broker: stub.broker, provider: "composio" }, + { modes: {} as never, use: createSharedUseStore(database, "composio") }, + ), + ); +} + +const send = (method: string, body?: unknown) => ({ + method, + headers: { "content-type": "application/json" }, + ...(body === undefined ? {} : { body: JSON.stringify(body) }), +}); + +async function seedApp( + key: string, + title: string, + accountMode: "personal" | "shared" = "personal", +) { + await database.insert(mcpServers).values({ + id: key, + title, + vendor: "Composio", + url: `composio://${key.replace(/^composio-/, "")}`, + provenance: "composio", + authScheme: "OAUTH2", + accountMode, + sharedVendorUserId: + accountMode === "shared" ? `openbot-deployment:${suite}` : null, + }); + await database.insert(mcpTools).values([ + { + serverId: key, + name: "FETCH", + description: "Read", + inputSchema: {}, + effect: "read", + }, + { + serverId: key, + name: "SEND", + description: "Send", + inputSchema: {}, + effect: "write", + }, + ]); +} + +async function connect(userId: string, name = "gmail") { + await database.insert(brokeredConnections).values({ + provider: "composio", + app: slug(name), + holder: "person", + userId, + vendorUserId: `vendor-${userId}`, + verified: true, + }); +} + +const createdBots: string[] = []; +async function botOf(actorId: string) { + const profile = await profiles.create( + { id: actorId, role: "user" } satisfies AgentActor, + { + name: "Research Helper", + title: "Research", + roleDescription: "Finds things out.", + visibility: "private", + }, + ); + createdBots.push(profile.id); + return profile.id; +} + +async function stateOf(actorId: string, botId: string, app = GMAIL) { + const response = await appAs(actorId).request( + `/api/plugins/for/${botId}/apps/${app}`, + ); + return ((await response.json()) as { app: AppAccess }).app; +} + +async function grantsOn(botId: string) { + return database + .select({ ref: pluginGrants.ref, grantedAs: pluginGrants.grantedAs }) + .from(pluginGrants) + .where(eq(pluginGrants.agentId, botId)); +} + +async function clear() { + await database + .delete(teamBotConsents) + .where(inArray(teamBotConsents.userId, people)); + if (createdBots.length) { + await database + .delete(teamBotPublications) + .where(inArray(teamBotPublications.agentId, createdBots)); + await database + .delete(agentProfiles) + .where(inArray(agentProfiles.agentId, createdBots.splice(0))); + } + await database + .delete(brokeredConnections) + .where(like(brokeredConnections.app, `%${suite}%`)); + await database.delete(mcpServers).where(like(mcpServers.id, `%${suite}%`)); + await database.delete(agents).where(like(agents.name, "Research Helper")); +} + +beforeAll(async () => { + for (const user of people) + await database.insert(users).values({ + id: user, + email: `${user}@example.test`, + name: user, + emailVerified: true, + }); +}); + +beforeEach(async () => { + events.length = 0; + await clear(); + await seedApp(GMAIL, "Gmail"); +}); + +afterAll(async () => { + await clear(); + await database.delete(users).where(inArray(users.id, people)); +}); + +describe("an owner giving their own app to their own Bot", () => { + test("the card's state is theirs to give, and asks them to connect first when they haven't", async () => { + const bot = await botOf(owner); + expect(await stateOf(owner, bot)).toMatchObject({ + state: "owner_grantable", + requestKind: null, + ownerGrants: "any", + needsConnect: true, + }); + await connect(owner); + expect(await stateOf(owner, bot)).toMatchObject({ + state: "owner_grantable", + needsConnect: false, + }); + }); + + test("one press gives the read-only actions, marked as the owner's, and leaves a trail", async () => { + const bot = await botOf(owner); + await connect(owner); + const given = await appAs(owner).request( + `/api/plugins/for/${bot}/apps/${GMAIL}/give`, + send("POST"), + ); + expect(given.status).toBe(200); + expect(await given.json()).toEqual({ granted: [`${GMAIL}/FETCH`] }); + expect(await grantsOn(bot)).toEqual([ + { ref: `${GMAIL}/FETCH`, grantedAs: "owner" }, + ]); + expect(await stateOf(owner, bot)).toMatchObject({ + state: "ready", + givenByOwner: true, + }); + expect( + events.find((event) => event.eventType === "app_access.owner_granted"), + ).toMatchObject({ actorUserId: owner, payload: { bot, app: GMAIL } }); + }); + + test("somebody else's Bot, an app kept for admins, and an app nobody turned on are refused", async () => { + const bot = await botOf(owner); + const others = await appAs(teammate).request( + `/api/plugins/for/${bot}/apps/${GMAIL}/give`, + send("POST"), + ); + expect(others.status).toBe(403); + expect(await stateOf(teammate, bot)).toMatchObject({ + state: "not_granted", + requestKind: "grant", + }); + + await seedApp(SLACK, "Slack", "shared"); + const shared = await appAs(owner).request( + `/api/plugins/for/${bot}/apps/${SLACK}/give`, + send("POST"), + ); + expect(shared.status).toBe(403); + expect(((await shared.json()) as { error: string }).error).toBe( + "Only an admin can give Slack to a Bot.", + ); + + // The admin opens Slack up; now it is the owner's to give. + const opened = await appAs(admin, "admin").request( + `/api/plugins/servers/${SLACK}/owner-grants`, + send("PUT", { ownerGrants: "any" }), + ); + expect(opened.status).toBe(200); + expect((await stateOf(owner, bot, SLACK)).state).toBe("owner_grantable"); + expect( + events.find( + (event) => + (event.payload as { change?: string }).change === + "owner_grants_changed", + ), + ).toMatchObject({ targetId: SLACK, payload: { from: null, to: "any" } }); + + // And an owner can never decide the rule themselves. + const notAdmin = await appAs(owner).request( + `/api/plugins/servers/${GMAIL}/owner-grants`, + send("PUT", { ownerGrants: "admin" }), + ); + expect(notAdmin.status).toBe(403); + + // An app this deployment never added stays an admin's to turn on. + await database.delete(mcpServers).where(eq(mcpServers.id, GMAIL)); + const absent = await appAs(owner).request( + `/api/plugins/grants`, + send("POST", { kind: "mcp", ref: `${GMAIL}/FETCH`, agentId: bot }), + ); + expect(absent.status).toBe(403); + expect(await grantsOn(bot)).toEqual([]); + }); + + test("the grant route lets an owner add one action, and only the owner's grants can be taken back by them", async () => { + const bot = await botOf(owner); + const added = await appAs(owner).request( + "/api/plugins/grants", + send("POST", { kind: "mcp", ref: `${GMAIL}/SEND`, agentId: bot }), + ); + expect(added.status).toBe(200); + // An admin's grant beside it, which the owner did not give and cannot take. + await store.grant("mcp", `${GMAIL}/FETCH`, bot, "admin@example.test"); + expect(await stateOf(owner, bot)).toMatchObject({ givenByOwner: false }); + + const viaGrants = await appAs(owner).request( + `/api/plugins/grants?kind=mcp&ref=${GMAIL}/SEND&agentId=${bot}`, + send("DELETE"), + ); + expect(viaGrants.status).toBe(403); + + const taken = await appAs(owner).request( + `/api/plugins/for/${bot}/apps/${GMAIL}/give`, + send("DELETE"), + ); + expect(await taken.json()).toEqual({ revoked: [`${GMAIL}/SEND`] }); + expect(await grantsOn(bot)).toEqual([ + { ref: `${GMAIL}/FETCH`, grantedAs: "admin" }, + ]); + }); + + test("the apps a person could give are the ones they connected, and open to owners", async () => { + await seedApp(SLACK, "Slack", "shared"); + const listed = async () => + ( + (await ( + await appAs(owner).request("/api/plugins/giveable") + ).json()) as { apps: GiveableApp[] } + ).apps.filter((app) => app.key.includes(suite)); + expect(await listed()).toEqual([]); + await connect(owner); + expect(await listed()).toEqual([ + { key: GMAIL, displayName: "Gmail", logo: null, account: "personal" }, + ]); + }); +}); + +describe("a Team Bot whose owner gave it their own Gmail", () => { + test("a teammate's call uses the teammate's own account or asks them to connect, never the owner's", async () => { + const bot = await botOf(owner); + await connect(owner); + const given = await appAs(owner).request( + `/api/plugins/for/${bot}/apps/${GMAIL}/give`, + send("POST"), + ); + expect(given.status).toBe(200); + await teamBots.publish({ id: owner, role: "user" }, bot, { + audience: "team", + emails: [], + groups: [], + }); + const ref = `${GMAIL}/FETCH`; + const teammateCall = teamBots.credentialActorFor(teammate, bot); + + // Not connected: the teammate's own (absent) account, which the call then asks them to connect. + expect(await teammateCall(ref)).toBe(teammate); + expect(await stateOf(teammate, bot)).toMatchObject({ + state: "not_connected", + accountHolder: "you", + }); + + // Connected: still the teammate's own, behind their consent, never the owner's. + await connect(teammate); + const asked = await teammateCall(ref).catch((error: unknown) => error); + expect(asked).toBeInstanceOf(PluginRefusedError); + expect((asked as Error).message).toStartWith(TEAM_BOT_CONSENT_MARKER); + await teamBots.consent( + { id: teammate, role: "user" }, + bot, + GMAIL, + "allow_always", + ); + expect(await teammateCall(ref)).toBe(teammate); + expect(await stateOf(teammate, bot)).toMatchObject({ + state: "ready", + accountHolder: "you", + }); + + // The owner's own calls are theirs, as on any Bot of their own. + expect(await teamBots.credentialActorFor(owner, bot)(ref)).toBe(owner); + }); + + test("the same Bot given Gmail by an admin uses the owner's account, as before", async () => { + const bot = await botOf(owner); + await connect(owner); + await connect(teammate); + await store.grant("mcp", `${GMAIL}/FETCH`, bot, "admin@example.test"); + await teamBots.publish({ id: owner, role: "user" }, bot, { + audience: "team", + emails: [], + groups: [], + }); + expect( + await teamBots.credentialActorFor(teammate, bot)(`${GMAIL}/FETCH`), + ).toBe(owner); + expect(await stateOf(teammate, bot)).toMatchObject({ + state: "ready", + accountHolder: "owner", + }); + }); +}); From c48fe9fb1c9b797dd68ddf904674666d2915a981 Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 12:35:46 -0300 Subject: [PATCH 05/19] Let an admin choose on an app's page whether Bot owners can give it --- app/src/lib/plugins/mutations.ts | 23 +++++++++++ app/src/lib/plugins/queries.ts | 8 ++++ app/src/routes/_authed/admin/plugins/$key.tsx | 41 +++++++++++++++++++ .../admin-brokered-row-provider.test.tsx | 2 + app/tests/bot-app-grants-screen.test.tsx | 2 + app/tests/brokered-account-row.test.tsx | 2 + .../connected-account-confirmation.test.tsx | 2 + app/tests/connected-account-grants.test.tsx | 2 + .../connected-account-landing-param.test.tsx | 2 + app/tests/plugin-mutation-refresh.test.ts | 15 +++++++ app/tests/skipped-tools-pages.test.tsx | 2 + 11 files changed, 101 insertions(+) diff --git a/app/src/lib/plugins/mutations.ts b/app/src/lib/plugins/mutations.ts index 2552a1f13a..02d3bacf34 100644 --- a/app/src/lib/plugins/mutations.ts +++ b/app/src/lib/plugins/mutations.ts @@ -194,6 +194,29 @@ export function setAccountModeMutationOptions(queryClient: QueryClient) { }); } +/** + * Who may give an app to a Bot: any Bot's owner (`any`) or only an admin (`admin`). The server writes + * the choice to the trail. + */ +export function setOwnerGrantsMutationOptions(queryClient: QueryClient) { + return mutationOptions({ + mutationFn: async (variables: { + serverId: string; + ownerGrants: "any" | "admin"; + }) => { + await client( + `/api/plugins/servers/${encodeURIComponent(variables.serverId)}/owner-grants`, + { + method: "PUT", + body: { ownerGrants: variables.ownerGrants }, + fallback: "Couldn't change who can give this app.", + }, + ); + }, + onSettled: () => invalidatePlugins(queryClient), + }); +} + /** * Whether one Bot carries one plugin. * diff --git a/app/src/lib/plugins/queries.ts b/app/src/lib/plugins/queries.ts index c3a42ccbaf..3879f67dae 100644 --- a/app/src/lib/plugins/queries.ts +++ b/app/src/lib/plugins/queries.ts @@ -118,6 +118,14 @@ export type PluginServer = { * {@link accountMode} is not `"shared"`. */ sharedVendorConnected: boolean; + /** + * Who may give this app to a Bot: any Bot's owner, for their own Bot (`any`), or only an admin + * (`admin`), with the default for its account filled in. Every person's own account defaults to + * `any`, as do the built-in Routines; everything else to `admin`. + */ + ownerGrants: "any" | "admin"; + /** What an admin chose, or null where the default is in force. */ + ownerGrantsChosen: "any" | "admin" | null; }; export type PluginSkill = { diff --git a/app/src/routes/_authed/admin/plugins/$key.tsx b/app/src/routes/_authed/admin/plugins/$key.tsx index 24feed509b..c86836471b 100644 --- a/app/src/routes/_authed/admin/plugins/$key.tsx +++ b/app/src/routes/_authed/admin/plugins/$key.tsx @@ -55,6 +55,7 @@ import { refreshPluginServerMutationOptions, registerOAuthClientMutationOptions, removePluginServerMutationOptions, + setOwnerGrantsMutationOptions, } from "@/lib/plugins/mutations"; import { brokeredConnectionDescription, @@ -301,6 +302,10 @@ function RouteComponent() { ...refreshPluginServerMutationOptions(queryClient), ...report, }); + const ownerGrants = useMutation({ + ...setOwnerGrantsMutationOptions(queryClient), + ...report, + }); const remove = useMutation({ ...removePluginServerMutationOptions(queryClient), ...report, @@ -638,6 +643,42 @@ function RouteComponent() { ) : null} + + {/* + * Who may give the app to a Bot. Binary and immediate, so a Switch. Every person's own + * account and the built-in Routines start on; a deployment token, a shared account and an + * app with no account start off, because a Bot given one of those reaches an account that + * is not the asker's. An owner's grant always runs on the asker's own account. + */} + {server ? ( + <> + + + + Bot owners can give it to their Bots + + {server.ownerGrants === "any" + ? "People can let Bots they own use it, on the account of whoever is asking." + : "Only an admin can give it to a Bot."} + + + + { + setError(null); + ownerGrants.mutate({ + serverId: key, + ownerGrants: next ? "any" : "admin", + }); + }} + /> + + + + ) : null} diff --git a/app/tests/admin-brokered-row-provider.test.tsx b/app/tests/admin-brokered-row-provider.test.tsx index acc104d3b7..3c4a986fd8 100644 --- a/app/tests/admin-brokered-row-provider.test.tsx +++ b/app/tests/admin-brokered-row-provider.test.tsx @@ -80,6 +80,8 @@ function server(provider: Provider, shared: boolean): PluginServer { dynamicClient: false, accountMode: shared ? "shared" : null, sharedVendorConnected: false, + ownerGrants: shared ? "admin" : "any", + ownerGrantsChosen: null, tools: [], withdrawn: [], }; diff --git a/app/tests/bot-app-grants-screen.test.tsx b/app/tests/bot-app-grants-screen.test.tsx index 8c707b7827..21204444ab 100644 --- a/app/tests/bot-app-grants-screen.test.tsx +++ b/app/tests/bot-app-grants-screen.test.tsx @@ -131,6 +131,8 @@ function server(overrides: Partial & { id: string }) { dynamicClient: false, accountMode: null, sharedVendorConnected: false, + ownerGrants: "admin", + ownerGrantsChosen: null, // Not brokered unless a case says otherwise, which is what a null means here. authScheme: null, tools: [], diff --git a/app/tests/brokered-account-row.test.tsx b/app/tests/brokered-account-row.test.tsx index f9d502c571..65f1bd229b 100644 --- a/app/tests/brokered-account-row.test.tsx +++ b/app/tests/brokered-account-row.test.tsx @@ -179,6 +179,8 @@ function brokeredServer(authScheme: string): PluginServer { dynamicClient: false, accountMode: null, sharedVendorConnected: false, + ownerGrants: "any", + ownerGrantsChosen: null, tools: [], withdrawn: [], }; diff --git a/app/tests/connected-account-confirmation.test.tsx b/app/tests/connected-account-confirmation.test.tsx index 11951a97a3..0fd1905488 100644 --- a/app/tests/connected-account-confirmation.test.tsx +++ b/app/tests/connected-account-confirmation.test.tsx @@ -92,6 +92,8 @@ function server(provider: Provider): PluginServer { dynamicClient: false, accountMode: null, sharedVendorConnected: false, + ownerGrants: "any", + ownerGrantsChosen: null, tools: [], withdrawn: [], }; diff --git a/app/tests/connected-account-grants.test.tsx b/app/tests/connected-account-grants.test.tsx index 77651f58dc..b0dad61542 100644 --- a/app/tests/connected-account-grants.test.tsx +++ b/app/tests/connected-account-grants.test.tsx @@ -94,6 +94,8 @@ function server( dynamicClient: false, accountMode, sharedVendorConnected: false, + ownerGrants: "any", + ownerGrantsChosen: null, tools: [], withdrawn: [], }; diff --git a/app/tests/connected-account-landing-param.test.tsx b/app/tests/connected-account-landing-param.test.tsx index e6e3bde278..47cccb8c0b 100644 --- a/app/tests/connected-account-landing-param.test.tsx +++ b/app/tests/connected-account-landing-param.test.tsx @@ -66,6 +66,8 @@ function server(accountMode: "shared" | null): PluginServer { dynamicClient: false, accountMode, sharedVendorConnected: false, + ownerGrants: "any", + ownerGrantsChosen: null, tools: [], withdrawn: [], }; diff --git a/app/tests/plugin-mutation-refresh.test.ts b/app/tests/plugin-mutation-refresh.test.ts index 3dd72fd7fa..f11bbdd07c 100644 --- a/app/tests/plugin-mutation-refresh.test.ts +++ b/app/tests/plugin-mutation-refresh.test.ts @@ -26,6 +26,7 @@ import { removeSkillMutationOptions, saveSkillMutationOptions, setAccountModeMutationOptions, + setOwnerGrantsMutationOptions, setPluginGrantMutationOptions, } from "../src/lib/plugins/mutations"; import { pluginKeys } from "../src/lib/plugins/queries"; @@ -299,6 +300,14 @@ const REFUSALS: { mode: "shared" as const, }, }, + { + name: "choosing who may give an app to a Bot", + route: "PUT /api/plugins/servers/:id/owner-grants", + status: 404, + message: "That app hasn't been added.", + factory: setOwnerGrantsMutationOptions, + variables: { serverId: "linear", ownerGrants: "admin" as const }, + }, ]; for (const refusal of REFUSALS) { @@ -567,6 +576,12 @@ const ENCODED_REQUESTS: { build: setAccountModeMutationOptions, variables: { serverId: HOSTILE_ID, mode: "shared" as const }, }, + { + name: "choosing who may give an app to a Bot", + url: `/api/plugins/servers/${ENCODED_ID}/owner-grants`, + build: setOwnerGrantsMutationOptions, + variables: { serverId: HOSTILE_ID, ownerGrants: "any" as const }, + }, { /* * The one write whose caller text goes in a QUERY rather than a path, and it takes the same diff --git a/app/tests/skipped-tools-pages.test.tsx b/app/tests/skipped-tools-pages.test.tsx index f51372798f..8fc1632772 100644 --- a/app/tests/skipped-tools-pages.test.tsx +++ b/app/tests/skipped-tools-pages.test.tsx @@ -91,6 +91,8 @@ function server(): PluginServer { dynamicClient: false, accountMode: null, sharedVendorConnected: false, + ownerGrants: "any", + ownerGrantsChosen: null, authScheme: null, tools: [ { From 8c1e8dfa20e5e111211f67992769d3217e450af8 Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 12:35:46 -0300 Subject: [PATCH 06/19] Ask a Bot's owner on the app card whether to let it use their app, in one press --- .../components/channels/app-access-card.tsx | 17 +- .../components/plugins/app-access-action.tsx | 146 +++++++++++++++++- app/src/lib/app-access/mutations.ts | 41 +++++ app/src/lib/app-access/queries.ts | 40 ++++- app/tests/app-access-action.test.tsx | 55 ++++++- app/tests/app-access-fixture.ts | 3 + 6 files changed, 289 insertions(+), 13 deletions(-) diff --git a/app/src/components/channels/app-access-card.tsx b/app/src/components/channels/app-access-card.tsx index e0eff73e31..5a9661be8d 100644 --- a/app/src/components/channels/app-access-card.tsx +++ b/app/src/components/channels/app-access-card.tsx @@ -1,3 +1,4 @@ +import type { ConnectPrompt } from "@openbot/shared/connect-prompt"; import { useQuery } from "@tanstack/react-query"; import { AppAccessAction, @@ -5,10 +6,10 @@ import { } from "@/components/plugins/app-access-action"; import { PluginLogo } from "@/components/plugins/plugin-logo"; import { Button } from "@/components/ui/button"; +import { agentQueryOptions } from "@/lib/agents/queries"; import { type AppAccess, botAppQueryOptions } from "@/lib/app-access/queries"; import { useDeclaredBotId } from "@/lib/copilot/active-bot"; import { type CardPlace, useCarryOn } from "@/lib/copilot/carry-on"; -import type { ConnectPrompt } from "@openbot/shared/connect-prompt"; import { ConnectAppCard } from "./connect-app-card"; /** @@ -76,6 +77,8 @@ function LiveAppAccessCard({ }); const access = live.data ? asRefused(live.data, prompt) : null; const name = access?.displayName ?? prompt.title; + // Only asked for by "Let Research Bot use your Gmail?", the one sentence that names the Bot. + const bot = useQuery(agentQueryOptions(agentId)).data?.name; const { carried, offer } = useCarryOn({ place, ready: access ? access.state === "ready" : null, @@ -111,12 +114,12 @@ function LiveAppAccessCard({ {live.isPending ? null : access ? (

{access.state !== "ready" - ? refusedSentence(access, prompt) + ? refusedSentence(access, prompt, bot) : carried || offer ? `${name} is ready now.` : prompt.reason !== "access" ? `${name} is ready now. Ask again and the Bot can use it.` - : refusedSentence(access, prompt)} + : refusedSentence(access, prompt, bot)}

) : (

@@ -178,12 +181,16 @@ function asRefused(live: AppAccess, prompt: ConnectPrompt): AppAccess { } /** As the shared sentence, except a lost team account, which needs reconnecting rather than connecting. */ -function refusedSentence(access: AppAccess, prompt: ConnectPrompt): string { +function refusedSentence( + access: AppAccess, + prompt: ConnectPrompt, + bot?: string, +): string { if ( access.state === "shared_not_connected" && prompt.reason === "shared" && !access.requestKind ) return `Your organisation's ${access.displayName} account needs reconnecting. Only an admin can reconnect it.`; - return appAccessSentence(access); + return appAccessSentence(access, bot); } diff --git a/app/src/components/plugins/app-access-action.tsx b/app/src/components/plugins/app-access-action.tsx index 42fe1cfbd5..d52311058f 100644 --- a/app/src/components/plugins/app-access-action.tsx +++ b/app/src/components/plugins/app-access-action.tsx @@ -7,6 +7,7 @@ import { agentQueryOptions } from "@/lib/agents/queries"; import { approveBotAppMutationOptions, cancelAppAccessRequestMutationOptions, + giveBotAppMutationOptions, requestAppAccessMutationOptions, } from "@/lib/app-access/mutations"; import { type AppAccess, appAccessKeys } from "@/lib/app-access/queries"; @@ -21,8 +22,9 @@ import { queryClient } from "@/query-client"; * whether this person can use this app through this Bot, whose move it is if not, and the button * for that move — so both draw it from here, and a sentence corrected for one is corrected for both. * - * WHO CAN FIX IT IS THE WHOLE SENTENCE. Seven states reduce to four answers: nothing to do, a step - * that is yours (connect, reconnect), a step only an administrator can take (switch it on, finish + * WHO CAN FIX IT IS THE WHOLE SENTENCE. Eight states reduce to four answers: nothing to do, a step + * that is yours (connect, reconnect, or giving your own app to your own Bot), a step only an + * administrator can take (switch it on, finish * setting it up, grant it, connect or approve the team account), and on somebody else's Team Bot a * step only its owner can take (reconnect the account the Bot runs on). The copy names which, * because "Gmail is unavailable" sends somebody hunting through their own settings for a switch that @@ -34,8 +36,11 @@ import { queryClient } from "@/query-client"; * `AppAccessCard` — and that is the caller's decision, made where the refusal is in hand. */ -/** What the person is told about one app, in words that say whose move it is. */ -export function appAccessSentence(access: AppAccess): string { +/** + * What the person is told about one app, in words that say whose move it is. `bot` is the Bot's name, + * for the one state that asks the person a question about it. + */ +export function appAccessSentence(access: AppAccess, bot?: string): string { const name = access.displayName; const owners = access.accountHolder === "owner"; switch (access.state) { @@ -59,6 +64,11 @@ export function appAccessSentence(access: AppAccess): string { return disabledSentence(access); case "not_granted": return `This Bot hasn't been given ${name}. Only an admin can give it.`; + case "owner_grantable": { + const yours = access.account === "personal" ? `your ${name}` : name; + const ask = `Let ${bot ?? "this Bot"} use ${yours}?`; + return access.needsConnect ? `${ask} Connect it first.` : ask; + } } } @@ -116,6 +126,9 @@ export function AppAccessAction({ return null; if (access.state === "not_connected" || access.state === "reconnect") return ; + // Theirs to give, admin or not: an owner's grant keeps every call on the asker's own account. + if (access.state === "owner_grantable") + return ; if (me?.role === "admin") return ; return ; @@ -217,6 +230,131 @@ function AdminAction({ ); } +/** + * The Bot's owner giving it one of their own apps, as one press: Allow, or Connect and allow where + * the app runs on their own account and they have not connected it. + * + * CONNECT FIRST, THEN GIVE, ON THE SAME PRESS. The consent opens in a new tab the way `ConnectAction` + * opens it, and this tab watches the server until the account is there — `needsConnect` turning false + * — and then gives the app without a second press. Giving first would leave a Bot holding an app + * nobody had connected if the person closed the consent screen, and would swap this button for a + * Connect in the middle of the press. + * + * AN APP CONNECTED WITH A TYPED KEY is connected on its page in Settings, as `ConnectAction` sends it; + * coming back, the app reads connected and the button says Allow. + * + * AND THEN THE TASK CARRIES ON. Every query is read again once the app is given, so the card sees the + * app ready and carries the conversation on (`lib/copilot/carry-on.ts`). + */ +function OwnerGiveAction({ + agentId, + access, +}: { + agentId: string; + access: AppAccess; +}) { + const bot = useQuery(agentQueryOptions(agentId)).data?.name ?? "this Bot"; + const give = useMutation(giveBotAppMutationOptions(queryClient)); + const tab = useConnectInNewTab(); + const { waiting, stopWaiting } = tab; + const name = access.displayName; + /** Pressed while the account still had to be connected: give it as soon as it is. */ + const pending = useRef(false); + + const giveNow = () => + give.mutate( + { agentId, app: access.key }, + { onSuccess: () => void queryClient.invalidateQueries() }, + ); + + useEffect(() => { + if (!waiting) return; + const timer = setInterval(() => { + void queryClient.invalidateQueries({ queryKey: appAccessKeys.all }); + }, 2_000); + return () => clearInterval(timer); + }, [waiting]); + + // biome-ignore lint/correctness/useExhaustiveDependencies: runs when the account appears, once per press. + useEffect(() => { + if (!pending.current || access.needsConnect) return; + pending.current = false; + stopWaiting(); + giveNow(); + }, [access.needsConnect]); + + if ( + access.needsConnect && + (access.connect === "key" || access.connect === null || tab.needsKey) + ) + return ( + + ); + + const error = give.error?.message ?? tab.error; + return ( +

+ {error ? ( +

+ {error} +

+ ) : null} + {tab.blockedUrl ? ( +

+ This browser blocked the tab.{" "} + + Open {name}'s sign-in + + . +

+ ) : waiting ? ( +

+ Finish in the tab that opened. +

+ ) : null} + +
+ ); +} + /** The person's own account: connect it, or reconnect it. */ function ConnectAction({ access }: { access: AppAccess }) { const label = access.state === "reconnect" ? "Reconnect" : "Connect"; diff --git a/app/src/lib/app-access/mutations.ts b/app/src/lib/app-access/mutations.ts index b923542bdc..af95e17105 100644 --- a/app/src/lib/app-access/mutations.ts +++ b/app/src/lib/app-access/mutations.ts @@ -111,6 +111,47 @@ export function approveBotAppMutationOptions(queryClient: QueryClient) { }); } +/** + * Give one of your own apps to a Bot you own: its read-only actions, run on whoever is asking's own + * account. A plain function as well as a mutation, because the New Bot sheet gives several in a row + * right after the Bot is made, outside any one press. + */ +export async function giveBotApp(variables: { + agentId: string; + app: string; +}): Promise<{ granted: string[] }> { + const response = await client( + `/api/plugins/for/${encodeURIComponent(variables.agentId)}/apps/${encodeURIComponent(variables.app)}/give`, + { method: "POST", fallback: "Couldn't give that app to this Bot." }, + ); + return response.json(); +} + +/** One press of "Let it use your Gmail", on the card or a Bot's page. */ +export function giveBotAppMutationOptions(queryClient: QueryClient) { + return mutationOptions({ + mutationFn: giveBotApp, + onSettled: () => refresh(queryClient), + }); +} + +/** Take back what you gave a Bot you own. Anything an admin gave it stays. */ +export function takeBackBotAppMutationOptions(queryClient: QueryClient) { + return mutationOptions({ + mutationFn: async (variables: { + agentId: string; + app: string; + }): Promise<{ revoked: string[] }> => { + const response = await client( + `/api/plugins/for/${encodeURIComponent(variables.agentId)}/apps/${encodeURIComponent(variables.app)}/give`, + { method: "DELETE", fallback: "Couldn't take that app back." }, + ); + return response.json(); + }, + onSettled: () => refresh(queryClient), + }); +} + /** Dismiss a request without changing anything else. */ export function dismissAppAccessRequestMutationOptions( queryClient: QueryClient, diff --git a/app/src/lib/app-access/queries.ts b/app/src/lib/app-access/queries.ts index 2c4313f2b5..b2dcf86380 100644 --- a/app/src/lib/app-access/queries.ts +++ b/app/src/lib/app-access/queries.ts @@ -12,7 +12,7 @@ import { client } from "@/lib/client"; */ /** - * The seven things a person can be told about one app on one Bot. + * The eight things a person can be told about one app on one Bot. * * `ready` — usable now. `not_connected` / `reconnect` — waiting on an account: this person's own, * which they connect or reconnect themselves, or on somebody else's Team Bot the owner's, which only @@ -21,7 +21,9 @@ import { client } from "@/lib/client"; * connected, but an administrator has not approved this Bot's audience for it, so every call is * refused; the refused call files the administrator's request itself, so there is none to press. * `disabled` — switched off at the deployment, for the reason `unavailable` gives. `not_granted` — - * enabled, and this Bot holds none of it. + * enabled, and this Bot holds none of it; only an admin can give it. `owner_grantable` — the same, + * except the person looking owns the Bot and the app is open to owners, so they can give it + * themselves (see `needsConnect`). */ export type AppAccessState = | "ready" @@ -30,7 +32,8 @@ export type AppAccessState = | "shared_not_connected" | "shared_not_approved" | "disabled" - | "not_granted"; + | "not_granted" + | "owner_grantable"; /** * Why an app is switched off. `absent` — nobody switched it on here. `composio_unconfigured` — a @@ -77,6 +80,15 @@ export type AppAccess = { asksFirst: boolean; /** Whether this Bot holds any of the app's tools. */ granted: boolean; + /** Who may give the app to a Bot: any Bot's owner, or only an admin. */ + ownerGrants: "any" | "admin"; + /** Whether everything this Bot holds of the app was given by its owner, who may take it back. */ + givenByOwner: boolean; + /** + * In `owner_grantable`, whether giving it has to start with connecting the person's own account. + * False in every other state. + */ + needsConnect: boolean; /** * When the account this app runs in for this person was connected, or null. Read by the chat card * to tell an account lost at the vendor from one connected again since. See `AppAccessCard`. @@ -123,8 +135,30 @@ export const appAccessKeys = { ["plugins", "app-access", "bot", agentId, "app", app] as const, inbox: (app?: string) => ["plugins", "app-access", "inbox", { app: app ?? null }] as const, + giveable: ["plugins", "app-access", "giveable"] as const, }; +/** An app this person could give their own Bots. */ +export type GiveableApp = Pick< + AppAccess, + "key" | "displayName" | "logo" | "account" +>; + +/** + * The apps this person could give their own Bots: switched on and open to owners, and connected + * where the app runs on their own account. What the New Bot sheet offers ticked, and the switches + * on a Bot's page. Empty where an admin turned the feature off for them. + */ +export function giveableAppsQueryOptions() { + return queryOptions({ + queryKey: appAccessKeys.giveable, + queryFn: (): Promise => + client("/api/plugins/giveable", "apps", { + fallback: "Couldn't load your apps.", + }), + }); +} + /** Every work app this Bot holds, with what each is for the person asking. */ export function botAppsQueryOptions(agentId: string) { return queryOptions({ diff --git a/app/tests/app-access-action.test.tsx b/app/tests/app-access-action.test.tsx index 89fb1d0a41..d25d2e4693 100644 --- a/app/tests/app-access-action.test.tsx +++ b/app/tests/app-access-action.test.tsx @@ -28,7 +28,7 @@ import { settleReactWork } from "./settle-react-work"; /** * The one piece the Bot page and the chat card both draw an app's state with. * - * Each of the seven states must say whose move it is and offer exactly that move — and nothing for a + * Each of the eight states must say whose move it is and offer exactly that move — and nothing for a * state that needs none, or a move that is somebody else's: an administrator's, or on somebody else's * Team Bot its owner's. The two moves that write are asserted against the request they send: the * browser names a Bot and an app and never a kind, which is the server's to decide. @@ -531,3 +531,56 @@ test.each([ expect(link.getAttribute("target")).toBe("_blank"); }, ); + +test("an owner is asked about their own app, by the Bot's name where the caller has it", () => { + const owners = appAccess({ state: "owner_grantable", granted: false }); + expect(appAccessSentence(owners)).toBe("Let this Bot use your Gmail?"); + expect(appAccessSentence(owners, "Research Bot")).toBe( + "Let Research Bot use your Gmail?", + ); + expect( + appAccessSentence({ ...owners, needsConnect: true }, "Research Bot"), + ).toBe("Let Research Bot use your Gmail? Connect it first."); + // An app on nobody's own account is not "your" anything. + expect( + appAccessSentence( + { ...owners, displayName: "Routines", account: "deployment" }, + "Research Bot", + ), + ).toBe("Let Research Bot use Routines?"); +}); + +test("an owner's Allow gives the app in one press, naming the Bot and the app and nothing else", async () => { + const { view, sent } = mounted( + appAccess({ state: "owner_grantable", granted: false }), + ); + fireEvent.click(await view.findByRole("button", { name: /^Let .* Gmail$/ })); + await waitFor(() => + expect(sent.filter((call) => call.method === "POST")).toEqual([ + { + method: "POST", + url: "/api/plugins/for/expenses/apps/composio-gmail/give", + body: undefined, + }, + ]), + ); +}); + +test("Connect and allow opens the person's consent first, and gives the app once it is connected", async () => { + const waiting = appAccess({ + state: "owner_grantable", + granted: false, + needsConnect: true, + }); + const { view, sent, opened, swap } = mounted(waiting); + fireEvent.click( + await view.findByRole("button", { name: /after you connect it$/ }), + ); + await waitFor(() => expect(opened).toEqual(["https://gmail.test/consent"])); + const gives = () => + sent.filter((call) => call.url.endsWith("/apps/composio-gmail/give")); + expect(gives()).toEqual([]); + // The server's next answer: connected now, and still not given. + swap({ ...waiting, needsConnect: false }); + await waitFor(() => expect(gives()).toHaveLength(1)); +}); diff --git a/app/tests/app-access-fixture.ts b/app/tests/app-access-fixture.ts index 2a6a99e7e8..e21306374d 100644 --- a/app/tests/app-access-fixture.ts +++ b/app/tests/app-access-fixture.ts @@ -19,6 +19,9 @@ export function appAccess(overrides: Partial = {}): AppAccess { accountHolder: "you", asksFirst: false, granted: true, + ownerGrants: "any", + givenByOwner: false, + needsConnect: false, connectedAt: null, requestKind: null, request: null, From 597ff8b2a713e4f64f949af43ede5f2b6fdd42f1 Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 12:35:46 -0300 Subject: [PATCH 07/19] Give a new Bot your connected apps, and switch them on its page --- app/src/components/agents/new-bot-sheet.tsx | 74 +++++++++ app/src/components/bot-profile/access.tsx | 142 +++++++++++++++- app/tests/bot-access-your-apps.test.tsx | 169 ++++++++++++++++++++ app/tests/new-bot-sheet.test.tsx | 55 +++++++ 4 files changed, 435 insertions(+), 5 deletions(-) create mode 100644 app/tests/bot-access-your-apps.test.tsx diff --git a/app/src/components/agents/new-bot-sheet.tsx b/app/src/components/agents/new-bot-sheet.tsx index 47386372dc..87393f7930 100644 --- a/app/src/components/agents/new-bot-sheet.tsx +++ b/app/src/components/agents/new-bot-sheet.tsx @@ -10,7 +10,9 @@ import { MotionConfig, motion, useReducedMotion } from "motion/react"; import { useEffect, useRef, useState } from "react"; import { HarnessMark } from "@/components/agents/harness-mark"; import AgentOrb from "@/components/agents/orb/agent-orb"; +import { PluginLogo } from "@/components/plugins/plugin-logo"; import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; import { Field, FieldDescription, @@ -39,7 +41,13 @@ import { agentCapabilitiesQueryOptions, testAgentConnection, } from "@/lib/agents/queries"; +import { giveBotApp } from "@/lib/app-access/mutations"; +import { + type GiveableApp, + giveableAppsQueryOptions, +} from "@/lib/app-access/queries"; import { isComposing } from "@/lib/composing"; +import { pluginKeys } from "@/lib/plugins/queries"; import { cn } from "@/lib/utils"; import { queryClient } from "@/query-client"; @@ -165,6 +173,13 @@ function NewBotFlow({ onCreated }: { onCreated: (agentId: string) => void }) { const [unreachable, setUnreachable] = useState(); /** The Bot just made, held for the moment the sheet spends saying so before it hands over. */ const [createdId, setCreatedId] = useState(); + /** + * The person's own apps this Bot may use. Ticked unless the person unticks one: an app they + * connected is one they already use, and a new Bot that cannot reach any of it is no use until + * somebody finds the Bot page. Kept as what was UNticked so an app that loads late arrives ticked. + */ + const giveable = useQuery(giveableAppsQueryOptions()).data ?? []; + const [leftOut, setLeftOut] = useState>(() => new Set()); const reduceMotion = useReducedMotion(); // A deployment with no Bot of its own cannot run one here, so nothing is chosen for the person. @@ -228,6 +243,17 @@ function NewBotFlow({ onCreated }: { onCreated: (agentId: string) => void }) { authValue: managed ? key : "", }), ); + /* + * The ticked apps, given once the Bot exists. A refusal is not a failed create: the Bot is made, + * and its page lists every app it could be given with a switch each. So each is tried, none + * holds up the others, and none stops the hand-over. + */ + await Promise.allSettled( + giveable + .filter((app) => !leftOut.has(app.key)) + .map((app) => giveBotApp({ agentId: agent.id, app: app.key })), + ); + void queryClient.invalidateQueries({ queryKey: pluginKeys.all }); setCreatedId(agent.id); }; @@ -334,8 +360,18 @@ function NewBotFlow({ onCreated }: { onCreated: (agentId: string) => void }) { > {step === "about" ? ( + setLeftOut((current) => { + const next = new Set(current); + if (used) next.delete(app); + else next.add(app); + return next; + }) + } runtime={runtime ?? { kind: "builtin" }} onInstructions={setInstructions} onName={setName} @@ -512,8 +548,14 @@ const VISIBILITY_OPTIONS: Array<{ * Who can see it was the old wizard's last step. It is here because this step had the room, and a * question that does not change what is being made is cheaper answered alongside the name than as a * screen of its own. + * + * The apps it can use come last and arrive ticked: the person's own connected apps an admin lets + * owners give. Drawn only when there are some, so a person with none connected is asked nothing. */ function AboutStep({ + apps, + leftOut, + onApp, name, title, instructions, @@ -533,6 +575,9 @@ function AboutStep({ onTitle: (value: string) => void; onInstructions: (value: string) => void; onVisibility: (value: Visibility) => void; + apps: GiveableApp[]; + leftOut: Set; + onApp: (app: string, used: boolean) => void; }) { const runsHere = runtime.kind === "builtin"; return ( @@ -611,6 +656,35 @@ function AboutStep({ })} + {apps.length > 0 ? ( +
+ Apps it can use + + It always uses the account of whoever is asking. + +
+ {apps.map((app) => ( + + ))} +
+
+ ) : null} ); } diff --git a/app/src/components/bot-profile/access.tsx b/app/src/components/bot-profile/access.tsx index f6b22ef8ef..64fe2b5803 100644 --- a/app/src/components/bot-profile/access.tsx +++ b/app/src/components/bot-profile/access.tsx @@ -2,6 +2,7 @@ import { IconApps, IconBox, IconChevronRight, + IconPlug, IconPlus, } from "@tabler/icons-react"; import { useMutation, useQuery } from "@tanstack/react-query"; @@ -30,7 +31,16 @@ import { import { Separator } from "@/components/ui/separator"; import { Switch } from "@/components/ui/switch"; import type { AgentProfile } from "@/lib/agents/queries"; -import { botAppsQueryOptions } from "@/lib/app-access/queries"; +import { + giveBotAppMutationOptions, + takeBackBotAppMutationOptions, +} from "@/lib/app-access/mutations"; +import { + type AppAccess, + botAppsQueryOptions, + type GiveableApp, + giveableAppsQueryOptions, +} from "@/lib/app-access/queries"; import { currentUserQueryOptions } from "@/lib/auth/queries"; import { setPluginGrantMutationOptions } from "@/lib/plugins/mutations"; import { @@ -86,13 +96,133 @@ export function connectorCount(plugins: GrantedPlugins): number { export function AccessSections({ agent }: { agent: AgentProfile }) { return ( <> + {agent.mine ? : null} {agent.mine ? : null} - + ); } +/** + * Your own apps, one switch each: the ones you connected that an admin lets owners give, and any you + * already gave this Bot. Only on your own Bot, for the reason the server gives — see + * `server/src/plugins/owner-grants.ts`. + * + * WHAT A SWITCH GIVES is what an admin's one press gives: the app's read-only actions. Every call + * runs on the asking person's own account, even after the Bot is published to the team, so the + * description says "your" only about the person reading it. + * + * AN APP AN ADMIN GAVE THIS BOT is drawn switched on and locked. Taking it away is the admin's, and + * the server would refuse it. + */ +function YourApps({ agent }: { agent: AgentProfile }) { + const giveable = useQuery(giveableAppsQueryOptions()); + const held = useQuery(botAppsQueryOptions(agent.id)); + const give = useMutation(giveBotAppMutationOptions(queryClient)); + const takeBack = useMutation(takeBackBotAppMutationOptions(queryClient)); + const error = give.error ?? takeBack.error; + const busy = give.isPending || takeBack.isPending; + if (giveable.isPending || held.isPending) return null; + const states = new Map((held.data ?? []).map((app) => [app.key, app])); + /* + * What you could give, and then anything you already gave that no longer is — an account you + * disconnected since, an app an admin closed to owners — so it can still be switched off here. + */ + const rows: (GiveableApp & { access?: AppAccess })[] = [ + ...(giveable.data ?? []).map((app) => ({ + ...app, + access: states.get(app.key), + })), + ...(held.data ?? []) + .filter( + (app) => + app.givenByOwner && + !(giveable.data ?? []).some((given) => given.key === app.key), + ) + .map((app) => ({ ...app, access: app })), + ]; + return ( + + {error ? ( +

+ {error.message} +

+ ) : null} + {giveable.error ? ( +

+ Couldn't load your apps. Reload the page. +

+ ) : rows.length === 0 ? ( + } + size="sm" + variant="outline" + > + + Connections + + } + icon={IconApps} + title="No apps to give yet" + > + Connect an app to let your Bots use it. + + ) : ( + + {rows.map((app, index) => { + const granted = app.access?.granted ?? false; + const locked = granted && !app.access?.givenByOwner; + const yours = + app.account === "personal" + ? `your ${app.displayName}` + : app.displayName; + return ( + + {index > 0 ? : null} + + + + + + {app.displayName} + + {locked + ? "An admin gave it to this Bot." + : granted + ? `${agent.name} can use ${yours}.` + : `${agent.name} can't use ${yours}.`} + + + + { + give.reset(); + takeBack.reset(); + const variables = { agentId: agent.id, app: app.key }; + if (next) give.mutate(variables); + else takeBack.mutate(variables); + }} + /> + + + + ); + })} + + )} +
+ ); +} + /** * Your own skills, one switch each. Only on your own Bot: the server lets a person put their own * skill on a Bot they own, and neither half alone is enough. @@ -177,7 +307,7 @@ function YourSkills({ agentId }: { agentId: string }) { * the person looking. * * READ-ONLY FOR GRANTS, NOT FOR THE PERSON'S OWN PART. Apps are granted by an administrator, who gets - * a link to where that is decided. But a Bot that holds Gmail is no use to somebody who has not + * a link to where that is decided — or, on a Bot you own, by you, under Your apps above. But a Bot that holds Gmail is no use to somebody who has not * connected their Gmail, and this list used to say "Gmail · 4 tools" to everybody alike — so each * app now says what it is for the person reading (`GET /api/plugins/for/:agentId/apps`) with the one * thing they can do about it: Connect, Reconnect, Request access, or Requested. The state is the @@ -187,7 +317,7 @@ function YourSkills({ agentId }: { agentId: string }) { * whoever is asking, exactly as the connections read does, so this cannot become a list of who has * connected what. */ -function Granted({ agentId }: { agentId: string }) { +function Granted({ agentId, mine }: { agentId: string; mine: boolean }) { const me = useQuery(currentUserQueryOptions()).data; const plugins = useQuery(agentPluginsQueryOptions(agentId)); const apps = useQuery(botAppsQueryOptions(agentId)); @@ -224,7 +354,9 @@ function Granted({ agentId }: { agentId: string }) { ) : null} {count === 0 ? ( - An admin has to add them. Until then this Bot can only talk with you. + {mine + ? "Turn one on under Your apps, or ask an admin. Until then this Bot can only talk with you." + : "An admin has to add them. Until then this Bot can only talk with you."} ) : ( diff --git a/app/tests/bot-access-your-apps.test.tsx b/app/tests/bot-access-your-apps.test.tsx new file mode 100644 index 0000000000..1601fff7c0 --- /dev/null +++ b/app/tests/bot-access-your-apps.test.tsx @@ -0,0 +1,169 @@ +import { afterAll, afterEach, beforeAll, expect, test } from "bun:test"; +import { GlobalRegistrator } from "@happy-dom/global-registrator"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { + createMemoryHistory, + createRootRoute, + createRouter, + RouterProvider, +} from "@tanstack/react-router"; +import { cleanup, fireEvent, render, waitFor } from "@testing-library/react"; +import { AccessSections } from "@/components/bot-profile/access"; +import type { AgentProfile } from "@/lib/agents/queries"; +import type { AppAccess } from "@/lib/app-access/queries"; +import { appAccess } from "./app-access-fixture"; +import { settleReactWork } from "./settle-react-work"; + +/** + * Your apps, on a Bot you own: one switch per app you connected that an admin lets owners give. + * Switching one on gives it; switching it off takes back what you gave; an app an admin gave stays + * on and cannot be switched by you. Not drawn at all on somebody else's Bot. + */ + +beforeAll(() => GlobalRegistrator.register()); +afterEach(cleanup); +afterAll(async () => { + await settleReactWork(); + GlobalRegistrator.unregister(); +}); + +const originalFetch = global.fetch; +afterEach(() => { + global.fetch = originalFetch; +}); + +const BOT: AgentProfile = { + id: "expenses", + name: "Expenses", + title: "Finance", + roleDescription: "Receipts.", + avatarSeed: "e", + visibility: "private", + endpoint: null, + builtIn: true, + hasAuth: false, + hasCallbackToken: false, + hidden: false, + pinned: false, + systemOwned: false, + canManage: true, + mine: true, +}; + +const GMAIL = { + key: "composio-gmail", + displayName: "Gmail", + logo: null, + account: "personal", +}; +const NOTION = { + key: "notion", + displayName: "Notion", + logo: null, + account: "personal", +}; + +function serving(held: AppAccess[]) { + const writes: string[] = []; + global.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => { + const url = String(input); + const method = init?.method ?? "GET"; + if (url.endsWith("/give")) { + writes.push(`${method} ${url}`); + return Response.json({ granted: [], revoked: [] }); + } + if (url === "/api/plugins/giveable") + return Response.json({ apps: [GMAIL, NOTION] }); + if (url.includes("/api/plugins/for/") && url.endsWith("/apps")) + return Response.json({ apps: held }); + if (url.includes("/api/plugins/for/")) + return Response.json({ tools: [], skills: [] }); + if (url.endsWith("/api/plugins")) + return Response.json({ + catalogue: [], + servers: [], + botsMayCallBack: true, + redirectUri: null, + skills: [], + }); + if (url.includes("/handoff")) + return Response.json({ + handoff: { + enabled: true, + canGrant: false, + reachable: [], + grantable: true, + }, + }); + if (url.includes("/api/agents")) return Response.json({ agents: [] }); + return Response.json({ + user: { id: "me", role: "user", email: "me@example.test", name: "Me" }, + }); + }) as unknown as typeof fetch; + return writes; +} + +function draw(agent: AgentProfile) { + const client = new QueryClient({ + defaultOptions: { queries: { retry: false } }, + }); + const router = createRouter({ + history: createMemoryHistory({ initialEntries: ["/"] }), + routeTree: createRootRoute({ + component: () => , + }), + }); + return render( + + + , + ); +} + +test("each app you could give has a switch, on where you gave it, and pressing it gives or takes back", async () => { + const writes = serving([ + appAccess({ key: "notion", displayName: "Notion", givenByOwner: true }), + ]); + const view = draw(BOT); + const gmail = await view.findByRole("switch", { + name: "Let Expenses use Gmail", + }); + const notion = view.getByRole("switch", { name: "Let Expenses use Notion" }); + expect(gmail.getAttribute("aria-checked")).toBe("false"); + expect(notion.getAttribute("aria-checked")).toBe("true"); + expect(view.getByText("Expenses can't use your Gmail.")).toBeTruthy(); + + fireEvent.click(gmail); + await waitFor(() => + expect(writes).toContain( + "POST /api/plugins/for/expenses/apps/composio-gmail/give", + ), + ); + fireEvent.click(notion); + await waitFor(() => + expect(writes).toContain( + "DELETE /api/plugins/for/expenses/apps/notion/give", + ), + ); +}); + +test("an app an admin gave this Bot is on, and not yours to switch off", async () => { + serving([appAccess({ givenByOwner: false })]); + const view = draw(BOT); + const gmail = await view.findByRole("switch", { + name: "Let Expenses use Gmail", + }); + expect(gmail.getAttribute("aria-checked")).toBe("true"); + expect( + gmail.hasAttribute("disabled") || + gmail.getAttribute("aria-disabled") === "true", + ).toBe(true); + expect(view.getByText("An admin gave it to this Bot.")).toBeTruthy(); +}); + +test("somebody else's Bot has no Your apps section", async () => { + serving([]); + const view = draw({ ...BOT, mine: false }); + await view.findByText("What it can use"); + expect(view.queryByText("Your apps")).toBeNull(); +}); diff --git a/app/tests/new-bot-sheet.test.tsx b/app/tests/new-bot-sheet.test.tsx index 7249b34cb0..4547f77f53 100644 --- a/app/tests/new-bot-sheet.test.tsx +++ b/app/tests/new-bot-sheet.test.tsx @@ -57,12 +57,22 @@ let verdict: { ok: true; events: string[] } | { ok: false; reason: string }; let creates: Record[] = []; /** Every address the server was asked to reach. */ let dialled: string[] = []; +/** The person's own apps the server offers to give a new Bot, and every give it was sent. */ +let giveable: { + key: string; + displayName: string; + logo: null; + account: string; +}[] = []; +let given: string[] = []; beforeEach(() => { builtInAvailable = true; verdict = { ok: true, events: ["RUN_STARTED"] }; creates = []; dialled = []; + giveable = []; + given = []; global.fetch = Object.assign( async (path: Parameters[0], init?: RequestInit) => { const body = init?.body ? JSON.parse(String(init.body)) : undefined; @@ -77,6 +87,12 @@ beforeEach(() => { creates.push(body); return Response.json({ agent: { id: "agent_new" } }, { status: 201 }); } + if (path === "/api/plugins/giveable") + return Response.json({ apps: giveable }); + if (String(path).endsWith("/give") && init?.method === "POST") { + given.push(String(path)); + return Response.json({ granted: [] }); + } return new Response(null, { status: 404 }); }, { preconnect: originalFetch.preconnect }, @@ -289,3 +305,42 @@ test("going back keeps what was answered, and Run it here leaves a typed address expect(creates[0]).toMatchObject({ endpoint: "", visibility: "public" }); expect(creates[0]).not.toHaveProperty("auth"); }); + +test("the person's own apps arrive ticked, and the ones left ticked are given once the Bot exists", async () => { + giveable = [ + { + key: "composio-gmail", + displayName: "Gmail", + logo: null, + account: "personal", + }, + { + key: "routines", + displayName: "Routines", + logo: null, + account: "deployment", + }, + ]; + const { view, user } = await opened(); + await user.click(view.getByRole("button", { name: "Continue" })); + await user.type(await view.findByLabelText("Name"), "Research"); + const gmail = await view.findByRole("checkbox", { name: "Gmail" }); + const routines = view.getByRole("checkbox", { name: "Routines" }); + expect(gmail.getAttribute("aria-checked")).toBe("true"); + expect(routines.getAttribute("aria-checked")).toBe("true"); + await user.click(routines); + expect(routines.getAttribute("aria-checked")).toBe("false"); + await user.click(view.getByRole("button", { name: "Create" })); + expect(await view.findByText("Research is ready")).toBeTruthy(); + expect(given).toEqual([ + "/api/plugins/for/agent_new/apps/composio-gmail/give", + ]); +}); + +test("a person with no apps to give is asked nothing about apps", async () => { + const { view, user } = await opened(); + await user.click(view.getByRole("button", { name: "Continue" })); + await view.findByLabelText("Name"); + await settle(); + expect(view.queryByText("Apps it can use")).toBeNull(); +}); From a40eda6221d5eac24096d74e3efeebfddc353015 Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 12:58:52 -0300 Subject: [PATCH 08/19] Refuse an owner a grant on an action their app does not advertise --- server/src/plugins/route-groups/grants.ts | 18 +++++++++++++- .../owner-app-grants.integration.test.ts | 24 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/server/src/plugins/route-groups/grants.ts b/server/src/plugins/route-groups/grants.ts index 61ebc11e98..51909e15c9 100644 --- a/server/src/plugins/route-groups/grants.ts +++ b/server/src/plugins/route-groups/grants.ts @@ -113,7 +113,23 @@ export function mountGrantRoutes({ : null; if (!inspected?.rowIds.includes(serverId ?? "")) return `${serverId} hasn't been added yet. Only an admin can add it.`; - return ownerGrantRefusal(store, actorId, agentId, inspected); + const refusal = await ownerGrantRefusal( + store, + actorId, + agentId, + inspected, + ); + if (refusal) return refusal; + /* + * AN ACTION THE APP ADVERTISES NOW, NOT ANY WORD AFTER THE SLASH. An administrator may grant a + * tool a server has stopped listing, to restore it (see `enablementRefusal`). An owner may not: + * a ref naming nothing would sit there until the vendor shipped an action by that name, and the + * Bot would then hold it with nobody having chosen it — and a bare server ref would mark the app + * given without giving it a single action. + */ + if (!(await store.appTools(serverId ?? "")).some((tool) => tool.ref === ref)) + return `${inspected.access.displayName} doesn't have that action.`; + return null; } /** diff --git a/server/tests/owner-app-grants.integration.test.ts b/server/tests/owner-app-grants.integration.test.ts index b1319c9455..034ffcb45c 100644 --- a/server/tests/owner-app-grants.integration.test.ts +++ b/server/tests/owner-app-grants.integration.test.ts @@ -371,6 +371,30 @@ describe("an owner giving their own app to their own Bot", () => { ]); }); + test("the grant route refuses an owner a ref the app does not advertise, and records nothing over an admin's grant", async () => { + const bot = await botOf(owner); + for (const ref of [`${GMAIL}/LATER`, GMAIL]) { + const refused = await appAs(owner).request( + "/api/plugins/grants", + send("POST", { kind: "mcp", ref, agentId: bot }), + ); + expect(refused.status).toBe(403); + } + expect(await grantsOn(bot)).toEqual([]); + + await store.grant("mcp", `${GMAIL}/FETCH`, bot, "admin@example.test"); + events.length = 0; + const again = await appAs(owner).request( + "/api/plugins/grants", + send("POST", { kind: "mcp", ref: `${GMAIL}/FETCH`, agentId: bot }), + ); + expect(again.status).toBe(200); + expect(await grantsOn(bot)).toEqual([ + { ref: `${GMAIL}/FETCH`, grantedAs: "admin" }, + ]); + expect(events).toEqual([]); + }); + test("the apps a person could give are the ones they connected, and open to owners", async () => { await seedApp(SLACK, "Slack", "shared"); const listed = async () => From 0e83dd3c49089d8f1f3566e71cf66bf1050f9b90 Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 12:58:52 -0300 Subject: [PATCH 09/19] Record nothing when an owner's grant lands on one that already stands --- server/src/plugins/plugin-store/grants.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/server/src/plugins/plugin-store/grants.ts b/server/src/plugins/plugin-store/grants.ts index bb6b285a3a..d49612efe3 100644 --- a/server/src/plugins/plugin-store/grants.ts +++ b/server/src/plugins/plugin-store/grants.ts @@ -32,8 +32,17 @@ export function grantMethods(context: StoreContext) { const insert = database .insert(pluginGrants) .values({ kind, ref, agentId, grantedBy: by, grantedAs: as }); - if (as === "owner") await insert.onConflictDoNothing(); - else + if (as === "owner") { + /* + * Nothing written, nothing in the trail. A row already there is an administrator's grant + * or the owner's own from before, and a `plugin_granted` row marked `owner` over an + * administrator's grant would tell a reader the owner had given what an administrator did. + */ + const written = await insert + .onConflictDoNothing() + .returning({ ref: pluginGrants.ref }); + if (written.length === 0) return; + } else await insert.onConflictDoUpdate({ target: [pluginGrants.kind, pluginGrants.ref, pluginGrants.agentId], set: { grantedBy: by, grantedAs: "admin", updatedAt: new Date() }, From d864e92487fbd6126e5c47fbffc3669233756fbe Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 12:58:52 -0300 Subject: [PATCH 10/19] Say where an owner gives a write action, in the give route's note --- server/src/plugins/route-groups/app-access.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/src/plugins/route-groups/app-access.ts b/server/src/plugins/route-groups/app-access.ts index f957345155..07338c93a0 100644 --- a/server/src/plugins/route-groups/app-access.ts +++ b/server/src/plugins/route-groups/app-access.ts @@ -626,8 +626,9 @@ export function mountAppAccessRoutes({ * * WHAT IS GIVEN IS WHAT AN ADMINISTRATOR'S ONE PRESS GIVES: the app's read-only actions, through * the same `appTools` reading, so an owner never gets more by asking themselves than an - * administrator's approval would have given. Anything that sends or writes is still chosen action by - * action by an administrator. ONE EXCEPTION, the built-in Routines: every action it has keeps, + * administrator's approval would have given. Anything that sends or writes is chosen action by + * action: by an administrator, or by the owner one advertised action at a time through + * `POST /grants`, still on the asker's own account and still behind the approval gate. ONE EXCEPTION, the built-in Routines: every action it has keeps, * changes or drops the asking person's own standing instructions and nothing else, and "every * morning, …" is a write — given only its reads, a Bot could list routines and never make one. * From e4eba71ffa2bf33d9674494faf32fc357d0bdbdd Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 12:59:20 -0300 Subject: [PATCH 11/19] Format the owner grant refusal --- server/src/plugins/route-groups/grants.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/server/src/plugins/route-groups/grants.ts b/server/src/plugins/route-groups/grants.ts index 51909e15c9..7570622118 100644 --- a/server/src/plugins/route-groups/grants.ts +++ b/server/src/plugins/route-groups/grants.ts @@ -113,12 +113,7 @@ export function mountGrantRoutes({ : null; if (!inspected?.rowIds.includes(serverId ?? "")) return `${serverId} hasn't been added yet. Only an admin can add it.`; - const refusal = await ownerGrantRefusal( - store, - actorId, - agentId, - inspected, - ); + const refusal = await ownerGrantRefusal(store, actorId, agentId, inspected); if (refusal) return refusal; /* * AN ACTION THE APP ADVERTISES NOW, NOT ANY WORD AFTER THE SLASH. An administrator may grant a @@ -127,7 +122,9 @@ export function mountGrantRoutes({ * Bot would then hold it with nobody having chosen it — and a bare server ref would mark the app * given without giving it a single action. */ - if (!(await store.appTools(serverId ?? "")).some((tool) => tool.ref === ref)) + if ( + !(await store.appTools(serverId ?? "")).some((tool) => tool.ref === ref) + ) return `${inspected.access.displayName} doesn't have that action.`; return null; } From 93070cabe90e2285b5242a01074b4046e539824a Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 13:15:56 -0300 Subject: [PATCH 12/19] Hold the owner's give call to an absent body, and follow the moved siblingReads line in the allow-list --- app/tests/app-access-action.test.tsx | 2 +- server/tests/assertion-shape.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/tests/app-access-action.test.tsx b/app/tests/app-access-action.test.tsx index 726257314d..5746399762 100644 --- a/app/tests/app-access-action.test.tsx +++ b/app/tests/app-access-action.test.tsx @@ -556,7 +556,7 @@ test("an owner's Allow gives the app in one press, naming the Bot and the app an ); fireEvent.click(await view.findByRole("button", { name: /^Let .* Gmail$/ })); await waitFor(() => - expect(sent.filter((call) => call.method === "POST")).toEqual([ + expect(sent.filter((call) => call.method === "POST")).toStrictEqual([ { method: "POST", url: "/api/plugins/for/expenses/apps/composio-gmail/give", diff --git a/server/tests/assertion-shape.test.ts b/server/tests/assertion-shape.test.ts index 83f13618c2..6f96f3038f 100644 --- a/server/tests/assertion-shape.test.ts +++ b/server/tests/assertion-shape.test.ts @@ -890,7 +890,7 @@ const ALLOW: Allowed[] = [ }, { file: "app/tests/connected-account-grants.test.tsx", - line: 124, + line: 126, shape: "e", finding: "NEW (round 2 did not find this)", why: "const siblingReads: string[] = [];", From 70d76ea31f3931939511cb7bfd7c95d32cded06d Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 13:23:12 -0300 Subject: [PATCH 13/19] Add a changelog line for owner app grants --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ae34840b9..1fa40a013b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,17 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged. - Migration `0062_action_policy_labels` adds a `labels` column to `action_policies`, defaulting to empty. Existing boundary rules keep working and show as "Custom rule" until somebody names them. +### A Bot's owner can give it their own apps + +The owner of a Bot can now let it use an app they connect for themselves, without an administrator: +from the app card ("Let Research Bot use your Gmail?"), under Your apps on the Bot's page, or on the +New Bot sheet, which offers their connected apps. Calls under such a grant always use the asking +person's own account, even on a published Team Bot, and an owner can take back only what they gave. +By default this covers apps each person connects for themselves and Routines; an administrator +decides per app on its page, and can switch it off for people with the "Give apps to their own Bots" +capability. Migration `0063_owner_app_grants` adds two nullable columns, `mcp_servers.owner_grants` +and `plugin_grants.granted_as`. + ### An app card carries the task on once the app is ready When a Bot stopped because it needed an app, and the person then connected it in another tab or an From 3848c8f37835b25ace7d272d635271c968c50388 Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 13:47:59 -0300 Subject: [PATCH 14/19] Mark a Shared-account audience an owner's grant set, so it can be withdrawn with it --- server/drizzle/0064_owner_shared_audience.sql | 2 + server/drizzle/meta/0064_snapshot.json | 10214 ++++++++++++++++ server/drizzle/meta/_journal.json | 7 + server/src/db/schema/shared-use.ts | 14 + 4 files changed, 10237 insertions(+) create mode 100644 server/drizzle/0064_owner_shared_audience.sql create mode 100644 server/drizzle/meta/0064_snapshot.json diff --git a/server/drizzle/0064_owner_shared_audience.sql b/server/drizzle/0064_owner_shared_audience.sql new file mode 100644 index 0000000000..dec812ad9b --- /dev/null +++ b/server/drizzle/0064_owner_shared_audience.sql @@ -0,0 +1,2 @@ +ALTER TABLE "shared_use_approvals" ADD COLUMN "approved_as" text;--> statement-breakpoint +ALTER TABLE "shared_use_approvals" ADD CONSTRAINT "shared_use_approvals_approved_as_check" CHECK ("shared_use_approvals"."approved_as" IS NULL OR "shared_use_approvals"."approved_as" IN ('admin', 'owner')); \ No newline at end of file diff --git a/server/drizzle/meta/0064_snapshot.json b/server/drizzle/meta/0064_snapshot.json new file mode 100644 index 0000000000..266f941e61 --- /dev/null +++ b/server/drizzle/meta/0064_snapshot.json @@ -0,0 +1,10214 @@ +{ + "id": "69984b21-7076-4157-887e-a7f06e2e3845", + "prevId": "59e401eb-8e91-4157-9ffd-c9e3aeb2dfab", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.accounts": { + "name": "accounts", + "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 + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "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 with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "accounts_provider_account_idx": { + "name": "accounts_provider_account_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "agent_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "configuration": { + "name": "configuration", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "package_id": { + "name": "package_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "override": { + "name": "override", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agents_package_id_deployment_packages_id_fk": { + "name": "agents_package_id_deployment_packages_id_fk", + "tableFrom": "agents", + "tableTo": "deployment_packages", + "columnsFrom": [ + "package_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.attachments": { + "name": "attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "bytes": { + "name": "bytes", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "attached_at": { + "name": "attached_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "upload_group": { + "name": "upload_group", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "attachments_channel_idx": { + "name": "attachments_channel_idx", + "columns": [ + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "attachments_uploaded_by_idx": { + "name": "attachments_uploaded_by_idx", + "columns": [ + { + "expression": "uploaded_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "attachments_upload_group_idx": { + "name": "attachments_upload_group_idx", + "columns": [ + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "uploaded_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "upload_group", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"attachments\".\"attached_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "attachments_staged_idx": { + "name": "attachments_staged_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"attachments\".\"attached_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "attachments_channel_id_channels_id_fk": { + "name": "attachments_channel_id_channels_id_fk", + "tableFrom": "attachments", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "attachments_uploaded_by_users_id_fk": { + "name": "attachments_uploaded_by_users_id_fk", + "tableFrom": "attachments", + "tableTo": "users", + "columnsFrom": [ + "uploaded_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_events": { + "name": "audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "initiator_kind": { + "name": "initiator_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'person'" + }, + "initiator_id": { + "name": "initiator_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_events_created_at_idx": { + "name": "audit_events_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_events_type_time_idx": { + "name": "audit_events_type_time_idx", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_events_actor_time_idx": { + "name": "audit_events_actor_time_idx", + "columns": [ + { + "expression": "actor_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_events_target_time_idx": { + "name": "audit_events_target_time_idx", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_events_initiator_time_idx": { + "name": "audit_events_initiator_time_idx", + "columns": [ + { + "expression": "initiator_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.channel_agents": { + "name": "channel_agents", + "schema": "", + "columns": { + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "channel_agents_channel_id_channels_id_fk": { + "name": "channel_agents_channel_id_channels_id_fk", + "tableFrom": "channel_agents", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channel_agents_agent_id_agents_id_fk": { + "name": "channel_agents_agent_id_agents_id_fk", + "tableFrom": "channel_agents", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "channel_agents_channel_id_agent_id_pk": { + "name": "channel_agents_channel_id_agent_id_pk", + "columns": [ + "channel_id", + "agent_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.channel_memberships": { + "name": "channel_memberships", + "schema": "", + "columns": { + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pinned_at": { + "name": "pinned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "channel_memberships_channel_id_channels_id_fk": { + "name": "channel_memberships_channel_id_channels_id_fk", + "tableFrom": "channel_memberships", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channel_memberships_user_id_users_id_fk": { + "name": "channel_memberships_user_id_users_id_fk", + "tableFrom": "channel_memberships", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "channel_memberships_channel_id_user_id_pk": { + "name": "channel_memberships_channel_id_user_id_pk", + "columns": [ + "channel_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.channels": { + "name": "channels", + "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": true + }, + "suggested_prompts": { + "name": "suggested_prompts", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "allowed_groups": { + "name": "allowed_groups", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "package_id": { + "name": "package_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "override": { + "name": "override", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary_at": { + "name": "summary_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_message": { + "name": "last_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_message_source_id": { + "name": "last_message_source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_message_at": { + "name": "last_message_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_message_agent_id": { + "name": "last_message_agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "channels_recent_activity_idx": { + "name": "channels_recent_activity_idx", + "columns": [ + { + "expression": "COALESCE(\"last_message_at\", \"created_at\") DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "channels_awaiting_summary_idx": { + "name": "channels_awaiting_summary_idx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"channels\".\"summary\" is null and \"channels\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "channels_package_id_deployment_packages_id_fk": { + "name": "channels_package_id_deployment_packages_id_fk", + "tableFrom": "channels", + "tableTo": "deployment_packages", + "columnsFrom": [ + "package_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "channels_last_message_agent_id_agents_id_fk": { + "name": "channels_last_message_agent_id_agents_id_fk", + "tableFrom": "channels", + "tableTo": "agents", + "columnsFrom": [ + "last_message_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credentials": { + "name": "credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "kind": { + "name": "kind", + "type": "credential_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_value": { + "name": "encrypted_value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credentials_active_key_idx": { + "name": "credentials_active_key_idx", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credentials\".\"revoked_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment_packages": { + "name": "deployment_packages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_path": { + "name": "source_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "loaded_at": { + "name": "loaded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "deployment_packages_tenant_id_unique": { + "name": "deployment_packages_tenant_id_unique", + "nullsNotDistinct": false, + "columns": [ + "tenant_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.intelligence_channel_mappings": { + "name": "intelligence_channel_mappings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "intelligence_channel_mappings_thread_idx": { + "name": "intelligence_channel_mappings_thread_idx", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "intelligence_channel_mappings_user_id_users_id_fk": { + "name": "intelligence_channel_mappings_user_id_users_id_fk", + "tableFrom": "intelligence_channel_mappings", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "intelligence_channel_mappings_channel_id_channels_id_fk": { + "name": "intelligence_channel_mappings_channel_id_channels_id_fk", + "tableFrom": "intelligence_channel_mappings", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "intelligence_channel_mappings_user_id_channel_id_pk": { + "name": "intelligence_channel_mappings_user_id_channel_id_pk", + "columns": [ + "user_id", + "channel_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.revoked_access": { + "name": "revoked_access", + "schema": "", + "columns": { + "email": { + "name": "email", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_by": { + "name": "revoked_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_token_unique": { + "name": "sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_providers": { + "name": "sso_providers", + "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 + }, + "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 + } + }, + "indexes": {}, + "foreignKeys": { + "sso_providers_user_id_users_id_fk": { + "name": "sso_providers_user_id_users_id_fk", + "tableFrom": "sso_providers", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sso_providers_provider_id_unique": { + "name": "sso_providers_provider_id_unique", + "nullsNotDistinct": false, + "columns": [ + "provider_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_instructions": { + "name": "user_instructions", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instructions": { + "name": "instructions", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_instructions_user_id_users_id_fk": { + "name": "user_instructions_user_id_users_id_fk", + "tableFrom": "user_instructions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_roles_user_id_role_pk": { + "name": "user_roles_user_id_role_pk", + "columns": [ + "user_id", + "role" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "preferences": { + "name": "preferences", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "groups": { + "name": "groups", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "onboarding_step": { + "name": "onboarding_step", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "onboarding_completed_at": { + "name": "onboarding_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_signed_in_at": { + "name": "last_signed_in_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verifications": { + "name": "verifications", + "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 with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.action_policy": { + "name": "action_policy", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deny": { + "name": "deny", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "allow": { + "name": "allow", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "labels": { + "name": "labels", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.computer_page_frame": { + "name": "computer_page_frame", + "schema": "", + "columns": { + "computer_id": { + "name": "computer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "frame": { + "name": "frame", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "computer_page_frame_captured_idx": { + "name": "computer_page_frame_captured_idx", + "columns": [ + { + "expression": "captured_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "computer_page_frame_computer_id_tool_call_id_pk": { + "name": "computer_page_frame_computer_id_tool_call_id_pk", + "columns": [ + "computer_id", + "tool_call_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.computer_snapshot": { + "name": "computer_snapshot", + "schema": "", + "columns": { + "computer_id": { + "name": "computer_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "snapshot_id": { + "name": "snapshot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "elements": { + "name": "elements", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "taken_at": { + "name": "taken_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "session": { + "name": "session", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_preferences": { + "name": "agent_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hidden_at": { + "name": "hidden_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "pinned_at": { + "name": "pinned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_preferences_user_id_users_id_fk": { + "name": "agent_preferences_user_id_users_id_fk", + "tableFrom": "agent_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_preferences_agent_id_agents_id_fk": { + "name": "agent_preferences_agent_id_agents_id_fk", + "tableFrom": "agent_preferences", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "agent_preferences_user_id_agent_id_pk": { + "name": "agent_preferences_user_id_agent_id_pk", + "columns": [ + "user_id", + "agent_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_profiles": { + "name": "agent_profiles", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_description": { + "name": "role_description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "avatar_seed": { + "name": "avatar_seed", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visibility": { + "name": "visibility", + "type": "agent_visibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "callback_token_hash": { + "name": "callback_token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "callback_token_issued_at": { + "name": "callback_token_issued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_profiles_visibility_deleted_idx": { + "name": "agent_profiles_visibility_deleted_idx", + "columns": [ + { + "expression": "visibility", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_profiles_agent_id_agents_id_fk": { + "name": "agent_profiles_agent_id_agents_id_fk", + "tableFrom": "agent_profiles", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_profiles_owner_user_id_users_id_fk": { + "name": "agent_profiles_owner_user_id_users_id_fk", + "tableFrom": "agent_profiles", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "routine_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "waiting": { + "name": "waiting", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'schedule'" + } + }, + "indexes": { + "routine_runs_by_routine_idx": { + "name": "routine_runs_by_routine_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_sweeps": { + "name": "routine_sweeps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "swept_at": { + "name": "swept_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instruction": { + "name": "instruction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cron": { + "name": "cron", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "enabled_at": { + "name": "enabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routines_due_idx": { + "name": "routines_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_by_owner_idx": { + "name": "routines_by_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_owner_user_id_users_id_fk": { + "name": "routines_owner_user_id_users_id_fk", + "tableFrom": "routines", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_agent_id_agents_id_fk": { + "name": "routines_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.component_exclusions": { + "name": "component_exclusions", + "schema": "", + "columns": { + "component_name": { + "name": "component_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "withheld_by": { + "name": "withheld_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "component_exclusions_component_name_components_name_fk": { + "name": "component_exclusions_component_name_components_name_fk", + "tableFrom": "component_exclusions", + "tableTo": "components", + "columnsFrom": [ + "component_name" + ], + "columnsTo": [ + "name" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "component_exclusions_agent_id_agents_id_fk": { + "name": "component_exclusions_agent_id_agents_id_fk", + "tableFrom": "component_exclusions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "component_exclusions_component_name_agent_id_pk": { + "name": "component_exclusions_component_name_agent_id_pk", + "columns": [ + "component_name", + "agent_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.component_functions": { + "name": "component_functions", + "schema": "", + "columns": { + "component_name": { + "name": "component_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "function_name": { + "name": "function_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "component_functions_component_name_components_name_fk": { + "name": "component_functions_component_name_components_name_fk", + "tableFrom": "component_functions", + "tableTo": "components", + "columnsFrom": [ + "component_name" + ], + "columnsTo": [ + "name" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "component_functions_component_name_function_name_pk": { + "name": "component_functions_component_name_function_name_pk", + "columns": [ + "component_name", + "function_name" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.components": { + "name": "components", + "schema": "", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "draft_description": { + "name": "draft_description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "published_description": { + "name": "published_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.app_access_requests": { + "name": "app_access_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "app_key": { + "name": "app_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app_title": { + "name": "app_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_by": { + "name": "resolved_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "app_access_requests_pending_idx": { + "name": "app_access_requests_pending_idx", + "columns": [ + { + "expression": "requested_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "app_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"agent_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"app_access_requests\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "app_access_requests_status_idx": { + "name": "app_access_requests_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "app_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "app_access_requests_requested_by_users_id_fk": { + "name": "app_access_requests_requested_by_users_id_fk", + "tableFrom": "app_access_requests", + "tableTo": "users", + "columnsFrom": [ + "requested_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "app_access_requests_agent_id_agents_id_fk": { + "name": "app_access_requests_agent_id_agents_id_fk", + "tableFrom": "app_access_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "app_access_requests_kind_check": { + "name": "app_access_requests_kind_check", + "value": "\"app_access_requests\".\"kind\" IN ('enable', 'grant', 'connect_shared')" + }, + "app_access_requests_status_check": { + "name": "app_access_requests_status_check", + "value": "\"app_access_requests\".\"status\" IN ('pending', 'approved', 'dismissed', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.brokered_connections": { + "name": "brokered_connections", + "schema": "", + "columns": { + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app": { + "name": "app", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "holder": { + "name": "holder", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vendor_user_id": { + "name": "vendor_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connected_by": { + "name": "connected_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "probe_action": { + "name": "probe_action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "brokered_connections_person_idx": { + "name": "brokered_connections_person_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "app", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"brokered_connections\".\"holder\" = 'person'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "brokered_connections_deployment_idx": { + "name": "brokered_connections_deployment_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "app", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"brokered_connections\".\"holder\" = 'deployment'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "brokered_connections_user_idx": { + "name": "brokered_connections_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "brokered_connections_holder_check": { + "name": "brokered_connections_holder_check", + "value": "(\"brokered_connections\".\"holder\" = 'person' AND \"brokered_connections\".\"user_id\" IS NOT NULL) OR (\"brokered_connections\".\"holder\" = 'deployment' AND \"brokered_connections\".\"user_id\" IS NULL)" + }, + "brokered_connections_provider_check": { + "name": "brokered_connections_provider_check", + "value": "\"brokered_connections\".\"provider\" IN ('composio', 'arcade')" + } + }, + "isRLSEnabled": false + }, + "public.composio_connections": { + "name": "composio_connections", + "schema": "", + "columns": { + "toolkit": { + "name": "toolkit", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "probe_action": { + "name": "probe_action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "composio_connections_user_idx": { + "name": "composio_connections_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "composio_connections_toolkit_user_id_pk": { + "name": "composio_connections_toolkit_user_id_pk", + "columns": [ + "toolkit", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_servers": { + "name": "mcp_servers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vendor": { + "name": "vendor", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provenance": { + "name": "provenance", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'first-party'" + }, + "credential_id": { + "name": "credential_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "auth_scheme": { + "name": "auth_scheme", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_mode": { + "name": "account_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_vendor_user_id": { + "name": "shared_vendor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_grants": { + "name": "owner_grants", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tools_refreshed_at": { + "name": "tools_refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "skipped_tools": { + "name": "skipped_tools", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_servers_credential_id_credentials_id_fk": { + "name": "mcp_servers_credential_id_credentials_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_servers_account_mode_check": { + "name": "mcp_servers_account_mode_check", + "value": "\"mcp_servers\".\"account_mode\" IS NULL OR \"mcp_servers\".\"account_mode\" IN ('personal', 'shared')" + }, + "mcp_servers_owner_grants_check": { + "name": "mcp_servers_owner_grants_check", + "value": "\"mcp_servers\".\"owner_grants\" IS NULL OR \"mcp_servers\".\"owner_grants\" IN ('any', 'admin')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_tools": { + "name": "mcp_tools", + "schema": "", + "columns": { + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vendor_name": { + "name": "vendor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "input_schema": { + "name": "input_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "destructive": { + "name": "destructive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_tools_server_id_mcp_servers_id_fk": { + "name": "mcp_tools_server_id_mcp_servers_id_fk", + "tableFrom": "mcp_tools", + "tableTo": "mcp_servers", + "columnsFrom": [ + "server_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "mcp_tools_server_id_name_pk": { + "name": "mcp_tools_server_id_name_pk", + "columns": [ + "server_id", + "name" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_user_credentials": { + "name": "mcp_user_credentials", + "schema": "", + "columns": { + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_user_credentials_user_idx": { + "name": "mcp_user_credentials_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_user_credentials_server_id_mcp_servers_id_fk": { + "name": "mcp_user_credentials_server_id_mcp_servers_id_fk", + "tableFrom": "mcp_user_credentials", + "tableTo": "mcp_servers", + "columnsFrom": [ + "server_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_user_credentials_user_id_users_id_fk": { + "name": "mcp_user_credentials_user_id_users_id_fk", + "tableFrom": "mcp_user_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_user_credentials_credential_id_credentials_id_fk": { + "name": "mcp_user_credentials_credential_id_credentials_id_fk", + "tableFrom": "mcp_user_credentials", + "tableTo": "credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "mcp_user_credentials_server_id_user_id_pk": { + "name": "mcp_user_credentials_server_id_user_id_pk", + "columns": [ + "server_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_authorizations": { + "name": "pending_authorizations", + "schema": "", + "columns": { + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "holder": { + "name": "holder", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vendor_user_id": { + "name": "vendor_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_id": { + "name": "auth_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "pending_authorizations_server_idx": { + "name": "pending_authorizations_server_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pending_authorizations_actor_id_users_id_fk": { + "name": "pending_authorizations_actor_id_users_id_fk", + "tableFrom": "pending_authorizations", + "tableTo": "users", + "columnsFrom": [ + "actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_authorizations_server_id_mcp_servers_id_fk": { + "name": "pending_authorizations_server_id_mcp_servers_id_fk", + "tableFrom": "pending_authorizations", + "tableTo": "mcp_servers", + "columnsFrom": [ + "server_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pending_authorizations_holder_check": { + "name": "pending_authorizations_holder_check", + "value": "(\"pending_authorizations\".\"holder\" = 'person' AND \"pending_authorizations\".\"user_id\" IS NOT NULL) OR (\"pending_authorizations\".\"holder\" = 'deployment' AND \"pending_authorizations\".\"user_id\" IS NULL)" + }, + "pending_authorizations_outcome_check": { + "name": "pending_authorizations_outcome_check", + "value": "\"pending_authorizations\".\"outcome\" IS NULL OR \"pending_authorizations\".\"outcome\" IN ('completed', 'failed', 'unreconciled', 'withdrawn')" + }, + "pending_authorizations_completion_check": { + "name": "pending_authorizations_completion_check", + "value": "(\"pending_authorizations\".\"completed_at\" IS NULL) = (\"pending_authorizations\".\"outcome\" IS NULL)" + }, + "pending_authorizations_user_is_actor_check": { + "name": "pending_authorizations_user_is_actor_check", + "value": "\"pending_authorizations\".\"user_id\" IS NULL OR \"pending_authorizations\".\"user_id\" = \"pending_authorizations\".\"actor_id\"" + } + }, + "isRLSEnabled": false + }, + "public.plugin_grants": { + "name": "plugin_grants", + "schema": "", + "columns": { + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ref": { + "name": "ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "granted_as": { + "name": "granted_as", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_grants_agent_idx": { + "name": "plugin_grants_agent_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_grants_agent_id_agents_id_fk": { + "name": "plugin_grants_agent_id_agents_id_fk", + "tableFrom": "plugin_grants", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_grants_kind_ref_agent_id_pk": { + "name": "plugin_grants_kind_ref_agent_id_pk", + "columns": [ + "kind", + "ref", + "agent_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "plugin_grants_granted_as_check": { + "name": "plugin_grants_granted_as_check", + "value": "\"plugin_grants\".\"granted_as\" IS NULL OR \"plugin_grants\".\"granted_as\" IN ('admin', 'owner')" + } + }, + "isRLSEnabled": false + }, + "public.sandboxed_components": { + "name": "sandboxed_components", + "schema": "", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "draft_description": { + "name": "draft_description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "draft_html": { + "name": "draft_html", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "draft_css": { + "name": "draft_css", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "draft_js_functions": { + "name": "draft_js_functions", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "draft_argument_schema": { + "name": "draft_argument_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "published_description": { + "name": "published_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published_html": { + "name": "published_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published_css": { + "name": "published_css", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published_js_functions": { + "name": "published_js_functions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published_argument_schema": { + "name": "published_argument_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "sample_arguments": { + "name": "sample_arguments", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "authored_by": { + "name": "authored_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skill_tools": { + "name": "skill_tools", + "schema": "", + "columns": { + "skill_id": { + "name": "skill_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ref": { + "name": "ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "declared_by": { + "name": "declared_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skill_tools_ref_idx": { + "name": "skill_tools_ref_idx", + "columns": [ + { + "expression": "ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skill_tools_skill_id_skills_id_fk": { + "name": "skill_tools_skill_id_skills_id_fk", + "tableFrom": "skill_tools", + "tableTo": "skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "skill_tools_skill_id_ref_pk": { + "name": "skill_tools_skill_id_ref_pk", + "columns": [ + "skill_id", + "ref" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skills": { + "name": "skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instructions": { + "name": "instructions", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'yours'" + }, + "installed_by": { + "name": "installed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skills_slug_key": { + "name": "skills_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "skills_owner_idx": { + "name": "skills_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skills_owner_user_id_users_id_fk": { + "name": "skills_owner_user_id_users_id_fk", + "tableFrom": "skills", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.work_items": { + "name": "work_items", + "schema": "", + "columns": { + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_at": { + "name": "run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_by": { + "name": "claimed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_until": { + "name": "lease_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "work_items_claimable_idx": { + "name": "work_items_claimable_idx", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "work_items_kind_key_pk": { + "name": "work_items_kind_key_pk", + "columns": [ + "kind", + "key" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.voice_sessions": { + "name": "voice_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_message_id": { + "name": "anchor_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "transcript": { + "name": "transcript", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary_status": { + "name": "summary_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'failed'" + } + }, + "indexes": { + "voice_sessions_channel_started_idx": { + "name": "voice_sessions_channel_started_idx", + "columns": [ + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "voice_sessions_channel_id_channels_id_fk": { + "name": "voice_sessions_channel_id_channels_id_fk", + "tableFrom": "voice_sessions", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "voice_sessions_user_id_users_id_fk": { + "name": "voice_sessions_user_id_users_id_fk", + "tableFrom": "voice_sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "voice_sessions_duration_check": { + "name": "voice_sessions_duration_check", + "value": "\"voice_sessions\".\"duration_seconds\" >= 0" + }, + "voice_sessions_summary_check": { + "name": "voice_sessions_summary_check", + "value": "(\"voice_sessions\".\"summary_status\" = 'ready' AND \"voice_sessions\".\"summary\" IS NOT NULL) OR (\"voice_sessions\".\"summary_status\" = 'failed' AND \"voice_sessions\".\"summary\" IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.learning_settings": { + "name": "learning_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.learning_thread_bindings": { + "name": "learning_thread_bindings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "container_id": { + "name": "container_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "learning_thread_bindings_user_id_thread_id_pk": { + "name": "learning_thread_bindings_user_id_thread_id_pk", + "columns": [ + "user_id", + "thread_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.responsibilities": { + "name": "responsibilities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instruction": { + "name": "instruction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "success_criteria": { + "name": "success_criteria", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "responsibility_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "progress": { + "name": "progress", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subscriptions": { + "name": "subscriptions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "responsibilities_owner_status_idx": { + "name": "responsibilities_owner_status_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "responsibilities_owner_user_id_users_id_fk": { + "name": "responsibilities_owner_user_id_users_id_fk", + "tableFrom": "responsibilities", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "responsibilities_agent_id_agents_id_fk": { + "name": "responsibilities_agent_id_agents_id_fk", + "tableFrom": "responsibilities", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.responsibility_events": { + "name": "responsibility_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "responsibility_events_source_id_idx": { + "name": "responsibility_events_source_id_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "responsibility_events_owner_user_id_users_id_fk": { + "name": "responsibility_events_owner_user_id_users_id_fk", + "tableFrom": "responsibility_events", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.responsibility_runs": { + "name": "responsibility_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "responsibility_id": { + "name": "responsibility_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "responsibility_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "reply_text": { + "name": "reply_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "waiting": { + "name": "waiting", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "continuation": { + "name": "continuation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "responsibility_runs_event_idx": { + "name": "responsibility_runs_event_idx", + "columns": [ + { + "expression": "responsibility_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "responsibility_runs_status_idx": { + "name": "responsibility_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "responsibility_runs_responsibility_id_responsibilities_id_fk": { + "name": "responsibility_runs_responsibility_id_responsibilities_id_fk", + "tableFrom": "responsibility_runs", + "tableTo": "responsibilities", + "columnsFrom": [ + "responsibility_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "responsibility_runs_event_id_responsibility_events_id_fk": { + "name": "responsibility_runs_event_id_responsibility_events_id_fk", + "tableFrom": "responsibility_runs", + "tableTo": "responsibility_events", + "columnsFrom": [ + "event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.responsibility_source_bindings": { + "name": "responsibility_source_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "responsibility_source_bindings_owner_idx": { + "name": "responsibility_source_bindings_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "responsibility_source_bindings_owner_user_id_users_id_fk": { + "name": "responsibility_source_bindings_owner_user_id_users_id_fk", + "tableFrom": "responsibility_source_bindings", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "responsibility_source_bindings_credential_id_credentials_id_fk": { + "name": "responsibility_source_bindings_credential_id_credentials_id_fk", + "tableFrom": "responsibility_source_bindings", + "tableTo": "credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.responsibility_triggers": { + "name": "responsibility_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsibility_id": { + "name": "responsibility_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "responsibility_triggers_owner_idx": { + "name": "responsibility_triggers_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "responsibility_triggers_responsibility_idx": { + "name": "responsibility_triggers_responsibility_idx", + "columns": [ + { + "expression": "responsibility_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "responsibility_triggers_kind_idx": { + "name": "responsibility_triggers_kind_idx", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "responsibility_triggers_owner_user_id_users_id_fk": { + "name": "responsibility_triggers_owner_user_id_users_id_fk", + "tableFrom": "responsibility_triggers", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "responsibility_triggers_responsibility_id_responsibilities_id_fk": { + "name": "responsibility_triggers_responsibility_id_responsibilities_id_fk", + "tableFrom": "responsibility_triggers", + "tableTo": "responsibilities", + "columnsFrom": [ + "responsibility_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "responsibility_triggers_credential_id_credentials_id_fk": { + "name": "responsibility_triggers_credential_id_credentials_id_fk", + "tableFrom": "responsibility_triggers", + "tableTo": "credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approval_preferences": { + "name": "approval_preferences", + "schema": "", + "columns": { + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "auto_review": { + "name": "auto_review", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "host_commands": { + "name": "host_commands", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ask'" + } + }, + "indexes": {}, + "foreignKeys": { + "approval_preferences_owner_user_id_users_id_fk": { + "name": "approval_preferences_owner_user_id_users_id_fk", + "tableFrom": "approval_preferences", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "approval_preferences_host_commands_check": { + "name": "approval_preferences_host_commands_check", + "value": "\"approval_preferences\".\"host_commands\" IN ('ask', 'allow', 'never')" + } + }, + "isRLSEnabled": false + }, + "public.approval_requests": { + "name": "approval_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action_digest": { + "name": "action_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "approval_requests_action_idx": { + "name": "approval_requests_action_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_requests_inbox_idx": { + "name": "approval_requests_inbox_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_requests_owner_user_id_users_id_fk": { + "name": "approval_requests_owner_user_id_users_id_fk", + "tableFrom": "approval_requests", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "approval_status_check": { + "name": "approval_status_check", + "value": "\"approval_requests\".\"status\" IN ('pending', 'approved', 'denied', 'consumed', 'completed')" + }, + "approval_decision_check": { + "name": "approval_decision_check", + "value": "\"approval_requests\".\"decision\" IN ('allow_once', 'allow_always', 'deny', 'handled')" + } + }, + "isRLSEnabled": false + }, + "public.approval_rules": { + "name": "approval_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_ref": { + "name": "tool_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "behaviour": { + "name": "behaviour", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'allow'" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_rules_owner_idx": { + "name": "approval_rules_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_rules_owner_user_id_users_id_fk": { + "name": "approval_rules_owner_user_id_users_id_fk", + "tableFrom": "approval_rules", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "approval_rules_behaviour_check": { + "name": "approval_rules_behaviour_check", + "value": "\"approval_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')" + } + }, + "isRLSEnabled": false + }, + "public.approval_team_rules": { + "name": "approval_team_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_ref": { + "name": "tool_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "behaviour": { + "name": "behaviour", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "approval_team_rules_created_by_users_id_fk": { + "name": "approval_team_rules_created_by_users_id_fk", + "tableFrom": "approval_team_rules", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "approval_team_rules_behaviour_check": { + "name": "approval_team_rules_behaviour_check", + "value": "\"approval_team_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')" + } + }, + "isRLSEnabled": false + }, + "public.approval_team_settings": { + "name": "approval_team_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "enforce_auto_review": { + "name": "enforce_auto_review", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "custom_rules_enabled": { + "name": "custom_rules_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "host_commands_cap": { + "name": "host_commands_cap", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'allow'" + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "approval_team_settings_updated_by_users_id_fk": { + "name": "approval_team_settings_updated_by_users_id_fk", + "tableFrom": "approval_team_settings", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "approval_team_settings_host_commands_cap_check": { + "name": "approval_team_settings_host_commands_cap_check", + "value": "\"approval_team_settings\".\"host_commands_cap\" IN ('ask', 'allow', 'never')" + } + }, + "isRLSEnabled": false + }, + "public.memory_sources": { + "name": "memory_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_ref": { + "name": "tool_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "sync_status": { + "name": "sync_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "sync_error": { + "name": "sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_sync_at": { + "name": "next_sync_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "memory_sources_owner_idx": { + "name": "memory_sources_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_sources_due_idx": { + "name": "memory_sources_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_sources_owner_user_id_users_id_fk": { + "name": "memory_sources_owner_user_id_users_id_fk", + "tableFrom": "memory_sources", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "memory_sources_agent_id_agents_id_fk": { + "name": "memory_sources_agent_id_agents_id_fk", + "tableFrom": "memory_sources", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.personal_memories": { + "name": "personal_memories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "import_digest": { + "name": "import_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provenance": { + "name": "provenance", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unreviewed'" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "formed_by": { + "name": "formed_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'person'" + }, + "formed_by_agent_id": { + "name": "formed_by_agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_app": { + "name": "source_app", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_link": { + "name": "source_link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "observed_at": { + "name": "observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "intelligence_memory_id": { + "name": "intelligence_memory_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "intelligence_digest": { + "name": "intelligence_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "personal_memories_owner_idx": { + "name": "personal_memories_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "personal_memories_intelligence_idx": { + "name": "personal_memories_intelligence_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "intelligence_memory_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "personal_memories_formed_idx": { + "name": "personal_memories_formed_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "formed_by_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "personal_memories_import_idx": { + "name": "personal_memories_import_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "personal_memories_formed_digest_idx": { + "name": "personal_memories_formed_digest_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "import_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"personal_memories\".\"formed_by\" = 'bot'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "personal_memories_owner_user_id_users_id_fk": { + "name": "personal_memories_owner_user_id_users_id_fk", + "tableFrom": "personal_memories", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "personal_memories_source_id_memory_sources_id_fk": { + "name": "personal_memories_source_id_memory_sources_id_fk", + "tableFrom": "personal_memories", + "tableTo": "memory_sources", + "columnsFrom": [ + "source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "personal_memories_formed_by_agent_id_agents_id_fk": { + "name": "personal_memories_formed_by_agent_id_agents_id_fk", + "tableFrom": "personal_memories", + "tableTo": "agents", + "columnsFrom": [ + "formed_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.default_opt_outs": { + "name": "default_opt_outs", + "schema": "", + "columns": { + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "feature": { + "name": "feature", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "default_opt_outs_owner_user_id_users_id_fk": { + "name": "default_opt_outs_owner_user_id_users_id_fk", + "tableFrom": "default_opt_outs", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "default_opt_outs_agent_id_agents_id_fk": { + "name": "default_opt_outs_agent_id_agents_id_fk", + "tableFrom": "default_opt_outs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "default_opt_outs_owner_user_id_agent_id_feature_scope_pk": { + "name": "default_opt_outs_owner_user_id_agent_id_feature_scope_pk", + "columns": [ + "owner_user_id", + "agent_id", + "feature", + "scope" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "default_opt_outs_feature_check": { + "name": "default_opt_outs_feature_check", + "value": "\"default_opt_outs\".\"feature\" IN ('memory_source', 'proactive_research')" + } + }, + "isRLSEnabled": false + }, + "public.proactive_settings": { + "name": "proactive_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "focus": { + "name": "focus", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "interval_minutes": { + "name": "interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 240 + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_status": { + "name": "last_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "proactive_settings_owner_agent_idx": { + "name": "proactive_settings_owner_agent_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "proactive_settings_due_idx": { + "name": "proactive_settings_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "proactive_settings_owner_user_id_users_id_fk": { + "name": "proactive_settings_owner_user_id_users_id_fk", + "tableFrom": "proactive_settings", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "proactive_settings_agent_id_agents_id_fk": { + "name": "proactive_settings_agent_id_agents_id_fk", + "tableFrom": "proactive_settings", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "proactive_settings_interval_check": { + "name": "proactive_settings_interval_check", + "value": "\"proactive_settings\".\"interval_minutes\" >= 60 AND \"proactive_settings\".\"interval_minutes\" <= 1440" + }, + "proactive_settings_status_check": { + "name": "proactive_settings_status_check", + "value": "\"proactive_settings\".\"last_status\" IN ('idle', 'running', 'succeeded', 'error')" + } + }, + "isRLSEnabled": false + }, + "public.proactive_suggestions": { + "name": "proactive_suggestions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "setting_id": { + "name": "setting_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_app": { + "name": "source_app", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_link": { + "name": "source_link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "proactive_suggestions_owner_idx": { + "name": "proactive_suggestions_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "proactive_suggestions_run_idx": { + "name": "proactive_suggestions_run_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "proactive_suggestions_owner_user_id_users_id_fk": { + "name": "proactive_suggestions_owner_user_id_users_id_fk", + "tableFrom": "proactive_suggestions", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "proactive_suggestions_agent_id_agents_id_fk": { + "name": "proactive_suggestions_agent_id_agents_id_fk", + "tableFrom": "proactive_suggestions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "proactive_suggestions_setting_id_proactive_settings_id_fk": { + "name": "proactive_suggestions_setting_id_proactive_settings_id_fk", + "tableFrom": "proactive_suggestions", + "tableTo": "proactive_settings", + "columnsFrom": [ + "setting_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "proactive_suggestions_status_check": { + "name": "proactive_suggestions_status_check", + "value": "\"proactive_suggestions\".\"status\" IN ('open', 'dismissed', 'started', 'superseded')" + } + }, + "isRLSEnabled": false + }, + "public.demonstrations": { + "name": "demonstrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'recording'" + }, + "actions": { + "name": "actions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "draft": { + "name": "draft", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "skill_slug": { + "name": "skill_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "demonstrations_owner_idx": { + "name": "demonstrations_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "demonstrations_active_bot_idx": { + "name": "demonstrations_active_bot_idx", + "columns": [ + { + "expression": "bot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"demonstrations\".\"status\" = 'recording'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "demonstrations_owner_user_id_users_id_fk": { + "name": "demonstrations_owner_user_id_users_id_fk", + "tableFrom": "demonstrations", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "demonstrations_bot_id_agents_id_fk": { + "name": "demonstrations_bot_id_agents_id_fk", + "tableFrom": "demonstrations", + "tableTo": "agents", + "columnsFrom": [ + "bot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.delivery_bindings": { + "name": "delivery_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identity": { + "name": "identity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "realm": { + "name": "realm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mention_id": { + "name": "mention_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "opted_out_at": { + "name": "opted_out_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "delivery_binding_identity_idx": { + "name": "delivery_binding_identity_idx", + "columns": [ + { + "expression": "transport", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "realm", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "identity", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "delivery_bindings_owner_idx": { + "name": "delivery_bindings_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "delivery_bindings_owner_user_id_users_id_fk": { + "name": "delivery_bindings_owner_user_id_users_id_fk", + "tableFrom": "delivery_bindings", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "delivery_bindings_channel_id_channels_id_fk": { + "name": "delivery_bindings_channel_id_channels_id_fk", + "tableFrom": "delivery_bindings", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "delivery_bindings_agent_id_agents_id_fk": { + "name": "delivery_bindings_agent_id_agents_id_fk", + "tableFrom": "delivery_bindings", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.delivery_challenges": { + "name": "delivery_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "delivery_challenges_owner_user_id_users_id_fk": { + "name": "delivery_challenges_owner_user_id_users_id_fk", + "tableFrom": "delivery_challenges", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "delivery_challenges_channel_id_channels_id_fk": { + "name": "delivery_challenges_channel_id_channels_id_fk", + "tableFrom": "delivery_challenges", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "delivery_challenges_agent_id_agents_id_fk": { + "name": "delivery_challenges_agent_id_agents_id_fk", + "tableFrom": "delivery_challenges", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.delivery_inbox": { + "name": "delivery_inbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "binding_id": { + "name": "binding_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "realm": { + "name": "realm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "delivery_inbox_dedupe_idx": { + "name": "delivery_inbox_dedupe_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "realm", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "delivery_inbox_owner_idx": { + "name": "delivery_inbox_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "delivery_inbox_owner_user_id_users_id_fk": { + "name": "delivery_inbox_owner_user_id_users_id_fk", + "tableFrom": "delivery_inbox", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "delivery_inbox_channel_id_channels_id_fk": { + "name": "delivery_inbox_channel_id_channels_id_fk", + "tableFrom": "delivery_inbox", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "delivery_inbox_agent_id_agents_id_fk": { + "name": "delivery_inbox_agent_id_agents_id_fk", + "tableFrom": "delivery_inbox", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.delivery_outbox": { + "name": "delivery_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "binding_id": { + "name": "binding_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "delivery_outbox_owner_idx": { + "name": "delivery_outbox_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "delivery_outbox_provider_idx": { + "name": "delivery_outbox_provider_idx", + "columns": [ + { + "expression": "transport", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "delivery_outbox_owner_user_id_users_id_fk": { + "name": "delivery_outbox_owner_user_id_users_id_fk", + "tableFrom": "delivery_outbox", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "delivery_outbox_channel_id_channels_id_fk": { + "name": "delivery_outbox_channel_id_channels_id_fk", + "tableFrom": "delivery_outbox", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "delivery_outbox_agent_id_agents_id_fk": { + "name": "delivery_outbox_agent_id_agents_id_fk", + "tableFrom": "delivery_outbox", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "delivery_outbox_dedupe_key_unique": { + "name": "delivery_outbox_dedupe_key_unique", + "nullsNotDistinct": false, + "columns": [ + "dedupe_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.push_devices": { + "name": "push_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "push_devices_owner_idx": { + "name": "push_devices_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "push_devices_owner_user_id_users_id_fk": { + "name": "push_devices_owner_user_id_users_id_fk", + "tableFrom": "push_devices", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "push_devices_token_unique": { + "name": "push_devices_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.group_bot_threads": { + "name": "group_bot_threads", + "schema": "", + "columns": { + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "group_bot_threads_thread_idx": { + "name": "group_bot_threads_thread_idx", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "group_bot_threads_owner_user_id_users_id_fk": { + "name": "group_bot_threads_owner_user_id_users_id_fk", + "tableFrom": "group_bot_threads", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "group_bot_threads_channel_id_channels_id_fk": { + "name": "group_bot_threads_channel_id_channels_id_fk", + "tableFrom": "group_bot_threads", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "group_bot_threads_owner_user_id_channel_id_agent_id_pk": { + "name": "group_bot_threads_owner_user_id_channel_id_agent_id_pk", + "columns": [ + "owner_user_id", + "channel_id", + "agent_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.group_messages": { + "name": "group_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'completed'" + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "group_messages_channel_idx": { + "name": "group_messages_channel_idx", + "columns": [ + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "group_messages_channel_id_channels_id_fk": { + "name": "group_messages_channel_id_channels_id_fk", + "tableFrom": "group_messages", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "group_messages_owner_user_id_users_id_fk": { + "name": "group_messages_owner_user_id_users_id_fk", + "tableFrom": "group_messages", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "group_messages_status_check": { + "name": "group_messages_status_check", + "value": "\"group_messages\".\"status\" IN ('queued', 'running', 'waiting', 'completed', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.action_records": { + "name": "action_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "surface": { + "name": "surface", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "action_records_created_idx": { + "name": "action_records_created_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "action_records_bot_idx": { + "name": "action_records_bot_idx", + "columns": [ + { + "expression": "bot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "action_records_surface_check": { + "name": "action_records_surface_check", + "value": "\"action_records\".\"surface\" IN ('cloud', 'local')" + } + }, + "isRLSEnabled": false + }, + "public.capability_settings": { + "name": "capability_settings", + "schema": "", + "columns": { + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "capability": { + "name": "capability", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed": { + "name": "allowed", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "capability_settings_scope_kind_scope_id_capability_pk": { + "name": "capability_settings_scope_kind_scope_id_capability_pk", + "columns": [ + "scope_kind", + "scope_id", + "capability" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "capability_settings_scope_kind_check": { + "name": "capability_settings_scope_kind_check", + "value": "\"capability_settings\".\"scope_kind\" IN ('organization', 'role', 'group')" + } + }, + "isRLSEnabled": false + }, + "public.enterprise_settings": { + "name": "enterprise_settings", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_usage": { + "name": "model_usage", + "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": false + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed": { + "name": "allowed", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "model_usage_created_idx": { + "name": "model_usage_created_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "model_usage_model_idx": { + "name": "model_usage_model_idx", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "model_usage_source_check": { + "name": "model_usage_source_check", + "value": "\"model_usage\".\"source\" IN ('configured', 'observed', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.network_policies": { + "name": "network_policies", + "schema": "", + "columns": { + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rules": { + "name": "rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"entries\":[]}'::jsonb" + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "network_policies_scope_kind_scope_id_pk": { + "name": "network_policies_scope_kind_scope_id_pk", + "columns": [ + "scope_kind", + "scope_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "network_policies_scope_kind_check": { + "name": "network_policies_scope_kind_check", + "value": "\"network_policies\".\"scope_kind\" IN ('organization', 'group')" + }, + "network_policies_mode_check": { + "name": "network_policies_mode_check", + "value": "\"network_policies\".\"mode\" IN ('allow_all', 'defaults_plus_allowlist', 'allowlist_only')" + } + }, + "isRLSEnabled": false + }, + "public.scim_connection_bindings": { + "name": "scim_connection_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_key": { + "name": "connection_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provisioning_domain_id": { + "name": "provisioning_domain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "decommissioned_at": { + "name": "decommissioned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "decommission_status": { + "name": "decommission_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "decommission_cursor_user_id": { + "name": "decommission_cursor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decommission_reconciled_user_count": { + "name": "decommission_reconciled_user_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "decommission_batch_count": { + "name": "decommission_batch_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "decommission_revision": { + "name": "decommission_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "decommission_completed_at": { + "name": "decommission_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "decommission_lease_id": { + "name": "decommission_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decommission_lease_expires_at": { + "name": "decommission_lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "scim_connection_bindings_connection_idx": { + "name": "scim_connection_bindings_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "scim_connection_bindings_connection_key_unique": { + "name": "scim_connection_bindings_connection_key_unique", + "nullsNotDistinct": false, + "columns": [ + "connection_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_group_members": { + "name": "scim_group_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scim_user_id": { + "name": "scim_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "membership_key": { + "name": "membership_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "scim_group_members_connection_idx": { + "name": "scim_group_members_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_members_group_idx": { + "name": "scim_group_members_group_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_members_scim_user_idx": { + "name": "scim_group_members_scim_user_idx", + "columns": [ + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_group_members_group_id_scim_groups_id_fk": { + "name": "scim_group_members_group_id_scim_groups_id_fk", + "tableFrom": "scim_group_members", + "tableTo": "scim_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_group_members_scim_user_id_scim_users_id_fk": { + "name": "scim_group_members_scim_user_id_scim_users_id_fk", + "tableFrom": "scim_group_members", + "tableTo": "scim_users", + "columnsFrom": [ + "scim_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "scim_group_members_membership_key_unique": { + "name": "scim_group_members_membership_key_unique", + "nullsNotDistinct": false, + "columns": [ + "membership_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_groups": { + "name": "scim_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provisioning_domain_id": { + "name": "provisioning_domain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name_key": { + "name": "display_name_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id_key": { + "name": "external_id_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "order_key": { + "name": "order_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "scim_groups_connection_idx": { + "name": "scim_groups_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_groups_domain_idx": { + "name": "scim_groups_domain_idx", + "columns": [ + { + "expression": "provisioning_domain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "scim_groups_display_name_key_unique": { + "name": "scim_groups_display_name_key_unique", + "nullsNotDistinct": false, + "columns": [ + "display_name_key" + ] + }, + "scim_groups_external_id_key_unique": { + "name": "scim_groups_external_id_key_unique", + "nullsNotDistinct": false, + "columns": [ + "external_id_key" + ] + }, + "scim_groups_order_key_unique": { + "name": "scim_groups_order_key_unique", + "nullsNotDistinct": false, + "columns": [ + "order_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_identity_tombstones": { + "name": "scim_identity_tombstones", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provisioning_domain_id": { + "name": "provisioning_domain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id_key": { + "name": "external_id_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile": { + "name": "profile", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "scim_identity_tombstones_connection_idx": { + "name": "scim_identity_tombstones_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_identity_tombstones_domain_idx": { + "name": "scim_identity_tombstones_domain_idx", + "columns": [ + { + "expression": "provisioning_domain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_identity_tombstones_user_idx": { + "name": "scim_identity_tombstones_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_identity_tombstones_user_id_users_id_fk": { + "name": "scim_identity_tombstones_user_id_users_id_fk", + "tableFrom": "scim_identity_tombstones", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "scim_identity_tombstones_external_id_key_unique": { + "name": "scim_identity_tombstones_external_id_key_unique", + "nullsNotDistinct": false, + "columns": [ + "external_id_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_projection_grants": { + "name": "scim_projection_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provisioning_domain_id": { + "name": "provisioning_domain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scim_user_id": { + "name": "scim_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_value": { + "name": "source_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant_key": { + "name": "grant_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "scim_projection_grants_connection_idx": { + "name": "scim_projection_grants_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_projection_grants_domain_idx": { + "name": "scim_projection_grants_domain_idx", + "columns": [ + { + "expression": "provisioning_domain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_projection_grants_scim_user_idx": { + "name": "scim_projection_grants_scim_user_idx", + "columns": [ + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_projection_grants_user_idx": { + "name": "scim_projection_grants_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_projection_grants_scim_user_id_scim_users_id_fk": { + "name": "scim_projection_grants_scim_user_id_scim_users_id_fk", + "tableFrom": "scim_projection_grants", + "tableTo": "scim_users", + "columnsFrom": [ + "scim_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_projection_grants_user_id_users_id_fk": { + "name": "scim_projection_grants_user_id_users_id_fk", + "tableFrom": "scim_projection_grants", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "scim_projection_grants_grant_key_unique": { + "name": "scim_projection_grants_grant_key_unique", + "nullsNotDistinct": false, + "columns": [ + "grant_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_subjects": { + "name": "scim_subjects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile_source_id": { + "name": "profile_source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "scim_subjects_profile_source_idx": { + "name": "scim_subjects_profile_source_idx", + "columns": [ + { + "expression": "profile_source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_subjects_user_id_users_id_fk": { + "name": "scim_subjects_user_id_users_id_fk", + "tableFrom": "scim_subjects", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "scim_subjects_user_id_unique": { + "name": "scim_subjects_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_users": { + "name": "scim_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provisioning_domain_id": { + "name": "provisioning_domain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_user_key": { + "name": "connection_user_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_name_key": { + "name": "user_name_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "primary_email": { + "name": "primary_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "work_email_value_index": { + "name": "work_email_value_index", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_value_index": { + "name": "email_value_index", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "formatted_name": { + "name": "formatted_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "given_name": { + "name": "given_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "family_name": { + "name": "family_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serialized_emails": { + "name": "serialized_emails", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serialized_attributes": { + "name": "serialized_attributes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id_key": { + "name": "external_id_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "order_key": { + "name": "order_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "scim_users_connection_idx": { + "name": "scim_users_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_users_domain_idx": { + "name": "scim_users_domain_idx", + "columns": [ + { + "expression": "provisioning_domain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_users_user_idx": { + "name": "scim_users_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_users_user_id_users_id_fk": { + "name": "scim_users_user_id_users_id_fk", + "tableFrom": "scim_users", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "scim_users_connection_user_key_unique": { + "name": "scim_users_connection_user_key_unique", + "nullsNotDistinct": false, + "columns": [ + "connection_user_key" + ] + }, + "scim_users_user_name_key_unique": { + "name": "scim_users_user_name_key_unique", + "nullsNotDistinct": false, + "columns": [ + "user_name_key" + ] + }, + "scim_users_external_id_key_unique": { + "name": "scim_users_external_id_key_unique", + "nullsNotDistinct": false, + "columns": [ + "external_id_key" + ] + }, + "scim_users_order_key_unique": { + "name": "scim_users_order_key_unique", + "nullsNotDistinct": false, + "columns": [ + "order_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_lifecycle": { + "name": "bot_lifecycle", + "schema": "", + "columns": { + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "notify": { + "name": "notify", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "bot_lifecycle_owner_user_id_users_id_fk": { + "name": "bot_lifecycle_owner_user_id_users_id_fk", + "tableFrom": "bot_lifecycle", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_lifecycle_agent_id_agents_id_fk": { + "name": "bot_lifecycle_agent_id_agents_id_fk", + "tableFrom": "bot_lifecycle", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "bot_lifecycle_owner_user_id_agent_id_pk": { + "name": "bot_lifecycle_owner_user_id_agent_id_pk", + "columns": [ + "owner_user_id", + "agent_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.update_routing_preferences": { + "name": "update_routing_preferences", + "schema": "", + "columns": { + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "update_routing_preferences_owner_user_id_users_id_fk": { + "name": "update_routing_preferences_owner_user_id_users_id_fk", + "tableFrom": "update_routing_preferences", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "update_routing_preferences_owner_user_id_kind_pk": { + "name": "update_routing_preferences_owner_user_id_kind_pk", + "columns": [ + "owner_user_id", + "kind" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.saved_logins": { + "name": "saved_logins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "saved_logins_owner_origin_username_idx": { + "name": "saved_logins_owner_origin_username_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "saved_logins_owner_idx": { + "name": "saved_logins_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "saved_logins_owner_user_id_users_id_fk": { + "name": "saved_logins_owner_user_id_users_id_fk", + "tableFrom": "saved_logins", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sign_in_requests": { + "name": "sign_in_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "control_request_id": { + "name": "control_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "continuation": { + "name": "continuation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "filling_until": { + "name": "filling_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sign_in_requests_owner_idx": { + "name": "sign_in_requests_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sign_in_requests_owner_user_id_users_id_fk": { + "name": "sign_in_requests_owner_user_id_users_id_fk", + "tableFrom": "sign_in_requests", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "sign_in_requests_status_check": { + "name": "sign_in_requests_status_check", + "value": "\"sign_in_requests\".\"status\" IN ('pending', 'filling', 'code_required', 'taken_over', 'signed_in', 'failed', 'cancelled', 'expired')" + }, + "sign_in_requests_method_check": { + "name": "sign_in_requests_method_check", + "value": "\"sign_in_requests\".\"method\" IS NULL OR \"sign_in_requests\".\"method\" IN ('typed', 'saved', 'takeover')" + } + }, + "isRLSEnabled": false + }, + "public.team_bot_assignments": { + "name": "team_bot_assignments", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_bot_assignments_agent_id_agents_id_fk": { + "name": "team_bot_assignments_agent_id_agents_id_fk", + "tableFrom": "team_bot_assignments", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_bot_assignments_assigned_by_users_id_fk": { + "name": "team_bot_assignments_assigned_by_users_id_fk", + "tableFrom": "team_bot_assignments", + "tableTo": "users", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "team_bot_assignments_agent_id_group_name_pk": { + "name": "team_bot_assignments_agent_id_group_name_pk", + "columns": [ + "agent_id", + "group_name" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_bot_audience": { + "name": "team_bot_audience", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_bot_audience_agent_id_agents_id_fk": { + "name": "team_bot_audience_agent_id_agents_id_fk", + "tableFrom": "team_bot_audience", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "team_bot_audience_agent_id_kind_value_pk": { + "name": "team_bot_audience_agent_id_kind_value_pk", + "columns": [ + "agent_id", + "kind", + "value" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "team_bot_audience_kind_check": { + "name": "team_bot_audience_kind_check", + "value": "\"team_bot_audience\".\"kind\" IN ('user', 'group')" + } + }, + "isRLSEnabled": false + }, + "public.team_bot_consent_defaults": { + "name": "team_bot_consent_defaults", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_bot_consent_defaults_user_id_users_id_fk": { + "name": "team_bot_consent_defaults_user_id_users_id_fk", + "tableFrom": "team_bot_consent_defaults", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "team_bot_consent_defaults_user_id_server_id_pk": { + "name": "team_bot_consent_defaults_user_id_server_id_pk", + "columns": [ + "user_id", + "server_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_bot_consents": { + "name": "team_bot_consents", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_bot_consents_user_id_users_id_fk": { + "name": "team_bot_consents_user_id_users_id_fk", + "tableFrom": "team_bot_consents", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_bot_consents_agent_id_agents_id_fk": { + "name": "team_bot_consents_agent_id_agents_id_fk", + "tableFrom": "team_bot_consents", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "team_bot_consents_user_id_agent_id_server_id_pk": { + "name": "team_bot_consents_user_id_agent_id_server_id_pk", + "columns": [ + "user_id", + "agent_id", + "server_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "team_bot_consents_decision_check": { + "name": "team_bot_consents_decision_check", + "value": "\"team_bot_consents\".\"decision\" IN ('always', 'once')" + } + }, + "isRLSEnabled": false + }, + "public.team_bot_publications": { + "name": "team_bot_publications", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "published_by": { + "name": "published_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "audience": { + "name": "audience", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_bot_publications_agent_id_agents_id_fk": { + "name": "team_bot_publications_agent_id_agents_id_fk", + "tableFrom": "team_bot_publications", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_bot_publications_published_by_users_id_fk": { + "name": "team_bot_publications_published_by_users_id_fk", + "tableFrom": "team_bot_publications", + "tableTo": "users", + "columnsFrom": [ + "published_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "team_bot_publications_audience_check": { + "name": "team_bot_publications_audience_check", + "value": "\"team_bot_publications\".\"audience\" IN ('team', 'people')" + } + }, + "isRLSEnabled": false + }, + "public.shared_use_approval_members": { + "name": "shared_use_approval_members", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "shared_use_approval_members_approval_fk": { + "name": "shared_use_approval_members_approval_fk", + "tableFrom": "shared_use_approval_members", + "tableTo": "shared_use_approvals", + "columnsFrom": [ + "agent_id", + "server_id" + ], + "columnsTo": [ + "agent_id", + "server_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_use_approval_members_agent_id_server_id_kind_value_pk": { + "name": "shared_use_approval_members_agent_id_server_id_kind_value_pk", + "columns": [ + "agent_id", + "server_id", + "kind", + "value" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "shared_use_approval_members_kind_check": { + "name": "shared_use_approval_members_kind_check", + "value": "\"shared_use_approval_members\".\"kind\" IN ('user', 'group')" + } + }, + "isRLSEnabled": false + }, + "public.shared_use_approvals": { + "name": "shared_use_approvals", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "audience": { + "name": "audience", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outside_input": { + "name": "outside_input", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "approved_by": { + "name": "approved_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_as": { + "name": "approved_as", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "shared_use_approvals_agent_id_agents_id_fk": { + "name": "shared_use_approvals_agent_id_agents_id_fk", + "tableFrom": "shared_use_approvals", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_use_approvals_server_id_mcp_servers_id_fk": { + "name": "shared_use_approvals_server_id_mcp_servers_id_fk", + "tableFrom": "shared_use_approvals", + "tableTo": "mcp_servers", + "columnsFrom": [ + "server_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "shared_use_approvals_agent_id_server_id_pk": { + "name": "shared_use_approvals_agent_id_server_id_pk", + "columns": [ + "agent_id", + "server_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "shared_use_approvals_audience_check": { + "name": "shared_use_approvals_audience_check", + "value": "\"shared_use_approvals\".\"audience\" IN ('owner', 'people', 'team')" + }, + "shared_use_approvals_approved_as_check": { + "name": "shared_use_approvals_approved_as_check", + "value": "\"shared_use_approvals\".\"approved_as\" IS NULL OR \"shared_use_approvals\".\"approved_as\" IN ('admin', 'owner')" + } + }, + "isRLSEnabled": false + }, + "public.shared_use_requests": { + "name": "shared_use_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "proposed_audience": { + "name": "proposed_audience", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "proposed_outside_input": { + "name": "proposed_outside_input", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "proposed_members": { + "name": "proposed_members", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "decided_by": { + "name": "decided_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "shared_use_requests_pending_idx": { + "name": "shared_use_requests_pending_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"shared_use_requests\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "shared_use_requests_status_idx": { + "name": "shared_use_requests_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_use_requests_agent_id_agents_id_fk": { + "name": "shared_use_requests_agent_id_agents_id_fk", + "tableFrom": "shared_use_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_use_requests_server_id_mcp_servers_id_fk": { + "name": "shared_use_requests_server_id_mcp_servers_id_fk", + "tableFrom": "shared_use_requests", + "tableTo": "mcp_servers", + "columnsFrom": [ + "server_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "shared_use_requests_audience_check": { + "name": "shared_use_requests_audience_check", + "value": "\"shared_use_requests\".\"proposed_audience\" IN ('owner', 'people', 'team')" + }, + "shared_use_requests_reason_check": { + "name": "shared_use_requests_reason_check", + "value": "\"shared_use_requests\".\"reason\" IN ('publish', 'trigger', 'grant', 'refused_call')" + }, + "shared_use_requests_status_check": { + "name": "shared_use_requests_status_check", + "value": "\"shared_use_requests\".\"status\" IN ('pending', 'approved', 'declined', 'superseded')" + } + }, + "isRLSEnabled": false + }, + "public.identity_link_challenges": { + "name": "identity_link_challenges", + "schema": "", + "columns": { + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "identity_link_challenges_expiry_idx": { + "name": "identity_link_challenges_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "identity_link_challenges_user_provider_idx": { + "name": "identity_link_challenges_user_provider_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.identity_links": { + "name": "identity_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "realm": { + "name": "realm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified_by": { + "name": "verified_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "identity_links_identity_idx": { + "name": "identity_links_identity_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "realm", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "identity_links_user_realm_idx": { + "name": "identity_links_user_realm_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "realm", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "identity_links_credential_idx": { + "name": "identity_links_credential_idx", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"identity_links\".\"credential_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "identity_links_credential_id_credentials_id_fk": { + "name": "identity_links_credential_id_credentials_id_fk", + "tableFrom": "identity_links", + "tableTo": "credentials", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "identity_links_verified_by_check": { + "name": "identity_links_verified_by_check", + "value": "\"identity_links\".\"verified_by\" IN ('challenge', 'oauth')" + }, + "identity_links_status_check": { + "name": "identity_links_status_check", + "value": "\"identity_links\".\"status\" IN ('active', 'needs_reconnect')" + } + }, + "isRLSEnabled": false + } + }, + "enums": { + "public.agent_type": { + "name": "agent_type", + "schema": "public", + "values": [ + "built_in", + "remote_ag_ui", + "remote_mastra" + ] + }, + "public.credential_kind": { + "name": "credential_kind", + "schema": "public", + "values": [ + "model", + "connector", + "agent", + "mcp", + "mcp_oauth_client", + "mcp_user_token" + ] + }, + "public.role": { + "name": "role", + "schema": "public", + "values": [ + "admin", + "user" + ] + }, + "public.agent_visibility": { + "name": "agent_visibility", + "schema": "public", + "values": [ + "public", + "private" + ] + }, + "public.routine_run_status": { + "name": "routine_run_status", + "schema": "public", + "values": [ + "succeeded", + "failed", + "skipped", + "waiting" + ] + }, + "public.responsibility_run_status": { + "name": "responsibility_run_status", + "schema": "public", + "values": [ + "queued", + "running", + "waiting", + "succeeded", + "failed", + "skipped" + ] + }, + "public.responsibility_status": { + "name": "responsibility_status", + "schema": "public", + "values": [ + "active", + "paused", + "completed" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/server/drizzle/meta/_journal.json b/server/drizzle/meta/_journal.json index 74a928ec93..98cee05d57 100644 --- a/server/drizzle/meta/_journal.json +++ b/server/drizzle/meta/_journal.json @@ -449,6 +449,13 @@ "when": 1793027892885, "tag": "0063_owner_app_grants", "breakpoints": true + }, + { + "idx": 64, + "version": "7", + "when": 1793033276066, + "tag": "0064_owner_shared_audience", + "breakpoints": true } ] } \ No newline at end of file diff --git a/server/src/db/schema/shared-use.ts b/server/src/db/schema/shared-use.ts index 7660e9c2e9..05b241053c 100644 --- a/server/src/db/schema/shared-use.ts +++ b/server/src/db/schema/shared-use.ts @@ -31,6 +31,16 @@ export const sharedUseApprovals = pgTable( audience: text("audience").$type<"owner" | "people" | "team">().notNull(), outsideInput: boolean("outside_input").notNull(), approvedBy: text("approved_by").notNull(), + /** + * Who set this audience: an administrator (`admin`, or null from a replica older than this + * column), or the Bot's owner giving it a Shared app whose rule lets owners decide (`owner`). + * + * AN OWNER'S AUDIENCE LIVES ONLY AS LONG AS WHAT ALLOWED IT. It is withdrawn when the owner takes + * the app back or an administrator stops letting owners decide, and it is not read at all while + * the app's `owner_grants` is anything but `any`. An administrator's approval over it makes it + * theirs, and then it stays. + */ + approvedAs: text("approved_as").$type<"admin" | "owner">(), approvedAt: timestamp("approved_at", { withTimezone: true }) .notNull() .defaultNow(), @@ -41,6 +51,10 @@ export const sharedUseApprovals = pgTable( "shared_use_approvals_audience_check", sql`${table.audience} IN ('owner', 'people', 'team')`, ), + check( + "shared_use_approvals_approved_as_check", + sql`${table.approvedAs} IS NULL OR ${table.approvedAs} IN ('admin', 'owner')`, + ), ], ); From 50ab1e39cd0c3b6c67ec30833d2c4b37406c4bdc Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 13:55:16 -0300 Subject: [PATCH 15/19] Let an owner's grant set a Shared app's audience, and read it only while owners decide --- server/src/audit.ts | 8 ++ server/src/plugins/owner-grants.ts | 72 +++++++++++ server/src/plugins/plugin-store/app-access.ts | 4 +- server/src/plugins/shared-use-store.ts | 119 +++++++++++++++++- 4 files changed, 196 insertions(+), 7 deletions(-) diff --git a/server/src/audit.ts b/server/src/audit.ts index 44939b23be..e4edea83c2 100644 --- a/server/src/audit.ts +++ b/server/src/audit.ts @@ -268,6 +268,14 @@ export const auditEventTypes = [ "shared_use.approved", // An administrator answered a request to use a Shared app through a Bot. "shared_use.declined", + /* + * A Bot's owner gave it a Shared app whose rule lets owners decide, and that set who may use the + * team account through the Bot (`approved_as = owner`); and that audience withdrawn, because the + * owner took the app back or an administrator stopped letting owners decide. Target the app; + * payload `bot`, `server`, and the `audience` set or `why` it went. + */ + "shared_use.owner_approved", + "shared_use.owner_withdrawn", /* * A person asking an administrator to switch a work app on, grant it to a Bot, or connect a * Shared app's team account (`app_access_requests`), and what became of it: cancelled by the diff --git a/server/src/plugins/owner-grants.ts b/server/src/plugins/owner-grants.ts index 4852f4ac38..2f744b41a7 100644 --- a/server/src/plugins/owner-grants.ts +++ b/server/src/plugins/owner-grants.ts @@ -1,6 +1,7 @@ import { capabilityRefusal } from "../admin/capabilities"; import { enterpriseControls } from "../admin/controls"; import type { AppInspection } from "./plugin-store/app-access"; +import type { SharedUseStore } from "./shared-use-store"; import type { PluginStore } from "./store"; /** @@ -19,6 +20,13 @@ import type { PluginStore } from "./store"; * the asking person's own account, even on the owner's published Team Bot. See * `credentialActorFor`. Publishing a Bot must not hand teammates the owner's mailbox. * + * A SHARED APP IS THE ONE EXCEPTION TO "WHOSE ACCOUNT", and the two rules do not meet. A Shared app + * runs on the deployment's one team account whoever the credential actor is (`accountFor` never + * reads it for `shared`), so the owner marker cannot send it to a personal account; and a Personal + * app never reaches here as Shared, so the marker keeps it on the asker's own. What a Shared app + * needs instead is an audience, and where an administrator has let owners decide (`owner_grants = + * any`, never the default for Shared) the owner's grant sets it: see {@link approveOwnersSharedUse}. + * * And an administrator can switch the whole thing off for anybody through the `ownerApps` * capability, which every check here reads. */ @@ -57,3 +65,67 @@ export async function ownerGrantRefusal( return `Only an admin can give ${name} to a Bot.`; return null; } + +type OwnerSharedUseEvents = Pick; + +/** + * The Shared-account audience a Bot owner's grant sets, and its row in the trail: who gave it, to + * which Bot, on which app, and the audience. Nothing where the app is not Shared, its rule is not + * `any`, or the Bot already holds an approval; see `approveOwnerGrant`. + */ +export async function approveOwnersSharedUse( + events: OwnerSharedUseEvents, + sharedUse: Pick, + input: { botId: string; serverId: string; ownerId: string }, +): Promise { + if (!input.serverId) return; + const approval = await sharedUse.approveOwnerGrant({ + botId: input.botId, + serverId: input.serverId, + by: input.ownerId, + }); + if (!approval) return; + const appId = await sharedUse.appIdOf(input.serverId); + await events.recordAppAccessEvent({ + eventType: "shared_use.owner_approved", + targetId: appId, + actorUserId: input.ownerId, + payload: { + bot: input.botId, + server: appId, + audience: approval.audience, + members: approval.members, + outsideInput: approval.outsideInput, + }, + }); +} + +/** + * Withdraw the audiences owners' grants set at an app — one Bot's, when its owner takes the app + * back, or every Bot's, when an administrator stops letting owners decide — each with a row in the + * trail. An administrator's approval is never touched. + */ +export async function withdrawOwnersSharedUse( + events: OwnerSharedUseEvents, + sharedUse: Pick, + input: { + serverId: string; + botId?: string; + actorId: string; + why: "owner_took_back" | "owner_grants_changed"; + }, +): Promise { + const bots = await sharedUse.withdrawOwnerApprovals({ + serverId: input.serverId, + ...(input.botId ? { botId: input.botId } : {}), + }); + if (bots.length === 0) return; + const appId = await sharedUse.appIdOf(input.serverId); + for (const bot of bots) + await events.recordAppAccessEvent({ + eventType: "shared_use.owner_withdrawn", + targetId: appId, + actorUserId: input.actorId, + payload: { bot, server: appId, why: input.why }, + }); +} diff --git a/server/src/plugins/plugin-store/app-access.ts b/server/src/plugins/plugin-store/app-access.ts index dc7a9435fd..f492886d76 100644 --- a/server/src/plugins/plugin-store/app-access.ts +++ b/server/src/plugins/plugin-store/app-access.ts @@ -1258,7 +1258,9 @@ export function appAccessMethods(context: StoreContext) { | "app_access.dismissed" | "app_access.refused" | "app_access.owner_granted" - | "app_access.owner_revoked"; + | "app_access.owner_revoked" + | "shared_use.owner_approved" + | "shared_use.owner_withdrawn"; }, ): Promise { await recordAuditEvent(auditStore, { diff --git a/server/src/plugins/shared-use-store.ts b/server/src/plugins/shared-use-store.ts index 8d794a15f4..59ef1f14c6 100644 --- a/server/src/plugins/shared-use-store.ts +++ b/server/src/plugins/shared-use-store.ts @@ -174,14 +174,17 @@ export function createSharedUseStore( return (await rowsOfApp(serverId)).appId; } - async function approvalFor( - botId: string, - anyServerId: string, - ): Promise { - const serverId = await appIdOf(anyServerId); + /** The approval row for this Bot at the app, whoever set it, and whether it may be read now. */ + async function storedApproval(botId: string, serverId: string) { const [row] = await database - .select() + .select({ + audience: sharedUseApprovals.audience, + outsideInput: sharedUseApprovals.outsideInput, + approvedAs: sharedUseApprovals.approvedAs, + ownersDecide: sql`coalesce(${mcpServers.ownerGrants} = 'any' and ${mcpServers.accountMode} = 'shared', false)`, + }) .from(sharedUseApprovals) + .leftJoin(mcpServers, eq(mcpServers.id, sharedUseApprovals.serverId)) .where( and( eq(sharedUseApprovals.agentId, botId), @@ -189,7 +192,36 @@ export function createSharedUseStore( ), ) .limit(1); + return row ?? null; + } + + /** + * Who set the audience this Bot holds at the app — `admin` (a null from an older replica is + * one), `owner`, or null where there is none. + */ + async function approvalSource( + botId: string, + anyServerId: string, + ): Promise<"admin" | "owner" | null> { + const row = await storedApproval(botId, await appIdOf(anyServerId)); if (!row) return null; + return row.approvedAs === "owner" ? "owner" : "admin"; + } + + /* + * AN OWNER'S AUDIENCE COUNTS ONLY WHILE OWNERS DECIDE. One set by an owner's grant is withdrawn + * when an administrator stops letting owners give the app, but a row that outlived that — a + * replica that switched the rule without withdrawing, a delete that failed — must not keep the + * team account open. So it is read as no approval unless the app's rule is `any` now. + */ + async function approvalFor( + botId: string, + anyServerId: string, + ): Promise { + const serverId = await appIdOf(anyServerId); + const row = await storedApproval(botId, serverId); + if (!row) return null; + if (row.approvedAs === "owner" && !row.ownersDecide) return null; const members = await database .select({ kind: sharedUseApprovalMembers.kind, @@ -214,8 +246,11 @@ export function createSharedUseStore( serverId: string; approval: SharedUseApproval; by: string; + /** Who is setting it. Only {@link approveOwnerGrant} says `owner`. */ + as?: "admin" | "owner"; }) { const serverId = await appIdOf(input.serverId); + const approvedAs = input.as ?? "admin"; await database.transaction(async (tx) => { await tx .insert(sharedUseApprovals) @@ -225,6 +260,7 @@ export function createSharedUseStore( audience: input.approval.audience, outsideInput: input.approval.outsideInput, approvedBy: input.by, + approvedAs, }) .onConflictDoUpdate({ target: [sharedUseApprovals.agentId, sharedUseApprovals.serverId], @@ -232,6 +268,7 @@ export function createSharedUseStore( audience: input.approval.audience, outsideInput: input.approval.outsideInput, approvedBy: input.by, + approvedAs, approvedAt: new Date(), }, }); @@ -300,6 +337,73 @@ export function createSharedUseStore( .where(inArray(sharedUseApprovals.serverId, ids)); } + /** + * A Bot's owner gave it a Shared app whose rule lets owners decide: the audience that grant sets, + * written and answered, or null where it sets none. + * + * WHO, EXACTLY THE PEOPLE WHO CAN USE THE BOT NOW. The Bot's exposure as it stands — its owner + * for a private Bot, its published audience for a Team Bot — and never outside input: an email + * or a webhook steering the team account is not somebody who can use the Bot, and stays an + * administrator's to approve. It is a snapshot, as an administrator's approval is: publishing the + * Bot further later is caught by the gate and asked of an administrator, the way every widening is. + * + * NEVER OVER AN APPROVAL ALREADY THERE. An administrator's stands for a reason the owner did not + * give, narrower or wider, and the owner's own from before is already this. Nothing is written + * unless the app's answering row is Shared and its rule is `any` now, read here rather than + * trusted from the caller. + */ + async function approveOwnerGrant(input: { + botId: string; + serverId: string; + by: string; + }): Promise { + const serverId = await appIdOf(input.serverId); + const [app] = await database + .select({ + accountMode: mcpServers.accountMode, + ownerGrants: mcpServers.ownerGrants, + }) + .from(mcpServers) + .where(eq(mcpServers.id, serverId)) + .limit(1); + if (app?.accountMode !== "shared" || app.ownerGrants !== "any") return null; + if (await storedApproval(input.botId, serverId)) return null; + const approval: SharedUseApproval = { + ...exposureOf(await botFacts(input.botId)), + outsideInput: false, + }; + await setApproval({ + botId: input.botId, + serverId, + approval, + by: input.by, + as: "owner", + }); + return approval; + } + + /** + * Withdraw the audiences owners' grants set at an app — for one Bot, or every Bot — and none an + * administrator set. Answers the Bots withdrawn from, so each can be written to the trail. + */ + async function withdrawOwnerApprovals(input: { + serverId: string; + botId?: string; + }): Promise { + const { ids } = await rowsOfApp(input.serverId); + const withdrawn = await database + .delete(sharedUseApprovals) + .where( + and( + inArray(sharedUseApprovals.serverId, ids), + eq(sharedUseApprovals.approvedAs, "owner"), + ...(input.botId ? [eq(sharedUseApprovals.agentId, input.botId)] : []), + ), + ) + .returning({ botId: sharedUseApprovals.agentId }); + return [...new Set(withdrawn.map((row) => row.botId))]; + } + async function sourcesOf(responsibilityId: string): Promise { const [goal] = await database .select({ subscriptions: responsibilities.subscriptions }) @@ -703,8 +807,11 @@ export function createSharedUseStore( return { appIdOf, approvalFor, + approvalSource, admitsPerson, setApproval, + approveOwnerGrant, + withdrawOwnerApprovals, deleteApprovalsFor, botFacts, actorFacts, From 4b6577d0fb363cb19c8be46b63e1d310e21e64af Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 13:55:16 -0300 Subject: [PATCH 16/19] Set the audience when an owner gives a Shared app, and withdraw it on take-back or a rule change --- server/src/plugins/route-groups/app-access.ts | 50 ++++++++++++++++++- server/src/plugins/route-groups/grants.ts | 21 ++++++-- 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/server/src/plugins/route-groups/app-access.ts b/server/src/plugins/route-groups/app-access.ts index 14d32d3850..673aa4f51a 100644 --- a/server/src/plugins/route-groups/app-access.ts +++ b/server/src/plugins/route-groups/app-access.ts @@ -8,7 +8,12 @@ import type { Unavailable, } from "../app-access"; import type { CatalogueEntry } from "../catalogue"; -import { ownerAppsAllowed, ownerGrantRefusal } from "../owner-grants"; +import { + approveOwnersSharedUse, + ownerAppsAllowed, + ownerGrantRefusal, + withdrawOwnersSharedUse, +} from "../owner-grants"; import type { AppAccessRequestRecord, AppDirectory, @@ -687,8 +692,25 @@ export function mountAppAccessRoutes({ inspected, ); if (refusal) return deny("owner_grant_refused", refusal, 403); - if (access.granted) return context.json({ granted: [] }); const serverId = inspected.answeringId ?? ""; + /* + * A SHARED APP OWNERS MAY GIVE IS USABLE AT ONCE: the grant sets who may use the team account + * through this Bot, the people who can use the Bot, and nobody else (`approveOwnerGrant`). Asked + * again of a Bot that already holds the owner's grant too, so pressing give puts back an + * audience withdrawn while an administrator had owners' choice switched off. + */ + const approveShared = async () => { + if (shared) + await approveOwnersSharedUse(store, shared.use, { + botId: agentId, + serverId, + ownerId: context.var.actor.id, + }); + }; + if (access.granted) { + if (access.givenByOwner) await approveShared(); + return context.json({ granted: [] }); + } const tools = await store.appTools(serverId); if (tools.length === 0) return deny( @@ -714,6 +736,7 @@ export function mountAppAccessRoutes({ actorUserId: context.var.actor.id, payload: { app: access.key, bot: agentId, refs }, }); + await approveShared(); return context.json({ granted: refs }); }); @@ -757,6 +780,17 @@ export function mountAppAccessRoutes({ actorUserId: context.var.actor.id, payload: { app: inspected.access.key, bot: agentId, refs }, }); + /* + * The Shared-account audience the owner's grant set goes with it. One an administrator + * approved stays, as their grants do. + */ + if (shared && inspected.answeringId) + await withdrawOwnersSharedUse(store, shared.use, { + serverId: inspected.answeringId, + botId: agentId, + actorId: context.var.actor.id, + why: "owner_took_back", + }); return context.json({ revoked: refs }); }, ); @@ -799,6 +833,18 @@ export function mountAppAccessRoutes({ }); if (!changed) return context.json({ error: "That app hasn't been added." }, 404); + /* + * Owners no longer decide, so the Shared-account audiences their grants set are withdrawn. + * The grants stay, an administrator's to keep or take, and their calls are refused until an + * administrator approves who may use the account. (`approvalFor` would not read those audiences + * now anyway; this removes them rather than leaving them to come back if the rule does.) + */ + if (value !== "any" && shared) + await withdrawOwnersSharedUse(store, shared.use, { + serverId: context.req.param("id"), + actorId: context.var.actor.id, + why: "owner_grants_changed", + }); return context.json({ ok: true }); }); diff --git a/server/src/plugins/route-groups/grants.ts b/server/src/plugins/route-groups/grants.ts index 7570622118..7fb5d0d2a1 100644 --- a/server/src/plugins/route-groups/grants.ts +++ b/server/src/plugins/route-groups/grants.ts @@ -5,7 +5,7 @@ import { exposureOf, type SharedUseApproval, } from "../shared-use"; -import { ownerGrantRefusal } from "../owner-grants"; +import { approveOwnersSharedUse, ownerGrantRefusal } from "../owner-grants"; import { CatalogueEntryUnknownError, isDeploymentFault, @@ -20,7 +20,8 @@ import type { PluginRouteContext } from "./context"; * * Without an approval the gate refuses every call the grant just allowed, so a grant with none sent * approves the Bot's exposure as it stands now — unless an approval is already recorded, which is - * an administrator's earlier decision and not this grant's to widen. "Is Shared" is the app's + * an administrator's earlier decision and not this grant's to widen (one an owner's grant set is not + * that, and is replaced). "Is Shared" is the app's * answer (its answering row), never the granted row's own column. Null `approval` is nothing to * record; `unreadable` is an approval sent that this deployment cannot read, which the caller * refuses. @@ -46,7 +47,11 @@ export async function sharedGrantApproval( const approval = asSharedUseApproval(sent); return approval ? { appId, approval } : "unreadable"; } - if (await shared.use.approvalFor(agentId, appId)) + /* + * An audience an owner's grant set is not an administrator's decision, so an administrator + * granting over it approves the Bot's exposure now, which makes the audience theirs. + */ + if ((await shared.use.approvalSource(agentId, appId)) === "admin") return { appId, approval: null }; return { appId, @@ -339,8 +344,8 @@ export function mountGrantRoutes({ * {@link sharedGrantApproval} for what is recorded when none is sent. */ /* - * An owner's grant records no Shared approval: who may reach a team account through a Bot is an - * administrator's decision, and the shared-use gate refuses the calls and files their request. + * An owner's grant sends no approval of its own. Where the app is Shared and an administrator + * lets owners decide, the grant sets the audience itself, below; see `approveOwnersSharedUse`. */ const asOwner = kind === "mcp" && !skillActor(context).isAdmin; const decided = @@ -373,6 +378,12 @@ export function mountGrantRoutes({ by: context.var.actor.id, }); } + if (asOwner && shared) + await approveOwnersSharedUse(store, shared.use, { + botId: grantAgentId, + serverId: grantRef.split("/")[0] ?? "", + ownerId: context.var.actor.id, + }); return context.json({ ok: true }); }); From 4333122f065c3597874a43db1b54ab4a2216bc7c Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 13:55:16 -0300 Subject: [PATCH 17/19] Test an owner's Shared-app grant: usable at once, scoped to the Bot, withdrawn with the grant or the rule --- .../owner-app-grants.integration.test.ts | 246 +++++++++++++++++- 1 file changed, 243 insertions(+), 3 deletions(-) diff --git a/server/tests/owner-app-grants.integration.test.ts b/server/tests/owner-app-grants.integration.test.ts index 034ffcb45c..cbae0bfba2 100644 --- a/server/tests/owner-app-grants.integration.test.ts +++ b/server/tests/owner-app-grants.integration.test.ts @@ -13,7 +13,11 @@ import { and, eq, inArray, like } from "drizzle-orm"; import { Hono, type MiddlewareHandler } from "hono"; import { createAgentProfileStore } from "../src/agents/profile-store"; import type { AgentActor } from "../src/agents/profile-types"; -import type { AuditEventInput, AuditStore } from "../src/audit"; +import { + type AuditEventInput, + type AuditStore, + PERSON_INITIATOR, +} from "../src/audit"; import type { AppVariables } from "../src/auth/guards"; import { createDatabase } from "../src/db/client"; import { @@ -23,6 +27,7 @@ import { mcpServers, mcpTools, pluginGrants, + sharedUseApprovals, teamBotConsents, teamBotPublications, users, @@ -30,7 +35,10 @@ import { import type { AppAccess } from "../src/plugins/app-access"; import type { GiveableApp } from "../src/plugins/plugin-store/app-access"; import { createPluginRoutes } from "../src/plugins/routes"; -import { createSharedUseStore } from "../src/plugins/shared-use-store"; +import { + createSharedUseGate, + createSharedUseStore, +} from "../src/plugins/shared-use-store"; import { createPluginStore, PluginRefusedError } from "../src/plugins/store"; import { createTeamBots, @@ -107,6 +115,8 @@ async function connectedServers(userId: string): Promise> { return new Set(rows.map((row) => `composio-${row.app}`)); } const teamBots = createTeamBots({ database, connectedServers }); +const sharedUse = createSharedUseStore(database, "composio"); +const sharedGate = createSharedUseGate(sharedUse, audit); function appAs(actorId: string, role: "admin" | "user" = "user") { const signedIn: MiddlewareHandler<{ Variables: AppVariables }> = async ( @@ -128,7 +138,7 @@ function appAs(actorId: string, role: "admin" | "user" = "user") { async () => true, undefined, { broker: stub.broker, provider: "composio" }, - { modes: {} as never, use: createSharedUseStore(database, "composio") }, + { modes: {} as never, use: sharedUse }, ), ); } @@ -475,3 +485,233 @@ describe("a Team Bot whose owner gave it their own Gmail", () => { }); }); }); + +describe("a Shared app whose rule lets Bot owners decide", () => { + /** The admin opens Slack, a Shared app with its team account connected, to owners. */ + async function openSlack() { + await seedApp(SLACK, "Slack", "shared"); + await database.insert(brokeredConnections).values({ + provider: "composio", + app: slug("slack"), + holder: "deployment", + userId: null, + vendorUserId: `openbot-deployment:${suite}`, + verified: true, + }); + const opened = await appAs(admin, "admin").request( + `/api/plugins/servers/${SLACK}/owner-grants`, + send("PUT", { ownerGrants: "any" }), + ); + expect(opened.status).toBe(200); + } + + const give = (botId: string) => + appAs(owner).request( + `/api/plugins/for/${botId}/apps/${SLACK}/give`, + send("POST"), + ); + + /** Whether this person's own chat with the Bot may reach the team account now. */ + const allowed = async (botId: string, actorId: string) => + ( + await sharedGate({ + botId, + serverId: SLACK, + title: "Slack", + actorId, + initiator: PERSON_INITIATOR, + }) + ).allowed; + + async function approvalRow(botId: string) { + const [row] = await database + .select({ + audience: sharedUseApprovals.audience, + outsideInput: sharedUseApprovals.outsideInput, + approvedBy: sharedUseApprovals.approvedBy, + approvedAs: sharedUseApprovals.approvedAs, + }) + .from(sharedUseApprovals) + .where(eq(sharedUseApprovals.agentId, botId)); + return row ?? null; + } + + const publishToTeam = (botId: string) => + teamBots.publish({ id: owner, role: "user" }, botId, { + audience: "team", + emails: [], + groups: [], + }); + + test("the owner's grant on a private Bot is usable at once, by the owner only, with no admin approval", async () => { + await openSlack(); + const bot = await botOf(owner); + expect(await allowed(bot, owner)).toBe(false); + + expect((await give(bot)).status).toBe(200); + expect(await approvalRow(bot)).toEqual({ + audience: "owner", + outsideInput: false, + approvedBy: owner, + approvedAs: "owner", + }); + expect(await allowed(bot, owner)).toBe(true); + expect(await allowed(bot, teammate)).toBe(false); + expect(await stateOf(owner, bot, SLACK)).toMatchObject({ + state: "ready", + givenByOwner: true, + }); + expect( + events.find((event) => event.eventType === "shared_use.owner_approved"), + ).toMatchObject({ + actorUserId: owner, + targetId: SLACK, + payload: { bot, server: SLACK, audience: "owner", outsideInput: false }, + }); + }); + + test("on a published Team Bot it reaches the Bot's audience, as it stood when given, and no further", async () => { + await openSlack(); + const team = await botOf(owner); + await publishToTeam(team); + expect((await give(team)).status).toBe(200); + expect(await approvalRow(team)).toMatchObject({ + audience: "team", + outsideInput: false, + approvedAs: "owner", + }); + expect(await allowed(team, teammate)).toBe(true); + expect(await stateOf(teammate, team, SLACK)).toMatchObject({ + state: "ready", + }); + // The account is the team's whoever asks: the owner marker never points it at a person. + expect( + await teamBots.credentialActorFor(teammate, team)(`${SLACK}/FETCH`), + ).toBe(teammate); + + // Published after the grant: the audience the grant set is the owner's, and widening asks an admin. + const later = await botOf(owner); + expect((await give(later)).status).toBe(200); + await publishToTeam(later); + expect(await allowed(later, owner)).toBe(true); + expect(await allowed(later, teammate)).toBe(false); + }); + + test("taking the app back withdraws the audience, and an admin's approval stays", async () => { + await openSlack(); + const bot = await botOf(owner); + expect((await give(bot)).status).toBe(200); + const taken = await appAs(owner).request( + `/api/plugins/for/${bot}/apps/${SLACK}/give`, + send("DELETE"), + ); + expect(await taken.json()).toEqual({ revoked: [`${SLACK}/FETCH`] }); + expect(await approvalRow(bot)).toBeNull(); + expect(await allowed(bot, owner)).toBe(false); + expect( + events.find((event) => event.eventType === "shared_use.owner_withdrawn"), + ).toMatchObject({ + actorUserId: owner, + payload: { bot, server: SLACK, why: "owner_took_back" }, + }); + + // An admin granting over the owner's audience makes it theirs, and the owner's take-back leaves it. + expect((await give(bot)).status).toBe(200); + const byAdmin = await appAs(admin, "admin").request( + "/api/plugins/grants", + send("POST", { kind: "mcp", ref: `${SLACK}/SEND`, agentId: bot }), + ); + expect(byAdmin.status).toBe(200); + expect(await approvalRow(bot)).toMatchObject({ approvedAs: "admin" }); + await appAs(owner).request( + `/api/plugins/for/${bot}/apps/${SLACK}/give`, + send("DELETE"), + ); + expect(await approvalRow(bot)).toMatchObject({ approvedAs: "admin" }); + expect(await allowed(bot, owner)).toBe(true); + }); + + test("an admin switching the rule back to admin withdraws the audience and refuses the calls", async () => { + await openSlack(); + const bot = await botOf(owner); + const other = await botOf(owner); + expect((await give(bot)).status).toBe(200); + // An admin's own approval on another Bot is not the owner's to lose. + await store.grant("mcp", `${SLACK}/FETCH`, other, "admin@example.test"); + await sharedUse.setApproval({ + botId: other, + serverId: SLACK, + approval: { audience: "owner", outsideInput: false, members: [] }, + by: admin, + }); + + const closed = await appAs(admin, "admin").request( + `/api/plugins/servers/${SLACK}/owner-grants`, + send("PUT", { ownerGrants: "admin" }), + ); + expect(closed.status).toBe(200); + expect(await approvalRow(bot)).toBeNull(); + expect(await allowed(bot, owner)).toBe(false); + expect(await approvalRow(other)).toMatchObject({ approvedAs: "admin" }); + expect( + events.find((event) => event.eventType === "shared_use.owner_withdrawn"), + ).toMatchObject({ + actorUserId: admin, + payload: { bot, server: SLACK, why: "owner_grants_changed" }, + }); + + // Opened again, the owner's press puts it back; their grant was never taken. + await appAs(admin, "admin").request( + `/api/plugins/servers/${SLACK}/owner-grants`, + send("PUT", { ownerGrants: "any" }), + ); + expect(await allowed(bot, owner)).toBe(false); + expect(await (await give(bot)).json()).toEqual({ granted: [] }); + expect(await allowed(bot, owner)).toBe(true); + }); + + test("an owner's audience left behind is not read while the rule is not any", async () => { + await openSlack(); + const bot = await botOf(owner); + expect((await give(bot)).status).toBe(200); + // The rule changed without the withdrawal, as a replica from before this change would. + await database + .update(mcpServers) + .set({ ownerGrants: null }) + .where(eq(mcpServers.id, SLACK)); + expect(await approvalRow(bot)).not.toBeNull(); + expect(await allowed(bot, owner)).toBe(false); + }); + + test("a Personal app's owner grant sets no Shared audience", async () => { + const bot = await botOf(owner); + await connect(owner); + expect( + ( + await appAs(owner).request( + `/api/plugins/for/${bot}/apps/${GMAIL}/give`, + send("POST"), + ) + ).status, + ).toBe(200); + expect(await approvalRow(bot)).toBeNull(); + expect( + events.some((event) => event.eventType === "shared_use.owner_approved"), + ).toBe(false); + }); + + test("an owner adding one action through the grant route sets the same audience", async () => { + await openSlack(); + const bot = await botOf(owner); + const added = await appAs(owner).request( + "/api/plugins/grants", + send("POST", { kind: "mcp", ref: `${SLACK}/SEND`, agentId: bot }), + ); + expect(added.status).toBe(200); + expect(await approvalRow(bot)).toMatchObject({ + audience: "owner", + approvedAs: "owner", + }); + expect(await allowed(bot, owner)).toBe(true); + }); +}); From f15712eb538bcc0473da896b097b75bf981115a1 Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 13:55:16 -0300 Subject: [PATCH 18/19] Say on a Shared app's page that owners who give it let their Bots use the team account --- app/src/routes/_authed/admin/plugins/$key.tsx | 11 +- app/tests/admin-owner-grants-line.test.tsx | 178 ++++++++++++++++++ 2 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 app/tests/admin-owner-grants-line.test.tsx diff --git a/app/src/routes/_authed/admin/plugins/$key.tsx b/app/src/routes/_authed/admin/plugins/$key.tsx index c86836471b..01f7b91858 100644 --- a/app/src/routes/_authed/admin/plugins/$key.tsx +++ b/app/src/routes/_authed/admin/plugins/$key.tsx @@ -648,7 +648,8 @@ function RouteComponent() { * Who may give the app to a Bot. Binary and immediate, so a Switch. Every person's own * account and the built-in Routines start on; a deployment token, a shared account and an * app with no account start off, because a Bot given one of those reaches an account that - * is not the asker's. An owner's grant always runs on the asker's own account. + * is not the asker's. An owner's grant runs on the asker's own account, except on a shared + * account, where the grant lets the people who can use the Bot use the team account. */} {server ? ( <> @@ -657,9 +658,11 @@ function RouteComponent() { Bot owners can give it to their Bots - {server.ownerGrants === "any" - ? "People can let Bots they own use it, on the account of whoever is asking." - : "Only an admin can give it to a Bot."} + {server.ownerGrants !== "any" + ? "Only an admin can give it to a Bot." + : shared + ? "Bot owners who give it to their Bots let those Bots use the team account." + : "People can let Bots they own use it, on the account of whoever is asking."} diff --git a/app/tests/admin-owner-grants-line.test.tsx b/app/tests/admin-owner-grants-line.test.tsx new file mode 100644 index 0000000000..615288f7f6 --- /dev/null +++ b/app/tests/admin-owner-grants-line.test.tsx @@ -0,0 +1,178 @@ +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + expect, + test, +} from "bun:test"; +import { GlobalRegistrator } from "@happy-dom/global-registrator"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, + Outlet, + RouterProvider, +} from "@tanstack/react-router"; +import { cleanup, render } from "@testing-library/react"; +import type { PluginServer, PluginsPage } from "@/lib/plugins/queries"; +import { Route as AdminPluginRoute } from "@/routes/_authed/admin/plugins/$key"; + +/** + * The admin app page's "Bot owners can give it to their Bots" line says what letting owners decide + * means for the app's account: on a shared account, an owner's grant lets their Bot use the team + * account. + */ + +beforeAll(() => GlobalRegistrator.register()); +afterEach(cleanup); +afterAll(async () => { + await new Promise((resolve) => setTimeout(resolve, 50)); + await GlobalRegistrator.unregister(); +}); + +const APP = "gmail"; + +function captureRouteState(route: object): Record { + return { ...route, options: { ...(route as { options: object }).options } }; +} +function restoreRouteState( + route: object, + snapshot: Record, +): void { + for (const key of Object.keys(route)) { + if (!(key in snapshot)) { + delete (route as Record)[key]; + } + } + Object.assign(route, snapshot); +} +const pristine = captureRouteState(AdminPluginRoute); +let snapshot: Record; +beforeEach(() => { + snapshot = captureRouteState(pristine); +}); +afterEach(() => { + restoreRouteState(AdminPluginRoute, snapshot); +}); + +function server(shared: boolean, ownerGrants: "any" | "admin"): PluginServer { + return { + authScheme: "OAUTH2", + id: APP, + title: "Gmail", + vendor: "Google", + url: "https://example.invalid", + summary: "Mail.", + docsUrl: "", + provenance: "composio", + hasCredential: false, + toolsRefreshedAt: null, + lastError: null, + addedBy: null, + dynamicClient: false, + accountMode: shared ? "shared" : null, + sharedVendorConnected: false, + ownerGrants, + ownerGrantsChosen: null, + tools: [], + withdrawn: [], + }; +} + +function install(setup: { shared: boolean; ownerGrants: "any" | "admin" }) { + const page: PluginsPage = { + catalogue: [], + servers: [server(setup.shared, setup.ownerGrants)], + skills: [], + botsMayCallBack: true, + redirectUri: null, + composioConfigured: true, + authorizesPerAction: false, + provider: "composio", + }; + const json = (body: unknown, status = 200) => + new Response(JSON.stringify(body), { + headers: { "content-type": "application/json" }, + status, + }); + global.fetch = (async (input: RequestInfo | URL) => { + const path = String(input).split("?")[0] ?? ""; + if (path === "/api/plugins") return json(page); + if (path.startsWith("/api/plugins/connections")) + return json({ connections: [], redirectUri: null }); + if (path.startsWith("/api/agents")) return json([]); + return json({ error: "unexpected" }, 404); + }) as typeof fetch; +} + +function renderScreen() { + const rootRoute = createRootRoute({ component: Outlet }); + const authedRoute = createRoute({ + id: "/_authed", + getParentRoute: () => rootRoute, + component: Outlet, + }); + const adminRoute = createRoute({ + path: "/admin", + getParentRoute: () => authedRoute, + component: Outlet, + }); + const indexRoute = createRoute({ + path: "/plugins/", + getParentRoute: () => adminRoute, + component: () => null, + }); + const wired = ( + AdminPluginRoute as unknown as { + update: (options: unknown) => typeof AdminPluginRoute; + } + ).update({ + id: "/plugins/$key", + path: "/plugins/$key", + getParentRoute: () => adminRoute, + }); + const router = createRouter({ + routeTree: rootRoute.addChildren([ + authedRoute.addChildren([adminRoute.addChildren([indexRoute, wired])]), + ]), + history: createMemoryHistory({ initialEntries: [`/admin/plugins/${APP}`] }), + }); + return render( + + + , + ); +} + +const cases = [ + { + shared: true, + ownerGrants: "any", + line: "Bot owners who give it to their Bots let those Bots use the team account.", + }, + { + shared: false, + ownerGrants: "any", + line: "People can let Bots they own use it, on the account of whoever is asking.", + }, + { + shared: true, + ownerGrants: "admin", + line: "Only an admin can give it to a Bot.", + }, +] as const; + +for (const { shared, ownerGrants, line } of cases) { + test(`a ${shared ? "shared" : "personal"} app whose rule is ${ownerGrants} says: ${line}`, async () => { + install({ shared, ownerGrants }); + const view = renderScreen(); + expect(await view.findByText(line)).toBeTruthy(); + }); +} From dc9690aa55260fbc3bf573a99db065feb2a588b5 Mon Sep 17 00:00:00 2001 From: Guido Vizoso Date: Mon, 26 Oct 2026 13:55:16 -0300 Subject: [PATCH 19/19] Document owners giving Shared apps, and add the changelog line --- CHANGELOG.md | 6 ++++++ docs/plugins/composio.md | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fa40a013b..f986ef5b28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,12 @@ decides per app on its page, and can switch it off for people with the "Give app capability. Migration `0063_owner_app_grants` adds two nullable columns, `mcp_servers.owner_grants` and `plugin_grants.granted_as`. +Where an administrator lets owners give a Shared app, an owner's grant works straight away: it lets +the people who can use the Bot (just the owner, or a Team Bot's audience) use the team account, with +no separate approval. Writes still ask first. Taking the app back, or the administrator switching the +app back to admins only, withdraws that. Migration `0064_owner_shared_audience` adds the nullable +`shared_use_approvals.approved_as`. + ### An app card carries the task on once the app is ready When a Bot stopped because it needed an app, and the person then connected it in another tab or an diff --git a/docs/plugins/composio.md b/docs/plugins/composio.md index 97b9a29f10..cb4db23773 100644 --- a/docs/plugins/composio.md +++ b/docs/plugins/composio.md @@ -429,6 +429,15 @@ them), or everyone — and separately, whether outside input (email, Slack, webh than a schedule or Run now) may. Every call is checked against that, including runs handed on from another Bot, which are judged by what started the first run. +**Owners can decide, if an administrator lets them.** By default only an administrator gives a +Shared app to a Bot. If the app's page says Bot owners can give it to their Bots, an owner who gives +it to a Bot they own also sets who may use the account through that Bot: the people who can use the +Bot when they give it (only the owner for a private Bot, its published audience for a Team Bot), +and never outside input. No separate approval is needed. The trail records it as +`shared_use.owner_approved`, naming the owner, the Bot and the app. Taking the app back, or the +administrator switching the app back to admins only, withdraws that audience +(`shared_use.owner_withdrawn`); an audience an administrator approved stays. + **Widening a Bot later** — publishing it further, assigning it to a group, making it public, adding a trigger — goes through. If an administrator made the change, it is approved in the same step. If the owner did, the screen says the shared calls will be refused and offers Request approval, and a call