From d6ec115348d0581fc2e6729298db7f31c776d1d6 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Tue, 7 Apr 2026 16:11:31 -0700 Subject: [PATCH 01/11] v0.6.29: login improvements, posthog telemetry (#4026) * feat(posthog): Add tracking on mothership abort (#4023) Co-authored-by: Theodore Li * fix(login): fix captcha headers for manual login (#4025) * fix(signup): fix turnstile key loading * fix(login): fix captcha header passing * Catch user already exists, remove login form captcha --- apps/sim/app/(auth)/signup/signup-form.tsx | 11 +++-------- .../app/workspace/[workspaceId]/home/home.tsx | 12 ++++++++++-- .../w/[workflowId]/components/panel/panel.tsx | 19 ++++++++++++++++++- apps/sim/lib/posthog/events.ts | 5 +++++ 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/apps/sim/app/(auth)/signup/signup-form.tsx b/apps/sim/app/(auth)/signup/signup-form.tsx index 55a0508ec1b..afb27cd729a 100644 --- a/apps/sim/app/(auth)/signup/signup-form.tsx +++ b/apps/sim/app/(auth)/signup/signup-form.tsx @@ -270,10 +270,8 @@ function SignupFormContent({ name: sanitizedName, }, { - fetchOptions: { - headers: { - ...(token ? { 'x-captcha-response': token } : {}), - }, + headers: { + ...(token ? { 'x-captcha-response': token } : {}), }, onError: (ctx) => { logger.error('Signup error:', ctx.error) @@ -282,10 +280,7 @@ function SignupFormContent({ let errorCode = 'unknown' if (ctx.error.code?.includes('USER_ALREADY_EXISTS')) { errorCode = 'user_already_exists' - errorMessage.push( - 'An account with this email already exists. Please sign in instead.' - ) - setEmailError(errorMessage[0]) + setEmailError('An account with this email already exists. Please sign in instead.') } else if ( ctx.error.code?.includes('BAD_REQUEST') || ctx.error.message?.includes('Email and password sign up is not enabled') diff --git a/apps/sim/app/workspace/[workspaceId]/home/home.tsx b/apps/sim/app/workspace/[workspaceId]/home/home.tsx index d76f17ff454..38367339197 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/home.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/home.tsx @@ -223,6 +223,14 @@ export function Home({ chatId }: HomeProps = {}) { posthogRef.current = posthog }, [posthog]) + const handleStopGeneration = useCallback(() => { + captureEvent(posthogRef.current, 'task_generation_aborted', { + workspace_id: workspaceId, + view: 'mothership', + }) + stopGeneration() + }, [stopGeneration, workspaceId]) + const handleSubmit = useCallback( (text: string, fileAttachments?: FileAttachmentForApi[], contexts?: ChatContext[]) => { const trimmed = text.trim() @@ -334,7 +342,7 @@ export function Home({ chatId }: HomeProps = {}) { defaultValue={initialPrompt} onSubmit={handleSubmit} isSending={isSending} - onStopGeneration={stopGeneration} + onStopGeneration={handleStopGeneration} userId={session?.user?.id} onContextAdd={handleContextAdd} /> @@ -359,7 +367,7 @@ export function Home({ chatId }: HomeProps = {}) { isSending={isSending} isReconnecting={isReconnecting} onSubmit={handleSubmit} - onStopGeneration={stopGeneration} + onStopGeneration={handleStopGeneration} messageQueue={messageQueue} onRemoveQueuedMessage={removeFromQueue} onSendQueuedMessage={sendNow} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx index 4d485c763ce..da51910789b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx @@ -4,6 +4,7 @@ import { memo, useCallback, useEffect, useRef, useState } from 'react' import { createLogger } from '@sim/logger' import { History, Plus, Square } from 'lucide-react' import { useParams, useRouter } from 'next/navigation' +import { usePostHog } from 'posthog-js/react' import { useShallow } from 'zustand/react/shallow' import { BubbleChatClose, @@ -33,6 +34,7 @@ import { import { Lock, Unlock, Upload } from '@/components/emcn/icons' import { VariableIcon } from '@/components/icons' import { useSession } from '@/lib/auth/auth-client' +import { captureEvent } from '@/lib/posthog/client' import { generateWorkflowJson } from '@/lib/workflows/operations/import-export' import { ConversationListItem } from '@/app/workspace/[workspaceId]/components' import { MothershipChat } from '@/app/workspace/[workspaceId]/home/components' @@ -101,6 +103,9 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel const params = useParams() const workspaceId = propWorkspaceId ?? (params.workspaceId as string) + const posthog = usePostHog() + const posthogRef = useRef(posthog) + const panelRef = useRef(null) const fileInputRef = useRef(null) const { activeTab, setActiveTab, panelWidth, _hasHydrated, setHasHydrated } = usePanelStore( @@ -264,6 +269,10 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel loadCopilotChats() }, [loadCopilotChats]) + useEffect(() => { + posthogRef.current = posthog + }, [posthog]) + const handleCopilotSelectChat = useCallback((chat: { id: string; title: string | null }) => { setCopilotChatId(chat.id) setCopilotChatTitle(chat.title) @@ -394,6 +403,14 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel [copilotEditQueuedMessage] ) + const handleCopilotStopGeneration = useCallback(() => { + captureEvent(posthogRef.current, 'task_generation_aborted', { + workspace_id: workspaceId, + view: 'copilot', + }) + copilotStopGeneration() + }, [copilotStopGeneration, workspaceId]) + const handleCopilotSubmit = useCallback( (text: string, fileAttachments?: FileAttachmentForApi[], contexts?: ChatContext[]) => { const trimmed = text.trim() @@ -833,7 +850,7 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel isSending={copilotIsSending} isReconnecting={copilotIsReconnecting} onSubmit={handleCopilotSubmit} - onStopGeneration={copilotStopGeneration} + onStopGeneration={handleCopilotStopGeneration} messageQueue={copilotMessageQueue} onRemoveQueuedMessage={copilotRemoveFromQueue} onSendQueuedMessage={copilotSendNow} diff --git a/apps/sim/lib/posthog/events.ts b/apps/sim/lib/posthog/events.ts index 537a9864282..faf9895bf62 100644 --- a/apps/sim/lib/posthog/events.ts +++ b/apps/sim/lib/posthog/events.ts @@ -378,6 +378,11 @@ export interface PostHogEventMap { workspace_id: string } + task_generation_aborted: { + workspace_id: string + view: 'mothership' | 'copilot' + } + task_message_sent: { workspace_id: string has_attachments: boolean From d985f12a72ae79f76ddc1f237b2cfd6762c66199 Mon Sep 17 00:00:00 2001 From: Ricardo DeMatos Date: Sun, 19 Jul 2026 17:14:13 -0700 Subject: [PATCH 02/11] feat(managed-agent): Claude Platform Managed Agents workflow blocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship two workflow blocks that let a workflow author invoke a Claude Platform Managed Agent (cloud or self-hosted) as a first-class node, plus the workspace-scoped connection layer that stores the linked Anthropic API key. Blocks - **Claude Managed Agents** (`managed_agent_cloud`) — cloud environments. Session-create payload carries `agent`, `environment_id`, `vault_ids`, `resources` (memory + files) and free-form `metadata` tags. - **Claude Managed Agents (self-hosted)** (`managed_agent_self_hosted`) — self-hosted environments. Session `metadata` is forwarded as env vars to the deployer's self-hosted agent sandbox; the memory fields are gated behind `NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED` because Claude self-hosted environments do not currently support the memory-store resource attach on the session API. Default seed rows for the Session-parameters table come from `NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS` (JSON object); the seeded keys stay out of source so this block is deployment- neutral in the public tree. Connections - New `managed_agent_connection` table (workspace-scoped, encrypted API key). All CRUD paths go through `getUserEntityPermissions(userId, 'workspace', workspaceId)`; `admin`/`write` to create, rotate, or delete; any workspace member can list/browse. Plaintext keys never cross the client boundary — list responses return only a masked preview. - Settings surface under `settings/components/managed-agents/` lets admins add / rotate / remove connections with an inline Anthropic-API verify (`GET /v1/agents`). - 8 server routes proxy the Claude Platform read endpoints (agents, environments, memory stores, vaults, environment detail) so the block picker resolves options against the linked workspace. Tool + session client - `tools/managed_agent/run_session.ts` (client-safe skeleton) + `run_session.server.ts` (server-only impl, self-registered on boot via `globalThis` so the client bundle never pulls `postgres` / `fs` / encryption). Reconnect + `events.list` catch-up loop matches the docs' two-step session lifecycle. - `lib/managed-agents/session-client.ts` — the reusable HTTP client that shapes the cloud vs. self-hosted request bodies. - `tools/managed_agent/normalizers.ts` — pure input normalizers that coerce the workflow block's runtime shapes (table rows, JSON strings, flat objects, comma-lists) into typed values. Node UX - Optional `BlockConfig.nodeWidth` (default 250, MA blocks use 400) threaded through `calculateWorkflowBlockDimensions` for both the workflow-block hook and autolayout so long Anthropic IDs are not ellipsis-truncated in the collapsed row. - Friendly-name hydration on the collapsed row resolves connection / agent / environment / vault / memory-store IDs to human labels via the React Query hooks. New `blockType` prop on `SubBlockRow` gates the lookup. - New optional `defaultRows` prop on the `Table` subblock so any block can seed a fresh table with initial rows. Test coverage Aligned with the repo's existing patterns (`@vitest-environment node`, `vi.hoisted` + `vi.mock` + static imports, `@sim/testing` helpers). 86 tests across 5 files: - `tools/managed_agent/normalizers.test.ts` (27) — every shape the block subblocks hand off + bad-input paths. - `lib/managed-agents/session-client.test.ts` (17) — cloud vs. self-hosted branch: memory routing (resource vs. metadata), file resources cloud-only, `vault_ids` only when non-empty, user metadata does not overwrite memory keys, envType-omitted default. - `lib/managed-agents/connections.test.ts` (17) — CRUD via captured DB-chain args: encryption on write (plaintext never lands in row), masking on read, workspace-scoped `WHERE` on every op, `verify` gate blocks persistence, error truncation to 500 chars. - `app/api/managed-agent-connections/route.test.ts` (15) — permission gates on GET/POST/DELETE: 401 unauthenticated, 400 missing params, 403 read-tier callers on create/delete, 404 for missing rows, plaintext never in POST response, both `write` and `admin` accepted. - `blocks/blocks/managed_agent_self_hosted.test.ts` (10) — env-driven `readSessionMetadataDefaults` and `isSelfHostedMemoryEnabled` (truthy/falsy/whitespace forms). Documentation - New env vars documented in `apps/sim/.env.example`: `MANAGED_AGENT_DEBUG_PAYLOAD`, `NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS`, `NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED`. --- apps/sim/.env.example | 2 + .../[id]/agents/route.ts | 89 + .../[id]/environments/[envId]/route.ts | 102 + .../[id]/environments/route.ts | 96 + .../[id]/memory-stores/route.ts | 108 + .../managed-agent-connections/[id]/route.ts | 69 + .../[id]/vaults/route.ts | 90 + .../managed-agent-connections/route.test.ts | 275 + .../api/managed-agent-connections/route.ts | 163 + .../settings/[section]/settings.tsx | 6 + .../managed-agents/connection-form-modal.tsx | 121 + .../components/managed-agents/index.ts | 1 + .../managed-agents/managed-agents.tsx | 200 + .../sub-block/components/table/table.tsx | 28 +- .../editor/components/sub-block/sub-block.tsx | 5 + .../workflow-block/workflow-block.tsx | 91 + apps/sim/blocks/blocks/managed_agent_cloud.ts | 212 + .../blocks/managed_agent_self_hosted.test.ts | 121 + .../blocks/managed_agent_self_hosted.ts | 263 + apps/sim/blocks/registry-maps.ts | 12 + apps/sim/blocks/types.ts | 7 + apps/sim/components/icons.tsx | 21 + apps/sim/components/settings/navigation.ts | 17 +- .../queries/managed-agent-connections.ts | 240 + apps/sim/lib/api/contracts/index.ts | 1 + .../contracts/managed-agent-connections.ts | 206 + apps/sim/lib/core/config/env.ts | 5 + .../lib/managed-agents/anthropic-verify.ts | 35 + .../lib/managed-agents/connections.test.ts | 367 + apps/sim/lib/managed-agents/connections.ts | 219 + apps/sim/lib/managed-agents/proxy.ts | 60 + .../lib/managed-agents/session-client.test.ts | 201 + apps/sim/lib/managed-agents/session-client.ts | 303 + .../lib/managed-agents/subblock-options.ts | 178 + apps/sim/lib/workflows/autolayout/utils.ts | 1 + .../blocks/deterministic-dimensions.ts | 5 +- apps/sim/tools/index.ts | 5 + apps/sim/tools/managed_agent/index.ts | 2 + .../tools/managed_agent/normalizers.test.ts | 206 + apps/sim/tools/managed_agent/normalizers.ts | 160 + .../tools/managed_agent/run_session.server.ts | 393 + apps/sim/tools/managed_agent/run_session.ts | 147 + apps/sim/tools/managed_agent/types.ts | 55 + apps/sim/tools/registry.ts | 2 + .../0264_managed_agent_connection.sql | 16 + .../db/migrations/meta/0264_snapshot.json | 17498 ++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/schema.ts | 32 + scripts/check-api-validation-contracts.ts | 4 +- 49 files changed, 22435 insertions(+), 12 deletions(-) create mode 100644 apps/sim/app/api/managed-agent-connections/[id]/agents/route.ts create mode 100644 apps/sim/app/api/managed-agent-connections/[id]/environments/[envId]/route.ts create mode 100644 apps/sim/app/api/managed-agent-connections/[id]/environments/route.ts create mode 100644 apps/sim/app/api/managed-agent-connections/[id]/memory-stores/route.ts create mode 100644 apps/sim/app/api/managed-agent-connections/[id]/route.ts create mode 100644 apps/sim/app/api/managed-agent-connections/[id]/vaults/route.ts create mode 100644 apps/sim/app/api/managed-agent-connections/route.test.ts create mode 100644 apps/sim/app/api/managed-agent-connections/route.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/connection-form-modal.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/managed-agents.tsx create mode 100644 apps/sim/blocks/blocks/managed_agent_cloud.ts create mode 100644 apps/sim/blocks/blocks/managed_agent_self_hosted.test.ts create mode 100644 apps/sim/blocks/blocks/managed_agent_self_hosted.ts create mode 100644 apps/sim/hooks/queries/managed-agent-connections.ts create mode 100644 apps/sim/lib/api/contracts/managed-agent-connections.ts create mode 100644 apps/sim/lib/managed-agents/anthropic-verify.ts create mode 100644 apps/sim/lib/managed-agents/connections.test.ts create mode 100644 apps/sim/lib/managed-agents/connections.ts create mode 100644 apps/sim/lib/managed-agents/proxy.ts create mode 100644 apps/sim/lib/managed-agents/session-client.test.ts create mode 100644 apps/sim/lib/managed-agents/session-client.ts create mode 100644 apps/sim/lib/managed-agents/subblock-options.ts create mode 100644 apps/sim/tools/managed_agent/index.ts create mode 100644 apps/sim/tools/managed_agent/normalizers.test.ts create mode 100644 apps/sim/tools/managed_agent/normalizers.ts create mode 100644 apps/sim/tools/managed_agent/run_session.server.ts create mode 100644 apps/sim/tools/managed_agent/run_session.ts create mode 100644 apps/sim/tools/managed_agent/types.ts create mode 100644 packages/db/migrations/0264_managed_agent_connection.sql create mode 100644 packages/db/migrations/meta/0264_snapshot.json diff --git a/apps/sim/.env.example b/apps/sim/.env.example index 08003939323..8dab23a2237 100644 --- a/apps/sim/.env.example +++ b/apps/sim/.env.example @@ -64,6 +64,8 @@ API_ENCRYPTION_KEY=your_api_encryption_key # Use `openssl rand -hex 32` to gener # LITELLM_API_KEY= # Optional bearer token if your LiteLLM proxy requires auth # FIREWORKS_API_KEY= # Optional Fireworks AI API key for model listing # NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS=true # Set when using AWS default credential chain (IAM roles, ECS task roles, IRSA). Hides credential fields in Agent block UI. +# NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS='{"SOURCE_TYPE":"git","SOURCE_REF":"main"}' # JSON object of default session-metadata rows seeded into fresh Claude Managed Agents (self-hosted) blocks. Deployment-specific; keys map to whatever env vars your self-hosted agent sandbox reads. Absent = empty table. +# NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED=true # Reveal Memory Store + Memory Access fields on the Claude Managed Agents (self-hosted) block. Off by default: Claude self-hosted environments do not currently support the memory-store resource attach on the session API. Enable only if your self-hosted agent sandbox implements memory attach on its side. # AZURE_OPENAI_ENDPOINT= # Azure OpenAI endpoint (hides field in UI when set alongside NEXT_PUBLIC_AZURE_CONFIGURED) # AZURE_OPENAI_API_KEY= # Azure OpenAI API key # AZURE_OPENAI_API_VERSION= # Azure OpenAI API version diff --git a/apps/sim/app/api/managed-agent-connections/[id]/agents/route.ts b/apps/sim/app/api/managed-agent-connections/[id]/agents/route.ts new file mode 100644 index 00000000000..4bc4e1c9855 --- /dev/null +++ b/apps/sim/app/api/managed-agent-connections/[id]/agents/route.ts @@ -0,0 +1,89 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { + type ManagedAgentAgent, + managedAgentProxyParamsSchema, + managedAgentProxyQuerySchema, +} from '@/lib/api/contracts' +import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { getDecryptedApiKey } from '@/lib/managed-agents/connections' +import { + type AnthropicListPage, + ManagedAgentProxyError, + proxyManagedAgentsGet, +} from '@/lib/managed-agents/proxy' +import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' + +const logger = createLogger('ManagedAgentAgentsAPI') + +interface AnthropicAgent { + id: string + name?: string + description?: string + latest_version?: number | string + version?: number | string +} + +interface RouteContext { + params: Promise<{ id: string }> +} + +export const GET = withRouteHandler(async (request: NextRequest, context: RouteContext) => { + const requestId = generateRequestId() + try { + const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success || !authResult.userId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + const userId = authResult.userId + + const rawParams = await context.params + const paramsValidation = managedAgentProxyParamsSchema.safeParse(rawParams) + const queryValidation = managedAgentProxyQuerySchema.safeParse( + Object.fromEntries(request.nextUrl.searchParams.entries()) + ) + if (!paramsValidation.success || !queryValidation.success) { + return NextResponse.json({ error: 'Invalid request data' }, { status: 400 }) + } + const { id } = paramsValidation.data + const { workspaceId } = queryValidation.data + + const userPermission = await getUserEntityPermissions(userId, 'workspace', workspaceId) + if (!userPermission) { + return NextResponse.json({ error: 'Access denied' }, { status: 403 }) + } + + const apiKey = await getDecryptedApiKey({ id, workspaceId }) + if (!apiKey) { + return NextResponse.json({ error: 'Connection not found' }, { status: 404 }) + } + + try { + const body = await proxyManagedAgentsGet>( + apiKey, + '/v1/agents?limit=100' + ) + const data: ManagedAgentAgent[] = (body.data ?? []).map((row) => ({ + id: row.id, + name: row.name ?? null, + description: row.description ?? null, + version: row.latest_version ?? row.version ?? null, + })) + return NextResponse.json({ data }) + } catch (error) { + if (error instanceof ManagedAgentProxyError) { + return NextResponse.json({ error: error.message }, { status: 502 }) + } + throw error + } + } catch (error) { + logger.error(`[${requestId}] Failed to list agents`, error) + return NextResponse.json( + { error: getErrorMessage(error, 'Failed to list agents') }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/managed-agent-connections/[id]/environments/[envId]/route.ts b/apps/sim/app/api/managed-agent-connections/[id]/environments/[envId]/route.ts new file mode 100644 index 00000000000..d88c7e89409 --- /dev/null +++ b/apps/sim/app/api/managed-agent-connections/[id]/environments/[envId]/route.ts @@ -0,0 +1,102 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { + type ManagedAgentEnvironment, + managedAgentEnvironmentDetailParamsSchema, + managedAgentProxyQuerySchema, +} from '@/lib/api/contracts' +import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { getDecryptedApiKey } from '@/lib/managed-agents/connections' +import { + ManagedAgentProxyError, + proxyManagedAgentsGet, +} from '@/lib/managed-agents/proxy' +import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' + +const logger = createLogger('ManagedAgentEnvironmentAPI') + +interface AnthropicEnvironment { + id: string + name?: string + description?: string + scope?: 'organization' | 'account' | null + config?: { type?: 'cloud' | 'self_hosted' } +} + +interface RouteContext { + params: Promise<{ id: string; envId: string }> +} + +/** + * GET /api/managed-agent-connections/[id]/environments/[envId] — used by + * the workflow-block tool at run time to resolve `envType` before + * building the session-create payload. + */ +export const GET = withRouteHandler(async (request: NextRequest, context: RouteContext) => { + const requestId = generateRequestId() + try { + const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success || !authResult.userId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + const userId = authResult.userId + + const rawParams = await context.params + const paramsValidation = managedAgentEnvironmentDetailParamsSchema.safeParse(rawParams) + const queryValidation = managedAgentProxyQuerySchema.safeParse( + Object.fromEntries(request.nextUrl.searchParams.entries()) + ) + if (!paramsValidation.success || !queryValidation.success) { + return NextResponse.json({ error: 'Invalid request data' }, { status: 400 }) + } + const { id, envId } = paramsValidation.data + const { workspaceId } = queryValidation.data + + const userPermission = await getUserEntityPermissions(userId, 'workspace', workspaceId) + if (!userPermission) { + return NextResponse.json({ error: 'Access denied' }, { status: 403 }) + } + + const apiKey = await getDecryptedApiKey({ id, workspaceId }) + if (!apiKey) { + return NextResponse.json({ error: 'Connection not found' }, { status: 404 }) + } + + try { + const body = await proxyManagedAgentsGet( + apiKey, + `/v1/environments/${encodeURIComponent(envId)}` + ) + const envType = body.config?.type + if (envType !== 'cloud' && envType !== 'self_hosted') { + return NextResponse.json( + { error: 'Environment returned an unrecognised config.type' }, + { status: 502 } + ) + } + const data: ManagedAgentEnvironment = { + id: body.id, + name: body.name ?? null, + description: body.description ?? null, + envType, + scope: body.scope ?? null, + } + return NextResponse.json({ data }) + } catch (error) { + if (error instanceof ManagedAgentProxyError) { + const status = error.status === 404 ? 404 : 502 + return NextResponse.json({ error: error.message }, { status }) + } + throw error + } + } catch (error) { + logger.error(`[${requestId}] Failed to fetch environment`, error) + return NextResponse.json( + { error: getErrorMessage(error, 'Failed to fetch environment') }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/managed-agent-connections/[id]/environments/route.ts b/apps/sim/app/api/managed-agent-connections/[id]/environments/route.ts new file mode 100644 index 00000000000..df7994fe86c --- /dev/null +++ b/apps/sim/app/api/managed-agent-connections/[id]/environments/route.ts @@ -0,0 +1,96 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { + type ManagedAgentEnvironment, + managedAgentProxyParamsSchema, + managedAgentProxyQuerySchema, +} from '@/lib/api/contracts' +import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { getDecryptedApiKey } from '@/lib/managed-agents/connections' +import { + type AnthropicListPage, + ManagedAgentProxyError, + proxyManagedAgentsGet, +} from '@/lib/managed-agents/proxy' +import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' + +const logger = createLogger('ManagedAgentEnvironmentsAPI') + +interface AnthropicEnvironment { + id: string + name?: string + description?: string + scope?: 'organization' | 'account' | null + config?: { type?: 'cloud' | 'self_hosted' } +} + +interface RouteContext { + params: Promise<{ id: string }> +} + +export const GET = withRouteHandler(async (request: NextRequest, context: RouteContext) => { + const requestId = generateRequestId() + try { + const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success || !authResult.userId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + const userId = authResult.userId + + const rawParams = await context.params + const paramsValidation = managedAgentProxyParamsSchema.safeParse(rawParams) + const queryValidation = managedAgentProxyQuerySchema.safeParse( + Object.fromEntries(request.nextUrl.searchParams.entries()) + ) + if (!paramsValidation.success || !queryValidation.success) { + return NextResponse.json({ error: 'Invalid request data' }, { status: 400 }) + } + const { id } = paramsValidation.data + const { workspaceId } = queryValidation.data + + const userPermission = await getUserEntityPermissions(userId, 'workspace', workspaceId) + if (!userPermission) { + return NextResponse.json({ error: 'Access denied' }, { status: 403 }) + } + + const apiKey = await getDecryptedApiKey({ id, workspaceId }) + if (!apiKey) { + return NextResponse.json({ error: 'Connection not found' }, { status: 404 }) + } + + try { + const body = await proxyManagedAgentsGet>( + apiKey, + '/v1/environments?limit=100' + ) + const data: ManagedAgentEnvironment[] = (body.data ?? []) + .map((row) => { + const envType = row.config?.type + if (envType !== 'cloud' && envType !== 'self_hosted') return null + return { + id: row.id, + name: row.name ?? null, + description: row.description ?? null, + envType, + scope: row.scope ?? null, + } + }) + .filter((row): row is ManagedAgentEnvironment => row !== null) + return NextResponse.json({ data }) + } catch (error) { + if (error instanceof ManagedAgentProxyError) { + return NextResponse.json({ error: error.message }, { status: 502 }) + } + throw error + } + } catch (error) { + logger.error(`[${requestId}] Failed to list environments`, error) + return NextResponse.json( + { error: getErrorMessage(error, 'Failed to list environments') }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/managed-agent-connections/[id]/memory-stores/route.ts b/apps/sim/app/api/managed-agent-connections/[id]/memory-stores/route.ts new file mode 100644 index 00000000000..931cad1c8e1 --- /dev/null +++ b/apps/sim/app/api/managed-agent-connections/[id]/memory-stores/route.ts @@ -0,0 +1,108 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { + type ManagedAgentMemoryStore, + managedAgentProxyParamsSchema, + managedAgentProxyQuerySchema, +} from '@/lib/api/contracts' +import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { getDecryptedApiKey } from '@/lib/managed-agents/connections' +import { + ManagedAgentProxyError, + proxyManagedAgentsGet, +} from '@/lib/managed-agents/proxy' +import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' + +const logger = createLogger('ManagedAgentMemoryStoresAPI') + +interface AnthropicMemoryStore { + id: string + name?: string + description?: string + archived_at?: string | null + type?: 'memory_store' +} + +interface AnthropicMemoryStorePage { + data?: AnthropicMemoryStore[] + next_page?: string | null +} + +interface RouteContext { + params: Promise<{ id: string }> +} + +/** + * GET /api/managed-agent-connections/[id]/memory-stores — proxies to + * Anthropic's `GET /v1/memory_stores` using the connection's stored API + * key. Powers the Memory Store combobox on the Managed Agent block. + * + * Archived stores are excluded (Anthropic's `include_archived` defaults + * to false, and we don't surface them to workflow authors — a stale + * memory store would just fail at session-create time). + */ +export const GET = withRouteHandler(async (request: NextRequest, context: RouteContext) => { + const requestId = generateRequestId() + try { + const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success || !authResult.userId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + const userId = authResult.userId + + const rawParams = await context.params + const paramsValidation = managedAgentProxyParamsSchema.safeParse(rawParams) + const queryValidation = managedAgentProxyQuerySchema.safeParse( + Object.fromEntries(request.nextUrl.searchParams.entries()) + ) + if (!paramsValidation.success || !queryValidation.success) { + return NextResponse.json({ error: 'Invalid request data' }, { status: 400 }) + } + const { id } = paramsValidation.data + const { workspaceId } = queryValidation.data + + const userPermission = await getUserEntityPermissions(userId, 'workspace', workspaceId) + if (!userPermission) { + return NextResponse.json({ error: 'Access denied' }, { status: 403 }) + } + + const apiKey = await getDecryptedApiKey({ id, workspaceId }) + if (!apiKey) { + return NextResponse.json({ error: 'Connection not found' }, { status: 404 }) + } + + try { + const body = await proxyManagedAgentsGet( + apiKey, + '/v1/memory_stores?limit=100' + ) + const data: ManagedAgentMemoryStore[] = (body.data ?? []) + .filter((row) => row.archived_at == null) + .map((row) => ({ + id: row.id, + name: row.name ?? null, + description: row.description ?? null, + archivedAt: row.archived_at ?? null, + })) + return NextResponse.json({ data }) + } catch (error) { + if (error instanceof ManagedAgentProxyError) { + // 404 = feature not enabled on this workspace / beta not granted. + // Return empty so the Memory Store field shows "no options" instead + // of blocking the whole editor. + if (error.status === 404) return NextResponse.json({ data: [] }) + return NextResponse.json({ error: error.message }, { status: 502 }) + } + throw error + } + } catch (error) { + logger.error(`[${requestId}] Failed to list memory stores`, error) + return NextResponse.json( + { error: getErrorMessage(error, 'Failed to list memory stores') }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/managed-agent-connections/[id]/route.ts b/apps/sim/app/api/managed-agent-connections/[id]/route.ts new file mode 100644 index 00000000000..b0069b157fb --- /dev/null +++ b/apps/sim/app/api/managed-agent-connections/[id]/route.ts @@ -0,0 +1,69 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { rotateManagedAgentConnectionContract } from '@/lib/api/contracts' +import { parseRequest } from '@/lib/api/server' +import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { verifyAnthropicApiKey } from '@/lib/managed-agents/anthropic-verify' +import { rotateConnectionKey } from '@/lib/managed-agents/connections' +import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' + +const logger = createLogger('ManagedAgentConnectionAPI') + +interface RouteContext { + params: Promise<{ id: string }> +} + +/** PATCH - Rotate a connection's API key. Verifies new key before writing. */ +export const PATCH = withRouteHandler(async (req: NextRequest, context: RouteContext) => { + const requestId = generateRequestId() + try { + const authResult = await checkSessionOrInternalAuth(req, { requireWorkflowId: false }) + if (!authResult.success || !authResult.userId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + const userId = authResult.userId + + const parsed = await parseRequest(rotateManagedAgentConnectionContract, req, context) + if (!parsed.success) return parsed.response + + const { id } = parsed.data.params + const { workspaceId, apiKey } = parsed.data.body + + const userPermission = await getUserEntityPermissions(userId, 'workspace', workspaceId) + if (!userPermission || (userPermission !== 'admin' && userPermission !== 'write')) { + return NextResponse.json({ error: 'Write permission required' }, { status: 403 }) + } + + try { + const rotated = await rotateConnectionKey({ + id, + workspaceId, + apiKey, + verify: (key) => verifyAnthropicApiKey(key), + }) + if (!rotated) { + return NextResponse.json({ error: 'Connection not found' }, { status: 404 }) + } + logger.info(`[${requestId}] Rotated managed-agent connection ${id}`) + return NextResponse.json({ + success: true, + data: { + ...rotated, + lastVerifiedAt: rotated.lastVerifiedAt ? rotated.lastVerifiedAt.toISOString() : null, + createdAt: rotated.createdAt.toISOString(), + updatedAt: rotated.updatedAt.toISOString(), + }, + }) + } catch (error) { + const message = getErrorMessage(error, 'Failed to rotate key') + logger.warn(`[${requestId}] Rotate failed: ${message}`) + return NextResponse.json({ error: message }, { status: 400 }) + } + } catch (error) { + logger.error(`[${requestId}] Failed to rotate connection key`, error) + return NextResponse.json({ error: 'Failed to rotate connection key' }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/managed-agent-connections/[id]/vaults/route.ts b/apps/sim/app/api/managed-agent-connections/[id]/vaults/route.ts new file mode 100644 index 00000000000..092f8cb5220 --- /dev/null +++ b/apps/sim/app/api/managed-agent-connections/[id]/vaults/route.ts @@ -0,0 +1,90 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { + type ManagedAgentVault, + managedAgentProxyParamsSchema, + managedAgentProxyQuerySchema, +} from '@/lib/api/contracts' +import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { getDecryptedApiKey } from '@/lib/managed-agents/connections' +import { + type AnthropicListPage, + ManagedAgentProxyError, + proxyManagedAgentsGet, +} from '@/lib/managed-agents/proxy' +import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' + +const logger = createLogger('ManagedAgentVaultsAPI') + +interface AnthropicVault { + id: string + name?: string + description?: string +} + +interface RouteContext { + params: Promise<{ id: string }> +} + +export const GET = withRouteHandler(async (request: NextRequest, context: RouteContext) => { + const requestId = generateRequestId() + try { + const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success || !authResult.userId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + const userId = authResult.userId + + const rawParams = await context.params + const paramsValidation = managedAgentProxyParamsSchema.safeParse(rawParams) + const queryValidation = managedAgentProxyQuerySchema.safeParse( + Object.fromEntries(request.nextUrl.searchParams.entries()) + ) + if (!paramsValidation.success || !queryValidation.success) { + return NextResponse.json({ error: 'Invalid request data' }, { status: 400 }) + } + const { id } = paramsValidation.data + const { workspaceId } = queryValidation.data + + const userPermission = await getUserEntityPermissions(userId, 'workspace', workspaceId) + if (!userPermission) { + return NextResponse.json({ error: 'Access denied' }, { status: 403 }) + } + + const apiKey = await getDecryptedApiKey({ id, workspaceId }) + if (!apiKey) { + return NextResponse.json({ error: 'Connection not found' }, { status: 404 }) + } + + try { + const body = await proxyManagedAgentsGet>( + apiKey, + '/v1/vaults?limit=100' + ) + const data: ManagedAgentVault[] = (body.data ?? []).map((row) => ({ + id: row.id, + name: row.name ?? null, + description: row.description ?? null, + })) + return NextResponse.json({ data }) + } catch (error) { + if (error instanceof ManagedAgentProxyError) { + // 404 on /v1/vaults could mean this beta doesn't expose vaults yet. + // Return an empty list so the block's Vault dropdown just stays empty + // instead of blocking the whole flow. + if (error.status === 404) return NextResponse.json({ data: [] }) + return NextResponse.json({ error: error.message }, { status: 502 }) + } + throw error + } + } catch (error) { + logger.error(`[${requestId}] Failed to list vaults`, error) + return NextResponse.json( + { error: getErrorMessage(error, 'Failed to list vaults') }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/managed-agent-connections/route.test.ts b/apps/sim/app/api/managed-agent-connections/route.test.ts new file mode 100644 index 00000000000..c5ec67cd9a9 --- /dev/null +++ b/apps/sim/app/api/managed-agent-connections/route.test.ts @@ -0,0 +1,275 @@ +/** + * Tests for the Managed Agent connections API route — permission gates. + * + * @vitest-environment node + */ +import { createMockRequest, permissionsMock, permissionsMockFns } from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockCheckSessionOrInternalAuth, mockListConnections, mockCreateConnection, mockDeleteConnection, mockVerifyAnthropicApiKey } = + vi.hoisted(() => ({ + mockCheckSessionOrInternalAuth: vi.fn(), + mockListConnections: vi.fn(), + mockCreateConnection: vi.fn(), + mockDeleteConnection: vi.fn(), + mockVerifyAnthropicApiKey: vi.fn(), + })) + +vi.mock('@/lib/auth/hybrid', () => ({ + checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, +})) + +vi.mock('@/lib/managed-agents/connections', () => ({ + createConnection: mockCreateConnection, + deleteConnection: mockDeleteConnection, + listConnections: mockListConnections, +})) + +vi.mock('@/lib/managed-agents/anthropic-verify', () => ({ + verifyAnthropicApiKey: mockVerifyAnthropicApiKey, +})) + +vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) + +import { DELETE, GET, POST } from '@/app/api/managed-agent-connections/route' + +const now = new Date('2026-07-19T00:00:00.000Z') +const connectionRow = { + id: 'conn_1', + workspaceId: 'ws_A', + userId: 'user_1', + name: 'prod', + maskedApiKey: 'sk-ant-a…wxyz', + lastVerifiedAt: now, + lastVerificationError: null, + createdAt: now, + updatedAt: now, +} + +describe('GET /api/managed-agent-connections', () => { + beforeEach(() => { + vi.clearAllMocks() + mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: 'user_1' }) + permissionsMockFns.mockGetUserEntityPermissions.mockResolvedValue('read') + mockListConnections.mockResolvedValue([connectionRow]) + }) + + it('returns 401 when not authenticated', async () => { + mockCheckSessionOrInternalAuth.mockResolvedValue({ success: false }) + const req = createMockRequest( + 'GET', + undefined, + {}, + 'http://localhost:3000/api/managed-agent-connections?workspaceId=ws_A' + ) + const res = await GET(req) + expect(res.status).toBe(401) + }) + + it('returns 400 when workspaceId is missing', async () => { + const req = createMockRequest( + 'GET', + undefined, + {}, + 'http://localhost:3000/api/managed-agent-connections' + ) + const res = await GET(req) + expect(res.status).toBe(400) + }) + + it('returns 403 when the user has no permission on the workspace', async () => { + permissionsMockFns.mockGetUserEntityPermissions.mockResolvedValue(null) + const req = createMockRequest( + 'GET', + undefined, + {}, + 'http://localhost:3000/api/managed-agent-connections?workspaceId=ws_A' + ) + const res = await GET(req) + expect(res.status).toBe(403) + expect(mockListConnections).not.toHaveBeenCalled() + }) + + it('allows read-tier members to list', async () => { + permissionsMockFns.mockGetUserEntityPermissions.mockResolvedValue('read') + const req = createMockRequest( + 'GET', + undefined, + {}, + 'http://localhost:3000/api/managed-agent-connections?workspaceId=ws_A' + ) + const res = await GET(req) + expect(res.status).toBe(200) + expect(mockListConnections).toHaveBeenCalledWith({ workspaceId: 'ws_A' }) + }) + + it('serializes dates as ISO strings', async () => { + const req = createMockRequest( + 'GET', + undefined, + {}, + 'http://localhost:3000/api/managed-agent-connections?workspaceId=ws_A' + ) + const res = await GET(req) + const body = await res.json() + expect(body.data[0].createdAt).toBe(now.toISOString()) + expect(body.data[0].lastVerifiedAt).toBe(now.toISOString()) + }) + + it('emits null for lastVerifiedAt when the row has never been verified', async () => { + mockListConnections.mockResolvedValue([{ ...connectionRow, lastVerifiedAt: null }]) + const req = createMockRequest( + 'GET', + undefined, + {}, + 'http://localhost:3000/api/managed-agent-connections?workspaceId=ws_A' + ) + const body = await (await GET(req)).json() + expect(body.data[0].lastVerifiedAt).toBeNull() + }) +}) + +describe('POST /api/managed-agent-connections', () => { + beforeEach(() => { + vi.clearAllMocks() + mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: 'user_1' }) + permissionsMockFns.mockGetUserEntityPermissions.mockResolvedValue('write') + mockVerifyAnthropicApiKey.mockResolvedValue({ ok: true }) + mockCreateConnection.mockResolvedValue(connectionRow) + }) + + it('returns 401 when not authenticated', async () => { + mockCheckSessionOrInternalAuth.mockResolvedValue({ success: false }) + const req = createMockRequest('POST', { + workspaceId: 'ws_A', + name: 'prod', + apiKey: 'sk-ant-plaintext', + }) + const res = await POST(req) + expect(res.status).toBe(401) + }) + + it('returns 403 when the caller only has read permission', async () => { + permissionsMockFns.mockGetUserEntityPermissions.mockResolvedValue('read') + const req = createMockRequest('POST', { + workspaceId: 'ws_A', + name: 'prod', + apiKey: 'sk-ant-plaintext', + }) + const res = await POST(req) + expect(res.status).toBe(403) + expect(mockCreateConnection).not.toHaveBeenCalled() + }) + + it('allows write and admin to create', async () => { + for (const perm of ['write', 'admin'] as const) { + permissionsMockFns.mockGetUserEntityPermissions.mockResolvedValue(perm) + mockCreateConnection.mockClear() + const req = createMockRequest('POST', { + workspaceId: 'ws_A', + name: 'prod', + apiKey: 'sk-ant-plaintext', + }) + const res = await POST(req) + expect(res.status).toBe(200) + expect(mockCreateConnection).toHaveBeenCalledTimes(1) + } + }) + + it('surfaces the verify error message when createConnection rejects', async () => { + mockCreateConnection.mockRejectedValue(new Error('Anthropic rejected the key')) + const req = createMockRequest('POST', { + workspaceId: 'ws_A', + name: 'prod', + apiKey: 'sk-ant-bad', + }) + const res = await POST(req) + expect(res.status).toBe(400) + const body = await res.json() + expect(body.error).toBe('Anthropic rejected the key') + }) + + it('never returns the plaintext api key in the response', async () => { + const req = createMockRequest('POST', { + workspaceId: 'ws_A', + name: 'prod', + apiKey: 'sk-ant-plaintext-abcd', + }) + const res = await POST(req) + const body = await res.text() + expect(body).not.toContain('sk-ant-plaintext-abcd') + }) +}) + +describe('DELETE /api/managed-agent-connections', () => { + beforeEach(() => { + vi.clearAllMocks() + mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: 'user_1' }) + permissionsMockFns.mockGetUserEntityPermissions.mockResolvedValue('write') + mockDeleteConnection.mockResolvedValue(true) + }) + + it('returns 401 when not authenticated', async () => { + mockCheckSessionOrInternalAuth.mockResolvedValue({ success: false }) + const req = createMockRequest( + 'DELETE', + undefined, + {}, + 'http://localhost:3000/api/managed-agent-connections?id=conn_1&workspaceId=ws_A' + ) + const res = await DELETE(req) + expect(res.status).toBe(401) + }) + + it('returns 400 when id or workspaceId is missing', async () => { + const req = createMockRequest( + 'DELETE', + undefined, + {}, + 'http://localhost:3000/api/managed-agent-connections?workspaceId=ws_A' + ) + const res = await DELETE(req) + expect(res.status).toBe(400) + }) + + it('returns 403 when the caller only has read permission', async () => { + permissionsMockFns.mockGetUserEntityPermissions.mockResolvedValue('read') + const req = createMockRequest( + 'DELETE', + undefined, + {}, + 'http://localhost:3000/api/managed-agent-connections?id=conn_1&workspaceId=ws_A' + ) + const res = await DELETE(req) + expect(res.status).toBe(403) + expect(mockDeleteConnection).not.toHaveBeenCalled() + }) + + it('returns 404 when the connection does not exist for that workspace', async () => { + mockDeleteConnection.mockResolvedValue(false) + const req = createMockRequest( + 'DELETE', + undefined, + {}, + 'http://localhost:3000/api/managed-agent-connections?id=conn_missing&workspaceId=ws_A' + ) + const res = await DELETE(req) + expect(res.status).toBe(404) + }) + + it('allows write and admin to delete', async () => { + for (const perm of ['write', 'admin'] as const) { + permissionsMockFns.mockGetUserEntityPermissions.mockResolvedValue(perm) + mockDeleteConnection.mockClear().mockResolvedValue(true) + const req = createMockRequest( + 'DELETE', + undefined, + {}, + 'http://localhost:3000/api/managed-agent-connections?id=conn_1&workspaceId=ws_A' + ) + const res = await DELETE(req) + expect(res.status).toBe(200) + expect(mockDeleteConnection).toHaveBeenCalledWith({ id: 'conn_1', workspaceId: 'ws_A' }) + } + }) +}) diff --git a/apps/sim/app/api/managed-agent-connections/route.ts b/apps/sim/app/api/managed-agent-connections/route.ts new file mode 100644 index 00000000000..ab10d61b6ea --- /dev/null +++ b/apps/sim/app/api/managed-agent-connections/route.ts @@ -0,0 +1,163 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { + createManagedAgentConnectionContract, + deleteManagedAgentConnectionQuerySchema, + listManagedAgentConnectionsQuerySchema, +} from '@/lib/api/contracts' +import { parseRequest } from '@/lib/api/server' +import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { verifyAnthropicApiKey } from '@/lib/managed-agents/anthropic-verify' +import { + createConnection, + deleteConnection, + listConnections, +} from '@/lib/managed-agents/connections' +import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' + +const logger = createLogger('ManagedAgentConnectionsAPI') + +function serialize< + T extends { + lastVerifiedAt: Date | null + createdAt: Date + updatedAt: Date + }, +>(row: T) { + return { + ...row, + lastVerifiedAt: row.lastVerifiedAt ? row.lastVerifiedAt.toISOString() : null, + createdAt: row.createdAt.toISOString(), + updatedAt: row.updatedAt.toISOString(), + } +} + +/** GET - List Managed Agent connections in a workspace */ +export const GET = withRouteHandler(async (request: NextRequest) => { + const requestId = generateRequestId() + try { + const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success || !authResult.userId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + const userId = authResult.userId + + const query = listManagedAgentConnectionsQuerySchema.safeParse( + Object.fromEntries(request.nextUrl.searchParams.entries()) + ) + if (!query.success) { + return NextResponse.json( + { error: 'Invalid request data', details: query.error.issues }, + { status: 400 } + ) + } + const { workspaceId } = query.data + + const userPermission = await getUserEntityPermissions(userId, 'workspace', workspaceId) + if (!userPermission) { + return NextResponse.json({ error: 'Access denied' }, { status: 403 }) + } + + const rows = await listConnections({ workspaceId }) + return NextResponse.json({ data: rows.map(serialize) }, { status: 200 }) + } catch (error) { + logger.error(`[${requestId}] Failed to list connections`, error) + return NextResponse.json( + { error: 'Failed to list managed-agent connections' }, + { status: 500 } + ) + } +}) + +/** + * POST - Register a new Managed Agent connection. + * + * Body: `{workspaceId, name, apiKey}`. The key is verified against + * `GET /v1/agents` before we persist the row; a failed verify never + * writes. + */ +export const POST = withRouteHandler(async (req: NextRequest) => { + const requestId = generateRequestId() + try { + const authResult = await checkSessionOrInternalAuth(req, { requireWorkflowId: false }) + if (!authResult.success || !authResult.userId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + const userId = authResult.userId + + const parsed = await parseRequest(createManagedAgentConnectionContract, req, {}) + if (!parsed.success) return parsed.response + const { workspaceId, name, apiKey } = parsed.data.body + + const userPermission = await getUserEntityPermissions(userId, 'workspace', workspaceId) + if (!userPermission || (userPermission !== 'admin' && userPermission !== 'write')) { + return NextResponse.json({ error: 'Write permission required' }, { status: 403 }) + } + + try { + const created = await createConnection({ + workspaceId, + userId, + name, + apiKey, + verify: (key) => verifyAnthropicApiKey(key), + }) + logger.info(`[${requestId}] Created managed-agent connection ${created.id}`) + return NextResponse.json({ success: true, data: serialize(created) }) + } catch (error) { + const message = getErrorMessage(error, 'Failed to create connection') + logger.warn(`[${requestId}] Create failed: ${message}`) + return NextResponse.json({ error: message }, { status: 400 }) + } + } catch (error) { + logger.error(`[${requestId}] Failed to create connection`, error) + return NextResponse.json( + { error: 'Failed to create managed-agent connection' }, + { status: 500 } + ) + } +}) + +/** DELETE - Remove a Managed Agent connection */ +export const DELETE = withRouteHandler(async (request: NextRequest) => { + const requestId = generateRequestId() + try { + const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success || !authResult.userId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + const userId = authResult.userId + + const query = deleteManagedAgentConnectionQuerySchema.safeParse( + Object.fromEntries(request.nextUrl.searchParams.entries()) + ) + if (!query.success) { + return NextResponse.json( + { error: 'Invalid request data', details: query.error.issues }, + { status: 400 } + ) + } + const { id, workspaceId } = query.data + + const userPermission = await getUserEntityPermissions(userId, 'workspace', workspaceId) + if (!userPermission || (userPermission !== 'admin' && userPermission !== 'write')) { + return NextResponse.json({ error: 'Write permission required' }, { status: 403 }) + } + + const deleted = await deleteConnection({ id, workspaceId }) + if (!deleted) { + return NextResponse.json({ error: 'Connection not found' }, { status: 404 }) + } + logger.info(`[${requestId}] Deleted managed-agent connection ${id}`) + return NextResponse.json({ success: true }) + } catch (error) { + logger.error(`[${requestId}] Failed to delete connection`, error) + return NextResponse.json( + { error: 'Failed to delete managed-agent connection' }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx b/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx index 1f6fe23b076..c020784304d 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx @@ -43,6 +43,11 @@ const Inbox = dynamic(() => const MCP = dynamic(() => import('@/app/workspace/[workspaceId]/settings/components/mcp/mcp').then((m) => m.MCP) ) +const ManagedAgents = dynamic(() => + import( + '@/app/workspace/[workspaceId]/settings/components/managed-agents' + ).then((m) => m.ManagedAgents) +) const Mothership = dynamic(() => import('@/app/workspace/[workspaceId]/settings/components/mothership/mothership').then( (m) => m.Mothership @@ -170,6 +175,7 @@ export function SettingsPage({ section }: SettingsPageProps) { {effectiveSection === 'byok' && } {effectiveSection === 'copilot' && } {effectiveSection === 'mcp' && } + {effectiveSection === 'managed-agents' && } {effectiveSection === 'forks' && } {effectiveSection === 'custom-tools' && } {effectiveSection === 'workflow-mcp-servers' && } diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/connection-form-modal.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/connection-form-modal.tsx new file mode 100644 index 00000000000..11ef81abadd --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/connection-form-modal.tsx @@ -0,0 +1,121 @@ +'use client' + +import { useState } from 'react' +import { + ChipModal, + ChipModalBody, + ChipModalError, + ChipModalField, + ChipModalFooter, + ChipModalHeader, +} from '@sim/emcn' +import { getErrorMessage } from '@sim/utils/errors' + +export interface ConnectionFormValues { + name: string + apiKey: string +} + +interface ConnectionFormModalProps { + open: boolean + onOpenChange: (open: boolean) => void + onSubmit: (values: ConnectionFormValues) => Promise +} + +/** + * "Link Claude workspace" dialog. User enters a display name + an + * Anthropic API key. Submission triggers a verify-then-save flow on the + * server; the modal keeps the dialog open until the request resolves so + * verification errors surface inline. + */ +export function ConnectionFormModal({ + open, + onOpenChange, + onSubmit, +}: ConnectionFormModalProps) { + const [name, setName] = useState('') + const [apiKey, setApiKey] = useState('') + const [submitting, setSubmitting] = useState(false) + const [error, setError] = useState(null) + const [prevOpen, setPrevOpen] = useState(false) + + if (open !== prevOpen) { + setPrevOpen(open) + if (open) { + setName('') + setApiKey('') + setError(null) + setSubmitting(false) + } + } + + const handleSubmit = async () => { + const trimmedName = name.trim() + const trimmedKey = apiKey.trim() + if (!trimmedName) { + setError('Give this connection a name (e.g. "prod", "staging").') + return + } + if (!trimmedKey) { + setError('Anthropic API key is required.') + return + } + setSubmitting(true) + setError(null) + try { + await onSubmit({ name: trimmedName, apiKey: trimmedKey }) + onOpenChange(false) + } catch (err) { + setError(getErrorMessage(err, 'Failed to link the Claude workspace.')) + } finally { + setSubmitting(false) + } + } + + return ( + + onOpenChange(false)}>Link Claude workspace + + + { + setName(value) + if (error) setError(null) + }} + placeholder='prod, staging, my-team, …' + required + hint='A label you’ll see when picking connections from a workflow block.' + /> + + { + setApiKey(value) + if (error) setError(null) + }} + placeholder='sk-ant-…' + password + required + hint='The workspace key from Claude Platform. Verified against /v1/agents before saving.' + /> + + {error ?? undefined} + + + onOpenChange(false)} + cancelDisabled={submitting} + primaryAction={{ + label: submitting ? 'Verifying…' : 'Link & save', + onClick: handleSubmit, + disabled: submitting || !name.trim() || !apiKey.trim(), + }} + /> + + ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/index.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/index.ts new file mode 100644 index 00000000000..71fafe65b5e --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/index.ts @@ -0,0 +1 @@ +export { ManagedAgents } from './managed-agents' diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/managed-agents.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/managed-agents.tsx new file mode 100644 index 00000000000..628a9b9d1a3 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/managed-agents.tsx @@ -0,0 +1,200 @@ +'use client' + +import { useState } from 'react' +import { ChipConfirmModal, toast } from '@sim/emcn' +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { Plus } from 'lucide-react' +import { useParams } from 'next/navigation' +import { canMutateWorkspaceSettingsSection } from '@/components/settings/navigation' +import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' +import { RowActionsMenu } from '@/app/workspace/[workspaceId]/settings/components/row-actions-menu' +import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' +import { SettingsPanel } from '@/app/workspace/[workspaceId]/settings/components/settings-panel' +import { useSettingsSearch } from '@/app/workspace/[workspaceId]/settings/components/use-settings-search' +import { ConnectionFormModal } from '@/app/workspace/[workspaceId]/settings/components/managed-agents/connection-form-modal' +import { + useCreateManagedAgentConnection, + useDeleteManagedAgentConnection, + useManagedAgentConnections, +} from '@/hooks/queries/managed-agent-connections' + +const logger = createLogger('ManagedAgentsSettings') + +export function ManagedAgents() { + const params = useParams() + const workspaceId = params.workspaceId as string + const workspacePermissions = useUserPermissionsContext() + const canEdit = canMutateWorkspaceSettingsSection('managed-agents', workspacePermissions) + + const { + data: connections = [], + isLoading, + error: listError, + } = useManagedAgentConnections(workspaceId) + const createConnection = useCreateManagedAgentConnection() + const deleteConnection = useDeleteManagedAgentConnection() + + const [searchTerm, setSearchTerm] = useSettingsSearch() + const [showAddModal, setShowAddModal] = useState(false) + const [connectionToDeleteId, setConnectionToDeleteId] = useState(null) + + const filtered = connections.filter((c) => { + if (!searchTerm.trim()) return true + return c.name.toLowerCase().includes(searchTerm.toLowerCase()) + }) + + const handleCreate = async (values: { name: string; apiKey: string }) => { + await createConnection.mutateAsync({ + workspaceId, + name: values.name, + apiKey: values.apiKey, + }) + toast.success('Claude workspace linked') + } + + const confirmDelete = async () => { + if (!connectionToDeleteId) return + const id = connectionToDeleteId + setConnectionToDeleteId(null) + try { + await deleteConnection.mutateAsync({ workspaceId, id }) + toast.success('Connection removed') + } catch (error) { + logger.error('Failed to delete connection:', error) + toast.error('Failed to remove connection', { + description: getErrorMessage(error), + }) + } + } + + const hasConnections = connections.length > 0 + const showNoResults = searchTerm.trim() && filtered.length === 0 && hasConnections + + return ( + <> + setShowAddModal(true), + disabled: isLoading, + }, + ] + : [] + } + > + {listError ? ( +
+

+ {getErrorMessage(listError, 'Failed to load connections')} +

+
+ ) : isLoading ? null : !hasConnections ? ( + + {canEdit + ? 'Click "Link Claude workspace" above to connect an Anthropic workspace.' + : 'No Managed Agent connections configured.'} + + ) : ( +
+ {filtered.map((c) => { + const subtitle = formatSubtitle(c) + return ( +
+
+
+ + {c.name} + + + {c.maskedApiKey} + +
+

+ {subtitle.text} +

+
+
+ setConnectionToDeleteId(c.id), + }, + ] + : []), + ]} + /> +
+
+ ) + })} + {showNoResults && ( + + No connections found matching "{searchTerm}" + + )} +
+ )} +
+ + {canEdit && ( + + )} + + {canEdit && ( + { + if (!open) setConnectionToDeleteId(null) + }} + srTitle='Remove connection' + title='Remove Managed Agent connection' + text={[ + 'Any workflows that reference this connection will fail at run time until you re-link the workspace. This action cannot be undone.', + ]} + confirm={{ label: 'Remove', onClick: confirmDelete }} + /> + )} + + ) +} + +function formatSubtitle(row: { + lastVerifiedAt: string | null + lastVerificationError: string | null +}): { text: string; isError: boolean } { + if (row.lastVerificationError) { + return { text: row.lastVerificationError, isError: true } + } + if (row.lastVerifiedAt) { + const when = new Date(row.lastVerifiedAt).toLocaleString() + return { text: `Verified ${when}`, isError: false } + } + return { text: 'Not verified', isError: false } +} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx index 80996a1adae..7f0654e219d 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx @@ -25,6 +25,12 @@ interface TableProps { isPreview?: boolean previewValue?: WorkflowTableRow[] | null disabled?: boolean + /** + * Optional seed rows applied when a fresh block first mounts (store value + * is missing/empty). Each entry's `cells` keys must match `columns` — any + * missing columns fall back to `""`. Existing values are never overwritten. + */ + defaultRows?: Array<{ cells: Record }> } interface WorkflowTableRow { @@ -214,6 +220,7 @@ export function Table({ isPreview = false, previewValue, disabled = false, + defaultRows, }: TableProps) { const activeSearchTarget = useActiveSearchTarget() const params = useParams() @@ -248,18 +255,23 @@ export function Table({ ) /** - * Initialize the table with a default empty row when the component mounts - * and when the current store value is missing or empty. + * Initialize the table when the component mounts and the store value is + * missing/empty. If the caller supplied `defaultRows`, seed those rows + * (any missing columns default to `""`); otherwise start with a single + * empty row. */ useEffect(() => { if (!isPreview && !disabled && (!Array.isArray(storeValue) || storeValue.length === 0)) { - const initialRow: WorkflowTableRow = { - id: generateId(), - cells: { ...emptyCellsTemplate }, - } - setStoreValue([initialRow]) + const seedRows: WorkflowTableRow[] = + Array.isArray(defaultRows) && defaultRows.length > 0 + ? defaultRows.map((row) => ({ + id: generateId(), + cells: { ...emptyCellsTemplate, ...(row.cells ?? {}) }, + })) + : [{ id: generateId(), cells: { ...emptyCellsTemplate } }] + setStoreValue(seedRows) } - }, [isPreview, disabled, storeValue, setStoreValue, emptyCellsTemplate]) + }, [isPreview, disabled, storeValue, setStoreValue, emptyCellsTemplate, defaultRows]) // Ensure value is properly typed and initialized const rows = useMemo(() => { diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx index e8752f0c4aa..6bd416ff8aa 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx @@ -749,6 +749,11 @@ function SubBlockComponent({ isPreview={isPreview} previewValue={previewValue as any} disabled={isDisabled} + defaultRows={ + Array.isArray(config.defaultValue) + ? (config.defaultValue as Array<{ cells: Record }>) + : undefined + } /> ) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx index 3e63939ba9e..0ce58c2a9c4 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx @@ -50,6 +50,13 @@ import { getDependsOnFields } from '@/blocks/utils' import { useKnowledgeBase } from '@/hooks/kb/use-knowledge' import { useCustomTools } from '@/hooks/queries/custom-tools' import { useDeployWorkflow } from '@/hooks/queries/deployments' +import { + useManagedAgentAgents, + useManagedAgentConnections, + useManagedAgentEnvironments, + useManagedAgentMemoryStores, + useManagedAgentVaults, +} from '@/hooks/queries/managed-agent-connections' import { useMcpServers, useMcpToolsQuery } from '@/hooks/queries/mcp' import { useCredentialName } from '@/hooks/queries/oauth/oauth-credentials' import { useReactivateSchedule, useScheduleInfo } from '@/hooks/queries/schedules' @@ -80,6 +87,7 @@ interface SubBlockRowProps { workspaceId?: string workflowId?: string blockId?: string + blockType?: string allSubBlockValues?: Record displayAdvancedOptions?: boolean canonicalIndex?: ReturnType @@ -106,6 +114,7 @@ const areSubBlockRowPropsEqual = ( prevProps.workspaceId === nextProps.workspaceId && prevProps.workflowId === nextProps.workflowId && prevProps.blockId === nextProps.blockId && + prevProps.blockType === nextProps.blockType && valueEqual && prevProps.displayAdvancedOptions === nextProps.displayAdvancedOptions && prevProps.canonicalIndex === nextProps.canonicalIndex && @@ -126,6 +135,7 @@ const SubBlockRow = memo(function SubBlockRow({ workspaceId, workflowId, blockId, + blockType, allSubBlockValues, displayAdvancedOptions, canonicalIndex, @@ -356,6 +366,83 @@ const SubBlockRow = memo(function SubBlockRow({ [subBlock, rawValue] ) + /** + * Hydrates managed-agent selectors (connection, agent, environment, + * vaults, memory store) on the Claude Managed Agents blocks. Anthropic + * IDs (`agent_01...`, `env_01...`, `vlt_...`) are meaningless in a + * collapsed row; look up the label from the React Query cache the + * combobox already populates when the editor picker opens. + */ + const isManagedAgentBlock = + typeof blockType === 'string' && blockType.startsWith('managed_agent_') + const managedAgentConnectionId = + isManagedAgentBlock && subBlock?.id !== 'connection' + ? (allSubBlockValues?.connection?.value as string | undefined) + : undefined + const { data: managedAgentConnections = [] } = useManagedAgentConnections( + isManagedAgentBlock && subBlock?.id === 'connection' && workspaceId ? workspaceId : '' + ) + const { data: managedAgentAgents = [] } = useManagedAgentAgents( + isManagedAgentBlock && subBlock?.id === 'agent' ? managedAgentConnectionId ?? null : null, + workspaceId ?? '' + ) + const { data: managedAgentEnvironments = [] } = useManagedAgentEnvironments( + isManagedAgentBlock && subBlock?.id === 'environment' + ? managedAgentConnectionId ?? null + : null, + workspaceId ?? '' + ) + const { data: managedAgentVaultOptions = [] } = useManagedAgentVaults( + isManagedAgentBlock && subBlock?.id === 'vaults' ? managedAgentConnectionId ?? null : null, + workspaceId ?? '' + ) + const { data: managedAgentMemoryStoreOptions = [] } = useManagedAgentMemoryStores( + isManagedAgentBlock && subBlock?.id === 'memoryStoreId' + ? managedAgentConnectionId ?? null + : null, + workspaceId ?? '' + ) + const managedAgentDisplayName = useMemo(() => { + if (!isManagedAgentBlock || !subBlock) return null + if (subBlock.id === 'connection' && typeof rawValue === 'string' && rawValue.length > 0) { + return managedAgentConnections.find((c) => c.id === rawValue)?.name ?? null + } + if (subBlock.id === 'agent' && typeof rawValue === 'string' && rawValue.length > 0) { + return managedAgentAgents.find((a) => a.id === rawValue)?.name ?? null + } + if (subBlock.id === 'environment' && typeof rawValue === 'string' && rawValue.length > 0) { + return managedAgentEnvironments.find((e) => e.id === rawValue)?.name ?? null + } + if (subBlock.id === 'memoryStoreId' && typeof rawValue === 'string' && rawValue.length > 0) { + return managedAgentMemoryStoreOptions.find((m) => m.id === rawValue)?.name ?? null + } + if (subBlock.id === 'vaults') { + const ids = Array.isArray(rawValue) + ? (rawValue as unknown[]).filter( + (v): v is string => typeof v === 'string' && v.length > 0 + ) + : typeof rawValue === 'string' && rawValue.length > 0 + ? [rawValue] + : [] + if (ids.length === 0) return null + const names = ids.map( + (id) => managedAgentVaultOptions.find((v) => v.id === id)?.name ?? id + ) + if (names.length === 1) return names[0] + return `${names[0]} +${names.length - 1}` + } + return null + }, [ + isManagedAgentBlock, + subBlock, + rawValue, + managedAgentConnections, + managedAgentAgents, + managedAgentEnvironments, + managedAgentVaultOptions, + managedAgentMemoryStoreOptions, + ]) + /** Hydrates skill references to display names. */ const { data: workspaceSkills = [] } = useSkills(workspaceId || '') const skillsDisplayValue = useMemo( @@ -381,6 +468,7 @@ const SubBlockRow = memo(function SubBlockRow({ mcpToolDisplayName || tableDisplayName || webhookUrlDisplayValue || + managedAgentDisplayName || selectorDisplayName const displayValue = maskedValue || hydratedName || (isSelectorType && value ? '-' : value) @@ -687,6 +775,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({ visibleSubBlockCount: totalRenderedRowCount, conditionRowCount: conditionRows.length, routerRowCount: routerRows.length, + nodeWidth: config.nodeWidth, }) }, dependencies: [ @@ -697,6 +786,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({ conditionRows.length, routerRows.length, horizontalHandles, + config.nodeWidth, ], }) @@ -756,6 +846,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({ workspaceId={workspaceId} workflowId={currentWorkflowId} blockId={id} + blockType={type} allSubBlockValues={subBlockState} displayAdvancedOptions={effectiveAdvanced} canonicalIndex={canonicalIndex} diff --git a/apps/sim/blocks/blocks/managed_agent_cloud.ts b/apps/sim/blocks/blocks/managed_agent_cloud.ts new file mode 100644 index 00000000000..e67bb1150bf --- /dev/null +++ b/apps/sim/blocks/blocks/managed_agent_cloud.ts @@ -0,0 +1,212 @@ +import { ClaudeIcon } from '@/components/icons' +import { + fetchManagedAgentAgentOptions, + fetchManagedAgentCloudEnvironmentOptions, + fetchManagedAgentConnectionOptions, + fetchManagedAgentMemoryStoreOptions, + fetchManagedAgentVaultOptions, +} from '@/lib/managed-agents/subblock-options' +import type { BlockConfig, BlockMeta } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' + +/** + * Claude Managed Agents block — cloud (Anthropic-managed) environments. + * + * Supports the full `POST /v1/sessions` shape: + * - `agent`, `environment_id`, `vault_ids` + * - `resources: [{type: 'memory_store', ...}, {type: 'file', ...}]` + * - `metadata: {...}` (arbitrary key/value tags) + * + * The environment picker is filtered to `config.type === 'cloud'` so this + * block only lists cloud envs. Self-hosted envs live on the sibling + * `managed_agent_self_hosted` block. + */ +export const ManagedAgentCloudBlock: BlockConfig = { + type: 'managed_agent_cloud', + name: 'Claude Managed Agents', + description: 'Run a Claude Platform Managed Agent (cloud environments)', + authMode: AuthMode.ApiKey, + longDescription: + "Invoke a Claude Platform Managed Agent running in an Anthropic-managed cloud sandbox. Pick a linked Claude workspace, agent, and cloud environment. Attach vaults, a memory store, and files via the session resources array. Add opaque metadata tags if needed. The block returns the assistant's final text.", + category: 'tools', + integrationType: IntegrationType.AI, + docsLink: 'https://docs.sim.ai/integrations/managed-agent', + bgColor: '#DA7756', + icon: ClaudeIcon, + nodeWidth: 400, + subBlocks: [ + { + id: 'connection', + title: 'Claude Workspace', + type: 'combobox', + required: true, + placeholder: 'Select a linked Claude workspace…', + commandSearchable: true, + options: [], + fetchOptions: fetchManagedAgentConnectionOptions, + }, + { + id: 'environment', + title: 'Cloud Environment', + type: 'combobox', + required: true, + placeholder: 'Select a cloud environment…', + commandSearchable: true, + options: [], + dependsOn: ['connection'], + fetchOptions: fetchManagedAgentCloudEnvironmentOptions, + }, + { + // Constant — cloud block only ever runs against cloud envs, so the + // session-client builds the cloud-shaped payload (resources + + // metadata, no session_parameters routing). + id: 'environmentType', + title: 'Environment Type', + type: 'short-input', + value: () => 'cloud', + hidden: true, + }, + { + id: 'vaults', + title: 'Credential vaults', + type: 'combobox', + required: false, + placeholder: 'Optional — pick zero or more OAuth vaults', + commandSearchable: true, + multiSelect: true, + options: [], + dependsOn: ['connection'], + fetchOptions: fetchManagedAgentVaultOptions, + }, + { + // Memory store — attached as a `memory_store` resource entry. + id: 'memoryStoreId', + title: 'Memory Store', + type: 'combobox', + required: false, + placeholder: 'Optional — pick a memory store or leave empty', + commandSearchable: true, + options: [], + dependsOn: ['connection'], + fetchOptions: fetchManagedAgentMemoryStoreOptions, + }, + { + id: 'memoryAccess', + title: 'Memory Access', + type: 'dropdown', + required: false, + options: [ + { label: 'Read + write (default)', id: 'read_write' }, + { label: 'Read only', id: 'read_only' }, + ], + value: () => 'read_write', + dependsOn: ['memoryStoreId'], + condition: { field: 'memoryStoreId', value: '', not: true }, + description: + 'read_write pushes changes back on session exit. read_only pulls only and never writes.', + }, + { + id: 'agent', + title: 'Agent', + type: 'combobox', + required: true, + placeholder: 'Select an agent from this workspace…', + commandSearchable: true, + options: [], + dependsOn: ['connection'], + fetchOptions: fetchManagedAgentAgentOptions, + }, + { + id: 'userMessage', + title: 'User message', + type: 'long-input', + required: true, + placeholder: 'Ask the Managed Agent to do something…', + }, + { + // Free-form session metadata. Cloud envs store these as opaque + // tags; use for downstream analytics or your own bookkeeping. + id: 'sessionParameters', + title: 'Metadata', + type: 'table', + required: false, + columns: ['Key', 'Value'], + description: + 'Optional key/value metadata forwarded on the session (top-level `metadata` field).', + }, + { + // File attachments — each row is a Files-API `file_...` id plus + // an optional mount path. Anthropic mounts each file into the + // session container so the agent can read it. + id: 'files', + title: 'Files', + type: 'table', + required: false, + columns: ['File ID', 'Mount path'], + description: + 'Files-API file ids (file_...) to mount into the session. Mount path is optional; Anthropic picks a default when omitted.', + }, + ], + tools: { + access: ['managed_agent_run_session'], + }, + inputs: { + connection: { type: 'string', description: 'Managed Agent connection id.' }, + agent: { type: 'string', description: 'Managed-agent id inside the linked Claude workspace.' }, + environment: { type: 'string', description: 'Cloud environment id.' }, + environmentType: { type: 'string', description: 'Always "cloud" for this block.' }, + vaults: { type: 'json', description: 'Vault ids for MCP auth (array of strings).' }, + memoryStoreId: { type: 'string', description: 'Optional Agent Memory Store id.' }, + memoryAccess: { + type: 'string', + description: "Memory store access mode — 'read_write' (default) or 'read_only'.", + }, + files: { type: 'json', description: 'File attachments — [{fileId, mountPath?}].' }, + sessionParameters: { + type: 'json', + description: 'Session metadata (top-level `metadata` field on the session).', + }, + userMessage: { type: 'string', description: 'The user message to send to the agent.' }, + }, + outputs: { + content: { type: 'string', description: "The Managed Agent's final assistant text." }, + sessionId: { + type: 'string', + description: 'Anthropic session id — logged so downstream steps can link to the run.', + }, + }, +} + +export const ManagedAgentCloudBlockMeta = { + tags: ['agent', 'anthropic', 'claude', 'session', 'managed-agent', 'cloud'], + url: 'https://platform.claude.com/', + templates: [ + { + icon: ClaudeIcon, + title: 'Delegate an analysis task to a cloud Managed Agent', + prompt: + "Build a workflow that opens a Managed Agent session in an Anthropic-managed cloud sandbox, attaches a memory store and any relevant files, and captures the agent's response.", + modules: ['agent', 'workflows'], + category: 'engineering', + tags: ['automation', 'analysis'], + }, + { + icon: ClaudeIcon, + title: 'Route a customer request to a vault-backed cloud agent', + prompt: + "Read a customer request from a webhook, invoke a cloud Managed Agent bound to an OAuth vault for MCP tool access, and return the agent's response as a chat reply.", + modules: ['agent', 'workflows'], + category: 'support', + tags: ['automation', 'customer-support'], + }, + ], + skills: [ + { + name: 'run-cloud-managed-agent', + description: + 'Delegate a task to a cloud Claude Platform Managed Agent — optionally attaching a memory store and files as session resources.', + content: + "# Run Cloud Managed Agent\n\nInvoke a Managed Agent running in Anthropic's cloud sandbox from a Sim workflow.\n\n## Steps\n1. Pick the connection (linked Claude workspace).\n2. Pick the cloud agent and environment.\n3. Optionally attach vaults, a memory store (with access mode), and files.\n4. Optionally add metadata tags for bookkeeping.\n5. Write the user message; `` references resolve at run time.\n\n## Output\nThe block returns the assistant's final text as `content`. Chain downstream blocks as needed.", + }, + ], +} as const satisfies BlockMeta diff --git a/apps/sim/blocks/blocks/managed_agent_self_hosted.test.ts b/apps/sim/blocks/blocks/managed_agent_self_hosted.test.ts new file mode 100644 index 00000000000..99094321ba6 --- /dev/null +++ b/apps/sim/blocks/blocks/managed_agent_self_hosted.test.ts @@ -0,0 +1,121 @@ +/** + * @vitest-environment node + */ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { envState } = vi.hoisted(() => ({ + envState: { + NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS: undefined as string | undefined, + NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED: undefined as string | undefined, + }, +})) + +vi.mock('@/lib/core/config/env', () => ({ + env: new Proxy({} as Record, { + get: (_target, key) => (envState as Record)[key as string], + }), +})) + +vi.mock('@/lib/managed-agents/subblock-options', () => ({ + fetchManagedAgentAgentOptions: vi.fn(), + fetchManagedAgentConnectionOptions: vi.fn(), + fetchManagedAgentMemoryStoreOptions: vi.fn(), + fetchManagedAgentSelfHostedEnvironmentOptions: vi.fn(), + fetchManagedAgentVaultOptions: vi.fn(), +})) + +import { + isSelfHostedMemoryEnabled, + readSessionMetadataDefaults, +} from '@/blocks/blocks/managed_agent_self_hosted' + +describe('readSessionMetadataDefaults', () => { + beforeEach(() => { + envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = undefined + }) + + it('returns [] when the env var is unset', () => { + envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = undefined + expect(readSessionMetadataDefaults()).toEqual([]) + }) + + it('returns [] when the env var is empty / whitespace-only', () => { + envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = '' + expect(readSessionMetadataDefaults()).toEqual([]) + envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = ' ' + expect(readSessionMetadataDefaults()).toEqual([]) + }) + + it('returns [] on invalid JSON', () => { + envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = '{not json' + expect(readSessionMetadataDefaults()).toEqual([]) + }) + + it('returns [] on a JSON array (must be an object of key/value pairs)', () => { + envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = '["a","b"]' + expect(readSessionMetadataDefaults()).toEqual([]) + }) + + it('coerces a valid JSON object into `{cells: {Key, Value}}` rows', () => { + envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = JSON.stringify({ + FOO: 'bar', + BAZ: 'qux', + }) + expect(readSessionMetadataDefaults()).toEqual([ + { cells: { Key: 'FOO', Value: 'bar' } }, + { cells: { Key: 'BAZ', Value: 'qux' } }, + ]) + }) + + it('drops entries with a blank key', () => { + envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = JSON.stringify({ + '': 'dropped', + ' ': 'also dropped', + keep: 'yes', + }) + expect(readSessionMetadataDefaults()).toEqual([{ cells: { Key: 'keep', Value: 'yes' } }]) + }) + + it('coerces non-string values to their string form', () => { + envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = JSON.stringify({ + A: 1, + B: true, + C: null, + }) + expect(readSessionMetadataDefaults()).toEqual([ + { cells: { Key: 'A', Value: '1' } }, + { cells: { Key: 'B', Value: 'true' } }, + { cells: { Key: 'C', Value: '' } }, + ]) + }) +}) + +describe('isSelfHostedMemoryEnabled', () => { + beforeEach(() => { + envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED = undefined + }) + + it('defaults to false when the env var is unset', () => { + envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED = undefined + expect(isSelfHostedMemoryEnabled()).toBe(false) + }) + + it('is true only for truthy string forms', () => { + for (const on of ['1', 'true', 'True', 'YES', 'yes']) { + envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED = on + expect(isSelfHostedMemoryEnabled()).toBe(true) + } + }) + + it('is false for other strings', () => { + for (const off of ['0', 'false', 'no', 'off', 'random']) { + envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED = off + expect(isSelfHostedMemoryEnabled()).toBe(false) + } + }) + + it('trims surrounding whitespace before comparing', () => { + envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED = ' true ' + expect(isSelfHostedMemoryEnabled()).toBe(true) + }) +}) diff --git a/apps/sim/blocks/blocks/managed_agent_self_hosted.ts b/apps/sim/blocks/blocks/managed_agent_self_hosted.ts new file mode 100644 index 00000000000..645b9698479 --- /dev/null +++ b/apps/sim/blocks/blocks/managed_agent_self_hosted.ts @@ -0,0 +1,263 @@ +import { ClaudeIcon } from '@/components/icons' +import { env } from '@/lib/core/config/env' +import { + fetchManagedAgentAgentOptions, + fetchManagedAgentConnectionOptions, + fetchManagedAgentMemoryStoreOptions, + fetchManagedAgentSelfHostedEnvironmentOptions, + fetchManagedAgentVaultOptions, +} from '@/lib/managed-agents/subblock-options' +import type { BlockConfig, BlockMeta, SubBlockConfig } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' + +/** + * Feature-flag gate for the memory-store + memory-access fields on the + * self-hosted variant. Off by default: Claude self-hosted environments + * do not currently support the memory-store resource attach on the + * session API. Deployers whose self-hosted agent sandbox has a custom + * memory-mount path can flip this on to expose the fields, and the + * key/value pair still ends up on the session's `metadata` for the + * sandbox to pick up. + */ +export function isSelfHostedMemoryEnabled(): boolean { + const raw = env.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED + if (typeof raw !== 'string') return false + const normalized = raw.trim().toLowerCase() + return normalized === '1' || normalized === 'true' || normalized === 'yes' +} + +const memorySubBlocks: SubBlockConfig[] = isSelfHostedMemoryEnabled() + ? [ + { + id: 'memoryStoreId', + title: 'Memory Store', + type: 'combobox', + required: false, + placeholder: 'Optional — pick a memory store or leave empty', + commandSearchable: true, + options: [], + dependsOn: ['connection'], + fetchOptions: fetchManagedAgentMemoryStoreOptions, + }, + { + id: 'memoryAccess', + title: 'Memory Access', + type: 'dropdown', + required: false, + options: [ + { label: 'Read + write (default)', id: 'read_write' }, + { label: 'Read only', id: 'read_only' }, + ], + value: () => 'read_write', + dependsOn: ['memoryStoreId'], + condition: { field: 'memoryStoreId', value: '', not: true }, + description: + 'read_write pushes changes back on session exit. read_only pulls only and never writes.', + }, + ] + : [] + +/** + * Read the JSON defaults for the self-hosted metadata table from + * `NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS`. Kept out of source + * so deployers can seed the block with the keys their self-hosted + * agent sandbox reads, without the values living in the repo. + * + * Expected shape: a JSON object `{"KEY": "VALUE", ...}`. Non-string + * values are coerced to their string form. Anything invalid (missing, + * unparseable, non-object) falls back to an empty seed — the block + * still renders, users see a single blank row. + */ +export function readSessionMetadataDefaults(): Array<{ cells: Record }> { + const raw = env.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS + if (typeof raw !== 'string' || raw.trim().length === 0) return [] + try { + const parsed = JSON.parse(raw) + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return [] + const rows: Array<{ cells: Record }> = [] + for (const [key, value] of Object.entries(parsed)) { + const k = typeof key === 'string' ? key.trim() : '' + if (!k) continue + const v = value == null ? '' : typeof value === 'string' ? value : String(value) + rows.push({ cells: { Key: k, Value: v } }) + } + return rows + } catch { + return [] + } +} + +/** + * Claude Managed Agents block — self-hosted variant. + * + * Same session-create shape as the cloud block **without** the + * `resources` array. Session metadata (top-level `metadata` field) is + * exposed to the self-hosted agent sandbox as env vars, so this + * block's Session parameters table is where you set the keys your + * deployment reads. The set of supported keys is deployment-specific + * — seed defaults with `NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS`. + * + * The environment picker is filtered to `config.type === 'self_hosted'`. + * Icon renders black-on-white to visually differentiate from the cloud + * block's warm-orange tile. + */ +export const ManagedAgentSelfHostedBlock: BlockConfig = { + type: 'managed_agent_self_hosted', + name: 'Claude Managed Agents (self-hosted)', + description: 'Run a Claude Platform Managed Agent (self-hosted environments)', + authMode: AuthMode.ApiKey, + longDescription: + "Invoke a Claude Platform Managed Agent running in a self-hosted sandbox on your own infrastructure. Pick a linked Claude workspace, agent, and self-hosted environment. Attach vaults. Set metadata keys the self-hosted agent sandbox forwards to the container as env vars (keys are deployment-specific). The block returns the assistant's final text.", + category: 'tools', + integrationType: IntegrationType.AI, + docsLink: 'https://docs.sim.ai/integrations/managed-agent', + bgColor: '#000000', + icon: ClaudeIcon, + nodeWidth: 400, + subBlocks: [ + { + id: 'connection', + title: 'Claude Workspace', + type: 'combobox', + required: true, + placeholder: 'Select a linked Claude workspace…', + commandSearchable: true, + options: [], + fetchOptions: fetchManagedAgentConnectionOptions, + }, + { + id: 'environment', + title: 'Self-hosted Environment', + type: 'combobox', + required: true, + placeholder: 'Select a self-hosted environment…', + commandSearchable: true, + options: [], + dependsOn: ['connection'], + fetchOptions: fetchManagedAgentSelfHostedEnvironmentOptions, + }, + { + // Constant — self-hosted block only runs against self-hosted envs. + // The session-client uses this to build the metadata-shaped payload + // (no top-level `resources`, memory folded into metadata). + id: 'environmentType', + title: 'Environment Type', + type: 'short-input', + value: () => 'self_hosted', + hidden: true, + }, + { + id: 'vaults', + title: 'Credential vaults', + type: 'combobox', + required: false, + placeholder: 'Optional — pick zero or more OAuth vaults', + commandSearchable: true, + multiSelect: true, + options: [], + dependsOn: ['connection'], + fetchOptions: fetchManagedAgentVaultOptions, + }, + ...memorySubBlocks, + { + id: 'agent', + title: 'Agent', + type: 'combobox', + required: true, + placeholder: 'Select an agent from this workspace…', + commandSearchable: true, + options: [], + dependsOn: ['connection'], + fetchOptions: fetchManagedAgentAgentOptions, + }, + { + id: 'userMessage', + title: 'User message', + type: 'long-input', + required: true, + placeholder: 'Ask the Managed Agent to do something…', + }, + { + // Session metadata forwarded to the self-hosted agent sandbox as + // env vars. The set of supported keys is deployment-specific and + // lives with the deployer, not in this repo. Seed rows come from + // `NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS` (see + // `readSessionMetadataDefaults` above); leave empty to start with + // a single blank row. + id: 'sessionParameters', + title: 'Session parameters', + type: 'table', + required: false, + columns: ['Key', 'Value'], + defaultValue: readSessionMetadataDefaults(), + description: + 'Key/value pairs forwarded to the self-hosted agent sandbox as environment variables. Supported keys depend on your deployment — consult your deployment docs. Value cells support / references.', + }, + ], + tools: { + access: ['managed_agent_run_session'], + }, + inputs: { + connection: { type: 'string', description: 'Managed Agent connection id.' }, + agent: { type: 'string', description: 'Managed-agent id inside the linked Claude workspace.' }, + environment: { type: 'string', description: 'Self-hosted environment id.' }, + environmentType: { type: 'string', description: 'Always "self_hosted" for this block.' }, + vaults: { type: 'json', description: 'Vault ids for MCP auth (array of strings).' }, + memoryStoreId: { + type: 'string', + description: + "Optional Agent Memory Store id. Only exposed when NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED is truthy; forwarded on the session's `metadata` for the self-hosted agent sandbox to consume.", + }, + memoryAccess: { + type: 'string', + description: "Memory store access mode — 'read_write' (default) or 'read_only'.", + }, + sessionParameters: { + type: 'json', + description: + 'Session parameters (key/value) forwarded to the self-hosted agent sandbox as env vars.', + }, + userMessage: { type: 'string', description: 'The user message to send to the agent.' }, + }, + outputs: { + content: { type: 'string', description: "The Managed Agent's final assistant text." }, + sessionId: { + type: 'string', + description: 'Anthropic session id — logged so downstream steps can link to the run.', + }, + }, +} + +export const ManagedAgentSelfHostedBlockMeta = { + tags: ['agent', 'anthropic', 'claude', 'session', 'managed-agent', 'self-hosted'], + url: 'https://platform.claude.com/', + templates: [ + { + icon: ClaudeIcon, + title: 'Delegate a repo-scoped code review to a self-hosted Managed Agent', + prompt: + 'Build a workflow that receives a repo URL and ref, invokes a Claude Platform Managed Agent in a self-hosted environment with SOURCE_TYPE=git session parameters, and captures the review as a summary comment.', + modules: ['agent', 'workflows'], + category: 'engineering', + tags: ['automation', 'code-review'], + }, + { + icon: ClaudeIcon, + title: 'Trigger a scheduled analysis on a git-repo-mounted agent', + prompt: + "Create a scheduled workflow that opens a Managed Agent session against a self-hosted environment, mounts a repository via git-repo manifest session parameters (SOURCE_TYPE=repo), and stores the agent's analysis in a tables block.", + modules: ['scheduled', 'agent', 'tables', 'workflows'], + category: 'engineering', + tags: ['automation', 'analysis'], + }, + ], + skills: [ + { + name: 'run-self-hosted-managed-agent', + description: + 'Delegate a repo-scoped task to a self-hosted Managed Agent that mounts the repo via session parameters.', + content: + "# Run Self-Hosted Managed Agent\n\nWhen a Managed Agent needs to reason about deployment-specific context (e.g. a repo checkout), configure it via session parameters — the self-hosted agent sandbox reads each key/value pair as an env var.\n\n## Steps\n1. Pick a self-hosted environment.\n2. Set the session-parameter keys your deployment defines.\n3. Write the user message referring to whatever the session parameters set up.\n\n## Output\nThe block returns the agent's final text.", + }, + ], +} as const satisfies BlockMeta diff --git a/apps/sim/blocks/registry-maps.ts b/apps/sim/blocks/registry-maps.ts index 65a48b4c310..d855839c6c6 100644 --- a/apps/sim/blocks/registry-maps.ts +++ b/apps/sim/blocks/registry-maps.ts @@ -183,6 +183,14 @@ import { LoopsBlock, LoopsBlockMeta } from '@/blocks/blocks/loops' import { LumaBlock, LumaBlockMeta } from '@/blocks/blocks/luma' import { MailchimpBlock, MailchimpBlockMeta } from '@/blocks/blocks/mailchimp' import { MailgunBlock, MailgunBlockMeta } from '@/blocks/blocks/mailgun' +import { + ManagedAgentCloudBlock, + ManagedAgentCloudBlockMeta, +} from '@/blocks/blocks/managed_agent_cloud' +import { + ManagedAgentSelfHostedBlock, + ManagedAgentSelfHostedBlockMeta, +} from '@/blocks/blocks/managed_agent_self_hosted' import { ManualTriggerBlock } from '@/blocks/blocks/manual_trigger' import { McpBlock } from '@/blocks/blocks/mcp' import { Mem0Block, Mem0BlockMeta } from '@/blocks/blocks/mem0' @@ -510,6 +518,8 @@ export const BLOCK_REGISTRY: Record = { luma: LumaBlock, mailchimp: MailchimpBlock, mailgun: MailgunBlock, + managed_agent_cloud: ManagedAgentCloudBlock, + managed_agent_self_hosted: ManagedAgentSelfHostedBlock, manual_trigger: ManualTriggerBlock, mcp: McpBlock, mem0: Mem0Block, @@ -807,6 +817,8 @@ export const BLOCK_META_REGISTRY: Record = { luma: LumaBlockMeta, mailchimp: MailchimpBlockMeta, mailgun: MailgunBlockMeta, + managed_agent_cloud: ManagedAgentCloudBlockMeta, + managed_agent_self_hosted: ManagedAgentSelfHostedBlockMeta, mem0: Mem0BlockMeta, microsoft_ad: MicrosoftAdBlockMeta, microsoft_dataverse: MicrosoftDataverseBlockMeta, diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts index 9024e44cb88..842654e94b0 100644 --- a/apps/sim/blocks/types.ts +++ b/apps/sim/blocks/types.ts @@ -500,6 +500,13 @@ export interface BlockConfig { enabled: boolean available: string[] // List of trigger IDs this block supports } + /** + * Override the canvas-node width (px) for this block. Defaults to + * `BLOCK_DIMENSIONS.FIXED_WIDTH` (250). Use only when the block's rows + * carry unavoidably long identifiers (e.g. Anthropic session ids) and a + * standard-width tile would truncate them into meaningless ellipses. + */ + nodeWidth?: number } interface OutputConfig { diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 9ac49e147a7..3c49c0a2dfc 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -3683,6 +3683,27 @@ export const AnthropicIcon = (props: SVGProps) => ( ) +/** + * Claude AI symbol — the orbital "burst" mark used on claude.com. Sourced + * from Wikimedia Commons (Claude_AI_symbol.svg, CC0 1.0 Public Domain + * Dedication). Rendered with `currentColor` so the block tile can drive + * contrast; `iconColor: '#DA7756'` on the block config keeps the Claude + * warm-orange even when rendered bare. + */ +export const ClaudeIcon = (props: SVGProps) => ( + + Claude + + +) + export function AzureIcon(props: SVGProps) { const id = useId() const gradient0 = `azure_paint0_${id}` diff --git a/apps/sim/components/settings/navigation.ts b/apps/sim/components/settings/navigation.ts index c03e332de9e..f40fc61ebdf 100644 --- a/apps/sim/components/settings/navigation.ts +++ b/apps/sim/components/settings/navigation.ts @@ -51,6 +51,7 @@ export type WorkspaceSettingsSection = | 'byok' | 'custom-tools' | 'mcp' + | 'managed-agents' | 'workflow-mcp-servers' | 'api-keys' | 'inbox' @@ -91,6 +92,7 @@ export type UnifiedSettingsSection = | 'forks' | 'mcp' | 'custom-tools' + | 'managed-agents' | 'workflow-mcp-servers' | 'inbox' | 'admin' @@ -432,6 +434,18 @@ export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[] workspace: { id: 'mcp', group: 'tools', order: 4 }, }, }, + { + label: 'Managed Agents', + icon: HexSimple, + unified: { + id: 'managed-agents', + description: 'Link Claude Platform workspaces and pick agents from workflows.', + group: 'tools', + }, + planes: { + workspace: { id: 'managed-agents', group: 'tools', order: 5 }, + }, + }, { label: 'Sim API keys', icon: TerminalWindow, @@ -464,7 +478,7 @@ export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[] group: 'system', }, planes: { - workspace: { id: 'workflow-mcp-servers', group: 'tools', order: 5 }, + workspace: { id: 'workflow-mcp-servers', group: 'tools', order: 6 }, }, }, { @@ -773,6 +787,7 @@ const WORKSPACE_MUTATION_PERMISSION: Record [...managedAgentConnectionsKeys.all, 'list'] as const, + list: (workspaceId: string) => + [...managedAgentConnectionsKeys.lists(), workspaceId] as const, + agents: (connectionId: string, workspaceId: string) => + [...managedAgentConnectionsKeys.all, 'agents', workspaceId, connectionId] as const, + environments: (connectionId: string, workspaceId: string) => + [ + ...managedAgentConnectionsKeys.all, + 'environments', + workspaceId, + connectionId, + ] as const, + vaults: (connectionId: string, workspaceId: string) => + [...managedAgentConnectionsKeys.all, 'vaults', workspaceId, connectionId] as const, + memoryStores: (connectionId: string, workspaceId: string) => + [ + ...managedAgentConnectionsKeys.all, + 'memory-stores', + workspaceId, + connectionId, + ] as const, +} + +async function fetchConnections( + workspaceId: string, + signal?: AbortSignal +): Promise { + const { data } = await requestJson(listManagedAgentConnectionsContract, { + query: { workspaceId }, + signal, + }) + return data +} + +export function useManagedAgentConnections(workspaceId: string) { + return useQuery({ + queryKey: managedAgentConnectionsKeys.list(workspaceId), + queryFn: ({ signal }) => fetchConnections(workspaceId, signal), + enabled: !!workspaceId, + staleTime: MANAGED_AGENT_CONNECTION_LIST_STALE_TIME, + placeholderData: keepPreviousData, + }) +} + +interface CreateConnectionParams { + workspaceId: string + name: string + apiKey: string +} + +export function useCreateManagedAgentConnection() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: async ({ workspaceId, name, apiKey }: CreateConnectionParams) => { + logger.info(`Creating managed-agent connection: ${name}`) + const { data } = await requestJson(createManagedAgentConnectionContract, { + body: { workspaceId, name, apiKey }, + }) + return data + }, + onSuccess: (_data, variables) => { + queryClient.invalidateQueries({ + queryKey: managedAgentConnectionsKeys.list(variables.workspaceId), + }) + }, + }) +} + +interface DeleteConnectionParams { + workspaceId: string + id: string +} + +export function useDeleteManagedAgentConnection() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: async ({ workspaceId, id }: DeleteConnectionParams) => { + logger.info(`Deleting managed-agent connection ${id}`) + const data = await requestJson(deleteManagedAgentConnectionContract, { + query: { id, workspaceId }, + }) + return data + }, + onSuccess: (_data, variables) => { + queryClient.invalidateQueries({ + queryKey: managedAgentConnectionsKeys.list(variables.workspaceId), + }) + }, + }) +} + +interface RotateKeyParams { + workspaceId: string + id: string + apiKey: string +} + +export function useRotateManagedAgentConnection() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: async ({ workspaceId, id, apiKey }: RotateKeyParams) => { + logger.info(`Rotating managed-agent connection ${id}`) + const { data } = await requestJson(rotateManagedAgentConnectionContract, { + params: { id }, + body: { workspaceId, apiKey }, + }) + return data + }, + onSuccess: (_data, variables) => { + queryClient.invalidateQueries({ + queryKey: managedAgentConnectionsKeys.list(variables.workspaceId), + }) + }, + }) +} + +async function fetchAgents( + connectionId: string, + workspaceId: string, + signal?: AbortSignal +): Promise { + const { data } = await requestJson(listManagedAgentAgentsContract, { + params: { id: connectionId }, + query: { workspaceId }, + signal, + }) + return data +} + +/** + * Loads agents from the linked Claude workspace tied to `connectionId`. + * Empty `connectionId` disables the query so the block editor doesn't + * fire a bad request while the user hasn't picked a connection yet. + */ +export function useManagedAgentAgents(connectionId: string | null, workspaceId: string) { + return useQuery({ + queryKey: managedAgentConnectionsKeys.agents(connectionId ?? '', workspaceId), + queryFn: ({ signal }) => fetchAgents(connectionId as string, workspaceId, signal), + enabled: !!connectionId && !!workspaceId, + staleTime: MANAGED_AGENT_RESOURCE_STALE_TIME, + }) +} + +async function fetchEnvironments( + connectionId: string, + workspaceId: string, + signal?: AbortSignal +): Promise { + const { data } = await requestJson(listManagedAgentEnvironmentsContract, { + params: { id: connectionId }, + query: { workspaceId }, + signal, + }) + return data +} + +export function useManagedAgentEnvironments( + connectionId: string | null, + workspaceId: string +) { + return useQuery({ + queryKey: managedAgentConnectionsKeys.environments(connectionId ?? '', workspaceId), + queryFn: ({ signal }) => fetchEnvironments(connectionId as string, workspaceId, signal), + enabled: !!connectionId && !!workspaceId, + staleTime: MANAGED_AGENT_RESOURCE_STALE_TIME, + }) +} + +async function fetchVaults( + connectionId: string, + workspaceId: string, + signal?: AbortSignal +): Promise { + const { data } = await requestJson(listManagedAgentVaultsContract, { + params: { id: connectionId }, + query: { workspaceId }, + signal, + }) + return data +} + +export function useManagedAgentVaults(connectionId: string | null, workspaceId: string) { + return useQuery({ + queryKey: managedAgentConnectionsKeys.vaults(connectionId ?? '', workspaceId), + queryFn: ({ signal }) => fetchVaults(connectionId as string, workspaceId, signal), + enabled: !!connectionId && !!workspaceId, + staleTime: MANAGED_AGENT_RESOURCE_STALE_TIME, + }) +} + +async function fetchMemoryStores( + connectionId: string, + workspaceId: string, + signal?: AbortSignal +): Promise { + const { data } = await requestJson(listManagedAgentMemoryStoresContract, { + params: { id: connectionId }, + query: { workspaceId }, + signal, + }) + return data +} + +export function useManagedAgentMemoryStores( + connectionId: string | null, + workspaceId: string +) { + return useQuery({ + queryKey: managedAgentConnectionsKeys.memoryStores(connectionId ?? '', workspaceId), + queryFn: ({ signal }) => fetchMemoryStores(connectionId as string, workspaceId, signal), + enabled: !!connectionId && !!workspaceId, + staleTime: MANAGED_AGENT_RESOURCE_STALE_TIME, + }) +} diff --git a/apps/sim/lib/api/contracts/index.ts b/apps/sim/lib/api/contracts/index.ts index ef339965c5b..7c16a0a9024 100644 --- a/apps/sim/lib/api/contracts/index.ts +++ b/apps/sim/lib/api/contracts/index.ts @@ -16,6 +16,7 @@ export * from './inbox' export * from './media' export * from './permission-groups' export * from './primitives' +export * from './managed-agent-connections' export * from './selectors' export * from './skills' export * from './storage-transfer' diff --git a/apps/sim/lib/api/contracts/managed-agent-connections.ts b/apps/sim/lib/api/contracts/managed-agent-connections.ts new file mode 100644 index 00000000000..d9f40ecbcd8 --- /dev/null +++ b/apps/sim/lib/api/contracts/managed-agent-connections.ts @@ -0,0 +1,206 @@ +import { z } from 'zod' +import { defineRouteContract } from '@/lib/api/contracts/types' + +export const managedAgentConnectionSchema = z.object({ + id: z.string(), + workspaceId: z.string(), + userId: z.string().nullable(), + name: z.string(), + /** First 8 chars + '…' + last 4. Never plaintext. */ + maskedApiKey: z.string(), + lastVerifiedAt: z.string().nullable(), + lastVerificationError: z.string().nullable(), + createdAt: z.string(), + updatedAt: z.string(), +}) + +export type ManagedAgentConnection = z.output + +export const listManagedAgentConnectionsQuerySchema = z.object({ + workspaceId: z.string().min(1), +}) + +export const createManagedAgentConnectionBodySchema = z.object({ + workspaceId: z.string().min(1), + name: z + .string() + .trim() + .min(1, 'Name is required') + .max(64, 'Name must be 64 characters or fewer'), + apiKey: z + .string() + .trim() + .min(1, 'API key is required') + .max(2048, 'API key looks too long — check for stray whitespace'), +}) + +export const deleteManagedAgentConnectionQuerySchema = z.object({ + id: z.string().min(1), + workspaceId: z.string().min(1), +}) + +export const rotateManagedAgentConnectionParamsSchema = z.object({ + id: z.string().min(1), +}) +export const rotateManagedAgentConnectionBodySchema = z.object({ + workspaceId: z.string().min(1), + apiKey: z.string().trim().min(1).max(2048), +}) + +export const managedAgentProxyParamsSchema = z.object({ + id: z.string().min(1), +}) +export const managedAgentProxyQuerySchema = z.object({ + workspaceId: z.string().min(1), +}) + +export const managedAgentAgentSchema = z.object({ + id: z.string(), + name: z.string().nullable().optional(), + description: z.string().nullable().optional(), + version: z.union([z.string(), z.number()]).nullable().optional(), +}) +export type ManagedAgentAgent = z.output + +export const managedAgentEnvironmentSchema = z.object({ + id: z.string(), + name: z.string().nullable().optional(), + description: z.string().nullable().optional(), + envType: z.enum(['cloud', 'self_hosted']), + scope: z.enum(['organization', 'account']).nullable().optional(), +}) +export type ManagedAgentEnvironment = z.output + +export const managedAgentVaultSchema = z.object({ + id: z.string(), + name: z.string().nullable().optional(), + description: z.string().nullable().optional(), +}) +export type ManagedAgentVault = z.output + +export const managedAgentMemoryStoreSchema = z.object({ + id: z.string(), + name: z.string().nullable().optional(), + description: z.string().nullable().optional(), + archivedAt: z.string().nullable().optional(), +}) +export type ManagedAgentMemoryStore = z.output + +export const managedAgentEnvironmentDetailParamsSchema = z.object({ + id: z.string().min(1), + envId: z.string().min(1), +}) + +export const listManagedAgentConnectionsContract = defineRouteContract({ + method: 'GET', + path: '/api/managed-agent-connections', + query: listManagedAgentConnectionsQuerySchema, + response: { + mode: 'json', + schema: z.object({ + data: z.array(managedAgentConnectionSchema), + }), + }, +}) + +export const createManagedAgentConnectionContract = defineRouteContract({ + method: 'POST', + path: '/api/managed-agent-connections', + body: createManagedAgentConnectionBodySchema, + response: { + mode: 'json', + schema: z.object({ + success: z.literal(true), + data: managedAgentConnectionSchema, + }), + }, +}) + +export const deleteManagedAgentConnectionContract = defineRouteContract({ + method: 'DELETE', + path: '/api/managed-agent-connections', + query: deleteManagedAgentConnectionQuerySchema, + response: { + mode: 'json', + schema: z.object({ success: z.literal(true) }), + }, +}) + +export const rotateManagedAgentConnectionContract = defineRouteContract({ + method: 'PATCH', + path: '/api/managed-agent-connections/[id]', + params: rotateManagedAgentConnectionParamsSchema, + body: rotateManagedAgentConnectionBodySchema, + response: { + mode: 'json', + schema: z.object({ + success: z.literal(true), + data: managedAgentConnectionSchema, + }), + }, +}) + +export const listManagedAgentAgentsContract = defineRouteContract({ + method: 'GET', + path: '/api/managed-agent-connections/[id]/agents', + params: managedAgentProxyParamsSchema, + query: managedAgentProxyQuerySchema, + response: { + mode: 'json', + schema: z.object({ + data: z.array(managedAgentAgentSchema), + }), + }, +}) + +export const listManagedAgentEnvironmentsContract = defineRouteContract({ + method: 'GET', + path: '/api/managed-agent-connections/[id]/environments', + params: managedAgentProxyParamsSchema, + query: managedAgentProxyQuerySchema, + response: { + mode: 'json', + schema: z.object({ + data: z.array(managedAgentEnvironmentSchema), + }), + }, +}) + +export const getManagedAgentEnvironmentContract = defineRouteContract({ + method: 'GET', + path: '/api/managed-agent-connections/[id]/environments/[envId]', + params: managedAgentEnvironmentDetailParamsSchema, + query: managedAgentProxyQuerySchema, + response: { + mode: 'json', + schema: z.object({ + data: managedAgentEnvironmentSchema, + }), + }, +}) + +export const listManagedAgentVaultsContract = defineRouteContract({ + method: 'GET', + path: '/api/managed-agent-connections/[id]/vaults', + params: managedAgentProxyParamsSchema, + query: managedAgentProxyQuerySchema, + response: { + mode: 'json', + schema: z.object({ + data: z.array(managedAgentVaultSchema), + }), + }, +}) + +export const listManagedAgentMemoryStoresContract = defineRouteContract({ + method: 'GET', + path: '/api/managed-agent-connections/[id]/memory-stores', + params: managedAgentProxyParamsSchema, + query: managedAgentProxyQuerySchema, + response: { + mode: 'json', + schema: z.object({ + data: z.array(managedAgentMemoryStoreSchema), + }), + }, +}) diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index c86eb9e7ff4..ff561e9ffd4 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -148,6 +148,7 @@ export const env = createEnv({ OPENAI_API_KEY_2: z.string().min(1).optional(), // Additional OpenAI API key for load balancing OPENAI_API_KEY_3: z.string().min(1).optional(), // Additional OpenAI API key for load balancing MISTRAL_API_KEY: z.string().min(1).optional(), // Mistral AI API key + MANAGED_AGENT_DEBUG_PAYLOAD: z.string().optional(), // When "1" | "true" | "yes", the Managed Agent workflow-block tool logs the full session-create payload (never includes the api key) so you can inspect memory / vault / metadata routing ANTHROPIC_API_KEY_1: z.string().min(1).optional(), // Primary Anthropic Claude API key ANTHROPIC_API_KEY_2: z.string().min(1).optional(), // Additional Anthropic API key for load balancing ANTHROPIC_API_KEY_3: z.string().min(1).optional(), // Additional Anthropic API key for load balancing @@ -537,6 +538,8 @@ export const env = createEnv({ NEXT_PUBLIC_SUPPORT_EMAIL: z.string().email().optional(), // Custom support email NEXT_PUBLIC_E2B_ENABLED: z.string().optional(), + NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS: z.string().optional(), // JSON object of default session-metadata key/value pairs seeded into fresh Claude Managed Agents (self-hosted) blocks. Example: '{"SOURCE_TYPE":"git","SOURCE_REF":"main"}'. Absent = empty table. + NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED: z.string().optional(), // When "1"|"true"|"yes", reveal the Memory Store + Memory Access fields on the Claude Managed Agents (self-hosted) block. Off by default because Claude self-hosted environments do not currently support memory-store attach. NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS: z.string().optional(), // Hide Bedrock credential fields when deployment uses AWS default credential chain (IAM roles, instance profiles, ECS task roles, IRSA) NEXT_PUBLIC_AZURE_CONFIGURED: z.string().optional(), // Hide Azure credential fields when endpoint/key/version are pre-configured server-side NEXT_PUBLIC_COHERE_CONFIGURED: z.string().optional(), // Hide Cohere API key field on Knowledge block when COHERE_API_KEY is pre-configured server-side @@ -610,6 +613,8 @@ export const env = createEnv({ NEXT_PUBLIC_EMAIL_PASSWORD_SIGNUP_ENABLED: process.env.NEXT_PUBLIC_EMAIL_PASSWORD_SIGNUP_ENABLED, NEXT_PUBLIC_TURNSTILE_SITE_KEY: process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY, NEXT_PUBLIC_E2B_ENABLED: process.env.NEXT_PUBLIC_E2B_ENABLED, + NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS: process.env.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS, + NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED: process.env.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED, NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS: process.env.NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS, NEXT_PUBLIC_AZURE_CONFIGURED: process.env.NEXT_PUBLIC_AZURE_CONFIGURED, NEXT_PUBLIC_COHERE_CONFIGURED: process.env.NEXT_PUBLIC_COHERE_CONFIGURED, diff --git a/apps/sim/lib/managed-agents/anthropic-verify.ts b/apps/sim/lib/managed-agents/anthropic-verify.ts new file mode 100644 index 00000000000..91fdba27c52 --- /dev/null +++ b/apps/sim/lib/managed-agents/anthropic-verify.ts @@ -0,0 +1,35 @@ +import { + ANTHROPIC_API_BASE, + ANTHROPIC_VERSION, + MANAGED_AGENTS_BETA, +} from '@/lib/managed-agents/session-client' + +/** + * Probes `GET /v1/agents` with the given key to prove the key is valid and + * scoped to a real Claude Platform workspace. On success we don't care about + * the response body — a 2xx is proof enough. On failure the body is trimmed + * to 200 chars and surfaced so the "Verify" button can render Anthropic's + * actual message. + */ +export async function verifyAnthropicApiKey( + apiKey: string, + signal?: AbortSignal +): Promise<{ ok: true } | { ok: false; error: string }> { + const resp = await fetch(`${ANTHROPIC_API_BASE}/v1/agents?limit=1`, { + method: 'GET', + headers: { + 'x-api-key': apiKey, + 'anthropic-version': ANTHROPIC_VERSION, + 'anthropic-beta': MANAGED_AGENTS_BETA, + }, + signal, + }) + if (resp.ok) return { ok: true } + const detail = await resp.text().catch(() => '') + const status = resp.status + const message = + status === 401 || status === 403 + ? 'Anthropic rejected the API key. Check that the key belongs to a Managed Agents-enabled workspace.' + : `Anthropic verify failed (HTTP ${status}). ${detail.slice(0, 200)}` + return { ok: false, error: message } +} diff --git a/apps/sim/lib/managed-agents/connections.test.ts b/apps/sim/lib/managed-agents/connections.test.ts new file mode 100644 index 00000000000..41d49ebb34f --- /dev/null +++ b/apps/sim/lib/managed-agents/connections.test.ts @@ -0,0 +1,367 @@ +/** + * @vitest-environment node + */ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { dbState, mockEncryptSecret, mockDecryptSecret, mockGenerateShortId } = vi.hoisted( + () => ({ + dbState: { + selectResults: [] as any[][], + insertRows: [] as any[], + updateArgs: [] as any[], + deleteArgs: [] as any[], + whereCalls: [] as any[], + }, + // Opaque encryption mock: the ciphertext never contains the plaintext, + // so tests can assert that the raw key does not leak into the row. + mockEncryptSecret: vi.fn(async (value: string) => ({ + encrypted: Buffer.from(value).toString('base64'), + iv: 'iv', + })), + mockDecryptSecret: vi.fn(async (value: string) => ({ + decrypted: Buffer.from(value, 'base64').toString('utf8'), + })), + mockGenerateShortId: vi.fn(() => 'gen-id-123'), + }) +) + +function makeSelectChain() { + const chain: any = {} + chain.from = vi.fn(() => chain) + chain.where = vi.fn((...args: any[]) => { + dbState.whereCalls.push(args) + return chain + }) + chain.orderBy = vi.fn(() => Promise.resolve(dbState.selectResults.shift() ?? [])) + chain.limit = vi.fn(() => Promise.resolve(dbState.selectResults.shift() ?? [])) + return chain +} + +function makeInsertChain() { + const chain: any = {} + chain.values = vi.fn((row: any) => { + dbState.insertRows.push(row) + return Promise.resolve(undefined) + }) + return chain +} + +function makeUpdateChain() { + const chain: any = {} + const capture: any = {} + chain.set = vi.fn((row: any) => { + capture.set = row + return chain + }) + chain.where = vi.fn((...args: any[]) => { + capture.where = args + dbState.updateArgs.push(capture) + return Promise.resolve(undefined) + }) + return chain +} + +function makeDeleteChain() { + const chain: any = {} + const capture: any = {} + chain.where = vi.fn((...args: any[]) => { + capture.where = args + dbState.deleteArgs.push(capture) + return Promise.resolve(undefined) + }) + return chain +} + +vi.mock('@sim/db', () => ({ + db: { + select: vi.fn(() => makeSelectChain()), + insert: vi.fn(() => makeInsertChain()), + update: vi.fn(() => makeUpdateChain()), + delete: vi.fn(() => makeDeleteChain()), + }, +})) + +vi.mock('@sim/db/schema', () => ({ + managedAgentConnection: { + id: 'managed_agent_connection.id', + workspaceId: 'managed_agent_connection.workspaceId', + createdAt: 'managed_agent_connection.createdAt', + }, +})) + +vi.mock('drizzle-orm', () => ({ + and: vi.fn((...args: unknown[]) => ({ and: args })), + eq: vi.fn((a: unknown, b: unknown) => ({ eq: [a, b] })), +})) + +vi.mock('@/lib/core/security/encryption', () => ({ + encryptSecret: mockEncryptSecret, + decryptSecret: mockDecryptSecret, +})) + +vi.mock('@sim/utils/id', () => ({ + generateShortId: mockGenerateShortId, +})) + +import { + createConnection, + deleteConnection, + getDecryptedApiKey, + listConnections, + markVerificationResult, + rotateConnectionKey, +} from '@/lib/managed-agents/connections' + +const now = new Date('2026-07-19T00:00:00.000Z') +const PLAINTEXT_KEY = 'sk-ant-secret-key-1234567890abcdef' +const CIPHERTEXT_KEY = Buffer.from(PLAINTEXT_KEY).toString('base64') +const baseRow = { + id: 'conn_1', + workspaceId: 'ws_A', + userId: 'user_1', + name: 'prod', + encryptedApiKey: CIPHERTEXT_KEY, + lastVerifiedAt: now, + lastVerificationError: null, + createdAt: now, + updatedAt: now, +} + +describe('listConnections', () => { + beforeEach(() => { + vi.clearAllMocks() + dbState.selectResults = [] + dbState.insertRows = [] + dbState.updateArgs = [] + dbState.deleteArgs = [] + dbState.whereCalls = [] + }) + + it('returns a masked preview of each connection and never exposes the plaintext', async () => { + dbState.selectResults = [[baseRow]] + const results = await listConnections({ workspaceId: 'ws_A' }) + expect(results).toHaveLength(1) + expect(results[0]).not.toHaveProperty('encryptedApiKey') + // 8-char prefix + `…` + 4-char suffix. + expect(results[0].maskedApiKey).toBe('sk-ant-s…cdef') + }) + + it('falls back to a solid bullet mask when a row cannot be decrypted', async () => { + dbState.selectResults = [[baseRow]] + mockDecryptSecret.mockRejectedValueOnce(new Error('bad tag')) + const results = await listConnections({ workspaceId: 'ws_A' }) + expect(results[0].maskedApiKey).toBe('••••••••') + }) + + it('scopes the query to the requested workspaceId', async () => { + dbState.selectResults = [[]] + await listConnections({ workspaceId: 'ws_A' }) + expect(dbState.whereCalls).toHaveLength(1) + // The where argument must reference the workspaceId column and value. + expect(JSON.stringify(dbState.whereCalls[0])).toContain('ws_A') + }) +}) + +describe('getDecryptedApiKey', () => { + beforeEach(() => { + vi.clearAllMocks() + dbState.selectResults = [] + dbState.whereCalls = [] + }) + + it('returns the decrypted plaintext when the connection exists', async () => { + dbState.selectResults = [[baseRow]] + const key = await getDecryptedApiKey({ id: 'conn_1', workspaceId: 'ws_A' }) + expect(key).toBe(PLAINTEXT_KEY) + // The DB lookup must include BOTH id and workspaceId (no cross-workspace read). + expect(JSON.stringify(dbState.whereCalls[0])).toContain('conn_1') + expect(JSON.stringify(dbState.whereCalls[0])).toContain('ws_A') + }) + + it('returns null when the connection does not exist for that workspace', async () => { + dbState.selectResults = [[]] + const key = await getDecryptedApiKey({ id: 'conn_1', workspaceId: 'ws_A' }) + expect(key).toBeNull() + }) +}) + +describe('createConnection', () => { + beforeEach(() => { + vi.clearAllMocks() + dbState.selectResults = [] + dbState.insertRows = [] + dbState.whereCalls = [] + mockGenerateShortId.mockReturnValue('gen-id-123') + }) + + it('encrypts the api key before persisting and never writes plaintext', async () => { + const plaintext = 'sk-ant-plaintext-secret-9999' + const expectedCiphertext = Buffer.from(plaintext).toString('base64') + dbState.selectResults = [[{ ...baseRow, id: 'gen-id-123' }]] // getConnection after insert + await createConnection({ + workspaceId: 'ws_A', + userId: 'user_1', + name: 'prod', + apiKey: plaintext, + }) + expect(mockEncryptSecret).toHaveBeenCalledWith(plaintext) + const inserted = dbState.insertRows[0] + expect(inserted.encryptedApiKey).toBe(expectedCiphertext) + // The raw key MUST NOT appear anywhere in the persisted row. + expect(JSON.stringify(inserted)).not.toContain(plaintext) + }) + + it('persists the connection under the provided workspaceId', async () => { + dbState.selectResults = [[{ ...baseRow, id: 'gen-id-123', workspaceId: 'ws_A' }]] + await createConnection({ + workspaceId: 'ws_A', + userId: 'user_1', + name: 'prod', + apiKey: 'sk-ant-plaintext', + }) + expect(dbState.insertRows[0].workspaceId).toBe('ws_A') + }) + + it('runs `verify` before writing and aborts on failure', async () => { + const verify = vi + .fn() + .mockResolvedValueOnce({ ok: false, error: 'Anthropic rejected the key' }) + await expect( + createConnection({ + workspaceId: 'ws_A', + userId: 'user_1', + name: 'prod', + apiKey: 'sk-ant-bad', + verify, + }) + ).rejects.toThrow('Anthropic rejected the key') + expect(dbState.insertRows).toHaveLength(0) + expect(mockEncryptSecret).not.toHaveBeenCalled() + }) + + it('sets lastVerifiedAt when verify passes, null otherwise', async () => { + dbState.selectResults = [[{ ...baseRow, id: 'gen-id-123' }]] + await createConnection({ + workspaceId: 'ws_A', + userId: 'user_1', + name: 'prod', + apiKey: 'sk-ant-plaintext', + verify: async () => ({ ok: true }), + }) + expect(dbState.insertRows[0].lastVerifiedAt).toBeInstanceOf(Date) + + dbState.selectResults = [[{ ...baseRow, id: 'gen-id-123' }]] + dbState.insertRows.length = 0 + await createConnection({ + workspaceId: 'ws_A', + userId: 'user_1', + name: 'prod', + apiKey: 'sk-ant-plaintext', + }) + expect(dbState.insertRows[0].lastVerifiedAt).toBeNull() + }) +}) + +describe('deleteConnection', () => { + beforeEach(() => { + vi.clearAllMocks() + dbState.selectResults = [] + dbState.deleteArgs = [] + }) + + it('returns false when the connection is not found', async () => { + dbState.selectResults = [[]] + const ok = await deleteConnection({ id: 'conn_missing', workspaceId: 'ws_A' }) + expect(ok).toBe(false) + expect(dbState.deleteArgs).toHaveLength(0) + }) + + it('returns true and issues a workspace-scoped delete when found', async () => { + dbState.selectResults = [[baseRow]] + const ok = await deleteConnection({ id: 'conn_1', workspaceId: 'ws_A' }) + expect(ok).toBe(true) + expect(dbState.deleteArgs).toHaveLength(1) + expect(JSON.stringify(dbState.deleteArgs[0].where)).toContain('conn_1') + expect(JSON.stringify(dbState.deleteArgs[0].where)).toContain('ws_A') + }) +}) + +describe('rotateConnectionKey', () => { + beforeEach(() => { + vi.clearAllMocks() + dbState.selectResults = [] + dbState.updateArgs = [] + }) + + it('returns null when the connection does not exist for that workspace', async () => { + dbState.selectResults = [[]] // getConnection returns empty + const result = await rotateConnectionKey({ + id: 'conn_missing', + workspaceId: 'ws_A', + apiKey: 'sk-ant-new', + }) + expect(result).toBeNull() + expect(dbState.updateArgs).toHaveLength(0) + }) + + it('encrypts the new key and updates the row workspace-scoped', async () => { + // Two select results: getConnection before update, getConnection after. + dbState.selectResults = [[baseRow], [baseRow]] + await rotateConnectionKey({ + id: 'conn_1', + workspaceId: 'ws_A', + apiKey: 'sk-ant-new', + }) + expect(mockEncryptSecret).toHaveBeenCalledWith('sk-ant-new') + expect(dbState.updateArgs).toHaveLength(1) + expect(dbState.updateArgs[0].set.encryptedApiKey).toBe( + Buffer.from('sk-ant-new').toString('base64') + ) + expect(JSON.stringify(dbState.updateArgs[0].where)).toContain('conn_1') + expect(JSON.stringify(dbState.updateArgs[0].where)).toContain('ws_A') + }) + + it('rejects when verify fails and never touches the row', async () => { + dbState.selectResults = [[baseRow]] + await expect( + rotateConnectionKey({ + id: 'conn_1', + workspaceId: 'ws_A', + apiKey: 'sk-ant-bad', + verify: async () => ({ ok: false, error: 'nope' }), + }) + ).rejects.toThrow('nope') + expect(dbState.updateArgs).toHaveLength(0) + expect(mockEncryptSecret).not.toHaveBeenCalled() + }) +}) + +describe('markVerificationResult', () => { + beforeEach(() => { + vi.clearAllMocks() + dbState.updateArgs = [] + }) + + it('records success as lastVerifiedAt=now, error=null', async () => { + await markVerificationResult({ id: 'conn_1', workspaceId: 'ws_A', ok: true }) + expect(dbState.updateArgs[0].set.lastVerifiedAt).toBeInstanceOf(Date) + expect(dbState.updateArgs[0].set.lastVerificationError).toBeNull() + }) + + it('records failure as lastVerifiedAt=null with a bounded error string', async () => { + const bigError = 'e'.repeat(1000) + await markVerificationResult({ + id: 'conn_1', + workspaceId: 'ws_A', + ok: false, + error: bigError, + }) + expect(dbState.updateArgs[0].set.lastVerifiedAt).toBeNull() + expect(dbState.updateArgs[0].set.lastVerificationError).toHaveLength(500) + }) + + it('scopes the update to the workspaceId', async () => { + await markVerificationResult({ id: 'conn_1', workspaceId: 'ws_A', ok: true }) + expect(JSON.stringify(dbState.updateArgs[0].where)).toContain('ws_A') + }) +}) diff --git a/apps/sim/lib/managed-agents/connections.ts b/apps/sim/lib/managed-agents/connections.ts new file mode 100644 index 00000000000..5f4551cb3a5 --- /dev/null +++ b/apps/sim/lib/managed-agents/connections.ts @@ -0,0 +1,219 @@ +import { db } from '@sim/db' +import { managedAgentConnection } from '@sim/db/schema' +import { createLogger } from '@sim/logger' +import { generateShortId } from '@sim/utils/id' +import { and, eq } from 'drizzle-orm' +import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption' + +const logger = createLogger('ManagedAgentConnections') + +/** + * Row shape returned by list/get. `encryptedApiKey` is intentionally + * dropped and replaced with `maskedApiKey` — nothing on the wire ever + * carries the plaintext. + */ +export interface ManagedAgentConnectionSummary { + id: string + workspaceId: string + userId: string | null + name: string + maskedApiKey: string + lastVerifiedAt: Date | null + lastVerificationError: string | null + createdAt: Date + updatedAt: Date +} + +function toSummary( + row: typeof managedAgentConnection.$inferSelect, + apiKey: string | null +): ManagedAgentConnectionSummary { + return { + id: row.id, + workspaceId: row.workspaceId, + userId: row.userId, + name: row.name, + maskedApiKey: apiKey ? maskApiKey(apiKey) : '••••••••', + lastVerifiedAt: row.lastVerifiedAt, + lastVerificationError: row.lastVerificationError, + createdAt: row.createdAt, + updatedAt: row.updatedAt, + } +} + +function maskApiKey(apiKey: string): string { + if (apiKey.length <= 12) return '••••••••' + return `${apiKey.slice(0, 8)}…${apiKey.slice(-4)}` +} + +export async function listConnections(params: { + workspaceId: string +}): Promise { + const rows = await db + .select() + .from(managedAgentConnection) + .where(eq(managedAgentConnection.workspaceId, params.workspaceId)) + .orderBy(managedAgentConnection.createdAt) + // Decrypt only to compute the mask preview; failures fall back to a + // solid bullet string so a corrupt row can't take down the list. + const summaries: ManagedAgentConnectionSummary[] = [] + for (const row of rows) { + let apiKey: string | null = null + try { + apiKey = (await decryptSecret(row.encryptedApiKey)).decrypted + } catch (err) { + logger.warn(`Failed to decrypt api key for connection ${row.id}`, { error: err }) + } + summaries.push(toSummary(row, apiKey)) + } + return summaries +} + +export async function getConnection(params: { + id: string + workspaceId: string +}): Promise { + const rows = await db + .select() + .from(managedAgentConnection) + .where( + and( + eq(managedAgentConnection.id, params.id), + eq(managedAgentConnection.workspaceId, params.workspaceId) + ) + ) + .limit(1) + return rows[0] ?? null +} + +/** + * Decrypts the stored API key for a given connection. Server-side only — + * never expose the return value to the browser. Callers are proxy routes + * and the workflow-block tool. + */ +export async function getDecryptedApiKey(params: { + id: string + workspaceId: string +}): Promise { + const row = await getConnection(params) + if (!row) return null + const { decrypted } = await decryptSecret(row.encryptedApiKey) + return decrypted +} + +export interface CreateConnectionInput { + workspaceId: string + userId: string + name: string + apiKey: string + /** + * Optional callback invoked with the decrypted key so the caller can + * verify it against `GET /v1/agents` before we commit the row. Return + * `{ ok: true }` to persist, `{ ok: false, error }` to reject. + */ + verify?: (apiKey: string) => Promise<{ ok: true } | { ok: false; error: string }> +} + +export async function createConnection( + input: CreateConnectionInput +): Promise { + if (input.verify) { + const outcome = await input.verify(input.apiKey) + if (!outcome.ok) { + throw new Error(outcome.error) + } + } + const now = new Date() + const { encrypted } = await encryptSecret(input.apiKey) + const id = generateShortId() + await db.insert(managedAgentConnection).values({ + id, + workspaceId: input.workspaceId, + userId: input.userId, + name: input.name, + encryptedApiKey: encrypted, + lastVerifiedAt: input.verify ? now : null, + lastVerificationError: null, + createdAt: now, + updatedAt: now, + }) + const created = await getConnection({ id, workspaceId: input.workspaceId }) + if (!created) throw new Error('Failed to load newly created managed-agent connection') + return toSummary(created, input.apiKey) +} + +export async function deleteConnection(params: { + id: string + workspaceId: string +}): Promise { + const existing = await getConnection(params) + if (!existing) return false + await db + .delete(managedAgentConnection) + .where( + and( + eq(managedAgentConnection.id, params.id), + eq(managedAgentConnection.workspaceId, params.workspaceId) + ) + ) + return true +} + +export interface RotateKeyInput { + id: string + workspaceId: string + apiKey: string + verify?: (apiKey: string) => Promise<{ ok: true } | { ok: false; error: string }> +} + +export async function rotateConnectionKey( + input: RotateKeyInput +): Promise { + const existing = await getConnection({ id: input.id, workspaceId: input.workspaceId }) + if (!existing) return null + if (input.verify) { + const outcome = await input.verify(input.apiKey) + if (!outcome.ok) throw new Error(outcome.error) + } + const { encrypted } = await encryptSecret(input.apiKey) + const now = new Date() + await db + .update(managedAgentConnection) + .set({ + encryptedApiKey: encrypted, + lastVerifiedAt: input.verify ? now : existing.lastVerifiedAt, + lastVerificationError: null, + updatedAt: now, + }) + .where( + and( + eq(managedAgentConnection.id, input.id), + eq(managedAgentConnection.workspaceId, input.workspaceId) + ) + ) + const refreshed = await getConnection({ id: input.id, workspaceId: input.workspaceId }) + if (!refreshed) return null + return toSummary(refreshed, input.apiKey) +} + +export async function markVerificationResult(params: { + id: string + workspaceId: string + ok: boolean + error?: string +}): Promise { + const now = new Date() + await db + .update(managedAgentConnection) + .set({ + lastVerifiedAt: params.ok ? now : null, + lastVerificationError: params.ok ? null : (params.error ?? 'Unknown error').slice(0, 500), + updatedAt: now, + }) + .where( + and( + eq(managedAgentConnection.id, params.id), + eq(managedAgentConnection.workspaceId, params.workspaceId) + ) + ) +} diff --git a/apps/sim/lib/managed-agents/proxy.ts b/apps/sim/lib/managed-agents/proxy.ts new file mode 100644 index 00000000000..eb523cb1988 --- /dev/null +++ b/apps/sim/lib/managed-agents/proxy.ts @@ -0,0 +1,60 @@ +import { createLogger } from '@sim/logger' +import { + ANTHROPIC_API_BASE, + ANTHROPIC_VERSION, + MANAGED_AGENTS_BETA, +} from '@/lib/managed-agents/session-client' + +const logger = createLogger('ManagedAgentProxy') + +/** + * Server-side proxy helper for the block-editor dropdowns + * (`GET /v1/agents`, `/v1/environments`, `/v1/environments/{id}`, + * `/v1/vaults`). Decrypted keys never touch the browser — the client + * always calls the workspace-scoped proxy route, which in turn calls + * Anthropic with the stored key. + */ +export async function proxyManagedAgentsGet( + apiKey: string, + pathAndQuery: string, + signal?: AbortSignal +): Promise { + const url = `${ANTHROPIC_API_BASE}${pathAndQuery}` + const resp = await fetch(url, { + method: 'GET', + headers: { + 'x-api-key': apiKey, + 'anthropic-version': ANTHROPIC_VERSION, + 'anthropic-beta': MANAGED_AGENTS_BETA, + }, + signal, + }) + if (!resp.ok) { + const detail = await resp.text().catch(() => '') + logger.warn('managed-agents proxy call failed', { + pathAndQuery, + status: resp.status, + detail: detail.slice(0, 200), + }) + throw new ManagedAgentProxyError(resp.status, detail.slice(0, 400)) + } + return (await resp.json()) as T +} + +export class ManagedAgentProxyError extends Error { + readonly status: number + readonly detail: string + constructor(status: number, detail: string) { + super(`Anthropic proxy call failed (HTTP ${status}): ${detail}`) + this.name = 'ManagedAgentProxyError' + this.status = status + this.detail = detail + } +} + +/** Normalise Anthropic's `data: []` + `has_more` list pages into a flat array. */ +export interface AnthropicListPage { + data?: T[] + has_more?: boolean + last_id?: string +} diff --git a/apps/sim/lib/managed-agents/session-client.test.ts b/apps/sim/lib/managed-agents/session-client.test.ts new file mode 100644 index 00000000000..960b82c624a --- /dev/null +++ b/apps/sim/lib/managed-agents/session-client.test.ts @@ -0,0 +1,201 @@ +/** + * @vitest-environment node + */ +import { describe, expect, it } from 'vitest' +import { buildSessionCreatePayload } from '@/lib/managed-agents/session-client' + +const BASE = { + apiKey: 'sk-ant-fake', + agentId: 'agent_01ABC', + environmentId: 'env_01XYZ', +} as const + +describe('buildSessionCreatePayload — always-on fields', () => { + it('emits `agent` and `environment_id` from the input', () => { + const payload = buildSessionCreatePayload({ ...BASE }) + expect(payload).toEqual({ agent: 'agent_01ABC', environment_id: 'env_01XYZ' }) + }) + + it('emits `title` when set', () => { + const payload = buildSessionCreatePayload({ ...BASE, title: 'my session' }) + expect(payload.title).toBe('my session') + }) + + it('emits `vault_ids` only when non-empty', () => { + const withNone = buildSessionCreatePayload({ ...BASE }) + expect(withNone.vault_ids).toBeUndefined() + + const withEmptyArray = buildSessionCreatePayload({ ...BASE, vaultIds: [] }) + expect(withEmptyArray.vault_ids).toBeUndefined() + + const withVaults = buildSessionCreatePayload({ + ...BASE, + vaultIds: ['vlt_1', 'vlt_2'], + }) + expect(withVaults.vault_ids).toEqual(['vlt_1', 'vlt_2']) + }) +}) + +describe('buildSessionCreatePayload — cloud env', () => { + it('attaches memory store as a `memory_store` resource with default access', () => { + const payload = buildSessionCreatePayload({ + ...BASE, + envType: 'cloud', + memoryStoreId: 'memstore_01', + }) + expect(payload.resources).toEqual([ + { type: 'memory_store', memory_store_id: 'memstore_01', access: 'read_write' }, + ]) + // Cloud must NOT fold memory into metadata. + expect(payload.metadata).toBeUndefined() + }) + + it('honors explicit read_only memory access', () => { + const payload = buildSessionCreatePayload({ + ...BASE, + envType: 'cloud', + memoryStoreId: 'memstore_01', + memoryAccess: 'read_only', + }) + expect(payload.resources).toEqual([ + { type: 'memory_store', memory_store_id: 'memstore_01', access: 'read_only' }, + ]) + }) + + it('attaches file resources with an optional mount_path', () => { + const payload = buildSessionCreatePayload({ + ...BASE, + envType: 'cloud', + files: [ + { fileId: 'file_1', mountPath: '/data/one' }, + { fileId: 'file_2' }, + ], + }) + expect(payload.resources).toEqual([ + { type: 'file', file_id: 'file_1', mount_path: '/data/one' }, + { type: 'file', file_id: 'file_2' }, + ]) + }) + + it('drops file entries missing a fileId', () => { + const payload = buildSessionCreatePayload({ + ...BASE, + envType: 'cloud', + files: [{ fileId: '' }, { fileId: 'file_ok' }], + }) + expect(payload.resources).toEqual([{ type: 'file', file_id: 'file_ok' }]) + }) + + it('emits `metadata` from sessionParameters as opaque tags', () => { + const payload = buildSessionCreatePayload({ + ...BASE, + envType: 'cloud', + sessionParameters: { foo: 'bar', baz: 'qux' }, + }) + expect(payload.metadata).toEqual({ foo: 'bar', baz: 'qux' }) + expect(payload.resources).toBeUndefined() + }) + + it('emits neither resources nor metadata when the caller supplies nothing', () => { + const payload = buildSessionCreatePayload({ ...BASE, envType: 'cloud' }) + expect(payload.resources).toBeUndefined() + expect(payload.metadata).toBeUndefined() + }) + + it('emits both resources[] and metadata when both are set', () => { + const payload = buildSessionCreatePayload({ + ...BASE, + envType: 'cloud', + memoryStoreId: 'memstore_01', + files: [{ fileId: 'file_1' }], + sessionParameters: { env: 'staging' }, + }) + expect(payload.resources).toEqual([ + { type: 'memory_store', memory_store_id: 'memstore_01', access: 'read_write' }, + { type: 'file', file_id: 'file_1' }, + ]) + expect(payload.metadata).toEqual({ env: 'staging' }) + }) +}) + +describe('buildSessionCreatePayload — self-hosted env', () => { + it('folds memory into metadata (never onto resources)', () => { + const payload = buildSessionCreatePayload({ + ...BASE, + envType: 'self_hosted', + memoryStoreId: 'memstore_01', + }) + expect(payload.resources).toBeUndefined() + expect(payload.metadata).toEqual({ + memory_store_ids: 'memstore_01', + memory_access: 'read_write', + }) + }) + + it('honors explicit read_only memory access when folded into metadata', () => { + const payload = buildSessionCreatePayload({ + ...BASE, + envType: 'self_hosted', + memoryStoreId: 'memstore_01', + memoryAccess: 'read_only', + }) + expect(payload.metadata).toEqual({ + memory_store_ids: 'memstore_01', + memory_access: 'read_only', + }) + }) + + it('merges user-supplied sessionParameters with memory metadata', () => { + const payload = buildSessionCreatePayload({ + ...BASE, + envType: 'self_hosted', + memoryStoreId: 'memstore_01', + sessionParameters: { SOURCE_URL: 'https://example/repo.git' }, + }) + expect(payload.metadata).toEqual({ + SOURCE_URL: 'https://example/repo.git', + memory_store_ids: 'memstore_01', + memory_access: 'read_write', + }) + }) + + it('does not overwrite a user-supplied memory_store_ids key', () => { + const payload = buildSessionCreatePayload({ + ...BASE, + envType: 'self_hosted', + memoryStoreId: 'memstore_A', + sessionParameters: { memory_store_ids: 'memstore_B' }, + }) + expect(payload.metadata).toEqual({ memory_store_ids: 'memstore_B' }) + }) + + it('never emits `resources` — even when files are supplied', () => { + const payload = buildSessionCreatePayload({ + ...BASE, + envType: 'self_hosted', + memoryStoreId: 'memstore_01', + files: [{ fileId: 'file_1', mountPath: '/dropped' }], + sessionParameters: { A: '1' }, + }) + expect(payload.resources).toBeUndefined() + }) + + it('omits metadata when nothing memory- or param-related is set', () => { + const payload = buildSessionCreatePayload({ ...BASE, envType: 'self_hosted' }) + expect(payload.metadata).toBeUndefined() + expect(payload.resources).toBeUndefined() + }) +}) + +describe('buildSessionCreatePayload — envType default (no route branch)', () => { + it('defaults to the cloud shape when envType is omitted', () => { + const payload = buildSessionCreatePayload({ + ...BASE, + memoryStoreId: 'memstore_01', + }) + expect(payload.resources).toEqual([ + { type: 'memory_store', memory_store_id: 'memstore_01', access: 'read_write' }, + ]) + expect(payload.metadata).toBeUndefined() + }) +}) diff --git a/apps/sim/lib/managed-agents/session-client.ts b/apps/sim/lib/managed-agents/session-client.ts new file mode 100644 index 00000000000..27accd2ba9a --- /dev/null +++ b/apps/sim/lib/managed-agents/session-client.ts @@ -0,0 +1,303 @@ +/** + * Provider-neutral HTTP client for the Claude Platform Managed Agents API. + * + * Two consumers today: + * 1. `apps/sim/lib/copilot/request/lifecycle/managed-agent-leg.ts` — the copilot + * home-chat integration; layers mothership-envelope translation, Sim tool + * dispatch, and Redis buffering on top of these primitives. + * 2. `apps/sim/tools/managed_agent/run_session.ts` (workflow-block tool) — calls + * these primitives directly and returns the accumulated assistant text as + * the block output. No envelope translation, no Sim-side tool dispatch. + * + * This module intentionally has NO Sim-domain dependencies (no `@sim/db`, no + * copilot buffer, no mothership stream types). It's a thin wrapper around + * `fetch` that speaks the Managed Agents beta. + */ + +import type { + AnthropicAgentCustomToolUseEvent, + AnthropicSessionEvent, +} from '@/lib/copilot/request/lifecycle/event-translator' + +export const ANTHROPIC_API_BASE = 'https://api.anthropic.com' +export const ANTHROPIC_VERSION = '2023-06-01' +export const MANAGED_AGENTS_BETA = 'managed-agents-2026-04-01' + +/** Environment `config.type` per `GET /v1/environments/{id}`. */ +export type ManagedAgentEnvType = 'cloud' | 'self_hosted' + +/** + * Shared inputs on every managed-agents call. `apiKey` is the caller's + * Claude Platform API key (an Anthropic workspace-scoped key); `signal` + * propagates cancellation into the outbound fetch. + */ +export interface SessionAuth { + apiKey: string + signal?: AbortSignal +} + +export interface CreateSessionInput extends SessionAuth { + agentId: string + environmentId: string + /** Optional session title stored on the Anthropic session. */ + title?: string + /** + * OAuth credential vaults the agent's MCP tools can reference. See + * https://platform.claude.com/docs/en/managed-agents/vaults. + */ + vaultIds?: string[] + /** + * Memory-store id (`memstore_...`). Routing depends on `envType`: + * - `cloud`: attached as a `memory_store` session resource + * (`resources: [{ type: 'memory_store', memory_store_id, access }]`). + * - `self_hosted`: forwarded via `metadata.memory_store_ids` so the + * self-hosted agent sandbox can expose it as an env var and + * mount the store on its side. + */ + memoryStoreId?: string + /** + * Access mode on the attached memory store — `read_write` (default) + * pushes changes back on session exit; `read_only` never writes. + * Cloud path: encoded on the resource entry. Self-hosted path: + * forwarded as `metadata.memory_access`. Ignored when `memoryStoreId` + * is unset. + */ + memoryAccess?: 'read_write' | 'read_only' + /** + * File attachments (cloud envs only — the Managed Agents `resources` + * array accepts entries of type `file`). Each mounts a Files-API + * file into the session container. `mountPath` is optional; Anthropic + * picks a default when omitted. Ignored for self-hosted envs. + */ + files?: Array<{ fileId: string; mountPath?: string }> + /** + * Arbitrary session metadata. Wire name: `metadata` (top-level on + * `POST /v1/sessions`). On self-hosted envs the self-hosted agent + * sandbox forwards each key to an env var; on cloud envs the keys + * are stored as opaque tags — safe to send from either block. + */ + sessionParameters?: Record + /** `cloud` or `self_hosted`. Routes memory + resources correctly. */ + envType?: ManagedAgentEnvType +} + +export interface CreateSessionResult { + id: string +} + +/** + * POST /v1/sessions — provisions a session sandbox. Does NOT start work; a + * subsequent `sendUserMessage` (or `sendSessionEvents`) is what causes the + * agent to run. This mirrors the docs' two-step lifecycle: + * https://platform.claude.com/docs/en/managed-agents/sessions#creating-a-session + */ +export async function createSession(input: CreateSessionInput): Promise { + const payload = buildSessionCreatePayload(input) + const resp = await fetch(`${ANTHROPIC_API_BASE}/v1/sessions`, { + method: 'POST', + headers: managedAgentsHeaders(input.apiKey, { json: true }), + body: JSON.stringify(payload), + signal: input.signal, + }) + if (!resp.ok) { + const detail = await resp.text().catch(() => '') + throw new Error(`Anthropic sessions.create failed (${resp.status}): ${detail.slice(0, 400)}`) + } + const body = (await resp.json()) as { id?: unknown } + if (typeof body.id !== 'string' || body.id.length === 0) { + throw new Error('Anthropic sessions.create returned no id') + } + return { id: body.id } +} + +/** + * Builds the request body for `POST /v1/sessions` from the caller's typed + * inputs. Keeps the env-type routing in ONE place so callers can't + * accidentally leak self-hosted-only fields onto a cloud session or vice + * versa. Any future rename of the session-parameters wire field is a + * one-line change here. + */ +export function buildSessionCreatePayload( + input: CreateSessionInput +): Record { + const payload: Record = { + agent: input.agentId, + environment_id: input.environmentId, + } + if (input.title) payload.title = input.title + if (input.vaultIds && input.vaultIds.length > 0) payload.vault_ids = input.vaultIds + + const isSelfHosted = input.envType === 'self_hosted' + const access = input.memoryAccess ?? 'read_write' + + if (isSelfHosted) { + // Self-hosted: memory + user-supplied metadata both live under + // top-level `metadata`. The self-hosted agent sandbox forwards + // keys to env vars. + const metadata: Record = { ...(input.sessionParameters ?? {}) } + if (input.memoryStoreId && !metadata.memory_store_ids) { + metadata.memory_store_ids = input.memoryStoreId + if (!metadata.memory_access) metadata.memory_access = access + } + if (Object.keys(metadata).length > 0) payload.metadata = metadata + // `files` and `resources` are intentionally NOT forwarded for + // self-hosted — the self-hosted agent sandbox owns any resource + // mounting on its side. + } else { + // Cloud: build resources[] (memory + file) and emit user metadata as + // opaque tags. Both are optional. + const resources: Array> = [] + if (input.memoryStoreId) { + resources.push({ + type: 'memory_store', + memory_store_id: input.memoryStoreId, + access, + }) + } + if (input.files && input.files.length > 0) { + for (const file of input.files) { + if (!file.fileId) continue + const entry: Record = { type: 'file', file_id: file.fileId } + if (file.mountPath) entry.mount_path = file.mountPath + resources.push(entry) + } + } + if (resources.length > 0) payload.resources = resources + if (input.sessionParameters && Object.keys(input.sessionParameters).length > 0) { + payload.metadata = { ...input.sessionParameters } + } + } + return payload +} + +interface UserMessageEvent { + type: 'user.message' + content: Array<{ type: 'text'; text: string }> +} + +interface UserCustomToolResultEvent { + type: 'user.custom_tool_result' + custom_tool_use_id: string + content: Array<{ type: 'text'; text: string }> + is_error: boolean +} + +export type OutboundSessionEvent = UserMessageEvent | UserCustomToolResultEvent + +/** POST /v1/sessions/{id}/events with a single `user.message`. */ +export async function sendUserMessage( + input: SessionAuth & { sessionId: string; text: string } +): Promise { + await sendSessionEvents({ + apiKey: input.apiKey, + signal: input.signal, + sessionId: input.sessionId, + events: [{ type: 'user.message', content: [{ type: 'text', text: input.text }] }], + }) +} + +/** Generic events-send used for both `user.message` and `user.custom_tool_result`. */ +export async function sendSessionEvents( + input: SessionAuth & { sessionId: string; events: OutboundSessionEvent[] } +): Promise { + if (input.events.length === 0) return + const resp = await fetch(`${ANTHROPIC_API_BASE}/v1/sessions/${input.sessionId}/events`, { + method: 'POST', + headers: managedAgentsHeaders(input.apiKey, { json: true }), + body: JSON.stringify({ events: input.events }), + signal: input.signal, + }) + if (!resp.ok) { + const detail = await resp.text().catch(() => '') + throw new Error(`Anthropic events.send failed (${resp.status}): ${detail.slice(0, 400)}`) + } +} + +/** GET /v1/sessions/{id}/events/stream — opens the SSE response. */ +export async function openSessionStream( + input: SessionAuth & { sessionId: string } +): Promise { + const resp = await fetch( + `${ANTHROPIC_API_BASE}/v1/sessions/${input.sessionId}/events/stream`, + { + method: 'GET', + headers: managedAgentsHeaders(input.apiKey, { accept: 'text/event-stream' }), + signal: input.signal, + } + ) + if (!resp.ok) { + const detail = await resp.text().catch(() => '') + throw new Error(`Anthropic events.stream failed (${resp.status}): ${detail.slice(0, 400)}`) + } + if (!resp.body) { + throw new Error('Anthropic events.stream returned no body') + } + return resp +} + +const DEFAULT_MAX_EVENTS_PER_CATCHUP = 500 + +/** + * Paginated pull of session events emitted after `afterId`, following + * `has_more` cursors up to `maxEvents` (default 500). Returns events in + * arrival order. Used by callers that want to reconcile after an SSE + * stream closes before a terminal state — the copilot leg's reconnect + * loop is the canonical consumer. + */ +export async function listEventsAfter( + input: SessionAuth & { + sessionId: string + afterId: string | null + maxEvents?: number + } +): Promise { + const collected: AnthropicSessionEvent[] = [] + const maxEvents = input.maxEvents ?? DEFAULT_MAX_EVENTS_PER_CATCHUP + let cursor: string | null = input.afterId + while (collected.length < maxEvents) { + const url = new URL(`${ANTHROPIC_API_BASE}/v1/sessions/${input.sessionId}/events`) + url.searchParams.set('limit', '100') + if (cursor) url.searchParams.set('after_id', cursor) + const resp = await fetch(url.toString(), { + method: 'GET', + headers: managedAgentsHeaders(input.apiKey), + signal: input.signal, + }) + if (!resp.ok) { + const detail = await resp.text().catch(() => '') + throw new Error(`Anthropic events.list failed (${resp.status}): ${detail.slice(0, 400)}`) + } + const body = (await resp.json()) as { + data?: AnthropicSessionEvent[] + has_more?: boolean + last_id?: string + } + const page = Array.isArray(body.data) ? body.data : [] + collected.push(...page) + if (!body.has_more || page.length === 0) break + cursor = body.last_id ?? page[page.length - 1]?.id ?? null + if (!cursor) break + } + return collected +} + +/** + * Standard header set for every Managed Agents call. Kept in one place so + * a beta-version bump (e.g. `managed-agents-2026-05-01`) is a single + * literal change. + */ +function managedAgentsHeaders( + apiKey: string, + options: { json?: boolean; accept?: string } = {} +): Record { + const headers: Record = { + 'x-api-key': apiKey, + 'anthropic-version': ANTHROPIC_VERSION, + 'anthropic-beta': MANAGED_AGENTS_BETA, + } + if (options.json) headers['content-type'] = 'application/json' + if (options.accept) headers.accept = options.accept + return headers +} + +export type { AnthropicSessionEvent, AnthropicAgentCustomToolUseEvent } diff --git a/apps/sim/lib/managed-agents/subblock-options.ts b/apps/sim/lib/managed-agents/subblock-options.ts new file mode 100644 index 00000000000..d753db8a014 --- /dev/null +++ b/apps/sim/lib/managed-agents/subblock-options.ts @@ -0,0 +1,178 @@ +import { requestJson } from '@/lib/api/client/request' +import { + listManagedAgentAgentsContract, + listManagedAgentConnectionsContract, + listManagedAgentEnvironmentsContract, + listManagedAgentMemoryStoresContract, + listManagedAgentVaultsContract, +} from '@/lib/api/contracts' +import { useSubBlockStore } from '@/stores/workflows/subblock/store' +import { useWorkflowRegistry } from '@/stores/workflows/registry/store' + +/** + * fetchOptions helpers for the Managed Agent workflow block's dropdowns. + * + * The subblock system passes only `blockId` to `fetchOptions`; each helper + * reads the sibling subblock values it depends on from the subblock store + * (`connection` → agents / environments / vaults). Same pattern the Agent + * block uses for its `reasoningEffort` / `verbosity` fetchers. + * + * All requests go through the workspace-scoped proxy routes, which decrypt + * the stored API key server-side and hit Claude Platform on our behalf — + * the API key never touches the browser. + */ + +interface SubBlockOption { + label: string + id: string +} + +function activeWorkspaceId(): string | null { + return useWorkflowRegistry.getState().hydration.workspaceId ?? null +} + +function connectionIdForBlock(blockId: string): string | null { + const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId + if (!activeWorkflowId) return null + const workflowValues = useSubBlockStore.getState().workflowValues[activeWorkflowId] + const value = workflowValues?.[blockId]?.connection + return typeof value === 'string' && value.length > 0 ? value : null +} + +/** List Managed Agent connections in the current workspace. */ +export async function fetchManagedAgentConnectionOptions(): Promise { + const workspaceId = activeWorkspaceId() + if (!workspaceId) return [] + try { + const { data } = await requestJson(listManagedAgentConnectionsContract, { + query: { workspaceId }, + }) + return data.map((row) => ({ id: row.id, label: row.name || row.id })) + } catch { + return [] + } +} + +/** List agents inside the connection selected on `blockId`. */ +export async function fetchManagedAgentAgentOptions(blockId: string): Promise { + const workspaceId = activeWorkspaceId() + const connectionId = connectionIdForBlock(blockId) + if (!workspaceId || !connectionId) return [] + try { + const { data } = await requestJson(listManagedAgentAgentsContract, { + params: { id: connectionId }, + query: { workspaceId }, + }) + return data.map((row) => ({ + id: row.id, + label: row.name ? `${row.name} (${row.id})` : row.id, + })) + } catch { + return [] + } +} + +/** + * List environments inside the connection selected on `blockId`. Options + * carry the config type in the label (e.g. "prod (self-hosted)") so users + * can tell which env types they're picking without a second lookup. + */ +export async function fetchManagedAgentEnvironmentOptions( + blockId: string, + filterType?: 'cloud' | 'self_hosted' +): Promise { + const workspaceId = activeWorkspaceId() + const connectionId = connectionIdForBlock(blockId) + if (!workspaceId || !connectionId) return [] + try { + const { data } = await requestJson(listManagedAgentEnvironmentsContract, { + params: { id: connectionId }, + query: { workspaceId }, + }) + return data + .filter((row) => (filterType ? row.envType === filterType : true)) + .map((row) => ({ id: row.id, label: row.name ? `${row.name} (${row.id})` : row.id })) + } catch { + return [] + } +} + +/** + * Cloud-only environment picker. Bound directly to a block's + * `fetchOptions` (no wrapping) so the block signature matches the + * subblock system's expected `(blockId: string) => Promise`. + */ +export function fetchManagedAgentCloudEnvironmentOptions( + blockId: string +): Promise { + return fetchManagedAgentEnvironmentOptions(blockId, 'cloud') +} + +/** Self-hosted-only environment picker. */ +export function fetchManagedAgentSelfHostedEnvironmentOptions( + blockId: string +): Promise { + return fetchManagedAgentEnvironmentOptions(blockId, 'self_hosted') +} + +/** + * Fetches the environment's raw config for the block's `environmentType` + * companion field. Called after the user picks an environment so the + * `sessionParameters` subblock's `condition: environmentType==='self_hosted'` + * can gate correctly without a follow-up server round-trip at execute time. + */ +export async function fetchManagedAgentEnvironmentType( + blockId: string, + environmentId: string +): Promise<'cloud' | 'self_hosted' | null> { + const workspaceId = activeWorkspaceId() + const connectionId = connectionIdForBlock(blockId) + if (!workspaceId || !connectionId || !environmentId) return null + try { + const { data } = await requestJson(listManagedAgentEnvironmentsContract, { + params: { id: connectionId }, + query: { workspaceId }, + }) + const match = data.find((row) => row.id === environmentId) + return match?.envType ?? null + } catch { + return null + } +} + +/** List vaults for the connection selected on `blockId`. */ +export async function fetchManagedAgentVaultOptions(blockId: string): Promise { + const workspaceId = activeWorkspaceId() + const connectionId = connectionIdForBlock(blockId) + if (!workspaceId || !connectionId) return [] + try { + const { data } = await requestJson(listManagedAgentVaultsContract, { + params: { id: connectionId }, + query: { workspaceId }, + }) + return data.map((row) => ({ id: row.id, label: row.name || row.id })) + } catch { + return [] + } +} + +/** List memory stores for the connection selected on `blockId`. */ +export async function fetchManagedAgentMemoryStoreOptions( + blockId: string +): Promise { + const workspaceId = activeWorkspaceId() + const connectionId = connectionIdForBlock(blockId) + if (!workspaceId || !connectionId) return [] + try { + const { data } = await requestJson(listManagedAgentMemoryStoresContract, { + params: { id: connectionId }, + query: { workspaceId }, + }) + return data.map((row) => ({ + id: row.id, + label: row.name ? `${row.name} (${row.id})` : row.id, + })) + } catch { + return [] + } +} diff --git a/apps/sim/lib/workflows/autolayout/utils.ts b/apps/sim/lib/workflows/autolayout/utils.ts index cd8fc3ada11..370fc143b62 100644 --- a/apps/sim/lib/workflows/autolayout/utils.ts +++ b/apps/sim/lib/workflows/autolayout/utils.ts @@ -236,6 +236,7 @@ function estimateWorkflowBlockDimensions(block: BlockState): { width: number; he block.type === 'router_v2' ? getRouterRows(block.id, block.subBlocks?.routes?.value).length : 0, + nodeWidth: blockConfig.nodeWidth, }) } diff --git a/apps/sim/lib/workflows/blocks/deterministic-dimensions.ts b/apps/sim/lib/workflows/blocks/deterministic-dimensions.ts index b3d8145e91b..52b57b5ecae 100644 --- a/apps/sim/lib/workflows/blocks/deterministic-dimensions.ts +++ b/apps/sim/lib/workflows/blocks/deterministic-dimensions.ts @@ -8,6 +8,8 @@ interface WorkflowBlockDimensionsInput { visibleSubBlockCount: number conditionRowCount?: number routerRowCount?: number + /** Optional per-block width override from `BlockConfig.nodeWidth`. */ + nodeWidth?: number } export function calculateWorkflowBlockDimensions({ @@ -17,6 +19,7 @@ export function calculateWorkflowBlockDimensions({ visibleSubBlockCount, conditionRowCount = 0, routerRowCount = 0, + nodeWidth, }: WorkflowBlockDimensionsInput): { width: number; height: number } { const shouldShowDefaultHandles = category !== 'triggers' && blockType !== 'starter' && !displayTriggerMode @@ -41,7 +44,7 @@ export function calculateWorkflowBlockDimensions({ ) return { - width: BLOCK_DIMENSIONS.FIXED_WIDTH, + width: nodeWidth ?? BLOCK_DIMENSIONS.FIXED_WIDTH, height, } } diff --git a/apps/sim/tools/index.ts b/apps/sim/tools/index.ts index f4b9c3fc241..b3c8a4f889f 100644 --- a/apps/sim/tools/index.ts +++ b/apps/sim/tools/index.ts @@ -1,3 +1,8 @@ +// Server-only side-effect: registers the Managed Agent tool's actual +// execution on `globalThis`. Kept out of the client-safe `tools/registry.ts` +// so `@sim/db` / postgres / node builtins don't get pulled into the browser +// bundle. See `apps/sim/tools/managed_agent/run_session.ts` for the design. +import '@/tools/managed_agent/run_session.server' import { createLogger } from '@sim/logger' import { getErrorMessage, toError } from '@sim/utils/errors' import { sleep } from '@sim/utils/helpers' diff --git a/apps/sim/tools/managed_agent/index.ts b/apps/sim/tools/managed_agent/index.ts new file mode 100644 index 00000000000..321e5cd977f --- /dev/null +++ b/apps/sim/tools/managed_agent/index.ts @@ -0,0 +1,2 @@ +export { managedAgentRunSessionTool } from './run_session' +export type { ManagedAgentRunSessionOutput, ManagedAgentRunSessionParams } from './types' diff --git a/apps/sim/tools/managed_agent/normalizers.test.ts b/apps/sim/tools/managed_agent/normalizers.test.ts new file mode 100644 index 00000000000..aca200de8f9 --- /dev/null +++ b/apps/sim/tools/managed_agent/normalizers.test.ts @@ -0,0 +1,206 @@ +/** + * @vitest-environment node + */ +import { describe, expect, it } from 'vitest' +import { + normalizeEnvType, + normalizeFiles, + normalizeMemoryAccess, + normalizeSessionParameters, + normalizeStringList, +} from '@/tools/managed_agent/normalizers' + +describe('normalizeEnvType', () => { + it('passes through cloud and self_hosted', () => { + expect(normalizeEnvType('cloud')).toBe('cloud') + expect(normalizeEnvType('self_hosted')).toBe('self_hosted') + }) + + it('returns undefined for anything else', () => { + expect(normalizeEnvType(undefined)).toBeUndefined() + expect(normalizeEnvType('')).toBeUndefined() + expect(normalizeEnvType('Cloud')).toBeUndefined() + expect(normalizeEnvType('other')).toBeUndefined() + }) +}) + +describe('normalizeMemoryAccess', () => { + it('passes through the two supported modes', () => { + expect(normalizeMemoryAccess('read_write')).toBe('read_write') + expect(normalizeMemoryAccess('read_only')).toBe('read_only') + }) + + it('returns undefined for anything else', () => { + expect(normalizeMemoryAccess(undefined)).toBeUndefined() + expect(normalizeMemoryAccess('')).toBeUndefined() + expect(normalizeMemoryAccess('write')).toBeUndefined() + expect(normalizeMemoryAccess('READ_WRITE')).toBeUndefined() + }) +}) + +describe('normalizeStringList', () => { + it('returns empty when the value is undefined / null / non-string non-array', () => { + expect(normalizeStringList(undefined)).toEqual([]) + expect(normalizeStringList(null)).toEqual([]) + expect(normalizeStringList(42)).toEqual([]) + expect(normalizeStringList({})).toEqual([]) + expect(normalizeStringList(true)).toEqual([]) + }) + + it('preserves array-of-string input and trims each entry', () => { + expect(normalizeStringList(['a', ' b ', 'c'])).toEqual(['a', 'b', 'c']) + }) + + it('drops non-string and blank entries from an array', () => { + expect(normalizeStringList(['a', '', ' ', null, 5, 'b'])).toEqual(['a', 'b']) + }) + + it('parses a JSON-encoded array string', () => { + expect(normalizeStringList('["vlt_1","vlt_2"]')).toEqual(['vlt_1', 'vlt_2']) + }) + + it('falls back to comma-split when the JSON array parse fails', () => { + // The leading `[` triggers the JSON attempt; broken JSON should not + // then silently vanish — the raw comma-split path is preferred. + // In practice broken JSON is not comma-separated, so the string is + // returned as a single entry after the trim. + expect(normalizeStringList('[not json')).toEqual(['[not json']) + }) + + it('splits comma-separated strings and trims each item', () => { + expect(normalizeStringList('vlt_1, vlt_2 ,vlt_3')).toEqual(['vlt_1', 'vlt_2', 'vlt_3']) + }) + + it('accepts a single-value string', () => { + expect(normalizeStringList('vlt_only')).toEqual(['vlt_only']) + }) + + it('returns empty for a whitespace-only string', () => { + expect(normalizeStringList(' ')).toEqual([]) + }) +}) + +describe('normalizeFiles', () => { + it('returns empty for non-array input', () => { + expect(normalizeFiles(undefined)).toEqual([]) + expect(normalizeFiles(null)).toEqual([]) + expect(normalizeFiles('file_id')).toEqual([]) + expect(normalizeFiles({})).toEqual([]) + }) + + it('accepts the flat {fileId, mountPath?} shape', () => { + expect( + normalizeFiles([ + { fileId: 'file_1', mountPath: '/data/one' }, + { fileId: 'file_2' }, + ]) + ).toEqual([ + { fileId: 'file_1', mountPath: '/data/one' }, + { fileId: 'file_2' }, + ]) + }) + + it('accepts the table-subblock {cells: {Key, Value}} shape', () => { + expect( + normalizeFiles([ + { id: 'r1', cells: { Key: 'file_1', Value: '/data/one' } }, + { id: 'r2', cells: { Key: 'file_2', Value: '' } }, + ]) + ).toEqual([ + { fileId: 'file_1', mountPath: '/data/one' }, + { fileId: 'file_2' }, + ]) + }) + + it('drops rows with no file id and rows that are not objects', () => { + expect( + normalizeFiles([ + { fileId: '' }, + { cells: { Key: ' ' } }, + null, + undefined, + 'garbage', + { cells: {} }, + { fileId: 'file_ok' }, + ]) + ).toEqual([{ fileId: 'file_ok' }]) + }) + + it('omits mountPath when it is blank / whitespace-only', () => { + expect( + normalizeFiles([ + { fileId: 'file_1', mountPath: ' ' }, + { fileId: 'file_2', mountPath: '/mount' }, + ]) + ).toEqual([{ fileId: 'file_1' }, { fileId: 'file_2', mountPath: '/mount' }]) + }) +}) + +describe('normalizeSessionParameters', () => { + it('returns undefined for empty / non-object / non-string values', () => { + expect(normalizeSessionParameters(undefined)).toBeUndefined() + expect(normalizeSessionParameters(null)).toBeUndefined() + expect(normalizeSessionParameters(42)).toBeUndefined() + expect(normalizeSessionParameters(true)).toBeUndefined() + expect(normalizeSessionParameters('')).toBeUndefined() + expect(normalizeSessionParameters(' ')).toBeUndefined() + }) + + it('collapses the table-row shape into a flat Record', () => { + // This is the shape the workflow `table` subblock stores — the bug the + // metadata: {} on the wire regression was caused by NOT handling this. + const rows = [ + { id: 'r1', cells: { Key: 'SOURCE_TYPE', Value: 'git' } }, + { id: 'r2', cells: { Key: 'SOURCE_URL', Value: 'https://example/repo.git' } }, + { id: 'r3', cells: { Key: 'DEST_DIR', Value: 'repo' } }, + ] + expect(normalizeSessionParameters(rows)).toEqual({ + SOURCE_TYPE: 'git', + SOURCE_URL: 'https://example/repo.git', + DEST_DIR: 'repo', + }) + }) + + it('accepts a JSON-encoded array-of-rows string', () => { + const encoded = JSON.stringify([ + { cells: { Key: 'K1', Value: 'V1' } }, + { cells: { Key: 'K2', Value: 'V2' } }, + ]) + expect(normalizeSessionParameters(encoded)).toEqual({ K1: 'V1', K2: 'V2' }) + }) + + it('accepts the flat Record shape', () => { + expect(normalizeSessionParameters({ A: '1', B: '2' })).toEqual({ A: '1', B: '2' }) + }) + + it('drops rows with a blank key', () => { + const rows = [ + { cells: { Key: '', Value: 'ignored' } }, + { cells: { Key: ' ', Value: 'also-ignored' } }, + { cells: { Key: 'KEEP', Value: 'yes' } }, + ] + expect(normalizeSessionParameters(rows)).toEqual({ KEEP: 'yes' }) + }) + + it('coerces non-string values to empty string but preserves the key', () => { + const rows = [ + { cells: { Key: 'A', Value: 'str' } }, + { cells: { Key: 'B', Value: 42 } }, + { cells: { Key: 'C', Value: null } }, + ] + expect(normalizeSessionParameters(rows)).toEqual({ A: 'str', B: '', C: '' }) + }) + + it('returns undefined when every row is dropped', () => { + expect( + normalizeSessionParameters([{ cells: { Key: '', Value: 'x' } }, { cells: {} }]) + ).toBeUndefined() + }) + + it('returns undefined for a non-parseable JSON string', () => { + // Non-`[`-prefixed strings are not parsed and not comma-split for + // metadata (unlike vault lists) — they cannot form a valid k/v map. + expect(normalizeSessionParameters('not json')).toBeUndefined() + expect(normalizeSessionParameters('[broken')).toBeUndefined() + }) +}) diff --git a/apps/sim/tools/managed_agent/normalizers.ts b/apps/sim/tools/managed_agent/normalizers.ts new file mode 100644 index 00000000000..d9eea549c17 --- /dev/null +++ b/apps/sim/tools/managed_agent/normalizers.ts @@ -0,0 +1,160 @@ +/** + * Pure normalization helpers used by the Managed Agent workflow-block tool + * to shape user input from Sim's block subblocks (which store their values + * in several different runtime shapes) into the tidy typed values the + * Anthropic session-client expects. + * + * Kept in a client-safe module (no `import 'server-only'`, no DB, no `env`) + * so the tests can import each helper directly without loading the whole + * session runtime and its side-effect registration. + */ + +import type { ManagedAgentEnvType } from '@/lib/managed-agents/session-client' + +export function normalizeEnvType( + value: string | undefined +): ManagedAgentEnvType | undefined { + if (value === 'cloud' || value === 'self_hosted') return value + return undefined +} + +export function normalizeMemoryAccess( + value: string | undefined +): 'read_write' | 'read_only' | undefined { + if (value === 'read_write' || value === 'read_only') return value + return undefined +} + +/** + * Coerces the block's file table (a JSON array of `{fileId, mountPath?}` + * or the raw table-subblock shape `Array<{Key: string, Value: string}>`) + * into the tidy shape the session-client expects. Silently drops rows + * missing a file id. + */ +export function normalizeFiles( + value: unknown +): Array<{ fileId: string; mountPath?: string }> { + if (!Array.isArray(value)) return [] + const out: Array<{ fileId: string; mountPath?: string }> = [] + for (const raw of value) { + if (!raw || typeof raw !== 'object') continue + const record = raw as Record + const cells = + record.cells && typeof record.cells === 'object' + ? (record.cells as Record) + : record + const fileId = + typeof cells.fileId === 'string' + ? cells.fileId + : typeof cells.Key === 'string' + ? cells.Key + : '' + if (!fileId.trim()) continue + const mountPath = + typeof cells.mountPath === 'string' + ? cells.mountPath + : typeof cells.Value === 'string' + ? cells.Value + : undefined + out.push({ + fileId: fileId.trim(), + ...(mountPath && mountPath.trim() ? { mountPath: mountPath.trim() } : {}), + }) + } + return out +} + +/** + * Coerce whatever shape the multi-select combobox / json input ends up + * with — an array, a JSON-encoded array string, a comma-separated string, + * or a single string — into a trimmed `string[]`. Handles the case where + * a `type: 'json'` input isn't parsed for us (fresh workflows, stale + * serialization, block hydration). + */ +export function normalizeStringList(value: unknown): string[] { + if (Array.isArray(value)) { + return value + .filter((v): v is string => typeof v === 'string' && v.trim().length > 0) + .map((v) => v.trim()) + } + if (typeof value !== 'string') return [] + const trimmed = value.trim() + if (!trimmed) return [] + if (trimmed.startsWith('[')) { + try { + const parsed = JSON.parse(trimmed) + if (Array.isArray(parsed)) { + return parsed + .filter((v): v is string => typeof v === 'string' && v.trim().length > 0) + .map((v) => v.trim()) + } + } catch { + // fall through to comma-split + } + } + return trimmed + .split(',') + .map((v) => v.trim()) + .filter((v) => v.length > 0) +} + +/** + * Coerce the block's session-parameters value into a `Record` + * ready for the Anthropic session-create `metadata` field. + * + * The `table` subblock stores rows as `WorkflowTableRow[]` — an array of + * `{ id, cells: { Key: string, Value: string } }` — not a flat object. It + * may also arrive as a JSON-encoded array string (fresh workflows, some + * hydration paths) or the flat `Record` shape (older + * serializations). Handle all three; drop rows/pairs with a blank key. + */ +export function normalizeSessionParameters( + value: unknown +): Record | undefined { + const rows = coerceToRows(value) + if (rows === undefined) return undefined + const out: Record = {} + for (const row of rows) { + const key = typeof row.key === 'string' ? row.key.trim() : '' + if (!key) continue + const val = typeof row.value === 'string' ? row.value : '' + out[key] = val + } + return Object.keys(out).length > 0 ? out : undefined +} + +function coerceToRows( + value: unknown +): Array<{ key: unknown; value: unknown }> | undefined { + if (Array.isArray(value)) { + return value.map((row) => tableRowToPair(row)) + } + if (typeof value === 'string') { + const trimmed = value.trim() + if (!trimmed || !trimmed.startsWith('[')) return undefined + try { + const parsed = JSON.parse(trimmed) + if (Array.isArray(parsed)) return parsed.map((row) => tableRowToPair(row)) + } catch { + return undefined + } + return undefined + } + if (value && typeof value === 'object') { + return Object.entries(value as Record).map(([key, val]) => ({ + key, + value: val, + })) + } + return undefined +} + +function tableRowToPair(row: unknown): { key: unknown; value: unknown } { + if (!row || typeof row !== 'object') return { key: undefined, value: undefined } + const record = row as Record + const cells = + record.cells && typeof record.cells === 'object' + ? (record.cells as Record) + : record + return { key: cells.Key ?? cells.key, value: cells.Value ?? cells.value } +} diff --git a/apps/sim/tools/managed_agent/run_session.server.ts b/apps/sim/tools/managed_agent/run_session.server.ts new file mode 100644 index 00000000000..0d959b51222 --- /dev/null +++ b/apps/sim/tools/managed_agent/run_session.server.ts @@ -0,0 +1,393 @@ +import 'server-only' + +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { readSSEEvents } from '@/lib/core/utils/sse' +import { getDecryptedApiKey } from '@/lib/managed-agents/connections' +import { env } from '@/lib/core/config/env' +import { + type AnthropicSessionEvent, + buildSessionCreatePayload, + createSession, + listEventsAfter, + openSessionStream, + sendSessionEvents, + sendUserMessage, +} from '@/lib/managed-agents/session-client' +import { + normalizeEnvType, + normalizeFiles, + normalizeMemoryAccess, + normalizeSessionParameters, + normalizeStringList, +} from '@/tools/managed_agent/normalizers' +import { + registerManagedAgentServerImpl, + type ManagedAgentServerImpl, +} from '@/tools/managed_agent/run_session' +import type { + ManagedAgentRunSessionOutput, + ManagedAgentRunSessionParams, +} from '@/tools/managed_agent/types' +import type { ToolResponse } from '@/tools/types' + +/** + * Server-only execution for the Managed Agent workflow-block tool. + * + * `import 'server-only'` fails the build if anything in the client + * graph reaches this file — matching the intent. The client tool + * skeleton (`./run_session.ts`) uses a `globalThis` lookup to reach + * this impl at runtime, so Turbopack finds no cross-boundary edge. + */ + +const logger = createLogger('ManagedAgentRunSessionServer') + +/** Upper bound on stream-close → catch-up → reopen cycles per invocation. */ +const MAX_RECONNECT_ITERATIONS = 8 + +const impl: ManagedAgentServerImpl = async ( + params: ManagedAgentRunSessionParams +): Promise => { + const startedAt = new Date() + const context = params._context + const workspaceId = context?.workspaceId + if (!workspaceId) { + return { + success: false, + output: {}, + error: 'Missing workspaceId in tool context — is this workflow tied to a workspace?', + } + } + + const apiKey = await getDecryptedApiKey({ id: params.connection, workspaceId }) + if (!apiKey) { + return { + success: false, + output: {}, + error: 'Managed Agent connection not found. Reconnect the Claude workspace and retry.', + } + } + + if (isPayloadDebugEnabled()) { + logger.info('Managed agent tool raw params (pre-normalization)', { + workspaceId, + vaultsType: Array.isArray(params.vaults) ? 'array' : typeof params.vaults, + vaultsValue: params.vaults, + memoryStoreIdValue: params.memoryStoreId, + filesType: Array.isArray(params.files) ? 'array' : typeof params.files, + environmentType: params.environmentType, + }) + } + + const envType = normalizeEnvType(params.environmentType) + const userMessage = (params.userMessage ?? '').trim() + if (!userMessage) { + return { success: false, output: {}, error: 'User message is required.' } + } + + const vaultIds = normalizeStringList(params.vaults) + const memoryStoreId = params.memoryStoreId?.trim() || undefined + const memoryAccess = normalizeMemoryAccess(params.memoryAccess) + const files = normalizeFiles(params.files) + const sessionParameters = normalizeSessionParameters(params.sessionParameters) + + const createSessionInput = { + apiKey, + agentId: params.agent, + environmentId: params.environment, + envType, + ...(vaultIds.length > 0 ? { vaultIds } : {}), + ...(memoryStoreId ? { memoryStoreId } : {}), + ...(memoryStoreId && memoryAccess ? { memoryAccess } : {}), + ...(files.length > 0 ? { files } : {}), + ...(sessionParameters && Object.keys(sessionParameters).length > 0 + ? { sessionParameters } + : {}), + } + + // Opt-in payload dump for debugging session-create request shape. + // Enable with `MANAGED_AGENT_DEBUG_PAYLOAD=1` in `.env` — the log line + // does NOT include the api key (buildSessionCreatePayload strips it). + if (isPayloadDebugEnabled()) { + logger.info('Managed agent session-create payload', { + workspaceId, + agentId: params.agent, + environmentId: params.environment, + envType, + payload: buildSessionCreatePayload(createSessionInput), + }) + } + + let sessionId: string + try { + const session = await createSession(createSessionInput) + sessionId = session.id + } catch (error) { + return { + success: false, + output: {}, + error: getErrorMessage(error, 'Failed to create Managed Agent session'), + } + } + + logger.info('Created managed agent session for workflow block', { + workspaceId, + workflowId: context?.workflowId, + sessionId, + agentId: params.agent, + environmentId: params.environment, + envType, + }) + + try { + await sendUserMessage({ apiKey, sessionId, text: userMessage }) + } catch (error) { + return { + success: false, + output: { sessionId }, + error: getErrorMessage(error, 'Failed to send user message'), + } + } + + const assistantText = { value: '' } + const seenIds = new Set() + const eventState: EventState = { + lastCustomToolResponseAt: 0, + lastRequiresActionAt: 0, + } + let lastEventId: string | null = null + let terminal: { status: 'complete' | 'error'; reason?: string } | null = null + + try { + for ( + let iteration = 0; + iteration < MAX_RECONNECT_ITERATIONS && !terminal; + iteration++ + ) { + const streamResp = await openSessionStream({ apiKey, sessionId }) + await readSSEEvents(streamResp, { + onParseError: (raw, err) => { + logger.warn('Un-parseable SSE line', { + sessionId, + preview: raw.slice(0, 200), + error: getErrorMessage(err), + }) + }, + onEvent: async (event) => { + const eventId = (event as { id?: string }).id + if (eventId && seenIds.has(eventId)) return undefined + if (eventId) { + seenIds.add(eventId) + lastEventId = eventId + } + const outcome = await handleEvent({ + event, + assistantText, + apiKey, + sessionId, + eventState, + }) + if (outcome) { + terminal = outcome + return true + } + return undefined + }, + }) + + if (terminal) break + + const missed = await listEventsAfter({ apiKey, sessionId, afterId: lastEventId }) + if (missed.length === 0) { + // Session went idle with no new events to catch up on. Three cases: + // 1. `requires_action` + we replied to the `custom_tool_use` → + // the agent is chewing on our reply; reconnect once more. + // Reset the marker so we cap this at one retry per cycle. + // 2. `requires_action` + we did NOT reply (typically a stuck + // server-side / MCP tool the agent couldn't complete) → + // surface a clear terminal error instead of silently + // succeeding with empty text. + // 3. No pending action → clean completion (usually already + // hit via `end_turn` earlier; this is the fallback path). + if (eventState.lastRequiresActionAt > 0) { + if (eventState.lastCustomToolResponseAt >= eventState.lastRequiresActionAt) { + eventState.lastRequiresActionAt = 0 + continue + } + terminal = { + status: 'error', + reason: + 'Session paused (requires_action) — the agent invoked a tool that could not complete. Check MCP server / vault configuration on Claude Platform.', + } + break + } + terminal = { status: 'complete' } + break + } + for (const event of missed) { + const eventId = (event as { id?: string }).id + if (eventId && seenIds.has(eventId)) continue + if (eventId) { + seenIds.add(eventId) + lastEventId = eventId + } + const outcome = await handleEvent({ + event, + assistantText, + apiKey, + sessionId, + eventState, + }) + if (outcome) { + terminal = outcome + break + } + } + } + } catch (error) { + logger.error('Managed agent stream failed', { sessionId, error: getErrorMessage(error) }) + return { + success: false, + output: { sessionId, content: assistantText.value }, + error: getErrorMessage(error, 'Managed Agent session failed'), + } + } + + const endedAt = new Date() + const timing = { + startTime: startedAt.toISOString(), + endTime: endedAt.toISOString(), + duration: endedAt.getTime() - startedAt.getTime(), + } + + if (!terminal || terminal.status === 'error') { + return { + success: false, + output: { sessionId, content: assistantText.value } satisfies ManagedAgentRunSessionOutput, + error: terminal?.reason ?? 'Reconnect iteration cap reached without a terminal state.', + timing, + } + } + + return { + success: true, + output: { sessionId, content: assistantText.value } satisfies ManagedAgentRunSessionOutput, + timing, + } +} + +/** Side-effect: bind the impl into the process at import time. */ +registerManagedAgentServerImpl(impl) + +/** + * Tracks how far along the session is in resolving a `requires_action` + * pause. If we replied to the pending `custom_tool_use` before the idle + * event fires, the session will unpause on its own — we must NOT treat + * the idle-with-requires-action as terminal in that case. + */ +interface EventState { + lastCustomToolResponseAt: number + lastRequiresActionAt: number +} + +async function handleEvent(args: { + event: AnthropicSessionEvent + assistantText: { value: string } + apiKey: string + sessionId: string + eventState: EventState +}): Promise<{ status: 'complete' | 'error'; reason?: string } | null> { + const { event, assistantText, apiKey, sessionId, eventState } = args + const type = (event as { type?: string }).type + + if (type === 'agent.message') { + const content = (event as { content?: Array<{ type: string; text?: string }> }).content + if (Array.isArray(content)) { + for (const block of content) { + if (block?.type === 'text' && typeof block.text === 'string') { + assistantText.value += block.text + } + } + } + return null + } + + if (type === 'agent.custom_tool_use') { + const toolEvent = event as { id: string; name?: string } + logger.warn( + `Managed Agent invoked a custom tool "${toolEvent.name ?? ''}" that Sim does not provide — replying with error` + ) + try { + await sendSessionEvents({ + apiKey, + sessionId, + events: [ + { + type: 'user.custom_tool_result', + custom_tool_use_id: toolEvent.id, + content: [ + { + type: 'text', + text: 'This Managed Agent is being invoked from a Sim workflow block. Sim does not provide custom tools here — configure the agent to use only tools available in its Claude Platform workspace.', + }, + ], + is_error: true, + }, + ], + }) + eventState.lastCustomToolResponseAt = Date.now() + } catch (err) { + logger.error('Failed to send custom_tool_result error reply', { + sessionId, + error: getErrorMessage(err), + }) + } + return null + } + + if (type === 'session.status_terminated') { + const message = + (event as { error?: { message?: string } }).error?.message ?? 'session_terminated' + return { status: 'error', reason: message } + } + + if (type === 'session.status_idle') { + const stop = (event as { stop_reason?: { type?: string } }).stop_reason?.type + if (stop === 'end_turn') return { status: 'complete' } + if (stop === 'retries_exhausted') return { status: 'error', reason: 'retries_exhausted' } + if (stop === 'requires_action') { + // Session paused for a pending action. If we've already replied to a + // `custom_tool_use` since the last pause, the session will resume on + // its own — return null so the reconnect loop reopens the stream and + // picks up the follow-on events. + eventState.lastRequiresActionAt = Date.now() + if (eventState.lastCustomToolResponseAt >= eventState.lastRequiresActionAt) { + return null + } + // No reply was sent — likely a server-side tool (MCP) call the agent + // itself will drive, or a genuinely stuck session. Return null too + // so the reconnect loop reopens the stream; if the session is truly + // stuck, `listEventsAfter` returning empty on the next iteration will + // finalize the run as complete with whatever text we have. + return null + } + return { status: 'error', reason: stop ?? 'idle_without_stop_reason' } + } + + if (type === 'session.error') { + const message = + (event as { error?: { message?: string } }).error?.message ?? + (event as { message?: string }).message ?? + 'session_error' + return { status: 'error', reason: message } + } + + return null +} + +function isPayloadDebugEnabled(): boolean { + const raw = env.MANAGED_AGENT_DEBUG_PAYLOAD + if (raw === undefined || raw === null) return false + const normalized = String(raw).toLowerCase() + return normalized === '1' || normalized === 'true' || normalized === 'yes' +} diff --git a/apps/sim/tools/managed_agent/run_session.ts b/apps/sim/tools/managed_agent/run_session.ts new file mode 100644 index 00000000000..ebfc73471da --- /dev/null +++ b/apps/sim/tools/managed_agent/run_session.ts @@ -0,0 +1,147 @@ +import type { + ManagedAgentRunSessionOutput, + ManagedAgentRunSessionParams, +} from '@/tools/managed_agent/types' +import type { ToolConfig, ToolResponse } from '@/tools/types' + +/** + * Client-safe half of the Managed Agent tool. + * + * The Sim tools registry is walked from client-side code (schema + * inspection, block editor UI). This module MUST NOT statically import + * any server-only code (`@sim/db`, encryption, drizzle, our + * `session-client` HTTP module). Even a lazy `import()` statically ties + * this module to the server graph and Turbopack transitively pulls + * `postgres` → `fs`/`net`/`tls` into the client bundle, which fails. + * + * Instead, the actual execution lives in `./run_session.server.ts` + * (which imports `@sim/db` freely). At server boot that module + * self-registers a `ManagedAgentServerImpl` via + * `registerManagedAgentServerImpl()` below. `directExecution` looks up + * the registered impl on `globalThis` at call time — Turbopack sees no + * cross-boundary edge. + * + * On the client the impl is never registered, so `directExecution` + * returns a clear error. In practice the tool is never invoked from the + * browser, so that branch is defensive only. + */ + +export type ManagedAgentServerImpl = ( + params: ManagedAgentRunSessionParams +) => Promise + +const GLOBAL_KEY = '__simManagedAgentServerImpl' as const +type ImplHolder = { [GLOBAL_KEY]?: ManagedAgentServerImpl } + +/** + * Registered from `apps/sim/tools/managed_agent/server.ts` (server-only) + * at server boot via a side-effect import in a server file. See + * `apps/sim/tools/managed_agent/register-server.ts`. + */ +export function registerManagedAgentServerImpl(impl: ManagedAgentServerImpl): void { + ;(globalThis as unknown as ImplHolder)[GLOBAL_KEY] = impl +} + +function getServerImpl(): ManagedAgentServerImpl | undefined { + return (globalThis as unknown as ImplHolder)[GLOBAL_KEY] +} + +export const managedAgentRunSessionTool: ToolConfig< + ManagedAgentRunSessionParams, + ToolResponse +> = { + id: 'managed_agent_run_session', + name: 'Managed Agent Run Session', + description: + 'Open a Claude Platform Managed Agent session and return the assistant response as text.', + version: '1.0.0', + + params: { + connection: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'ID of the linked Claude Platform workspace (Managed Agent connection).', + }, + agent: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Managed-agent id inside the selected workspace.', + }, + environment: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Environment id inside the selected workspace.', + }, + environmentType: { + type: 'string', + required: false, + visibility: 'user-only', + description: "Environment config type — 'cloud' or 'self_hosted'.", + }, + vaults: { + type: 'array', + required: false, + visibility: 'user-only', + description: 'Zero or more vault ids for MCP tool auth.', + }, + memoryStoreId: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Optional Agent Memory Store id.', + }, + memoryAccess: { + type: 'string', + required: false, + visibility: 'user-only', + description: "Access mode: 'read_write' (default) or 'read_only'.", + }, + files: { + type: 'array', + required: false, + visibility: 'user-only', + description: + 'File attachments (cloud envs only). Array of `{fileId, mountPath?}` — Anthropic mounts each into the session container.', + }, + sessionParameters: { + type: 'object', + required: false, + visibility: 'user-only', + description: + 'Session metadata (top-level `metadata` on the wire). On self-hosted envs the self-hosted agent sandbox exposes each key as an env var; on cloud envs metadata is stored as opaque tags.', + }, + userMessage: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The user message to send to the Managed Agent.', + }, + }, + + directExecution: async (params: ManagedAgentRunSessionParams): Promise => { + const impl = getServerImpl() + if (!impl) { + return { + success: false, + output: {} satisfies Partial, + error: + 'Managed Agent server impl is not registered. This tool must be invoked from a Sim workflow execution on the server.', + } + } + return impl(params) + }, + + request: { + url: () => 'https://api.anthropic.com/v1/sessions', + method: 'POST', + headers: () => ({}), + }, + + outputs: { + content: { type: 'string', description: 'Final assistant text from the Managed Agent session.' }, + sessionId: { type: 'string', description: 'Anthropic session id (for logs / linking).' }, + }, +} diff --git a/apps/sim/tools/managed_agent/types.ts b/apps/sim/tools/managed_agent/types.ts new file mode 100644 index 00000000000..216ca8e87ae --- /dev/null +++ b/apps/sim/tools/managed_agent/types.ts @@ -0,0 +1,55 @@ +import type { WorkflowToolExecutionContext } from '@/tools/types' + +/** + * Params accepted by the `managed_agent_run_session` tool. Values come + * from the Managed Agent workflow block's subblocks; the executor + * injects `_context` (workspaceId / userId / workflowId) at runtime. + */ +export interface ManagedAgentRunSessionParams { + /** ID of the `managed_agent_connection` row that stores the API key. */ + connection: string + /** Managed-agent id from the linked Claude workspace. */ + agent: string + /** Environment id from the linked Claude workspace. */ + environment: string + /** + * Env `config.type` — set by the block's environment combobox + * `onChange`; used here to route memoryStoreId / sessionParameters + * correctly at session-create time. + */ + environmentType?: 'cloud' | 'self_hosted' | string + /** Zero or more vault ids for MCP auth. Empty array allowed. */ + vaults?: string[] + /** Optional Agent Memory Store id. */ + memoryStoreId?: string + /** + * Access mode for the attached memory store. `read_write` (default) + * pushes changes back on session exit; `read_only` never writes. + * Ignored when `memoryStoreId` is empty. + */ + memoryAccess?: 'read_write' | 'read_only' | string + /** + * File attachments (cloud envs only). Each row = `{fileId, + * mountPath?}`. Ignored for self-hosted envs. + */ + files?: Array<{ fileId: string; mountPath?: string }> + /** + * Key/value session metadata forwarded to the session's top-level + * `metadata` field. On self-hosted envs the self-hosted agent sandbox + * exposes each key as an env var; on cloud envs metadata is stored + * as opaque tags. Each value has already been variable-resolved by + * the executor. + */ + sessionParameters?: Record + /** The user's turn as plain text. Resolved by the executor. */ + userMessage: string + /** Injected by the executor. */ + _context?: WorkflowToolExecutionContext +} + +export interface ManagedAgentRunSessionOutput { + /** Final assistant text accumulated from `agent.message` events. */ + content: string + /** Anthropic session id (returned so downstream blocks can log/link it). */ + sessionId: string +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 9c301e67b98..c8db8396c88 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -2309,6 +2309,7 @@ import { } from '@/tools/linear' import { linkedInGetProfileTool, linkedInSharePostTool } from '@/tools/linkedin' import { linkupSearchTool } from '@/tools/linkup' +import { managedAgentRunSessionTool } from '@/tools/managed_agent' import { linqAddParticipantTool, linqCheckImessageTool, @@ -5151,6 +5152,7 @@ export const tools: Record = { logs_get: logsGetTool, logs_get_execution: logsGetExecutionTool, logs_get_run_details: logsGetRunDetailsTool, + managed_agent_run_session: managedAgentRunSessionTool, loops_check_contact_suppression: loopsCheckContactSuppressionTool, loops_create_contact: loopsCreateContactTool, loops_create_contact_property: loopsCreateContactPropertyTool, diff --git a/packages/db/migrations/0264_managed_agent_connection.sql b/packages/db/migrations/0264_managed_agent_connection.sql new file mode 100644 index 00000000000..d44e038d02f --- /dev/null +++ b/packages/db/migrations/0264_managed_agent_connection.sql @@ -0,0 +1,16 @@ +CREATE TABLE "managed_agent_connection" ( + "id" text PRIMARY KEY NOT NULL, + "workspace_id" text NOT NULL, + "user_id" text, + "name" text NOT NULL, + "encrypted_api_key" text NOT NULL, + "last_verified_at" timestamp, + "last_verification_error" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "managed_agent_connection" ADD CONSTRAINT "managed_agent_connection_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "managed_agent_connection" ADD CONSTRAINT "managed_agent_connection_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "managed_agent_connection_workspace_id_idx" ON "managed_agent_connection" USING btree ("workspace_id");--> statement-breakpoint +CREATE UNIQUE INDEX "managed_agent_connection_workspace_name_unique" ON "managed_agent_connection" USING btree ("workspace_id","name"); \ No newline at end of file diff --git a/packages/db/migrations/meta/0264_snapshot.json b/packages/db/migrations/meta/0264_snapshot.json new file mode 100644 index 00000000000..78be441cded --- /dev/null +++ b/packages/db/migrations/meta/0264_snapshot.json @@ -0,0 +1,17498 @@ +{ + "id": "cca39e95-9fb8-4e3d-9e0d-591aa668edb5", + "prevId": "296b31b9-f1e6-4d67-838c-06801e9818a3", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.academy_certificate": { + "name": "academy_certificate", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "academy_cert_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "issued_at": { + "name": "issued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "certificate_number": { + "name": "certificate_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "academy_certificate_user_id_idx": { + "name": "academy_certificate_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_course_id_idx": { + "name": "academy_certificate_course_id_idx", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_user_course_unique": { + "name": "academy_certificate_user_course_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_number_idx": { + "name": "academy_certificate_number_idx", + "columns": [ + { + "expression": "certificate_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_status_idx": { + "name": "academy_certificate_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "academy_certificate_user_id_user_id_fk": { + "name": "academy_certificate_user_id_user_id_fk", + "tableFrom": "academy_certificate", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "academy_certificate_certificate_number_unique": { + "name": "academy_certificate_certificate_number_unique", + "nullsNotDistinct": false, + "columns": ["certificate_number"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_user_id_idx": { + "name": "account_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_account_on_account_id_provider_id": { + "name": "idx_account_on_account_id_provider_id", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_key": { + "name": "api_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "api_key_workspace_type_idx": { + "name": "api_key_workspace_type_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_user_type_idx": { + "name": "api_key_user_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_key_hash_idx": { + "name": "api_key_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_key_user_id_user_id_fk": { + "name": "api_key_user_id_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_workspace_id_workspace_id_fk": { + "name": "api_key_workspace_id_workspace_id_fk", + "tableFrom": "api_key", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_created_by_user_id_fk": { + "name": "api_key_created_by_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_unique": { + "name": "api_key_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": { + "workspace_type_check": { + "name": "workspace_type_check", + "value": "(type = 'workspace' AND workspace_id IS NOT NULL) OR (type = 'personal' AND workspace_id IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.async_jobs": { + "name": "async_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "run_at": { + "name": "run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "async_jobs_status_started_at_idx": { + "name": "async_jobs_status_started_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_status_completed_at_idx": { + "name": "async_jobs_status_completed_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "completed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_pending_run_at_idx": { + "name": "async_jobs_schedule_pending_run_at_idx", + "columns": [ + { + "expression": "run_at", + "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, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_processing_started_at_idx": { + "name": "async_jobs_schedule_processing_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'processing'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "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", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_log_workspace_created_idx": { + "name": "audit_log_workspace_created_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_workspace_created_at_id_idx": { + "name": "audit_log_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_actor_created_idx": { + "name": "audit_log_actor_created_idx", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_resource_idx": { + "name": "audit_log_resource_idx", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_action_idx": { + "name": "audit_log_action_idx", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_log_workspace_id_workspace_id_fk": { + "name": "audit_log_workspace_id_workspace_id_fk", + "tableFrom": "audit_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "audit_log_actor_id_user_id_fk": { + "name": "audit_log_actor_id_user_id_fk", + "tableFrom": "audit_log", + "tableTo": "user", + "columnsFrom": ["actor_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.background_work_status": { + "name": "background_work_status", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "background_work_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "background_work_status_value", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "background_work_status_workspace_status_idx": { + "name": "background_work_status_workspace_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_workflow_status_idx": { + "name": "background_work_status_workflow_status_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_meta_child_ws_idx": { + "name": "background_work_status_meta_child_ws_idx", + "columns": [ + { + "expression": "(\"metadata\" ->> 'childWorkspaceId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_meta_other_ws_idx": { + "name": "background_work_status_meta_other_ws_idx", + "columns": [ + { + "expression": "(\"metadata\" ->> 'otherWorkspaceId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "background_work_status_workspace_id_workspace_id_fk": { + "name": "background_work_status_workspace_id_workspace_id_fk", + "tableFrom": "background_work_status", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "background_work_status_workflow_id_workflow_id_fk": { + "name": "background_work_status_workflow_id_workflow_id_fk", + "tableFrom": "background_work_status", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat": { + "name": "chat", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "customizations": { + "name": "customizations", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "output_configs": { + "name": "output_configs", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "identifier_idx": { + "name": "identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_archived_at_partial_idx": { + "name": "chat_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"chat\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_chat_on_workflow_id_archived_at": { + "name": "idx_chat_on_workflow_id_archived_at", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_workflow_id_workflow_id_fk": { + "name": "chat_workflow_id_workflow_id_fk", + "tableFrom": "chat", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_user_id_user_id_fk": { + "name": "chat_user_id_user_id_fk", + "tableFrom": "chat", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_async_tool_calls": { + "name": "copilot_async_tool_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "copilot_async_tool_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "claimed_by": { + "name": "claimed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_async_tool_calls_run_id_idx": { + "name": "copilot_async_tool_calls_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_checkpoint_id_idx": { + "name": "copilot_async_tool_calls_checkpoint_id_idx", + "columns": [ + { + "expression": "checkpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_idx": { + "name": "copilot_async_tool_calls_tool_call_id_idx", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_status_idx": { + "name": "copilot_async_tool_calls_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_run_status_idx": { + "name": "copilot_async_tool_calls_run_status_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_unique": { + "name": "copilot_async_tool_calls_tool_call_id_unique", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_async_tool_calls_run_id_copilot_runs_id_fk": { + "name": "copilot_async_tool_calls_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk": { + "name": "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_run_checkpoints", + "columnsFrom": ["checkpoint_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_chats": { + "name": "copilot_chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "chat_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'copilot'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-3-7-sonnet-latest'" + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_yaml": { + "name": "preview_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan_artifact": { + "name": "plan_artifact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resources": { + "name": "resources", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_chats_user_id_idx": { + "name": "copilot_chats_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workflow_id_idx": { + "name": "copilot_chats_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workflow_idx": { + "name": "copilot_chats_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workspace_idx": { + "name": "copilot_chats_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_created_at_idx": { + "name": "copilot_chats_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_updated_at_idx": { + "name": "copilot_chats_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workspace_created_at_id_idx": { + "name": "copilot_chats_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_chats_user_id_user_id_fk": { + "name": "copilot_chats_user_id_user_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workflow_id_workflow_id_fk": { + "name": "copilot_chats_workflow_id_workflow_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workspace_id_workspace_id_fk": { + "name": "copilot_chats_workspace_id_workspace_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_feedback": { + "name": "copilot_feedback", + "schema": "", + "columns": { + "feedback_id": { + "name": "feedback_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_query": { + "name": "user_query", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_response": { + "name": "agent_response", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_positive": { + "name": "is_positive", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_yaml": { + "name": "workflow_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_feedback_user_id_idx": { + "name": "copilot_feedback_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_chat_id_idx": { + "name": "copilot_feedback_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_user_chat_idx": { + "name": "copilot_feedback_user_chat_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_is_positive_idx": { + "name": "copilot_feedback_is_positive_idx", + "columns": [ + { + "expression": "is_positive", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_created_at_idx": { + "name": "copilot_feedback_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_feedback_user_id_user_id_fk": { + "name": "copilot_feedback_user_id_user_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_feedback_chat_id_copilot_chats_id_fk": { + "name": "copilot_feedback_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_messages": { + "name": "copilot_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_message_id": { + "name": "parent_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens_in": { + "name": "tokens_in", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tokens_out": { + "name": "tokens_out", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_messages_chat_message_unique": { + "name": "copilot_messages_chat_message_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_created_at_idx": { + "name": "copilot_messages_chat_created_at_idx", + "columns": [ + { + "expression": "chat_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, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_seq_idx": { + "name": "copilot_messages_chat_seq_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_stream_idx": { + "name": "copilot_messages_chat_stream_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"stream_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_user_created_at_idx": { + "name": "copilot_messages_user_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"role\" = 'user' AND \"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_messages_chat_id_copilot_chats_id_fk": { + "name": "copilot_messages_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_messages", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_run_checkpoints": { + "name": "copilot_run_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pending_tool_call_id": { + "name": "pending_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_snapshot": { + "name": "conversation_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "agent_state": { + "name": "agent_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "provider_request": { + "name": "provider_request", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_run_checkpoints_run_id_idx": { + "name": "copilot_run_checkpoints_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_pending_tool_call_id_idx": { + "name": "copilot_run_checkpoints_pending_tool_call_id_idx", + "columns": [ + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_run_pending_tool_unique": { + "name": "copilot_run_checkpoints_run_pending_tool_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { + "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_run_checkpoints", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_runs": { + "name": "copilot_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "copilot_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "request_context": { + "name": "request_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_runs_execution_id_idx": { + "name": "copilot_runs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_parent_run_id_idx": { + "name": "copilot_runs_parent_run_id_idx", + "columns": [ + { + "expression": "parent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_id_idx": { + "name": "copilot_runs_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_user_id_idx": { + "name": "copilot_runs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workflow_id_idx": { + "name": "copilot_runs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_id_idx": { + "name": "copilot_runs_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_status_idx": { + "name": "copilot_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_execution_idx": { + "name": "copilot_runs_chat_execution_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_execution_started_at_idx": { + "name": "copilot_runs_execution_started_at_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_completed_at_id_idx": { + "name": "copilot_runs_workspace_completed_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"completed_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_stream_id_unique": { + "name": "copilot_runs_stream_id_unique", + "columns": [ + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_runs_chat_id_copilot_chats_id_fk": { + "name": "copilot_runs_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_user_id_user_id_fk": { + "name": "copilot_runs_user_id_user_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workflow_id_workflow_id_fk": { + "name": "copilot_runs_workflow_id_workflow_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workspace_id_workspace_id_fk": { + "name": "copilot_runs_workspace_id_workspace_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_workflow_read_hashes": { + "name": "copilot_workflow_read_hashes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_workflow_read_hashes_chat_id_idx": { + "name": "copilot_workflow_read_hashes_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_workflow_id_idx": { + "name": "copilot_workflow_read_hashes_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_chat_workflow_unique": { + "name": "copilot_workflow_read_hashes_chat_workflow_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk": { + "name": "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_workflow_read_hashes_workflow_id_workflow_id_fk": { + "name": "copilot_workflow_read_hashes_workflow_id_workflow_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential": { + "name": "credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "credential_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_owner_user_id": { + "name": "env_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_service_account_key": { + "name": "encrypted_service_account_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_workspace_id_idx": { + "name": "credential_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_type_idx": { + "name": "credential_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_provider_id_idx": { + "name": "credential_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_account_id_idx": { + "name": "credential_account_id_idx", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_env_owner_user_id_idx": { + "name": "credential_env_owner_user_id_idx", + "columns": [ + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_account_unique": { + "name": "credential_workspace_account_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "account_id IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_env_unique": { + "name": "credential_workspace_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_workspace'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_personal_env_unique": { + "name": "credential_workspace_personal_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_personal'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_workspace_id_workspace_id_fk": { + "name": "credential_workspace_id_workspace_id_fk", + "tableFrom": "credential", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_account_id_account_id_fk": { + "name": "credential_account_id_account_id_fk", + "tableFrom": "credential", + "tableTo": "account", + "columnsFrom": ["account_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_env_owner_user_id_user_id_fk": { + "name": "credential_env_owner_user_id_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["env_owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_created_by_user_id_fk": { + "name": "credential_created_by_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_oauth_source_check": { + "name": "credential_oauth_source_check", + "value": "(type <> 'oauth') OR (account_id IS NOT NULL AND provider_id IS NOT NULL)" + }, + "credential_workspace_env_source_check": { + "name": "credential_workspace_env_source_check", + "value": "(type <> 'env_workspace') OR (env_key IS NOT NULL AND env_owner_user_id IS NULL)" + }, + "credential_personal_env_source_check": { + "name": "credential_personal_env_source_check", + "value": "(type <> 'env_personal') OR (env_key IS NOT NULL AND env_owner_user_id IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.credential_member": { + "name": "credential_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "credential_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "credential_member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_member_user_id_idx": { + "name": "credential_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_role_idx": { + "name": "credential_member_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_status_idx": { + "name": "credential_member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_unique": { + "name": "credential_member_unique", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_member_credential_id_credential_id_fk": { + "name": "credential_member_credential_id_credential_id_fk", + "tableFrom": "credential_member", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_user_id_user_id_fk": { + "name": "credential_member_user_id_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_invited_by_user_id_fk": { + "name": "credential_member_invited_by_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_block": { + "name": "custom_block", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inputs": { + "name": "inputs", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "outputs": { + "name": "outputs", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_block_organization_id_idx": { + "name": "custom_block_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_block_workflow_id_idx": { + "name": "custom_block_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_block_organization_type_unique": { + "name": "custom_block_organization_type_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_block_organization_id_organization_id_fk": { + "name": "custom_block_organization_id_organization_id_fk", + "tableFrom": "custom_block", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_block_workflow_id_workflow_id_fk": { + "name": "custom_block_workflow_id_workflow_id_fk", + "tableFrom": "custom_block", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_block_created_by_user_id_fk": { + "name": "custom_block_created_by_user_id_fk", + "tableFrom": "custom_block", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_tools": { + "name": "custom_tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_tools_workspace_id_idx": { + "name": "custom_tools_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_tools_workspace_title_unique": { + "name": "custom_tools_workspace_title_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_tools_workspace_id_workspace_id_fk": { + "name": "custom_tools_workspace_id_workspace_id_fk", + "tableFrom": "custom_tools", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_tools_user_id_user_id_fk": { + "name": "custom_tools_user_id_user_id_fk", + "tableFrom": "custom_tools", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drain_runs": { + "name": "data_drain_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "drain_id": { + "name": "drain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "data_drain_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "data_drain_run_trigger", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "rows_exported": { + "name": "rows_exported", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "bytes_written": { + "name": "bytes_written", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cursor_before": { + "name": "cursor_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cursor_after": { + "name": "cursor_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locators": { + "name": "locators", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "data_drain_runs_drain_started_idx": { + "name": "data_drain_runs_drain_started_idx", + "columns": [ + { + "expression": "drain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drain_runs_drain_id_data_drains_id_fk": { + "name": "data_drain_runs_drain_id_data_drains_id_fk", + "tableFrom": "data_drain_runs", + "tableTo": "data_drains", + "columnsFrom": ["drain_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drains": { + "name": "data_drains", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "data_drain_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_type": { + "name": "destination_type", + "type": "data_drain_destination", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_config": { + "name": "destination_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "destination_credentials": { + "name": "destination_credentials", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule_cadence": { + "name": "schedule_cadence", + "type": "data_drain_cadence", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "data_drains_org_idx": { + "name": "data_drains_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_due_idx": { + "name": "data_drains_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_org_name_unique": { + "name": "data_drains_org_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drains_organization_id_organization_id_fk": { + "name": "data_drains_organization_id_organization_id_fk", + "tableFrom": "data_drains", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "data_drains_created_by_user_id_fk": { + "name": "data_drains_created_by_user_id_fk", + "tableFrom": "data_drains", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docs_embeddings": { + "name": "docs_embeddings", + "schema": "", + "columns": { + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chunk_text": { + "name": "chunk_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_document": { + "name": "source_document", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_link": { + "name": "source_link", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_text": { + "name": "header_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_level": { + "name": "header_level", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": true + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "chunk_text_tsv": { + "name": "chunk_text_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"docs_embeddings\".\"chunk_text\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "docs_emb_source_document_idx": { + "name": "docs_emb_source_document_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_header_level_idx": { + "name": "docs_emb_header_level_idx", + "columns": [ + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_source_header_idx": { + "name": "docs_emb_source_header_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_model_idx": { + "name": "docs_emb_model_idx", + "columns": [ + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_created_at_idx": { + "name": "docs_emb_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_embedding_vector_hnsw_idx": { + "name": "docs_embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "docs_emb_metadata_gin_idx": { + "name": "docs_emb_metadata_gin_idx", + "columns": [ + { + "expression": "metadata", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "docs_emb_chunk_text_fts_idx": { + "name": "docs_emb_chunk_text_fts_idx", + "columns": [ + { + "expression": "chunk_text_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "docs_embedding_not_null_check": { + "name": "docs_embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + }, + "docs_header_level_check": { + "name": "docs_header_level_check", + "value": "\"header_level\" >= 1 AND \"header_level\" <= 6" + } + }, + "isRLSEnabled": false + }, + "public.document": { + "name": "document", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_url": { + "name": "file_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "character_count": { + "name": "character_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "processing_status": { + "name": "processing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_completed_at": { + "name": "processing_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_error": { + "name": "processing_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "user_excluded": { + "name": "user_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "doc_kb_id_idx": { + "name": "doc_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_filename_idx": { + "name": "doc_filename_idx", + "columns": [ + { + "expression": "filename", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_processing_status_idx": { + "name": "doc_processing_status_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "processing_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_external_id_idx": { + "name": "doc_connector_external_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_id_idx": { + "name": "doc_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_storage_key_idx": { + "name": "doc_storage_key_idx", + "columns": [ + { + "expression": "storage_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"storage_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_archived_at_partial_idx": { + "name": "doc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_deleted_at_partial_idx": { + "name": "doc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag1_idx": { + "name": "doc_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag2_idx": { + "name": "doc_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag3_idx": { + "name": "doc_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag4_idx": { + "name": "doc_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag5_idx": { + "name": "doc_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag6_idx": { + "name": "doc_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag7_idx": { + "name": "doc_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number1_idx": { + "name": "doc_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number2_idx": { + "name": "doc_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number3_idx": { + "name": "doc_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number4_idx": { + "name": "doc_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number5_idx": { + "name": "doc_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date1_idx": { + "name": "doc_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_connector_id_knowledge_connector_id_fk": { + "name": "document_connector_id_knowledge_connector_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_uploaded_by_user_id_fk": { + "name": "document_uploaded_by_user_id_fk", + "tableFrom": "document", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.embedding": { + "name": "embedding", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_index": { + "name": "chunk_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chunk_hash": { + "name": "chunk_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_length": { + "name": "content_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "start_offset": { + "name": "start_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_offset": { + "name": "end_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "content_tsv": { + "name": "content_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"embedding\".\"content\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "emb_kb_id_idx": { + "name": "emb_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_id_idx": { + "name": "emb_doc_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_chunk_idx": { + "name": "emb_doc_chunk_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chunk_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_model_idx": { + "name": "emb_kb_model_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_enabled_idx": { + "name": "emb_kb_enabled_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_enabled_idx": { + "name": "emb_doc_enabled_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_vector_hnsw_idx": { + "name": "embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "emb_tag1_idx": { + "name": "emb_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag2_idx": { + "name": "emb_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag3_idx": { + "name": "emb_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag4_idx": { + "name": "emb_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag5_idx": { + "name": "emb_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag6_idx": { + "name": "emb_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag7_idx": { + "name": "emb_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number1_idx": { + "name": "emb_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number2_idx": { + "name": "emb_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number3_idx": { + "name": "emb_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number4_idx": { + "name": "emb_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number5_idx": { + "name": "emb_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date1_idx": { + "name": "emb_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_not_null_check": { + "name": "embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "environment_user_id_user_id_fk": { + "name": "environment_user_id_user_id_fk", + "tableFrom": "environment", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "environment_user_id_unique": { + "name": "environment_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_dependencies": { + "name": "execution_large_value_dependencies", + "schema": "", + "columns": { + "parent_key": { + "name": "parent_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_key": { + "name": "child_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_dependencies_workspace_parent_key_idx": { + "name": "execution_large_value_dependencies_workspace_parent_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_value_dependencies_workspace_child_key_idx": { + "name": "execution_large_value_dependencies_workspace_child_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_dependencies_workspace_id_workspace_id_fk": { + "name": "execution_large_value_dependencies_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_dependencies", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_dependencies_parent_key_child_key_pk": { + "name": "execution_large_value_dependencies_parent_key_child_key_pk", + "columns": ["parent_key", "child_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_references": { + "name": "execution_large_value_references", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "execution_large_value_reference_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_references_workspace_execution_source_idx": { + "name": "execution_large_value_references_workspace_execution_source_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_references_workspace_id_workspace_id_fk": { + "name": "execution_large_value_references_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_value_references_workflow_id_workflow_id_fk": { + "name": "execution_large_value_references_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_references_key_execution_id_source_pk": { + "name": "execution_large_value_references_key_execution_id_source_pk", + "columns": ["key", "execution_id", "source"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_values": { + "name": "execution_large_values", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_execution_id": { + "name": "owner_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "execution_large_values_owner_execution_id_idx": { + "name": "execution_large_values_owner_execution_id_idx", + "columns": [ + { + "expression": "owner_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_cleanup_idx": { + "name": "execution_large_values_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_tombstone_cleanup_idx": { + "name": "execution_large_values_tombstone_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_values_workspace_id_workspace_id_fk": { + "name": "execution_large_values_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_values_workflow_id_workflow_id_fk": { + "name": "execution_large_values_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.idempotency_key": { + "name": "idempotency_key", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "result": { + "name": "result", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idempotency_key_created_at_idx": { + "name": "idempotency_key_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "invitation_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'organization'" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "membership_intent": { + "name": "membership_intent", + "type": "invitation_membership_intent", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'internal'" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "invitation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_organization_id_idx": { + "name": "invitation_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_status_idx": { + "name": "invitation_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_pending_email_org_unique": { + "name": "invitation_pending_email_org_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"invitation\".\"status\" = 'pending' AND \"invitation\".\"organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invitation_token_unique": { + "name": "invitation_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation_workspace_grant": { + "name": "invitation_workspace_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "invitation_id": { + "name": "invitation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_workspace_grant_unique": { + "name": "invitation_workspace_grant_unique", + "columns": [ + { + "expression": "invitation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_workspace_grant_workspace_id_idx": { + "name": "invitation_workspace_grant_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_workspace_grant_invitation_id_invitation_id_fk": { + "name": "invitation_workspace_grant_invitation_id_invitation_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "invitation", + "columnsFrom": ["invitation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_workspace_grant_workspace_id_workspace_id_fk": { + "name": "invitation_workspace_grant_workspace_id_workspace_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.job_execution_logs": { + "name": "job_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "job_execution_logs_schedule_id_idx": { + "name": "job_execution_logs_schedule_id_idx", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_started_at_idx": { + "name": "job_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_ended_at_id_idx": { + "name": "job_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_execution_id_unique": { + "name": "job_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_trigger_idx": { + "name": "job_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "job_execution_logs_schedule_id_workflow_schedule_id_fk": { + "name": "job_execution_logs_schedule_id_workflow_schedule_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workflow_schedule", + "columnsFrom": ["schedule_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "job_execution_logs_workspace_id_workspace_id_fk": { + "name": "job_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base": { + "name": "knowledge_base", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "embedding_dimension": { + "name": "embedding_dimension", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1536 + }, + "chunking_config": { + "name": "chunking_config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{\"maxSize\": 1024, \"minSize\": 1, \"overlap\": 200}'" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_user_id_idx": { + "name": "kb_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_id_idx": { + "name": "kb_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_user_workspace_idx": { + "name": "kb_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_deleted_at_idx": { + "name": "kb_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_deleted_partial_idx": { + "name": "kb_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_base\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_name_active_unique": { + "name": "kb_workspace_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_user_id_user_id_fk": { + "name": "knowledge_base_user_id_user_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_workspace_id_workspace_id_fk": { + "name": "knowledge_base_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base_tag_definitions": { + "name": "knowledge_base_tag_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_slot": { + "name": "tag_slot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_tag_definitions_kb_slot_idx": { + "name": "kb_tag_definitions_kb_slot_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag_slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_display_name_idx": { + "name": "kb_tag_definitions_kb_display_name_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_id_idx": { + "name": "kb_tag_definitions_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_base_tag_definitions", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector": { + "name": "knowledge_connector", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_type": { + "name": "connector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_config": { + "name": "source_config", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "sync_mode": { + "name": "sync_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'full'" + }, + "sync_interval_minutes": { + "name": "sync_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1440 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_sync_error": { + "name": "last_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_doc_count": { + "name": "last_sync_doc_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "next_sync_at": { + "name": "next_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "consecutive_failures": { + "name": "consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kc_knowledge_base_id_idx": { + "name": "kc_knowledge_base_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_status_next_sync_idx": { + "name": "kc_status_next_sync_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_archived_at_partial_idx": { + "name": "kc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_deleted_at_partial_idx": { + "name": "kc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_connector_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_connector", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector_sync_log": { + "name": "knowledge_connector_sync_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "docs_added": { + "name": "docs_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_updated": { + "name": "docs_updated", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_deleted": { + "name": "docs_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_unchanged": { + "name": "docs_unchanged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_failed": { + "name": "docs_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcsl_connector_id_idx": { + "name": "kcsl_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.managed_agent_connection": { + "name": "managed_agent_connection", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_verified_at": { + "name": "last_verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_verification_error": { + "name": "last_verification_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "managed_agent_connection_workspace_id_idx": { + "name": "managed_agent_connection_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "managed_agent_connection_workspace_name_unique": { + "name": "managed_agent_connection_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "managed_agent_connection_workspace_id_workspace_id_fk": { + "name": "managed_agent_connection_workspace_id_workspace_id_fk", + "tableFrom": "managed_agent_connection", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "managed_agent_connection_user_id_user_id_fk": { + "name": "managed_agent_connection_user_id_user_id_fk", + "tableFrom": "managed_agent_connection", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_server_oauth": { + "name": "mcp_server_oauth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_server_id": { + "name": "mcp_server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_information": { + "name": "client_information", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens": { + "name": "tokens", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_created_at": { + "name": "state_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_refreshed_at": { + "name": "last_refreshed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_server_oauth_server_unique": { + "name": "mcp_server_oauth_server_unique", + "columns": [ + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_server_oauth_state_idx": { + "name": "mcp_server_oauth_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk": { + "name": "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "mcp_servers", + "columnsFrom": ["mcp_server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_server_oauth_user_id_user_id_fk": { + "name": "mcp_server_oauth_user_id_user_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_server_oauth_workspace_id_workspace_id_fk": { + "name": "mcp_server_oauth_workspace_id_workspace_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_servers": { + "name": "mcp_servers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'headers'" + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_client_secret": { + "name": "oauth_client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "headers": { + "name": "headers", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30000 + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_connected": { + "name": "last_connected", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "connection_status": { + "name": "connection_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'disconnected'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_config": { + "name": "status_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "tool_count": { + "name": "tool_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_tools_refresh": { + "name": "last_tools_refresh", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_requests": { + "name": "total_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_servers_workspace_enabled_idx": { + "name": "mcp_servers_workspace_enabled_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_workspace_deleted_partial_idx": { + "name": "mcp_servers_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"mcp_servers\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_servers_workspace_id_workspace_id_fk": { + "name": "mcp_servers_workspace_id_workspace_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_servers_created_by_user_id_fk": { + "name": "mcp_servers_created_by_user_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "member_user_id_unique": { + "name": "member_user_id_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_organization_id_idx": { + "name": "member_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory": { + "name": "memory", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_idx": { + "name": "memory_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_key_idx": { + "name": "memory_workspace_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_deleted_partial_idx": { + "name": "memory_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"memory\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workspace_id_workspace_id_fk": { + "name": "memory_workspace_id_workspace_id_fk", + "tableFrom": "memory", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_allowed_sender": { + "name": "mothership_inbox_allowed_sender", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_sender_ws_email_idx": { + "name": "inbox_sender_ws_email_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_allowed_sender_added_by_user_id_fk": { + "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "user", + "columnsFrom": ["added_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_task": { + "name": "mothership_inbox_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_email": { + "name": "from_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_preview": { + "name": "body_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_message_id": { + "name": "email_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_reply_to": { + "name": "in_reply_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_message_id": { + "name": "response_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agentmail_message_id": { + "name": "agentmail_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger_job_id": { + "name": "trigger_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cc_recipients": { + "name": "cc_recipients", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inbox_task_ws_created_at_idx": { + "name": "inbox_task_ws_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_ws_status_idx": { + "name": "inbox_task_ws_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_response_msg_id_idx": { + "name": "inbox_task_response_msg_id_idx", + "columns": [ + { + "expression": "response_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_email_msg_id_idx": { + "name": "inbox_task_email_msg_id_idx", + "columns": [ + { + "expression": "email_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_task_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_task_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_task_chat_id_copilot_chats_id_fk": { + "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_webhook": { + "name": "mothership_inbox_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_inbox_webhook_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_webhook", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mothership_inbox_webhook_workspace_id_unique": { + "name": "mothership_inbox_webhook_workspace_id_unique", + "nullsNotDistinct": false, + "columns": ["workspace_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_settings": { + "name": "mothership_settings", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_tool_refs": { + "name": "mcp_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "custom_tool_refs": { + "name": "custom_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "skill_refs": { + "name": "skill_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mothership_settings_workspace_id_idx": { + "name": "mothership_settings_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_settings_workspace_id_workspace_id_fk": { + "name": "mothership_settings_workspace_id_workspace_id_fk", + "tableFrom": "mothership_settings", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "whitelabel_settings": { + "name": "whitelabel_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data_retention_settings": { + "name": "data_retention_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "org_usage_limit": { + "name": "org_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "departed_member_usage": { + "name": "departed_member_usage", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_member_usage_limit": { + "name": "organization_member_usage_limit", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usage_limit": { + "name": "usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "set_by": { + "name": "set_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "org_member_usage_limit_org_user_unique": { + "name": "org_member_usage_limit_org_user_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "org_member_usage_limit_organization_id_idx": { + "name": "org_member_usage_limit_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_member_usage_limit_organization_id_organization_id_fk": { + "name": "organization_member_usage_limit_organization_id_organization_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_member_usage_limit_user_id_user_id_fk": { + "name": "organization_member_usage_limit_user_id_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_member_usage_limit_set_by_user_id_fk": { + "name": "organization_member_usage_limit_set_by_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "user", + "columnsFrom": ["set_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_event": { + "name": "outbox_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "available_at": { + "name": "available_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "outbox_event_status_available_idx": { + "name": "outbox_event_status_available_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_event_locked_at_idx": { + "name": "outbox_event_locked_at_idx", + "columns": [ + { + "expression": "locked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_event_type_created_idx": { + "name": "outbox_event_type_created_idx", + "columns": [ + { + "expression": "event_type", + "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": {}, + "isRLSEnabled": false + }, + "public.paused_executions": { + "name": "paused_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_snapshot": { + "name": "execution_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "pause_points": { + "name": "pause_points", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "total_pause_count": { + "name": "total_pause_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "resumed_count": { + "name": "resumed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "automatic_resume_retry_count": { + "name": "automatic_resume_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paused'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_resume_at": { + "name": "next_resume_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "paused_executions_workflow_id_idx": { + "name": "paused_executions_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_status_idx": { + "name": "paused_executions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_execution_id_unique": { + "name": "paused_executions_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_next_resume_at_idx": { + "name": "paused_executions_next_resume_at_idx", + "columns": [ + { + "expression": "next_resume_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'paused' AND next_resume_at IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "paused_executions_workflow_id_workflow_id_fk": { + "name": "paused_executions_workflow_id_workflow_id_fk", + "tableFrom": "paused_executions", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_credential_draft": { + "name": "pending_credential_draft", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pending_draft_user_provider_ws": { + "name": "pending_draft_user_provider_ws", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pending_credential_draft_user_id_user_id_fk": { + "name": "pending_credential_draft_user_id_user_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_workspace_id_workspace_id_fk": { + "name": "pending_credential_draft_workspace_id_workspace_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_credential_id_credential_id_fk": { + "name": "pending_credential_draft_credential_id_credential_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group": { + "name": "permission_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "permission_group_created_by_idx": { + "name": "permission_group_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_organization_name_unique": { + "name": "permission_group_organization_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_organization_default_unique": { + "name": "permission_group_organization_default_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "is_default = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_organization_id_organization_id_fk": { + "name": "permission_group_organization_id_organization_id_fk", + "tableFrom": "permission_group", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_created_by_user_id_fk": { + "name": "permission_group_created_by_user_id_fk", + "tableFrom": "permission_group", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_member": { + "name": "permission_group_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_member_group_id_idx": { + "name": "permission_group_member_group_id_idx", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_group_user_unique": { + "name": "permission_group_member_group_user_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_organization_user_idx": { + "name": "permission_group_member_organization_user_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_member_permission_group_id_permission_group_id_fk": { + "name": "permission_group_member_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_organization_id_organization_id_fk": { + "name": "permission_group_member_organization_id_organization_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_user_id_user_id_fk": { + "name": "permission_group_member_user_id_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_assigned_by_user_id_fk": { + "name": "permission_group_member_assigned_by_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["assigned_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_workspace": { + "name": "permission_group_workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_workspace_workspace_id_idx": { + "name": "permission_group_workspace_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_workspace_group_workspace_unique": { + "name": "permission_group_workspace_group_workspace_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_workspace_permission_group_id_permission_group_id_fk": { + "name": "permission_group_workspace_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_workspace_workspace_id_workspace_id_fk": { + "name": "permission_group_workspace_workspace_id_workspace_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_workspace_organization_id_organization_id_fk": { + "name": "permission_group_workspace_organization_id_organization_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permissions": { + "name": "permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permissions_user_id_idx": { + "name": "permissions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_entity_idx": { + "name": "permissions_entity_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_type_idx": { + "name": "permissions_user_entity_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_permission_idx": { + "name": "permissions_user_entity_permission_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_idx": { + "name": "permissions_user_entity_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_unique_constraint": { + "name": "permissions_unique_constraint", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permissions_user_id_user_id_fk": { + "name": "permissions_user_id_user_id_fk", + "tableFrom": "permissions", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.public_share": { + "name": "public_share", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "public_share_token_unique": { + "name": "public_share_token_unique", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_resource_unique": { + "name": "public_share_resource_unique", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_resource_id_idx": { + "name": "public_share_resource_id_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_workspace_id_idx": { + "name": "public_share_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "public_share_workspace_id_workspace_id_fk": { + "name": "public_share_workspace_id_workspace_id_fk", + "tableFrom": "public_share", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "public_share_created_by_user_id_fk": { + "name": "public_share_created_by_user_id_fk", + "tableFrom": "public_share", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rate_limit_bucket": { + "name": "rate_limit_bucket", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tokens": { + "name": "tokens", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resume_queue": { + "name": "resume_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "paused_execution_id": { + "name": "paused_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_execution_id": { + "name": "parent_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_execution_id": { + "name": "new_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context_id": { + "name": "context_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resume_input": { + "name": "resume_input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "resume_queue_parent_status_idx": { + "name": "resume_queue_parent_status_idx", + "columns": [ + { + "expression": "parent_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resume_queue_new_execution_idx": { + "name": "resume_queue_new_execution_idx", + "columns": [ + { + "expression": "new_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resume_queue_paused_execution_id_paused_executions_id_fk": { + "name": "resume_queue_paused_execution_id_paused_executions_id_fk", + "tableFrom": "resume_queue", + "tableTo": "paused_executions", + "columnsFrom": ["paused_execution_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_user_id_idx": { + "name": "session_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_token_idx": { + "name": "session_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_active_organization_id_organization_id_fk": { + "name": "session_active_organization_id_organization_id_fk", + "tableFrom": "session", + "tableTo": "organization", + "columnsFrom": ["active_organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "auto_connect": { + "name": "auto_connect", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "telemetry_enabled": { + "name": "telemetry_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "email_preferences": { + "name": "email_preferences", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "billing_usage_notifications_enabled": { + "name": "billing_usage_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_training_controls": { + "name": "show_training_controls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "super_user_mode_enabled": { + "name": "super_user_mode_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "mothership_environment": { + "name": "mothership_environment", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "error_notifications_enabled": { + "name": "error_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "snap_to_grid_size": { + "name": "snap_to_grid_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "show_action_bar": { + "name": "show_action_bar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "copilot_enabled_models": { + "name": "copilot_enabled_models", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "copilot_auto_allowed_tools": { + "name": "copilot_auto_allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_active_workspace_id": { + "name": "last_active_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "settings_user_id_user_id_fk": { + "name": "settings_user_id_user_id_fk", + "tableFrom": "settings", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "settings_user_id_unique": { + "name": "settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sim_trigger_state": { + "name": "sim_trigger_state", + "schema": "", + "columns": { + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sim_trigger_state_workflow_id_workflow_id_fk": { + "name": "sim_trigger_state_workflow_id_workflow_id_fk", + "tableFrom": "sim_trigger_state", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sim_trigger_state_workflow_id_block_id_scope_key_pk": { + "name": "sim_trigger_state_workflow_id_block_id_scope_key_pk", + "columns": ["workflow_id", "block_id", "scope_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skill": { + "name": "skill", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skill_workspace_name_unique": { + "name": "skill_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skill_workspace_id_workspace_id_fk": { + "name": "skill_workspace_id_workspace_id_fk", + "tableFrom": "skill", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "skill_user_id_user_id_fk": { + "name": "skill_user_id_user_id_fk", + "tableFrom": "skill", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "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": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sso_provider_provider_id_idx": { + "name": "sso_provider_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_domain_idx": { + "name": "sso_provider_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_user_id_idx": { + "name": "sso_provider_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_organization_id_idx": { + "name": "sso_provider_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sso_provider_organization_id_organization_id_fk": { + "name": "sso_provider_organization_id_organization_id_fk", + "tableFrom": "sso_provider", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscription": { + "name": "subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cancel_at": { + "name": "cancel_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_interval": { + "name": "billing_interval", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "subscription_reference_status_idx": { + "name": "subscription_reference_status_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "check_enterprise_metadata": { + "name": "check_enterprise_metadata", + "value": "plan != 'enterprise' OR metadata IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.table_jobs": { + "name": "table_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rows_processed": { + "name": "rows_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_jobs_one_active_per_table": { + "name": "table_jobs_one_active_per_table", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"table_jobs\".\"status\" = 'running' AND \"table_jobs\".\"type\" <> 'export'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_jobs_watchdog_idx": { + "name": "table_jobs_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_jobs_table_started_idx": { + "name": "table_jobs_table_started_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_jobs_table_id_user_table_definitions_id_fk": { + "name": "table_jobs_table_id_user_table_definitions_id_fk", + "tableFrom": "table_jobs", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_jobs_workspace_id_workspace_id_fk": { + "name": "table_jobs_workspace_id_workspace_id_fk", + "tableFrom": "table_jobs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_row_executions": { + "name": "table_row_executions", + "schema": "", + "columns": { + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_id": { + "name": "job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "running_block_ids": { + "name": "running_block_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "block_errors": { + "name": "block_errors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enrichment_details": { + "name": "enrichment_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "table_row_executions_table_status_idx": { + "name": "table_row_executions_table_status_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"status\" IN ('queued', 'running', 'pending')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_execution_id_idx": { + "name": "table_row_executions_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"execution_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_table_group_idx": { + "name": "table_row_executions_table_group_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_row_executions_table_id_user_table_definitions_id_fk": { + "name": "table_row_executions_table_id_user_table_definitions_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_row_executions_row_id_user_table_rows_id_fk": { + "name": "table_row_executions_row_id_user_table_rows_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_rows", + "columnsFrom": ["row_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "table_row_executions_row_id_group_id_pk": { + "name": "table_row_executions_row_id_group_id_pk", + "columns": ["row_id", "group_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_run_dispatches": { + "name": "table_run_dispatches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit": { + "name": "limit", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "processed_count": { + "name": "processed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_manual_run": { + "name": "is_manual_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "triggered_by_user_id": { + "name": "triggered_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_run_dispatches_active_idx": { + "name": "table_run_dispatches_active_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_run_dispatches_watchdog_idx": { + "name": "table_run_dispatches_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_run_dispatches_table_id_user_table_definitions_id_fk": { + "name": "table_run_dispatches_table_id_user_table_definitions_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_run_dispatches_workspace_id_workspace_id_fk": { + "name": "table_run_dispatches_workspace_id_workspace_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_run_dispatches_triggered_by_user_id_user_id_fk": { + "name": "table_run_dispatches_triggered_by_user_id_user_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user", + "columnsFrom": ["triggered_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_log": { + "name": "usage_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "usage_log_category", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "usage_log_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "event_key": { + "name": "event_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_entity_type": { + "name": "billing_entity_type", + "type": "billing_entity_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "billing_entity_id": { + "name": "billing_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_period_start": { + "name": "billing_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_period_end": { + "name": "billing_period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "usage_log_user_created_at_idx": { + "name": "usage_log_user_created_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_source_idx": { + "name": "usage_log_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_id_idx": { + "name": "usage_log_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workflow_id_idx": { + "name": "usage_log_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_event_key_unique": { + "name": "usage_log_event_key_unique", + "columns": [ + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"usage_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_period_idx": { + "name": "usage_log_billing_entity_period_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_created_at_idx": { + "name": "usage_log_workspace_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_execution_id_idx": { + "name": "usage_log_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "usage_log_user_id_user_id_fk": { + "name": "usage_log_user_id_user_id_fk", + "tableFrom": "usage_log", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "usage_log_workspace_id_workspace_id_fk": { + "name": "usage_log_workspace_id_workspace_id_fk", + "tableFrom": "usage_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "usage_log_workflow_id_workflow_id_fk": { + "name": "usage_log_workflow_id_workflow_id_fk", + "tableFrom": "usage_log", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "usage_log_billing_scope_all_or_none": { + "name": "usage_log_billing_scope_all_or_none", + "value": "(\n (\"usage_log\".\"billing_entity_type\" IS NULL AND \"usage_log\".\"billing_entity_id\" IS NULL AND \"usage_log\".\"billing_period_start\" IS NULL AND \"usage_log\".\"billing_period_end\" IS NULL)\n OR\n (\"usage_log\".\"billing_entity_type\" IS NOT NULL AND \"usage_log\".\"billing_entity_id\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" IS NOT NULL AND \"usage_log\".\"billing_period_end\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" < \"usage_log\".\"billing_period_end\")\n )" + } + }, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + }, + "user_normalized_email_unique": { + "name": "user_normalized_email_unique", + "nullsNotDistinct": false, + "columns": ["normalized_email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_stats": { + "name": "user_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "total_manual_executions": { + "name": "total_manual_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_api_calls": { + "name": "total_api_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_webhook_triggers": { + "name": "total_webhook_triggers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_scheduled_executions": { + "name": "total_scheduled_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_chat_executions": { + "name": "total_chat_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_executions": { + "name": "total_mcp_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_tokens_used": { + "name": "total_tokens_used", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost": { + "name": "total_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_usage_limit": { + "name": "current_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'5'" + }, + "usage_limit_updated_at": { + "name": "usage_limit_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "current_period_cost": { + "name": "current_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_cost": { + "name": "last_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "billed_overage_this_period": { + "name": "billed_overage_this_period", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "pro_period_cost_snapshot": { + "name": "pro_period_cost_snapshot", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "pro_period_cost_snapshot_at": { + "name": "pro_period_cost_snapshot_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "total_copilot_cost": { + "name": "total_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_copilot_cost": { + "name": "current_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_copilot_cost": { + "name": "last_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "total_copilot_tokens": { + "name": "total_copilot_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_copilot_calls": { + "name": "total_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_copilot_calls": { + "name": "total_mcp_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_copilot_cost": { + "name": "total_mcp_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_mcp_copilot_cost": { + "name": "current_period_mcp_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_active": { + "name": "last_active", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "billing_blocked": { + "name": "billing_blocked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "billing_blocked_reason": { + "name": "billing_blocked_reason", + "type": "billing_blocked_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "user_stats_user_id_user_id_fk": { + "name": "user_stats_user_id_user_id_fk", + "tableFrom": "user_stats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_stats_user_id_unique": { + "name": "user_stats_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_definitions": { + "name": "user_table_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_rows": { + "name": "max_rows", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "rows_version": { + "name": "rows_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_table_def_workspace_id_idx": { + "name": "user_table_def_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_name_unique": { + "name": "user_table_def_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_table_definitions\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_archived_at_idx": { + "name": "user_table_def_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_archived_partial_idx": { + "name": "user_table_def_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_table_definitions\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_definitions_workspace_id_workspace_id_fk": { + "name": "user_table_definitions_workspace_id_workspace_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_definitions_created_by_user_id_fk": { + "name": "user_table_definitions_created_by_user_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_rows": { + "name": "user_table_rows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "order_key": { + "name": "order_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_table_rows_tenant_data_gin_idx": { + "name": "user_table_rows_tenant_data_gin_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"data\" jsonb_path_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "user_table_rows_workspace_table_idx": { + "name": "user_table_rows_workspace_table_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_position_idx": { + "name": "user_table_rows_table_position_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_order_key_idx": { + "name": "user_table_rows_table_order_key_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_id_id_idx": { + "name": "user_table_rows_table_id_id_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_rows_table_id_user_table_definitions_id_fk": { + "name": "user_table_rows_table_id_user_table_definitions_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_workspace_id_workspace_id_fk": { + "name": "user_table_rows_workspace_id_workspace_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_created_by_user_id_fk": { + "name": "user_table_rows_created_by_user_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "verification_expires_at_idx": { + "name": "verification_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook": { + "name": "webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration_status": { + "name": "registration_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration_generation": { + "name": "registration_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "config_fingerprint": { + "name": "config_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prepared_at": { + "name": "prepared_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "routing_key": { + "name": "routing_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config": { + "name": "provider_config", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "path_deployment_unique": { + "name": "path_deployment_unique", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"webhook\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_workflow_deployment_idx": { + "name": "webhook_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_routing_key_active_idx": { + "name": "webhook_routing_key_active_idx", + "columns": [ + { + "expression": "routing_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"webhook\".\"archived_at\" IS NULL AND \"webhook\".\"routing_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_archived_at_partial_idx": { + "name": "webhook_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"webhook\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468": { + "name": "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_tiktok_credential_id_idx": { + "name": "webhook_tiktok_credential_id_idx", + "columns": [ + { + "expression": "((\"provider_config\")::jsonb ->> 'credentialId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"webhook\".\"provider\" = 'tiktok' AND \"webhook\".\"is_active\" = true AND \"webhook\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_workflow_id_block_id_updated_at_desc": { + "name": "idx_webhook_on_workflow_id_block_id_updated_at_desc", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_active_registration_unique": { + "name": "webhook_active_registration_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"webhook\".\"registration_status\" = 'active' AND \"webhook\".\"block_id\" IS NOT NULL AND \"webhook\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_candidate_registration_unique": { + "name": "webhook_candidate_registration_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"webhook\".\"registration_status\" = 'candidate' AND \"webhook\".\"block_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_registration_status_generation_idx": { + "name": "webhook_registration_status_generation_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "registration_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "registration_generation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_workflow_id_workflow_id_fk": { + "name": "webhook_workflow_id_workflow_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webhook_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "webhook_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "webhook_registration_status_check": { + "name": "webhook_registration_status_check", + "value": "\"webhook\".\"registration_status\" IS NULL OR \"webhook\".\"registration_status\" IN ('active', 'candidate', 'retired', 'orphaned')" + }, + "webhook_registration_generation_check": { + "name": "webhook_registration_generation_check", + "value": "\"webhook\".\"registration_generation\" IS NULL OR \"webhook\".\"registration_generation\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.webhook_path_claim": { + "name": "webhook_path_claim", + "schema": "", + "columns": { + "path": { + "name": "path", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "webhook_path_claim_workflow_idx": { + "name": "webhook_path_claim_workflow_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_path_claim_workflow_id_workflow_id_fk": { + "name": "webhook_path_claim_workflow_id_workflow_id_fk", + "tableFrom": "webhook_path_claim", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "webhook_path_claim_generation_check": { + "name": "webhook_path_claim_generation_check", + "value": "\"webhook_path_claim\".\"generation\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.workflow": { + "name": "workflow", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced": { + "name": "last_synced", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_deployed": { + "name": "is_deployed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deployed_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "is_public_api": { + "name": "is_public_api", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "fork_sync_excluded": { + "name": "fork_sync_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_user_id_idx": { + "name": "workflow_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_id_idx": { + "name": "workflow_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_user_workspace_idx": { + "name": "workflow_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_folder_name_active_unique": { + "name": "workflow_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_sort_idx": { + "name": "workflow_folder_sort_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_archived_at_idx": { + "name": "workflow_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_archived_partial_idx": { + "name": "workflow_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_user_id_user_id_fk": { + "name": "workflow_user_id_user_id_fk", + "tableFrom": "workflow", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_workspace_id_workspace_id_fk": { + "name": "workflow_workspace_id_workspace_id_fk", + "tableFrom": "workflow", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_id_workflow_folder_id_fk": { + "name": "workflow_folder_id_workflow_folder_id_fk", + "tableFrom": "workflow", + "tableTo": "workflow_folder", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_blocks": { + "name": "workflow_blocks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_x": { + "name": "position_x", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "position_y": { + "name": "position_y", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "horizontal_handles": { + "name": "horizontal_handles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_wide": { + "name": "is_wide", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "advanced_mode": { + "name": "advanced_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_mode": { + "name": "trigger_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "height": { + "name": "height", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "sub_blocks": { + "name": "sub_blocks", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "outputs": { + "name": "outputs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_blocks_workflow_id_idx": { + "name": "workflow_blocks_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_blocks_type_idx": { + "name": "workflow_blocks_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_blocks_workflow_id_workflow_id_fk": { + "name": "workflow_blocks_workflow_id_workflow_id_fk", + "tableFrom": "workflow_blocks", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_checkpoints": { + "name": "workflow_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_state": { + "name": "workflow_state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_checkpoints_user_id_idx": { + "name": "workflow_checkpoints_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_id_idx": { + "name": "workflow_checkpoints_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_id_idx": { + "name": "workflow_checkpoints_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_message_id_idx": { + "name": "workflow_checkpoints_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_user_workflow_idx": { + "name": "workflow_checkpoints_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_chat_idx": { + "name": "workflow_checkpoints_workflow_chat_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_created_at_idx": { + "name": "workflow_checkpoints_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_created_at_idx": { + "name": "workflow_checkpoints_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_checkpoints_user_id_user_id_fk": { + "name": "workflow_checkpoints_user_id_user_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_workflow_id_workflow_id_fk": { + "name": "workflow_checkpoints_workflow_id_workflow_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_chat_id_copilot_chats_id_fk": { + "name": "workflow_checkpoints_chat_id_copilot_chats_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_deployment_operation": { + "name": "workflow_deployment_operation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_active_version_id": { + "name": "previous_active_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "protocol_version": { + "name": "protocol_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'preparing'" + }, + "component_readiness": { + "name": "component_readiness", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_deployment_operation_workflow_generation_unique": { + "name": "workflow_deployment_operation_workflow_generation_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_workflow_idempotency_unique": { + "name": "workflow_deployment_operation_workflow_idempotency_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_deployment_operation\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_workflow_in_flight_unique": { + "name": "workflow_deployment_operation_workflow_in_flight_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_deployment_operation\".\"status\" IN ('preparing', 'activating')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_workflow_status_idx": { + "name": "workflow_deployment_operation_workflow_status_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_deployment_version_idx": { + "name": "workflow_deployment_operation_deployment_version_idx", + "columns": [ + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_workflow_version_generation_idx": { + "name": "workflow_deployment_operation_workflow_version_generation_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_deployment_operation_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_operation_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_operation", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_deployment_operation_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_deployment_operation_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_deployment_operation", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_deployment_operation_previous_active_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_deployment_operation_previous_active_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_deployment_operation", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["previous_active_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workflow_deployment_operation_action_check": { + "name": "workflow_deployment_operation_action_check", + "value": "\"workflow_deployment_operation\".\"action\" IN ('deploy', 'activate')" + }, + "workflow_deployment_operation_status_check": { + "name": "workflow_deployment_operation_status_check", + "value": "\"workflow_deployment_operation\".\"status\" IN ('preparing', 'activating', 'active', 'failed', 'superseded')" + }, + "workflow_deployment_operation_generation_check": { + "name": "workflow_deployment_operation_generation_check", + "value": "\"workflow_deployment_operation\".\"generation\" > 0" + }, + "workflow_deployment_operation_protocol_version_check": { + "name": "workflow_deployment_operation_protocol_version_check", + "value": "\"workflow_deployment_operation\".\"protocol_version\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.workflow_deployment_version": { + "name": "workflow_deployment_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_deployment_version_workflow_version_unique": { + "name": "workflow_deployment_version_workflow_version_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_workflow_active_idx": { + "name": "workflow_deployment_version_workflow_active_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_created_at_idx": { + "name": "workflow_deployment_version_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_deployment_version_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_version_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_version", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_edges": { + "name": "workflow_edges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_block_id": { + "name": "source_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_handle": { + "name": "source_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_handle": { + "name": "target_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_edges_workflow_id_idx": { + "name": "workflow_edges_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_source_idx": { + "name": "workflow_edges_workflow_source_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_target_idx": { + "name": "workflow_edges_workflow_target_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_edges_workflow_id_workflow_id_fk": { + "name": "workflow_edges_workflow_id_workflow_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_source_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_source_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["source_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_target_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_target_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["target_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_logs": { + "name": "workflow_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_snapshot_id": { + "name": "state_snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost_total": { + "name": "cost_total", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "models_used": { + "name": "models_used", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "files": { + "name": "files", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_execution_logs_workflow_id_idx": { + "name": "workflow_execution_logs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_state_snapshot_id_idx": { + "name": "workflow_execution_logs_state_snapshot_id_idx", + "columns": [ + { + "expression": "state_snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_deployment_version_id_idx": { + "name": "workflow_execution_logs_deployment_version_id_idx", + "columns": [ + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_trigger_idx": { + "name": "workflow_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_level_idx": { + "name": "workflow_execution_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_started_at_idx": { + "name": "workflow_execution_logs_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_execution_id_unique": { + "name": "workflow_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workflow_started_at_idx": { + "name": "workflow_execution_logs_workflow_started_at_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_started_at_idx": { + "name": "workflow_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_started_at_id_desc_idx": { + "name": "workflow_execution_logs_workspace_started_at_id_desc_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_cost_total_idx": { + "name": "workflow_execution_logs_workspace_cost_total_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_total", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_models_used_idx": { + "name": "workflow_execution_logs_models_used_idx", + "columns": [ + { + "expression": "models_used", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "workflow_execution_logs_workspace_ended_at_id_idx": { + "name": "workflow_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_started_at_idx": { + "name": "workflow_execution_logs_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_completed_ended_at_idx": { + "name": "workflow_execution_logs_completed_ended_at_idx", + "columns": [ + { + "expression": "ended_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'completed' AND \"workflow_execution_logs\".\"level\" = 'info' AND \"workflow_execution_logs\".\"ended_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_execution_snapshots", + "columnsFrom": ["state_snapshot_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_folder": { + "name": "workflow_folder", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'#6B7280'" + }, + "is_expanded": { + "name": "is_expanded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_folder_user_idx": { + "name": "workflow_folder_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_parent_idx": { + "name": "workflow_folder_workspace_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_parent_sort_idx": { + "name": "workflow_folder_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_archived_at_idx": { + "name": "workflow_folder_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_archived_partial_idx": { + "name": "workflow_folder_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_folder\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_folder_user_id_user_id_fk": { + "name": "workflow_folder_user_id_user_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_workspace_id_workspace_id_fk": { + "name": "workflow_folder_workspace_id_workspace_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_server": { + "name": "workflow_mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_server_workspace_id_idx": { + "name": "workflow_mcp_server_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_created_by_idx": { + "name": "workflow_mcp_server_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_deleted_at_idx": { + "name": "workflow_mcp_server_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_workspace_deleted_partial_idx": { + "name": "workflow_mcp_server_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_tool": { + "name": "workflow_mcp_tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_description": { + "name": "tool_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parameter_schema": { + "name": "parameter_schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "parameter_description_overrides": { + "name": "parameter_description_overrides", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_tool_server_id_idx": { + "name": "workflow_mcp_tool_server_id_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_workflow_id_idx": { + "name": "workflow_mcp_tool_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_server_workflow_unique": { + "name": "workflow_mcp_tool_server_workflow_unique", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_archived_at_partial_idx": { + "name": "workflow_mcp_tool_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { + "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow_mcp_server", + "columnsFrom": ["server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_operation_id": { + "name": "deployment_operation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_queued_at": { + "name": "last_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "infra_retry_count": { + "name": "infra_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workflow'" + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'persistent'" + }, + "success_condition": { + "name": "success_condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_runs": { + "name": "max_runs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_chat_id": { + "name": "source_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_name": { + "name": "source_task_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_user_id": { + "name": "source_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_history": { + "name": "job_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "contexts": { + "name": "contexts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "excluded_dates": { + "name": "excluded_dates", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_deployment_unique": { + "name": "workflow_schedule_workflow_block_deployment_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_workflow_deployment_idx": { + "name": "workflow_schedule_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_archived_at_partial_idx": { + "name": "workflow_schedule_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": { + "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6", + "columns": [ + { + "expression": "source_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_workflow_idx": { + "name": "workflow_schedule_due_workflow_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_job_idx": { + "name": "workflow_schedule_due_job_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk": { + "name": "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_operation", + "columnsFrom": ["deployment_operation_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "user", + "columnsFrom": ["source_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workspace", + "columnsFrom": ["source_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#33C482'" + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_mode": { + "name": "workspace_mode", + "type": "workspace_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'grandfathered_shared'" + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inbox_enabled": { + "name": "inbox_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inbox_address": { + "name": "inbox_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_provider_id": { + "name": "inbox_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "organization_assigned_at": { + "name": "organization_assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "forked_from_workspace_id": { + "name": "forked_from_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_owner_id_idx": { + "name": "workspace_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_organization_id_idx": { + "name": "workspace_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_mode_idx": { + "name": "workspace_mode_idx", + "columns": [ + { + "expression": "workspace_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_forked_from_workspace_id_idx": { + "name": "workspace_forked_from_workspace_id_idx", + "columns": [ + { + "expression": "forked_from_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_organization_id_organization_id_fk": { + "name": "workspace_organization_id_organization_id_fk", + "tableFrom": "workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["billed_account_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_forked_from_workspace_id_workspace_id_fk": { + "name": "workspace_forked_from_workspace_id_workspace_id_fk", + "tableFrom": "workspace", + "tableTo": "workspace", + "columnsFrom": ["forked_from_workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_storage_used_bytes_non_negative": { + "name": "workspace_storage_used_bytes_non_negative", + "value": "\"workspace\".\"storage_used_bytes\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.workspace_byok_keys": { + "name": "workspace_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_byok_workspace_provider_idx": { + "name": "workspace_byok_workspace_provider_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_byok_keys_workspace_id_workspace_id_fk": { + "name": "workspace_byok_keys_workspace_id_workspace_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_byok_keys_created_by_user_id_fk": { + "name": "workspace_byok_keys_created_by_user_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_key_idx": { + "name": "workspace_file_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_deleted_at_idx": { + "name": "workspace_file_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_workspace_deleted_partial_idx": { + "name": "workspace_file_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_folders": { + "name": "workspace_file_folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_folders_workspace_parent_idx": { + "name": "workspace_file_folders_workspace_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_parent_sort_idx": { + "name": "workspace_file_folders_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_deleted_at_idx": { + "name": "workspace_file_folders_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_workspace_deleted_partial_idx": { + "name": "workspace_file_folders_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file_folders\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_workspace_parent_name_active_unique": { + "name": "workspace_file_folders_workspace_parent_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"parent_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_file_folders\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_folders_user_id_user_id_fk": { + "name": "workspace_file_folders_user_id_user_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_folders_workspace_id_workspace_id_fk": { + "name": "workspace_file_folders_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_folders_parent_id_workspace_file_folders_id_fk": { + "name": "workspace_file_folders_parent_id_workspace_file_folders_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "workspace_file_folders", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_files": { + "name": "workspace_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_files_key_active_unique": { + "name": "workspace_files_key_active_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_folder_name_active_unique": { + "name": "workspace_files_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "original_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_display_name_unique": { + "name": "workspace_files_chat_display_name_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_key_idx": { + "name": "workspace_files_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_user_id_idx": { + "name": "workspace_files_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_id_idx": { + "name": "workspace_files_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_folder_id_idx": { + "name": "workspace_files_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_context_idx": { + "name": "workspace_files_context_idx", + "columns": [ + { + "expression": "context", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_id_idx": { + "name": "workspace_files_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_deleted_at_idx": { + "name": "workspace_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_deleted_partial_idx": { + "name": "workspace_files_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_files\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_files_user_id_user_id_fk": { + "name": "workspace_files_user_id_user_id_fk", + "tableFrom": "workspace_files", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_workspace_id_workspace_id_fk": { + "name": "workspace_files_workspace_id_workspace_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_folder_id_workspace_file_folders_id_fk": { + "name": "workspace_files_folder_id_workspace_file_folders_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace_file_folders", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_files_chat_id_copilot_chats_id_fk": { + "name": "workspace_files_chat_id_copilot_chats_id_fk", + "tableFrom": "workspace_files", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_block_map": { + "name": "workspace_fork_block_map", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_workflow_id": { + "name": "parent_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_block_id": { + "name": "parent_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_workflow_id": { + "name": "child_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_block_id": { + "name": "child_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_block_map_child_ws_parent_unique": { + "name": "workspace_fork_block_map_child_ws_parent_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_child_unique": { + "name": "workspace_fork_block_map_child_ws_child_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_parent_wf_idx": { + "name": "workspace_fork_block_map_child_ws_parent_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_child_wf_idx": { + "name": "workspace_fork_block_map_child_ws_child_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_block_map_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_block_map_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_block_map", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_dependent_value": { + "name": "workspace_fork_dependent_value", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_workflow_id": { + "name": "target_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sub_block_key": { + "name": "sub_block_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_dependent_value_child_ws_wf_idx": { + "name": "workspace_fork_dependent_value_child_ws_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_dependent_value_field_unique": { + "name": "workspace_fork_dependent_value_field_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sub_block_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_dependent_value", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_promote_run": { + "name": "workspace_fork_promote_run", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_workspace_id": { + "name": "target_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "workspace_fork_promote_direction", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_promote_run_child_ws_target_unique": { + "name": "workspace_fork_promote_run_child_ws_target_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_promote_run_target_ws_idx": { + "name": "workspace_fork_promote_run_target_ws_idx", + "columns": [ + { + "expression": "target_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_promote_run_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_promote_run_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_promote_run", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_fork_promote_run_created_by_user_id_fk": { + "name": "workspace_fork_promote_run_created_by_user_id_fk", + "tableFrom": "workspace_fork_promote_run", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_resource_map": { + "name": "workspace_fork_resource_map", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "workspace_fork_resource_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "parent_resource_id": { + "name": "parent_resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_resource_id": { + "name": "child_resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_resource_map_child_ws_idx": { + "name": "workspace_fork_resource_map_child_ws_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_resource_map_child_ws_type_idx": { + "name": "workspace_fork_resource_map_child_ws_type_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_resource_map_child_type_parent_unique": { + "name": "workspace_fork_resource_map_child_type_parent_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_resource_map_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_resource_map_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_resource_map", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_fork_resource_map_created_by_user_id_fk": { + "name": "workspace_fork_resource_map_created_by_user_id_fk", + "tableFrom": "workspace_fork_resource_map", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.academy_cert_status": { + "name": "academy_cert_status", + "schema": "public", + "values": ["active", "revoked", "expired"] + }, + "public.background_work_kind": { + "name": "background_work_kind", + "schema": "public", + "values": ["deployment_side_effects", "fork_content_copy", "fork_sync", "fork_rollback"] + }, + "public.background_work_status_value": { + "name": "background_work_status_value", + "schema": "public", + "values": ["pending", "processing", "completed", "completed_with_warnings", "failed"] + }, + "public.billing_blocked_reason": { + "name": "billing_blocked_reason", + "schema": "public", + "values": ["payment_failed", "dispute"] + }, + "public.billing_entity_type": { + "name": "billing_entity_type", + "schema": "public", + "values": ["user", "organization"] + }, + "public.chat_type": { + "name": "chat_type", + "schema": "public", + "values": ["mothership", "copilot"] + }, + "public.copilot_async_tool_status": { + "name": "copilot_async_tool_status", + "schema": "public", + "values": ["pending", "running", "completed", "failed", "cancelled", "delivered"] + }, + "public.copilot_run_status": { + "name": "copilot_run_status", + "schema": "public", + "values": ["active", "paused_waiting_for_tool", "resuming", "complete", "error", "cancelled"] + }, + "public.credential_member_role": { + "name": "credential_member_role", + "schema": "public", + "values": ["admin", "member"] + }, + "public.credential_member_status": { + "name": "credential_member_status", + "schema": "public", + "values": ["active", "pending", "revoked"] + }, + "public.credential_type": { + "name": "credential_type", + "schema": "public", + "values": ["oauth", "env_workspace", "env_personal", "service_account"] + }, + "public.data_drain_cadence": { + "name": "data_drain_cadence", + "schema": "public", + "values": ["hourly", "daily"] + }, + "public.data_drain_destination": { + "name": "data_drain_destination", + "schema": "public", + "values": ["s3", "gcs", "azure_blob", "datadog", "bigquery", "snowflake", "webhook"] + }, + "public.data_drain_run_status": { + "name": "data_drain_run_status", + "schema": "public", + "values": ["running", "success", "failed"] + }, + "public.data_drain_run_trigger": { + "name": "data_drain_run_trigger", + "schema": "public", + "values": ["cron", "manual"] + }, + "public.data_drain_source": { + "name": "data_drain_source", + "schema": "public", + "values": ["workflow_logs", "job_logs", "audit_logs", "copilot_chats", "copilot_runs"] + }, + "public.execution_large_value_reference_source": { + "name": "execution_large_value_reference_source", + "schema": "public", + "values": ["execution_log", "paused_snapshot"] + }, + "public.invitation_kind": { + "name": "invitation_kind", + "schema": "public", + "values": ["organization", "workspace"] + }, + "public.invitation_membership_intent": { + "name": "invitation_membership_intent", + "schema": "public", + "values": ["internal", "external"] + }, + "public.invitation_status": { + "name": "invitation_status", + "schema": "public", + "values": ["pending", "accepted", "rejected", "cancelled", "expired"] + }, + "public.permission_type": { + "name": "permission_type", + "schema": "public", + "values": ["admin", "write", "read"] + }, + "public.usage_log_category": { + "name": "usage_log_category", + "schema": "public", + "values": ["model", "fixed", "tool"] + }, + "public.usage_log_source": { + "name": "usage_log_source", + "schema": "public", + "values": [ + "workflow", + "wand", + "copilot", + "workspace-chat", + "mcp_copilot", + "mothership_block", + "knowledge-base", + "voice-input", + "enrichment" + ] + }, + "public.workspace_fork_promote_direction": { + "name": "workspace_fork_promote_direction", + "schema": "public", + "values": ["push", "pull"] + }, + "public.workspace_fork_resource_type": { + "name": "workspace_fork_resource_type", + "schema": "public", + "values": [ + "workflow", + "oauth_credential", + "service_account_credential", + "env_var", + "table", + "knowledge_base", + "knowledge_document", + "file", + "mcp_server", + "workflow_mcp_server", + "custom_tool", + "skill" + ] + }, + "public.workspace_mode": { + "name": "workspace_mode", + "schema": "public", + "values": ["personal", "organization", "grandfathered_shared"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index 8a9a55e7524..35d8dd533e8 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -1842,6 +1842,13 @@ "when": 1784252317428, "tag": "0263_workflow_fork_sync_excluded", "breakpoints": true + }, + { + "idx": 264, + "version": "7", + "when": 1784439900082, + "tag": "0264_managed_agent_connection", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 24157f8fa31..49674c37b52 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -2832,6 +2832,38 @@ export const outboxEvent = pgTable( }) ) +export const managedAgentConnection = pgTable( + 'managed_agent_connection', + { + id: text('id').primaryKey(), + workspaceId: text('workspace_id') + .notNull() + .references(() => workspace.id, { onDelete: 'cascade' }), + userId: text('user_id').references(() => user.id, { onDelete: 'set null' }), + name: text('name').notNull(), + /** + * Anthropic workspace API key (a `sk-ant-*` value) encrypted with the + * shared `encryptSecret` helper. Never returned to the client — list + * endpoints return a masked preview only, and the tool decrypts + * server-side per invocation. + */ + encryptedApiKey: text('encrypted_api_key').notNull(), + /** Timestamp of the last successful `GET /v1/agents` probe. */ + lastVerifiedAt: timestamp('last_verified_at'), + /** Truncated error text from the most recent failed verify, if any. */ + lastVerificationError: text('last_verification_error'), + createdAt: timestamp('created_at').notNull().defaultNow(), + updatedAt: timestamp('updated_at').notNull().defaultNow(), + }, + (table) => ({ + workspaceIdIdx: index('managed_agent_connection_workspace_id_idx').on(table.workspaceId), + workspaceNameUnique: uniqueIndex('managed_agent_connection_workspace_name_unique').on( + table.workspaceId, + table.name + ), + }) +) + export const mcpServers = pgTable( 'mcp_servers', { diff --git a/scripts/check-api-validation-contracts.ts b/scripts/check-api-validation-contracts.ts index 6e44238b79f..863ecee66d9 100644 --- a/scripts/check-api-validation-contracts.ts +++ b/scripts/check-api-validation-contracts.ts @@ -9,8 +9,8 @@ const QUERY_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/queries') const SELECTOR_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/selectors') const BASELINE = { - totalRoutes: 964, - zodRoutes: 964, + totalRoutes: 972, + zodRoutes: 972, nonZodRoutes: 0, } as const From 45278a5a0c10d87b254a178cc1752ebe059a8439 Mon Sep 17 00:00:00 2001 From: Ricardo DeMatos Date: Sun, 19 Jul 2026 22:42:31 -0700 Subject: [PATCH 03/11] fix(managed-agent): address MR feedback (decrypt escape, cancel, requires_action, public defaults) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four fixes to PR #5769 review comments. - `getDecryptedApiKey` throws when stored ciphertext cannot be decrypted (typically after an `ENCRYPTION_KEY` rotation). Wrapped the call in the tool's error-handling path so the failure returns the tool's declared `{success:false, output:{}, error:'...'}` shape with an actionable message pointing at Settings → Managed Agents → rotate, rather than an unhandled promise rejection. - Added the workflow's abort signal to `_context.abortSignal` on the `directExecution` path in `executeTool`, so any tool that wants to propagate cancellation can. This is opt-in — existing tools are unaffected. - Read the signal in `run_session.server.ts` and thread it into `createSession`, `sendUserMessage`, `openSessionStream`, `readSSEEvents`, and `listEventsAfter`. Every long-lived HTTP hop now cancels cleanly. - Guard all loop entrances with `signal?.aborted` and translate any abort-caused exception into a clean `{success:false, error:'aborted'}` response — no scary stack traces on cancel. - Server-side / vault-backed MCP tools legitimately hold the session in `requires_action` with no client-visible events until the tool finishes. My previous logic returned a terminal error on the first empty `listEventsAfter` catch-up, failing those workflows before the agent could complete. - New policy: `requires_action` idle is NEVER terminal by itself. If the outer reconnect loop finds no new events, back off (500ms, doubling to 5s max) and re-poll `listEventsAfter`. Only fail after a hard `MAX_REQUIRES_ACTION_WAIT_MS` cap (~5 min) — with a clear error message — or on an explicit `session.status_terminated` / `session.error`. - `EventState` gains `requiresActionEnteredAt` (first entry into the busy stretch) and `currentBackoffMs`; both reset when the session makes progress (non-empty catch-up). Reconnect ceiling raised to 60 since the loop now sleeps between polls. - Deleted `NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS` — any deployer who accidentally seeded a token into the JSON would leak it to every browser via the client bundle. - Replaced with server-only `MANAGED_AGENT_SELF_HOSTED_DEFAULTS` + `GET /api/managed-agent-defaults` route that returns the parsed rows. Values never enter the client bundle at build time. - Extended the `Table` sub-block component with an optional `fetchDefaultRows` async prop (mirrors the `fetchOptions` pattern used by comboboxes). The block config points at `fetchManagedAgentSelfHostedDefaults` in `subblock-options.ts`, which calls the new API route via `requestJson(contract, ...)`. - Removed the deleted function's tests; added coverage for the new API route (`app/api/managed-agent-defaults/route.test.ts` — 7 tests). - Updated `.env.example` docs to make the safety property explicit. Verified: 86 tests pass across the 6 managed-agent test files. Pre-existing tsc errors (integration-tag enum, environments-route type mismatch, etc.) are unchanged. --- apps/sim/.env.example | 2 +- .../api/managed-agent-defaults/route.test.ts | 84 +++++++++ .../app/api/managed-agent-defaults/route.ts | 52 ++++++ .../sub-block/components/table/table.tsx | 49 ++++- .../editor/components/sub-block/sub-block.tsx | 1 + .../blocks/managed_agent_self_hosted.test.ts | 68 +------ .../blocks/managed_agent_self_hosted.ts | 40 +---- apps/sim/blocks/types.ts | 7 + .../contracts/managed-agent-connections.ts | 25 +++ apps/sim/lib/core/config/env.ts | 3 +- .../lib/managed-agents/subblock-options.ts | 20 +++ apps/sim/tools/index.ts | 15 +- .../tools/managed_agent/run_session.server.ts | 168 +++++++++++++----- scripts/check-api-validation-contracts.ts | 4 +- 14 files changed, 385 insertions(+), 153 deletions(-) create mode 100644 apps/sim/app/api/managed-agent-defaults/route.test.ts create mode 100644 apps/sim/app/api/managed-agent-defaults/route.ts diff --git a/apps/sim/.env.example b/apps/sim/.env.example index 8dab23a2237..09e555043f6 100644 --- a/apps/sim/.env.example +++ b/apps/sim/.env.example @@ -64,7 +64,7 @@ API_ENCRYPTION_KEY=your_api_encryption_key # Use `openssl rand -hex 32` to gener # LITELLM_API_KEY= # Optional bearer token if your LiteLLM proxy requires auth # FIREWORKS_API_KEY= # Optional Fireworks AI API key for model listing # NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS=true # Set when using AWS default credential chain (IAM roles, ECS task roles, IRSA). Hides credential fields in Agent block UI. -# NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS='{"SOURCE_TYPE":"git","SOURCE_REF":"main"}' # JSON object of default session-metadata rows seeded into fresh Claude Managed Agents (self-hosted) blocks. Deployment-specific; keys map to whatever env vars your self-hosted agent sandbox reads. Absent = empty table. +# MANAGED_AGENT_SELF_HOSTED_DEFAULTS='{"SOURCE_TYPE":"git","SOURCE_REF":"main"}' # Server-only JSON object of default session-metadata rows seeded into fresh Claude Managed Agents (self-hosted) blocks. Deployment-specific; keys map to whatever env vars your self-hosted agent sandbox reads. Absent = empty table. Values are served via GET /api/managed-agent-defaults and never inlined into the client bundle — safe to include values that should not leak to the browser. # NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED=true # Reveal Memory Store + Memory Access fields on the Claude Managed Agents (self-hosted) block. Off by default: Claude self-hosted environments do not currently support the memory-store resource attach on the session API. Enable only if your self-hosted agent sandbox implements memory attach on its side. # AZURE_OPENAI_ENDPOINT= # Azure OpenAI endpoint (hides field in UI when set alongside NEXT_PUBLIC_AZURE_CONFIGURED) # AZURE_OPENAI_API_KEY= # Azure OpenAI API key diff --git a/apps/sim/app/api/managed-agent-defaults/route.test.ts b/apps/sim/app/api/managed-agent-defaults/route.test.ts new file mode 100644 index 00000000000..cdacfdcd41b --- /dev/null +++ b/apps/sim/app/api/managed-agent-defaults/route.test.ts @@ -0,0 +1,84 @@ +/** + * @vitest-environment node + */ +import { createMockRequest } from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { envState } = vi.hoisted(() => ({ + envState: { MANAGED_AGENT_SELF_HOSTED_DEFAULTS: undefined as string | undefined }, +})) + +vi.mock('@/lib/core/config/env', () => ({ + env: new Proxy({} as Record, { + get: (_target, key) => (envState as Record)[key as string], + }), +})) + +import { GET } from '@/app/api/managed-agent-defaults/route' + +describe('GET /api/managed-agent-defaults', () => { + beforeEach(() => { + envState.MANAGED_AGENT_SELF_HOSTED_DEFAULTS = undefined + }) + + it('returns an empty list when the env var is unset', async () => { + const res = await GET(createMockRequest('GET')) + expect(res.status).toBe(200) + const body = await res.json() + expect(body.selfHosted).toEqual([]) + }) + + it('returns an empty list for whitespace-only env values', async () => { + envState.MANAGED_AGENT_SELF_HOSTED_DEFAULTS = ' ' + const body = await (await GET(createMockRequest('GET'))).json() + expect(body.selfHosted).toEqual([]) + }) + + it('returns an empty list on invalid JSON', async () => { + envState.MANAGED_AGENT_SELF_HOSTED_DEFAULTS = '{not-json' + const body = await (await GET(createMockRequest('GET'))).json() + expect(body.selfHosted).toEqual([]) + }) + + it('returns an empty list on a JSON array (must be an object)', async () => { + envState.MANAGED_AGENT_SELF_HOSTED_DEFAULTS = '["a","b"]' + const body = await (await GET(createMockRequest('GET'))).json() + expect(body.selfHosted).toEqual([]) + }) + + it('coerces a valid JSON object into table-row shape', async () => { + envState.MANAGED_AGENT_SELF_HOSTED_DEFAULTS = JSON.stringify({ + FOO: 'bar', + BAZ: 'qux', + }) + const body = await (await GET(createMockRequest('GET'))).json() + expect(body.selfHosted).toEqual([ + { cells: { Key: 'FOO', Value: 'bar' } }, + { cells: { Key: 'BAZ', Value: 'qux' } }, + ]) + }) + + it('drops entries with a blank key', async () => { + envState.MANAGED_AGENT_SELF_HOSTED_DEFAULTS = JSON.stringify({ + '': 'dropped', + ' ': 'also dropped', + keep: 'yes', + }) + const body = await (await GET(createMockRequest('GET'))).json() + expect(body.selfHosted).toEqual([{ cells: { Key: 'keep', Value: 'yes' } }]) + }) + + it('coerces non-string values to their string form', async () => { + envState.MANAGED_AGENT_SELF_HOSTED_DEFAULTS = JSON.stringify({ + A: 1, + B: true, + C: null, + }) + const body = await (await GET(createMockRequest('GET'))).json() + expect(body.selfHosted).toEqual([ + { cells: { Key: 'A', Value: '1' } }, + { cells: { Key: 'B', Value: 'true' } }, + { cells: { Key: 'C', Value: '' } }, + ]) + }) +}) diff --git a/apps/sim/app/api/managed-agent-defaults/route.ts b/apps/sim/app/api/managed-agent-defaults/route.ts new file mode 100644 index 00000000000..a693649104e --- /dev/null +++ b/apps/sim/app/api/managed-agent-defaults/route.ts @@ -0,0 +1,52 @@ +import { createLogger } from '@sim/logger' +import { type NextRequest, NextResponse } from 'next/server' +import { env } from '@/lib/core/config/env' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' + +const logger = createLogger('ManagedAgentDefaultsAPI') + +interface DefaultRow { + cells: Record +} + +/** + * Read the JSON defaults for the Claude Managed Agents (self-hosted) + * block's Session parameters table from the SERVER-ONLY env var + * `MANAGED_AGENT_SELF_HOSTED_DEFAULTS`. Kept behind an API route (rather + * than a `NEXT_PUBLIC_*` var) so seeded values never leak into the + * client bundle at build time — deployers can safely put anything the + * self-hosted agent sandbox reads without inadvertently shipping it to + * the browser. + */ +function readSelfHostedDefaults(): DefaultRow[] { + const raw = env.MANAGED_AGENT_SELF_HOSTED_DEFAULTS + if (typeof raw !== 'string' || raw.trim().length === 0) return [] + try { + const parsed = JSON.parse(raw) + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return [] + const rows: DefaultRow[] = [] + for (const [key, value] of Object.entries(parsed as Record)) { + const k = typeof key === 'string' ? key.trim() : '' + if (!k) continue + const v = value == null ? '' : typeof value === 'string' ? value : String(value) + rows.push({ cells: { Key: k, Value: v } }) + } + return rows + } catch (error) { + logger.warn('Failed to parse MANAGED_AGENT_SELF_HOSTED_DEFAULTS', { error }) + return [] + } +} + +/** + * GET /api/managed-agent-defaults + * + * Returns the deployer-configured default rows the block picker seeds + * into a fresh Claude Managed Agents (self-hosted) block. No auth + * required — the response contains only default keys the deployer + * chose to expose to their workflow authors; there is no per-user or + * per-workspace state. + */ +export const GET = withRouteHandler(async (_request: NextRequest) => { + return NextResponse.json({ selfHosted: readSelfHostedDefaults() }, { status: 200 }) +}) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx index 7f0654e219d..a061295d012 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx @@ -31,6 +31,15 @@ interface TableProps { * missing columns fall back to `""`. Existing values are never overwritten. */ defaultRows?: Array<{ cells: Record }> + /** + * Optional async fetcher for seed rows — used when the block's default + * rows come from a server-side source (e.g. deployer-configured env vars + * read via an API route). Called once on first mount when the store is + * empty. If the fetcher rejects or returns an empty array, falls back to + * `defaultRows`, then to a single empty row. Same overwrite rules as + * `defaultRows`: existing store values are never touched. + */ + fetchDefaultRows?: () => Promise }>> } interface WorkflowTableRow { @@ -221,6 +230,7 @@ export function Table({ previewValue, disabled = false, defaultRows, + fetchDefaultRows, }: TableProps) { const activeSearchTarget = useActiveSearchTarget() const params = useParams() @@ -256,22 +266,47 @@ export function Table({ /** * Initialize the table when the component mounts and the store value is - * missing/empty. If the caller supplied `defaultRows`, seed those rows - * (any missing columns default to `""`); otherwise start with a single - * empty row. + * missing/empty. Precedence: + * 1. `fetchDefaultRows` (async) — used when defaults live server-side + * (e.g. deployer env-var read via an API route). + * 2. `defaultRows` (sync) — static seeds declared on the block config. + * 3. Single empty row — the pre-existing behavior. + * Existing store values are never touched. */ useEffect(() => { - if (!isPreview && !disabled && (!Array.isArray(storeValue) || storeValue.length === 0)) { + if (isPreview || disabled) return + if (Array.isArray(storeValue) && storeValue.length > 0) return + let cancelled = false + const seedWith = (rows: Array<{ cells: Record }> | undefined) => { + if (cancelled) return const seedRows: WorkflowTableRow[] = - Array.isArray(defaultRows) && defaultRows.length > 0 - ? defaultRows.map((row) => ({ + Array.isArray(rows) && rows.length > 0 + ? rows.map((row) => ({ id: generateId(), cells: { ...emptyCellsTemplate, ...(row.cells ?? {}) }, })) : [{ id: generateId(), cells: { ...emptyCellsTemplate } }] setStoreValue(seedRows) } - }, [isPreview, disabled, storeValue, setStoreValue, emptyCellsTemplate, defaultRows]) + if (fetchDefaultRows) { + fetchDefaultRows() + .then((rows) => seedWith(rows.length > 0 ? rows : defaultRows)) + .catch(() => seedWith(defaultRows)) + } else { + seedWith(defaultRows) + } + return () => { + cancelled = true + } + }, [ + isPreview, + disabled, + storeValue, + setStoreValue, + emptyCellsTemplate, + defaultRows, + fetchDefaultRows, + ]) // Ensure value is properly typed and initialized const rows = useMemo(() => { diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx index 6bd416ff8aa..134ca350944 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx @@ -754,6 +754,7 @@ function SubBlockComponent({ ? (config.defaultValue as Array<{ cells: Record }>) : undefined } + fetchDefaultRows={config.fetchDefaultRows} /> ) diff --git a/apps/sim/blocks/blocks/managed_agent_self_hosted.test.ts b/apps/sim/blocks/blocks/managed_agent_self_hosted.test.ts index 99094321ba6..125c2133b30 100644 --- a/apps/sim/blocks/blocks/managed_agent_self_hosted.test.ts +++ b/apps/sim/blocks/blocks/managed_agent_self_hosted.test.ts @@ -5,7 +5,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' const { envState } = vi.hoisted(() => ({ envState: { - NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS: undefined as string | undefined, NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED: undefined as string | undefined, }, })) @@ -20,75 +19,12 @@ vi.mock('@/lib/managed-agents/subblock-options', () => ({ fetchManagedAgentAgentOptions: vi.fn(), fetchManagedAgentConnectionOptions: vi.fn(), fetchManagedAgentMemoryStoreOptions: vi.fn(), + fetchManagedAgentSelfHostedDefaults: vi.fn(), fetchManagedAgentSelfHostedEnvironmentOptions: vi.fn(), fetchManagedAgentVaultOptions: vi.fn(), })) -import { - isSelfHostedMemoryEnabled, - readSessionMetadataDefaults, -} from '@/blocks/blocks/managed_agent_self_hosted' - -describe('readSessionMetadataDefaults', () => { - beforeEach(() => { - envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = undefined - }) - - it('returns [] when the env var is unset', () => { - envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = undefined - expect(readSessionMetadataDefaults()).toEqual([]) - }) - - it('returns [] when the env var is empty / whitespace-only', () => { - envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = '' - expect(readSessionMetadataDefaults()).toEqual([]) - envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = ' ' - expect(readSessionMetadataDefaults()).toEqual([]) - }) - - it('returns [] on invalid JSON', () => { - envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = '{not json' - expect(readSessionMetadataDefaults()).toEqual([]) - }) - - it('returns [] on a JSON array (must be an object of key/value pairs)', () => { - envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = '["a","b"]' - expect(readSessionMetadataDefaults()).toEqual([]) - }) - - it('coerces a valid JSON object into `{cells: {Key, Value}}` rows', () => { - envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = JSON.stringify({ - FOO: 'bar', - BAZ: 'qux', - }) - expect(readSessionMetadataDefaults()).toEqual([ - { cells: { Key: 'FOO', Value: 'bar' } }, - { cells: { Key: 'BAZ', Value: 'qux' } }, - ]) - }) - - it('drops entries with a blank key', () => { - envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = JSON.stringify({ - '': 'dropped', - ' ': 'also dropped', - keep: 'yes', - }) - expect(readSessionMetadataDefaults()).toEqual([{ cells: { Key: 'keep', Value: 'yes' } }]) - }) - - it('coerces non-string values to their string form', () => { - envState.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS = JSON.stringify({ - A: 1, - B: true, - C: null, - }) - expect(readSessionMetadataDefaults()).toEqual([ - { cells: { Key: 'A', Value: '1' } }, - { cells: { Key: 'B', Value: 'true' } }, - { cells: { Key: 'C', Value: '' } }, - ]) - }) -}) +import { isSelfHostedMemoryEnabled } from '@/blocks/blocks/managed_agent_self_hosted' describe('isSelfHostedMemoryEnabled', () => { beforeEach(() => { diff --git a/apps/sim/blocks/blocks/managed_agent_self_hosted.ts b/apps/sim/blocks/blocks/managed_agent_self_hosted.ts index 645b9698479..0250c9c701d 100644 --- a/apps/sim/blocks/blocks/managed_agent_self_hosted.ts +++ b/apps/sim/blocks/blocks/managed_agent_self_hosted.ts @@ -4,6 +4,7 @@ import { fetchManagedAgentAgentOptions, fetchManagedAgentConnectionOptions, fetchManagedAgentMemoryStoreOptions, + fetchManagedAgentSelfHostedDefaults, fetchManagedAgentSelfHostedEnvironmentOptions, fetchManagedAgentVaultOptions, } from '@/lib/managed-agents/subblock-options' @@ -57,36 +58,6 @@ const memorySubBlocks: SubBlockConfig[] = isSelfHostedMemoryEnabled() ] : [] -/** - * Read the JSON defaults for the self-hosted metadata table from - * `NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS`. Kept out of source - * so deployers can seed the block with the keys their self-hosted - * agent sandbox reads, without the values living in the repo. - * - * Expected shape: a JSON object `{"KEY": "VALUE", ...}`. Non-string - * values are coerced to their string form. Anything invalid (missing, - * unparseable, non-object) falls back to an empty seed — the block - * still renders, users see a single blank row. - */ -export function readSessionMetadataDefaults(): Array<{ cells: Record }> { - const raw = env.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS - if (typeof raw !== 'string' || raw.trim().length === 0) return [] - try { - const parsed = JSON.parse(raw) - if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return [] - const rows: Array<{ cells: Record }> = [] - for (const [key, value] of Object.entries(parsed)) { - const k = typeof key === 'string' ? key.trim() : '' - if (!k) continue - const v = value == null ? '' : typeof value === 'string' ? value : String(value) - rows.push({ cells: { Key: k, Value: v } }) - } - return rows - } catch { - return [] - } -} - /** * Claude Managed Agents block — self-hosted variant. * @@ -181,15 +152,16 @@ export const ManagedAgentSelfHostedBlock: BlockConfig = { // Session metadata forwarded to the self-hosted agent sandbox as // env vars. The set of supported keys is deployment-specific and // lives with the deployer, not in this repo. Seed rows come from - // `NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS` (see - // `readSessionMetadataDefaults` above); leave empty to start with - // a single blank row. + // the server-only `MANAGED_AGENT_SELF_HOSTED_DEFAULTS` env var, + // fetched via `/api/managed-agent-defaults` — the values never + // enter the client bundle, so deployers can safely include + // anything their sandbox reads. id: 'sessionParameters', title: 'Session parameters', type: 'table', required: false, columns: ['Key', 'Value'], - defaultValue: readSessionMetadataDefaults(), + fetchDefaultRows: fetchManagedAgentSelfHostedDefaults, description: 'Key/value pairs forwarded to the self-hosted agent sandbox as environment variables. Supported keys depend on your deployment — consult your deployment docs. Value cells support / references.', }, diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts index 842654e94b0..d6764d929c6 100644 --- a/apps/sim/blocks/types.ts +++ b/apps/sim/blocks/types.ts @@ -285,6 +285,13 @@ export interface SubBlockConfig { } }) defaultValue?: string | number | boolean | Record | Array + /** + * Optional async fetcher for a `table` subblock's initial rows — used + * when defaults come from a server-side source (e.g. deployer-configured + * env vars) and must not be inlined into the client bundle. Called once + * on first mount when the store is empty. See `Table.fetchDefaultRows`. + */ + fetchDefaultRows?: () => Promise }>> options?: | { label: string diff --git a/apps/sim/lib/api/contracts/managed-agent-connections.ts b/apps/sim/lib/api/contracts/managed-agent-connections.ts index d9f40ecbcd8..714de70b502 100644 --- a/apps/sim/lib/api/contracts/managed-agent-connections.ts +++ b/apps/sim/lib/api/contracts/managed-agent-connections.ts @@ -204,3 +204,28 @@ export const listManagedAgentMemoryStoresContract = defineRouteContract({ }), }, }) + +/** + * Server-side default rows the Claude Managed Agents (self-hosted) block + * seeds into its Session parameters table. Deployers configure this via + * the server-only `MANAGED_AGENT_SELF_HOSTED_DEFAULTS` env var (a JSON + * object) so seeded values — which may reference internal keys — never + * touch the client bundle. + */ +export const managedAgentSelfHostedDefaultRowSchema = z.object({ + cells: z.record(z.string(), z.string()), +}) +export type ManagedAgentSelfHostedDefaultRow = z.output< + typeof managedAgentSelfHostedDefaultRowSchema +> + +export const getManagedAgentDefaultsContract = defineRouteContract({ + method: 'GET', + path: '/api/managed-agent-defaults', + response: { + mode: 'json', + schema: z.object({ + selfHosted: z.array(managedAgentSelfHostedDefaultRowSchema), + }), + }, +}) diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index ff561e9ffd4..7e598c8c545 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -149,6 +149,7 @@ export const env = createEnv({ OPENAI_API_KEY_3: z.string().min(1).optional(), // Additional OpenAI API key for load balancing MISTRAL_API_KEY: z.string().min(1).optional(), // Mistral AI API key MANAGED_AGENT_DEBUG_PAYLOAD: z.string().optional(), // When "1" | "true" | "yes", the Managed Agent workflow-block tool logs the full session-create payload (never includes the api key) so you can inspect memory / vault / metadata routing + MANAGED_AGENT_SELF_HOSTED_DEFAULTS: z.string().optional(), // Server-only JSON object of default session-metadata key/value pairs the Claude Managed Agents (self-hosted) block seeds into fresh workflows. Example: '{"SOURCE_TYPE":"git","SOURCE_REF":"main"}'. Never exposed to the client bundle — read via GET /api/managed-agent-defaults. ANTHROPIC_API_KEY_1: z.string().min(1).optional(), // Primary Anthropic Claude API key ANTHROPIC_API_KEY_2: z.string().min(1).optional(), // Additional Anthropic API key for load balancing ANTHROPIC_API_KEY_3: z.string().min(1).optional(), // Additional Anthropic API key for load balancing @@ -538,7 +539,6 @@ export const env = createEnv({ NEXT_PUBLIC_SUPPORT_EMAIL: z.string().email().optional(), // Custom support email NEXT_PUBLIC_E2B_ENABLED: z.string().optional(), - NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS: z.string().optional(), // JSON object of default session-metadata key/value pairs seeded into fresh Claude Managed Agents (self-hosted) blocks. Example: '{"SOURCE_TYPE":"git","SOURCE_REF":"main"}'. Absent = empty table. NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED: z.string().optional(), // When "1"|"true"|"yes", reveal the Memory Store + Memory Access fields on the Claude Managed Agents (self-hosted) block. Off by default because Claude self-hosted environments do not currently support memory-store attach. NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS: z.string().optional(), // Hide Bedrock credential fields when deployment uses AWS default credential chain (IAM roles, instance profiles, ECS task roles, IRSA) NEXT_PUBLIC_AZURE_CONFIGURED: z.string().optional(), // Hide Azure credential fields when endpoint/key/version are pre-configured server-side @@ -613,7 +613,6 @@ export const env = createEnv({ NEXT_PUBLIC_EMAIL_PASSWORD_SIGNUP_ENABLED: process.env.NEXT_PUBLIC_EMAIL_PASSWORD_SIGNUP_ENABLED, NEXT_PUBLIC_TURNSTILE_SITE_KEY: process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY, NEXT_PUBLIC_E2B_ENABLED: process.env.NEXT_PUBLIC_E2B_ENABLED, - NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS: process.env.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS, NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED: process.env.NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED, NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS: process.env.NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS, NEXT_PUBLIC_AZURE_CONFIGURED: process.env.NEXT_PUBLIC_AZURE_CONFIGURED, diff --git a/apps/sim/lib/managed-agents/subblock-options.ts b/apps/sim/lib/managed-agents/subblock-options.ts index d753db8a014..e306c9e7f6a 100644 --- a/apps/sim/lib/managed-agents/subblock-options.ts +++ b/apps/sim/lib/managed-agents/subblock-options.ts @@ -1,10 +1,12 @@ import { requestJson } from '@/lib/api/client/request' import { + getManagedAgentDefaultsContract, listManagedAgentAgentsContract, listManagedAgentConnectionsContract, listManagedAgentEnvironmentsContract, listManagedAgentMemoryStoresContract, listManagedAgentVaultsContract, + type ManagedAgentSelfHostedDefaultRow, } from '@/lib/api/contracts' import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' @@ -176,3 +178,21 @@ export async function fetchManagedAgentMemoryStoreOptions( return [] } } + +/** + * Fetch the deployer-configured default rows the Claude Managed Agents + * (self-hosted) block seeds into its Session parameters table. Values + * live in the server-only env var `MANAGED_AGENT_SELF_HOSTED_DEFAULTS` + * and are read via the `/api/managed-agent-defaults` route so they + * never enter the client bundle at build time. + */ +export async function fetchManagedAgentSelfHostedDefaults(): Promise< + ManagedAgentSelfHostedDefaultRow[] +> { + try { + const { selfHosted } = await requestJson(getManagedAgentDefaultsContract, {}) + return selfHosted + } catch { + return [] + } +} diff --git a/apps/sim/tools/index.ts b/apps/sim/tools/index.ts index b3c8a4f889f..58d825c0e14 100644 --- a/apps/sim/tools/index.ts +++ b/apps/sim/tools/index.ts @@ -1245,7 +1245,20 @@ export async function executeTool( // Check for direct execution (no HTTP request needed) if (tool.directExecution) { logger.info(`[${requestId}] Using directExecution for ${toolId}`) - const result = await tool.directExecution(contextParams) + // Attach the workflow's abort signal to `_context.abortSignal` so + // direct-execution tools (Managed Agent session, etc.) can propagate + // cancellation into long-lived operations like SSE streams. Non-abort + // callers get the same context they had before. + const directExecutionParams = effectiveSignal + ? { + ...contextParams, + _context: { + ...(contextParams._context as Record | undefined), + abortSignal: effectiveSignal, + }, + } + : contextParams + const result = await tool.directExecution(directExecutionParams) // Apply post-processing if available and not skipped let finalResult = result diff --git a/apps/sim/tools/managed_agent/run_session.server.ts b/apps/sim/tools/managed_agent/run_session.server.ts index 0d959b51222..16373102d84 100644 --- a/apps/sim/tools/managed_agent/run_session.server.ts +++ b/apps/sim/tools/managed_agent/run_session.server.ts @@ -2,6 +2,7 @@ import 'server-only' import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' +import { sleep } from '@sim/utils/helpers' import { readSSEEvents } from '@/lib/core/utils/sse' import { getDecryptedApiKey } from '@/lib/managed-agents/connections' import { env } from '@/lib/core/config/env' @@ -43,7 +44,19 @@ import type { ToolResponse } from '@/tools/types' const logger = createLogger('ManagedAgentRunSessionServer') /** Upper bound on stream-close → catch-up → reopen cycles per invocation. */ -const MAX_RECONNECT_ITERATIONS = 8 +const MAX_RECONNECT_ITERATIONS = 60 + +/** + * Backoff for `listEventsAfter` polling while the session is legitimately + * busy (server-side / MCP tools stay in `requires_action` with no + * client-visible events until they finish). Starts at 500ms, doubles up + * to 5s, capped so a stuck session terminates instead of spinning + * forever — see MAX_REQUIRES_ACTION_WAIT_MS. + */ +const REQUIRES_ACTION_BACKOFF_START_MS = 500 +const REQUIRES_ACTION_BACKOFF_MAX_MS = 5000 +/** Hard cap on total time spent waiting on a busy session — ~5 minutes. */ +const MAX_REQUIRES_ACTION_WAIT_MS = 5 * 60 * 1000 const impl: ManagedAgentServerImpl = async ( params: ManagedAgentRunSessionParams @@ -51,6 +64,14 @@ const impl: ManagedAgentServerImpl = async ( const startedAt = new Date() const context = params._context const workspaceId = context?.workspaceId + // Workflow abort signal — set by `executeTool` on the directExecution + // path. When the workflow is cancelled we forward this to the stream + // fetch, the SSE reader, and the catch-up pagination so the HTTP + // connection is released instead of leaked. + const signal = + context && typeof context === 'object' && 'abortSignal' in context + ? (context as { abortSignal?: AbortSignal }).abortSignal + : undefined if (!workspaceId) { return { success: false, @@ -58,8 +79,30 @@ const impl: ManagedAgentServerImpl = async ( error: 'Missing workspaceId in tool context — is this workflow tied to a workspace?', } } + if (signal?.aborted) { + return { success: false, output: {}, error: 'aborted' } + } - const apiKey = await getDecryptedApiKey({ id: params.connection, workspaceId }) + let apiKey: string | null + try { + apiKey = await getDecryptedApiKey({ id: params.connection, workspaceId }) + } catch (error) { + // The stored ciphertext could not be decrypted — most commonly a rotated + // ENCRYPTION_KEY invalidating an existing row. Translate the exception + // into the tool's declared error shape rather than rejecting the promise + // (which would surface as an unhandled workflow failure). + logger.warn('Failed to decrypt Managed Agent api key', { + workspaceId, + connectionId: params.connection, + error: getErrorMessage(error), + }) + return { + success: false, + output: {}, + error: + 'Managed Agent connection could not be decrypted — the workspace encryption key may have rotated. Rotate the API key in Settings → Managed Agents to re-encrypt.', + } + } if (!apiKey) { return { success: false, @@ -103,6 +146,7 @@ const impl: ManagedAgentServerImpl = async ( ...(sessionParameters && Object.keys(sessionParameters).length > 0 ? { sessionParameters } : {}), + ...(signal ? { signal } : {}), } // Opt-in payload dump for debugging session-create request shape. @@ -140,12 +184,14 @@ const impl: ManagedAgentServerImpl = async ( }) try { - await sendUserMessage({ apiKey, sessionId, text: userMessage }) + await sendUserMessage({ apiKey, sessionId, text: userMessage, signal }) } catch (error) { return { success: false, output: { sessionId }, - error: getErrorMessage(error, 'Failed to send user message'), + error: signal?.aborted + ? 'aborted' + : getErrorMessage(error, 'Failed to send user message'), } } @@ -154,6 +200,8 @@ const impl: ManagedAgentServerImpl = async ( const eventState: EventState = { lastCustomToolResponseAt: 0, lastRequiresActionAt: 0, + requiresActionEnteredAt: 0, + currentBackoffMs: REQUIRES_ACTION_BACKOFF_START_MS, } let lastEventId: string | null = null let terminal: { status: 'complete' | 'error'; reason?: string } | null = null @@ -164,8 +212,10 @@ const impl: ManagedAgentServerImpl = async ( iteration < MAX_RECONNECT_ITERATIONS && !terminal; iteration++ ) { - const streamResp = await openSessionStream({ apiKey, sessionId }) + if (signal?.aborted) break + const streamResp = await openSessionStream({ apiKey, sessionId, signal }) await readSSEEvents(streamResp, { + signal, onParseError: (raw, err) => { logger.warn('Un-parseable SSE line', { sessionId, @@ -196,34 +246,39 @@ const impl: ManagedAgentServerImpl = async ( }) if (terminal) break + if (signal?.aborted) break - const missed = await listEventsAfter({ apiKey, sessionId, afterId: lastEventId }) + const missed = await listEventsAfter({ apiKey, sessionId, afterId: lastEventId, signal }) if (missed.length === 0) { - // Session went idle with no new events to catch up on. Three cases: - // 1. `requires_action` + we replied to the `custom_tool_use` → - // the agent is chewing on our reply; reconnect once more. - // Reset the marker so we cap this at one retry per cycle. - // 2. `requires_action` + we did NOT reply (typically a stuck - // server-side / MCP tool the agent couldn't complete) → - // surface a clear terminal error instead of silently - // succeeding with empty text. - // 3. No pending action → clean completion (usually already + // Session went idle with no new events to catch up on. Two cases: + // 1. `requires_action` is currently outstanding — either we + // already sent our tool-result reply and the agent is + // processing it, or a server-side / MCP tool is executing + // on the platform side. In BOTH cases the session is + // legitimately busy; back off and re-poll rather than + // failing (server-side tools can stay in requires_action + // for tens of seconds with no client-visible events). + // 2. No pending action → clean completion (usually already // hit via `end_turn` earlier; this is the fallback path). if (eventState.lastRequiresActionAt > 0) { - if (eventState.lastCustomToolResponseAt >= eventState.lastRequiresActionAt) { - eventState.lastRequiresActionAt = 0 - continue + const waitedMs = Date.now() - eventState.requiresActionEnteredAt + if (waitedMs >= MAX_REQUIRES_ACTION_WAIT_MS) { + terminal = { + status: 'error', + reason: `Session paused (requires_action) for over ${Math.floor(MAX_REQUIRES_ACTION_WAIT_MS / 1000)}s without progress — the pending tool call could not complete. Check MCP server / vault configuration on Claude Platform.`, + } + break } - terminal = { - status: 'error', - reason: - 'Session paused (requires_action) — the agent invoked a tool that could not complete. Check MCP server / vault configuration on Claude Platform.', - } - break + await sleep(nextBackoffMs(eventState)) + continue } terminal = { status: 'complete' } break } + // Session made progress — reset the requires_action wait clock + // and backoff so the next pause starts fresh. + eventState.requiresActionEnteredAt = 0 + eventState.currentBackoffMs = REQUIRES_ACTION_BACKOFF_START_MS for (const event of missed) { const eventId = (event as { id?: string }).id if (eventId && seenIds.has(eventId)) continue @@ -245,6 +300,16 @@ const impl: ManagedAgentServerImpl = async ( } } } catch (error) { + // A workflow abort surfaces as an AbortError / DOMException here — do + // not log that as a stream failure, and return a clean 'aborted' + // response so the executor can attribute cancellation correctly. + if (signal?.aborted) { + return { + success: false, + output: { sessionId, content: assistantText.value }, + error: 'aborted', + } + } logger.error('Managed agent stream failed', { sessionId, error: getErrorMessage(error) }) return { success: false, @@ -260,6 +325,15 @@ const impl: ManagedAgentServerImpl = async ( duration: endedAt.getTime() - startedAt.getTime(), } + if (signal?.aborted) { + return { + success: false, + output: { sessionId, content: assistantText.value } satisfies ManagedAgentRunSessionOutput, + error: 'aborted', + timing, + } + } + if (!terminal || terminal.status === 'error') { return { success: false, @@ -280,14 +354,30 @@ const impl: ManagedAgentServerImpl = async ( registerManagedAgentServerImpl(impl) /** - * Tracks how far along the session is in resolving a `requires_action` - * pause. If we replied to the pending `custom_tool_use` before the idle - * event fires, the session will unpause on its own — we must NOT treat - * the idle-with-requires-action as terminal in that case. + * Tracks progress across `requires_action` idle events. Server-side or + * MCP tools can legitimately keep the session in `requires_action` for + * tens of seconds with no client-visible events; we back off + re-poll + * `listEventsAfter` rather than treating catch-up silence as failure. + * `lastCustomToolResponseAt` retains historical use for tests that check + * "did we reply to a `custom_tool_use`", but the retry policy now applies + * uniformly regardless. */ interface EventState { lastCustomToolResponseAt: number lastRequiresActionAt: number + /** Timestamp of the first `requires_action` in the current busy stretch. */ + requiresActionEnteredAt: number + /** Next backoff to use if we re-poll on catch-up silence. */ + currentBackoffMs: number +} + +function nextBackoffMs(state: EventState): number { + const next = state.currentBackoffMs + state.currentBackoffMs = Math.min( + Math.max(next * 2, REQUIRES_ACTION_BACKOFF_START_MS), + REQUIRES_ACTION_BACKOFF_MAX_MS + ) + return next } async function handleEvent(args: { @@ -356,19 +446,17 @@ async function handleEvent(args: { if (stop === 'end_turn') return { status: 'complete' } if (stop === 'retries_exhausted') return { status: 'error', reason: 'retries_exhausted' } if (stop === 'requires_action') { - // Session paused for a pending action. If we've already replied to a - // `custom_tool_use` since the last pause, the session will resume on - // its own — return null so the reconnect loop reopens the stream and - // picks up the follow-on events. - eventState.lastRequiresActionAt = Date.now() - if (eventState.lastCustomToolResponseAt >= eventState.lastRequiresActionAt) { - return null + // Session paused for a pending action. Never terminal: either + // (a) we already replied to a `custom_tool_use` and the agent is + // processing it, or (b) a server-side / MCP tool is executing on + // the platform side. Both cases resolve on their own; the outer + // reconnect loop backs off + re-polls `listEventsAfter` until + // either new events arrive or the total-wait cap is hit. + const now = Date.now() + if (eventState.requiresActionEnteredAt === 0) { + eventState.requiresActionEnteredAt = now } - // No reply was sent — likely a server-side tool (MCP) call the agent - // itself will drive, or a genuinely stuck session. Return null too - // so the reconnect loop reopens the stream; if the session is truly - // stuck, `listEventsAfter` returning empty on the next iteration will - // finalize the run as complete with whatever text we have. + eventState.lastRequiresActionAt = now return null } return { status: 'error', reason: stop ?? 'idle_without_stop_reason' } diff --git a/scripts/check-api-validation-contracts.ts b/scripts/check-api-validation-contracts.ts index 863ecee66d9..7bbfe14e8c6 100644 --- a/scripts/check-api-validation-contracts.ts +++ b/scripts/check-api-validation-contracts.ts @@ -9,8 +9,8 @@ const QUERY_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/queries') const SELECTOR_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/selectors') const BASELINE = { - totalRoutes: 972, - zodRoutes: 972, + totalRoutes: 973, + zodRoutes: 973, nonZodRoutes: 0, } as const From e51d388039894df64e9cf7e21f08d0f54aa50c45 Mon Sep 17 00:00:00 2001 From: Ricardo DeMatos Date: Sun, 19 Jul 2026 23:06:54 -0700 Subject: [PATCH 04/11] fix(managed-agent): proxy routes no longer 500 on decrypt failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Followup on PR #5769 review: Cursor Bugbot flagged that `getDecryptedApiKey` still throws on ciphertext decrypt failure, so the five proxy routes (agents / environments / environment-detail / vaults / memory-stores) surface a 500 to the client instead of a controlled response. My prior tool-level try/catch only handled the workflow-block invocation path. Change: `getDecryptedApiKey` now returns a discriminated result — `{ ok: true, apiKey } | { ok: false, reason: 'not_found' | 'decrypt_failed' }` — so callers can render each failure mode with the correct status: - `not_found` → 404 "Connection not found" (unchanged) - `decrypt_failed` → 502 with an actionable "rotate the API key to re-encrypt" hint, matching the tool-side message New `apiKeyFailureResponse` helper in `lib/managed-agents/proxy.ts` keeps the mapping in one place — each proxy route now reads: const keyResult = await getDecryptedApiKey({ id, workspaceId }) if (!keyResult.ok) return apiKeyFailureResponse(keyResult) const apiKey = keyResult.apiKey Tool call site (`run_session.server.ts`) collapses its previous try/catch to the same `keyResult.ok` check — cleaner and shares the same error phrasing. Tests updated: `getDecryptedApiKey` cases now assert the shape; added a third case that mocks `decryptSecret` to reject and asserts `{ ok: false, reason: 'decrypt_failed' }`. All 87 tests pass. --- .../[id]/agents/route.ts | 8 ++--- .../[id]/environments/[envId]/route.ts | 8 ++--- .../[id]/environments/route.ts | 8 ++--- .../[id]/memory-stores/route.ts | 8 ++--- .../[id]/vaults/route.ts | 8 ++--- .../lib/managed-agents/connections.test.ts | 22 ++++++++++---- apps/sim/lib/managed-agents/connections.ts | 29 +++++++++++++++---- apps/sim/lib/managed-agents/proxy.ts | 23 +++++++++++++++ .../tools/managed_agent/run_session.server.ts | 27 ++++------------- 9 files changed, 89 insertions(+), 52 deletions(-) diff --git a/apps/sim/app/api/managed-agent-connections/[id]/agents/route.ts b/apps/sim/app/api/managed-agent-connections/[id]/agents/route.ts index 4bc4e1c9855..117904b6e66 100644 --- a/apps/sim/app/api/managed-agent-connections/[id]/agents/route.ts +++ b/apps/sim/app/api/managed-agent-connections/[id]/agents/route.ts @@ -12,6 +12,7 @@ import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { getDecryptedApiKey } from '@/lib/managed-agents/connections' import { type AnthropicListPage, + apiKeyFailureResponse, ManagedAgentProxyError, proxyManagedAgentsGet, } from '@/lib/managed-agents/proxy' @@ -56,10 +57,9 @@ export const GET = withRouteHandler(async (request: NextRequest, context: RouteC return NextResponse.json({ error: 'Access denied' }, { status: 403 }) } - const apiKey = await getDecryptedApiKey({ id, workspaceId }) - if (!apiKey) { - return NextResponse.json({ error: 'Connection not found' }, { status: 404 }) - } + const keyResult = await getDecryptedApiKey({ id, workspaceId }) + if (!keyResult.ok) return apiKeyFailureResponse(keyResult) + const apiKey = keyResult.apiKey try { const body = await proxyManagedAgentsGet>( diff --git a/apps/sim/app/api/managed-agent-connections/[id]/environments/[envId]/route.ts b/apps/sim/app/api/managed-agent-connections/[id]/environments/[envId]/route.ts index d88c7e89409..0f49cae4cc1 100644 --- a/apps/sim/app/api/managed-agent-connections/[id]/environments/[envId]/route.ts +++ b/apps/sim/app/api/managed-agent-connections/[id]/environments/[envId]/route.ts @@ -11,6 +11,7 @@ import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { getDecryptedApiKey } from '@/lib/managed-agents/connections' import { + apiKeyFailureResponse, ManagedAgentProxyError, proxyManagedAgentsGet, } from '@/lib/managed-agents/proxy' @@ -60,10 +61,9 @@ export const GET = withRouteHandler(async (request: NextRequest, context: RouteC return NextResponse.json({ error: 'Access denied' }, { status: 403 }) } - const apiKey = await getDecryptedApiKey({ id, workspaceId }) - if (!apiKey) { - return NextResponse.json({ error: 'Connection not found' }, { status: 404 }) - } + const keyResult = await getDecryptedApiKey({ id, workspaceId }) + if (!keyResult.ok) return apiKeyFailureResponse(keyResult) + const apiKey = keyResult.apiKey try { const body = await proxyManagedAgentsGet( diff --git a/apps/sim/app/api/managed-agent-connections/[id]/environments/route.ts b/apps/sim/app/api/managed-agent-connections/[id]/environments/route.ts index df7994fe86c..7d4e2330d02 100644 --- a/apps/sim/app/api/managed-agent-connections/[id]/environments/route.ts +++ b/apps/sim/app/api/managed-agent-connections/[id]/environments/route.ts @@ -12,6 +12,7 @@ import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { getDecryptedApiKey } from '@/lib/managed-agents/connections' import { type AnthropicListPage, + apiKeyFailureResponse, ManagedAgentProxyError, proxyManagedAgentsGet, } from '@/lib/managed-agents/proxy' @@ -56,10 +57,9 @@ export const GET = withRouteHandler(async (request: NextRequest, context: RouteC return NextResponse.json({ error: 'Access denied' }, { status: 403 }) } - const apiKey = await getDecryptedApiKey({ id, workspaceId }) - if (!apiKey) { - return NextResponse.json({ error: 'Connection not found' }, { status: 404 }) - } + const keyResult = await getDecryptedApiKey({ id, workspaceId }) + if (!keyResult.ok) return apiKeyFailureResponse(keyResult) + const apiKey = keyResult.apiKey try { const body = await proxyManagedAgentsGet>( diff --git a/apps/sim/app/api/managed-agent-connections/[id]/memory-stores/route.ts b/apps/sim/app/api/managed-agent-connections/[id]/memory-stores/route.ts index 931cad1c8e1..b7c011e397b 100644 --- a/apps/sim/app/api/managed-agent-connections/[id]/memory-stores/route.ts +++ b/apps/sim/app/api/managed-agent-connections/[id]/memory-stores/route.ts @@ -11,6 +11,7 @@ import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { getDecryptedApiKey } from '@/lib/managed-agents/connections' import { + apiKeyFailureResponse, ManagedAgentProxyError, proxyManagedAgentsGet, } from '@/lib/managed-agents/proxy' @@ -69,10 +70,9 @@ export const GET = withRouteHandler(async (request: NextRequest, context: RouteC return NextResponse.json({ error: 'Access denied' }, { status: 403 }) } - const apiKey = await getDecryptedApiKey({ id, workspaceId }) - if (!apiKey) { - return NextResponse.json({ error: 'Connection not found' }, { status: 404 }) - } + const keyResult = await getDecryptedApiKey({ id, workspaceId }) + if (!keyResult.ok) return apiKeyFailureResponse(keyResult) + const apiKey = keyResult.apiKey try { const body = await proxyManagedAgentsGet( diff --git a/apps/sim/app/api/managed-agent-connections/[id]/vaults/route.ts b/apps/sim/app/api/managed-agent-connections/[id]/vaults/route.ts index 092f8cb5220..431947fe524 100644 --- a/apps/sim/app/api/managed-agent-connections/[id]/vaults/route.ts +++ b/apps/sim/app/api/managed-agent-connections/[id]/vaults/route.ts @@ -12,6 +12,7 @@ import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { getDecryptedApiKey } from '@/lib/managed-agents/connections' import { type AnthropicListPage, + apiKeyFailureResponse, ManagedAgentProxyError, proxyManagedAgentsGet, } from '@/lib/managed-agents/proxy' @@ -54,10 +55,9 @@ export const GET = withRouteHandler(async (request: NextRequest, context: RouteC return NextResponse.json({ error: 'Access denied' }, { status: 403 }) } - const apiKey = await getDecryptedApiKey({ id, workspaceId }) - if (!apiKey) { - return NextResponse.json({ error: 'Connection not found' }, { status: 404 }) - } + const keyResult = await getDecryptedApiKey({ id, workspaceId }) + if (!keyResult.ok) return apiKeyFailureResponse(keyResult) + const apiKey = keyResult.apiKey try { const body = await proxyManagedAgentsGet>( diff --git a/apps/sim/lib/managed-agents/connections.test.ts b/apps/sim/lib/managed-agents/connections.test.ts index 41d49ebb34f..58414a7efd5 100644 --- a/apps/sim/lib/managed-agents/connections.test.ts +++ b/apps/sim/lib/managed-agents/connections.test.ts @@ -169,19 +169,29 @@ describe('getDecryptedApiKey', () => { dbState.whereCalls = [] }) - it('returns the decrypted plaintext when the connection exists', async () => { + it('returns {ok:true, apiKey} when the connection exists', async () => { dbState.selectResults = [[baseRow]] - const key = await getDecryptedApiKey({ id: 'conn_1', workspaceId: 'ws_A' }) - expect(key).toBe(PLAINTEXT_KEY) + const result = await getDecryptedApiKey({ id: 'conn_1', workspaceId: 'ws_A' }) + expect(result).toEqual({ ok: true, apiKey: PLAINTEXT_KEY }) // The DB lookup must include BOTH id and workspaceId (no cross-workspace read). expect(JSON.stringify(dbState.whereCalls[0])).toContain('conn_1') expect(JSON.stringify(dbState.whereCalls[0])).toContain('ws_A') }) - it('returns null when the connection does not exist for that workspace', async () => { + it('returns {ok:false, reason:"not_found"} when the connection does not exist', async () => { dbState.selectResults = [[]] - const key = await getDecryptedApiKey({ id: 'conn_1', workspaceId: 'ws_A' }) - expect(key).toBeNull() + const result = await getDecryptedApiKey({ id: 'conn_1', workspaceId: 'ws_A' }) + expect(result).toEqual({ ok: false, reason: 'not_found' }) + }) + + it('returns {ok:false, reason:"decrypt_failed"} when the ciphertext cannot be decrypted', async () => { + // Simulates the post-ENCRYPTION_KEY-rotation case: row exists but the + // stored blob no longer decrypts. Callers must render this as an + // actionable 502 rather than a 500. + dbState.selectResults = [[baseRow]] + mockDecryptSecret.mockRejectedValueOnce(new Error('bad auth tag')) + const result = await getDecryptedApiKey({ id: 'conn_1', workspaceId: 'ws_A' }) + expect(result).toEqual({ ok: false, reason: 'decrypt_failed' }) }) }) diff --git a/apps/sim/lib/managed-agents/connections.ts b/apps/sim/lib/managed-agents/connections.ts index 5f4551cb3a5..610f6cefab5 100644 --- a/apps/sim/lib/managed-agents/connections.ts +++ b/apps/sim/lib/managed-agents/connections.ts @@ -86,19 +86,38 @@ export async function getConnection(params: { return rows[0] ?? null } +/** + * Result of {@link getDecryptedApiKey}. Callers must distinguish the two + * failure modes so they can surface the right message: + * - `not_found`: the row does not exist for this workspace — 404. + * - `decrypt_failed`: the row exists but its ciphertext cannot be + * decoded (typically after an `ENCRYPTION_KEY` rotation) — a + * controlled 502 with a "rotate the API key to re-encrypt" hint. + */ +export type DecryptedApiKeyResult = + | { ok: true; apiKey: string } + | { ok: false; reason: 'not_found' | 'decrypt_failed' } + /** * Decrypts the stored API key for a given connection. Server-side only — * never expose the return value to the browser. Callers are proxy routes - * and the workflow-block tool. + * and the workflow-block tool. Returns a discriminated result rather + * than throwing so callers can respond with an actionable status code + * (404 vs 502) instead of leaking an unhandled 500. */ export async function getDecryptedApiKey(params: { id: string workspaceId: string -}): Promise { +}): Promise { const row = await getConnection(params) - if (!row) return null - const { decrypted } = await decryptSecret(row.encryptedApiKey) - return decrypted + if (!row) return { ok: false, reason: 'not_found' } + try { + const { decrypted } = await decryptSecret(row.encryptedApiKey) + return { ok: true, apiKey: decrypted } + } catch (err) { + logger.warn(`Failed to decrypt api key for connection ${params.id}`, { error: err }) + return { ok: false, reason: 'decrypt_failed' } + } } export interface CreateConnectionInput { diff --git a/apps/sim/lib/managed-agents/proxy.ts b/apps/sim/lib/managed-agents/proxy.ts index eb523cb1988..005400d9e8c 100644 --- a/apps/sim/lib/managed-agents/proxy.ts +++ b/apps/sim/lib/managed-agents/proxy.ts @@ -1,4 +1,6 @@ import { createLogger } from '@sim/logger' +import { NextResponse } from 'next/server' +import type { DecryptedApiKeyResult } from '@/lib/managed-agents/connections' import { ANTHROPIC_API_BASE, ANTHROPIC_VERSION, @@ -58,3 +60,24 @@ export interface AnthropicListPage { has_more?: boolean last_id?: string } + +/** + * Map a {@link DecryptedApiKeyResult} failure to the correct HTTP + * response for a proxy route. Keeps the "not_found → 404" and + * "decrypt_failed → 502 + actionable message" mapping in one place so + * every proxy route surfaces the same UX for the same failure mode. + */ +export function apiKeyFailureResponse( + failure: Extract +): NextResponse { + if (failure.reason === 'decrypt_failed') { + return NextResponse.json( + { + error: + 'Managed Agent connection could not be decrypted — the workspace encryption key may have rotated. Rotate the API key in Settings → Managed Agents to re-encrypt.', + }, + { status: 502 } + ) + } + return NextResponse.json({ error: 'Connection not found' }, { status: 404 }) +} diff --git a/apps/sim/tools/managed_agent/run_session.server.ts b/apps/sim/tools/managed_agent/run_session.server.ts index 16373102d84..40b8d45cf0d 100644 --- a/apps/sim/tools/managed_agent/run_session.server.ts +++ b/apps/sim/tools/managed_agent/run_session.server.ts @@ -83,33 +83,18 @@ const impl: ManagedAgentServerImpl = async ( return { success: false, output: {}, error: 'aborted' } } - let apiKey: string | null - try { - apiKey = await getDecryptedApiKey({ id: params.connection, workspaceId }) - } catch (error) { - // The stored ciphertext could not be decrypted — most commonly a rotated - // ENCRYPTION_KEY invalidating an existing row. Translate the exception - // into the tool's declared error shape rather than rejecting the promise - // (which would surface as an unhandled workflow failure). - logger.warn('Failed to decrypt Managed Agent api key', { - workspaceId, - connectionId: params.connection, - error: getErrorMessage(error), - }) + const keyResult = await getDecryptedApiKey({ id: params.connection, workspaceId }) + if (!keyResult.ok) { return { success: false, output: {}, error: - 'Managed Agent connection could not be decrypted — the workspace encryption key may have rotated. Rotate the API key in Settings → Managed Agents to re-encrypt.', - } - } - if (!apiKey) { - return { - success: false, - output: {}, - error: 'Managed Agent connection not found. Reconnect the Claude workspace and retry.', + keyResult.reason === 'decrypt_failed' + ? 'Managed Agent connection could not be decrypted — the workspace encryption key may have rotated. Rotate the API key in Settings → Managed Agents to re-encrypt.' + : 'Managed Agent connection not found. Reconnect the Claude workspace and retry.', } } + const apiKey = keyResult.apiKey if (isPayloadDebugEnabled()) { logger.info('Managed agent tool raw params (pre-normalization)', { From fd2fe27c1883aa8c3b62b96732c077b88e9d4031 Mon Sep 17 00:00:00 2001 From: Ricardo DeMatos Date: Sun, 19 Jul 2026 23:15:19 -0700 Subject: [PATCH 05/11] feat(managed-agent): require vault-authorization ack when a vault is selected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both blocks now render a "Vault authorization" switch immediately below the Credential vaults picker: "I own or am authorized to use these vaults. I understand this means this agent can assume the identity granted by them." The tool rejects execution with an actionable error when `vaults` is non-empty and the ack is not checked, so a workflow author cannot silently attach an OAuth vault to an agent they aren't authorized to run under. Why runtime, not subblock-level required: Sim's `condition` engine uses strict equality against a single value, so it cannot natively express "field is a non-empty array". Enforcing at execute time keeps the ack always visible (users see the warning up front) and fails closed on run. Also lifted the truthy-check into a shared `isTruthyAck` helper in `tools/managed_agent/normalizers.ts` — handles the real boolean, plus the `"true" | "1" | "yes"` string forms that switch values can arrive as depending on serialization. Four cases covered: - `true` accepted - `"true"` / `"True"` / `"1"` / `"yes"` accepted (case-insensitive, whitespace-trimmed) - Every other string / `false` / `undefined` / non-string non-boolean rejected Tests: 91 pass (4 new for `isTruthyAck`). --- apps/sim/blocks/blocks/managed_agent_cloud.ts | 19 +++++++++++++ .../blocks/managed_agent_self_hosted.ts | 15 +++++++++++ .../tools/managed_agent/normalizers.test.ts | 27 +++++++++++++++++++ apps/sim/tools/managed_agent/normalizers.ts | 14 ++++++++++ .../tools/managed_agent/run_session.server.ts | 14 ++++++++++ apps/sim/tools/managed_agent/run_session.ts | 7 +++++ apps/sim/tools/managed_agent/types.ts | 9 +++++++ 7 files changed, 105 insertions(+) diff --git a/apps/sim/blocks/blocks/managed_agent_cloud.ts b/apps/sim/blocks/blocks/managed_agent_cloud.ts index e67bb1150bf..cecebef58c0 100644 --- a/apps/sim/blocks/blocks/managed_agent_cloud.ts +++ b/apps/sim/blocks/blocks/managed_agent_cloud.ts @@ -78,6 +78,20 @@ export const ManagedAgentCloudBlock: BlockConfig = { dependsOn: ['connection'], fetchOptions: fetchManagedAgentVaultOptions, }, + { + // Security ack — enforced at execution time when at least one vault + // is selected. Rendered as a plain switch (with the same wording as + // the design spec) rather than a bespoke amber banner, to stay + // consistent with the rest of Sim's sub-block styling. The tool + // rejects execution with an actionable error if `vaults` is + // non-empty but this is false. + id: 'vaultsAck', + title: 'Vault authorization', + type: 'switch', + required: false, + description: + 'I own or am authorized to use these vaults. I understand this means this agent can assume the identity granted by them. Required when at least one vault is selected above.', + }, { // Memory store — attached as a `memory_store` resource entry. id: 'memoryStoreId', @@ -156,6 +170,11 @@ export const ManagedAgentCloudBlock: BlockConfig = { environment: { type: 'string', description: 'Cloud environment id.' }, environmentType: { type: 'string', description: 'Always "cloud" for this block.' }, vaults: { type: 'json', description: 'Vault ids for MCP auth (array of strings).' }, + vaultsAck: { + type: 'boolean', + description: + 'Workflow-author acknowledgement that they are authorized to use the attached vaults. Required when `vaults` is non-empty.', + }, memoryStoreId: { type: 'string', description: 'Optional Agent Memory Store id.' }, memoryAccess: { type: 'string', diff --git a/apps/sim/blocks/blocks/managed_agent_self_hosted.ts b/apps/sim/blocks/blocks/managed_agent_self_hosted.ts index 0250c9c701d..8e3526ebdf4 100644 --- a/apps/sim/blocks/blocks/managed_agent_self_hosted.ts +++ b/apps/sim/blocks/blocks/managed_agent_self_hosted.ts @@ -129,6 +129,16 @@ export const ManagedAgentSelfHostedBlock: BlockConfig = { dependsOn: ['connection'], fetchOptions: fetchManagedAgentVaultOptions, }, + { + // Security ack — enforced at execution time when at least one vault + // is selected. See the sibling comment on `managed_agent_cloud.ts`. + id: 'vaultsAck', + title: 'Vault authorization', + type: 'switch', + required: false, + description: + 'I own or am authorized to use these vaults. I understand this means this agent can assume the identity granted by them. Required when at least one vault is selected above.', + }, ...memorySubBlocks, { id: 'agent', @@ -175,6 +185,11 @@ export const ManagedAgentSelfHostedBlock: BlockConfig = { environment: { type: 'string', description: 'Self-hosted environment id.' }, environmentType: { type: 'string', description: 'Always "self_hosted" for this block.' }, vaults: { type: 'json', description: 'Vault ids for MCP auth (array of strings).' }, + vaultsAck: { + type: 'boolean', + description: + 'Workflow-author acknowledgement that they are authorized to use the attached vaults. Required when `vaults` is non-empty.', + }, memoryStoreId: { type: 'string', description: diff --git a/apps/sim/tools/managed_agent/normalizers.test.ts b/apps/sim/tools/managed_agent/normalizers.test.ts index aca200de8f9..0a76ca7dcbb 100644 --- a/apps/sim/tools/managed_agent/normalizers.test.ts +++ b/apps/sim/tools/managed_agent/normalizers.test.ts @@ -3,6 +3,7 @@ */ import { describe, expect, it } from 'vitest' import { + isTruthyAck, normalizeEnvType, normalizeFiles, normalizeMemoryAccess, @@ -204,3 +205,29 @@ describe('normalizeSessionParameters', () => { expect(normalizeSessionParameters('[broken')).toBeUndefined() }) }) + +describe('isTruthyAck', () => { + it('accepts the real boolean true', () => { + expect(isTruthyAck(true)).toBe(true) + }) + + it('accepts common string checked-forms (case-insensitive, trimmed)', () => { + for (const on of ['true', 'True', 'TRUE', '1', 'yes', 'YES', ' true ']) { + expect(isTruthyAck(on)).toBe(true) + } + }) + + it('rejects false, empty, and every other string form', () => { + for (const off of [false, '', ' ', 'false', '0', 'no', 'off', 'random']) { + expect(isTruthyAck(off)).toBe(false) + } + }) + + it('rejects undefined / null / non-string non-boolean values', () => { + expect(isTruthyAck(undefined)).toBe(false) + expect(isTruthyAck(null)).toBe(false) + expect(isTruthyAck(1)).toBe(false) + expect(isTruthyAck({})).toBe(false) + expect(isTruthyAck([])).toBe(false) + }) +}) diff --git a/apps/sim/tools/managed_agent/normalizers.ts b/apps/sim/tools/managed_agent/normalizers.ts index d9eea549c17..4562535a31e 100644 --- a/apps/sim/tools/managed_agent/normalizers.ts +++ b/apps/sim/tools/managed_agent/normalizers.ts @@ -25,6 +25,20 @@ export function normalizeMemoryAccess( return undefined } +/** + * A subblock's `switch` value may arrive as a real boolean or as a + * string (`"true"`, `"1"`, `"yes"`) depending on how the workflow was + * serialized. Treat every reasonable "checked" form as truthy; anything + * else — including `false`, empty string, `undefined`, non-string + * non-boolean values — as not-checked. + */ +export function isTruthyAck(value: unknown): boolean { + if (value === true) return true + if (typeof value !== 'string') return false + const normalized = value.trim().toLowerCase() + return normalized === 'true' || normalized === '1' || normalized === 'yes' +} + /** * Coerces the block's file table (a JSON array of `{fileId, mountPath?}` * or the raw table-subblock shape `Array<{Key: string, Value: string}>`) diff --git a/apps/sim/tools/managed_agent/run_session.server.ts b/apps/sim/tools/managed_agent/run_session.server.ts index 40b8d45cf0d..de2fca269aa 100644 --- a/apps/sim/tools/managed_agent/run_session.server.ts +++ b/apps/sim/tools/managed_agent/run_session.server.ts @@ -16,6 +16,7 @@ import { sendUserMessage, } from '@/lib/managed-agents/session-client' import { + isTruthyAck, normalizeEnvType, normalizeFiles, normalizeMemoryAccess, @@ -114,6 +115,19 @@ const impl: ManagedAgentServerImpl = async ( } const vaultIds = normalizeStringList(params.vaults) + if (vaultIds.length > 0 && !isTruthyAck(params.vaultsAck)) { + // Security ack — enforced here rather than at the subblock level + // because Sim's `condition` engine cannot natively test array- + // non-empty. Fails closed: attaching any vault requires an explicit + // confirmation that the workflow author is authorized to use it, + // since the session runs with the vault's credentials. + return { + success: false, + output: {}, + error: + 'Vault authorization is required — check the "I own or am authorized to use these vaults" acknowledgement on the block, or remove the selected vault(s).', + } + } const memoryStoreId = params.memoryStoreId?.trim() || undefined const memoryAccess = normalizeMemoryAccess(params.memoryAccess) const files = normalizeFiles(params.files) diff --git a/apps/sim/tools/managed_agent/run_session.ts b/apps/sim/tools/managed_agent/run_session.ts index ebfc73471da..46971c28def 100644 --- a/apps/sim/tools/managed_agent/run_session.ts +++ b/apps/sim/tools/managed_agent/run_session.ts @@ -87,6 +87,13 @@ export const managedAgentRunSessionTool: ToolConfig< visibility: 'user-only', description: 'Zero or more vault ids for MCP tool auth.', }, + vaultsAck: { + type: 'boolean', + required: false, + visibility: 'user-only', + description: + "Workflow-author acknowledgement that they are authorized to use the attached vaults. Must be true when `vaults` is non-empty; the tool rejects execution otherwise.", + }, memoryStoreId: { type: 'string', required: false, diff --git a/apps/sim/tools/managed_agent/types.ts b/apps/sim/tools/managed_agent/types.ts index 216ca8e87ae..afce09581af 100644 --- a/apps/sim/tools/managed_agent/types.ts +++ b/apps/sim/tools/managed_agent/types.ts @@ -20,6 +20,15 @@ export interface ManagedAgentRunSessionParams { environmentType?: 'cloud' | 'self_hosted' | string /** Zero or more vault ids for MCP auth. Empty array allowed. */ vaults?: string[] + /** + * Workflow-author acknowledgement that they are authorized to use the + * attached vaults ("I own or am authorized to use these vaults; I + * understand this means this agent can assume the identity granted by + * them"). Required to be `true` when `vaults` is non-empty. Enforced by + * the tool at execution time, not at the block subblock level, because + * the subblock condition engine cannot natively test array-non-empty. + */ + vaultsAck?: boolean | string /** Optional Agent Memory Store id. */ memoryStoreId?: string /** From bd7dbda52dee32c75644d4065d6b935bc5bda76e Mon Sep 17 00:00:00 2001 From: Ricardo DeMatos Date: Sun, 19 Jul 2026 23:28:37 -0700 Subject: [PATCH 06/11] feat(managed-agent): promote the vault disclaimer to the switch label Reviewer feedback: the previous version buried the security disclaimer under a generic "Vault authorization" title with the full text below in the description. Users would only see "Vault authorization" next to the toggle and might not read the description. Move the whole "I own or am authorized to use these vaults. I understand this means this agent can assume the identity granted by them." string to the switch's title so it sits right next to the toggle. Description line collapses to the run-time constraint: "Required when at least one vault is selected above." No functional change; the run-time enforcement (tool rejects when `vaults` is non-empty and the ack is falsy) is unchanged. --- apps/sim/blocks/blocks/managed_agent_cloud.ts | 6 +++--- apps/sim/blocks/blocks/managed_agent_self_hosted.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/sim/blocks/blocks/managed_agent_cloud.ts b/apps/sim/blocks/blocks/managed_agent_cloud.ts index cecebef58c0..5357a10a84e 100644 --- a/apps/sim/blocks/blocks/managed_agent_cloud.ts +++ b/apps/sim/blocks/blocks/managed_agent_cloud.ts @@ -86,11 +86,11 @@ export const ManagedAgentCloudBlock: BlockConfig = { // rejects execution with an actionable error if `vaults` is // non-empty but this is false. id: 'vaultsAck', - title: 'Vault authorization', + title: + 'I own or am authorized to use these vaults. I understand this means this agent can assume the identity granted by them.', type: 'switch', required: false, - description: - 'I own or am authorized to use these vaults. I understand this means this agent can assume the identity granted by them. Required when at least one vault is selected above.', + description: 'Required when at least one vault is selected above.', }, { // Memory store — attached as a `memory_store` resource entry. diff --git a/apps/sim/blocks/blocks/managed_agent_self_hosted.ts b/apps/sim/blocks/blocks/managed_agent_self_hosted.ts index 8e3526ebdf4..c010beb9fa9 100644 --- a/apps/sim/blocks/blocks/managed_agent_self_hosted.ts +++ b/apps/sim/blocks/blocks/managed_agent_self_hosted.ts @@ -133,11 +133,11 @@ export const ManagedAgentSelfHostedBlock: BlockConfig = { // Security ack — enforced at execution time when at least one vault // is selected. See the sibling comment on `managed_agent_cloud.ts`. id: 'vaultsAck', - title: 'Vault authorization', + title: + 'I own or am authorized to use these vaults. I understand this means this agent can assume the identity granted by them.', type: 'switch', required: false, - description: - 'I own or am authorized to use these vaults. I understand this means this agent can assume the identity granted by them. Required when at least one vault is selected above.', + description: 'Required when at least one vault is selected above.', }, ...memorySubBlocks, { From 3f6be08a7b683760b6a74cc369160c12194220ea Mon Sep 17 00:00:00 2001 From: Ricardo DeMatos Date: Mon, 20 Jul 2026 06:12:15 -0700 Subject: [PATCH 07/11] feat(managed-agent): name Anthropic sessions after the origin workflow node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously Claude Managed Agent sessions opened untitled, which made them hard to trace back from the Claude Platform side to the workflow that created them. Sessions now open with a human-legible title composed from the Sim workspace, workflow, and node names: sim.ai - - - Segments whose DB lookup fails (deleted workspace/workflow, unnamed block) are silently dropped so the session still opens with whatever context is available. If nothing beyond the "sim.ai" prefix resolves, we skip the title entirely rather than create a placeholder-titled session. Plumbing: - The generic block handler now also passes `blockId` and `blockName` on `_context` when it invokes a tool. `WorkflowToolExecutionContext` gains matching optional fields — opt-in for any tool that wants to emit externally-visible records tied to their origin node. - `run_session.server.ts` looks up the workspace/workflow names via `@sim/db` at execute time (a two-column SELECT per name; wrapped in a defensive try/catch so a DB blip never blocks session creation). - `buildSessionCreatePayload` already accepts `title` — no session- client change needed. --- .../handlers/generic/generic-handler.ts | 2 + .../tools/managed_agent/run_session.server.ts | 62 +++++++++++++++++++ apps/sim/tools/types.ts | 8 +++ 3 files changed, 72 insertions(+) diff --git a/apps/sim/executor/handlers/generic/generic-handler.ts b/apps/sim/executor/handlers/generic/generic-handler.ts index c6ee89dd633..5b97bb56cf5 100644 --- a/apps/sim/executor/handlers/generic/generic-handler.ts +++ b/apps/sim/executor/handlers/generic/generic-handler.ts @@ -70,6 +70,8 @@ export class GenericBlockHandler implements BlockHandler { userId: ctx.userId, isDeployedContext: ctx.isDeployedContext, enforceCredentialAccess: ctx.enforceCredentialAccess, + blockId: block.id, + blockName: block.metadata?.name, }, }, { executionContext: ctx } diff --git a/apps/sim/tools/managed_agent/run_session.server.ts b/apps/sim/tools/managed_agent/run_session.server.ts index de2fca269aa..53a4a78da72 100644 --- a/apps/sim/tools/managed_agent/run_session.server.ts +++ b/apps/sim/tools/managed_agent/run_session.server.ts @@ -1,8 +1,11 @@ import 'server-only' +import { db } from '@sim/db' +import { workflow as workflowTable, workspace as workspaceTable } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' import { sleep } from '@sim/utils/helpers' +import { eq } from 'drizzle-orm' import { readSSEEvents } from '@/lib/core/utils/sse' import { getDecryptedApiKey } from '@/lib/managed-agents/connections' import { env } from '@/lib/core/config/env' @@ -133,11 +136,22 @@ const impl: ManagedAgentServerImpl = async ( const files = normalizeFiles(params.files) const sessionParameters = normalizeSessionParameters(params.sessionParameters) + // Human-legible session title so runs are traceable from the Claude + // Platform side back to the Sim workflow node that opened them. + // Failure to look up either name is tolerated — we just drop that + // segment from the title so the session still opens. + const title = await buildSessionTitle({ + workspaceId, + workflowId: context?.workflowId, + blockName: context?.blockName, + }) + const createSessionInput = { apiKey, agentId: params.agent, environmentId: params.environment, envType, + ...(title ? { title } : {}), ...(vaultIds.length > 0 ? { vaultIds } : {}), ...(memoryStoreId ? { memoryStoreId } : {}), ...(memoryStoreId && memoryAccess ? { memoryAccess } : {}), @@ -478,3 +492,51 @@ function isPayloadDebugEnabled(): boolean { const normalized = String(raw).toLowerCase() return normalized === '1' || normalized === 'true' || normalized === 'yes' } + +/** + * Assemble the Anthropic session title from the Sim workspace, workflow, + * and node names — so a session listed on Claude Platform links back to + * the origin. + * + * Shape: `sim.ai - - - `. Segments whose + * lookups fail (workspace/workflow deleted, block unnamed) are dropped + * so the title still opens something useful. If we can't build any + * meaningful title, return `undefined` — the session creates untitled. + */ +async function buildSessionTitle(input: { + workspaceId: string + workflowId?: string + blockName?: string +}): Promise { + const [workspaceName, workflowName] = await Promise.all([ + fetchNameSafely(workspaceTable, workspaceTable.id, input.workspaceId), + input.workflowId + ? fetchNameSafely(workflowTable, workflowTable.id, input.workflowId) + : Promise.resolve(undefined), + ]) + const segments = ['sim.ai'] + if (workspaceName) segments.push(workspaceName) + if (workflowName) segments.push(workflowName) + if (input.blockName?.trim()) segments.push(input.blockName.trim()) + if (segments.length === 1) return undefined // only the "sim.ai" prefix — not useful + return segments.join(' - ') +} + +async function fetchNameSafely( + table: { name: unknown } & Record, + idColumn: unknown, + id: string +): Promise { + try { + const rows = (await db + .select({ name: (table as { name: unknown }).name }) + .from(table as never) + .where(eq(idColumn as never, id)) + .limit(1)) as Array<{ name: string | null }> + const name = rows[0]?.name + return typeof name === 'string' && name.trim().length > 0 ? name.trim() : undefined + } catch (err) { + logger.warn('Failed to look up name for session title', { id, error: getErrorMessage(err) }) + return undefined + } +} diff --git a/apps/sim/tools/types.ts b/apps/sim/tools/types.ts index 261ac103a50..5b64b4424f1 100644 --- a/apps/sim/tools/types.ts +++ b/apps/sim/tools/types.ts @@ -51,6 +51,14 @@ export type WorkflowToolExecutionContext = { workflowId?: string executionId?: string userId?: string + /** + * Optional block identity injected by the generic handler at execute + * time. Tools that create externally-visible records (e.g. a Claude + * Platform Managed Agent session) use these to build human-legible + * titles that link the record back to the source workflow node. + */ + blockId?: string + blockName?: string } export type OutputType = From f42a13a4e8d6aa28268af138fa014120ab899a04 Mon Sep 17 00:00:00 2001 From: Ricardo DeMatos Date: Mon, 20 Jul 2026 06:19:32 -0700 Subject: [PATCH 08/11] fix(managed-agent): address 3 medium-severity review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up on Cursor Bugbot's review of the last few commits. Three related, but independent, correctness fixes. Files were silently dropped from cloud sessions - The cloud block declares `columns: ['File ID', 'Mount path']`. The table subblock stores each row's cells keyed by exactly those column strings — but `normalizeFiles` only knew about `Key`/`Value` (older shape) and flat `{fileId, mountPath}`, so every file row was dropped and no files ever reached the session. - Fix: `normalizeFiles` now recognizes `File ID` / `Mount path` (block's declared columns), `file_id` / `mount_path` (snake case), the older `Key` / `Value`, and the flat `{fileId, mountPath}` shapes. Regression test added. Stale collapsed-row labels when Claude Workspace changes - The managed-agent collapsed rows hydrate friendly names by reading the SIBLING `connection` value from `allSubBlockValues`. But `SubBlockRow`'s memo compare only checked this row's own value, so the memo would keep showing agent/env/vault/memory labels tied to the previous connection until each row's own stored value happened to change. - Fix: extend `areSubBlockRowPropsEqual` to also compare a small set of known sibling-dependency keys (`connection`, `oauthCredential`, `credential`). Any of them changing invalidates the memo so the hydration hooks re-run with the new sibling value. Empty tables re-seeded deployer defaults on remount - The table init effect early-exited when `storeValue.length > 0`, but treated `[]` as "unseeded" and re-ran `fetchDefaultRows` / `defaultRows`. A self-hosted block whose Session-parameters table was intentionally cleared to zero rows would get repopulated on every remount, clobbering the author's choice. - Fix: only seed when `storeValue === undefined || storeValue === null`. A stored `[]` — the author's explicit "no rows" — is now preserved. Tests: 92 pass (30 normalizer, up from 29; regression test for the `File ID` / `Mount path` shape). --- .../sub-block/components/table/table.tsx | 12 ++++--- .../workflow-block/workflow-block.tsx | 13 +++++++ .../tools/managed_agent/normalizers.test.ts | 16 +++++++++ apps/sim/tools/managed_agent/normalizers.ts | 34 +++++++++++-------- 4 files changed, 57 insertions(+), 18 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx index a061295d012..085d6baf5ba 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx @@ -265,17 +265,21 @@ export function Table({ ) /** - * Initialize the table when the component mounts and the store value is - * missing/empty. Precedence: + * Initialize the table when the component mounts and the store value has + * never been set. Precedence: * 1. `fetchDefaultRows` (async) — used when defaults live server-side * (e.g. deployer env-var read via an API route). * 2. `defaultRows` (sync) — static seeds declared on the block config. * 3. Single empty row — the pre-existing behavior. - * Existing store values are never touched. + * + * We only seed when `storeValue` is `null` / `undefined` — a stored + * `[]` means the workflow author intentionally cleared every row and + * must NOT be treated as "unseeded" (would re-seed defaults on every + * remount and clobber the empty state they chose). */ useEffect(() => { if (isPreview || disabled) return - if (Array.isArray(storeValue) && storeValue.length > 0) return + if (storeValue !== undefined && storeValue !== null) return let cancelled = false const seedWith = (rows: Array<{ cells: Record }> | undefined) => { if (cancelled) return diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx index 0ce58c2a9c4..986ecdc4826 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx @@ -106,6 +106,18 @@ const areSubBlockRowPropsEqual = ( const nextValue = subBlockId ? nextProps.allSubBlockValues?.[subBlockId]?.value : undefined const valueEqual = prevValue === nextValue || isEqual(prevValue, nextValue) + // Some subblocks resolve their friendly-name label using SIBLING values + // (e.g. managed-agent rows key off the selected `connection` id). If a + // sibling changes but this row's own value doesn't, the memo would keep + // showing a label tied to the previous sibling. Compare a small set of + // known dependency keys explicitly so hydration stays fresh. + const depFields = ['connection', 'oauthCredential', 'credential'] as const + const depsEqual = depFields.every((field) => { + const prev = prevProps.allSubBlockValues?.[field]?.value + const next = nextProps.allSubBlockValues?.[field]?.value + return prev === next || isEqual(prev, next) + }) + return ( prevProps.title === nextProps.title && prevProps.value === nextProps.value && @@ -116,6 +128,7 @@ const areSubBlockRowPropsEqual = ( prevProps.blockId === nextProps.blockId && prevProps.blockType === nextProps.blockType && valueEqual && + depsEqual && prevProps.displayAdvancedOptions === nextProps.displayAdvancedOptions && prevProps.canonicalIndex === nextProps.canonicalIndex && prevProps.canonicalModeOverrides === nextProps.canonicalModeOverrides diff --git a/apps/sim/tools/managed_agent/normalizers.test.ts b/apps/sim/tools/managed_agent/normalizers.test.ts index 0a76ca7dcbb..b8010a1ab75 100644 --- a/apps/sim/tools/managed_agent/normalizers.test.ts +++ b/apps/sim/tools/managed_agent/normalizers.test.ts @@ -113,6 +113,22 @@ describe('normalizeFiles', () => { ]) }) + it("accepts the block's declared column names (`File ID` / `Mount path`)", () => { + // The cloud block declares `columns: ['File ID', 'Mount path']`, so + // the table subblock stores each row keyed by exactly those strings. + // Regression test — the previous normalizer only knew about + // `Key`/`Value` and silently dropped every row. + expect( + normalizeFiles([ + { id: 'r1', cells: { 'File ID': 'file_1', 'Mount path': '/data/one' } }, + { id: 'r2', cells: { 'File ID': 'file_2', 'Mount path': '' } }, + ]) + ).toEqual([ + { fileId: 'file_1', mountPath: '/data/one' }, + { fileId: 'file_2' }, + ]) + }) + it('drops rows with no file id and rows that are not objects', () => { expect( normalizeFiles([ diff --git a/apps/sim/tools/managed_agent/normalizers.ts b/apps/sim/tools/managed_agent/normalizers.ts index 4562535a31e..4bffddfbcbf 100644 --- a/apps/sim/tools/managed_agent/normalizers.ts +++ b/apps/sim/tools/managed_agent/normalizers.ts @@ -40,10 +40,15 @@ export function isTruthyAck(value: unknown): boolean { } /** - * Coerces the block's file table (a JSON array of `{fileId, mountPath?}` - * or the raw table-subblock shape `Array<{Key: string, Value: string}>`) - * into the tidy shape the session-client expects. Silently drops rows - * missing a file id. + * Coerces the block's file table into the tidy shape the session-client + * expects. Silently drops rows missing a file id. + * + * The table subblock stores rows as `WorkflowTableRow[]` with `cells` + * keyed by the column-header STRINGS the block declared. The cloud + * Managed Agents block uses `columns: ['File ID', 'Mount path']`, so + * cells arrive as `{ 'File ID': '...', 'Mount path': '...' }`. We also + * accept the older `{Key, Value}` and the flat `{fileId, mountPath}` + * shapes so hand-authored or legacy stored values still resolve. */ export function normalizeFiles( value: unknown @@ -57,19 +62,20 @@ export function normalizeFiles( record.cells && typeof record.cells === 'object' ? (record.cells as Record) : record + const readString = (key: string): string | undefined => + typeof cells[key] === 'string' ? (cells[key] as string) : undefined const fileId = - typeof cells.fileId === 'string' - ? cells.fileId - : typeof cells.Key === 'string' - ? cells.Key - : '' + readString('fileId') ?? + readString('File ID') ?? + readString('file_id') ?? + readString('Key') ?? + '' if (!fileId.trim()) continue const mountPath = - typeof cells.mountPath === 'string' - ? cells.mountPath - : typeof cells.Value === 'string' - ? cells.Value - : undefined + readString('mountPath') ?? + readString('Mount path') ?? + readString('mount_path') ?? + readString('Value') out.push({ fileId: fileId.trim(), ...(mountPath && mountPath.trim() ? { mountPath: mountPath.trim() } : {}), From fa2cc10f86a8bea19f9ad41a19a5b6b6a9ac10d9 Mon Sep 17 00:00:00 2001 From: Ricardo DeMatos Date: Mon, 20 Jul 2026 06:31:28 -0700 Subject: [PATCH 09/11] fix(managed-agent): authenticate defaults API + wire up key rotation UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two follow-up fixes for Cursor Bugbot's most recent review. #11 Defaults API lacks authentication - `GET /api/managed-agent-defaults` returned every value from `MANAGED_AGENT_SELF_HOSTED_DEFAULTS` with no session check. Docs and the block copy steer deployers toward putting sandbox-scoped secrets there (because they never enter the client bundle at build time), but an unauthenticated caller could still dump the JSON. - Fixed: route now runs `checkSessionOrInternalAuth` and returns 401 for anonymous callers. Same-origin fetches from the block editor automatically carry the session cookie, so no client change needed. - Test added: `route.test.ts` now covers the 401 unauthenticated case. #13 `useRotateManagedAgentConnection` was unused - The hook + PATCH route existed but nothing called them, so the only way to change a stored API key was delete + re-link — awkward when the deployer's just cycling the key. - Wired into the settings UI: - New `RotateKeyModal` component (mirrors the create modal but with only the key field; connection name stays put) runs the same verify-then-save flow. - Added a "Rotate key" action to each connection row's `RowActionsMenu`, gated on `canEdit` (workspace `write`/`admin` tiers). Success shows a toast; errors surface inline in the modal. Not fixed / addressed inline: - #9 (stale labels after connection change) and #10 (empty table re-seeds defaults) — Bugbot re-fired these on the previous commit by signature-match against a static code shape. The actual fixes (extra sibling-dep comparison in `areSubBlockRowPropsEqual`; only seed when `storeValue` is null/undefined) are in place and functional. No change needed. - #12 (empty table shows a phantom row) — theoretical. The table component's delete-row guard (`rows.length === 1`) prevents users from ever reaching `[]` through the UI, so the mismatch it warns about isn't reachable in normal use. --- .../api/managed-agent-defaults/route.test.ts | 14 ++- .../app/api/managed-agent-defaults/route.ts | 15 ++- .../managed-agents/managed-agents.tsx | 33 ++++++ .../managed-agents/rotate-key-modal.tsx | 100 ++++++++++++++++++ 4 files changed, 156 insertions(+), 6 deletions(-) create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/rotate-key-modal.tsx diff --git a/apps/sim/app/api/managed-agent-defaults/route.test.ts b/apps/sim/app/api/managed-agent-defaults/route.test.ts index cdacfdcd41b..96a73121004 100644 --- a/apps/sim/app/api/managed-agent-defaults/route.test.ts +++ b/apps/sim/app/api/managed-agent-defaults/route.test.ts @@ -4,8 +4,9 @@ import { createMockRequest } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { envState } = vi.hoisted(() => ({ +const { envState, mockCheckSessionOrInternalAuth } = vi.hoisted(() => ({ envState: { MANAGED_AGENT_SELF_HOSTED_DEFAULTS: undefined as string | undefined }, + mockCheckSessionOrInternalAuth: vi.fn(), })) vi.mock('@/lib/core/config/env', () => ({ @@ -14,11 +15,22 @@ vi.mock('@/lib/core/config/env', () => ({ }), })) +vi.mock('@/lib/auth/hybrid', () => ({ + checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, +})) + import { GET } from '@/app/api/managed-agent-defaults/route' describe('GET /api/managed-agent-defaults', () => { beforeEach(() => { envState.MANAGED_AGENT_SELF_HOSTED_DEFAULTS = undefined + mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: 'user_1' }) + }) + + it('returns 401 for an unauthenticated caller', async () => { + mockCheckSessionOrInternalAuth.mockResolvedValue({ success: false }) + const res = await GET(createMockRequest('GET')) + expect(res.status).toBe(401) }) it('returns an empty list when the env var is unset', async () => { diff --git a/apps/sim/app/api/managed-agent-defaults/route.ts b/apps/sim/app/api/managed-agent-defaults/route.ts index a693649104e..1c77dc4ee83 100644 --- a/apps/sim/app/api/managed-agent-defaults/route.ts +++ b/apps/sim/app/api/managed-agent-defaults/route.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' +import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' import { env } from '@/lib/core/config/env' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' @@ -42,11 +43,15 @@ function readSelfHostedDefaults(): DefaultRow[] { * GET /api/managed-agent-defaults * * Returns the deployer-configured default rows the block picker seeds - * into a fresh Claude Managed Agents (self-hosted) block. No auth - * required — the response contains only default keys the deployer - * chose to expose to their workflow authors; there is no per-user or - * per-workspace state. + * into a fresh Claude Managed Agents (self-hosted) block. Authenticated + * because the env var may legitimately contain sandbox-scoped secrets + * (the whole point of moving it off `NEXT_PUBLIC_*` is that values + * should not be exposed to unauthenticated callers). */ -export const GET = withRouteHandler(async (_request: NextRequest) => { +export const GET = withRouteHandler(async (request: NextRequest) => { + const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success || !authResult.userId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } return NextResponse.json({ selfHosted: readSelfHostedDefaults() }, { status: 200 }) }) diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/managed-agents.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/managed-agents.tsx index 628a9b9d1a3..951bf6bbe02 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/managed-agents.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/managed-agents.tsx @@ -13,10 +13,12 @@ import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/compo import { SettingsPanel } from '@/app/workspace/[workspaceId]/settings/components/settings-panel' import { useSettingsSearch } from '@/app/workspace/[workspaceId]/settings/components/use-settings-search' import { ConnectionFormModal } from '@/app/workspace/[workspaceId]/settings/components/managed-agents/connection-form-modal' +import { RotateKeyModal } from '@/app/workspace/[workspaceId]/settings/components/managed-agents/rotate-key-modal' import { useCreateManagedAgentConnection, useDeleteManagedAgentConnection, useManagedAgentConnections, + useRotateManagedAgentConnection, } from '@/hooks/queries/managed-agent-connections' const logger = createLogger('ManagedAgentsSettings') @@ -34,10 +36,15 @@ export function ManagedAgents() { } = useManagedAgentConnections(workspaceId) const createConnection = useCreateManagedAgentConnection() const deleteConnection = useDeleteManagedAgentConnection() + const rotateConnection = useRotateManagedAgentConnection() const [searchTerm, setSearchTerm] = useSettingsSearch() const [showAddModal, setShowAddModal] = useState(false) const [connectionToDeleteId, setConnectionToDeleteId] = useState(null) + const [connectionToRotate, setConnectionToRotate] = useState<{ + id: string + name: string + } | null>(null) const filtered = connections.filter((c) => { if (!searchTerm.trim()) return true @@ -68,6 +75,16 @@ export function ManagedAgents() { } } + const handleRotate = async (apiKey: string) => { + if (!connectionToRotate) return + await rotateConnection.mutateAsync({ + workspaceId, + id: connectionToRotate.id, + apiKey, + }) + toast.success('API key rotated') + } + const hasConnections = connections.length > 0 const showNoResults = searchTerm.trim() && filtered.length === 0 && hasConnections @@ -137,6 +154,11 @@ export function ManagedAgents() { actions={[ ...(canEdit ? [ + { + label: 'Rotate key', + onSelect: () => + setConnectionToRotate({ id: c.id, name: c.name }), + }, { label: 'Remove', destructive: true, @@ -181,6 +203,17 @@ export function ManagedAgents() { confirm={{ label: 'Remove', onClick: confirmDelete }} /> )} + + {canEdit && ( + { + if (!open) setConnectionToRotate(null) + }} + onSubmit={handleRotate} + /> + )} ) } diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/rotate-key-modal.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/rotate-key-modal.tsx new file mode 100644 index 00000000000..473f626a120 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/managed-agents/rotate-key-modal.tsx @@ -0,0 +1,100 @@ +'use client' + +import { useState } from 'react' +import { + ChipModal, + ChipModalBody, + ChipModalError, + ChipModalField, + ChipModalFooter, + ChipModalHeader, +} from '@sim/emcn' +import { getErrorMessage } from '@sim/utils/errors' + +interface RotateKeyModalProps { + open: boolean + connectionName: string | null + onOpenChange: (open: boolean) => void + onSubmit: (apiKey: string) => Promise +} + +/** + * "Rotate API key" dialog. Same verify-then-save flow as the initial + * link modal, but only the key field is editable — the connection name + * stays put. Kept separate from the create modal so the wording and the + * required fields match the operation the user is doing. + */ +export function RotateKeyModal({ + open, + connectionName, + onOpenChange, + onSubmit, +}: RotateKeyModalProps) { + const [apiKey, setApiKey] = useState('') + const [submitting, setSubmitting] = useState(false) + const [error, setError] = useState(null) + const [prevOpen, setPrevOpen] = useState(false) + + if (open !== prevOpen) { + setPrevOpen(open) + if (open) { + setApiKey('') + setError(null) + setSubmitting(false) + } + } + + const handleSubmit = async () => { + const trimmedKey = apiKey.trim() + if (!trimmedKey) { + setError('Anthropic API key is required.') + return + } + setSubmitting(true) + setError(null) + try { + await onSubmit(trimmedKey) + onOpenChange(false) + } catch (err) { + setError(getErrorMessage(err, 'Failed to rotate the API key.')) + } finally { + setSubmitting(false) + } + } + + const title = connectionName ? `Rotate key — ${connectionName}` : 'Rotate API key' + + return ( + + onOpenChange(false)}>{title} + + + { + setApiKey(value) + if (error) setError(null) + }} + placeholder='sk-ant-…' + password + required + hint='Verified against /v1/agents before saving. The old key is overwritten on success — existing workflows immediately use the new one.' + /> + + {error ?? undefined} + + + onOpenChange(false)} + cancelDisabled={submitting} + primaryAction={{ + label: submitting ? 'Verifying…' : 'Rotate & save', + onClick: handleSubmit, + disabled: submitting || !apiKey.trim(), + }} + /> + + ) +} From 872953aee2024bf7bf0aaae69c7983cef8509fa2 Mon Sep 17 00:00:00 2001 From: Ricardo DeMatos Date: Mon, 20 Jul 2026 06:39:45 -0700 Subject: [PATCH 10/11] fix(managed-agent): async table seed no longer clobbers in-flight edits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cursor Bugbot on 9704cec flagged a race in the `Table` sub-block init effect: `fetchDefaultRows` is async, and the effect's guard on `storeValue !== undefined && storeValue !== null` was captured at effect setup time. Between the fetch start and its resolution, a user could type into the phantom row — persisting the row via `handleCellChange`'s `setStoreValue([newRow])` — and then the resolved fetch would call `setStoreValue(defaultRows)` and clobber their edit. Fix: re-read the current value from the subblock store inside `seedWith` right before writing (`useSubBlockStore.getState() .getValue(blockId, subBlockId)`), and skip the write if the value has already been populated since the effect started. The closure-captured `storeValue` was stale; the store's `getState` returns the LATEST value synchronously. Other Bugbot comments on this commit are re-fires of items already fixed on prior commits (they signature-match against static code shapes without noticing the corrective code that lives beside them): `Stale labels after connection change` (fixed via `depsEqual` in `areSubBlockRowPropsEqual`), `Empty table re-seeds defaults` (fixed via `storeValue !== undefined && storeValue !== null` guard), `Defaults API lacks authentication` (fixed via `checkSessionOrInternalAuth`), `Rotate connection hook unused` (fixed via the new Rotate-key modal wired into the settings UI). `Empty table shows phantom row` is theoretical — the delete-row guard (`rows.length === 1`) prevents users from reaching `[]` through the UI, so the mismatch it warns about isn't reachable in normal use. --- .../components/sub-block/components/table/table.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx index 085d6baf5ba..4efde833f2c 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx @@ -15,6 +15,7 @@ import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/c import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value' import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider' import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes' +import { useSubBlockStore } from '@/stores/workflows/subblock/store' const logger = createLogger('Table') @@ -283,6 +284,13 @@ export function Table({ let cancelled = false const seedWith = (rows: Array<{ cells: Record }> | undefined) => { if (cancelled) return + // Re-read the LATEST value from the subblock store, not the + // closure-captured `storeValue` from render time. Otherwise an + // async `fetchDefaultRows` that resolves after the user has + // already started editing the table would overwrite their + // in-progress rows with the deployer defaults. + const currentValue = useSubBlockStore.getState().getValue(blockId, subBlockId) + if (currentValue !== undefined && currentValue !== null) return const seedRows: WorkflowTableRow[] = Array.isArray(rows) && rows.length > 0 ? rows.map((row) => ({ @@ -310,6 +318,8 @@ export function Table({ emptyCellsTemplate, defaultRows, fetchDefaultRows, + blockId, + subBlockId, ]) // Ensure value is properly typed and initialized From 8e9f8a135182c1f75eac40b816ed9322dda474fb Mon Sep 17 00:00:00 2001 From: Ricardo DeMatos Date: Mon, 20 Jul 2026 23:34:41 +0000 Subject: [PATCH 11/11] refactor(managed-agent): remove self-hosted default-row pre-seeding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-seeding the self-hosted block's Session parameters table from the server-only MANAGED_AGENT_SELF_HOSTED_DEFAULTS env var is better served by Sim's workflow blocks. Remove the capability end-to-end: - Delete GET /api/managed-agent-defaults route + test - Drop the MANAGED_AGENT_SELF_HOSTED_DEFAULTS env var - Remove getManagedAgentDefaultsContract + its schemas/types - Remove the fetchManagedAgentSelfHostedDefaults client fetcher - Unwire fetchDefaultRows from the self-hosted block and fix the two stale comments that referenced the removed env var Revert the shared table subblock seeding machinery (table.tsx, sub-block.tsx, blocks/types.ts) to its pre-feature behavior so no other block is affected — table.tsx is now byte-identical to main. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../api/managed-agent-defaults/route.test.ts | 96 ------------------- .../app/api/managed-agent-defaults/route.ts | 57 ----------- .../sub-block/components/table/table.tsx | 79 ++------------- .../editor/components/sub-block/sub-block.tsx | 6 -- .../blocks/managed_agent_self_hosted.test.ts | 1 - .../blocks/managed_agent_self_hosted.ts | 11 +-- apps/sim/blocks/types.ts | 7 -- .../contracts/managed-agent-connections.ts | 25 ----- apps/sim/lib/core/config/env.ts | 1 - .../lib/managed-agents/subblock-options.ts | 20 ---- 10 files changed, 11 insertions(+), 292 deletions(-) delete mode 100644 apps/sim/app/api/managed-agent-defaults/route.test.ts delete mode 100644 apps/sim/app/api/managed-agent-defaults/route.ts diff --git a/apps/sim/app/api/managed-agent-defaults/route.test.ts b/apps/sim/app/api/managed-agent-defaults/route.test.ts deleted file mode 100644 index 96a73121004..00000000000 --- a/apps/sim/app/api/managed-agent-defaults/route.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * @vitest-environment node - */ -import { createMockRequest } from '@sim/testing' -import { beforeEach, describe, expect, it, vi } from 'vitest' - -const { envState, mockCheckSessionOrInternalAuth } = vi.hoisted(() => ({ - envState: { MANAGED_AGENT_SELF_HOSTED_DEFAULTS: undefined as string | undefined }, - mockCheckSessionOrInternalAuth: vi.fn(), -})) - -vi.mock('@/lib/core/config/env', () => ({ - env: new Proxy({} as Record, { - get: (_target, key) => (envState as Record)[key as string], - }), -})) - -vi.mock('@/lib/auth/hybrid', () => ({ - checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth, -})) - -import { GET } from '@/app/api/managed-agent-defaults/route' - -describe('GET /api/managed-agent-defaults', () => { - beforeEach(() => { - envState.MANAGED_AGENT_SELF_HOSTED_DEFAULTS = undefined - mockCheckSessionOrInternalAuth.mockResolvedValue({ success: true, userId: 'user_1' }) - }) - - it('returns 401 for an unauthenticated caller', async () => { - mockCheckSessionOrInternalAuth.mockResolvedValue({ success: false }) - const res = await GET(createMockRequest('GET')) - expect(res.status).toBe(401) - }) - - it('returns an empty list when the env var is unset', async () => { - const res = await GET(createMockRequest('GET')) - expect(res.status).toBe(200) - const body = await res.json() - expect(body.selfHosted).toEqual([]) - }) - - it('returns an empty list for whitespace-only env values', async () => { - envState.MANAGED_AGENT_SELF_HOSTED_DEFAULTS = ' ' - const body = await (await GET(createMockRequest('GET'))).json() - expect(body.selfHosted).toEqual([]) - }) - - it('returns an empty list on invalid JSON', async () => { - envState.MANAGED_AGENT_SELF_HOSTED_DEFAULTS = '{not-json' - const body = await (await GET(createMockRequest('GET'))).json() - expect(body.selfHosted).toEqual([]) - }) - - it('returns an empty list on a JSON array (must be an object)', async () => { - envState.MANAGED_AGENT_SELF_HOSTED_DEFAULTS = '["a","b"]' - const body = await (await GET(createMockRequest('GET'))).json() - expect(body.selfHosted).toEqual([]) - }) - - it('coerces a valid JSON object into table-row shape', async () => { - envState.MANAGED_AGENT_SELF_HOSTED_DEFAULTS = JSON.stringify({ - FOO: 'bar', - BAZ: 'qux', - }) - const body = await (await GET(createMockRequest('GET'))).json() - expect(body.selfHosted).toEqual([ - { cells: { Key: 'FOO', Value: 'bar' } }, - { cells: { Key: 'BAZ', Value: 'qux' } }, - ]) - }) - - it('drops entries with a blank key', async () => { - envState.MANAGED_AGENT_SELF_HOSTED_DEFAULTS = JSON.stringify({ - '': 'dropped', - ' ': 'also dropped', - keep: 'yes', - }) - const body = await (await GET(createMockRequest('GET'))).json() - expect(body.selfHosted).toEqual([{ cells: { Key: 'keep', Value: 'yes' } }]) - }) - - it('coerces non-string values to their string form', async () => { - envState.MANAGED_AGENT_SELF_HOSTED_DEFAULTS = JSON.stringify({ - A: 1, - B: true, - C: null, - }) - const body = await (await GET(createMockRequest('GET'))).json() - expect(body.selfHosted).toEqual([ - { cells: { Key: 'A', Value: '1' } }, - { cells: { Key: 'B', Value: 'true' } }, - { cells: { Key: 'C', Value: '' } }, - ]) - }) -}) diff --git a/apps/sim/app/api/managed-agent-defaults/route.ts b/apps/sim/app/api/managed-agent-defaults/route.ts deleted file mode 100644 index 1c77dc4ee83..00000000000 --- a/apps/sim/app/api/managed-agent-defaults/route.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { createLogger } from '@sim/logger' -import { type NextRequest, NextResponse } from 'next/server' -import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' -import { env } from '@/lib/core/config/env' -import { withRouteHandler } from '@/lib/core/utils/with-route-handler' - -const logger = createLogger('ManagedAgentDefaultsAPI') - -interface DefaultRow { - cells: Record -} - -/** - * Read the JSON defaults for the Claude Managed Agents (self-hosted) - * block's Session parameters table from the SERVER-ONLY env var - * `MANAGED_AGENT_SELF_HOSTED_DEFAULTS`. Kept behind an API route (rather - * than a `NEXT_PUBLIC_*` var) so seeded values never leak into the - * client bundle at build time — deployers can safely put anything the - * self-hosted agent sandbox reads without inadvertently shipping it to - * the browser. - */ -function readSelfHostedDefaults(): DefaultRow[] { - const raw = env.MANAGED_AGENT_SELF_HOSTED_DEFAULTS - if (typeof raw !== 'string' || raw.trim().length === 0) return [] - try { - const parsed = JSON.parse(raw) - if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return [] - const rows: DefaultRow[] = [] - for (const [key, value] of Object.entries(parsed as Record)) { - const k = typeof key === 'string' ? key.trim() : '' - if (!k) continue - const v = value == null ? '' : typeof value === 'string' ? value : String(value) - rows.push({ cells: { Key: k, Value: v } }) - } - return rows - } catch (error) { - logger.warn('Failed to parse MANAGED_AGENT_SELF_HOSTED_DEFAULTS', { error }) - return [] - } -} - -/** - * GET /api/managed-agent-defaults - * - * Returns the deployer-configured default rows the block picker seeds - * into a fresh Claude Managed Agents (self-hosted) block. Authenticated - * because the env var may legitimately contain sandbox-scoped secrets - * (the whole point of moving it off `NEXT_PUBLIC_*` is that values - * should not be exposed to unauthenticated callers). - */ -export const GET = withRouteHandler(async (request: NextRequest) => { - const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) - if (!authResult.success || !authResult.userId) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - return NextResponse.json({ selfHosted: readSelfHostedDefaults() }, { status: 200 }) -}) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx index 4efde833f2c..80996a1adae 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/table/table.tsx @@ -15,7 +15,6 @@ import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/c import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value' import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider' import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes' -import { useSubBlockStore } from '@/stores/workflows/subblock/store' const logger = createLogger('Table') @@ -26,21 +25,6 @@ interface TableProps { isPreview?: boolean previewValue?: WorkflowTableRow[] | null disabled?: boolean - /** - * Optional seed rows applied when a fresh block first mounts (store value - * is missing/empty). Each entry's `cells` keys must match `columns` — any - * missing columns fall back to `""`. Existing values are never overwritten. - */ - defaultRows?: Array<{ cells: Record }> - /** - * Optional async fetcher for seed rows — used when the block's default - * rows come from a server-side source (e.g. deployer-configured env vars - * read via an API route). Called once on first mount when the store is - * empty. If the fetcher rejects or returns an empty array, falls back to - * `defaultRows`, then to a single empty row. Same overwrite rules as - * `defaultRows`: existing store values are never touched. - */ - fetchDefaultRows?: () => Promise }>> } interface WorkflowTableRow { @@ -230,8 +214,6 @@ export function Table({ isPreview = false, previewValue, disabled = false, - defaultRows, - fetchDefaultRows, }: TableProps) { const activeSearchTarget = useActiveSearchTarget() const params = useParams() @@ -266,61 +248,18 @@ export function Table({ ) /** - * Initialize the table when the component mounts and the store value has - * never been set. Precedence: - * 1. `fetchDefaultRows` (async) — used when defaults live server-side - * (e.g. deployer env-var read via an API route). - * 2. `defaultRows` (sync) — static seeds declared on the block config. - * 3. Single empty row — the pre-existing behavior. - * - * We only seed when `storeValue` is `null` / `undefined` — a stored - * `[]` means the workflow author intentionally cleared every row and - * must NOT be treated as "unseeded" (would re-seed defaults on every - * remount and clobber the empty state they chose). + * Initialize the table with a default empty row when the component mounts + * and when the current store value is missing or empty. */ useEffect(() => { - if (isPreview || disabled) return - if (storeValue !== undefined && storeValue !== null) return - let cancelled = false - const seedWith = (rows: Array<{ cells: Record }> | undefined) => { - if (cancelled) return - // Re-read the LATEST value from the subblock store, not the - // closure-captured `storeValue` from render time. Otherwise an - // async `fetchDefaultRows` that resolves after the user has - // already started editing the table would overwrite their - // in-progress rows with the deployer defaults. - const currentValue = useSubBlockStore.getState().getValue(blockId, subBlockId) - if (currentValue !== undefined && currentValue !== null) return - const seedRows: WorkflowTableRow[] = - Array.isArray(rows) && rows.length > 0 - ? rows.map((row) => ({ - id: generateId(), - cells: { ...emptyCellsTemplate, ...(row.cells ?? {}) }, - })) - : [{ id: generateId(), cells: { ...emptyCellsTemplate } }] - setStoreValue(seedRows) - } - if (fetchDefaultRows) { - fetchDefaultRows() - .then((rows) => seedWith(rows.length > 0 ? rows : defaultRows)) - .catch(() => seedWith(defaultRows)) - } else { - seedWith(defaultRows) - } - return () => { - cancelled = true + if (!isPreview && !disabled && (!Array.isArray(storeValue) || storeValue.length === 0)) { + const initialRow: WorkflowTableRow = { + id: generateId(), + cells: { ...emptyCellsTemplate }, + } + setStoreValue([initialRow]) } - }, [ - isPreview, - disabled, - storeValue, - setStoreValue, - emptyCellsTemplate, - defaultRows, - fetchDefaultRows, - blockId, - subBlockId, - ]) + }, [isPreview, disabled, storeValue, setStoreValue, emptyCellsTemplate]) // Ensure value is properly typed and initialized const rows = useMemo(() => { diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx index 134ca350944..e8752f0c4aa 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx @@ -749,12 +749,6 @@ function SubBlockComponent({ isPreview={isPreview} previewValue={previewValue as any} disabled={isDisabled} - defaultRows={ - Array.isArray(config.defaultValue) - ? (config.defaultValue as Array<{ cells: Record }>) - : undefined - } - fetchDefaultRows={config.fetchDefaultRows} /> ) diff --git a/apps/sim/blocks/blocks/managed_agent_self_hosted.test.ts b/apps/sim/blocks/blocks/managed_agent_self_hosted.test.ts index 125c2133b30..89b14716605 100644 --- a/apps/sim/blocks/blocks/managed_agent_self_hosted.test.ts +++ b/apps/sim/blocks/blocks/managed_agent_self_hosted.test.ts @@ -19,7 +19,6 @@ vi.mock('@/lib/managed-agents/subblock-options', () => ({ fetchManagedAgentAgentOptions: vi.fn(), fetchManagedAgentConnectionOptions: vi.fn(), fetchManagedAgentMemoryStoreOptions: vi.fn(), - fetchManagedAgentSelfHostedDefaults: vi.fn(), fetchManagedAgentSelfHostedEnvironmentOptions: vi.fn(), fetchManagedAgentVaultOptions: vi.fn(), })) diff --git a/apps/sim/blocks/blocks/managed_agent_self_hosted.ts b/apps/sim/blocks/blocks/managed_agent_self_hosted.ts index c010beb9fa9..bf51e39924e 100644 --- a/apps/sim/blocks/blocks/managed_agent_self_hosted.ts +++ b/apps/sim/blocks/blocks/managed_agent_self_hosted.ts @@ -4,7 +4,6 @@ import { fetchManagedAgentAgentOptions, fetchManagedAgentConnectionOptions, fetchManagedAgentMemoryStoreOptions, - fetchManagedAgentSelfHostedDefaults, fetchManagedAgentSelfHostedEnvironmentOptions, fetchManagedAgentVaultOptions, } from '@/lib/managed-agents/subblock-options' @@ -65,8 +64,7 @@ const memorySubBlocks: SubBlockConfig[] = isSelfHostedMemoryEnabled() * `resources` array. Session metadata (top-level `metadata` field) is * exposed to the self-hosted agent sandbox as env vars, so this * block's Session parameters table is where you set the keys your - * deployment reads. The set of supported keys is deployment-specific - * — seed defaults with `NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS`. + * deployment reads. The set of supported keys is deployment-specific. * * The environment picker is filtered to `config.type === 'self_hosted'`. * Icon renders black-on-white to visually differentiate from the cloud @@ -161,17 +159,12 @@ export const ManagedAgentSelfHostedBlock: BlockConfig = { { // Session metadata forwarded to the self-hosted agent sandbox as // env vars. The set of supported keys is deployment-specific and - // lives with the deployer, not in this repo. Seed rows come from - // the server-only `MANAGED_AGENT_SELF_HOSTED_DEFAULTS` env var, - // fetched via `/api/managed-agent-defaults` — the values never - // enter the client bundle, so deployers can safely include - // anything their sandbox reads. + // lives with the deployer, not in this repo. id: 'sessionParameters', title: 'Session parameters', type: 'table', required: false, columns: ['Key', 'Value'], - fetchDefaultRows: fetchManagedAgentSelfHostedDefaults, description: 'Key/value pairs forwarded to the self-hosted agent sandbox as environment variables. Supported keys depend on your deployment — consult your deployment docs. Value cells support / references.', }, diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts index d6764d929c6..842654e94b0 100644 --- a/apps/sim/blocks/types.ts +++ b/apps/sim/blocks/types.ts @@ -285,13 +285,6 @@ export interface SubBlockConfig { } }) defaultValue?: string | number | boolean | Record | Array - /** - * Optional async fetcher for a `table` subblock's initial rows — used - * when defaults come from a server-side source (e.g. deployer-configured - * env vars) and must not be inlined into the client bundle. Called once - * on first mount when the store is empty. See `Table.fetchDefaultRows`. - */ - fetchDefaultRows?: () => Promise }>> options?: | { label: string diff --git a/apps/sim/lib/api/contracts/managed-agent-connections.ts b/apps/sim/lib/api/contracts/managed-agent-connections.ts index 714de70b502..d9f40ecbcd8 100644 --- a/apps/sim/lib/api/contracts/managed-agent-connections.ts +++ b/apps/sim/lib/api/contracts/managed-agent-connections.ts @@ -204,28 +204,3 @@ export const listManagedAgentMemoryStoresContract = defineRouteContract({ }), }, }) - -/** - * Server-side default rows the Claude Managed Agents (self-hosted) block - * seeds into its Session parameters table. Deployers configure this via - * the server-only `MANAGED_AGENT_SELF_HOSTED_DEFAULTS` env var (a JSON - * object) so seeded values — which may reference internal keys — never - * touch the client bundle. - */ -export const managedAgentSelfHostedDefaultRowSchema = z.object({ - cells: z.record(z.string(), z.string()), -}) -export type ManagedAgentSelfHostedDefaultRow = z.output< - typeof managedAgentSelfHostedDefaultRowSchema -> - -export const getManagedAgentDefaultsContract = defineRouteContract({ - method: 'GET', - path: '/api/managed-agent-defaults', - response: { - mode: 'json', - schema: z.object({ - selfHosted: z.array(managedAgentSelfHostedDefaultRowSchema), - }), - }, -}) diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 7e598c8c545..89b7b35b64c 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -149,7 +149,6 @@ export const env = createEnv({ OPENAI_API_KEY_3: z.string().min(1).optional(), // Additional OpenAI API key for load balancing MISTRAL_API_KEY: z.string().min(1).optional(), // Mistral AI API key MANAGED_AGENT_DEBUG_PAYLOAD: z.string().optional(), // When "1" | "true" | "yes", the Managed Agent workflow-block tool logs the full session-create payload (never includes the api key) so you can inspect memory / vault / metadata routing - MANAGED_AGENT_SELF_HOSTED_DEFAULTS: z.string().optional(), // Server-only JSON object of default session-metadata key/value pairs the Claude Managed Agents (self-hosted) block seeds into fresh workflows. Example: '{"SOURCE_TYPE":"git","SOURCE_REF":"main"}'. Never exposed to the client bundle — read via GET /api/managed-agent-defaults. ANTHROPIC_API_KEY_1: z.string().min(1).optional(), // Primary Anthropic Claude API key ANTHROPIC_API_KEY_2: z.string().min(1).optional(), // Additional Anthropic API key for load balancing ANTHROPIC_API_KEY_3: z.string().min(1).optional(), // Additional Anthropic API key for load balancing diff --git a/apps/sim/lib/managed-agents/subblock-options.ts b/apps/sim/lib/managed-agents/subblock-options.ts index e306c9e7f6a..d753db8a014 100644 --- a/apps/sim/lib/managed-agents/subblock-options.ts +++ b/apps/sim/lib/managed-agents/subblock-options.ts @@ -1,12 +1,10 @@ import { requestJson } from '@/lib/api/client/request' import { - getManagedAgentDefaultsContract, listManagedAgentAgentsContract, listManagedAgentConnectionsContract, listManagedAgentEnvironmentsContract, listManagedAgentMemoryStoresContract, listManagedAgentVaultsContract, - type ManagedAgentSelfHostedDefaultRow, } from '@/lib/api/contracts' import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' @@ -178,21 +176,3 @@ export async function fetchManagedAgentMemoryStoreOptions( return [] } } - -/** - * Fetch the deployer-configured default rows the Claude Managed Agents - * (self-hosted) block seeds into its Session parameters table. Values - * live in the server-only env var `MANAGED_AGENT_SELF_HOSTED_DEFAULTS` - * and are read via the `/api/managed-agent-defaults` route so they - * never enter the client bundle at build time. - */ -export async function fetchManagedAgentSelfHostedDefaults(): Promise< - ManagedAgentSelfHostedDefaultRow[] -> { - try { - const { selfHosted } = await requestJson(getManagedAgentDefaultsContract, {}) - return selfHosted - } catch { - return [] - } -}