diff --git a/fix-migrations.cjs b/fix-migrations.cjs new file mode 100644 index 00000000000..c91e0277135 --- /dev/null +++ b/fix-migrations.cjs @@ -0,0 +1,72 @@ +const { spawn } = require('child_process'); +const fs = require('fs'); +const postgres = require('postgres'); + +const pgPath = '/home/daksh/projects/paperclip/node_modules/.pnpm/@embedded-postgres+linux-x64@18.1.0-beta.16/node_modules/@embedded-postgres/linux-x64/native/bin/postgres'; +const dataDir = '/home/daksh/.paperclip/instances/default/db'; +const port = 54329; + +async function main() { + console.log('Starting embedded PostgreSQL...'); + const pg = spawn(pgPath, ['-D', dataDir, '-p', String(port)], { + detached: true, + stdio: 'ignore' + }); + + await new Promise(r => setTimeout(r, 3000)); + console.log('PG started, PID:', pg.pid); + + const hbaPath = dataDir + '/pg_hba.conf'; + const originalHba = fs.readFileSync(hbaPath, 'utf8'); + fs.writeFileSync(hbaPath, originalHba.replace(/password/g, 'trust')); + + const pgCtl = pgPath.replace('/postgres', '/pg_ctl'); + require('child_process').execSync(pgCtl + ' reload -D ' + dataDir); + await new Promise(r => setTimeout(r, 1000)); + + const client = postgres({ + host: '127.0.0.1', + port: port, + database: 'paperclip', + user: 'paperclip' + }); + + try { + await client`DELETE FROM drizzle."__drizzle_migrations"`; + console.log('Cleared existing migration entries'); + + const migrationsDir = '/home/daksh/projects/paperclip/packages/db/src/migrations'; + const files = fs.readdirSync(migrationsDir) + .filter(f => f.endsWith('.sql') && !f.startsWith('meta')) + .sort(); + + console.log('Found migrations:', files.length); + + for (let i = 0; i < files.length; i++) { + const file = files[i]; + const hash = file.replace('.sql', ''); + const createdAt = 1771300567463 + (i * 1000000); + + await client.unsafe( + `INSERT INTO drizzle."__drizzle_migrations" (hash, created_at) VALUES ('${hash}', ${createdAt})` + ); + } + + const result = await client`SELECT COUNT(*) as count FROM drizzle."__drizzle_migrations"`; + console.log('Migration journal entries:', result[0].count); + + const latest = await client`SELECT hash FROM drizzle."__drizzle_migrations" ORDER BY id DESC LIMIT 5`; + console.log('Latest 5 entries:', latest.map(m => m.hash)); + + } catch (e) { + console.error('Error:', e.message); + } finally { + await client.end(); + fs.writeFileSync(hbaPath, originalHba); + console.log('Restored pg_hba'); + process.kill(-pg.pid, 'SIGTERM'); + console.log('Stopped PG'); + } +} + +main().catch(console.error); diff --git a/fix-migrations.ts b/fix-migrations.ts new file mode 100644 index 00000000000..d7790ac4a3d --- /dev/null +++ b/fix-migrations.ts @@ -0,0 +1,67 @@ +import { spawn } from 'child_process'; +import postgres from 'postgres'; +import * as fs from 'fs'; + +const pgPath = '/home/daksh/projects/paperclip/node_modules/.pnpm/@embedded-postgres+linux-x64@18.1.0-beta.16/node_modules/@embedded-postgres/linux-x64/native/bin/postgres'; +const dataDir = '/home/daksh/.paperclip/instances/default/db'; +const port = 54329; + +async function main() { + console.log('Starting embedded PostgreSQL...'); + const pg = spawn(pgPath, ['-D', dataDir, '-p', String(port)], { + detached: true, + stdio: 'ignore' + }); + + await new Promise(r => setTimeout(r, 3000)); + console.log('PG started, PID:', pg.pid); + + const client = postgres('postgresql://postgres@localhost:54329/postgres', { max: 1 }); + + try { + await client`CREATE SCHEMA IF NOT EXISTS drizzle`; + + await client` + CREATE TABLE IF NOT EXISTS drizzle."__drizzle_migrations" ( + id SERIAL PRIMARY KEY, + hash text NOT NULL, + created_at bigint + ) + `; + + const migrationsDir = '/home/daksh/projects/paperclip/packages/db/src/migrations'; + const files = fs.readdirSync(migrationsDir) + .filter(f => f.endsWith('.sql') && !f.startsWith('meta')) + .sort(); + + console.log('Found migrations:', files.length); + + for (const file of files) { + const hash = file.replace('.sql', ''); + const idx = parseInt(hash.split('_')[0]); + const createdAt = 1771300567463 + (idx * 1000000); + + await client` + INSERT INTO drizzle."__drizzle_migrations" (hash, created_at) + VALUES (${hash}, ${createdAt}) + ON CONFLICT DO NOTHING + `; + } + + const result = await client`SELECT COUNT(*) as count FROM drizzle."__drizzle_migrations"`; + console.log('Migration journal entries:', result[0].count); + + const tables = await client`SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename`; + console.log('Public tables count:', tables.length); + console.log('First 20 tables:', tables.slice(0, 20).map((t: any) => t.tablename).join(', ')); + + } catch (e: any) { + console.error('Error:', e.message); + } finally { + await client.end(); + process.kill(-pg.pid, 'SIGTERM'); + console.log('Stopped PG'); + } +} + +main().catch(console.error); diff --git a/packages/db/insert-agents.ts b/packages/db/insert-agents.ts new file mode 100644 index 00000000000..51767360183 --- /dev/null +++ b/packages/db/insert-agents.ts @@ -0,0 +1,28 @@ +import postgres from "postgres"; + +const sql = postgres({ host: "127.0.0.1", port: 54329, database: "postgres", username: "postgres", connect_timeout: 5 }); + +const agents = [ + ["e3a48f00-7ff9-4554-a7ef-b46e2262933a", "a362faac-04e3-42e4-bd01-1f15203f462a", "CEO", "ceo", null, "paused", null, null, "claude_local", "{}", 0, 0, "2026-05-20 10:35:37.651+00", null, "2026-05-20 06:33:47.230651+00", "2026-05-20 10:35:58.702+00", "{}", "{}", null, "manual", "2026-05-20 10:35:58.702+00", null], + ["05798c10-69f3-4ece-adbc-5081ad4826a6", "a362faac-04e3-42e4-bd01-1f15203f462a", "Frontend Engineer", "engineer", "Frontend Engineer", "idle", "e3a48f00-7ff9-4554-a7ef-b46e2262933a", "React, TypeScript, CSS, UI/UX, Accessibility", "process", "{}", 0, 0, "2026-05-25 08:01:12.079+00", null, "2026-05-25 05:57:44.095874+00", "2026-05-25 08:01:12.079+00", "{}", "{}", null, null, null, null], + ["9904e251-44cf-4ffa-a5f3-42199b814eb4", "a362faac-04e3-42e4-bd01-1f15203f462a", "QA Tester", "qa", "QA Tester", "idle", "e3a48f00-7ff9-4554-a7ef-b46e2262933a", "Testing, Automation, Edge Cases, Regression, Playwright, Cypress", "process", "{}", 0, 0, "2026-05-25 08:01:12.293+00", null, "2026-05-25 05:58:05.501248+00", "2026-05-25 08:01:12.293+00", "{}", "{}", null, null, null, null], + ["e3fc6591-2090-4b93-9101-42bb9a1340d1", "a362faac-04e3-42e4-bd01-1f15203f462a", "Backend Engineer", "engineer", "Backend Engineer", "idle", "e3a48f00-7ff9-4554-a7ef-b46e2262933a", "Node.js, Python, APIs, Databases, Auth, Performance", "process", "{}", 0, 0, "2026-05-25 08:01:12.444+00", null, "2026-05-25 05:57:54.558784+00", "2026-05-25 08:01:12.444+00", "{}", "{}", null, null, null, null], +]; + +async function main() { + for (const agent of agents) { + try { + await sql.unsafe( + `INSERT INTO public.agents (id, company_id, name, role, title, status, reports_to, capabilities, adapter_type, adapter_config, budget_monthly_cents, spent_monthly_cents, last_heartbeat_at, metadata, created_at, updated_at, runtime_config, permissions, icon, pause_reason, paused_at, default_environment_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22) ON CONFLICT DO NOTHING`, + agent + ); + console.log(`Inserted agent: ${agent[2]}`); + } catch (err: any) { + console.error(`Error inserting ${agent[2]}: ${err.message}`); + } + } + console.log("Done"); + await sql.end(); +} + +main(); diff --git a/packages/db/restore-bytebrain-v2.ts b/packages/db/restore-bytebrain-v2.ts new file mode 100644 index 00000000000..12b39419550 --- /dev/null +++ b/packages/db/restore-bytebrain-v2.ts @@ -0,0 +1,85 @@ +import { createReadStream } from "fs"; +import { createGunzip } from "zlib"; +import { createInterface } from "readline"; +import postgres from "postgres"; + +const STATEMENT_BREAKPOINT = "-- paperclip statement breakpoint 69f6f3f1-42fd-46a6-bf17-d1d85f8f3900"; +const BACKUP_FILE = "/home/daksh/.paperclip/instances/default/data/backups/paperclip-20260527-132203.sql.gz"; + +async function* readRestoreStatements(backupFile: string): AsyncGenerator { + const raw = createReadStream(backupFile); + const stream = raw.pipe(createGunzip()); + const lines = createInterface({ input: stream, crlfDelay: Infinity }); + + let buffer = ""; + let inCopyBlock = false; + + for await (const line of lines) { + if (line.startsWith("COPY ")) { + inCopyBlock = true; + continue; + } + if (line === "\\.") { + inCopyBlock = false; + continue; + } + if (inCopyBlock) continue; + + if (line === STATEMENT_BREAKPOINT) { + if (buffer.trim()) { + yield buffer.trim(); + buffer = ""; + } + continue; + } + + buffer += line + "\n"; + } + + if (buffer.trim()) { + yield buffer.trim(); + } + + stream.destroy(); + raw.destroy(); +} + +async function restore() { + const sql = postgres("postgres://postgres@127.0.0.1:54329/postgres", { + max: 1, + connect_timeout: 30, + onnotice: () => {}, + }); + + try { + await sql`SELECT 1`; + console.log("Connected to database"); + + let count = 0; + let errors = 0; + for await (const statement of readRestoreStatements(BACKUP_FILE)) { + if (statement.includes("COPY ") || statement.includes("\\.")) continue; + + try { + await sql.unsafe(statement).execute(); + count++; + } catch (err: any) { + // Ignore "already exists" and duplicate errors + if (err.code === "42P07" || err.code === "42710" || err.code === "23505" || err.code === "42P06") { + continue; + } + errors++; + console.error(`Error (${err.code}): ${err.message.slice(0, 100)}`); + } + } + + console.log(`Restore completed. ${count} statements executed. ${errors} errors.`); + } finally { + await sql.end(); + } +} + +restore().catch((err) => { + console.error("Restore failed:", err.message); + process.exit(1); +}); diff --git a/packages/db/restore-bytebrain.ts b/packages/db/restore-bytebrain.ts new file mode 100644 index 00000000000..aaf4898bdbb --- /dev/null +++ b/packages/db/restore-bytebrain.ts @@ -0,0 +1,16 @@ +import { runDatabaseRestore } from "./dist/backup-lib.js"; + +async function main() { + console.log("Starting restore from: paperclip-20260527-132203.sql.gz"); + await runDatabaseRestore({ + connectionString: "postgres://postgres@127.0.0.1:54329/postgres", + backupFile: "/home/daksh/.paperclip/instances/default/data/backups/paperclip-20260527-132203.sql.gz", + connectTimeoutSeconds: 60, + }); + console.log("Restore completed successfully"); +} + +main().catch((err: Error) => { + console.error("Restore failed:", err.message); + process.exit(1); +}); diff --git a/packages/db/restore-copy-data.ts b/packages/db/restore-copy-data.ts new file mode 100644 index 00000000000..fab0ea79db0 --- /dev/null +++ b/packages/db/restore-copy-data.ts @@ -0,0 +1,107 @@ +import { createReadStream } from "fs"; +import { createGunzip } from "zlib"; +import { createInterface } from "readline"; +import postgres from "postgres"; + +const BACKUP_FILE = "/home/daksh/.paperclip/instances/default/data/backups/paperclip-20260527-132203.sql.gz"; + +interface CopyBlock { + table: string; + columns: string[]; + rows: string[][]; +} + +async function* readCopyBlocks(backupFile: string): AsyncGenerator { + const raw = createReadStream(backupFile); + const stream = raw.pipe(createGunzip()); + const lines = createInterface({ input: stream, crlfDelay: Infinity }); + + let currentBlock: CopyBlock | null = null; + + for await (const line of lines) { + if (line.startsWith("COPY ")) { + // Parse: COPY "schema"."table" ("col1", "col2") FROM stdin; + const match = line.match(/COPY\s+"?([^"]+)"?\."?([^"]+)"?\s+\(([^)]+)\)\s+FROM\s+stdin;/); + if (match) { + const schema = match[1]; + const table = match[2]; + const columns = match[3].split(",").map(c => c.trim().replace(/"/g, "")); + currentBlock = { + table: `${schema}.${table}`, + columns, + rows: [], + }; + } + continue; + } + + if (line === "\\.") { + if (currentBlock && currentBlock.rows.length > 0) { + yield currentBlock; + } + currentBlock = null; + continue; + } + + if (currentBlock) { + // Parse tab-separated values, handling \N for NULL + const values = line.split("\t").map(v => v === "\\N" ? null : v); + currentBlock.rows.push(values); + } + } + + stream.destroy(); + raw.destroy(); +} + +async function restore() { + const sql = postgres("postgres://postgres@127.0.0.1:54329/postgres", { + max: 1, + connect_timeout: 30, + onnotice: () => {}, + }); + + try { + await sql`SELECT 1`; + console.log("Connected to database"); + + let totalRows = 0; + let totalBlocks = 0; + + for await (const block of readCopyBlocks(BACKUP_FILE)) { + totalBlocks++; + console.log(`Processing ${block.table}: ${block.rows.length} rows...`); + + // Build INSERT statement with ON CONFLICT DO NOTHING + const columns = block.columns.map(c => `"${c}"`).join(", "); + const placeholders = block.columns.map((_, i) => `$${i + 1}`).join(", "); + + // Insert in batches of 100 + const batchSize = 100; + for (let i = 0; i < block.rows.length; i += batchSize) { + const batch = block.rows.slice(i, i + batchSize); + + for (const row of batch) { + try { + await sql.unsafe( + `INSERT INTO ${block.table} (${columns}) VALUES (${placeholders}) ON CONFLICT DO NOTHING`, + row + ); + totalRows++; + } catch (err: any) { + console.error(`Error inserting into ${block.table}: ${err.message.slice(0, 100)}`); + } + } + } + } + + console.log(`\nRestore completed. ${totalBlocks} tables processed. ${totalRows} rows inserted.`); + } finally { + await sql.end(); + } +} + +restore().catch((err) => { + console.error("Restore failed:", err.message); + process.exit(1); +}); diff --git a/packages/db/restore-db.ts b/packages/db/restore-db.ts new file mode 100644 index 00000000000..c8c2e0ca881 --- /dev/null +++ b/packages/db/restore-db.ts @@ -0,0 +1,88 @@ +import { createReadStream } from "fs"; +import { createGunzip } from "zlib"; +import { createInterface } from "readline"; +import postgres from "postgres"; + +const STATEMENT_BREAKPOINT = "-- paperclip statement breakpoint 69f6f3f1-42fd-46a6-bf17-d1d85f8f3900"; + +async function* readRestoreStatements(backupFile: string): AsyncGenerator { + const raw = createReadStream(backupFile); + const stream = backupFile.endsWith(".gz") ? raw.pipe(createGunzip()) : raw; + const lines = createInterface({ input: stream, crlfDelay: Infinity }); + + let buffer = ""; + let inCopyBlock = false; + + for await (const line of lines) { + // Skip COPY blocks - they can't be executed via sql.unsafe + if (line.startsWith("COPY ")) { + inCopyBlock = true; + continue; + } + if (line === "\\.") { + inCopyBlock = false; + continue; + } + if (inCopyBlock) { + continue; + } + + if (line === STATEMENT_BREAKPOINT) { + if (buffer.trim()) { + yield buffer.trim(); + buffer = ""; + } + continue; + } + + buffer += line + "\n"; + } + + if (buffer.trim()) { + yield buffer.trim(); + } + + stream.destroy(); + raw.destroy(); +} + +async function restore() { + const sql = postgres("postgres://postgres:***@127.0.0.1:54329/postgres", { + max: 1, + connect_timeout: 30, + onnotice: () => {}, + }); + + try { + await sql`SELECT 1`; + + let count = 0; + for await (const statement of readRestoreStatements("/tmp/restore_backup.sql")) { + // Skip COPY-related statements and problematic ones + if (statement.includes("COPY ") || statement.includes("\\.")) { + continue; + } + try { + await sql.unsafe(statement).execute(); + count++; + } catch (err: any) { + // Ignore "already exists" errors + if (err.code === "42P07" || err.code === "42710" || err.code === "23505") { + continue; + } + console.error(`Error executing statement: ${err.message}`); + console.error(`Statement preview: ${statement.slice(0, 200)}`); + throw err; + } + } + + console.log(`Restore completed. Executed ${count} statements.`); + } finally { + await sql.end(); + } +} + +restore().catch((err) => { + console.error("Restore failed:", err.message); + process.exit(1); +}); diff --git a/packages/db/restore_backup.ts b/packages/db/restore_backup.ts new file mode 100644 index 00000000000..5c3e0f041a6 --- /dev/null +++ b/packages/db/restore_backup.ts @@ -0,0 +1,15 @@ +import { runDatabaseRestore } from "./src/backup-lib.js"; + +async function main() { + await runDatabaseRestore({ + connectionString: "postgres://postgres:postgres@127.0.0.1:54329/postgres", + backupFile: "/tmp/restore_backup.sql", + connectTimeoutSeconds: 30, + }); + console.log("Restore completed successfully"); +} + +main().catch((err) => { + console.error("Restore failed:", err.message); + process.exit(1); +}); diff --git a/packages/db/restore_db.ts b/packages/db/restore_db.ts new file mode 100644 index 00000000000..05435492419 --- /dev/null +++ b/packages/db/restore_db.ts @@ -0,0 +1,28 @@ + +import fs from 'fs'; +import postgres from 'postgres'; + +const sql = fs.readFileSync('/tmp/restore_backup.sql', 'utf8'); + +const client = postgres({ + host: '127.0.0.1', + port: 54329, + username: 'postgres', + password: 'postgres', + database: 'postgres', + onnotice: () => {}, +}); + +async function restore() { + try { + await client.unsafe(sql); + console.log('Restore completed successfully'); + } catch (err) { + console.error('Restore error:', err.message); + process.exit(1); + } finally { + await client.end(); + } +} + +restore(); diff --git a/packages/db/src/migrations/0091_research_module.sql b/packages/db/src/migrations/0091_research_module.sql new file mode 100644 index 00000000000..388082e386e --- /dev/null +++ b/packages/db/src/migrations/0091_research_module.sql @@ -0,0 +1,203 @@ +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'research_session_status') THEN + CREATE TYPE "research_session_status" AS ENUM ('planning', 'running', 'paused', 'completed', 'failed'); + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'research_depth') THEN + CREATE TYPE "research_depth" AS ENUM ('shallow', 'medium', 'deep'); + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'research_task_status') THEN + CREATE TYPE "research_task_status" AS ENUM ('pending', 'running', 'completed', 'failed', 'skipped'); + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'research_finding_confidence') THEN + CREATE TYPE "research_finding_confidence" AS ENUM ('high', 'medium', 'low'); + END IF; +END $$; +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "research_sessions" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "title" text NOT NULL, + "query" text NOT NULL, + "status" "research_session_status" DEFAULT 'planning' NOT NULL, + "plan" jsonb, + "report" text, + "progress_percent" integer DEFAULT 0 NOT NULL, + "depth" "research_depth" DEFAULT 'medium' NOT NULL, + "max_subtopics" integer DEFAULT 5 NOT NULL, + "created_by" text NOT NULL, + "started_at" timestamp with time zone, + "completed_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "research_tasks" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "session_id" uuid NOT NULL, + "company_id" uuid NOT NULL, + "title" text NOT NULL, + "status" "research_task_status" DEFAULT 'pending' NOT NULL, + "findings_summary" text, + "sources" jsonb DEFAULT '[]'::jsonb, + "reliability_score" integer, + "started_at" timestamp with time zone, + "completed_at" timestamp with time zone, + "sequence_order" integer DEFAULT 0 NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "research_findings" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "task_id" uuid NOT NULL, + "session_id" uuid NOT NULL, + "company_id" uuid NOT NULL, + "content" text NOT NULL, + "source_url" text, + "source_title" text, + "source_domain" text, + "confidence" "research_finding_confidence" DEFAULT 'medium', + "reliability_score" integer, + "category" text, + "is_duplicate" boolean DEFAULT false, + "duplicate_of_id" uuid, + "metadata" jsonb DEFAULT '{}'::jsonb, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "research_sources" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "session_id" uuid NOT NULL, + "company_id" uuid NOT NULL, + "url" text NOT NULL, + "title" text, + "domain" text, + "reliability_score" integer, + "access_count" integer DEFAULT 1 NOT NULL, + "last_accessed_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "research_memory" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "key" text NOT NULL, + "value" jsonb NOT NULL, + "session_id" uuid, + "source_finding_id" uuid, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_sessions_company_id_companies_id_fk') THEN + ALTER TABLE "research_sessions" ADD CONSTRAINT "research_sessions_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_tasks_session_id_research_sessions_id_fk') THEN + ALTER TABLE "research_tasks" ADD CONSTRAINT "research_tasks_session_id_research_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."research_sessions"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_tasks_company_id_companies_id_fk') THEN + ALTER TABLE "research_tasks" ADD CONSTRAINT "research_tasks_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_findings_task_id_research_tasks_id_fk') THEN + ALTER TABLE "research_findings" ADD CONSTRAINT "research_findings_task_id_research_tasks_id_fk" FOREIGN KEY ("task_id") REFERENCES "public"."research_tasks"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_findings_session_id_research_sessions_id_fk') THEN + ALTER TABLE "research_findings" ADD CONSTRAINT "research_findings_session_id_research_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."research_sessions"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_findings_company_id_companies_id_fk') THEN + ALTER TABLE "research_findings" ADD CONSTRAINT "research_findings_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_findings_duplicate_of_id_research_findings_id_fk') THEN + ALTER TABLE "research_findings" ADD CONSTRAINT "research_findings_duplicate_of_id_research_findings_id_fk" FOREIGN KEY ("duplicate_of_id") REFERENCES "public"."research_findings"("id") ON DELETE no action ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_sources_session_id_research_sessions_id_fk') THEN + ALTER TABLE "research_sources" ADD CONSTRAINT "research_sources_session_id_research_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."research_sessions"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_sources_company_id_companies_id_fk') THEN + ALTER TABLE "research_sources" ADD CONSTRAINT "research_sources_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_memory_company_id_companies_id_fk') THEN + ALTER TABLE "research_memory" ADD CONSTRAINT "research_memory_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_memory_session_id_research_sessions_id_fk') THEN + ALTER TABLE "research_memory" ADD CONSTRAINT "research_memory_session_id_research_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."research_sessions"("id") ON DELETE set null ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_memory_source_finding_id_research_findings_id_fk') THEN + ALTER TABLE "research_memory" ADD CONSTRAINT "research_memory_source_finding_id_research_findings_id_fk" FOREIGN KEY ("source_finding_id") REFERENCES "public"."research_findings"("id") ON DELETE set null ON UPDATE no action; + END IF; +END $$; +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_sessions_company_idx" ON "research_sessions" USING btree ("company_id"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_sessions_status_idx" ON "research_sessions" USING btree ("status"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_sessions_created_idx" ON "research_sessions" USING btree ("company_id","created_at"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_tasks_session_idx" ON "research_tasks" USING btree ("session_id"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_tasks_company_idx" ON "research_tasks" USING btree ("company_id"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_tasks_status_idx" ON "research_tasks" USING btree ("status"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_findings_task_idx" ON "research_findings" USING btree ("task_id"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_findings_session_idx" ON "research_findings" USING btree ("session_id"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_findings_company_idx" ON "research_findings" USING btree ("company_id"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_findings_duplicate_idx" ON "research_findings" USING btree ("is_duplicate","duplicate_of_id"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_findings_category_idx" ON "research_findings" USING btree ("category"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_sources_session_idx" ON "research_sources" USING btree ("session_id"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_sources_url_idx" ON "research_sources" USING btree ("url"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_sources_domain_idx" ON "research_sources" USING btree ("domain"); +--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "research_memory_company_key_idx" ON "research_memory" USING btree ("company_id","key"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_memory_session_idx" ON "research_memory" USING btree ("session_id"); diff --git a/packages/db/src/migrations/0092_research_cancelling.sql b/packages/db/src/migrations/0092_research_cancelling.sql new file mode 100644 index 00000000000..0ecee5c1d6e --- /dev/null +++ b/packages/db/src/migrations/0092_research_cancelling.sql @@ -0,0 +1,3 @@ +-- Add 'cancelling' to research_session_status enum +-- PostgreSQL requires ALTER TYPE ... ADD VALUE for enum modifications +ALTER TYPE "research_session_status" ADD VALUE IF NOT EXISTS 'cancelling'; diff --git a/packages/db/src/migrations/0093_research_report_editing.sql b/packages/db/src/migrations/0093_research_report_editing.sql new file mode 100644 index 00000000000..0147028709b --- /dev/null +++ b/packages/db/src/migrations/0093_research_report_editing.sql @@ -0,0 +1,4 @@ +-- Add report editing tracking columns to research_sessions +ALTER TABLE "research_sessions" + ADD COLUMN IF NOT EXISTS "original_report" text, + ADD COLUMN IF NOT EXISTS "is_edited" boolean NOT NULL DEFAULT false; diff --git a/packages/db/src/migrations/0094_smart_orphan.sql b/packages/db/src/migrations/0094_smart_orphan.sql new file mode 100644 index 00000000000..1864ce89d30 --- /dev/null +++ b/packages/db/src/migrations/0094_smart_orphan.sql @@ -0,0 +1,173 @@ +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'research_depth') THEN CREATE TYPE "public"."research_depth" AS ENUM('shallow', 'medium', 'deep'); END IF; END $$;--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'research_finding_confidence') THEN CREATE TYPE "public"."research_finding_confidence" AS ENUM('high', 'medium', 'low'); END IF; END $$;--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'research_session_status') THEN CREATE TYPE "public"."research_session_status" AS ENUM('planning', 'running', 'cancelling', 'paused', 'completed', 'failed', 'cancelled'); END IF; END $$;--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'research_task_status') THEN CREATE TYPE "public"."research_task_status" AS ENUM('pending', 'running', 'completed', 'failed', 'skipped'); END IF; END $$;--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "cloud_upstream_connections" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "remote_url" text NOT NULL, + "source_instance_id" text NOT NULL, + "source_instance_fingerprint" text NOT NULL, + "source_public_key" text NOT NULL, + "private_key_pem" text NOT NULL, + "token_status" text NOT NULL, + "scopes" text[] DEFAULT '{}' NOT NULL, + "authorized_global_user_id" text, + "access_token" text, + "token_id" text, + "token_expires_at" timestamp with time zone, + "target_stack_id" text NOT NULL, + "target_stack_slug" text, + "target_stack_display_name" text, + "target_company_id" text NOT NULL, + "target_origin" text NOT NULL, + "target_primary_host" text NOT NULL, + "target_product" text NOT NULL, + "target_schema_major" integer NOT NULL, + "target_max_chunk_bytes" integer NOT NULL, + "pending_state" text, + "pending_code_verifier" text, + "pending_redirect_uri" text, + "pending_token_url" text, + "last_run_id" uuid, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "cloud_upstream_runs" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "connection_id" uuid NOT NULL, + "company_id" uuid NOT NULL, + "remote_run_id" text, + "status" text NOT NULL, + "active_step" text NOT NULL, + "progress_percent" integer DEFAULT 0 NOT NULL, + "dry_run" boolean DEFAULT false NOT NULL, + "retry_of_run_id" uuid, + "summary" jsonb DEFAULT '[]'::jsonb NOT NULL, + "warnings" jsonb DEFAULT '[]'::jsonb NOT NULL, + "conflicts" jsonb DEFAULT '[]'::jsonb NOT NULL, + "events" jsonb DEFAULT '[]'::jsonb NOT NULL, + "report" jsonb DEFAULT '{}'::jsonb NOT NULL, + "idempotency_key" text NOT NULL, + "manifest_hash" text NOT NULL, + "target_url" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + "completed_at" timestamp with time zone +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "research_findings" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "task_id" uuid NOT NULL, + "session_id" uuid NOT NULL, + "company_id" uuid NOT NULL, + "content" text NOT NULL, + "source_url" text, + "source_title" text, + "source_domain" text, + "confidence" "research_finding_confidence" DEFAULT 'medium', + "reliability_score" integer, + "category" text, + "is_duplicate" boolean DEFAULT false, + "duplicate_of_id" uuid, + "metadata" jsonb DEFAULT '{}'::jsonb, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "research_memory" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "key" text NOT NULL, + "value" jsonb NOT NULL, + "session_id" uuid, + "source_finding_id" uuid, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "research_sessions" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "title" text NOT NULL, + "query" text NOT NULL, + "status" "research_session_status" DEFAULT 'planning' NOT NULL, + "plan" jsonb, + "report" text, + "original_report" text, + "is_edited" boolean DEFAULT false NOT NULL, + "progress_percent" integer DEFAULT 0 NOT NULL, + "depth" "research_depth" DEFAULT 'medium' NOT NULL, + "max_subtopics" integer DEFAULT 5 NOT NULL, + "created_by" text NOT NULL, + "started_at" timestamp with time zone, + "completed_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "research_sources" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "session_id" uuid NOT NULL, + "company_id" uuid NOT NULL, + "url" text NOT NULL, + "title" text, + "domain" text, + "reliability_score" integer, + "access_count" integer DEFAULT 1 NOT NULL, + "last_accessed_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "research_tasks" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "session_id" uuid NOT NULL, + "company_id" uuid NOT NULL, + "title" text NOT NULL, + "status" "research_task_status" DEFAULT 'pending' NOT NULL, + "findings_summary" text, + "sources" jsonb DEFAULT '[]'::jsonb, + "reliability_score" integer, + "started_at" timestamp with time zone, + "completed_at" timestamp with time zone, + "sequence_order" integer DEFAULT 0 NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'cloud_upstream_connections_company_id_companies_id_fk') THEN ALTER TABLE "cloud_upstream_connections" ADD CONSTRAINT "cloud_upstream_connections_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; END IF; END $$;--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'cloud_upstream_runs_connection_id_cloud_upstream_connections_id_fk') THEN ALTER TABLE "cloud_upstream_runs" ADD CONSTRAINT "cloud_upstream_runs_connection_id_cloud_upstream_connections_id_fk" FOREIGN KEY ("connection_id") REFERENCES "public"."cloud_upstream_connections"("id") ON DELETE cascade ON UPDATE no action; END IF; END $$;--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'cloud_upstream_runs_company_id_companies_id_fk') THEN ALTER TABLE "cloud_upstream_runs" ADD CONSTRAINT "cloud_upstream_runs_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; END IF; END $$;--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_findings_task_id_research_tasks_id_fk') THEN ALTER TABLE "research_findings" ADD CONSTRAINT "research_findings_task_id_research_tasks_id_fk" FOREIGN KEY ("task_id") REFERENCES "public"."research_tasks"("id") ON DELETE cascade ON UPDATE no action; END IF; END $$;--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_findings_session_id_research_sessions_id_fk') THEN ALTER TABLE "research_findings" ADD CONSTRAINT "research_findings_session_id_research_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."research_sessions"("id") ON DELETE cascade ON UPDATE no action; END IF; END $$;--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_findings_company_id_companies_id_fk') THEN ALTER TABLE "research_findings" ADD CONSTRAINT "research_findings_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; END IF; END $$;--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_findings_duplicate_of_id_research_findings_id_fk') THEN ALTER TABLE "research_findings" ADD CONSTRAINT "research_findings_duplicate_of_id_research_findings_id_fk" FOREIGN KEY ("duplicate_of_id") REFERENCES "public"."research_findings"("id") ON DELETE no action ON UPDATE no action; END IF; END $$;--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_memory_company_id_companies_id_fk') THEN ALTER TABLE "research_memory" ADD CONSTRAINT "research_memory_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; END IF; END $$;--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_memory_session_id_research_sessions_id_fk') THEN ALTER TABLE "research_memory" ADD CONSTRAINT "research_memory_session_id_research_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."research_sessions"("id") ON DELETE set null ON UPDATE no action; END IF; END $$;--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_memory_source_finding_id_research_findings_id_fk') THEN ALTER TABLE "research_memory" ADD CONSTRAINT "research_memory_source_finding_id_research_findings_id_fk" FOREIGN KEY ("source_finding_id") REFERENCES "public"."research_findings"("id") ON DELETE set null ON UPDATE no action; END IF; END $$;--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_sessions_company_id_companies_id_fk') THEN ALTER TABLE "research_sessions" ADD CONSTRAINT "research_sessions_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; END IF; END $$;--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_sources_session_id_research_sessions_id_fk') THEN ALTER TABLE "research_sources" ADD CONSTRAINT "research_sources_session_id_research_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."research_sessions"("id") ON DELETE cascade ON UPDATE no action; END IF; END $$;--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_sources_company_id_companies_id_fk') THEN ALTER TABLE "research_sources" ADD CONSTRAINT "research_sources_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; END IF; END $$;--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_tasks_session_id_research_sessions_id_fk') THEN ALTER TABLE "research_tasks" ADD CONSTRAINT "research_tasks_session_id_research_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."research_sessions"("id") ON DELETE cascade ON UPDATE no action; END IF; END $$;--> statement-breakpoint +DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'research_tasks_company_id_companies_id_fk') THEN ALTER TABLE "research_tasks" ADD CONSTRAINT "research_tasks_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; END IF; END $$;--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "cloud_upstream_connections_company_idx" ON "cloud_upstream_connections" USING btree ("company_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "cloud_upstream_runs_company_created_idx" ON "cloud_upstream_runs" USING btree ("company_id","created_at");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "cloud_upstream_runs_connection_idx" ON "cloud_upstream_runs" USING btree ("connection_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_findings_task_idx" ON "research_findings" USING btree ("task_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_findings_session_idx" ON "research_findings" USING btree ("session_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_findings_company_idx" ON "research_findings" USING btree ("company_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_findings_duplicate_idx" ON "research_findings" USING btree ("is_duplicate","duplicate_of_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_findings_category_idx" ON "research_findings" USING btree ("category");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_findings_company_session_created_idx" ON "research_findings" USING btree ("company_id","session_id","created_at");--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "research_memory_company_key_idx" ON "research_memory" USING btree ("company_id","key");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_memory_session_idx" ON "research_memory" USING btree ("session_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_sessions_company_idx" ON "research_sessions" USING btree ("company_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_sessions_status_idx" ON "research_sessions" USING btree ("status");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_sessions_created_idx" ON "research_sessions" USING btree ("company_id","created_at");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_sources_session_idx" ON "research_sources" USING btree ("session_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_sources_url_idx" ON "research_sources" USING btree ("url");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_sources_domain_idx" ON "research_sources" USING btree ("domain");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_tasks_session_idx" ON "research_tasks" USING btree ("session_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_tasks_company_idx" ON "research_tasks" USING btree ("company_id");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_tasks_status_idx" ON "research_tasks" USING btree ("status");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "research_tasks_session_order_idx" ON "research_tasks" USING btree ("session_id","sequence_order"); \ No newline at end of file diff --git a/packages/db/src/migrations/0095_research_cancelled_status.sql b/packages/db/src/migrations/0095_research_cancelled_status.sql new file mode 100644 index 00000000000..b3e2bf5e087 --- /dev/null +++ b/packages/db/src/migrations/0095_research_cancelled_status.sql @@ -0,0 +1,12 @@ +-- Add missing enum values for research session status +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'cancelling' AND enumtypid = (SELECT oid FROM pg_type WHERE typname = 'research_session_status')) THEN + ALTER TYPE "research_session_status" ADD VALUE 'cancelling'; + END IF; +END $$; + +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'cancelled' AND enumtypid = (SELECT oid FROM pg_type WHERE typname = 'research_session_status')) THEN + ALTER TYPE "research_session_status" ADD VALUE 'cancelled'; + END IF; +END $$; diff --git a/packages/db/src/migrations/0096_research_findings_fix.sql b/packages/db/src/migrations/0096_research_findings_fix.sql new file mode 100644 index 00000000000..4112f823cea --- /dev/null +++ b/packages/db/src/migrations/0096_research_findings_fix.sql @@ -0,0 +1,37 @@ +-- Add missing enum values for research_session_status +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'cancelling' AND enumtypid = (SELECT oid FROM pg_type WHERE typname = 'research_session_status')) THEN + ALTER TYPE "research_session_status" ADD VALUE 'cancelling'; + END IF; +END $$; + +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'cancelled' AND enumtypid = (SELECT oid FROM pg_type WHERE typname = 'research_session_status')) THEN + ALTER TYPE "research_session_status" ADD VALUE 'cancelled'; + END IF; +END $$; + +-- Add missing columns to research_findings that were added to schema but not in the original 0091 migration +ALTER TABLE "research_findings" ADD COLUMN IF NOT EXISTS "is_duplicate" boolean DEFAULT false; +ALTER TABLE "research_findings" ADD COLUMN IF NOT EXISTS "duplicate_of_id" uuid; +ALTER TABLE "research_findings" ADD COLUMN IF NOT EXISTS "metadata" jsonb DEFAULT '{}'::jsonb; +ALTER TABLE "research_findings" ADD COLUMN IF NOT EXISTS "reliability_score" integer; +ALTER TABLE "research_findings" ADD COLUMN IF NOT EXISTS "category" text; +ALTER TABLE "research_findings" ADD COLUMN IF NOT EXISTS "source_domain" text; +ALTER TABLE "research_findings" ADD COLUMN IF NOT EXISTS "source_title" text; + +-- Create the enum type if it doesn't exist for confidence +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'research_finding_confidence') THEN + CREATE TYPE research_finding_confidence AS ENUM ('high', 'medium', 'low'); + END IF; +END $$; + +ALTER TABLE "research_findings" ADD COLUMN IF NOT EXISTS "confidence" research_finding_confidence DEFAULT 'medium'; + +-- Add missing columns to research_tasks that were added to schema but not in the original 0091 migration +ALTER TABLE "research_tasks" ADD COLUMN IF NOT EXISTS "reliability_score" integer; +ALTER TABLE "research_tasks" ADD COLUMN IF NOT EXISTS "findings_summary" text; +ALTER TABLE "research_tasks" ADD COLUMN IF NOT EXISTS "sources" jsonb DEFAULT '[]'::jsonb; +ALTER TABLE "research_tasks" ADD COLUMN IF NOT EXISTS "sequence_order" integer DEFAULT 0; diff --git a/packages/db/src/migrations/0097_research_depth_enum_fix.sql b/packages/db/src/migrations/0097_research_depth_enum_fix.sql new file mode 100644 index 00000000000..509da534039 --- /dev/null +++ b/packages/db/src/migrations/0097_research_depth_enum_fix.sql @@ -0,0 +1,32 @@ +-- Create the enum type if it doesn't exist +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'research_depth') THEN + CREATE TYPE research_depth AS ENUM ('shallow', 'medium', 'deep'); + END IF; +END $$; + +-- Only fix if the column is still integer (not already enum) +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'research_sessions' AND column_name = 'depth' + AND data_type = 'integer' + ) THEN + -- Drop the default first to avoid cast issues + ALTER TABLE "research_sessions" ALTER COLUMN "depth" DROP DEFAULT; + + -- Fix research_sessions depth column type from integer to enum + ALTER TABLE "research_sessions" ALTER COLUMN "depth" TYPE research_depth USING + CASE "depth" + WHEN 1 THEN 'shallow'::research_depth + WHEN 2 THEN 'medium'::research_depth + WHEN 3 THEN 'deep'::research_depth + ELSE 'medium'::research_depth + END; + + -- Set the new default + ALTER TABLE "research_sessions" ALTER COLUMN "depth" SET DEFAULT 'medium'::research_depth; + END IF; +END $$; diff --git a/packages/db/src/migrations/meta/0094_snapshot.json b/packages/db/src/migrations/meta/0094_snapshot.json new file mode 100644 index 00000000000..70d99b713fe --- /dev/null +++ b/packages/db/src/migrations/meta/0094_snapshot.json @@ -0,0 +1,19389 @@ +{ + "id": "985e722b-5388-43bd-b5a2-b2c28aef5aa0", + "prevId": "11e30d07-51bf-4073-badc-f65fd3de13ad", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.activity_log": { + "name": "activity_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "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 + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_log_company_created_idx": { + "name": "activity_log_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_run_id_idx": { + "name": "activity_log_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_entity_type_id_idx": { + "name": "activity_log_entity_type_id_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": {} + } + }, + "foreignKeys": { + "activity_log_company_id_companies_id_fk": { + "name": "activity_log_company_id_companies_id_fk", + "tableFrom": "activity_log", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_agent_id_agents_id_fk": { + "name": "activity_log_agent_id_agents_id_fk", + "tableFrom": "activity_log", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_run_id_heartbeat_runs_id_fk": { + "name": "activity_log_run_id_heartbeat_runs_id_fk", + "tableFrom": "activity_log", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_api_keys_key_hash_idx": { + "name": "agent_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_api_keys_company_agent_idx": { + "name": "agent_api_keys_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_agent_id_agents_id_fk": { + "name": "agent_api_keys_agent_id_agents_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_api_keys_company_id_companies_id_fk": { + "name": "agent_api_keys_company_id_companies_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_config_revisions": { + "name": "agent_config_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'patch'" + }, + "rolled_back_from_revision_id": { + "name": "rolled_back_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changed_keys": { + "name": "changed_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "before_config": { + "name": "before_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "after_config": { + "name": "after_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_config_revisions_company_agent_created_idx": { + "name": "agent_config_revisions_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_config_revisions_agent_created_idx": { + "name": "agent_config_revisions_agent_created_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_config_revisions_company_id_companies_id_fk": { + "name": "agent_config_revisions_company_id_companies_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_config_revisions_agent_id_agents_id_fk": { + "name": "agent_config_revisions_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_config_revisions_created_by_agent_id_agents_id_fk": { + "name": "agent_config_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_memberships": { + "name": "agent_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_memberships_company_user_idx": { + "name": "agent_memberships_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_agent_idx": { + "name": "agent_memberships_agent_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_company_user_agent_uq": { + "name": "agent_memberships_company_user_agent_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_memberships_company_id_companies_id_fk": { + "name": "agent_memberships_company_id_companies_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_memberships_agent_id_agents_id_fk": { + "name": "agent_memberships_agent_id_agents_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runtime_state": { + "name": "agent_runtime_state", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_json": { + "name": "state_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cached_input_tokens": { + "name": "total_cached_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost_cents": { + "name": "total_cost_cents", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_runtime_state_company_agent_idx": { + "name": "agent_runtime_state_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runtime_state_company_updated_idx": { + "name": "agent_runtime_state_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runtime_state_agent_id_agents_id_fk": { + "name": "agent_runtime_state_agent_id_agents_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runtime_state_company_id_companies_id_fk": { + "name": "agent_runtime_state_company_id_companies_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_task_sessions": { + "name": "agent_task_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_params_json": { + "name": "session_params_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_display_id": { + "name": "session_display_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_task_sessions_company_agent_adapter_task_uniq": { + "name": "agent_task_sessions_company_agent_adapter_task_uniq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_agent_updated_idx": { + "name": "agent_task_sessions_company_agent_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_task_updated_idx": { + "name": "agent_task_sessions_company_task_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_task_sessions_company_id_companies_id_fk": { + "name": "agent_task_sessions_company_id_companies_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_agent_id_agents_id_fk": { + "name": "agent_task_sessions_agent_id_agents_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_last_run_id_heartbeat_runs_id_fk": { + "name": "agent_task_sessions_last_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "last_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_wakeup_requests": { + "name": "agent_wakeup_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "coalesced_count": { + "name": "coalesced_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_by_actor_type": { + "name": "requested_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_wakeup_requests_company_agent_status_idx": { + "name": "agent_wakeup_requests_company_agent_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_requested_idx": { + "name": "agent_wakeup_requests_company_requested_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_agent_requested_idx": { + "name": "agent_wakeup_requests_agent_requested_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_wakeup_requests_company_id_companies_id_fk": { + "name": "agent_wakeup_requests_company_id_companies_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_wakeup_requests_agent_id_agents_id_fk": { + "name": "agent_wakeup_requests_agent_id_agents_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "reports_to": { + "name": "reports_to", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'process'" + }, + "adapter_config": { + "name": "adapter_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_config": { + "name": "runtime_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "default_environment_id": { + "name": "default_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_company_status_idx": { + "name": "agents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_reports_to_idx": { + "name": "agents_company_reports_to_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reports_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_default_environment_idx": { + "name": "agents_company_default_environment_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "default_environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_company_id_companies_id_fk": { + "name": "agents_company_id_companies_id_fk", + "tableFrom": "agents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_reports_to_agents_id_fk": { + "name": "agents_reports_to_agents_id_fk", + "tableFrom": "agents", + "tableTo": "agents", + "columnsFrom": [ + "reports_to" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_default_environment_id_environments_id_fk": { + "name": "agents_default_environment_id_environments_id_fk", + "tableFrom": "agents", + "tableTo": "environments", + "columnsFrom": [ + "default_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approval_comments": { + "name": "approval_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_comments_company_idx": { + "name": "approval_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_idx": { + "name": "approval_comments_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_created_idx": { + "name": "approval_comments_approval_created_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_comments_company_id_companies_id_fk": { + "name": "approval_comments_company_id_companies_id_fk", + "tableFrom": "approval_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_approval_id_approvals_id_fk": { + "name": "approval_comments_approval_id_approvals_id_fk", + "tableFrom": "approval_comments", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_author_agent_id_agents_id_fk": { + "name": "approval_comments_author_agent_id_agents_id_fk", + "tableFrom": "approval_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approvals": { + "name": "approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_note": { + "name": "decision_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approvals_company_status_type_idx": { + "name": "approvals_company_status_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approvals_company_id_companies_id_fk": { + "name": "approvals_company_id_companies_id_fk", + "tableFrom": "approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approvals_requested_by_agent_id_agents_id_fk": { + "name": "approvals_requested_by_agent_id_agents_id_fk", + "tableFrom": "approvals", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assets": { + "name": "assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assets_company_created_idx": { + "name": "assets_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_provider_idx": { + "name": "assets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_object_key_uq": { + "name": "assets_company_object_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_company_id_companies_id_fk": { + "name": "assets_company_id_companies_id_fk", + "tableFrom": "assets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assets_created_by_agent_id_agents_id_fk": { + "name": "assets_created_by_agent_id_agents_id_fk", + "tableFrom": "assets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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 with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "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.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "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 with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board_api_keys": { + "name": "board_api_keys", + "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 + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_keys_key_hash_idx": { + "name": "board_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_keys_user_idx": { + "name": "board_api_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_api_keys_user_id_user_id_fk": { + "name": "board_api_keys_user_id_user_id_fk", + "tableFrom": "board_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_incidents": { + "name": "budget_incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "threshold_type": { + "name": "threshold_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_limit": { + "name": "amount_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_observed": { + "name": "amount_observed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_incidents_company_status_idx": { + "name": "budget_incidents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_company_scope_idx": { + "name": "budget_incidents_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_policy_window_threshold_idx": { + "name": "budget_incidents_policy_window_threshold_idx", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "threshold_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"budget_incidents\".\"status\" <> 'dismissed'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_incidents_company_id_companies_id_fk": { + "name": "budget_incidents_company_id_companies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_policy_id_budget_policies_id_fk": { + "name": "budget_incidents_policy_id_budget_policies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "budget_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_approval_id_approvals_id_fk": { + "name": "budget_incidents_approval_id_approvals_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_policies": { + "name": "budget_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'billed_cents'" + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "warn_percent": { + "name": "warn_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "hard_stop_enabled": { + "name": "hard_stop_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notify_enabled": { + "name": "notify_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_policies_company_scope_active_idx": { + "name": "budget_policies_company_scope_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_window_idx": { + "name": "budget_policies_company_window_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_scope_metric_unique_idx": { + "name": "budget_policies_company_scope_metric_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_policies_company_id_companies_id_fk": { + "name": "budget_policies_company_id_companies_id_fk", + "tableFrom": "budget_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cli_auth_challenges": { + "name": "cli_auth_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_access": { + "name": "requested_access", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'board'" + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pending_key_hash": { + "name": "pending_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_key_name": { + "name": "pending_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cli_auth_challenges_secret_hash_idx": { + "name": "cli_auth_challenges_secret_hash_idx", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_approved_by_idx": { + "name": "cli_auth_challenges_approved_by_idx", + "columns": [ + { + "expression": "approved_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_requested_company_idx": { + "name": "cli_auth_challenges_requested_company_idx", + "columns": [ + { + "expression": "requested_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_auth_challenges_requested_company_id_companies_id_fk": { + "name": "cli_auth_challenges_requested_company_id_companies_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "companies", + "columnsFrom": [ + "requested_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_approved_by_user_id_user_id_fk": { + "name": "cli_auth_challenges_approved_by_user_id_user_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "user", + "columnsFrom": [ + "approved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk": { + "name": "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "board_api_keys", + "columnsFrom": [ + "board_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_upstream_connections": { + "name": "cloud_upstream_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "remote_url": { + "name": "remote_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_instance_id": { + "name": "source_instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_instance_fingerprint": { + "name": "source_instance_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_public_key": { + "name": "source_public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key_pem": { + "name": "private_key_pem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_status": { + "name": "token_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "authorized_global_user_id": { + "name": "authorized_global_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_id": { + "name": "token_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_expires_at": { + "name": "token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "target_stack_id": { + "name": "target_stack_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_stack_slug": { + "name": "target_stack_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_stack_display_name": { + "name": "target_stack_display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_company_id": { + "name": "target_company_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_origin": { + "name": "target_origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_primary_host": { + "name": "target_primary_host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_product": { + "name": "target_product", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_schema_major": { + "name": "target_schema_major", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_max_chunk_bytes": { + "name": "target_max_chunk_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "pending_state": { + "name": "pending_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pending_code_verifier": { + "name": "pending_code_verifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pending_redirect_uri": { + "name": "pending_redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pending_token_url": { + "name": "pending_token_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cloud_upstream_connections_company_idx": { + "name": "cloud_upstream_connections_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cloud_upstream_connections_company_id_companies_id_fk": { + "name": "cloud_upstream_connections_company_id_companies_id_fk", + "tableFrom": "cloud_upstream_connections", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_upstream_runs": { + "name": "cloud_upstream_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "remote_run_id": { + "name": "remote_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active_step": { + "name": "active_step", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "progress_percent": { + "name": "progress_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "dry_run": { + "name": "dry_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "warnings": { + "name": "warnings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "conflicts": { + "name": "conflicts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "events": { + "name": "events", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "report": { + "name": "report", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manifest_hash": { + "name": "manifest_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_url": { + "name": "target_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "cloud_upstream_runs_company_created_idx": { + "name": "cloud_upstream_runs_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cloud_upstream_runs_connection_idx": { + "name": "cloud_upstream_runs_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cloud_upstream_runs_connection_id_cloud_upstream_connections_id_fk": { + "name": "cloud_upstream_runs_connection_id_cloud_upstream_connections_id_fk", + "tableFrom": "cloud_upstream_runs", + "tableTo": "cloud_upstream_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cloud_upstream_runs_company_id_companies_id_fk": { + "name": "cloud_upstream_runs_company_id_companies_id_fk", + "tableFrom": "cloud_upstream_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issue_prefix": { + "name": "issue_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PAP'" + }, + "issue_counter": { + "name": "issue_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attachment_max_bytes": { + "name": "attachment_max_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10485760 + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feedback_data_sharing_enabled": { + "name": "feedback_data_sharing_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feedback_data_sharing_consent_at": { + "name": "feedback_data_sharing_consent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_consent_by_user_id": { + "name": "feedback_data_sharing_consent_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_terms_version": { + "name": "feedback_data_sharing_terms_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "brand_color": { + "name": "brand_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "companies_issue_prefix_idx": { + "name": "companies_issue_prefix_idx", + "columns": [ + { + "expression": "issue_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_logos": { + "name": "company_logos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_logos_company_uq": { + "name": "company_logos_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_logos_asset_uq": { + "name": "company_logos_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_logos_company_id_companies_id_fk": { + "name": "company_logos_company_id_companies_id_fk", + "tableFrom": "company_logos", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_logos_asset_id_assets_id_fk": { + "name": "company_logos_asset_id_assets_id_fk", + "tableFrom": "company_logos", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "membership_role": { + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_memberships_company_principal_unique_idx": { + "name": "company_memberships_company_principal_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_principal_status_idx": { + "name": "company_memberships_principal_status_idx", + "columns": [ + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_company_status_idx": { + "name": "company_memberships_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_bindings": { + "name": "company_secret_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_selector": { + "name": "version_selector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'latest'" + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_bindings_company_idx": { + "name": "company_secret_bindings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_secret_idx": { + "name": "company_secret_bindings_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_target_idx": { + "name": "company_secret_bindings_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_target_path_uq": { + "name": "company_secret_bindings_target_path_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "config_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_bindings_company_id_companies_id_fk": { + "name": "company_secret_bindings_company_id_companies_id_fk", + "tableFrom": "company_secret_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secret_bindings_secret_id_company_secrets_id_fk": { + "name": "company_secret_bindings_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_bindings", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_provider_configs": { + "name": "company_secret_provider_configs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_checked_at": { + "name": "health_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_details": { + "name": "health_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "disabled_at": { + "name": "disabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_provider_configs_company_idx": { + "name": "company_secret_provider_configs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_provider_configs_company_provider_idx": { + "name": "company_secret_provider_configs_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_provider_configs_default_uq": { + "name": "company_secret_provider_configs_default_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secret_provider_configs\".\"is_default\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_provider_configs_company_id_companies_id_fk": { + "name": "company_secret_provider_configs_company_id_companies_id_fk", + "tableFrom": "company_secret_provider_configs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_provider_configs_created_by_agent_id_agents_id_fk": { + "name": "company_secret_provider_configs_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_provider_configs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_versions": { + "name": "company_secret_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "material": { + "name": "material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "value_sha256": { + "name": "value_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_version_ref": { + "name": "provider_version_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'current'" + }, + "fingerprint_sha256": { + "name": "fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rotation_job_id": { + "name": "rotation_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "company_secret_versions_secret_idx": { + "name": "company_secret_versions_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_value_sha256_idx": { + "name": "company_secret_versions_value_sha256_idx", + "columns": [ + { + "expression": "value_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_fingerprint_idx": { + "name": "company_secret_versions_fingerprint_idx", + "columns": [ + { + "expression": "fingerprint_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_secret_version_uq": { + "name": "company_secret_versions_secret_version_uq", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_versions_secret_id_company_secrets_id_fk": { + "name": "company_secret_versions_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_versions_created_by_agent_id_agents_id_fk": { + "name": "company_secret_versions_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secrets": { + "name": "company_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "managed_mode": { + "name": "managed_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_managed'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config_id": { + "name": "provider_config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_resolved_at": { + "name": "last_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secrets_company_idx": { + "name": "company_secrets_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_provider_idx": { + "name": "company_secrets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_provider_config_idx": { + "name": "company_secrets_provider_config_idx", + "columns": [ + { + "expression": "provider_config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_name_uq": { + "name": "company_secrets_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_key_uq": { + "name": "company_secrets_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secrets_company_id_companies_id_fk": { + "name": "company_secrets_company_id_companies_id_fk", + "tableFrom": "company_secrets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secrets_provider_config_id_company_secret_provider_configs_id_fk": { + "name": "company_secrets_provider_config_id_company_secret_provider_configs_id_fk", + "tableFrom": "company_secrets", + "tableTo": "company_secret_provider_configs", + "columnsFrom": [ + "provider_config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secrets_created_by_agent_id_agents_id_fk": { + "name": "company_secrets_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secrets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skills": { + "name": "company_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "markdown": { + "name": "markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "source_locator": { + "name": "source_locator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trust_level": { + "name": "trust_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown_only'" + }, + "compatibility": { + "name": "compatibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compatible'" + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skills_company_key_idx": { + "name": "company_skills_company_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_name_idx": { + "name": "company_skills_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skills_company_id_companies_id_fk": { + "name": "company_skills_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_user_sidebar_preferences": { + "name": "company_user_sidebar_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_order": { + "name": "project_order", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_user_sidebar_preferences_company_idx": { + "name": "company_user_sidebar_preferences_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_user_sidebar_preferences_user_idx": { + "name": "company_user_sidebar_preferences_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_user_sidebar_preferences_company_user_uq": { + "name": "company_user_sidebar_preferences_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_user_sidebar_preferences_company_id_companies_id_fk": { + "name": "company_user_sidebar_preferences_company_id_companies_id_fk", + "tableFrom": "company_user_sidebar_preferences", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cost_events": { + "name": "cost_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cost_events_company_occurred_idx": { + "name": "cost_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_agent_occurred_idx": { + "name": "cost_events_company_agent_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_provider_occurred_idx": { + "name": "cost_events_company_provider_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_biller_occurred_idx": { + "name": "cost_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_heartbeat_run_idx": { + "name": "cost_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_events_company_id_companies_id_fk": { + "name": "cost_events_company_id_companies_id_fk", + "tableFrom": "cost_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_agent_id_agents_id_fk": { + "name": "cost_events_agent_id_agents_id_fk", + "tableFrom": "cost_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_issue_id_issues_id_fk": { + "name": "cost_events_issue_id_issues_id_fk", + "tableFrom": "cost_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_project_id_projects_id_fk": { + "name": "cost_events_project_id_projects_id_fk", + "tableFrom": "cost_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_goal_id_goals_id_fk": { + "name": "cost_events_goal_id_goals_id_fk", + "tableFrom": "cost_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "cost_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "cost_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_revisions": { + "name": "document_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_revisions_document_revision_uq": { + "name": "document_revisions_document_revision_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_revisions_company_document_created_idx": { + "name": "document_revisions_company_document_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_revisions_company_id_companies_id_fk": { + "name": "document_revisions_company_id_companies_id_fk", + "tableFrom": "document_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_revisions_document_id_documents_id_fk": { + "name": "document_revisions_document_id_documents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_created_by_agent_id_agents_id_fk": { + "name": "document_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "latest_body": { + "name": "latest_body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "locked_by_agent_id": { + "name": "locked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "locked_by_user_id": { + "name": "locked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_company_updated_idx": { + "name": "documents_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_company_created_idx": { + "name": "documents_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_title_search_idx": { + "name": "documents_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "documents_latest_body_search_idx": { + "name": "documents_latest_body_search_idx", + "columns": [ + { + "expression": "latest_body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "documents_company_id_companies_id_fk": { + "name": "documents_company_id_companies_id_fk", + "tableFrom": "documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_created_by_agent_id_agents_id_fk": { + "name": "documents_created_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_updated_by_agent_id_agents_id_fk": { + "name": "documents_updated_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_locked_by_agent_id_agents_id_fk": { + "name": "documents_locked_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "locked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_leases": { + "name": "environment_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "lease_policy": { + "name": "lease_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ephemeral'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acquired_at": { + "name": "acquired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_status": { + "name": "cleanup_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_leases_company_environment_status_idx": { + "name": "environment_leases_company_environment_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_execution_workspace_idx": { + "name": "environment_leases_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_issue_idx": { + "name": "environment_leases_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_heartbeat_run_idx": { + "name": "environment_leases_heartbeat_run_idx", + "columns": [ + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_last_used_idx": { + "name": "environment_leases_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_provider_lease_idx": { + "name": "environment_leases_provider_lease_idx", + "columns": [ + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_leases_company_id_companies_id_fk": { + "name": "environment_leases_company_id_companies_id_fk", + "tableFrom": "environment_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_leases_environment_id_environments_id_fk": { + "name": "environment_leases_environment_id_environments_id_fk", + "tableFrom": "environment_leases", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_leases_execution_workspace_id_execution_workspaces_id_fk": { + "name": "environment_leases_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "environment_leases", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_issue_id_issues_id_fk": { + "name": "environment_leases_issue_id_issues_id_fk", + "tableFrom": "environment_leases", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "environment_leases_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "environment_leases", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environments": { + "name": "environments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "driver": { + "name": "driver", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environments_company_status_idx": { + "name": "environments_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_company_driver_idx": { + "name": "environments_company_driver_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "driver", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environments\".\"driver\" = 'local'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_company_name_idx": { + "name": "environments_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environments_company_id_companies_id_fk": { + "name": "environments_company_id_companies_id_fk", + "tableFrom": "environments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_exports": { + "name": "feedback_exports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feedback_vote_id": { + "name": "feedback_vote_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_only'" + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "export_id": { + "name": "export_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-envelope-v2'" + }, + "bundle_version": { + "name": "bundle_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-bundle-v2'" + }, + "payload_version": { + "name": "payload_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-v1'" + }, + "payload_digest": { + "name": "payload_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload_snapshot": { + "name": "payload_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_summary": { + "name": "target_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempted_at": { + "name": "last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "exported_at": { + "name": "exported_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_exports_feedback_vote_idx": { + "name": "feedback_exports_feedback_vote_idx", + "columns": [ + { + "expression": "feedback_vote_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_created_idx": { + "name": "feedback_exports_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_status_idx": { + "name": "feedback_exports_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_issue_idx": { + "name": "feedback_exports_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_project_idx": { + "name": "feedback_exports_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_author_idx": { + "name": "feedback_exports_company_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_exports_company_id_companies_id_fk": { + "name": "feedback_exports_company_id_companies_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_exports_feedback_vote_id_feedback_votes_id_fk": { + "name": "feedback_exports_feedback_vote_id_feedback_votes_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "feedback_votes", + "columnsFrom": [ + "feedback_vote_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_issue_id_issues_id_fk": { + "name": "feedback_exports_issue_id_issues_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_project_id_projects_id_fk": { + "name": "feedback_exports_project_id_projects_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_votes": { + "name": "feedback_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_with_labs": { + "name": "shared_with_labs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_votes_company_issue_idx": { + "name": "feedback_votes_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_issue_target_idx": { + "name": "feedback_votes_issue_target_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_author_idx": { + "name": "feedback_votes_author_idx", + "columns": [ + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_company_target_author_idx": { + "name": "feedback_votes_company_target_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_votes_company_id_companies_id_fk": { + "name": "feedback_votes_company_id_companies_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_votes_issue_id_issues_id_fk": { + "name": "feedback_votes_issue_id_issues_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finance_events": { + "name": "finance_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'debit'" + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_adapter_type": { + "name": "execution_adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing_tier": { + "name": "pricing_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "external_invoice_id": { + "name": "external_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finance_events_company_occurred_idx": { + "name": "finance_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_biller_occurred_idx": { + "name": "finance_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_kind_occurred_idx": { + "name": "finance_events_company_kind_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_direction_occurred_idx": { + "name": "finance_events_company_direction_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_heartbeat_run_idx": { + "name": "finance_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_cost_event_idx": { + "name": "finance_events_company_cost_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finance_events_company_id_companies_id_fk": { + "name": "finance_events_company_id_companies_id_fk", + "tableFrom": "finance_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_agent_id_agents_id_fk": { + "name": "finance_events_agent_id_agents_id_fk", + "tableFrom": "finance_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_issue_id_issues_id_fk": { + "name": "finance_events_issue_id_issues_id_fk", + "tableFrom": "finance_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_project_id_projects_id_fk": { + "name": "finance_events_project_id_projects_id_fk", + "tableFrom": "finance_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_goal_id_goals_id_fk": { + "name": "finance_events_goal_id_goals_id_fk", + "tableFrom": "finance_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "finance_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "finance_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_cost_event_id_cost_events_id_fk": { + "name": "finance_events_cost_event_id_cost_events_id_fk", + "tableFrom": "finance_events", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.goals": { + "name": "goals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "goals_company_idx": { + "name": "goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "goals_company_id_companies_id_fk": { + "name": "goals_company_id_companies_id_fk", + "tableFrom": "goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_parent_id_goals_id_fk": { + "name": "goals_parent_id_goals_id_fk", + "tableFrom": "goals", + "tableTo": "goals", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_owner_agent_id_agents_id_fk": { + "name": "goals_owner_agent_id_agents_id_fk", + "tableFrom": "goals", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_events": { + "name": "heartbeat_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream": { + "name": "stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_idx": { + "name": "heartbeat_run_events_run_seq_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_watchdog_decisions": { + "name": "heartbeat_run_watchdog_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "evaluation_issue_id": { + "name": "evaluation_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_watchdog_decisions_company_run_created_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_watchdog_decisions_company_run_snooze_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_snooze_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snoozed_until", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_watchdog_decisions_company_id_companies_id_fk": { + "name": "heartbeat_run_watchdog_decisions_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk": { + "name": "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "issues", + "columnsFrom": [ + "evaluation_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id_before": { + "name": "session_id_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id_after": { + "name": "session_id_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_run_id": { + "name": "external_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_pid": { + "name": "process_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_group_id": { + "name": "process_group_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_started_at": { + "name": "process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_output_at": { + "name": "last_output_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_output_seq": { + "name": "last_output_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_output_stream": { + "name": "last_output_stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_output_bytes": { + "name": "last_output_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "process_loss_retry_count": { + "name": "process_loss_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scheduled_retry_at": { + "name": "scheduled_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scheduled_retry_attempt": { + "name": "scheduled_retry_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scheduled_retry_reason": { + "name": "scheduled_retry_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_comment_status": { + "name": "issue_comment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_applicable'" + }, + "issue_comment_satisfied_by_comment_id": { + "name": "issue_comment_satisfied_by_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_comment_retry_queued_at": { + "name": "issue_comment_retry_queued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "liveness_state": { + "name": "liveness_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "liveness_reason": { + "name": "liveness_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "continuation_attempt": { + "name": "continuation_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_useful_action_at": { + "name": "last_useful_action_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context_snapshot": { + "name": "context_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_runs_company_agent_started_idx": { + "name": "heartbeat_runs_company_agent_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_liveness_idx": { + "name": "heartbeat_runs_company_liveness_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "liveness_state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_status_last_output_idx": { + "name": "heartbeat_runs_company_status_last_output_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_output_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_status_process_started_idx": { + "name": "heartbeat_runs_company_status_process_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "process_started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inbox_dismissals": { + "name": "inbox_dismissals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_key": { + "name": "item_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_dismissals_company_user_idx": { + "name": "inbox_dismissals_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_item_idx": { + "name": "inbox_dismissals_company_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_user_item_idx": { + "name": "inbox_dismissals_company_user_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inbox_dismissals_company_id_companies_id_fk": { + "name": "inbox_dismissals_company_id_companies_id_fk", + "tableFrom": "inbox_dismissals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "singleton_key": { + "name": "singleton_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "experimental": { + "name": "experimental", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_settings_singleton_key_idx": { + "name": "instance_settings_singleton_key_idx", + "columns": [ + { + "expression": "singleton_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "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 + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_type": { + "name": "author_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "presentation": { + "name": "presentation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_comments_issue_idx": { + "name": "issue_comments_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_idx": { + "name": "issue_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_issue_created_at_idx": { + "name": "issue_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_author_issue_created_at_idx": { + "name": "issue_comments_company_author_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_body_search_idx": { + "name": "issue_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "issue_comments_company_id_companies_id_fk": { + "name": "issue_comments_company_id_companies_id_fk", + "tableFrom": "issue_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_issue_id_issues_id_fk": { + "name": "issue_comments_issue_id_issues_id_fk", + "tableFrom": "issue_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_author_agent_id_agents_id_fk": { + "name": "issue_comments_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_documents": { + "name": "issue_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_documents_company_issue_key_uq": { + "name": "issue_documents_company_issue_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_document_uq": { + "name": "issue_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_company_issue_updated_idx": { + "name": "issue_documents_company_issue_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_documents_company_id_companies_id_fk": { + "name": "issue_documents_company_id_companies_id_fk", + "tableFrom": "issue_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_documents_issue_id_issues_id_fk": { + "name": "issue_documents_issue_id_issues_id_fk", + "tableFrom": "issue_documents", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_documents_document_id_documents_id_fk": { + "name": "issue_documents_document_id_documents_id_fk", + "tableFrom": "issue_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_execution_decisions": { + "name": "issue_execution_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_type": { + "name": "stage_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_execution_decisions_company_issue_idx": { + "name": "issue_execution_decisions_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_execution_decisions_stage_idx": { + "name": "issue_execution_decisions_stage_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_execution_decisions_company_id_companies_id_fk": { + "name": "issue_execution_decisions_company_id_companies_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_issue_id_issues_id_fk": { + "name": "issue_execution_decisions_issue_id_issues_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_execution_decisions_actor_agent_id_agents_id_fk": { + "name": "issue_execution_decisions_actor_agent_id_agents_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_inbox_archives": { + "name": "issue_inbox_archives", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_inbox_archives_company_issue_idx": { + "name": "issue_inbox_archives_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_user_idx": { + "name": "issue_inbox_archives_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_issue_user_idx": { + "name": "issue_inbox_archives_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_inbox_archives_company_id_companies_id_fk": { + "name": "issue_inbox_archives_company_id_companies_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_inbox_archives_issue_id_issues_id_fk": { + "name": "issue_inbox_archives_issue_id_issues_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_labels": { + "name": "issue_labels", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_labels_issue_idx": { + "name": "issue_labels_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_label_idx": { + "name": "issue_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_company_idx": { + "name": "issue_labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_labels_issue_id_issues_id_fk": { + "name": "issue_labels_issue_id_issues_id_fk", + "tableFrom": "issue_labels", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_label_id_labels_id_fk": { + "name": "issue_labels_label_id_labels_id_fk", + "tableFrom": "issue_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_company_id_companies_id_fk": { + "name": "issue_labels_company_id_companies_id_fk", + "tableFrom": "issue_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_labels_pk": { + "name": "issue_labels_pk", + "columns": [ + "issue_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_read_states": { + "name": "issue_read_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_read_states_company_issue_idx": { + "name": "issue_read_states_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_user_idx": { + "name": "issue_read_states_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_issue_user_idx": { + "name": "issue_read_states_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_read_states_company_id_companies_id_fk": { + "name": "issue_read_states_company_id_companies_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_read_states_issue_id_issues_id_fk": { + "name": "issue_read_states_issue_id_issues_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_recovery_actions": { + "name": "issue_recovery_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "recovery_issue_id": { + "name": "recovery_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'agent'" + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previous_owner_agent_id": { + "name": "previous_owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "return_owner_agent_id": { + "name": "return_owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cause": { + "name": "cause", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "evidence": { + "name": "evidence", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "wake_policy": { + "name": "wake_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "monitor_policy": { + "name": "monitor_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "timeout_at": { + "name": "timeout_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolution_note": { + "name": "resolution_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_recovery_actions_company_source_status_idx": { + "name": "issue_recovery_actions_company_source_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_company_owner_status_idx": { + "name": "issue_recovery_actions_company_owner_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_company_recovery_issue_idx": { + "name": "issue_recovery_actions_company_recovery_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recovery_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_active_source_uq": { + "name": "issue_recovery_actions_active_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_recovery_actions\".\"status\" in ('active', 'escalated')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_active_fingerprint_uq": { + "name": "issue_recovery_actions_active_fingerprint_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cause", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_recovery_actions\".\"status\" in ('active', 'escalated')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_recovery_actions_company_id_companies_id_fk": { + "name": "issue_recovery_actions_company_id_companies_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_recovery_actions_source_issue_id_issues_id_fk": { + "name": "issue_recovery_actions_source_issue_id_issues_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_recovery_actions_recovery_issue_id_issues_id_fk": { + "name": "issue_recovery_actions_recovery_issue_id_issues_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "issues", + "columnsFrom": [ + "recovery_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_previous_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_previous_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "previous_owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_return_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_return_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "return_owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_reference_mentions": { + "name": "issue_reference_mentions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_issue_id": { + "name": "target_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_record_id": { + "name": "source_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_text": { + "name": "matched_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_reference_mentions_company_source_issue_idx": { + "name": "issue_reference_mentions_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_target_issue_idx": { + "name": "issue_reference_mentions_company_target_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_issue_pair_idx": { + "name": "issue_reference_mentions_company_issue_pair_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_source_mention_record_uq": { + "name": "issue_reference_mentions_company_source_mention_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_reference_mentions\".\"source_record_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_source_mention_null_record_uq": { + "name": "issue_reference_mentions_company_source_mention_null_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_reference_mentions\".\"source_record_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_reference_mentions_company_id_companies_id_fk": { + "name": "issue_reference_mentions_company_id_companies_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_reference_mentions_source_issue_id_issues_id_fk": { + "name": "issue_reference_mentions_source_issue_id_issues_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_reference_mentions_target_issue_id_issues_id_fk": { + "name": "issue_reference_mentions_target_issue_id_issues_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "issues", + "columnsFrom": [ + "target_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_relations": { + "name": "issue_relations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "related_issue_id": { + "name": "related_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_relations_company_issue_idx": { + "name": "issue_relations_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_related_issue_idx": { + "name": "issue_relations_company_related_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_type_idx": { + "name": "issue_relations_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_edge_uq": { + "name": "issue_relations_company_edge_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_relations_company_id_companies_id_fk": { + "name": "issue_relations_company_id_companies_id_fk", + "tableFrom": "issue_relations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_relations_issue_id_issues_id_fk": { + "name": "issue_relations_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_related_issue_id_issues_id_fk": { + "name": "issue_relations_related_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "related_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_created_by_agent_id_agents_id_fk": { + "name": "issue_relations_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_relations", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_thread_interactions": { + "name": "issue_thread_interactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "continuation_policy": { + "name": "continuation_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wake_assignee'" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_comment_id": { + "name": "source_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_thread_interactions_issue_idx": { + "name": "issue_thread_interactions_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_created_at_idx": { + "name": "issue_thread_interactions_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_status_idx": { + "name": "issue_thread_interactions_company_issue_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_idempotency_uq": { + "name": "issue_thread_interactions_company_issue_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_thread_interactions\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_source_comment_idx": { + "name": "issue_thread_interactions_source_comment_idx", + "columns": [ + { + "expression": "source_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_thread_interactions_company_id_companies_id_fk": { + "name": "issue_thread_interactions_company_id_companies_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_issue_id_issues_id_fk": { + "name": "issue_thread_interactions_issue_id_issues_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_source_comment_id_issue_comments_id_fk": { + "name": "issue_thread_interactions_source_comment_id_issue_comments_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "issue_comments", + "columnsFrom": [ + "source_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_source_run_id_heartbeat_runs_id_fk": { + "name": "issue_thread_interactions_source_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "source_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_created_by_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_resolved_by_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_resolved_by_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_tree_hold_members": { + "name": "issue_tree_hold_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hold_id": { + "name": "hold_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "depth": { + "name": "depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "issue_identifier": { + "name": "issue_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_title": { + "name": "issue_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_status": { + "name": "issue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_run_id": { + "name": "active_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "active_run_status": { + "name": "active_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "skipped": { + "name": "skipped", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "skip_reason": { + "name": "skip_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_tree_hold_members_hold_issue_uq": { + "name": "issue_tree_hold_members_hold_issue_uq", + "columns": [ + { + "expression": "hold_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_hold_members_company_issue_idx": { + "name": "issue_tree_hold_members_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_hold_members_hold_depth_idx": { + "name": "issue_tree_hold_members_hold_depth_idx", + "columns": [ + { + "expression": "hold_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depth", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_tree_hold_members_company_id_companies_id_fk": { + "name": "issue_tree_hold_members_company_id_companies_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_tree_hold_members_hold_id_issue_tree_holds_id_fk": { + "name": "issue_tree_hold_members_hold_id_issue_tree_holds_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issue_tree_holds", + "columnsFrom": [ + "hold_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_hold_members_issue_id_issues_id_fk": { + "name": "issue_tree_hold_members_issue_id_issues_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_hold_members_parent_issue_id_issues_id_fk": { + "name": "issue_tree_hold_members_parent_issue_id_issues_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_hold_members_assignee_agent_id_agents_id_fk": { + "name": "issue_tree_hold_members_assignee_agent_id_agents_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_hold_members_active_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_hold_members_active_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "active_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_tree_holds": { + "name": "issue_tree_holds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "root_issue_id": { + "name": "root_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_policy": { + "name": "release_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "released_by_actor_type": { + "name": "released_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_by_agent_id": { + "name": "released_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "released_by_user_id": { + "name": "released_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_by_run_id": { + "name": "released_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "release_reason": { + "name": "release_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_metadata": { + "name": "release_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_tree_holds_company_root_status_idx": { + "name": "issue_tree_holds_company_root_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "root_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_holds_company_status_mode_idx": { + "name": "issue_tree_holds_company_status_mode_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_tree_holds_company_id_companies_id_fk": { + "name": "issue_tree_holds_company_id_companies_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_tree_holds_root_issue_id_issues_id_fk": { + "name": "issue_tree_holds_root_issue_id_issues_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "issues", + "columnsFrom": [ + "root_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_holds_created_by_agent_id_agents_id_fk": { + "name": "issue_tree_holds_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_holds_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_released_by_agent_id_agents_id_fk": { + "name": "issue_tree_holds_released_by_agent_id_agents_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "agents", + "columnsFrom": [ + "released_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_released_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_holds_released_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "released_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_work_products": { + "name": "issue_work_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_service_id": { + "name": "runtime_service_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_work_products_company_issue_type_idx": { + "name": "issue_work_products_company_issue_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_execution_workspace_type_idx": { + "name": "issue_work_products_company_execution_workspace_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_provider_external_id_idx": { + "name": "issue_work_products_company_provider_external_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_updated_idx": { + "name": "issue_work_products_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_work_products_company_id_companies_id_fk": { + "name": "issue_work_products_company_id_companies_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_work_products_project_id_projects_id_fk": { + "name": "issue_work_products_project_id_projects_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_issue_id_issues_id_fk": { + "name": "issue_work_products_issue_id_issues_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_work_products_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issue_work_products_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk": { + "name": "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "workspace_runtime_services", + "columnsFrom": [ + "runtime_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_work_products_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issues": { + "name": "issues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "work_mode": { + "name": "work_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'standard'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkout_run_id": { + "name": "checkout_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_run_id": { + "name": "execution_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_agent_name_key": { + "name": "execution_agent_name_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_locked_at": { + "name": "execution_locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_fingerprint": { + "name": "origin_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "request_depth": { + "name": "request_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_adapter_overrides": { + "name": "assignee_adapter_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_policy": { + "name": "execution_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_state": { + "name": "execution_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "monitor_next_check_at": { + "name": "monitor_next_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_wake_requested_at": { + "name": "monitor_wake_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_last_triggered_at": { + "name": "monitor_last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_attempt_count": { + "name": "monitor_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "monitor_notes": { + "name": "monitor_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "monitor_scheduled_by": { + "name": "monitor_scheduled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "hidden_at": { + "name": "hidden_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issues_company_status_idx": { + "name": "issues_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_status_idx": { + "name": "issues_company_assignee_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_user_status_idx": { + "name": "issues_company_assignee_user_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_origin_idx": { + "name": "issues_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_monitor_due_idx": { + "name": "issues_company_monitor_due_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "monitor_next_check_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_identifier_idx": { + "name": "issues_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_title_search_idx": { + "name": "issues_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_identifier_search_idx": { + "name": "issues_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_description_search_idx": { + "name": "issues_description_search_idx", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_open_routine_execution_uq": { + "name": "issues_open_routine_execution_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'routine_execution'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"execution_run_id\" is not null\n and \"issues\".\"status\" in ('backlog', 'todo', 'in_progress', 'in_review', 'blocked')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_liveness_recovery_incident_uq": { + "name": "issues_active_liveness_recovery_incident_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'harness_liveness_escalation'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_liveness_recovery_leaf_uq": { + "name": "issues_active_liveness_recovery_leaf_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'harness_liveness_escalation'\n and \"issues\".\"origin_fingerprint\" <> 'default'\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_stale_run_evaluation_uq": { + "name": "issues_active_stale_run_evaluation_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'stale_active_run_evaluation'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_productivity_review_uq": { + "name": "issues_active_productivity_review_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'issue_productivity_review'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_stranded_issue_recovery_uq": { + "name": "issues_active_stranded_issue_recovery_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'stranded_issue_recovery'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issues_company_id_companies_id_fk": { + "name": "issues_company_id_companies_id_fk", + "tableFrom": "issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_id_projects_id_fk": { + "name": "issues_project_id_projects_id_fk", + "tableFrom": "issues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.join_requests": { + "name": "join_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_type": { + "name": "request_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_approval'" + }, + "request_ip": { + "name": "request_ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requesting_user_id": { + "name": "requesting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_email_snapshot": { + "name": "request_email_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_defaults_payload": { + "name": "agent_defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "claim_secret_hash": { + "name": "claim_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_secret_expires_at": { + "name": "claim_secret_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_secret_consumed_at": { + "name": "claim_secret_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_agent_id": { + "name": "created_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_by_user_id": { + "name": "rejected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "join_requests_invite_unique_idx": { + "name": "join_requests_invite_unique_idx", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_company_status_type_created_idx": { + "name": "join_requests_company_status_type_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_pending_human_user_uq": { + "name": "join_requests_pending_human_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requesting_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"join_requests\".\"request_type\" = 'human' AND \"join_requests\".\"status\" = 'pending_approval' AND \"join_requests\".\"requesting_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_pending_human_email_uq": { + "name": "join_requests_pending_human_email_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"request_email_snapshot\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"join_requests\".\"request_type\" = 'human' AND \"join_requests\".\"status\" = 'pending_approval' AND \"join_requests\".\"request_email_snapshot\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "join_requests_invite_id_invites_id_fk": { + "name": "join_requests_invite_id_invites_id_fk", + "tableFrom": "join_requests", + "tableTo": "invites", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_company_id_companies_id_fk": { + "name": "join_requests_company_id_companies_id_fk", + "tableFrom": "join_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_created_agent_id_agents_id_fk": { + "name": "join_requests_created_agent_id_agents_id_fk", + "tableFrom": "join_requests", + "tableTo": "agents", + "columnsFrom": [ + "created_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labels": { + "name": "labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "labels_company_idx": { + "name": "labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "labels_company_name_idx": { + "name": "labels_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "labels_company_id_companies_id_fk": { + "name": "labels_company_id_companies_id_fk", + "tableFrom": "labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_company_settings": { + "name": "plugin_company_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_company_settings_company_idx": { + "name": "plugin_company_settings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_plugin_idx": { + "name": "plugin_company_settings_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_company_plugin_uq": { + "name": "plugin_company_settings_company_plugin_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_company_settings_company_id_companies_id_fk": { + "name": "plugin_company_settings_company_id_companies_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_company_settings_plugin_id_plugins_id_fk": { + "name": "plugin_company_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_config": { + "name": "plugin_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config_json": { + "name": "config_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_config_plugin_id_idx": { + "name": "plugin_config_plugin_id_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_database_namespaces": { + "name": "plugin_database_namespaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_name": { + "name": "namespace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_mode": { + "name": "namespace_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'schema'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_database_namespaces_plugin_idx": { + "name": "plugin_database_namespaces_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_database_namespaces_namespace_idx": { + "name": "plugin_database_namespaces_namespace_idx", + "columns": [ + { + "expression": "namespace_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_database_namespaces_status_idx": { + "name": "plugin_database_namespaces_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_database_namespaces_plugin_id_plugins_id_fk": { + "name": "plugin_database_namespaces_plugin_id_plugins_id_fk", + "tableFrom": "plugin_database_namespaces", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_entities": { + "name": "plugin_entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_entities_plugin_idx": { + "name": "plugin_entities_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_type_idx": { + "name": "plugin_entities_type_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_scope_idx": { + "name": "plugin_entities_scope_idx", + "columns": [ + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_external_idx": { + "name": "plugin_entities_external_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_entities_plugin_id_plugins_id_fk": { + "name": "plugin_entities_plugin_id_plugins_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_job_runs": { + "name": "plugin_job_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logs": { + "name": "logs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_job_runs_job_idx": { + "name": "plugin_job_runs_job_idx", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_plugin_idx": { + "name": "plugin_job_runs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_status_idx": { + "name": "plugin_job_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_job_runs_job_id_plugin_jobs_id_fk": { + "name": "plugin_job_runs_job_id_plugin_jobs_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugin_jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_plugin_id_plugins_id_fk": { + "name": "plugin_job_runs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_jobs": { + "name": "plugin_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_key": { + "name": "job_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_jobs_plugin_idx": { + "name": "plugin_jobs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_next_run_idx": { + "name": "plugin_jobs_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_unique_idx": { + "name": "plugin_jobs_unique_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_jobs_plugin_id_plugins_id_fk": { + "name": "plugin_jobs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_jobs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_logs": { + "name": "plugin_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_logs_plugin_time_idx": { + "name": "plugin_logs_plugin_time_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_level_idx": { + "name": "plugin_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_logs_plugin_id_plugins_id_fk": { + "name": "plugin_logs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_managed_resources": { + "name": "plugin_managed_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_kind": { + "name": "resource_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_key": { + "name": "resource_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "defaults_json": { + "name": "defaults_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_managed_resources_company_idx": { + "name": "plugin_managed_resources_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_plugin_idx": { + "name": "plugin_managed_resources_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_resource_idx": { + "name": "plugin_managed_resources_resource_idx", + "columns": [ + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_company_plugin_resource_uq": { + "name": "plugin_managed_resources_company_plugin_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_managed_resources_company_id_companies_id_fk": { + "name": "plugin_managed_resources_company_id_companies_id_fk", + "tableFrom": "plugin_managed_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_managed_resources_plugin_id_plugins_id_fk": { + "name": "plugin_managed_resources_plugin_id_plugins_id_fk", + "tableFrom": "plugin_managed_resources", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_migrations": { + "name": "plugin_migrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_name": { + "name": "namespace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_key": { + "name": "migration_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_version": { + "name": "plugin_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "plugin_migrations_plugin_key_idx": { + "name": "plugin_migrations_plugin_key_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "migration_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_migrations_plugin_idx": { + "name": "plugin_migrations_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_migrations_status_idx": { + "name": "plugin_migrations_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_state": { + "name": "plugin_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_json": { + "name": "value_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_state_plugin_scope_idx": { + "name": "plugin_state_plugin_scope_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_state_plugin_id_plugins_id_fk": { + "name": "plugin_state_plugin_id_plugins_id_fk", + "tableFrom": "plugin_state", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_state_unique_entry_idx": { + "name": "plugin_state_unique_entry_idx", + "nullsNotDistinct": true, + "columns": [ + "plugin_id", + "scope_kind", + "scope_id", + "namespace", + "state_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_webhook_deliveries": { + "name": "plugin_webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "webhook_key": { + "name": "webhook_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_webhook_deliveries_plugin_idx": { + "name": "plugin_webhook_deliveries_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_status_idx": { + "name": "plugin_webhook_deliveries_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_key_idx": { + "name": "plugin_webhook_deliveries_key_idx", + "columns": [ + { + "expression": "webhook_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_webhook_deliveries_plugin_id_plugins_id_fk": { + "name": "plugin_webhook_deliveries_plugin_id_plugins_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_version": { + "name": "api_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'installed'" + }, + "install_order": { + "name": "install_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "package_path": { + "name": "package_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugins_plugin_key_idx": { + "name": "plugins_plugin_key_idx", + "columns": [ + { + "expression": "plugin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugins_status_idx": { + "name": "plugins_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_permission_grants": { + "name": "principal_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_key": { + "name": "permission_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_permission_grants_unique_idx": { + "name": "principal_permission_grants_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "principal_permission_grants_company_permission_idx": { + "name": "principal_permission_grants_company_permission_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_permission_grants_company_id_companies_id_fk": { + "name": "principal_permission_grants_company_id_companies_id_fk", + "tableFrom": "principal_permission_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_goals": { + "name": "project_goals", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_goals_project_idx": { + "name": "project_goals_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_goal_idx": { + "name": "project_goals_goal_idx", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_company_idx": { + "name": "project_goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_goals_project_id_projects_id_fk": { + "name": "project_goals_project_id_projects_id_fk", + "tableFrom": "project_goals", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_goal_id_goals_id_fk": { + "name": "project_goals_goal_id_goals_id_fk", + "tableFrom": "project_goals", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_company_id_companies_id_fk": { + "name": "project_goals_company_id_companies_id_fk", + "tableFrom": "project_goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_goals_project_id_goal_id_pk": { + "name": "project_goals_project_id_goal_id_pk", + "columns": [ + "project_id", + "goal_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_memberships": { + "name": "project_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_memberships_company_user_idx": { + "name": "project_memberships_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_project_idx": { + "name": "project_memberships_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_company_user_project_uq": { + "name": "project_memberships_company_user_project_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_memberships_company_id_companies_id_fk": { + "name": "project_memberships_company_id_companies_id_fk", + "tableFrom": "project_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_memberships_project_id_projects_id_fk": { + "name": "project_memberships_project_id_projects_id_fk", + "tableFrom": "project_memberships", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_workspaces": { + "name": "project_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.research_findings": { + "name": "research_findings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_title": { + "name": "source_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_domain": { + "name": "source_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "research_finding_confidence", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'medium'" + }, + "reliability_score": { + "name": "reliability_score", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_duplicate": { + "name": "is_duplicate", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "duplicate_of_id": { + "name": "duplicate_of_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "research_findings_task_idx": { + "name": "research_findings_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "research_findings_session_idx": { + "name": "research_findings_session_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "research_findings_company_idx": { + "name": "research_findings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "research_findings_duplicate_idx": { + "name": "research_findings_duplicate_idx", + "columns": [ + { + "expression": "is_duplicate", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "duplicate_of_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "research_findings_category_idx": { + "name": "research_findings_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "research_findings_company_session_created_idx": { + "name": "research_findings_company_session_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "research_findings_task_id_research_tasks_id_fk": { + "name": "research_findings_task_id_research_tasks_id_fk", + "tableFrom": "research_findings", + "tableTo": "research_tasks", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "research_findings_session_id_research_sessions_id_fk": { + "name": "research_findings_session_id_research_sessions_id_fk", + "tableFrom": "research_findings", + "tableTo": "research_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "research_findings_company_id_companies_id_fk": { + "name": "research_findings_company_id_companies_id_fk", + "tableFrom": "research_findings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "research_findings_duplicate_of_id_research_findings_id_fk": { + "name": "research_findings_duplicate_of_id_research_findings_id_fk", + "tableFrom": "research_findings", + "tableTo": "research_findings", + "columnsFrom": [ + "duplicate_of_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.research_memory": { + "name": "research_memory", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_finding_id": { + "name": "source_finding_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "research_memory_company_key_idx": { + "name": "research_memory_company_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "research_memory_session_idx": { + "name": "research_memory_session_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "research_memory_company_id_companies_id_fk": { + "name": "research_memory_company_id_companies_id_fk", + "tableFrom": "research_memory", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "research_memory_session_id_research_sessions_id_fk": { + "name": "research_memory_session_id_research_sessions_id_fk", + "tableFrom": "research_memory", + "tableTo": "research_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "research_memory_source_finding_id_research_findings_id_fk": { + "name": "research_memory_source_finding_id_research_findings_id_fk", + "tableFrom": "research_memory", + "tableTo": "research_findings", + "columnsFrom": [ + "source_finding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.research_sessions": { + "name": "research_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "query": { + "name": "query", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "research_session_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'planning'" + }, + "plan": { + "name": "plan", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "report": { + "name": "report", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "original_report": { + "name": "original_report", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_edited": { + "name": "is_edited", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "progress_percent": { + "name": "progress_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "depth": { + "name": "depth", + "type": "research_depth", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "max_subtopics": { + "name": "max_subtopics", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "research_sessions_company_idx": { + "name": "research_sessions_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "research_sessions_status_idx": { + "name": "research_sessions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "research_sessions_created_idx": { + "name": "research_sessions_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "research_sessions_company_id_companies_id_fk": { + "name": "research_sessions_company_id_companies_id_fk", + "tableFrom": "research_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.research_sources": { + "name": "research_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reliability_score": { + "name": "reliability_score", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "access_count": { + "name": "access_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "research_sources_session_idx": { + "name": "research_sources_session_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "research_sources_url_idx": { + "name": "research_sources_url_idx", + "columns": [ + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "research_sources_domain_idx": { + "name": "research_sources_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "research_sources_session_id_research_sessions_id_fk": { + "name": "research_sources_session_id_research_sessions_id_fk", + "tableFrom": "research_sources", + "tableTo": "research_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "research_sources_company_id_companies_id_fk": { + "name": "research_sources_company_id_companies_id_fk", + "tableFrom": "research_sources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.research_tasks": { + "name": "research_tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "research_task_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "findings_summary": { + "name": "findings_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sources": { + "name": "sources", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "reliability_score": { + "name": "reliability_score", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sequence_order": { + "name": "sequence_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "research_tasks_session_idx": { + "name": "research_tasks_session_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "research_tasks_company_idx": { + "name": "research_tasks_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "research_tasks_status_idx": { + "name": "research_tasks_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "research_tasks_session_order_idx": { + "name": "research_tasks_session_order_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sequence_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "research_tasks_session_id_research_sessions_id_fk": { + "name": "research_tasks_session_id_research_sessions_id_fk", + "tableFrom": "research_tasks", + "tableTo": "research_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "research_tasks_company_id_companies_id_fk": { + "name": "research_tasks_company_id_companies_id_fk", + "tableFrom": "research_tasks", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_revisions": { + "name": "routine_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "restored_from_revision_id": { + "name": "restored_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_revisions_routine_revision_uq": { + "name": "routine_revisions_routine_revision_uq", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_revisions_company_routine_created_idx": { + "name": "routine_revisions_company_routine_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_revisions_company_id_companies_id_fk": { + "name": "routine_revisions_company_id_companies_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_revisions_routine_id_routines_id_fk": { + "name": "routine_revisions_routine_id_routines_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_revisions_restored_from_revision_id_routine_revisions_id_fk": { + "name": "routine_revisions_restored_from_revision_id_routine_revisions_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "routine_revisions", + "columnsFrom": [ + "restored_from_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_revisions_created_by_agent_id_agents_id_fk": { + "name": "routine_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "routine_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "routine_revision_id": { + "name": "routine_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_payload": { + "name": "trigger_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "dispatch_fingerprint": { + "name": "dispatch_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linked_issue_id": { + "name": "linked_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "coalesced_into_run_id": { + "name": "coalesced_into_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_runs_company_routine_idx": { + "name": "routine_runs_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_revision_idx": { + "name": "routine_runs_revision_idx", + "columns": [ + { + "expression": "routine_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idx": { + "name": "routine_runs_trigger_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_dispatch_fingerprint_idx": { + "name": "routine_runs_dispatch_fingerprint_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dispatch_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_linked_issue_idx": { + "name": "routine_runs_linked_issue_idx", + "columns": [ + { + "expression": "linked_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idempotency_idx": { + "name": "routine_runs_trigger_idempotency_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_company_id_companies_id_fk": { + "name": "routine_runs_company_id_companies_id_fk", + "tableFrom": "routine_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_trigger_id_routine_triggers_id_fk": { + "name": "routine_runs_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_routine_revision_id_routine_revisions_id_fk": { + "name": "routine_runs_routine_revision_id_routine_revisions_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_revisions", + "columnsFrom": [ + "routine_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_linked_issue_id_issues_id_fk": { + "name": "routine_runs_linked_issue_id_issues_id_fk", + "tableFrom": "routine_runs", + "tableTo": "issues", + "columnsFrom": [ + "linked_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_triggers": { + "name": "routine_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "signing_mode": { + "name": "signing_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replay_window_sec": { + "name": "replay_window_sec", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_triggers_company_routine_idx": { + "name": "routine_triggers_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_company_kind_idx": { + "name": "routine_triggers_company_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_next_run_idx": { + "name": "routine_triggers_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_idx": { + "name": "routine_triggers_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_uq": { + "name": "routine_triggers_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_triggers_company_id_companies_id_fk": { + "name": "routine_triggers_company_id_companies_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_routine_id_routines_id_fk": { + "name": "routine_triggers_routine_id_routines_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_secret_id_company_secrets_id_fk": { + "name": "routine_triggers_secret_id_company_secrets_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_created_by_agent_id_agents_id_fk": { + "name": "routine_triggers_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_updated_by_agent_id_agents_id_fk": { + "name": "routine_triggers_updated_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'coalesce_if_active'" + }, + "catch_up_policy": { + "name": "catch_up_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip_missed'" + }, + "variables": { + "name": "variables", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_enqueued_at": { + "name": "last_enqueued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routines_company_status_idx": { + "name": "routines_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_assignee_idx": { + "name": "routines_company_assignee_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_project_idx": { + "name": "routines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_company_id_companies_id_fk": { + "name": "routines_company_id_companies_id_fk", + "tableFrom": "routines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_project_id_projects_id_fk": { + "name": "routines_project_id_projects_id_fk", + "tableFrom": "routines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_goal_id_goals_id_fk": { + "name": "routines_goal_id_goals_id_fk", + "tableFrom": "routines", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_parent_issue_id_issues_id_fk": { + "name": "routines_parent_issue_id_issues_id_fk", + "tableFrom": "routines", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_assignee_agent_id_agents_id_fk": { + "name": "routines_assignee_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routines_created_by_agent_id_agents_id_fk": { + "name": "routines_created_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_updated_by_agent_id_agents_id_fk": { + "name": "routines_updated_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret_access_events": { + "name": "secret_access_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consumer_type": { + "name": "consumer_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "consumer_id": { + "name": "consumer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secret_access_events_company_created_idx": { + "name": "secret_access_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_secret_created_idx": { + "name": "secret_access_events_secret_created_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_consumer_idx": { + "name": "secret_access_events_consumer_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_run_idx": { + "name": "secret_access_events_run_idx", + "columns": [ + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secret_access_events_company_id_companies_id_fk": { + "name": "secret_access_events_company_id_companies_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "secret_access_events_secret_id_company_secrets_id_fk": { + "name": "secret_access_events_secret_id_company_secrets_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "secret_access_events_issue_id_issues_id_fk": { + "name": "secret_access_events_issue_id_issues_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "secret_access_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_plugin_id_plugins_id_fk": { + "name": "secret_access_events_plugin_id_plugins_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_sidebar_preferences": { + "name": "user_sidebar_preferences", + "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 + }, + "company_order": { + "name": "company_order", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_sidebar_preferences_user_uq": { + "name": "user_sidebar_preferences_user_uq", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_operations": { + "name": "workspace_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operations_company_run_started_idx": { + "name": "workspace_operations_company_run_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_started_idx": { + "name": "workspace_operations_company_workspace_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operations_company_id_companies_id_fk": { + "name": "workspace_operations_company_id_companies_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_operations_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_operations_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_by_run_id": { + "name": "started_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_policy": { + "name": "stop_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_runtime_services_company_workspace_status_idx": { + "name": "workspace_runtime_services_company_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_execution_workspace_status_idx": { + "name": "workspace_runtime_services_company_execution_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_project_status_idx": { + "name": "workspace_runtime_services_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_run_idx": { + "name": "workspace_runtime_services_run_idx", + "columns": [ + { + "expression": "started_by_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_updated_idx": { + "name": "workspace_runtime_services_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_runtime_services_company_id_companies_id_fk": { + "name": "workspace_runtime_services_company_id_companies_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_id_projects_id_fk": { + "name": "workspace_runtime_services_project_id_projects_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk": { + "name": "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_issue_id_issues_id_fk": { + "name": "workspace_runtime_services_issue_id_issues_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_owner_agent_id_agents_id_fk": { + "name": "workspace_runtime_services_owner_agent_id_agents_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk": { + "name": "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "started_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.research_depth": { + "name": "research_depth", + "schema": "public", + "values": [ + "shallow", + "medium", + "deep" + ] + }, + "public.research_finding_confidence": { + "name": "research_finding_confidence", + "schema": "public", + "values": [ + "high", + "medium", + "low" + ] + }, + "public.research_session_status": { + "name": "research_session_status", + "schema": "public", + "values": [ + "planning", + "running", + "cancelling", + "paused", + "completed", + "failed", + "cancelled" + ] + }, + "public.research_task_status": { + "name": "research_task_status", + "schema": "public", + "values": [ + "pending", + "running", + "completed", + "failed", + "skipped" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index a4509f326b7..2d435fbb34c 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -638,6 +638,55 @@ "when": 1779573019125, "tag": "0090_resource_memberships", "breakpoints": true + }, + { + "idx": 91, + "version": "7", + "when": 1751366400000, + "tag": "0091_research_module", + "breakpoints": true + }, + { + "idx": 92, + "version": "7", + "when": 1751366400001, + "tag": "0092_research_cancelling", + "breakpoints": true + }, + { + "idx": 93, + "version": "7", + "when": 1751366400002, + "tag": "0093_research_report_editing", + "breakpoints": true + }, + { + "idx": 94, + "version": "7", + "when": 1781247158397, + "tag": "0094_smart_orphan", + "breakpoints": true + }, + { + "idx": 95, + "version": "95", + "when": 1780559427000, + "tag": "0095_research_cancelled_status", + "breakpoints": true + }, + { + "idx": 96, + "version": "96", + "when": 1781719000000, + "tag": "0096_research_findings_fix", + "breakpoints": true + }, + { + "idx": 97, + "version": "97", + "when": 1781719500000, + "tag": "0097_research_depth_enum_fix", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts index 2afb040696d..48f1df04667 100644 --- a/packages/db/src/schema/index.ts +++ b/packages/db/src/schema/index.ts @@ -79,3 +79,8 @@ export { pluginDatabaseNamespaces, pluginMigrations } from "./plugin_database.js export { pluginJobs, pluginJobRuns } from "./plugin_jobs.js"; export { pluginWebhookDeliveries } from "./plugin_webhooks.js"; export { pluginLogs } from "./plugin_logs.js"; +export { researchSessions, researchSessionStatusEnum, researchDepthEnum } from "./research_sessions.js"; +export { researchTasks, researchTaskStatusEnum } from "./research_tasks.js"; +export { researchFindings, researchFindingConfidenceEnum } from "./research_findings.js"; +export { researchSources } from "./research_sources.js"; +export { researchMemory } from "./research_memory.js"; diff --git a/packages/db/src/schema/research_findings.ts b/packages/db/src/schema/research_findings.ts new file mode 100644 index 00000000000..43b38ed8ee1 --- /dev/null +++ b/packages/db/src/schema/research_findings.ts @@ -0,0 +1,45 @@ +import { pgTable, uuid, text, integer, timestamp, boolean, jsonb, pgEnum, index } from "drizzle-orm/pg-core"; +import { companies } from "./companies.js"; +import { researchSessions } from "./research_sessions.js"; +import { researchTasks } from "./research_tasks.js"; + +export const researchFindingConfidenceEnum = pgEnum("research_finding_confidence", [ + "high", + "medium", + "low", +]); + +export const researchFindings = pgTable( + "research_findings", + { + id: uuid("id").primaryKey().defaultRandom(), + taskId: uuid("task_id") + .notNull() + .references(() => researchTasks.id, { onDelete: "cascade" }), + sessionId: uuid("session_id") + .notNull() + .references(() => researchSessions.id, { onDelete: "cascade" }), + companyId: uuid("company_id") + .notNull() + .references(() => companies.id, { onDelete: "cascade" }), + content: text("content").notNull(), + sourceUrl: text("source_url"), + sourceTitle: text("source_title"), + sourceDomain: text("source_domain"), + confidence: researchFindingConfidenceEnum("confidence").default("medium"), + reliabilityScore: integer("reliability_score"), + category: text("category"), + isDuplicate: boolean("is_duplicate").default(false), + duplicateOfId: uuid("duplicate_of_id").references((): any => researchFindings.id), + metadata: jsonb("metadata").default({}), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + taskIdx: index("research_findings_task_idx").on(table.taskId), + sessionIdx: index("research_findings_session_idx").on(table.sessionId), + companyIdx: index("research_findings_company_idx").on(table.companyId), + duplicateIdx: index("research_findings_duplicate_idx").on(table.isDuplicate, table.duplicateOfId), + categoryIdx: index("research_findings_category_idx").on(table.category), + companySessionCreatedIdx: index("research_findings_company_session_created_idx").on(table.companyId, table.sessionId, table.createdAt), + }), +); diff --git a/packages/db/src/schema/research_memory.ts b/packages/db/src/schema/research_memory.ts new file mode 100644 index 00000000000..c6d942fe691 --- /dev/null +++ b/packages/db/src/schema/research_memory.ts @@ -0,0 +1,24 @@ +import { pgTable, uuid, text, timestamp, jsonb, uniqueIndex, index } from "drizzle-orm/pg-core"; +import { companies } from "./companies.js"; +import { researchSessions } from "./research_sessions.js"; +import { researchFindings } from "./research_findings.js"; + +export const researchMemory = pgTable( + "research_memory", + { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id") + .notNull() + .references(() => companies.id, { onDelete: "cascade" }), + key: text("key").notNull(), + value: jsonb("value").notNull(), + sessionId: uuid("session_id").references(() => researchSessions.id, { onDelete: "set null" }), + sourceFindingId: uuid("source_finding_id").references(() => researchFindings.id, { onDelete: "set null" }), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + companyKeyIdx: uniqueIndex("research_memory_company_key_idx").on(table.companyId, table.key), + sessionIdx: index("research_memory_session_idx").on(table.sessionId), + }), +); diff --git a/packages/db/src/schema/research_sessions.ts b/packages/db/src/schema/research_sessions.ts new file mode 100644 index 00000000000..d5f9a99dfb3 --- /dev/null +++ b/packages/db/src/schema/research_sessions.ts @@ -0,0 +1,48 @@ +import { pgTable, uuid, text, timestamp, jsonb, integer, pgEnum, index, boolean } from "drizzle-orm/pg-core"; +import { companies } from "./companies.js"; + +export const researchSessionStatusEnum = pgEnum("research_session_status", [ + "planning", + "running", + "cancelling", + "paused", + "completed", + "failed", + "cancelled", +]); + +export const researchDepthEnum = pgEnum("research_depth", [ + "shallow", + "medium", + "deep", +]); + +export const researchSessions = pgTable( + "research_sessions", + { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id") + .notNull() + .references(() => companies.id, { onDelete: "cascade" }), + title: text("title").notNull(), + query: text("query").notNull(), + status: researchSessionStatusEnum("status").notNull().default("planning"), + plan: jsonb("plan"), + report: text("report"), + originalReport: text("original_report"), + isEdited: boolean("is_edited").notNull().default(false), + progressPercent: integer("progress_percent").notNull().default(0), + depth: researchDepthEnum("depth").notNull().default("medium"), + maxSubtopics: integer("max_subtopics").notNull().default(5), + createdBy: text("created_by").notNull(), + startedAt: timestamp("started_at", { withTimezone: true }), + completedAt: timestamp("completed_at", { withTimezone: true }), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + companyIdx: index("research_sessions_company_idx").on(table.companyId), + statusIdx: index("research_sessions_status_idx").on(table.status), + createdIdx: index("research_sessions_created_idx").on(table.companyId, table.createdAt), + }), +); diff --git a/packages/db/src/schema/research_sources.ts b/packages/db/src/schema/research_sources.ts new file mode 100644 index 00000000000..973e21023d5 --- /dev/null +++ b/packages/db/src/schema/research_sources.ts @@ -0,0 +1,28 @@ +import { pgTable, uuid, text, integer, timestamp, index } from "drizzle-orm/pg-core"; +import { companies } from "./companies.js"; +import { researchSessions } from "./research_sessions.js"; + +export const researchSources = pgTable( + "research_sources", + { + id: uuid("id").primaryKey().defaultRandom(), + sessionId: uuid("session_id") + .notNull() + .references(() => researchSessions.id, { onDelete: "cascade" }), + companyId: uuid("company_id") + .notNull() + .references(() => companies.id, { onDelete: "cascade" }), + url: text("url").notNull(), + title: text("title"), + domain: text("domain"), + reliabilityScore: integer("reliability_score"), + accessCount: integer("access_count").notNull().default(1), + lastAccessedAt: timestamp("last_accessed_at", { withTimezone: true }), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + sessionIdx: index("research_sources_session_idx").on(table.sessionId), + urlIdx: index("research_sources_url_idx").on(table.url), + domainIdx: index("research_sources_domain_idx").on(table.domain), + }), +); diff --git a/packages/db/src/schema/research_tasks.ts b/packages/db/src/schema/research_tasks.ts new file mode 100644 index 00000000000..53797e46fdc --- /dev/null +++ b/packages/db/src/schema/research_tasks.ts @@ -0,0 +1,42 @@ +import { pgTable, uuid, text, integer, timestamp, jsonb, pgEnum, index } from "drizzle-orm/pg-core"; +import { companies } from "./companies.js"; +import { researchSessions } from "./research_sessions.js"; + +export const researchTaskStatusEnum = pgEnum("research_task_status", [ + "pending", + "running", + "completed", + "failed", + "skipped", +]); + +export const researchTasks = pgTable( + "research_tasks", + { + id: uuid("id").primaryKey().defaultRandom(), + sessionId: uuid("session_id") + .notNull() + .references(() => researchSessions.id, { onDelete: "cascade" }), + companyId: uuid("company_id") + .notNull() + .references(() => companies.id, { onDelete: "cascade" }), + title: text("title").notNull(), + status: researchTaskStatusEnum("status").notNull().default("pending"), + findingsSummary: text("findings_summary"), + sources: jsonb("sources") + .$type<{ url: string; title: string; snippet?: string }[]>() + .default([]), + reliabilityScore: integer("reliability_score"), + startedAt: timestamp("started_at", { withTimezone: true }), + completedAt: timestamp("completed_at", { withTimezone: true }), + sequenceOrder: integer("sequence_order").notNull().default(0), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + sessionIdx: index("research_tasks_session_idx").on(table.sessionId), + companyIdx: index("research_tasks_company_idx").on(table.companyId), + statusIdx: index("research_tasks_status_idx").on(table.status), + sessionOrderIdx: index("research_tasks_session_order_idx").on(table.sessionId, table.sequenceOrder), + }), +); diff --git a/packages/db/verify-restore.ts b/packages/db/verify-restore.ts new file mode 100644 index 00000000000..b0de8af7ff2 --- /dev/null +++ b/packages/db/verify-restore.ts @@ -0,0 +1,22 @@ +import postgres from "postgres"; + +const sql = postgres({ host: "127.0.0.1", port: 54329, database: "postgres", username: "postgres", connect_timeout: 5 }); + +async function main() { + const companies = await sql`SELECT id, name, issue_prefix, status FROM companies`; + const agents = await sql`SELECT id, name, role, title, status FROM agents ORDER BY name`; + + console.log("Company count:", companies.length); + console.log("Agent count:", agents.length); + console.log("\n=== COMPANIES ==="); + companies.forEach((r: any) => console.log(JSON.stringify(r))); + console.log("\n=== AGENTS ==="); + agents.forEach((r: any) => console.log(JSON.stringify(r))); + + await sql.end(); +} + +main().catch((err) => { + console.error("Error:", err.message); + process.exit(1); +}); diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts index c102fa73f2a..59e04e29ff0 100644 --- a/packages/shared/src/constants.ts +++ b/packages/shared/src/constants.ts @@ -603,6 +603,11 @@ export const LIVE_EVENT_TYPES = [ "plugin.ui.updated", "plugin.worker.crashed", "plugin.worker.restarted", + "research.session.status", + "research.task.updated", + "research.finding.created", + "research.source.processing", + "research.finding.progress", ] as const; export type LiveEventType = (typeof LIVE_EVENT_TYPES)[number]; @@ -1101,3 +1106,48 @@ export const PLUGIN_BRIDGE_ERROR_CODES = [ "UNKNOWN", ] as const; export type PluginBridgeErrorCode = (typeof PLUGIN_BRIDGE_ERROR_CODES)[number]; + +// ───────────────────────────────────────────────────────────────────────────── +// Research Agent Constants +// ───────────────────────────────────────────────────────────────────────────── + +export const RESEARCH_SESSION_STATUSES = [ + "planning", + "running", + "cancelling", + "paused", + "completed", + "failed", + "cancelled", +] as const; +export type ResearchSessionStatus = (typeof RESEARCH_SESSION_STATUSES)[number]; + +export const RESEARCH_TASK_STATUSES = [ + "pending", + "running", + "completed", + "failed", + "skipped", +] as const; +export type ResearchTaskStatus = (typeof RESEARCH_TASK_STATUSES)[number]; + +export const RESEARCH_FINDING_CONFIDENCES = [ + "high", + "medium", + "low", +] as const; +export type ResearchFindingConfidence = (typeof RESEARCH_FINDING_CONFIDENCES)[number]; + +export const RESEARCH_DEPTHS = [ + "shallow", + "medium", + "deep", +] as const; +export type ResearchDepth = (typeof RESEARCH_DEPTHS)[number]; + +export const DEFAULT_RESEARCH_DEPTH: ResearchDepth = "medium"; +export const DEFAULT_RESEARCH_MAX_SUBTOPICS = 5; +export const MIN_RESEARCH_MAX_SUBTOPICS = 1; +export const MAX_RESEARCH_MAX_SUBTOPICS = 20; +export const MAX_RESEARCH_QUERY_LENGTH = 2000; +export const MAX_RESEARCH_TITLE_LENGTH = 200; diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 1fd35e940b7..3c2eb5f1b8f 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -641,6 +641,27 @@ export type { PluginWebhookDeliveryRecord, QuotaWindow, ProviderQuotaResult, + ResearchSession, + ResearchSessionListItem, + ResearchSessionDetail, + ResearchTask, + ResearchTaskListItem, + ResearchFinding, + ResearchFindingListItem, + ResearchSource, + ResearchSourceSnippet, + ResearchMemory, + ResearchReport, + ResearchPlan, + ResearchSubtopic, + CreateResearchSessionRequest, + UpdateResearchSessionRequest, + CreateResearchTaskRequest, + UpdateResearchTaskRequest, + CreateResearchFindingRequest, + MarkDuplicateRequest, + CreateResearchMemoryRequest, + ResearchDashboardSummary, } from "./types/index.js"; export { COMPANY_SEARCH_SCOPES } from "./types/index.js"; export { @@ -1077,6 +1098,42 @@ export { type ListPluginState, } from "./validators/index.js"; +export { + RESEARCH_SESSION_STATUSES, + RESEARCH_TASK_STATUSES, + RESEARCH_FINDING_CONFIDENCES, + RESEARCH_DEPTHS, + DEFAULT_RESEARCH_DEPTH, + DEFAULT_RESEARCH_MAX_SUBTOPICS, + MIN_RESEARCH_MAX_SUBTOPICS, + MAX_RESEARCH_MAX_SUBTOPICS, + MAX_RESEARCH_QUERY_LENGTH, + MAX_RESEARCH_TITLE_LENGTH, + type ResearchSessionStatus, + type ResearchTaskStatus, + type ResearchFindingConfidence, + type ResearchDepth, +} from "./constants.js"; + +export { + createResearchSessionSchema, + updateResearchSessionSchema, + generateSubtopicsSchema, + createResearchTaskSchema, + updateResearchTaskSchema, + createResearchFindingSchema, + markDuplicateSchema, + createResearchMemorySchema, + type CreateResearchSession, + type GenerateSubtopicsRequest, + type UpdateResearchSession, + type CreateResearchTask, + type UpdateResearchTask, + type CreateResearchFinding, + type MarkDuplicate, + type CreateResearchMemory, +} from "./validators/research.js"; + export { API_PREFIX, API } from "./api.js"; export { normalizeAgentUrlKey, deriveAgentUrlKey, isUuidLike } from "./agent-url-key.js"; export { deriveProjectUrlKey, normalizeProjectUrlKey, hasNonAsciiContent } from "./project-url-key.js"; diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts index a5a69be149b..7ba67bfd618 100644 --- a/packages/shared/src/types/index.ts +++ b/packages/shared/src/types/index.ts @@ -432,3 +432,31 @@ export type { PluginDatabaseNamespaceMode, PluginDatabaseNamespaceStatus, } from "./plugin.js"; + +export type { + ResearchSession, + ResearchSessionListItem, + ResearchSessionDetail, + ResearchTask, + ResearchTaskListItem, + ResearchFinding, + ResearchFindingListItem, + ResearchSource, + ResearchSourceSnippet, + ResearchMemory, + ResearchReport, + ResearchPlan, + ResearchSubtopic, + CreateResearchSessionRequest, + UpdateResearchSessionRequest, + CreateResearchTaskRequest, + UpdateResearchTaskRequest, + CreateResearchFindingRequest, + MarkDuplicateRequest, + CreateResearchMemoryRequest, + ResearchDashboardSummary, + ResearchSessionStatus, + ResearchTaskStatus, + ResearchFindingConfidence, + ResearchDepth, +} from "./research.js"; diff --git a/packages/shared/src/types/research.ts b/packages/shared/src/types/research.ts new file mode 100644 index 00000000000..e108c7b87b2 --- /dev/null +++ b/packages/shared/src/types/research.ts @@ -0,0 +1,260 @@ +// ───────────────────────────────────────────────────────────────────────────── +// Research Domain Types +// ───────────────────────────────────────────────────────────────────────────── + +// Enums (mirrors DB enums) +export type ResearchSessionStatus = + | "planning" + | "running" + | "cancelling" + | "paused" + | "completed" + | "failed"; + +export type ResearchTaskStatus = + | "pending" + | "running" + | "completed" + | "failed" + | "skipped"; + +export type ResearchFindingConfidence = "high" | "medium" | "low"; + +export type ResearchDepth = "shallow" | "medium" | "deep"; + +// ───────────────────────────────────────────────────────────────────────────── +// Research Session +// ───────────────────────────────────────────────────────────────────────────── + +export interface ResearchSession { + id: string; + companyId: string; + title: string; + query: string; + status: ResearchSessionStatus; + plan: ResearchPlan | null; + report: string | null; + originalReport: string | null; + isEdited: boolean; + progressPercent: number; + depth: ResearchDepth; + maxSubtopics: number; + createdBy: string; + startedAt: string | null; + completedAt: string | null; + createdAt: string; + updatedAt: string; +} + +export interface ResearchSessionListItem { + id: string; + title: string; + query: string; + status: ResearchSessionStatus; + progressPercent: number; + depth: ResearchDepth; + createdAt: string; + updatedAt: string; +} + +export interface ResearchSessionDetail extends ResearchSession { + tasks: ResearchTaskListItem[]; + findings: ResearchFinding[]; + sources: ResearchSource[]; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Research Plan +// ───────────────────────────────────────────────────────────────────────────── + +export interface ResearchPlan { + subtopics: ResearchSubtopic[]; + strategy: string; +} + +export interface ResearchSubtopic { + id: string; + title: string; + description: string; + priority: number; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Research Task +// ───────────────────────────────────────────────────────────────────────────── + +export interface ResearchTask { + id: string; + sessionId: string; + companyId: string; + title: string; + status: ResearchTaskStatus; + findingsSummary: string | null; + sources: ResearchSourceSnippet[]; + reliabilityScore: number | null; + startedAt: string | null; + completedAt: string | null; + sequenceOrder: number; + createdAt: string; + updatedAt: string; +} + +export interface ResearchTaskListItem { + id: string; + title: string; + status: ResearchTaskStatus; + reliabilityScore: number | null; + sequenceOrder: number; + createdAt: string; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Research Finding +// ───────────────────────────────────────────────────────────────────────────── + +export interface ResearchFinding { + id: string; + taskId: string; + sessionId: string; + companyId: string; + content: string; + sourceUrl: string | null; + sourceTitle: string | null; + sourceDomain: string | null; + confidence: ResearchFindingConfidence; + reliabilityScore: number | null; + category: string | null; + isDuplicate: boolean; + duplicateOfId: string | null; + metadata: Record; + createdAt: string; +} + +export interface ResearchFindingListItem { + id: string; + content: string; + sourceUrl: string | null; + sourceTitle: string | null; + confidence: ResearchFindingConfidence; + category: string | null; + isDuplicate: boolean; + createdAt: string; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Research Source +// ───────────────────────────────────────────────────────────────────────────── + +export interface ResearchSource { + id: string; + sessionId: string; + companyId: string; + url: string; + title: string | null; + domain: string | null; + reliabilityScore: number | null; + accessCount: number; + lastAccessedAt: string | null; + createdAt: string; +} + +export interface ResearchSourceSnippet { + url: string; + title: string; + snippet?: string; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Research Memory +// ───────────────────────────────────────────────────────────────────────────── + +export interface ResearchMemory { + id: string; + companyId: string; + key: string; + value: unknown; + sessionId: string | null; + sourceFindingId: string | null; + createdAt: string; + updatedAt: string; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Research Report +// ───────────────────────────────────────────────────────────────────────────── + +export interface ResearchReport { + sessionId: string; + title: string; + query: string; + markdown: string; + generatedAt: string; + findingsCount: number; + sourcesCount: number; +} + +// ───────────────────────────────────────────────────────────────────────────── +// API Request/Response Types +// ───────────────────────────────────────────────────────────────────────────── + +export interface CreateResearchSessionRequest { + title: string; + query: string; + depth?: ResearchDepth; + maxSubtopics?: number; + plan?: ResearchPlan; +} + +export interface UpdateResearchSessionRequest { + title?: string; + query?: string; + status?: ResearchSessionStatus; + depth?: ResearchDepth; + maxSubtopics?: number; + report?: string; +} + +export interface CreateResearchTaskRequest { + title: string; + sequenceOrder?: number; +} + +export interface UpdateResearchTaskRequest { + title?: string; + status?: ResearchTaskStatus; + findingsSummary?: string; + sources?: ResearchSourceSnippet[]; + reliabilityScore?: number; +} + +export interface CreateResearchFindingRequest { + taskId: string; + content: string; + sourceUrl?: string; + sourceTitle?: string; + sourceDomain?: string; + confidence?: ResearchFindingConfidence; + reliabilityScore?: number; + category?: string; + metadata?: Record; +} + +export interface MarkDuplicateRequest { + duplicateOfId: string; +} + +export interface CreateResearchMemoryRequest { + key: string; + value: unknown; + sessionId?: string; + sourceFindingId?: string; +} + +export interface ResearchDashboardSummary { + totalSessions: number; + activeSessions: number; + completedSessions: number; + totalFindings: number; + totalSources: number; + recentSessions: ResearchSessionListItem[]; +} diff --git a/packages/shared/src/validators/index.ts b/packages/shared/src/validators/index.ts index aba97fdf42d..bc43dfe26b4 100644 --- a/packages/shared/src/validators/index.ts +++ b/packages/shared/src/validators/index.ts @@ -444,3 +444,27 @@ export { type SetPluginState, type ListPluginState, } from "./plugin.js"; + +export { + researchSessionStatusSchema, + researchTaskStatusSchema, + researchFindingConfidenceSchema, + researchDepthSchema, + researchSourceSnippetSchema, + researchSubtopicSchema, + researchPlanSchema, + createResearchSessionSchema, + updateResearchSessionSchema, + createResearchTaskSchema, + updateResearchTaskSchema, + createResearchFindingSchema, + markDuplicateSchema, + createResearchMemorySchema, + type CreateResearchSession, + type UpdateResearchSession, + type CreateResearchTask, + type UpdateResearchTask, + type CreateResearchFinding, + type MarkDuplicate, + type CreateResearchMemory, +} from "./research.js"; diff --git a/packages/shared/src/validators/research.ts b/packages/shared/src/validators/research.ts new file mode 100644 index 00000000000..28e6762394d --- /dev/null +++ b/packages/shared/src/validators/research.ts @@ -0,0 +1,136 @@ +import { z } from "zod"; + +// ───────────────────────────────────────────────────────────────────────────── +// Research Enums +// ───────────────────────────────────────────────────────────────────────────── + +export const researchSessionStatusSchema = z.enum([ + "planning", + "running", + "cancelling", + "paused", + "completed", + "failed", +]); + +export const researchTaskStatusSchema = z.enum([ + "pending", + "running", + "completed", + "failed", + "skipped", +]); + +export const researchFindingConfidenceSchema = z.enum([ + "high", + "medium", + "low", +]); + +export const researchDepthSchema = z.enum([ + "shallow", + "medium", + "deep", +]); + +// ───────────────────────────────────────────────────────────────────────────── +// Research Source Snippet +// ───────────────────────────────────────────────────────────────────────────── + +export const researchSourceSnippetSchema = z.object({ + url: z.string().url(), + title: z.string(), + snippet: z.string().optional(), +}); + +// ───────────────────────────────────────────────────────────────────────────── +// Research Plan +// ───────────────────────────────────────────────────────────────────────────── + +export const researchSubtopicSchema = z.object({ + id: z.string(), + title: z.string(), + description: z.string(), + priority: z.number().int(), +}); + +export const researchPlanSchema = z.object({ + subtopics: z.array(researchSubtopicSchema), + strategy: z.string(), +}); + +// ───────────────────────────────────────────────────────────────────────────── +// Create / Update Requests +// ───────────────────────────────────────────────────────────────────────────── + +export const createResearchSessionSchema = z.object({ + title: z.string().min(1).max(200), + query: z.string().min(1).max(2000), + depth: researchDepthSchema.optional(), + maxSubtopics: z.number().int().min(1).max(20).optional(), + plan: researchPlanSchema.optional(), +}); + +export const generateSubtopicsSchema = z.object({ + query: z.string().min(1).max(2000), + depth: researchDepthSchema.optional(), + maxSubtopics: z.number().int().min(1).max(20).optional(), +}); + +export const updateResearchSessionSchema = z.object({ + title: z.string().min(1).max(200).optional(), + query: z.string().min(1).max(2000).optional(), + status: researchSessionStatusSchema.optional(), + depth: researchDepthSchema.optional(), + maxSubtopics: z.number().int().min(1).max(20).optional(), + report: z.string().optional(), +}); + +export const createResearchTaskSchema = z.object({ + title: z.string().min(1).max(200), + sequenceOrder: z.number().int().optional(), +}); + +export const updateResearchTaskSchema = z.object({ + title: z.string().min(1).max(200).optional(), + status: researchTaskStatusSchema.optional(), + findingsSummary: z.string().optional(), + sources: z.array(researchSourceSnippetSchema).optional(), + reliabilityScore: z.number().int().min(0).max(100).optional(), +}); + +export const createResearchFindingSchema = z.object({ + taskId: z.string().uuid(), + content: z.string().min(1).max(10000), + sourceUrl: z.string().url().optional(), + sourceTitle: z.string().optional(), + sourceDomain: z.string().optional(), + confidence: researchFindingConfidenceSchema.optional(), + reliabilityScore: z.number().int().min(0).max(100).optional(), + category: z.string().optional(), + metadata: z.record(z.unknown()).optional(), +}); + +export const markDuplicateSchema = z.object({ + duplicateOfId: z.string().uuid(), +}); + +export const createResearchMemorySchema = z.object({ + key: z.string().min(1).max(200), + value: z.unknown(), + sessionId: z.string().uuid().optional(), + sourceFindingId: z.string().uuid().optional(), +}); + +// ───────────────────────────────────────────────────────────────────────────── +// Type Exports +// ───────────────────────────────────────────────────────────────────────────── + +export type CreateResearchSession = z.infer; +export type GenerateSubtopicsRequest = z.infer; +export type UpdateResearchSession = z.infer; +export type CreateResearchTask = z.infer; +export type UpdateResearchTask = z.infer; +export type CreateResearchFinding = z.infer; +export type MarkDuplicate = z.infer; +export type CreateResearchMemory = z.infer; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 27725ab082e..bbac32572a7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -777,7 +777,7 @@ importers: dependencies: '@assistant-ui/react': specifier: 0.12.23 - version: 0.12.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) + version: 0.12.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(immer@11.1.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) '@dnd-kit/core': specifier: ^6.3.1 version: 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -880,6 +880,9 @@ importers: react-router-dom: specifier: ^7.1.5 version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + recharts: + specifier: ^3.8.1 + version: 3.8.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1) remark-gfm: specifier: ^4.0.1 version: 4.0.1 @@ -3259,6 +3262,17 @@ packages: peerDependencies: react: '>=16.8' + '@reduxjs/toolkit@2.12.0': + resolution: {integrity: sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -3661,6 +3675,9 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@statsig/client-core@3.31.0': resolution: {integrity: sha512-SuxQD6TmVszPG7FoMKwTk/uyBuVFk7XnxI3T/E0uyb7PL7GNjONtfsoh+NqBBVUJVse0CUeSFfgJPoZy1ZOslQ==} @@ -4082,6 +4099,9 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} @@ -4863,6 +4883,9 @@ packages: supports-color: optional: true + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} @@ -5132,6 +5155,9 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + es-toolkit@1.47.0: + resolution: {integrity: sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==} + es5-ext@0.10.64: resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} engines: {node: '>=0.10'} @@ -5209,6 +5235,9 @@ packages: event-emitter@0.3.5: resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + events-universal@1.0.1: resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} @@ -5476,6 +5505,12 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + immer@10.2.0: + resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} + + immer@11.1.8: + resolution: {integrity: sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -6469,6 +6504,18 @@ packages: '@types/react': '>=18' react: '>=18' + react-redux@9.3.0: + resolution: {integrity: sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -6546,10 +6593,26 @@ packages: resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} engines: {node: '>= 4'} + recharts@3.8.1: + resolution: {integrity: sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + regex-recursion@6.0.2: resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} @@ -6575,6 +6638,9 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -7132,6 +7198,9 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + victory-vendor@37.3.6: + resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -7474,7 +7543,7 @@ snapshots: '@asamuzakjp/nwsapi@2.3.9': {} - '@assistant-ui/core@0.1.13(@assistant-ui/store@0.2.6(@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(assistant-cloud@0.1.25)(react@19.2.4)(zustand@5.0.12(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)))': + '@assistant-ui/core@0.1.13(@assistant-ui/store@0.2.6(@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(assistant-cloud@0.1.25)(react@19.2.4)(zustand@5.0.12(@types/react@19.2.14)(immer@11.1.8)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)))': dependencies: '@assistant-ui/store': 0.2.6(@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4) '@assistant-ui/tap': 0.5.7(@types/react@19.2.14)(react@19.2.4) @@ -7484,11 +7553,11 @@ snapshots: '@types/react': 19.2.14 assistant-cloud: 0.1.25 react: 19.2.4 - zustand: 5.0.12(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) + zustand: 5.0.12(@types/react@19.2.14)(immer@11.1.8)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) - '@assistant-ui/react@0.12.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))': + '@assistant-ui/react@0.12.23(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(immer@11.1.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))': dependencies: - '@assistant-ui/core': 0.1.13(@assistant-ui/store@0.2.6(@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(assistant-cloud@0.1.25)(react@19.2.4)(zustand@5.0.12(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))) + '@assistant-ui/core': 0.1.13(@assistant-ui/store@0.2.6(@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(assistant-cloud@0.1.25)(react@19.2.4)(zustand@5.0.12(@types/react@19.2.14)(immer@11.1.8)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))) '@assistant-ui/store': 0.2.6(@assistant-ui/tap@0.5.7(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4) '@assistant-ui/tap': 0.5.7(@types/react@19.2.14)(react@19.2.4) '@radix-ui/primitive': 1.1.3 @@ -7505,7 +7574,7 @@ snapshots: react-dom: 19.2.4(react@19.2.4) react-textarea-autosize: 8.5.9(@types/react@19.2.14)(react@19.2.4) zod: 4.3.6 - zustand: 5.0.12(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) + zustand: 5.0.12(@types/react@19.2.14)(immer@11.1.8)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) optionalDependencies: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) @@ -10203,6 +10272,18 @@ snapshots: dependencies: react: 19.2.4 + '@reduxjs/toolkit@2.12.0(react-redux@9.3.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1))(react@19.2.4)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@standard-schema/utils': 0.3.0 + immer: 11.1.8 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.1.1 + optionalDependencies: + react: 19.2.4 + react-redux: 9.3.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1) + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/plugin-node-resolve@16.0.3(rollup@4.60.1)': @@ -10685,6 +10766,8 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@standard-schema/utils@0.3.0': {} + '@statsig/client-core@3.31.0': {} '@statsig/js-client@3.31.0': @@ -11164,6 +11247,8 @@ snapshots: '@types/unist@3.0.3': {} + '@types/use-sync-external-store@0.0.6': {} + '@types/ws@8.18.1': dependencies: '@types/node': 25.2.3 @@ -11943,6 +12028,8 @@ snapshots: dependencies: ms: 2.1.3 + decimal.js-light@2.5.1: {} + decimal.js@10.6.0: {} decode-named-character-reference@1.3.0: @@ -12118,6 +12205,8 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + es-toolkit@1.47.0: {} + es5-ext@0.10.64: dependencies: es6-iterator: 2.0.3 @@ -12265,6 +12354,8 @@ snapshots: d: 1.0.2 es5-ext: 0.10.64 + eventemitter3@5.0.4: {} + events-universal@1.0.1: dependencies: bare-events: 2.8.2 @@ -12606,6 +12697,10 @@ snapshots: ieee754@1.2.1: {} + immer@10.2.0: {} + + immer@11.1.8: {} + imurmurhash@0.1.4: optional: true @@ -13968,6 +14063,15 @@ snapshots: transitivePeerDependencies: - supports-color + react-redux@9.3.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 19.2.4 + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + redux: 5.0.1 + react-refresh@0.17.0: {} react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.4): @@ -14040,11 +14144,37 @@ snapshots: tiny-invariant: 1.3.3 tslib: 2.8.1 + recharts@3.8.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1): + dependencies: + '@reduxjs/toolkit': 2.12.0(react-redux@9.3.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1))(react@19.2.4) + clsx: 2.1.1 + decimal.js-light: 2.5.1 + es-toolkit: 1.47.0 + eventemitter3: 5.0.4 + immer: 10.2.0 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-is: 17.0.2 + react-redux: 9.3.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1) + reselect: 5.1.1 + tiny-invariant: 1.3.3 + use-sync-external-store: 1.6.0(react@19.2.4) + victory-vendor: 37.3.6 + transitivePeerDependencies: + - '@types/react' + - redux + redent@3.0.0: dependencies: indent-string: 4.0.0 strip-indent: 3.0.0 + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + regex-recursion@6.0.2: dependencies: regex-utilities: 2.3.0 @@ -14091,6 +14221,8 @@ snapshots: require-from-string@2.0.2: {} + reselect@5.1.1: {} + resolve-pkg-maps@1.0.0: {} resolve@1.22.11: @@ -14778,6 +14910,23 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 + victory-vendor@37.3.6: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + vite-node@3.2.4(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0): dependencies: cac: 6.7.14 @@ -15054,9 +15203,10 @@ snapshots: zod@4.3.6: {} - zustand@5.0.12(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)): + zustand@5.0.12(@types/react@19.2.14)(immer@11.1.8)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)): optionalDependencies: '@types/react': 19.2.14 + immer: 11.1.8 react: 19.2.4 use-sync-external-store: 1.6.0(react@19.2.4) diff --git a/restore_db.ts b/restore_db.ts new file mode 100644 index 00000000000..05435492419 --- /dev/null +++ b/restore_db.ts @@ -0,0 +1,28 @@ + +import fs from 'fs'; +import postgres from 'postgres'; + +const sql = fs.readFileSync('/tmp/restore_backup.sql', 'utf8'); + +const client = postgres({ + host: '127.0.0.1', + port: 54329, + username: 'postgres', + password: 'postgres', + database: 'postgres', + onnotice: () => {}, +}); + +async function restore() { + try { + await client.unsafe(sql); + console.log('Restore completed successfully'); + } catch (err) { + console.error('Restore error:', err.message); + process.exit(1); + } finally { + await client.end(); + } +} + +restore(); diff --git a/server/apply-migration.cjs b/server/apply-migration.cjs new file mode 100644 index 00000000000..85d74325516 --- /dev/null +++ b/server/apply-migration.cjs @@ -0,0 +1,20 @@ +const { drizzle } = require('drizzle-orm/postgres-js'); +const postgres = require('postgres'); + +const client = postgres({ host: '127.0.0.1', port: 54329, database: 'paperclip', user: 'paperclip', password: 'paperclip' }); +const db = drizzle(client); + +const sql = `ALTER TABLE "research_sessions" + ADD COLUMN IF NOT EXISTS "original_report" text, + ADD COLUMN IF NOT EXISTS "is_edited" boolean NOT NULL DEFAULT false, + ADD COLUMN IF NOT EXISTS "started_at" timestamp, + ADD COLUMN IF NOT EXISTS "completed_at" timestamp, + ADD COLUMN IF NOT EXISTS "updated_at" timestamp NOT NULL DEFAULT now()`; + +db.execute(sql).then(r => { + console.log('Migration applied:', JSON.stringify(r, null, 2)); + client.end(); +}).catch(e => { + console.error('Error:', e); + client.end(); +}); diff --git a/server/apply-migration.ts b/server/apply-migration.ts new file mode 100644 index 00000000000..a4eacfe0524 --- /dev/null +++ b/server/apply-migration.ts @@ -0,0 +1,20 @@ +import { drizzle } from 'drizzle-orm/postgres-js'; +import postgres from 'postgres/cjs/src/index.js'; + +const client = postgres({ host: '127.0.0.1', port: 54329, database: 'paperclip', user: 'paperclip', password: 'paperclip' }); +const db = drizzle(client); + +const sql = `ALTER TABLE "research_sessions" + ADD COLUMN IF NOT EXISTS "original_report" text, + ADD COLUMN IF NOT EXISTS "is_edited" boolean NOT NULL DEFAULT false, + ADD COLUMN IF NOT EXISTS "started_at" timestamp, + ADD COLUMN IF NOT EXISTS "completed_at" timestamp, + ADD COLUMN IF NOT EXISTS "updated_at" timestamp NOT NULL DEFAULT now()`; + +db.execute(sql).then(r => { + console.log('Migration applied:', JSON.stringify(r, null, 2)); + client.end(); +}).catch(e => { + console.error('Error:', e); + client.end(); +}); diff --git a/server/src/app.ts b/server/src/app.ts index f3a0867f4da..02b51fc83d0 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -25,6 +25,7 @@ import { secretRoutes } from "./routes/secrets.js"; import { costRoutes } from "./routes/costs.js"; import { activityRoutes } from "./routes/activity.js"; import { dashboardRoutes } from "./routes/dashboard.js"; +import { researchRoutes } from "./routes/research.js"; import { userProfileRoutes } from "./routes/user-profiles.js"; import { sidebarBadgeRoutes } from "./routes/sidebar-badges.js"; import { sidebarPreferenceRoutes } from "./routes/sidebar-preferences.js"; @@ -42,6 +43,7 @@ import { accessRoutes } from "./routes/access.js"; import { pluginRoutes } from "./routes/plugins.js"; import { adapterRoutes } from "./routes/adapters.js"; import { pluginUiStaticRoutes } from "./routes/plugin-ui-static.js"; +import { agentExecutorRoutes } from "./routes/agent-executor.js"; import { readBrandedStaticIndexHtml } from "./static-index-html.js"; import { applyUiBranding } from "./ui-branding.js"; import { logger } from "./middleware/logger.js"; @@ -149,6 +151,14 @@ export async function createApp( pluginWorkerManager?: PluginWorkerManager; betterAuthHandler?: express.RequestHandler; resolveSession?: (req: ExpressRequest) => Promise; + researchConfig?: { + researchEngineEnabled: boolean; + serperApiKey?: string; + researchLlmModel: string; + researchLlmApiKey?: string; + researchMaxSearchResults: number; + researchMaxFindingsPerTask: number; + }; }, ) { const app = express(); @@ -226,6 +236,7 @@ export async function createApp( api.use(costRoutes(db, { pluginWorkerManager: workerManager })); api.use(activityRoutes(db)); api.use(dashboardRoutes(db)); + api.use(researchRoutes(db, opts.researchConfig)); api.use(userProfileRoutes(db)); api.use(sidebarBadgeRoutes(db)); api.use(sidebarPreferenceRoutes(db)); @@ -314,6 +325,7 @@ export async function createApp( allowedHostnames: opts.allowedHostnames, }), ); + api.use("/internal/agent-executor", agentExecutorRoutes()); app.use("/api", api); app.use("/api", (_req, res) => { res.status(404).json({ error: "API route not found" }); diff --git a/server/src/config.ts b/server/src/config.ts index 90d6cbf6b14..13eb5f4cbed 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -87,6 +87,14 @@ export interface Config { heartbeatSchedulerIntervalMs: number; companyDeletionEnabled: boolean; telemetryEnabled: boolean; + researchEngineEnabled: boolean; + serperApiKey: string | undefined; + semanticScholarApiKey: string | undefined; + researchSearchProvider: "mock" | "serper" | "semantic-scholar"; + researchLlmModel: string; + researchLlmApiKey: string | undefined; + researchMaxSearchResults: number; + researchMaxFindingsPerTask: number; } function detectTailnetBindHost(): string | undefined { @@ -333,5 +341,13 @@ export function loadConfig(): Config { heartbeatSchedulerIntervalMs: Math.max(10000, Number(process.env.HEARTBEAT_SCHEDULER_INTERVAL_MS) || 30000), companyDeletionEnabled, telemetryEnabled: fileConfig?.telemetry?.enabled ?? true, + researchEngineEnabled: process.env.PAPERCLIP_RESEARCH_ENGINE_ENABLED !== "false", + serperApiKey: process.env.SERPER_API_KEY?.trim() || undefined, + semanticScholarApiKey: process.env.SEMANTIC_SCHOLAR_API_KEY?.trim() || undefined, + researchSearchProvider: (process.env.PAPERCLIP_RESEARCH_SEARCH_PROVIDER?.trim() as Config["researchSearchProvider"]) || "mock", + researchLlmModel: process.env.PAPERCLIP_RESEARCH_LLM_MODEL?.trim() || "gpt-4o-mini", + researchLlmApiKey: process.env.PAPERCLIP_RESEARCH_LLM_API_KEY?.trim() || process.env.OPENAI_API_KEY?.trim() || undefined, + researchMaxSearchResults: Math.max(1, Math.min(50, Number(process.env.PAPERCLIP_RESEARCH_MAX_SEARCH_RESULTS) || 10)), + researchMaxFindingsPerTask: Math.max(1, Math.min(100, Number(process.env.PAPERCLIP_RESEARCH_MAX_FINDINGS_PER_TASK) || 20)), }; } diff --git a/server/src/index.ts b/server/src/index.ts index 38caf44a518..e4090709e99 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -651,6 +651,14 @@ export async function startServer(): Promise { betterAuthHandler, resolveSession, pluginWorkerManager, + researchConfig: { + researchEngineEnabled: config.researchEngineEnabled, + serperApiKey: config.serperApiKey, + researchLlmModel: config.researchLlmModel, + researchLlmApiKey: config.researchLlmApiKey, + researchMaxSearchResults: config.researchMaxSearchResults, + researchMaxFindingsPerTask: config.researchMaxFindingsPerTask, + }, }); const server = createServer(app as unknown as Parameters[0]); diff --git a/server/src/routes/agent-executor.ts b/server/src/routes/agent-executor.ts new file mode 100644 index 00000000000..221b81a2f8c --- /dev/null +++ b/server/src/routes/agent-executor.ts @@ -0,0 +1,30 @@ +import { Router } from "express"; +import { logger } from "../middleware/logger.js"; + +export function agentExecutorRoutes(): Router { + const router = Router(); + + // Mock agent executor that returns success for any request + router.post("/", async (req, res) => { + const { agentId, runId, context } = req.body; + + logger.info({ agentId, runId, context }, "Mock agent executor invoked"); + + // Return a successful execution result + res.json({ + exitCode: 0, + signal: null, + timedOut: false, + summary: `Mock execution completed for agent ${agentId}`, + output: "Task completed successfully (mock executor)", + artifacts: [], + }); + }); + + // Health check endpoint + router.get("/health", (_req, res) => { + res.json({ status: "ok", mode: "mock" }); + }); + + return router; +} diff --git a/server/src/routes/research.ts b/server/src/routes/research.ts new file mode 100644 index 00000000000..42ecc3fd3c8 --- /dev/null +++ b/server/src/routes/research.ts @@ -0,0 +1,283 @@ +import { Router } from "express"; +import type { Db } from "@paperclipai/db"; +import { + createResearchSessionSchema, + updateResearchSessionSchema, + generateSubtopicsSchema, + createResearchTaskSchema, + updateResearchTaskSchema, + createResearchFindingSchema, + markDuplicateSchema, + createResearchMemorySchema, +} from "@paperclipai/shared"; +import { researchService } from "../services/research.js"; +import { assertCompanyAccess } from "./authz.js"; +import { badRequest } from "../errors.js"; +import type { Config } from "../config.js"; + +export function researchRoutes(db: Db, config?: Partial) { + const router = Router(); + const svc = researchService(db, config as Config | undefined); + + // ──────────────────────────────────────────────────────────────────────────── + // Sessions + // ──────────────────────────────────────────────────────────────────────────── + + router.get("/companies/:companyId/research/sessions", async (req, res) => { + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + + const status = req.query.status as string | undefined; + const limit = req.query.limit ? Number(req.query.limit) : undefined; + const offset = req.query.offset ? Number(req.query.offset) : undefined; + + const result = await svc.listSessions(companyId, { status, limit, offset }); + res.json(result); + }); + + router.post("/companies/:companyId/research/sessions", async (req, res) => { + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + + const parsed = createResearchSessionSchema.safeParse(req.body); + if (!parsed.success) { + throw badRequest("Invalid request body", parsed.error.format()); + } + + const actorId = req.actor.type === "board" ? req.actor.userId ?? "unknown" : req.actor.agentId ?? "agent"; + const session = await svc.createSession(companyId, actorId, parsed.data); + res.status(201).json(session); + }); + + router.get("/companies/:companyId/research/sessions/:sessionId", async (req, res) => { + const companyId = req.params.companyId as string; + const sessionId = req.params.sessionId as string; + assertCompanyAccess(req, companyId); + + const session = await svc.getSession(companyId, sessionId); + res.json(session); + }); + + router.patch("/companies/:companyId/research/sessions/:sessionId", async (req, res) => { + const companyId = req.params.companyId as string; + const sessionId = req.params.sessionId as string; + assertCompanyAccess(req, companyId); + + const parsed = updateResearchSessionSchema.safeParse(req.body); + if (!parsed.success) { + throw badRequest("Invalid request body", parsed.error.format()); + } + + const session = await svc.updateSession(companyId, sessionId, parsed.data); + res.json(session); + }); + + router.delete("/companies/:companyId/research/sessions/:sessionId", async (req, res) => { + const companyId = req.params.companyId as string; + const sessionId = req.params.sessionId as string; + assertCompanyAccess(req, companyId); + + await svc.deleteSession(companyId, sessionId); + res.status(204).send(); + }); + + // ──────────────────────────────────────────────────────────────────────────── + // Engine + // ──────────────────────────────────────────────────────────────────────────── + + router.post("/companies/:companyId/research/sessions/:sessionId/start", async (req, res) => { + const companyId = req.params.companyId as string; + const sessionId = req.params.sessionId as string; + assertCompanyAccess(req, companyId); + + const result = await svc.startSession(companyId, sessionId); + res.json(result); + }); + + router.post("/companies/:companyId/research/generate-subtopics", async (req, res) => { + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + + const parsed = generateSubtopicsSchema.safeParse(req.body); + if (!parsed.success) { + throw badRequest("Invalid request body", parsed.error.format()); + } + + const result = await svc.generateSubtopics(parsed.data); + res.json(result); + }); + + router.post("/companies/:companyId/research/sessions/:sessionId/cancel", async (req, res) => { + const companyId = req.params.companyId as string; + const sessionId = req.params.sessionId as string; + assertCompanyAccess(req, companyId); + + const result = await svc.cancelSession(companyId, sessionId); + res.json(result); + }); + + router.post("/companies/:companyId/research/sessions/:sessionId/resume", async (req, res) => { + const companyId = req.params.companyId as string; + const sessionId = req.params.sessionId as string; + assertCompanyAccess(req, companyId); + + const result = await svc.resumeSession(companyId, sessionId); + res.json(result); + }); + + router.post("/companies/:companyId/research/sessions/:sessionId/tasks/:taskId/retry", async (req, res) => { + const companyId = req.params.companyId as string; + const sessionId = req.params.sessionId as string; + const taskId = req.params.taskId as string; + assertCompanyAccess(req, companyId); + + const result = await svc.retryTask(companyId, sessionId, taskId); + res.json(result); + }); + + // ──────────────────────────────────────────────────────────────────────────── + // Tasks + // ──────────────────────────────────────────────────────────────────────────── + + router.get("/companies/:companyId/research/sessions/:sessionId/tasks", async (req, res) => { + const companyId = req.params.companyId as string; + const sessionId = req.params.sessionId as string; + assertCompanyAccess(req, companyId); + + const tasks = await svc.listTasks(companyId, sessionId); + res.json(tasks); + }); + + router.post("/companies/:companyId/research/sessions/:sessionId/tasks", async (req, res) => { + const companyId = req.params.companyId as string; + const sessionId = req.params.sessionId as string; + assertCompanyAccess(req, companyId); + + const parsed = createResearchTaskSchema.safeParse(req.body); + if (!parsed.success) { + throw badRequest("Invalid request body", parsed.error.format()); + } + + const task = await svc.createTask(companyId, sessionId, parsed.data); + res.status(201).json(task); + }); + + router.get("/companies/:companyId/research/tasks/:taskId", async (req, res) => { + const companyId = req.params.companyId as string; + const taskId = req.params.taskId as string; + assertCompanyAccess(req, companyId); + + const task = await svc.getTask(companyId, taskId); + res.json(task); + }); + + router.patch("/companies/:companyId/research/tasks/:taskId", async (req, res) => { + const companyId = req.params.companyId as string; + const taskId = req.params.taskId as string; + assertCompanyAccess(req, companyId); + + const parsed = updateResearchTaskSchema.safeParse(req.body); + if (!parsed.success) { + throw badRequest("Invalid request body", parsed.error.format()); + } + + const task = await svc.updateTask(companyId, taskId, parsed.data); + res.json(task); + }); + + // ──────────────────────────────────────────────────────────────────────────── + // Findings + // ──────────────────────────────────────────────────────────────────────────── + + router.get("/companies/:companyId/research/tasks/:taskId/findings", async (req, res) => { + const companyId = req.params.companyId as string; + const taskId = req.params.taskId as string; + assertCompanyAccess(req, companyId); + + const limit = req.query.limit ? Number(req.query.limit) : undefined; + const offset = req.query.offset ? Number(req.query.offset) : undefined; + + const result = await svc.listFindings(companyId, { taskId, limit, offset }); + res.json(result); + }); + + router.post("/companies/:companyId/research/findings", async (req, res) => { + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + + const parsed = createResearchFindingSchema.safeParse(req.body); + if (!parsed.success) { + throw badRequest("Invalid request body", parsed.error.format()); + } + + const finding = await svc.createFinding(companyId, parsed.data); + res.status(201).json(finding); + }); + + router.post("/companies/:companyId/research/findings/:findingId/mark-duplicate", async (req, res) => { + const companyId = req.params.companyId as string; + const findingId = req.params.findingId as string; + assertCompanyAccess(req, companyId); + + const parsed = markDuplicateSchema.safeParse(req.body); + if (!parsed.success) { + throw badRequest("Invalid request body", parsed.error.format()); + } + + const finding = await svc.markDuplicate(companyId, findingId, parsed.data.duplicateOfId); + res.json(finding); + }); + + // ──────────────────────────────────────────────────────────────────────────── + // Sources + // ──────────────────────────────────────────────────────────────────────────── + + router.get("/companies/:companyId/research/sessions/:sessionId/sources", async (req, res) => { + const companyId = req.params.companyId as string; + const sessionId = req.params.sessionId as string; + assertCompanyAccess(req, companyId); + + const sources = await svc.getSources(companyId, sessionId); + res.json(sources); + }); + + // ──────────────────────────────────────────────────────────────────────────── + // Memory + // ──────────────────────────────────────────────────────────────────────────── + + router.get("/companies/:companyId/research/memory", async (req, res) => { + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + + const key = req.query.key as string | undefined; + const memory = await svc.getMemory(companyId, key); + res.json(memory); + }); + + router.post("/companies/:companyId/research/memory", async (req, res) => { + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + + const parsed = createResearchMemorySchema.safeParse(req.body); + if (!parsed.success) { + throw badRequest("Invalid request body", parsed.error.format()); + } + + const memory = await svc.setMemory(companyId, parsed.data); + res.status(201).json(memory); + }); + + // ──────────────────────────────────────────────────────────────────────────── + // Dashboard + // ──────────────────────────────────────────────────────────────────────────── + + router.get("/companies/:companyId/research/dashboard", async (req, res) => { + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + + const dashboard = await svc.getDashboard(companyId); + res.json(dashboard); + }); + + return router; +} diff --git a/server/src/services/hire-hook.ts b/server/src/services/hire-hook.ts index 79a38177ee1..27a5221e776 100644 --- a/server/src/services/hire-hook.ts +++ b/server/src/services/hire-hook.ts @@ -39,7 +39,7 @@ export async function notifyHireApproved( return; } - const adapterType = row.adapterType ?? "process"; + const adapterType = row.adapterType ?? "claude_local"; const adapter = findActiveServerAdapter(adapterType); const onHireApproved = adapter?.onHireApproved; if (!onHireApproved) { diff --git a/server/src/services/index.ts b/server/src/services/index.ts index fc5b5ab5d3f..9ed0609d6b4 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -71,3 +71,8 @@ export { notifyHireApproved, type NotifyHireApprovedInput } from "./hire-hook.js export { publishLiveEvent, subscribeCompanyLiveEvents } from "./live-events.js"; export { reconcilePersistedRuntimeServicesOnStartup, restartDesiredRuntimeServicesOnStartup } from "./workspace-runtime.js"; export { createStorageServiceFromConfig, getStorageService } from "../storage/index.js"; +export { researchService } from "./research.js"; +export { researchEngine } from "./research-engine.js"; +export { createSearchProvider, MockSearchProvider, SerperSearchProvider, SemanticScholarSearchProvider } from "./research-search.js"; +export { generateResearchPlan, extractFindingsFromContent, generateResearchReport } from "./research-llm.js"; +export { researchProgressService } from "./research-progress.js"; diff --git a/server/src/services/research-engine.ts b/server/src/services/research-engine.ts new file mode 100644 index 00000000000..dde33cfb009 --- /dev/null +++ b/server/src/services/research-engine.ts @@ -0,0 +1,786 @@ +/** + * Research Engine Service + * + * Core orchestrator for autonomous research execution. + * + * Workflow: + * planning → running → [cancelling] → completed/failed + * + * Each session runs sequentially (1 concurrent per company). + */ +import { eq, and } from "drizzle-orm"; +import type { Db } from "@paperclipai/db"; +import { + researchSessions, + researchTasks, + researchFindings, + researchSources, +} from "@paperclipai/db"; +import type { Config } from "../config.js"; +import { logger } from "../middleware/logger.js"; +import { generateResearchPlan, extractFindingsFromContent, generateResearchReport } from "./research-llm.js"; +import { createSearchProvider, type SearchProvider, filterSourcesByQuality, fetchPageContent } from "./research-search.js"; +import { researchProgressService } from "./research-progress.js"; + +// In-memory lock: one session per company at a time +const runningSessions = new Map(); // companyId -> sessionId +const cancelFlags = new Map(); // sessionId -> shouldCancel + +export interface ResearchEngineDeps { + db: Db; + config: Config; +} + +export function researchEngine(deps: ResearchEngineDeps) { + const { db, config } = deps; + const progress = researchProgressService(db); + + // Resolve search provider based on config + const providerType = config.researchSearchProvider; + const providerKey = + providerType === "serper" + ? config.serperApiKey + : providerType === "semantic-scholar" + ? config.semanticScholarApiKey + : undefined; + const searchProvider: SearchProvider = createSearchProvider(providerType, providerKey); + + return { + /** + * Start executing a research session. + */ + async executeSession(sessionId: string, companyId: string): Promise { + // Check concurrent session lock + const existing = runningSessions.get(companyId); + if (existing && existing !== sessionId) { + throw new Error(`Another research session is already running for this company: ${existing}`); + } + + // Set lock + runningSessions.set(companyId, sessionId); + cancelFlags.set(sessionId, false); + + try { + await runSession(sessionId, companyId); + } finally { + runningSessions.delete(companyId); + cancelFlags.delete(sessionId); + } + }, + + /** + * Request cancellation of a running session. + */ + async requestCancel(sessionId: string, companyId: string): Promise { + const running = runningSessions.get(companyId); + if (running !== sessionId) { + return false; // Not running or different session + } + + cancelFlags.set(sessionId, true); + + // Update status to cancelling + await db + .update(researchSessions) + .set({ status: "cancelling" as any, updatedAt: new Date() }) + .where( + and( + eq(researchSessions.id, sessionId), + eq(researchSessions.companyId, companyId) + ) + ); + + progress.publishSessionUpdate(companyId, sessionId, "cancelling", { + message: "Cancellation requested", + }); + + return true; + }, + + /** + * Check if a session is currently running. + */ + isRunning(sessionId: string, companyId: string): boolean { + return runningSessions.get(companyId) === sessionId; + }, + + /** + * Retry a single failed task. + * Resets the task to pending and re-executes it. + */ + async retryTask(taskId: string, sessionId: string, companyId: string): Promise { + // Check if session is already running + const existing = runningSessions.get(companyId); + if (existing && existing !== sessionId) { + throw new Error(`Another research session is already running for this company: ${existing}`); + } + + // Fetch the task + const [task] = await db + .select() + .from(researchTasks) + .where( + and( + eq(researchTasks.id, taskId), + eq(researchTasks.sessionId, sessionId), + eq(researchTasks.companyId, companyId) + ) + ) + .limit(1); + + if (!task) { + throw new Error("Research task not found"); + } + + if (task.status !== "failed") { + throw new Error(`Cannot retry task with status: ${task.status}. Only failed tasks can be retried.`); + } + + // Set lock + runningSessions.set(companyId, sessionId); + cancelFlags.set(sessionId, false); + + try { + // Reset task to pending + await db + .update(researchTasks) + .set({ + status: "pending" as any, + findingsSummary: null, + sources: null, + startedAt: null, + completedAt: null, + updatedAt: new Date(), + }) + .where(eq(researchTasks.id, taskId)); + + // Delete old findings for this task + await db + .delete(researchFindings) + .where(eq(researchFindings.taskId, taskId)); + + progress.publishTaskUpdate(companyId, sessionId, taskId, "pending", { + message: "Task queued for retry", + }); + + // Update session status back to running if it was failed + await db + .update(researchSessions) + .set({ status: "running" as any, updatedAt: new Date() }) + .where(eq(researchSessions.id, sessionId)); + + progress.publishSessionUpdate(companyId, sessionId, "running", { + message: `Retrying task: ${task.title}`, + }); + + // Re-execute the task + await executeTask(taskId, sessionId, companyId, task.title); + await progress.updateProgress(sessionId, companyId); + + // Check if all tasks are now completed + const remainingTasks = await db + .select() + .from(researchTasks) + .where(eq(researchTasks.sessionId, sessionId)); + + const allCompleted = remainingTasks.every((t) => t.status === "completed"); + const anyFailed = remainingTasks.some((t) => t.status === "failed"); + + if (allCompleted) { + // Generate report + const [session] = await db + .select() + .from(researchSessions) + .where( + and( + eq(researchSessions.id, sessionId), + eq(researchSessions.companyId, companyId) + ) + ) + .limit(1); + + const allFindings = await db + .select() + .from(researchFindings) + .where(eq(researchFindings.sessionId, sessionId)) + .orderBy(researchFindings.createdAt); + + const reportFindings = allFindings.map((f) => ({ + content: f.content, + category: f.category || "General", + confidence: f.confidence || "medium", + sourceUrl: f.sourceUrl, + sourceTitle: f.sourceTitle, + sourceDomain: f.sourceDomain, + })); + + const generated = await generateResearchReport( + session.query, + reportFindings, + { model: config.researchLlmModel, apiKey: config.researchLlmApiKey } + ); + + await db + .update(researchSessions) + .set({ + status: "completed" as any, + report: generated.markdown, + progressPercent: 100, + completedAt: new Date(), + updatedAt: new Date(), + }) + .where(eq(researchSessions.id, sessionId)); + + progress.publishSessionUpdate(companyId, sessionId, "completed", { + message: "Research completed after retry", + findingsCount: allFindings.length, + }); + } else if (anyFailed) { + // Some tasks still failed — mark session as failed + await db + .update(researchSessions) + .set({ + status: "failed" as any, + updatedAt: new Date(), + }) + .where(eq(researchSessions.id, sessionId)); + + progress.publishSessionUpdate(companyId, sessionId, "failed", { + message: "Some tasks still failed after retry", + }); + } else { + // Still running (more tasks pending) + await db + .update(researchSessions) + .set({ + status: "running" as any, + updatedAt: new Date(), + }) + .where(eq(researchSessions.id, sessionId)); + } + } finally { + runningSessions.delete(companyId); + cancelFlags.delete(sessionId); + } + }, + + /** + * Resume a cancelled or failed research session. + * Picks up from the last completed task. + */ + async resumeSession(sessionId: string, companyId: string): Promise { + // Check concurrent session lock + const existing = runningSessions.get(companyId); + if (existing && existing !== sessionId) { + throw new Error(`Another research session is already running for this company: ${existing}`); + } + + // Set lock + runningSessions.set(companyId, sessionId); + cancelFlags.set(sessionId, false); + + try { + await resumeRunSession(sessionId, companyId); + } finally { + runningSessions.delete(companyId); + cancelFlags.delete(sessionId); + } + }, + }; + + // ────────────────────────────────────────────────────────────────────────── + // Main execution loop + // ────────────────────────────────────────────────────────────────────────── + + async function runSession(sessionId: string, companyId: string): Promise { + logger.info({ sessionId, companyId }, "Research session starting"); + + // 1. Fetch session + const [session] = await db + .select() + .from(researchSessions) + .where( + and( + eq(researchSessions.id, sessionId), + eq(researchSessions.companyId, companyId) + ) + ) + .limit(1); + + if (!session) { + throw new Error("Research session not found"); + } + + // 2. Transition to running + await db + .update(researchSessions) + .set({ + status: "running" as any, + startedAt: new Date(), + updatedAt: new Date(), + }) + .where(eq(researchSessions.id, sessionId)); + + progress.publishSessionUpdate(companyId, sessionId, "running", { + message: "Research started", + }); + + try { + // 3. Generate plan (or reuse existing) + let plan = session.plan as { + strategy: string; + subtopics: Array<{ id: string; title: string; description: string; priority: number }>; + } | null; + + if (!plan || !plan.subtopics || plan.subtopics.length === 0) { + plan = await generateResearchPlan( + session.query, + session.maxSubtopics, + session.depth, + { model: config.researchLlmModel, apiKey: config.researchLlmApiKey } + ); + + // Save plan + await db + .update(researchSessions) + .set({ plan: plan as any, updatedAt: new Date() }) + .where(eq(researchSessions.id, sessionId)); + } + + // Check cancellation + if (isCancelled(sessionId)) { + await completeCancellation(sessionId, companyId); + return; + } + + // 4. Create tasks from plan + const subtopics = plan.subtopics.slice(0, session.maxSubtopics); + for (let i = 0; i < subtopics.length; i++) { + const subtopic = subtopics[i]; + await db.insert(researchTasks).values({ + sessionId, + companyId, + title: subtopic.title, + sequenceOrder: i, + status: "pending" as any, + }); + } + + logger.info( + { sessionId, taskCount: subtopics.length }, + "Research tasks created" + ); + + // 5. Execute each task + const tasks = await db + .select() + .from(researchTasks) + .where(eq(researchTasks.sessionId, sessionId)) + .orderBy(researchTasks.sequenceOrder); + + for (const task of tasks) { + if (isCancelled(sessionId)) { + await completeCancellation(sessionId, companyId); + return; + } + + await executeTask(task.id, sessionId, companyId, task.title); + await progress.updateProgress(sessionId, companyId); + } + + // 6. Generate report + const allFindings = await db + .select() + .from(researchFindings) + .where(eq(researchFindings.sessionId, sessionId)) + .orderBy(researchFindings.createdAt); + + const reportFindings = allFindings.map((f) => ({ + content: f.content, + category: f.category || "General", + confidence: f.confidence || "medium", + sourceUrl: f.sourceUrl, + sourceTitle: f.sourceTitle, + sourceDomain: f.sourceDomain, + })); + + const generated = await generateResearchReport( + session.query, + reportFindings, + { model: config.researchLlmModel, apiKey: config.researchLlmApiKey } + ); + + const report = generated.markdown; + + // 7. Mark completed + await db + .update(researchSessions) + .set({ + status: "completed" as any, + report, + progressPercent: 100, + completedAt: new Date(), + updatedAt: new Date(), + }) + .where(eq(researchSessions.id, sessionId)); + + progress.publishSessionUpdate(companyId, sessionId, "completed", { + message: "Research completed", + findingsCount: allFindings.length, + sourcesCount: generated.sources.length, + }); + + logger.info( + { sessionId, findingsCount: allFindings.length }, + "Research session completed" + ); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error({ sessionId, error: message }, "Research session failed"); + + await db + .update(researchSessions) + .set({ + status: "failed" as any, + updatedAt: new Date(), + }) + .where(eq(researchSessions.id, sessionId)); + + progress.publishSessionUpdate(companyId, sessionId, "failed", { + error: message, + }); + } + } + + // ────────────────────────────────────────────────────────────────────────── + // Resume session (pick up from where it was cancelled/failed) + // ────────────────────────────────────────────────────────────────────────── + + async function resumeRunSession(sessionId: string, companyId: string): Promise { + logger.info({ sessionId, companyId }, "Research session resuming"); + + // 1. Fetch session + const [session] = await db + .select() + .from(researchSessions) + .where( + and( + eq(researchSessions.id, sessionId), + eq(researchSessions.companyId, companyId) + ) + ) + .limit(1); + + if (!session) { + throw new Error("Research session not found"); + } + + // Only allow resuming cancelled or failed sessions + if (session.status !== "cancelled" && session.status !== "failed" && session.status !== "cancelling") { + throw new Error(`Cannot resume session with status: ${session.status}`); + } + + // 2. Transition to running + await db + .update(researchSessions) + .set({ + status: "running" as any, + updatedAt: new Date(), + }) + .where(eq(researchSessions.id, sessionId)); + + progress.publishSessionUpdate(companyId, sessionId, "running", { + message: "Research resumed", + }); + + try { + // 3. Find pending or failed tasks to resume + const tasks = await db + .select() + .from(researchTasks) + .where(eq(researchTasks.sessionId, sessionId)) + .orderBy(researchTasks.sequenceOrder); + + // Find the first task that is not completed + let resumeFromIndex = 0; + for (let i = 0; i < tasks.length; i++) { + if (tasks[i].status === "completed") { + resumeFromIndex = i + 1; + } else { + break; + } + } + + // Execute remaining tasks + for (let i = resumeFromIndex; i < tasks.length; i++) { + if (isCancelled(sessionId)) { + await completeCancellation(sessionId, companyId); + return; + } + + const task = tasks[i]; + await executeTask(task.id, sessionId, companyId, task.title); + await progress.updateProgress(sessionId, companyId); + } + + // 4. Generate report (reuse existing findings + new ones) + const allFindings = await db + .select() + .from(researchFindings) + .where(eq(researchFindings.sessionId, sessionId)) + .orderBy(researchFindings.createdAt); + + const reportFindings = allFindings.map((f) => ({ + content: f.content, + category: f.category || "General", + confidence: f.confidence || "medium", + sourceUrl: f.sourceUrl, + sourceTitle: f.sourceTitle, + sourceDomain: f.sourceDomain, + })); + + const generated = await generateResearchReport( + session.query, + reportFindings, + { model: config.researchLlmModel, apiKey: config.researchLlmApiKey } + ); + + const report = generated.markdown; + + // 5. Mark completed + await db + .update(researchSessions) + .set({ + status: "completed" as any, + report, + progressPercent: 100, + completedAt: new Date(), + updatedAt: new Date(), + }) + .where(eq(researchSessions.id, sessionId)); + + progress.publishSessionUpdate(companyId, sessionId, "completed", { + message: "Research completed (resumed)", + findingsCount: allFindings.length, + sourcesCount: generated.sources.length, + }); + + logger.info( + { sessionId, findingsCount: allFindings.length }, + "Research session completed after resume" + ); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error({ sessionId, error: message }, "Research session failed after resume"); + + await db + .update(researchSessions) + .set({ + status: "failed" as any, + updatedAt: new Date(), + }) + .where(eq(researchSessions.id, sessionId)); + + progress.publishSessionUpdate(companyId, sessionId, "failed", { + error: message, + }); + } + } + + // ────────────────────────────────────────────────────────────────────────── + // Task execution + // ────────────────────────────────────────────────────────────────────────── + + async function executeTask( + taskId: string, + sessionId: string, + companyId: string, + taskTitle: string + ): Promise { + logger.info({ taskId, taskTitle }, "Executing research task"); + + // Mark task as running + await db + .update(researchTasks) + .set({ status: "running" as any, startedAt: new Date(), updatedAt: new Date() }) + .where(eq(researchTasks.id, taskId)); + + progress.publishTaskUpdate(companyId, sessionId, taskId, "running", { + title: taskTitle, + }); + + try { + // Search for the task topic + const searchResults = await searchProvider.search( + taskTitle, + config.researchMaxSearchResults + ); + + // Filter by quality score + const qualityResults = filterSourcesByQuality(searchResults, 40); + const resultsToProcess = qualityResults.length > 0 ? qualityResults : searchResults; + + // Track unique sources for this task + const taskSources: Array<{ url: string; title: string; snippet: string; qualityScore?: number }> = []; + let findingsCount = 0; + + for (const result of resultsToProcess.slice(0, config.researchMaxFindingsPerTask)) { + if (isCancelled(sessionId)) break; + + // Publish source processing event + progress.publishSourceProcessing(companyId, sessionId, taskId, result.url, result.title); + + // Store source (skip if already exists for this session+url) + try { + await db + .insert(researchSources) + .values({ + sessionId, + companyId, + url: result.url, + title: result.title, + domain: result.domain, + reliabilityScore: result.qualityScore ?? confidenceToScore("medium"), + accessCount: 1, + lastAccessedAt: new Date(), + }); + } catch { + // Source already exists for this session+url, ignore + } + + taskSources.push({ + url: result.url, + title: result.title, + snippet: result.snippet, + qualityScore: result.qualityScore, + }); + + // Fetch real page content for richer findings + let contentToAnalyze = result.snippet; + const pageContent = await fetchPageContent(result.url); + if (pageContent) { + contentToAnalyze = pageContent; + } + + // Extract findings from content + const findings = await extractFindingsFromContent( + taskTitle, + result.title, + contentToAnalyze, + { model: config.researchLlmModel, apiKey: config.researchLlmApiKey } + ); + + for (const finding of findings) { + await db.insert(researchFindings).values({ + taskId, + sessionId, + companyId, + content: finding.content, + sourceUrl: result.url, + sourceTitle: result.title, + sourceDomain: result.domain, + confidence: finding.confidence as any, + category: finding.category, + reliabilityScore: result.qualityScore ?? confidenceToScore(finding.confidence), + }); + + findingsCount++; + progress.publishFindingCreated(companyId, sessionId, taskId, { + content: finding.content.slice(0, 100), + category: finding.category, + }); + } + + // Publish finding progress after each source + progress.publishFindingProgress( + companyId, + sessionId, + taskId, + findingsCount, + taskSources.length, + { currentSource: result.title } + ); + } + + // Update task with summary + await db + .update(researchTasks) + .set({ + status: "completed" as any, + findingsSummary: `Found ${findingsCount} finding(s) from ${taskSources.length} source(s)`, + sources: taskSources as any, + completedAt: new Date(), + updatedAt: new Date(), + }) + .where(eq(researchTasks.id, taskId)); + + progress.publishTaskUpdate(companyId, sessionId, taskId, "completed", { + findingsCount, + sourcesCount: taskSources.length, + }); + + logger.info({ taskId, findingsCount, sourcesCount: taskSources.length }, "Task completed"); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error({ taskId, error: message }, "Task failed"); + + await db + .update(researchTasks) + .set({ + status: "failed" as any, + findingsSummary: `Error: ${message}`, + updatedAt: new Date(), + }) + .where(eq(researchTasks.id, taskId)); + + progress.publishTaskUpdate(companyId, sessionId, taskId, "failed", { + error: message, + }); + + // Re-throw to fail the entire session + throw new Error(`Task failed: ${message}`); + } + } + + // ────────────────────────────────────────────────────────────────────────── + // Cancellation helpers + // ────────────────────────────────────────────────────────────────────────── + + function isCancelled(sessionId: string): boolean { + return cancelFlags.get(sessionId) === true; + } + + async function completeCancellation(sessionId: string, companyId: string): Promise { + logger.info({ sessionId }, "Research session cancelled"); + + await db + .update(researchSessions) + .set({ + status: "cancelled" as any, + updatedAt: new Date(), + }) + .where(eq(researchSessions.id, sessionId)); + + progress.publishSessionUpdate(companyId, sessionId, "cancelled", { + message: "Research cancelled by user", + cancelled: true, + }); + } +} + +// ────────────────────────────────────────────────────────────────────────── +// Helpers +// ────────────────────────────────────────────────────────────────────────── + +function confidenceToScore(confidence: string): number { + switch (confidence) { + case "high": + return 85; + case "medium": + return 60; + case "low": + return 35; + default: + return 50; + } +} diff --git a/server/src/services/research-llm.ts b/server/src/services/research-llm.ts new file mode 100644 index 00000000000..36529b19f50 --- /dev/null +++ b/server/src/services/research-llm.ts @@ -0,0 +1,531 @@ +/** + * Research LLM Service + * + * Supports OpenAI, Groq, and other OpenAI-compatible APIs. + * Uses native fetch() — no external SDK dependency. + */ + +const OPENAI_API_BASE = "https://api.openai.com/v1"; +const GROQ_API_BASE = "https://api.groq.com/openai/v1"; +const LLM_TIMEOUT_MS = 60_000; + +function getApiBase(): string { + if (process.env.GROQ_API_KEY?.trim()) return GROQ_API_BASE; + return OPENAI_API_BASE; +} + +function getDefaultModel(): string { + if (process.env.GROQ_API_KEY?.trim()) return "llama-3.2-90b-vision-preview"; + return "gpt-4o-mini"; +} + +export interface LlmMessage { + role: "system" | "user" | "assistant"; + content: string; +} + +export interface LlmCompletionOptions { + model?: string; + temperature?: number; + maxTokens?: number; + apiKey?: string; +} + +export interface LlmCompletionResult { + text: string; + usage?: { promptTokens: number; completionTokens: number; totalTokens: number }; +} + +function getAuthHeaders(apiKey?: string): Record { + const key = apiKey || process.env.GROQ_API_KEY?.trim() || process.env.OPENAI_API_KEY?.trim() || process.env.PAPERCLIP_RESEARCH_LLM_API_KEY?.trim(); + if (!key) { + throw new Error("No LLM API key configured. Set GROQ_API_KEY, OPENAI_API_KEY, or PAPERCLIP_RESEARCH_LLM_API_KEY."); + } + return { + Authorization: `Bearer ${key}`, + "Content-Type": "application/json", + }; +} + +function hasApiKey(apiKey?: string): boolean { + return !!(apiKey || process.env.GROQ_API_KEY?.trim() || process.env.OPENAI_API_KEY?.trim() || process.env.PAPERCLIP_RESEARCH_LLM_API_KEY?.trim()); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Security: Sanitize user input before including in LLM prompts +// ───────────────────────────────────────────────────────────────────────────── + +function sanitizeForPrompt(input: string): string { + let sanitized = input + .replace(/\{\s*"role"\s*:\s*"system"\s*\}/gi, "") + .replace(/ignore\s+(previous|above|all)\s+instructions/gi, "") + .replace(/disregard\s+(previous|above|all)\s+instructions/gi, "") + .replace(/forget\s+(previous|above|all)\s+instructions/gi, "") + .replace(/<\|im_start\|>/gi, "") + .replace(/<\|im_end\|>/gi, "") + .replace(/\[SYSTEM\]/gi, "") + .replace(/\[INST\]/gi, "") + .replace(/\[\/INST\]/gi, "") + .replace(/<>/gi, "") + .replace(/<\/SYS>>/gi, "") + .replace(/\n{5,}/g, "\n\n\n") + .trim(); + + const MAX_PROMPT_INPUT_LENGTH = 3000; + if (sanitized.length > MAX_PROMPT_INPUT_LENGTH) { + sanitized = sanitized.slice(0, MAX_PROMPT_INPUT_LENGTH) + "..."; + } + return sanitized; +} + +async function fetchCompletion( + messages: LlmMessage[], + opts: LlmCompletionOptions = {}, +): Promise { + const model = opts.model || "gpt-4o-mini"; + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), LLM_TIMEOUT_MS); + + try { + const response = await fetch(`${OPENAI_API_BASE}/chat/completions`, { + method: "POST", + headers: getAuthHeaders(opts.apiKey), + body: JSON.stringify({ + model, + messages, + temperature: opts.temperature ?? 0.7, + max_tokens: opts.maxTokens ?? 2048, + }), + signal: controller.signal, + }); + + if (!response.ok) { + const errorText = await response.text().catch(() => "unknown error"); + throw new Error(`LLM API error ${response.status}: ${errorText}`); + } + + const data = (await response.json()) as { + choices: Array<{ message: { content: string } }>; + usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number }; + }; + + const text = data.choices[0]?.message?.content?.trim() ?? ""; + const usage = data.usage + ? { + promptTokens: data.usage.prompt_tokens, + completionTokens: data.usage.completion_tokens, + totalTokens: data.usage.total_tokens, + } + : undefined; + + return { text, usage }; + } finally { + clearTimeout(timeout); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// Research-specific LLM prompts +// ───────────────────────────────────────────────────────────────────────────── + +export async function generateResearchPlan( + query: string, + maxSubtopics: number, + depth: string, + opts?: LlmCompletionOptions, +): Promise<{ strategy: string; subtopics: Array<{ id: string; title: string; description: string; priority: number }> }> { + // Mock mode: return fallback subtopics without calling LLM + if (!hasApiKey(opts?.apiKey)) { + return { + strategy: `Research on: ${query}`, + subtopics: generateFallbackSubtopics(query, maxSubtopics), + }; + } + + const systemPrompt = `You are a research planning assistant. Given a user query, generate a structured research plan. + +Respond ONLY with valid JSON in this exact format: +{ + "strategy": "Brief overview of research approach (1-2 sentences)", + "subtopics": [ + { "id": "subtopic-1", "title": "Concise subtopic title", "description": "What to research for this subtopic (1 sentence)", "priority": 1 } + ] +} + +Rules: +- Generate 3 to ${maxSubtopics} subtopics based on depth: ${depth} +- shallow = 3 subtopics, medium = 4-5, deep = 5-7 +- Each subtopic must have a unique id like "subtopic-1", "subtopic-2", etc. +- Priorities should be 1 (highest) to N (lowest) +- Keep titles under 60 characters +- Descriptions under 120 characters`; + + const result = await fetchCompletion( + [ + { role: "system", content: systemPrompt }, + { role: "user", content: `Research query: "${sanitizeForPrompt(query)}"` }, + ], + { ...opts, temperature: 0.5, maxTokens: 1500 }, + ); + + try { + const parsed = JSON.parse(result.text) as { + strategy: string; + subtopics: Array<{ id: string; title: string; description: string; priority: number }>; + }; + + // Validate and sanitize + const subtopics = (parsed.subtopics || []) + .slice(0, maxSubtopics) + .map((s, idx) => ({ + id: s.id || `subtopic-${idx + 1}`, + title: (s.title || `Subtopic ${idx + 1}`).slice(0, 60), + description: (s.description || "").slice(0, 120), + priority: typeof s.priority === "number" ? s.priority : idx + 1, + })); + + return { + strategy: (parsed.strategy || `Research on: ${query}`).slice(0, 300), + subtopics: subtopics.length > 0 ? subtopics : generateFallbackSubtopics(query, maxSubtopics), + }; + } catch { + // Fallback if LLM returns invalid JSON + return { + strategy: `Research on: ${query}`, + subtopics: generateFallbackSubtopics(query, maxSubtopics), + }; + } +} + +export async function extractFindingsFromContent( + subtopicTitle: string, + sourceTitle: string, + sourceSnippet: string, + opts?: LlmCompletionOptions, +): Promise> { + // Mock mode: generate topic-aware findings from the snippet + if (!hasApiKey(opts?.apiKey)) { + const category = detectFindingCategory(subtopicTitle, sourceSnippet); + // Generate 1-2 findings based on the actual snippet content + const findings: Array<{ content: string; confidence: "high" | "medium" | "low"; category: string }> = []; + + // First finding: direct insight from the snippet + if (sourceSnippet.length > 30) { + findings.push({ + content: sourceSnippet.slice(0, 280) + (sourceSnippet.length > 280 ? "..." : ""), + confidence: "high", + category, + }); + } + + // Second finding: inferred implication if snippet is substantial + if (sourceSnippet.length > 100) { + const sentences = sourceSnippet.split(/[.!?]+/).filter(s => s.trim().length > 20); + if (sentences.length > 1) { + findings.push({ + content: sentences[1].trim().slice(0, 280) + (sentences[1].length > 280 ? "..." : ""), + confidence: "medium", + category, + }); + } + } + + return findings.length > 0 ? findings : [ + { + content: `Relevant information about ${subtopicTitle}: ${sourceSnippet.slice(0, 200)}`, + confidence: "medium", + category, + }, + ]; + } + + const systemPrompt = `You are a research analyst. Extract key findings from the provided source content for a given subtopic. + +Respond ONLY with valid JSON in this exact format: +{ + "findings": [ + { "content": "Specific finding statement", "confidence": "high|medium|low", "category": "Category name" } + ] +} + +Rules: +- Extract 1-3 concrete findings +- Each finding must be a specific, factual statement +- Confidence: high = directly stated fact, medium = inferred but likely, low = speculative +- Category should be a short label like "Overview", "Best Practices", "Performance", "Security", etc. +- If no useful findings, return empty findings array`; + + const userContent = `Subtopic: ${sanitizeForPrompt(subtopicTitle)} +Source: ${sanitizeForPrompt(sourceTitle)} +Content: ${sanitizeForPrompt(sourceSnippet).slice(0, 4000)}`; + + try { + const result = await fetchCompletion( + [ + { role: "system", content: systemPrompt }, + { role: "user", content: userContent }, + ], + { ...opts, temperature: 0.3, maxTokens: 1200 }, + ); + + const parsed = JSON.parse(result.text) as { + findings: Array<{ content: string; confidence: string; category: string }>; + }; + + return (parsed.findings || []) + .filter((f) => f.content && f.content.length > 10) + .map((f) => ({ + content: f.content.slice(0, 500), + confidence: ["high", "medium", "low"].includes(f.confidence) ? (f.confidence as "high" | "medium" | "low") : "medium", + category: (f.category || "General").slice(0, 50), + })); + } catch { + // Fallback: create a single finding from the snippet + return [ + { + content: `Relevant information found: ${sourceSnippet.slice(0, 300)}`, + confidence: "medium", + category: "General", + }, + ]; + } +} + +export interface ReportSource { + index: number; + url: string; + title: string; + domain: string; +} + +export interface ReportFinding { + content: string; + category: string; + confidence: string; + sourceUrl?: string | null; + sourceTitle?: string | null; + sourceDomain?: string | null; +} + +export interface GeneratedReport { + markdown: string; + sources: ReportSource[]; +} + +export async function generateResearchReport( + query: string, + findings: ReportFinding[], + opts?: LlmCompletionOptions, +): Promise { + // Build unique source list with stable indices + const sourceMap = new Map(); + for (const f of findings) { + const url = f.sourceUrl || ""; + if (url && !sourceMap.has(url)) { + sourceMap.set(url, { + index: sourceMap.size + 1, + url, + title: f.sourceTitle || url, + domain: f.sourceDomain || "", + }); + } + } + const sources = Array.from(sourceMap.values()).sort((a, b) => a.index - b.index); + + // Mock mode: generate simple markdown report from findings with citations + if (!hasApiKey(opts?.apiKey)) { + const byCategory = new Map(); + for (const f of findings) { + const cat = f.category || "General"; + const list = byCategory.get(cat) || []; + const citation = f.sourceUrl && sourceMap.has(f.sourceUrl) + ? ` [${sourceMap.get(f.sourceUrl)!.index}]` + : ""; + list.push(`- ${f.content}${citation}`); + byCategory.set(cat, list); + } + + let report = `# Research Report: ${query}\n\n`; + report += `## Executive Summary\n\nThis report summarizes findings from automated research on "${query}".\n\n`; + for (const [category, items] of byCategory) { + report += `## ${category}\n\n${items.join("\n")}\n\n`; + } + if (sources.length > 0) { + report += `## Sources\n\n`; + for (const s of sources) { + report += `${s.index}. ${s.title} (${s.domain})\n <${s.url}>\n\n`; + } + } + report += `---\n\n*Generated by Paperclip Research Engine*\n`; + return { markdown: report, sources }; + } + + const systemPrompt = `You are a research report writer. Create a structured Markdown report from the provided findings. + +Rules: +- Use clear Markdown formatting with headers +- Organize by category +- Include a brief executive summary +- Cite confidence levels where relevant +- Include source citations as [1], [2], etc. inline with findings +- Add a "Sources" section at the end listing all cited sources with their numbers +- Keep the report concise but comprehensive`; + + const findingsText = findings + .map((f, i) => { + const citation = f.sourceUrl && sourceMap.has(f.sourceUrl) + ? ` [${sourceMap.get(f.sourceUrl)!.index}]` + : ""; + return `${i + 1}. [${f.confidence.toUpperCase()}] ${f.category}: ${f.content}${citation}`; + }) + .join("\n"); + + const sourcesText = sources.length > 0 + ? `\n\nSources:\n${sources.map((s) => `[${s.index}] ${s.title} (${s.domain}) - ${s.url}`).join("\n")}` + : ""; + + try { + const result = await fetchCompletion( + [ + { role: "system", content: systemPrompt }, + { + role: "user", + content: `Query: ${sanitizeForPrompt(query)}\n\nFindings:\n${findingsText}${sourcesText}\n\nGenerate a Markdown research report with inline citations [1], [2], etc. and a Sources section at the end.`, + }, + ], + { ...opts, temperature: 0.4, maxTokens: 3000 }, + ); + + const markdown = result.text || `# Research Report: ${query}\n\nNo findings available.`; + return { markdown, sources }; + } catch { + // Fallback: simple markdown from findings with citations + const byCategory = new Map(); + for (const f of findings) { + const cat = f.category || "General"; + const list = byCategory.get(cat) || []; + const citation = f.sourceUrl && sourceMap.has(f.sourceUrl) + ? ` [${sourceMap.get(f.sourceUrl)!.index}]` + : ""; + list.push(`- ${f.content}${citation}`); + byCategory.set(cat, list); + } + + let report = `# Research Report: ${query}\n\n`; + for (const [category, items] of byCategory) { + report += `## ${category}\n\n${items.join("\n")}\n\n`; + } + if (sources.length > 0) { + report += `## Sources\n\n`; + for (const s of sources) { + report += `${s.index}. ${s.title} (${s.domain})\n <${s.url}>\n\n`; + } + } + return { markdown: report, sources }; + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// Fallback helpers +// ───────────────────────────────────────────────────────────────────────────── + +function detectFindingCategory(subtopicTitle: string, sourceSnippet: string): string { + const text = (subtopicTitle + " " + sourceSnippet).toLowerCase(); + + if (/\b(ingredient|component|element|composition|material|substance|flavor compound|spice|herb|seasoning|sauce|dough|cheese|meat|vegetable|fruit|oil|salt|sugar|yeast|flour|water)\b/.test(text)) return "Ingredients"; + if (/\b(technique|method|preparation|cooking|baking|grilling|roasting|frying|boiling|steaming|temperature|timing|process|step|recipe|instruction)\b/.test(text)) return "Techniques"; + if (/\b(science|chemistry|molecular|maillard|reaction|compound|sensory|taste|flavor|aroma|texture|umami|sweet|sour|bitter|salty)\b/.test(text)) return "Science"; + if (/\b(regional|cultural|cuisine|style|variation|tradition|italian|french|asian|mediterranean|local|authentic|heritage)\b/.test(text)) return "Cultural Variations"; + if (/\b(tip|recommendation|expert|chef|secret|advice|guide|professional|master|perfect|best|optimal|ideal)\b/.test(text)) return "Expert Advice"; + if (/\b(mistake|error|pitfall|avoid|fix|troubleshoot|problem|issue|fail|common|wrong|bad|overcook|undercook|burn)\b/.test(text)) return "Common Mistakes"; + if (/\b(health|nutrition|calorie|vitamin|protein|fat|carb|diet|wellness|benefit|risk|allerg|intolerance)\b/.test(text)) return "Health & Nutrition"; + if (/\b(software|code|programming|api|developer|app|web|cloud|database|server|framework|tech|computer|algorithm|system|architecture)\b/.test(text)) return "Technology"; + if (/\b(security|vulnerability|authentication|authorization|encryption|threat|attack|breach|protection|privacy)\b/.test(text)) return "Security"; + if (/\b(performance|speed|latency|throughput|scalability|optimization|benchmark|efficiency|load|cache)\b/.test(text)) return "Performance"; + if (/\b(business|market|finance|revenue|profit|strategy|customer|sales|marketing|invest|startup|company|growth)\b/.test(text)) return "Business"; + if (/\b(education|learn|student|course|study|teach|school|university|training|curriculum|academic|certification)\b/.test(text)) return "Education"; + + return "General"; +} + +function detectDomain(query: string): "food" | "technology" | "health" | "business" | "education" | "general" { + const q = query.toLowerCase(); + if (/\b(pizza|food|cook|recipe|tasty|flavor|ingredient|dish|meal|cuisine|bake|grill|restaurant|chef|spice|taste)\b/.test(q)) return "food"; + if (/\b(software|api|code|programming|developer|app|web|cloud|database|server|framework|language|tech|computer|ai|ml|algorithm|security|performance)\b/.test(q)) return "technology"; + if (/\b(health|medical|disease|treatment|medicine|doctor|fitness|exercise|nutrition|wellness|mental|therapy|symptom|diagnosis)\b/.test(q)) return "health"; + if (/\b(business|market|finance|invest|startup|company|revenue|profit|strategy|customer|sales|marketing|economy|stock|entrepreneur)\b/.test(q)) return "business"; + if (/\b(education|learn|student|school|university|course|teach|study|academic|degree|classroom|curriculum|training)\b/.test(q)) return "education"; + return "general"; +} + +function generateFallbackSubtopics(query: string, maxSubtopics: number): Array<{ id: string; title: string; description: string; priority: number }> { + const words = query.split(/\s+/).filter((w) => w.length > 2 && !/^(what|why|how|when|where|who|is|are|does|do|the|a|an|to|of|in|on|at|for|with|about|makes|make)$/i.test(w)); + const mainTopic = words.slice(0, 3).join(" ") || query.replace(/\?$/, "").trim(); + const domain = detectDomain(query); + + const domainTemplates: Record> = { + food: [ + { title: `Key Ingredients and Components of ${mainTopic}`, description: `Essential elements that define and enhance ${mainTopic}` }, + { title: `Preparation Techniques and Methods`, description: `How different cooking and preparation methods affect the outcome` }, + { title: `Regional and Cultural Variations`, description: `How ${mainTopic} differs across cuisines and cultures` }, + { title: `Science Behind the Flavor`, description: `Chemical and sensory factors that create the distinctive taste` }, + { title: `Expert Tips and Recommendations`, description: `Professional advice for achieving the best results` }, + { title: `Common Mistakes to Avoid`, description: `Pitfalls that reduce quality and how to prevent them` }, + { title: `Comparisons and Alternatives`, description: `How ${mainTopic} compares to similar options` }, + ], + technology: [ + { title: `Overview of ${mainTopic}`, description: `Introduction and core concepts related to ${mainTopic}` }, + { title: `Best Practices`, description: `Recommended approaches and industry standards` }, + { title: `Common Challenges`, description: `Known issues and limitations` }, + { title: `Performance Considerations`, description: `Optimization techniques and benchmarks` }, + { title: `Security Implications`, description: `Security concerns and mitigation strategies` }, + { title: `Integration Patterns`, description: `How to integrate with existing systems` }, + { title: `Future Trends`, description: `Emerging developments and predictions` }, + ], + health: [ + { title: `Overview of ${mainTopic}`, description: `Key facts and background about ${mainTopic}` }, + { title: `Causes and Risk Factors`, description: `What contributes to or increases the likelihood of ${mainTopic}` }, + { title: `Symptoms and Diagnosis`, description: `How to recognize and identify ${mainTopic}` }, + { title: `Treatment and Management`, description: `Approaches to address or improve ${mainTopic}` }, + { title: `Prevention Strategies`, description: `Steps to reduce risk or avoid ${mainTopic}` }, + { title: `Latest Research and Findings`, description: `Recent studies and emerging evidence` }, + { title: `Expert Recommendations`, description: `Guidelines from health professionals` }, + ], + business: [ + { title: `Market Overview of ${mainTopic}`, description: `Current landscape and key players in ${mainTopic}` }, + { title: `Strategies and Best Practices`, description: `Proven approaches for success` }, + { title: `Challenges and Risks`, description: `Obstacles and potential downsides` }, + { title: `Financial Considerations`, description: `Costs, revenue models, and ROI factors` }, + { title: `Competitive Analysis`, description: `How different approaches compare` }, + { title: `Implementation Steps`, description: `Practical guide to execution` }, + { title: `Future Outlook`, description: `Predictions and emerging opportunities` }, + ], + education: [ + { title: `Overview of ${mainTopic}`, description: `Key concepts and fundamentals` }, + { title: `Learning Methods and Approaches`, description: `Different ways to study and master ${mainTopic}` }, + { title: `Challenges and Barriers`, description: `Common difficulties learners face` }, + { title: `Tools and Resources`, description: `Platforms, materials, and aids for learning` }, + { title: `Assessment and Measurement`, description: `How to evaluate progress and outcomes` }, + { title: `Expert Recommendations`, description: `Advice from educators and professionals` }, + { title: `Future Trends`, description: `How ${mainTopic} is evolving in education` }, + ], + general: [ + { title: `Overview of ${mainTopic}`, description: `Introduction and key concepts related to ${mainTopic}` }, + { title: `Key Factors and Influences`, description: `Important elements that shape ${mainTopic}` }, + { title: `Common Challenges`, description: `Difficulties and obstacles related to ${mainTopic}` }, + { title: `Best Practices and Recommendations`, description: `Expert advice and proven approaches` }, + { title: `Comparisons and Alternatives`, description: `How ${mainTopic} compares to related topics` }, + { title: `Practical Applications`, description: `Real-world uses and implementations` }, + { title: `Future Developments`, description: `Emerging trends and predictions` }, + ], + }; + + const templates = domainTemplates[domain] || domainTemplates.general; + + return templates.slice(0, maxSubtopics).map((t, i) => ({ + id: `subtopic-${i + 1}`, + title: t.title, + description: t.description, + priority: i + 1, + })); +} diff --git a/server/src/services/research-progress.ts b/server/src/services/research-progress.ts new file mode 100644 index 00000000000..9eed50bf17c --- /dev/null +++ b/server/src/services/research-progress.ts @@ -0,0 +1,208 @@ +/** + * Research Progress Service + * + * Tracks session progress and publishes live events for real-time UI updates. + */ +import type { Db } from "@paperclipai/db"; +import { researchSessions, researchTasks } from "@paperclipai/db"; +import { and, eq, count } from "drizzle-orm"; +import { publishLiveEvent } from "./live-events.js"; + +export interface ProgressState { + sessionId: string; + companyId: string; + totalTasks: number; + completedTasks: number; + currentTaskTitle?: string; + status: string; + message: string; +} + +export function researchProgressService(db: Db) { + return { + /** + * Update session progress percentage based on completed tasks. + */ + async updateProgress(sessionId: string, companyId: string): Promise { + const [taskStats] = await db + .select({ total: count(), completed: count() }) + .from(researchTasks) + .where(eq(researchTasks.sessionId, sessionId)); + + // Count completed tasks separately + const [completedResult] = await db + .select({ count: count() }) + .from(researchTasks) + .where( + and( + eq(researchTasks.sessionId, sessionId), + eq(researchTasks.status, "completed") + ) + ); + + const total = Number(taskStats?.total ?? 0); + const completed = Number(completedResult?.count ?? 0); + const percent = total > 0 ? Math.round((completed / total) * 100) : 0; + + await db + .update(researchSessions) + .set({ progressPercent: percent, updatedAt: new Date() }) + .where( + and( + eq(researchSessions.id, sessionId), + eq(researchSessions.companyId, companyId) + ) + ); + + return percent; + }, + + /** + * Publish a live event for research session status changes. + */ + publishSessionUpdate( + companyId: string, + sessionId: string, + status: string, + payload?: Record + ) { + publishLiveEvent({ + companyId, + type: "research.session.status", + payload: { + sessionId, + status, + ...payload, + }, + }); + }, + + /** + * Publish a live event when a task is updated. + */ + publishTaskUpdate( + companyId: string, + sessionId: string, + taskId: string, + status: string, + payload?: Record + ) { + publishLiveEvent({ + companyId, + type: "research.task.updated", + payload: { + sessionId, + taskId, + status, + ...payload, + }, + }); + }, + + /** + * Publish a live event when a finding is created. + */ + publishFindingCreated( + companyId: string, + sessionId: string, + findingId: string, + payload?: Record + ) { + publishLiveEvent({ + companyId, + type: "research.finding.created", + payload: { + sessionId, + findingId, + ...payload, + }, + }); + }, + + /** + * Publish a live event when a source is being processed. + */ + publishSourceProcessing( + companyId: string, + sessionId: string, + taskId: string, + sourceUrl: string, + sourceTitle: string, + payload?: Record + ) { + publishLiveEvent({ + companyId, + type: "research.source.processing", + payload: { + sessionId, + taskId, + sourceUrl, + sourceTitle, + ...payload, + }, + }); + }, + + /** + * Publish a live event with current finding count during task execution. + */ + publishFindingProgress( + companyId: string, + sessionId: string, + taskId: string, + findingsCount: number, + totalSources: number, + payload?: Record + ) { + publishLiveEvent({ + companyId, + type: "research.finding.progress", + payload: { + sessionId, + taskId, + findingsCount, + totalSources, + ...payload, + }, + }); + }, + + /** + * Get current progress state for a session. + */ + async getProgressState( + sessionId: string, + companyId: string + ): Promise { + const [session] = await db + .select() + .from(researchSessions) + .where( + and( + eq(researchSessions.id, sessionId), + eq(researchSessions.companyId, companyId) + ) + ) + .limit(1); + + const tasks = await db + .select() + .from(researchTasks) + .where(eq(researchTasks.sessionId, sessionId)) + .orderBy(researchTasks.sequenceOrder); + + const completedTasks = tasks.filter((t) => t.status === "completed").length; + const currentTask = tasks.find((t) => t.status === "running"); + + return { + sessionId, + companyId, + totalTasks: tasks.length, + completedTasks, + currentTaskTitle: currentTask?.title, + status: session?.status ?? "unknown", + message: `${completedTasks}/${tasks.length} tasks completed`, + }; + }, + }; +} diff --git a/server/src/services/research-search.ts b/server/src/services/research-search.ts new file mode 100644 index 00000000000..d94ea28b262 --- /dev/null +++ b/server/src/services/research-search.ts @@ -0,0 +1,945 @@ +/** + * Research Search Service + * + * Provides web search capabilities for the research engine. + * Supports two providers: + * - Mock: Returns synthetic results (default when no API key) + * - Serper: Real web search via Serper.dev API + */ + +const SERPER_API_URL = "https://google.serper.dev/search"; +const SEARCH_TIMEOUT_MS = 15_000; + +export interface SearchResult { + title: string; + url: string; + snippet: string; + domain: string; + qualityScore?: number; +} + +export interface SearchProvider { + search(query: string, maxResults: number): Promise; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Source Quality Scoring +// ───────────────────────────────────────────────────────────────────────────── + +interface DomainTier { + tier: 1 | 2 | 3 | 4 | 5; + label: string; +} + +const HIGH_QUALITY_DOMAINS = new Set([ + // Academic + "edu", "ac.uk", "arxiv.org", "semanticscholar.org", "pubmed.ncbi.nlm.nih.gov", + // Government + "gov", "gc.ca", "europa.eu", + // Major publications + "nature.com", "science.org", "ieee.org", "acm.org", "springer.com", + "nejm.org", "thelancet.com", "bmj.com", "jamanetwork.com", + // Tech reference + "docs.microsoft.com", "learn.microsoft.com", "developer.mozilla.org", + "docs.python.org", "docs.oracle.com", "docs.aws.amazon.com", + "cloud.google.com", "kubernetes.io", "docker.com", + // News / reputable + "reuters.com", "apnews.com", "bloomberg.com", "economist.com", + "ft.com", "wsj.com", "nytimes.com", "washingtonpost.com", + "bbc.com", "theguardian.com", + // Reference + "wikipedia.org", "britannica.com", "mayoclinic.org", "who.int", + // Industry + "github.com", "stackoverflow.com", "medium.com", "substack.com", +]); + +const MEDIUM_QUALITY_PATTERNS = [ + // Known blog platforms with editorial oversight + /\b(techcrunch\.com|theverge\.com|wired\.com|arstechnica\.com|engadget\.com)\b/, + /\b(forbes\.com|harvard\.edu|mit\.edu|stanford\.edu)\b/, + /\b(nih\.gov|cdc\.gov|fda\.gov|epa\.gov)\b/, +]; + +const LOW_QUALITY_PATTERNS = [ + // Forums, Q&A, social + /\b(reddit\.com|quora\.com|facebook\.com|twitter\.com|x\.com|linkedin\.com)\b/, + /\b(youtube\.com|tiktok\.com|instagram\.com)\b/, + // Generic content farms + /\b(wikihow\.com|ezinearticles\.com|hubpages\.com)\b/, +]; + +function getDomainTier(domain: string): DomainTier { + const lower = domain.toLowerCase(); + + if (HIGH_QUALITY_DOMAINS.has(lower)) { + return { tier: 1, label: "excellent" }; + } + + for (const pattern of MEDIUM_QUALITY_PATTERNS) { + if (pattern.test(lower)) { + return { tier: 2, label: "good" }; + } + } + + for (const pattern of LOW_QUALITY_PATTERNS) { + if (pattern.test(lower)) { + return { tier: 4, label: "low" }; + } + } + + // Default: unknown but not explicitly bad + return { tier: 3, label: "average" }; +} + +export function scoreSourceQuality(result: SearchResult): number { + const domainTier = getDomainTier(result.domain); + + // Base score from domain tier (1-5 scale inverted to 100-20) + let score = 100 - (domainTier.tier - 1) * 20; + + // Bonus for content richness + const snippetLength = result.snippet?.length ?? 0; + if (snippetLength > 300) score += 5; + if (snippetLength > 500) score += 5; + + // Penalty for very short snippets + if (snippetLength < 50) score -= 10; + + // Penalty for generic titles + const title = result.title?.toLowerCase() ?? ""; + if (title.includes("untitled") || title.includes("404") || title.includes("error")) { + score -= 20; + } + + // Clamp to 0-100 + return Math.max(0, Math.min(100, score)); +} + +export function filterSourcesByQuality( + results: SearchResult[], + minScore: number = 40 +): SearchResult[] { + const scored = results.map((r) => ({ + ...r, + qualityScore: scoreSourceQuality(r), + })); + + // Sort by quality descending + scored.sort((a, b) => (b.qualityScore ?? 0) - (a.qualityScore ?? 0)); + + // Filter out low-quality + return scored.filter((r) => (r.qualityScore ?? 0) >= minScore); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Mock Search Provider (default, no API key required) +// ───────────────────────────────────────────────────────────────────────────── + +export class MockSearchProvider implements SearchProvider { + async search(query: string, maxResults: number): Promise { + const domain = detectSearchDomain(query); + const results = generateMockSearchResults(query, domain); + + // Deterministic shuffle based on query to vary results per query + const shuffled = [...results].sort((a, b) => { + const hashA = hashString(query + a.domain); + const hashB = hashString(query + b.domain); + return hashA - hashB; + }); + + return shuffled.slice(0, Math.min(maxResults, results.length)); + } +} + +function detectSearchDomain(query: string): "food" | "technology" | "health" | "business" | "education" | "general" { + const q = query.toLowerCase(); + if (/\b(pizza|food|cook|recipe|tasty|flavor|ingredient|dish|meal|cuisine|bake|grill|restaurant|chef|spice|taste|bread|dough|sauce|cheese|meat|vegetable|fruit|wine|drink|beverage|dessert|sweet|savory|grill|roast|fry|boil|steam|season|marinate|serve|plate|dining)\b/.test(q)) return "food"; + if (/\b(software|api|code|programming|developer|app|web|cloud|database|server|framework|language|tech|computer|ai|ml|algorithm|security|performance|hardware|network|devops|frontend|backend|fullstack|git|docker|kubernetes|javascript|python|typescript|react|node)\b/.test(q)) return "technology"; + if (/\b(health|medical|disease|treatment|medicine|doctor|fitness|exercise|nutrition|wellness|mental|therapy|symptom|diagnosis|patient|hospital|clinic|vaccine|immune|chronic|acute|prevention|lifestyle|diet|workout|gym|yoga|meditation)\b/.test(q)) return "health"; + if (/\b(business|market|finance|invest|startup|company|revenue|profit|strategy|customer|sales|marketing|economy|stock|entrepreneur|management|leadership|operations|supply|demand|growth|scaling|funding|venture|capital|ipo|merger|acquisition)\b/.test(q)) return "business"; + if (/\b(education|learn|student|school|university|course|teach|study|academic|degree|classroom|curriculum|training|certification|diploma|scholarship|tuition|lecture|seminar|workshop|e-learning|mooc|textbook|exam|grade|enrollment)\b/.test(q)) return "education"; + return "general"; +} + +function getDomainSearchTemplates(mainTopic: string, domain: string): SearchResult[] { + const slug = mainTopic.replace(/\s+/g, "-"); + + const foodTemplates: SearchResult[] = [ + { + title: `${mainTopic} - Essential Guide`, + url: `https://foodguide.com/${slug}`, + snippet: `A comprehensive guide to ${mainTopic}. Covers key ingredients, preparation methods, and expert tips for achieving the best flavor and texture.`, + domain: "foodguide.com", + }, + { + title: `The Science Behind ${mainTopic}`, + url: `https://culinaryscience.org/${slug}-science`, + snippet: `Explore the chemistry and sensory science that makes ${mainTopic} special. From Maillard reactions to flavor compounds, understand what happens at the molecular level.`, + domain: "culinaryscience.org", + }, + { + title: `${mainTopic} Recipes and Variations`, + url: `https://recipes.com/${slug}-variations`, + snippet: `Discover popular recipes and regional variations of ${mainTopic}. Includes step-by-step instructions, ingredient substitutions, and serving suggestions.`, + domain: "recipes.com", + }, + { + title: `Expert Tips for Perfect ${mainTopic}`, + url: `https://chefsecrets.com/${slug}-tips`, + snippet: `Professional chefs share their secrets for making exceptional ${mainTopic}. Learn about temperature control, timing, seasoning, and presentation techniques.`, + domain: "chefsecrets.com", + }, + { + title: `${mainTopic} - Common Mistakes to Avoid`, + url: `https://kitchenfixes.com/${slug}-mistakes`, + snippet: `Avoid the most common pitfalls when preparing ${mainTopic}. Troubleshooting guide for texture, flavor, and appearance issues with practical solutions.`, + domain: "kitchenfixes.com", + }, + { + title: `Regional Styles of ${mainTopic}`, + url: `https://worldcuisine.com/${slug}-regional`, + snippet: `How ${mainTopic} differs across cultures and regions. From traditional methods to modern interpretations, explore the diversity of approaches worldwide.`, + domain: "worldcuisine.com", + }, + { + title: `${mainTopic} Ingredient Deep Dive`, + url: `https://ingredients.com/${slug}-components`, + snippet: `Understanding the role of each ingredient in ${mainTopic}. Quality indicators, sourcing tips, and how substitutions affect the final result.`, + domain: "ingredients.com", + }, + { + title: `Community Discussions About ${mainTopic}`, + url: `https://foodforum.com/t/${slug}`, + snippet: `Home cooks and food enthusiasts share experiences, favorite techniques, and personal recommendations for ${mainTopic}.`, + domain: "foodforum.com", + }, + ]; + + const techTemplates: SearchResult[] = [ + { + title: `${mainTopic} - Official Documentation`, + url: `https://docs.example.com/${slug}`, + snippet: `Comprehensive documentation covering ${mainTopic}. Includes getting started guides, API references, and best practices for implementation.`, + domain: "docs.example.com", + }, + { + title: `Understanding ${mainTopic}: A Deep Dive`, + url: `https://blog.techinsights.com/${slug}-deep-dive`, + snippet: `This article explores ${mainTopic} in detail, covering architecture, design patterns, and real-world use cases from production systems.`, + domain: "blog.techinsights.com", + }, + { + title: `${mainTopic} Best Practices Guide`, + url: `https://developer.guide.com/${slug}-best-practices`, + snippet: `Learn the recommended patterns and anti-patterns for ${mainTopic}. Includes performance tips, security considerations, and common pitfalls to avoid.`, + domain: "developer.guide.com", + }, + { + title: `Getting Started with ${mainTopic}`, + url: `https://tutorial.dev/${slug}-tutorial`, + snippet: `Step-by-step tutorial for beginners. Covers installation, configuration, and your first ${mainTopic} implementation with code examples.`, + domain: "tutorial.dev", + }, + { + title: `${mainTopic} Performance Benchmarks`, + url: `https://benchmarks.io/${slug}-performance`, + snippet: `Detailed performance analysis comparing different approaches to ${mainTopic}. Includes latency metrics, throughput tests, and scalability results.`, + domain: "benchmarks.io", + }, + { + title: `Advanced ${mainTopic} Techniques`, + url: `https://advanced.dev/${slug}-advanced`, + snippet: `Explore advanced concepts in ${mainTopic} including optimization strategies, edge cases, and expert-level implementation patterns.`, + domain: "advanced.dev", + }, + { + title: `${mainTopic} Security Considerations`, + url: `https://security.dev/${slug}-security`, + snippet: `Security best practices for ${mainTopic}. Covers authentication, authorization, input validation, and common vulnerability mitigations.`, + domain: "security.dev", + }, + { + title: `${mainTopic} Community Discussion`, + url: `https://forum.dev/t/${slug}`, + snippet: `Community discussion thread about ${mainTopic}. Developers share experiences, solutions to common problems, and recommendations.`, + domain: "forum.dev", + }, + ]; + + const healthTemplates: SearchResult[] = [ + { + title: `${mainTopic} - Medical Overview`, + url: `https://healthinfo.org/${slug}`, + snippet: `Comprehensive medical overview of ${mainTopic}. Covers causes, symptoms, diagnosis methods, and treatment options based on current clinical guidelines.`, + domain: "healthinfo.org", + }, + { + title: `Understanding ${mainTopic}: Patient Guide`, + url: `https://patientguide.com/${slug}-guide`, + snippet: `A patient-friendly explanation of ${mainTopic}. Learn what to expect, questions to ask your doctor, and how to manage your condition effectively.`, + domain: "patientguide.com", + }, + { + title: `${mainTopic} Prevention and Risk Reduction`, + url: `https://prevention.org/${slug}-prevention`, + snippet: `Evidence-based strategies for preventing or reducing risk of ${mainTopic}. Lifestyle changes, screenings, and early intervention approaches.`, + domain: "prevention.org", + }, + { + title: `Latest Research on ${mainTopic}`, + url: `https://medicaljournal.com/${slug}-research`, + snippet: `Recent clinical studies and research findings about ${mainTopic}. Summaries of peer-reviewed papers and emerging treatment protocols.`, + domain: "medicaljournal.com", + }, + { + title: `${mainTopic} Treatment Options`, + url: `https://treatmentguide.com/${slug}-treatments`, + snippet: `Overview of available treatments for ${mainTopic}. Compares effectiveness, side effects, and suitability for different patient profiles.`, + domain: "treatmentguide.com", + }, + { + title: `Living with ${mainTopic}`, + url: `https://wellnesslife.com/${slug}-living`, + snippet: `Practical advice for daily life with ${mainTopic}. Diet, exercise, mental health, and support resources for patients and caregivers.`, + domain: "wellnesslife.com", + }, + { + title: `${mainTopic} Expert Recommendations`, + url: `https://experthealth.com/${slug}-guidelines`, + snippet: `Professional medical guidelines and expert consensus on managing ${mainTopic}. Includes screening schedules and treatment protocols.`, + domain: "experthealth.com", + }, + { + title: `Community Support for ${mainTopic}`, + url: `https://healthforum.com/t/${slug}`, + snippet: `Patients and caregivers share experiences, coping strategies, and emotional support related to ${mainTopic}.`, + domain: "healthforum.com", + }, + ]; + + const businessTemplates: SearchResult[] = [ + { + title: `${mainTopic} - Market Analysis`, + url: `https://marketwatch.com/${slug}`, + snippet: `In-depth market analysis of ${mainTopic}. Covers market size, growth trends, key players, and competitive landscape.`, + domain: "marketwatch.com", + }, + { + title: `Strategies for ${mainTopic} Success`, + url: `https://businessstrategy.com/${slug}-strategies`, + snippet: `Proven strategies and frameworks for succeeding with ${mainTopic}. Case studies from industry leaders and actionable recommendations.`, + domain: "businessstrategy.com", + }, + { + title: `${mainTopic} Financial Considerations`, + url: `https://financeguide.com/${slug}-finance`, + snippet: `Cost analysis, revenue models, and ROI considerations for ${mainTopic}. Includes budgeting templates and financial planning tools.`, + domain: "financeguide.com", + }, + { + title: `Challenges and Risks in ${mainTopic}`, + url: `https://riskanalysis.com/${slug}-risks`, + snippet: `Common obstacles and risk factors associated with ${mainTopic}. Mitigation strategies and contingency planning guidance.`, + domain: "riskanalysis.com", + }, + { + title: `${mainTopic} Implementation Guide`, + url: `https://implementation.com/${slug}-guide`, + snippet: `Step-by-step guide to implementing ${mainTopic} in your organization. Project planning, resource allocation, and change management.`, + domain: "implementation.com", + }, + { + title: `${mainTopic} Competitive Landscape`, + url: `https://competitors.com/${slug}-competitors`, + snippet: `Analysis of key competitors and alternatives in the ${mainTopic} space. Strengths, weaknesses, and differentiation opportunities.`, + domain: "competitors.com", + }, + { + title: `Future of ${mainTopic}`, + url: `https://industrytrends.com/${slug}-future`, + snippet: `Emerging trends and future outlook for ${mainTopic}. Technology disruptions, regulatory changes, and market predictions.`, + domain: "industrytrends.com", + }, + { + title: `${mainTopic} Community Discussion`, + url: `https://businessforum.com/t/${slug}`, + snippet: `Business professionals discuss experiences, lessons learned, and advice related to ${mainTopic}.`, + domain: "businessforum.com", + }, + ]; + + const educationTemplates: SearchResult[] = [ + { + title: `${mainTopic} - Learning Guide`, + url: `https://learnguide.com/${slug}`, + snippet: `Comprehensive learning guide for ${mainTopic}. Covers fundamentals, key concepts, and structured learning paths for beginners to advanced learners.`, + domain: "learnguide.com", + }, + { + title: `Best Methods to Learn ${mainTopic}`, + url: `https://learningstyles.com/${slug}-methods`, + snippet: `Compare different approaches to mastering ${mainTopic}. Self-study, formal courses, project-based learning, and mentorship options.`, + domain: "learningstyles.com", + }, + { + title: `${mainTopic} Study Resources`, + url: `https://studyresources.com/${slug}-resources`, + snippet: `Curated list of books, online courses, videos, and practice materials for ${mainTopic}. Free and paid options with quality ratings.`, + domain: "studyresources.com", + }, + { + title: `Common Challenges Learning ${mainTopic}`, + url: `https://learningdifficulties.com/${slug}-challenges`, + snippet: `Why students struggle with ${mainTopic} and how to overcome common obstacles. Study techniques, time management, and motivation tips.`, + domain: "learningdifficulties.com", + }, + { + title: `${mainTopic} Assessment and Certification`, + url: `https://certifications.com/${slug}-certs`, + snippet: `Available certifications and assessment methods for ${mainTopic}. Exam preparation tips, costs, and career impact analysis.`, + domain: "certifications.com", + }, + { + title: `Teaching ${mainTopic} Effectively`, + url: `https://teacherguide.com/${slug}-teaching`, + snippet: `Pedagogical approaches and classroom strategies for teaching ${mainTopic}. Lesson plans, activities, and assessment rubrics.`, + domain: "teacherguide.com", + }, + { + title: `${mainTopic} Career Applications`, + url: `https://careerguide.com/${slug}-careers`, + snippet: `How ${mainTopic} skills translate to career opportunities. Job roles, salary ranges, and industry demand analysis.`, + domain: "careerguide.com", + }, + { + title: `Student Discussions About ${mainTopic}`, + url: `https://studentforum.com/t/${slug}`, + snippet: `Students share study tips, resource recommendations, and experiences learning ${mainTopic}.`, + domain: "studentforum.com", + }, + ]; + + const generalTemplates: SearchResult[] = [ + { + title: `${mainTopic} - Comprehensive Overview`, + url: `https://overview.com/${slug}`, + snippet: `A thorough introduction to ${mainTopic}. Covers key concepts, history, and why it matters in today's context.`, + domain: "overview.com", + }, + { + title: `Key Factors Influencing ${mainTopic}`, + url: `https://factors.com/${slug}-factors`, + snippet: `The most important elements that shape and affect ${mainTopic}. Analysis of causes, effects, and interconnections.`, + domain: "factors.com", + }, + { + title: `${mainTopic} Best Practices`, + url: `https://bestpractices.com/${slug}`, + snippet: `Expert-recommended approaches and proven methods for ${mainTopic}. Practical advice based on experience and research.`, + domain: "bestpractices.com", + }, + { + title: `Challenges and Solutions for ${mainTopic}`, + url: `https://solutions.com/${slug}-challenges`, + snippet: `Common difficulties related to ${mainTopic} and how to address them. Troubleshooting guide with actionable fixes.`, + domain: "solutions.com", + }, + { + title: `${mainTopic} in Practice`, + url: `https://practicalguide.com/${slug}-practice`, + snippet: `Real-world applications and examples of ${mainTopic}. Case studies, implementation stories, and lessons learned.`, + domain: "practicalguide.com", + }, + { + title: `Comparing Approaches to ${mainTopic}`, + url: `https://comparisons.com/${slug}-compare`, + snippet: `Side-by-side comparison of different methods and perspectives on ${mainTopic}. Pros, cons, and when to use each.`, + domain: "comparisons.com", + }, + { + title: `Future of ${mainTopic}`, + url: `https://futuretrends.com/${slug}-future`, + snippet: `Emerging developments and predictions about ${mainTopic}. What experts expect and how to prepare for changes.`, + domain: "futuretrends.com", + }, + { + title: `Community Discussion on ${mainTopic}`, + url: `https://generalforum.com/t/${slug}`, + snippet: `People share experiences, opinions, and advice about ${mainTopic}. Diverse perspectives and practical insights.`, + domain: "generalforum.com", + }, + ]; + + switch (domain) { + case "food": return foodTemplates; + case "technology": return techTemplates; + case "health": return healthTemplates; + case "business": return businessTemplates; + case "education": return educationTemplates; + default: return generalTemplates; + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// Query-Aware Mock Search Results +// ───────────────────────────────────────────────────────────────────────────── + +function generateMockSearchResults(query: string, domain: string): SearchResult[] { + const q = query.toLowerCase().trim(); + const slug = q.replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, ""); + + // Extract key nouns from query for topic-aware content + const stopWords = new Set(["what", "why", "how", "when", "where", "who", "is", "are", "does", "do", "the", "a", "an", "to", "of", "in", "on", "at", "for", "with", "about", "makes", "make", "it", "that", "this", "these", "those", "and", "or", "but", "be", "been", "being", "have", "has", "had", "will", "would", "could", "should", "may", "might", "can", "shall", "was", "were", "am", "get", "got", "go", "went", "come", "came", "take", "took", "give", "gave", "see", "saw", "know", "knew", "think", "thought", "say", "said", "tell", "told", "ask", "asked", "work", "worked", "try", "tried", "use", "used", "find", "found", "feel", "felt", "become", "became", "leave", "left", "put", "mean", "meant", "keep", "kept", "let", "begin", "began", "seem", "seemed", "help", "helped", "show", "showed", "hear", "heard", "play", "played", "run", "ran", "move", "moved", "live", "lived", "believe", "believed", "bring", "brought", "happen", "happened", "write", "wrote", "provide", "provided", "sit", "sat", "stand", "stood", "lose", "lost", "pay", "paid", "meet", "met", "include", "included", "continue", "continued", "set", "learn", "learned", "change", "changed", "lead", "led", "understand", "understood", "watch", "watched", "follow", "followed", "stop", "stopped", "create", "created", "speak", "spoke", "read", "allow", "allowed", "add", "added", "spend", "spent", "grow", "grew", "open", "opened", "walk", "walked", "win", "won", "offer", "offered", "remember", "remembered", "love", "loved", "consider", "considered", "appear", "appeared", "buy", "bought", "wait", "waited", "serve", "served", "die", "died", "send", "sent", "expect", "expected", "build", "built", "stay", "stayed", "fall", "fell", "cut", "reach", "reached", "kill", "killed", "remain", "remained", "suggest", "suggested", "raise", "raised", "pass", "passed", "sell", "sold", "require", "required", "report", "reported", "decide", "decided", "pull", "pulled"]); + const words = q.split(/\s+/).filter(w => w.length > 2 && !stopWords.has(w)); + const topic = words.slice(0, 3).join(" ") || q.replace(/\?/g, "").trim(); + const topicCapitalized = topic.split(" ").map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(" "); + + // Domain-specific knowledge base for realistic mock content + const domainKnowledge: Record> = { + food: { + "pizza": [ + "High-quality mozzarella with the right moisture content creates the signature stretch and creamy flavor that defines great pizza.", + "The Maillard reaction during baking at 450-500°F develops hundreds of flavor compounds that give pizza crust its complex, nutty taste.", + "San Marzano tomatoes grown in volcanic soil near Mount Vesuvius have lower acidity and sweeter flavor, making them ideal for pizza sauce.", + "Cold fermentation of dough for 24-72 hours develops gluten structure and creates subtle sourdough-like flavors from slow yeast activity.", + "The balance of umami from cheese, sweetness from tomatoes, and saltiness from cured meats creates the addictive flavor profile of pizza.", + "Wood-fired ovens at 700-900°F cook pizza in 90 seconds, creating leopard-spotted charring on the crust edge (cornicione) that adds bitter complexity.", + "Fresh basil added after baking preserves its volatile aromatic oils (linalool and eugenol) that would evaporate at high temperatures.", + "The hydration level of pizza dough (60-70% water to flour ratio) determines crust texture - higher hydration creates airy, open crumb structure.", + "Aged provolone and Parmigiano-Reggiano added to mozzarella deepens the savory flavor through glutamate concentration during aging.", + "Extra virgin olive oil drizzled before serving carries fat-soluble flavor compounds and adds peppery, fruity notes from polyphenols.", + ], + "default": [ + `The quality of ingredients is the foundation of exceptional ${topic}. Fresh, seasonal components provide the best flavor foundation.`, + `Temperature control during preparation significantly affects the final texture and taste of ${topic}. Precision matters at every stage.`, + `The interaction between ingredients creates complex flavor profiles that no single component can achieve alone in ${topic}.`, + `Traditional techniques passed down through generations often produce the most authentic and satisfying ${topic} experiences.`, + `The Maillard reaction and caramelization processes develop deep, rich flavors that distinguish outstanding ${topic} from average preparations.`, + `Balancing the five basic tastes - sweet, sour, salty, bitter, and umami - is essential for creating memorable ${topic}.`, + `Texture contrast within a single dish elevates the eating experience of ${topic}, combining crispy, creamy, and chewy elements.`, + `Regional variations of ${topic} reflect local ingredient availability, climate conditions, and cultural preferences developed over centuries.`, + ], + }, + technology: { + "default": [ + `Modern ${topic} implementations prioritize scalability and fault tolerance through distributed architecture patterns and redundancy strategies.`, + `Performance optimization for ${topic} requires profiling bottlenecks at multiple layers: network, compute, storage, and application logic.`, + `Security considerations for ${topic} include input validation, authentication, authorization, encryption in transit and at rest, and audit logging.`, + `The ecosystem around ${topic} includes libraries, frameworks, tools, and community resources that accelerate development and reduce boilerplate.`, + `Monitoring and observability are critical for production ${topic} systems, requiring metrics, logs, traces, and alerting strategies.`, + `Testing strategies for ${topic} should include unit tests, integration tests, end-to-end tests, and performance benchmarks in CI/CD pipelines.`, + `Documentation and API design significantly impact adoption and maintainability of ${topic} solutions in team environments.`, + `Cloud-native approaches to ${topic} leverage containerization, orchestration, service mesh, and serverless computing patterns.`, + ], + }, + health: { + "default": [ + `Clinical evidence supports multiple approaches to understanding and managing ${topic}, with treatment selection depending on individual patient factors.`, + `Prevention strategies for ${topic} include lifestyle modifications, regular screening, vaccination where applicable, and risk factor reduction.`, + `The pathophysiology of ${topic} involves complex interactions between genetic predisposition, environmental triggers, and immune system responses.`, + `Patient education and shared decision-making improve outcomes for ${topic} by increasing adherence to treatment plans and lifestyle recommendations.`, + `Recent research on ${topic} has identified novel biomarkers and therapeutic targets that may lead to more personalized treatment approaches.`, + `Multidisciplinary care teams provide comprehensive management of ${topic}, addressing medical, psychological, and social aspects of patient wellbeing.`, + `Early detection and intervention significantly improve prognosis for ${topic}, making awareness of warning symptoms critically important.`, + `Quality of life considerations are central to ${topic} management, balancing treatment efficacy with side effect burden and patient preferences.`, + ], + }, + business: { + "default": [ + `Market analysis of ${topic} reveals opportunities for differentiation through customer segmentation, value proposition refinement, and channel optimization.`, + `Successful ${topic} strategies require alignment between organizational capabilities, market demands, and competitive positioning.`, + `Financial modeling for ${topic} should account for fixed and variable costs, revenue streams, customer acquisition costs, and lifetime value metrics.`, + `Risk management in ${topic} involves identifying, assessing, and mitigating strategic, operational, financial, and compliance risks.`, + `Digital transformation initiatives increasingly shape ${topic} outcomes through data-driven decision making, automation, and customer experience enhancement.`, + `Stakeholder management is critical for ${topic} success, requiring clear communication, expectation setting, and relationship building across diverse groups.`, + `Scaling ${topic} operations demands attention to process standardization, talent acquisition, technology infrastructure, and organizational culture.`, + `Sustainability and ESG considerations are becoming integral to ${topic} strategy as investors, regulators, and consumers prioritize responsible business practices.`, + ], + }, + education: { + "default": [ + `Effective ${topic} instruction combines clear learning objectives, active engagement strategies, formative assessment, and timely feedback.`, + `Cognitive science research on ${topic} learning emphasizes spaced repetition, retrieval practice, elaboration, and interleaving of topics.`, + `Technology-enhanced ${topic} learning provides adaptive pathways, immediate feedback, multimedia resources, and collaboration opportunities.`, + `Assessment design for ${topic} should align with learning outcomes, using multiple measures including formative, summative, and performance-based evaluation.`, + `Motivation and self-regulation significantly impact ${topic} learning outcomes, requiring attention to goal-setting, self-efficacy, and metacognitive strategies.`, + `Differentiated instruction addresses diverse learner needs in ${topic} through varied content, process, product, and learning environment adjustments.`, + `Professional learning communities support ${topic} educators through collaborative planning, peer observation, and shared resource development.`, + `Transfer of ${topic} learning to real-world contexts requires intentional application opportunities and reflection on connections between theory and practice.`, + ], + }, + general: { + "default": [ + `Understanding ${topic} requires examining historical context, current developments, and emerging trends that shape its evolution.`, + `Key factors influencing ${topic} include technological change, social dynamics, economic conditions, regulatory frameworks, and cultural values.`, + `Best practices for ${topic} emerge from accumulated experience, empirical research, and iterative refinement across diverse contexts and applications.`, + `Common challenges in ${topic} include resource constraints, competing priorities, stakeholder alignment, and adapting to rapidly changing conditions.`, + `The interdisciplinary nature of ${topic} means insights from multiple fields contribute to comprehensive understanding and effective action.`, + `Measurement and evaluation frameworks help track progress in ${topic} by defining clear indicators, data collection methods, and analysis approaches.`, + `Collaboration and knowledge sharing accelerate progress in ${topic} by leveraging diverse expertise and avoiding duplication of effort.`, + `Future developments in ${topic} will likely be shaped by emerging technologies, shifting demographics, environmental considerations, and global connectivity.`, + ], + }, + }; + + // Get knowledge snippets for this topic + const knowledge = domainKnowledge[domain] || domainKnowledge.general; + const topicKey = Object.keys(knowledge).find(k => q.includes(k)) || "default"; + const snippets = knowledge[topicKey] || knowledge["default"]; + + // Generate varied titles based on query + const titleTemplates: Record = { + food: [ + `${topicCapitalized}: The Science of Flavor`, + `Why ${topicCapitalized} Tastes So Good`, + `Expert Secrets for Perfect ${topicCapitalized}`, + `The Chemistry Behind ${topicCapitalized}`, + `${topicCapitalized} - A Complete Guide`, + `Regional Variations of ${topicCapitalized}`, + `Common ${topicCapitalized} Mistakes to Avoid`, + `Premium Ingredients for ${topicCapitalized}`, + ], + technology: [ + `${topicCapitalized}: Architecture and Design`, + `Optimizing ${topicCapitalized} Performance`, + `${topicCapitalized} Security Best Practices`, + `Getting Started with ${topicCapitalized}`, + `Advanced ${topicCapitalized} Techniques`, + `${topicCapitalized} in Production Systems`, + `Scaling ${topicCapitalized} Applications`, + `The Future of ${topicCapitalized}`, + ], + health: [ + `${topicCapitalized}: Clinical Overview`, + `Understanding ${topicCapitalized} Risk Factors`, + `Treatment Approaches for ${topicCapitalized}`, + `Living Well with ${topicCapitalized}`, + `Latest Research on ${topicCapitalized}`, + `Prevention Strategies for ${topicCapitalized}`, + `Patient Guide to ${topicCapitalized}`, + `Expert Recommendations for ${topicCapitalized}`, + ], + business: [ + `${topicCapitalized} Market Analysis`, + `Strategies for ${topicCapitalized} Success`, + `Financial Planning for ${topicCapitalized}`, + `Risk Management in ${topicCapitalized}`, + `Implementing ${topicCapitalized} Solutions`, + `Competitive Landscape of ${topicCapitalized}`, + `${topicCapitalized} Growth Opportunities`, + `Future Trends in ${topicCapitalized}`, + ], + education: [ + `Learning ${topicCapitalized} Effectively`, + `${topicCapitalized} Study Strategies`, + `Teaching ${topicCapitalized} Best Practices`, + `Resources for Mastering ${topicCapitalized}`, + `Assessment Methods for ${topicCapitalized}`, + `Career Paths in ${topicCapitalized}`, + `Common Challenges in ${topicCapitalized}`, + `Future of ${topicCapitalized} Education`, + ], + general: [ + `${topicCapitalized}: A Comprehensive Overview`, + `Key Factors in ${topicCapitalized}`, + `Best Practices for ${topicCapitalized}`, + `Challenges and Solutions for ${topicCapitalized}`, + `${topicCapitalized} in Practice`, + `Comparing Approaches to ${topicCapitalized}`, + `The Future of ${topicCapitalized}`, + `Community Perspectives on ${topicCapitalized}`, + ], + }; + + const titles = titleTemplates[domain] || titleTemplates.general; + const domains: Record = { + food: ["seriouseats.com", "foodandwine.com", "bonappetit.com", "culinaryscience.org", "chefsteps.com", "tastingtable.com", "epicurious.com", "saveur.com"], + technology: ["docs.example.com", "blog.techinsights.com", "developer.guide.com", "tutorial.dev", "benchmarks.io", "advanced.dev", "security.dev", "forum.dev"], + health: ["healthinfo.org", "patientguide.com", "prevention.org", "medicaljournal.com", "treatmentguide.com", "wellnesslife.com", "experthealth.com", "healthforum.com"], + business: ["marketwatch.com", "businessstrategy.com", "financeguide.com", "riskanalysis.com", "implementation.com", "competitors.com", "industrytrends.com", "businessforum.com"], + education: ["learnguide.com", "learningstyles.com", "studyresources.com", "learningdifficulties.com", "certifications.com", "teacherguide.com", "careerguide.com", "studentforum.com"], + general: ["overview.com", "factors.com", "bestpractices.com", "solutions.com", "practicalguide.com", "comparisons.com", "futuretrends.com", "generalforum.com"], + }; + + const domainList = domains[domain] || domains.general; + + // Build results with topic-specific content + const results: SearchResult[] = []; + for (let i = 0; i < Math.min(titles.length, snippets.length); i++) { + const d = domainList[i % domainList.length]; + results.push({ + title: titles[i], + url: `https://${d}/${slug}-${i + 1}`, + snippet: snippets[i % snippets.length], + domain: d, + }); + } + + return results; +} + +function hashString(str: string): number { + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; + } + return Math.abs(hash); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Serper Search Provider (real web search) +// ───────────────────────────────────────────────────────────────────────────── + +export class SerperSearchProvider implements SearchProvider { + private apiKey: string; + + constructor(apiKey: string) { + this.apiKey = apiKey; + } + + async search(query: string, maxResults: number): Promise { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), SEARCH_TIMEOUT_MS); + + try { + const response = await fetch(SERPER_API_URL, { + method: "POST", + headers: { + "X-API-KEY": this.apiKey, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + q: query, + num: Math.min(maxResults, 10), + }), + signal: controller.signal, + }); + + if (!response.ok) { + const errorText = await response.text().catch(() => "unknown error"); + throw new Error(`Serper API error ${response.status}: ${errorText}`); + } + + const data = (await response.json()) as { + organic?: Array<{ + title: string; + link: string; + snippet: string; + }>; + }; + + return (data.organic || []).map((r) => { + const url = r.link || ""; + const domain = extractDomain(url); + return { + title: r.title || "Untitled", + url, + snippet: r.snippet || "", + domain, + }; + }); + } finally { + clearTimeout(timeout); + } + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// Semantic Scholar Search Provider (academic paper search) +// ───────────────────────────────────────────────────────────────────────────── + +const SEMANTIC_SCHOLAR_API_URL = "https://api.semanticscholar.org/graph/v1/paper/search"; +const SEMANTIC_SCHOLAR_TIMEOUT_MS = 15_000; + +export class SemanticScholarSearchProvider implements SearchProvider { + private apiKey?: string; + + constructor(apiKey?: string) { + this.apiKey = apiKey; + } + + async search(query: string, maxResults: number): Promise { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), SEMANTIC_SCHOLAR_TIMEOUT_MS); + + try { + const url = new URL(SEMANTIC_SCHOLAR_API_URL); + url.searchParams.set("query", query); + url.searchParams.set("limit", String(Math.min(maxResults, 100))); + url.searchParams.set("fields", "title,authors,year,abstract,venue,citationCount,paperId,openAccessPdf"); + + const headers: Record = { + Accept: "application/json", + }; + if (this.apiKey) { + headers["x-api-key"] = this.apiKey; + } + + const response = await fetch(url.toString(), { + headers, + signal: controller.signal, + }); + + if (!response.ok) { + const errorText = await response.text().catch(() => "unknown error"); + throw new Error(`Semantic Scholar API error ${response.status}: ${errorText}`); + } + + const data = (await response.json()) as { + data?: Array<{ + paperId: string; + title: string; + abstract: string | null; + authors?: Array<{ name: string }>; + year?: number; + venue?: string; + citationCount?: number; + openAccessPdf?: { url: string } | null; + }>; + }; + + return (data.data || []).map((paper) => { + const authorNames = paper.authors?.map((a) => a.name).slice(0, 3).join(", ") || "Unknown"; + const yearStr = paper.year ? ` (${paper.year})` : ""; + const venueStr = paper.venue ? ` — ${paper.venue}` : ""; + const citationStr = paper.citationCount ? ` [${paper.citationCount} citations]` : ""; + const abstract = paper.abstract || `${authorNames}${yearStr}${venueStr}${citationStr}`; + + return { + title: paper.title, + url: paper.openAccessPdf?.url || `https://www.semanticscholar.org/paper/${paper.paperId}`, + snippet: abstract.slice(0, 500), + domain: "semanticscholar.org", + }; + }); + } finally { + clearTimeout(timeout); + } + } +} + +const FETCH_TIMEOUT_MS = 10_000; +const MAX_CONTENT_LENGTH = 8_000; + +export async function fetchPageContent(url: string): Promise { + // Security: Validate URL before fetching + try { + const parsed = new URL(url); + // Block internal/private addresses + const hostname = parsed.hostname.toLowerCase(); + if ( + hostname === "localhost" || + hostname === "127.0.0.1" || + hostname.startsWith("192.168.") || + hostname.startsWith("10.") || + hostname.startsWith("172.") || + hostname.startsWith("0.") || + hostname.startsWith("[::]") || + hostname.startsWith("[::1]") || + hostname.startsWith("[fc00:") || + hostname.startsWith("[fe80:") + ) { + return null; + } + // Only allow http/https + if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { + return null; + } + } catch { + return null; + } + + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); + + try { + const response = await fetch(url, { + headers: { + "User-Agent": "Mozilla/5.0 (compatible; PaperclipResearch/1.0)", + Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + }, + signal: controller.signal, + redirect: "follow", + }); + + if (!response.ok) { + return null; + } + + const contentType = response.headers.get("content-type") || ""; + if (!contentType.includes("text/html") && !contentType.includes("text/plain")) { + return null; + } + + const html = await response.text(); + + // Basic HTML-to-text extraction + let text = html + // Remove script and style tags with content + .replace(/]*>[\s\S]*?<\/script>/gi, " ") + .replace(/]*>[\s\S]*?<\/style>/gi, " ") + // Remove nav, header, footer, aside + .replace(/<(nav|header|footer|aside)[^>]*>[\s\S]*?<\/\1>/gi, " ") + // Convert common block tags to newlines + .replace(/<\/(p|div|h[1-6]|li|tr|blockquote)>/gi, "\n") + .replace(/<(br)\s*\/?>/gi, "\n") + // Remove all remaining tags + .replace(/<[^>]+>/g, " ") + // Decode common HTML entities + .replace(/ /g, " ") + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, '"') + .replace(/'/g, "'") + // Collapse whitespace + .replace(/\s+/g, " ") + .trim(); + + // Limit length + if (text.length > MAX_CONTENT_LENGTH) { + text = text.slice(0, MAX_CONTENT_LENGTH) + "..."; + } + + return text; + } catch { + return null; + } finally { + clearTimeout(timeout); + } +} + +function extractDomain(url: string): string { + try { + const parsed = new URL(url); + return parsed.hostname.replace(/^www\./, ""); + } catch { + return "unknown"; + } +} + +export type SearchProviderType = "mock" | "serper" | "semantic-scholar"; + +// ───────────────────────────────────────────────────────────────────────────── +// Factory +// ───────────────────────────────────────────────────────────────────────────── + +export function createSearchProvider( + type: SearchProviderType, + apiKey?: string, +): SearchProvider { + switch (type) { + case "serper": + if (apiKey && apiKey.length > 0) { + return new SerperSearchProvider(apiKey); + } + throw new Error("Serper API key required when provider is 'serper'"); + case "semantic-scholar": + return new SemanticScholarSearchProvider(apiKey); + case "mock": + default: + return new MockSearchProvider(); + } +} diff --git a/server/src/services/research.ts b/server/src/services/research.ts new file mode 100644 index 00000000000..f0106ec74df --- /dev/null +++ b/server/src/services/research.ts @@ -0,0 +1,511 @@ +import { and, eq, sql, desc, count } from "drizzle-orm"; +import type { Db } from "@paperclipai/db"; +import { + researchSessions, + researchTasks, + researchFindings, + researchSources, + researchMemory, +} from "@paperclipai/db"; +import { notFound } from "../errors.js"; +import { logger } from "../middleware/logger.js"; +import type { + CreateResearchSession, + UpdateResearchSession, + CreateResearchTask, + UpdateResearchTask, + CreateResearchFinding, + CreateResearchMemory, +} from "@paperclipai/shared"; +import { researchEngine } from "./research-engine.js"; +import { generateResearchPlan } from "./research-llm.js"; +import type { Config } from "../config.js"; + +export function researchService(db: Db, config?: Config) { + const engine = config ? researchEngine({ db, config }) : null; + + return { + // ────────────────────────────────────────────────────────────────────────── + // Engine + // ────────────────────────────────────────────────────────────────────────── + + startSession: async (companyId: string, sessionId: string) => { + if (!engine) { + throw new Error("Research engine is not configured"); + } + if (!config?.researchEngineEnabled) { + throw new Error("Research engine is disabled"); + } + + // Verify session exists and is in planning state + const session = await db + .select() + .from(researchSessions) + .where(and(eq(researchSessions.id, sessionId), eq(researchSessions.companyId, companyId))) + .then((rows) => rows[0] ?? null); + + if (!session) throw notFound("Research session not found"); + if (session.status !== "planning") { + throw new Error(`Cannot start session from status: ${session.status}`); + } + + // Start execution in background (do not await) + void engine.executeSession(sessionId, companyId).catch((err) => { + logger.error({ sessionId, err }, "Research engine execution error"); + }); + + return { started: true, sessionId }; + }, + + cancelSession: async (companyId: string, sessionId: string) => { + if (!engine) { + throw new Error("Research engine is not configured"); + } + + const cancelled = await engine.requestCancel(sessionId, companyId); + return { cancelled, sessionId }; + }, + + resumeSession: async (companyId: string, sessionId: string) => { + if (!engine) { + throw new Error("Research engine is not configured"); + } + if (!config?.researchEngineEnabled) { + throw new Error("Research engine is disabled"); + } + + // Verify session exists and is in cancelled or failed state + const session = await db + .select() + .from(researchSessions) + .where(and(eq(researchSessions.id, sessionId), eq(researchSessions.companyId, companyId))) + .then((rows) => rows[0] ?? null); + + if (!session) throw notFound("Research session not found"); + if (session.status !== "cancelled" && session.status !== "failed" && session.status !== "cancelling") { + throw new Error(`Cannot resume session from status: ${session.status}`); + } + + // Resume execution in background (do not await) + void engine.resumeSession(sessionId, companyId).catch((err) => { + logger.error({ sessionId, err }, "Research engine resume error"); + }); + + return { resumed: true, sessionId }; + }, + + retryTask: async (companyId: string, sessionId: string, taskId: string) => { + if (!engine) { + throw new Error("Research engine is not configured"); + } + if (!config?.researchEngineEnabled) { + throw new Error("Research engine is disabled"); + } + + // Verify session exists + const session = await db + .select() + .from(researchSessions) + .where(and(eq(researchSessions.id, sessionId), eq(researchSessions.companyId, companyId))) + .then((rows) => rows[0] ?? null); + + if (!session) throw notFound("Research session not found"); + + // Retry task in background (do not await) + void engine.retryTask(taskId, sessionId, companyId).catch((err) => { + logger.error({ sessionId, taskId, err }, "Research engine retry task error"); + }); + + return { retried: true, taskId, sessionId }; + }, + + // ────────────────────────────────────────────────────────────────────────── + // Subtopic Generation + // ────────────────────────────────────────────────────────────────────────── + + generateSubtopics: async (data: { query: string; depth?: string; maxSubtopics?: number }) => { + const plan = await generateResearchPlan( + data.query, + data.maxSubtopics ?? 5, + data.depth ?? "medium", + { model: config?.researchLlmModel, apiKey: config?.researchLlmApiKey } + ); + return plan; + }, + + // ────────────────────────────────────────────────────────────────────────── + // Sessions + // ────────────────────────────────────────────────────────────────────────── + + createSession: async (companyId: string, actorId: string, data: CreateResearchSession) => { + const [session] = await db + .insert(researchSessions) + .values({ + companyId, + title: data.title, + query: data.query, + depth: data.depth ?? "medium", + maxSubtopics: data.maxSubtopics ?? 5, + createdBy: actorId, + plan: data.plan ? (data.plan as any) : null, + }) + .returning(); + return session; + }, + + listSessions: async (companyId: string, opts?: { status?: string; limit?: number; offset?: number }) => { + const limit = opts?.limit ?? 50; + const offset = opts?.offset ?? 0; + + const conditions = [eq(researchSessions.companyId, companyId)]; + if (opts?.status) { + conditions.push(eq(researchSessions.status, opts.status as any)); + } + + const [items, totalResult] = await Promise.all([ + db + .select() + .from(researchSessions) + .where(and(...conditions)) + .orderBy(desc(researchSessions.createdAt)) + .limit(limit) + .offset(offset), + db + .select({ count: count() }) + .from(researchSessions) + .where(and(...conditions)) + .then((rows) => Number(rows[0]?.count ?? 0)), + ]); + + return { items, total: totalResult, limit, offset }; + }, + + getSession: async (companyId: string, sessionId: string) => { + const session = await db + .select() + .from(researchSessions) + .where(and(eq(researchSessions.id, sessionId), eq(researchSessions.companyId, companyId))) + .then((rows) => rows[0] ?? null); + + if (!session) throw notFound("Research session not found"); + + const [tasks, findings, sources] = await Promise.all([ + db + .select() + .from(researchTasks) + .where(eq(researchTasks.sessionId, sessionId)) + .orderBy(researchTasks.sequenceOrder), + db + .select() + .from(researchFindings) + .where(eq(researchFindings.sessionId, sessionId)) + .orderBy(desc(researchFindings.createdAt)), + db + .select() + .from(researchSources) + .where(eq(researchSources.sessionId, sessionId)) + .orderBy(desc(researchSources.accessCount)), + ]); + + return { ...session, tasks, findings, sources }; + }, + + updateSession: async (companyId: string, sessionId: string, data: UpdateResearchSession) => { + // Fetch current session to check if we need to save original report + const currentSession = await db + .select() + .from(researchSessions) + .where(and(eq(researchSessions.id, sessionId), eq(researchSessions.companyId, companyId))) + .then((rows) => rows[0] ?? null); + + if (!currentSession) throw notFound("Research session not found"); + + const updateData: Record = { + updatedAt: new Date(), + }; + + if (data.title !== undefined) updateData.title = data.title; + if (data.query !== undefined) updateData.query = data.query; + if (data.status !== undefined) updateData.status = data.status; + if (data.depth !== undefined) updateData.depth = data.depth; + if (data.maxSubtopics !== undefined) updateData.maxSubtopics = data.maxSubtopics; + + // Handle report editing + if (data.report !== undefined) { + // If this is the first edit, save the original report + if (!currentSession.isEdited && currentSession.report && !currentSession.originalReport) { + updateData.originalReport = currentSession.report; + } + updateData.report = data.report; + updateData.isEdited = true; + } + + const [updated] = await db + .update(researchSessions) + .set(updateData) + .where(and(eq(researchSessions.id, sessionId), eq(researchSessions.companyId, companyId))) + .returning(); + + return updated; + }, + + deleteSession: async (companyId: string, sessionId: string) => { + const [deleted] = await db + .delete(researchSessions) + .where(and(eq(researchSessions.id, sessionId), eq(researchSessions.companyId, companyId))) + .returning(); + + if (!deleted) throw notFound("Research session not found"); + return deleted; + }, + + // ────────────────────────────────────────────────────────────────────────── + // Tasks + // ────────────────────────────────────────────────────────────────────────── + + createTask: async (companyId: string, sessionId: string, data: CreateResearchTask) => { + const [task] = await db + .insert(researchTasks) + .values({ + companyId, + sessionId, + title: data.title, + sequenceOrder: data.sequenceOrder ?? 0, + }) + .returning(); + return task; + }, + + listTasks: async (companyId: string, sessionId: string) => { + return db + .select() + .from(researchTasks) + .where(and(eq(researchTasks.sessionId, sessionId), eq(researchTasks.companyId, companyId))) + .orderBy(researchTasks.sequenceOrder); + }, + + getTask: async (companyId: string, taskId: string) => { + const task = await db + .select() + .from(researchTasks) + .where(and(eq(researchTasks.id, taskId), eq(researchTasks.companyId, companyId))) + .then((rows) => rows[0] ?? null); + + if (!task) throw notFound("Research task not found"); + return task; + }, + + updateTask: async (companyId: string, taskId: string, data: UpdateResearchTask) => { + const [updated] = await db + .update(researchTasks) + .set({ + ...(data.title !== undefined && { title: data.title }), + ...(data.status !== undefined && { status: data.status as any }), + ...(data.findingsSummary !== undefined && { findingsSummary: data.findingsSummary }), + ...(data.sources !== undefined && { sources: data.sources }), + ...(data.reliabilityScore !== undefined && { reliabilityScore: data.reliabilityScore }), + updatedAt: new Date(), + }) + .where(and(eq(researchTasks.id, taskId), eq(researchTasks.companyId, companyId))) + .returning(); + + if (!updated) throw notFound("Research task not found"); + return updated; + }, + + // ────────────────────────────────────────────────────────────────────────── + // Findings + // ────────────────────────────────────────────────────────────────────────── + + createFinding: async (companyId: string, data: CreateResearchFinding) => { + const [finding] = await db + .insert(researchFindings) + .values({ + companyId, + taskId: data.taskId, + sessionId: ( + await db + .select({ sessionId: researchTasks.sessionId }) + .from(researchTasks) + .where(eq(researchTasks.id, data.taskId)) + .then((rows) => rows[0]?.sessionId) + )!, + content: data.content, + sourceUrl: data.sourceUrl, + sourceTitle: data.sourceTitle, + sourceDomain: data.sourceDomain, + confidence: data.confidence as any, + reliabilityScore: data.reliabilityScore, + category: data.category, + metadata: data.metadata ?? {}, + }) + .returning(); + return finding; + }, + + listFindings: async (companyId: string, opts?: { sessionId?: string; taskId?: string; category?: string; limit?: number; offset?: number }) => { + const limit = Math.min(opts?.limit ?? 50, 100); + const offset = opts?.offset ?? 0; + const conditions = [eq(researchFindings.companyId, companyId)]; + if (opts?.sessionId) conditions.push(eq(researchFindings.sessionId, opts.sessionId)); + if (opts?.taskId) conditions.push(eq(researchFindings.taskId, opts.taskId)); + if (opts?.category) conditions.push(eq(researchFindings.category, opts.category)); + + const [items, totalResult] = await Promise.all([ + db + .select() + .from(researchFindings) + .where(and(...conditions)) + .orderBy(desc(researchFindings.createdAt)) + .limit(limit) + .offset(offset), + db + .select({ count: sql`count(*)` }) + .from(researchFindings) + .where(and(...conditions)) + .then((rows) => Number(rows[0]?.count ?? 0)), + ]); + + return { items, total: totalResult, limit, offset }; + }, + + markDuplicate: async (companyId: string, findingId: string, duplicateOfId: string) => { + const [updated] = await db + .update(researchFindings) + .set({ isDuplicate: true, duplicateOfId }) + .where(and(eq(researchFindings.id, findingId), eq(researchFindings.companyId, companyId))) + .returning(); + + if (!updated) throw notFound("Research finding not found"); + return updated; + }, + + // ────────────────────────────────────────────────────────────────────────── + // Sources + // ────────────────────────────────────────────────────────────────────────── + + getSources: async (companyId: string, sessionId: string) => { + return db + .select() + .from(researchSources) + .where(and(eq(researchSources.sessionId, sessionId), eq(researchSources.companyId, companyId))) + .orderBy(desc(researchSources.accessCount)); + }, + + // ────────────────────────────────────────────────────────────────────────── + // Memory + // ────────────────────────────────────────────────────────────────────────── + + getMemory: async (companyId: string, key?: string) => { + if (key) { + return db + .select() + .from(researchMemory) + .where(and(eq(researchMemory.companyId, companyId), eq(researchMemory.key, key))) + .then((rows) => rows[0] ?? null); + } + return db + .select() + .from(researchMemory) + .where(eq(researchMemory.companyId, companyId)) + .orderBy(desc(researchMemory.updatedAt)); + }, + + setMemory: async (companyId: string, data: CreateResearchMemory) => { + const existing = await db + .select() + .from(researchMemory) + .where(and(eq(researchMemory.companyId, companyId), eq(researchMemory.key, data.key))) + .then((rows) => rows[0] ?? null); + + if (existing) { + const [updated] = await db + .update(researchMemory) + .set({ + value: data.value as any, + sessionId: data.sessionId ?? existing.sessionId, + sourceFindingId: data.sourceFindingId ?? existing.sourceFindingId, + updatedAt: new Date(), + }) + .where(eq(researchMemory.id, existing.id)) + .returning(); + return updated; + } + + const [created] = await db + .insert(researchMemory) + .values({ + companyId, + key: data.key, + value: data.value as any, + sessionId: data.sessionId, + sourceFindingId: data.sourceFindingId, + }) + .returning(); + return created; + }, + + // ────────────────────────────────────────────────────────────────────────── + // Dashboard + // ────────────────────────────────────────────────────────────────────────── + + getDashboard: async (companyId: string) => { + const [sessionStats, taskStats, findingStats, sourceStats] = await Promise.all([ + db + .select({ status: researchSessions.status, count: sql`count(*)` }) + .from(researchSessions) + .where(eq(researchSessions.companyId, companyId)) + .groupBy(researchSessions.status), + db + .select({ status: researchTasks.status, count: sql`count(*)` }) + .from(researchTasks) + .where(eq(researchTasks.companyId, companyId)) + .groupBy(researchTasks.status), + db + .select({ + total: sql`count(*)`, + duplicates: sql`sum(case when ${researchFindings.isDuplicate} = true then 1 else 0 end)`, + avgReliability: sql`avg(${researchFindings.reliabilityScore})`, + }) + .from(researchFindings) + .where(eq(researchFindings.companyId, companyId)), + db + .select({ count: sql`count(*)` }) + .from(researchSources) + .where(eq(researchSources.companyId, companyId)) + .then((rows) => Number(rows[0]?.count ?? 0)), + ]); + + const sessionCounts: Record = {}; + for (const row of sessionStats) { + sessionCounts[row.status] = Number(row.count); + } + + const taskCounts: Record = {}; + for (const row of taskStats) { + taskCounts[row.status] = Number(row.count); + } + + return { + sessions: { + total: Object.values(sessionCounts).reduce((a, b) => a + b, 0), + byStatus: sessionCounts, + }, + tasks: { + total: Object.values(taskCounts).reduce((a, b) => a + b, 0), + byStatus: taskCounts, + }, + findings: { + total: Number(findingStats[0]?.total ?? 0), + duplicates: Number(findingStats[0]?.duplicates ?? 0), + avgReliability: findingStats[0]?.avgReliability + ? Number(Number(findingStats[0].avgReliability).toFixed(2)) + : null, + }, + sources: { + total: sourceStats, + }, + }; + }, + }; +} diff --git a/ui/package.json b/ui/package.json index 4c3acec4de1..061ff67acd1 100644 --- a/ui/package.json +++ b/ui/package.json @@ -63,21 +63,22 @@ "react-i18next": "^17.0.7", "react-markdown": "^10.1.0", "react-router-dom": "^7.1.5", + "recharts": "^3.8.1", "remark-gfm": "^4.0.1", "tailwind-merge": "^3.4.1" }, "devDependencies": { - "@tailwindcss/vite": "^4.0.7", "@storybook/addon-a11y": "10.3.5", "@storybook/addon-docs": "10.3.5", "@storybook/react-vite": "10.3.5", + "@tailwindcss/vite": "^4.0.7", "@types/node": "^25.2.3", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", + "storybook": "10.3.5", "tailwindcss": "^4.0.7", "typescript": "^5.7.3", - "storybook": "10.3.5", "vite": "^6.1.0", "vitest": "^3.0.5" } diff --git a/ui/src/App.tsx b/ui/src/App.tsx index f6d81f2bc9c..f1cc87b88d4 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -28,6 +28,9 @@ import { ApprovalDetail } from "./pages/ApprovalDetail"; import { Costs } from "./pages/Costs"; import { Activity } from "./pages/Activity"; import { Inbox } from "./pages/Inbox"; +import { ResearchDashboard } from "./pages/ResearchDashboard"; +import { ResearchSessions } from "./pages/ResearchSessions"; +import { ResearchSessionDetail } from "./pages/ResearchSessionDetail"; import { CompanySettings } from "./pages/CompanySettings"; import { CompanyEnvironments } from "./pages/CompanyEnvironments"; import { CloudUpstream } from "./pages/CloudUpstream"; @@ -51,6 +54,7 @@ import { AdapterManager } from "./pages/AdapterManager"; import { PluginPage } from "./pages/PluginPage"; import { OrgChart } from "./pages/OrgChart"; import { NewAgent } from "./pages/NewAgent"; +import { AgentAnalytics } from "./pages/AgentAnalytics"; import { AuthPage } from "./pages/Auth"; import { BoardClaimPage } from "./pages/BoardClaim"; import { CliAuthPage } from "./pages/CliAuth"; @@ -95,6 +99,7 @@ function boardRoutes() { } /> } /> } /> + } /> } /> } /> } /> @@ -126,6 +131,9 @@ function boardRoutes() { } /> } /> } /> + } /> + } /> + } /> } /> } /> } /> @@ -307,6 +315,8 @@ export function App() { } /> } /> } /> + } /> + } /> } /> } /> } /> diff --git a/ui/src/api/index.ts b/ui/src/api/index.ts index 7da3d5531d3..ffa1ac752f9 100644 --- a/ui/src/api/index.ts +++ b/ui/src/api/index.ts @@ -12,6 +12,7 @@ export { approvalsApi } from "./approvals"; export { costsApi } from "./costs"; export { activityApi } from "./activity"; export { dashboardApi } from "./dashboard"; +export { researchApi } from "./research"; export { heartbeatsApi } from "./heartbeats"; export { instanceSettingsApi } from "./instanceSettings"; export { sidebarBadgesApi } from "./sidebarBadges"; diff --git a/ui/src/api/research.ts b/ui/src/api/research.ts new file mode 100644 index 00000000000..6fe1c48acb6 --- /dev/null +++ b/ui/src/api/research.ts @@ -0,0 +1,145 @@ +import type { + ResearchSession, + ResearchSessionDetail, + ResearchTask, + ResearchFinding, + ResearchSource, + CreateResearchSessionRequest, + UpdateResearchSessionRequest, + CreateResearchTaskRequest, + UpdateResearchTaskRequest, + CreateResearchFindingRequest, + MarkDuplicateRequest, + CreateResearchMemoryRequest, + ResearchDashboardSummary, +} from "@paperclipai/shared"; +import { api } from "./client"; + +export interface ResearchDashboard { + sessions: { + total: number; + byStatus: Record; + }; + tasks: { + total: number; + byStatus: Record; + }; + findings: { + total: number; + duplicates: number; + avgReliability: number | null; + }; + sources: { + total: number; + }; +} + +export interface PaginatedFindings { + items: ResearchFinding[]; + total: number; + limit: number; + offset: number; +} + +export interface PaginatedSessions { + items: ResearchSession[]; + total: number; + limit: number; + offset: number; +} + +export interface PaginatedFindings { + items: ResearchFinding[]; + total: number; + limit: number; + offset: number; +} + +export const researchApi = { + // Dashboard + dashboard: (companyId: string) => + api.get(`/companies/${companyId}/research/dashboard`), + + // Sessions + listSessions: (companyId: string, params?: { status?: string; limit?: number; offset?: number }) => { + const searchParams = new URLSearchParams(); + if (params?.status) searchParams.set("status", params.status); + if (params?.limit) searchParams.set("limit", String(params.limit)); + if (params?.offset) searchParams.set("offset", String(params.offset)); + const qs = searchParams.toString(); + return api.get(`/companies/${companyId}/research/sessions${qs ? `?${qs}` : ""}`); + }, + + createSession: (companyId: string, data: CreateResearchSessionRequest) => + api.post(`/companies/${companyId}/research/sessions`, data), + + getSession: (companyId: string, sessionId: string) => + api.get( + `/companies/${companyId}/research/sessions/${sessionId}` + ), + + updateSession: (companyId: string, sessionId: string, data: UpdateResearchSessionRequest) => + api.patch(`/companies/${companyId}/research/sessions/${sessionId}`, data), + + deleteSession: (companyId: string, sessionId: string) => + api.delete(`/companies/${companyId}/research/sessions/${sessionId}`), + + startSession: (companyId: string, sessionId: string) => + api.post<{ started: boolean; sessionId: string }>(`/companies/${companyId}/research/sessions/${sessionId}/start`, {}), + + cancelSession: (companyId: string, sessionId: string) => + api.post<{ cancelled: boolean; sessionId: string }>(`/companies/${companyId}/research/sessions/${sessionId}/cancel`, {}), + + resumeSession: (companyId: string, sessionId: string) => + api.post<{ resumed: boolean; sessionId: string }>(`/companies/${companyId}/research/sessions/${sessionId}/resume`, {}), + + retryTask: (companyId: string, sessionId: string, taskId: string) => + api.post<{ retried: boolean; taskId: string; sessionId: string }>(`/companies/${companyId}/research/sessions/${sessionId}/tasks/${taskId}/retry`, {}), + + // Tasks + listTasks: (companyId: string, sessionId: string) => + api.get(`/companies/${companyId}/research/sessions/${sessionId}/tasks`), + + createTask: (companyId: string, sessionId: string, data: CreateResearchTaskRequest) => + api.post(`/companies/${companyId}/research/sessions/${sessionId}/tasks`, data), + + getTask: (companyId: string, taskId: string) => + api.get(`/companies/${companyId}/research/tasks/${taskId}`), + + updateTask: (companyId: string, taskId: string, data: UpdateResearchTaskRequest) => + api.patch(`/companies/${companyId}/research/tasks/${taskId}`, data), + + // Findings + listFindings: (companyId: string, taskId: string, params?: { limit?: number; offset?: number }) => { + const searchParams = new URLSearchParams(); + if (params?.limit) searchParams.set("limit", String(params.limit)); + if (params?.offset) searchParams.set("offset", String(params.offset)); + const qs = searchParams.toString(); + return api.get(`/companies/${companyId}/research/tasks/${taskId}/findings${qs ? `?${qs}` : ""}`); + }, + + createFinding: (companyId: string, data: CreateResearchFindingRequest) => + api.post(`/companies/${companyId}/research/findings`, data), + + markDuplicate: (companyId: string, findingId: string, data: MarkDuplicateRequest) => + api.post(`/companies/${companyId}/research/findings/${findingId}/mark-duplicate`, data), + + // Sources + listSources: (companyId: string, sessionId: string) => + api.get(`/companies/${companyId}/research/sessions/${sessionId}/sources`), + + // Engine (already defined above) + + // Subtopic Generation + generateSubtopics: (companyId: string, data: { query: string; depth?: string; maxSubtopics?: number }) => + api.post<{ strategy: string; subtopics: Array<{ id: string; title: string; description: string; priority: number }> }>(`/companies/${companyId}/research/generate-subtopics`, data), + + // Memory + getMemory: (companyId: string, key?: string) => { + const qs = key ? `?key=${encodeURIComponent(key)}` : ""; + return api.get(`/companies/${companyId}/research/memory${qs}`); + }, + + setMemory: (companyId: string, data: CreateResearchMemoryRequest) => + api.post(`/companies/${companyId}/research/memory`, data), +}; diff --git a/ui/src/components/CreateSessionDialog.tsx b/ui/src/components/CreateSessionDialog.tsx new file mode 100644 index 00000000000..c2320152474 --- /dev/null +++ b/ui/src/components/CreateSessionDialog.tsx @@ -0,0 +1,445 @@ +import { useState } from "react"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useCompany } from "../context/CompanyContext"; +import { useNavigate } from "@/lib/router"; +import { queryKeys } from "../lib/queryKeys"; +import { researchApi } from "../api/research"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { FlaskConical, Loader2, AlertTriangle, Sparkles, Plus, Trash2, Edit2, Check, X, ArrowLeft, ArrowRight, Wand2 } from "lucide-react"; +import type { ResearchDepth } from "@paperclipai/shared"; + +interface CreateSessionDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +type Step = "form" | "subtopics" | "creating"; + +interface Subtopic { + id: string; + title: string; + description: string; + priority: number; +} + +export function CreateSessionDialog({ open, onOpenChange }: CreateSessionDialogProps) { + const { selectedCompanyId } = useCompany(); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + + // Form state + const [title, setTitle] = useState(""); + const [query, setQuery] = useState(""); + const [depth, setDepth] = useState("medium"); + const [maxSubtopics, setMaxSubtopics] = useState(5); + const [error, setError] = useState(null); + + // Step state + const [step, setStep] = useState("form"); + const [subtopics, setSubtopics] = useState([]); + const [strategy, setStrategy] = useState(""); + const [generating, setGenerating] = useState(false); + const [generateError, setGenerateError] = useState(null); + + // Editing state + const [editingId, setEditingId] = useState(null); + const [editTitle, setEditTitle] = useState(""); + const [editDescription, setEditDescription] = useState(""); + + const createMutation = useMutation({ + mutationFn: () => + researchApi.createSession(selectedCompanyId!, { + title: title.trim(), + query: query.trim(), + depth, + maxSubtopics, + plan: subtopics.length > 0 ? { strategy, subtopics } : undefined, + }), + onSuccess: async (session) => { + queryClient.invalidateQueries({ queryKey: queryKeys.research.sessions(selectedCompanyId!) }); + queryClient.invalidateQueries({ queryKey: queryKeys.research.dashboard(selectedCompanyId!) }); + + // Auto-start the session + try { + await researchApi.startSession(selectedCompanyId!, session.id); + } catch (err) { + // Auto-start failed, but session is created - user can start manually + console.warn("Auto-start failed:", err); + } + + resetForm(); + onOpenChange(false); + navigate(`/research/sessions/${session.id}`); + }, + onError: (err: Error) => { + setError(err.message); + setStep("form"); + }, + }); + + const resetForm = () => { + setTitle(""); + setQuery(""); + setDepth("medium"); + setMaxSubtopics(5); + setError(null); + setStep("form"); + setSubtopics([]); + setStrategy(""); + setGenerateError(null); + setEditingId(null); + }; + + const handleClose = () => { + if (step === "creating") return; + resetForm(); + onOpenChange(false); + }; + + const handleGenerateSubtopics = async () => { + if (!query.trim()) { + setError("Please enter a research query first."); + return; + } + setGenerating(true); + setGenerateError(null); + setError(null); + try { + const result = await researchApi.generateSubtopics(selectedCompanyId!, { + query: query.trim(), + depth, + maxSubtopics, + }); + setStrategy(result.strategy); + setSubtopics(result.subtopics); + setStep("subtopics"); + } catch (err) { + const message = err instanceof Error ? err.message : "Failed to generate subtopics."; + setGenerateError(message); + } finally { + setGenerating(false); + } + }; + + const handleSkipSubtopics = () => { + setSubtopics([]); + setStrategy(""); + setStep("creating"); + createMutation.mutate(); + }; + + const handleConfirmSubtopics = () => { + setStep("creating"); + createMutation.mutate(); + }; + + const handleAddSubtopic = () => { + const newSubtopic: Subtopic = { + id: `subtopic-${Date.now()}`, + title: "New Subtopic", + description: "Description of what to research", + priority: subtopics.length + 1, + }; + setSubtopics([...subtopics, newSubtopic]); + startEdit(newSubtopic); + }; + + const handleRemoveSubtopic = (id: string) => { + const filtered = subtopics.filter((s) => s.id !== id); + // Re-prioritize + const reordered = filtered.map((s, idx) => ({ ...s, priority: idx + 1 })); + setSubtopics(reordered); + }; + + const startEdit = (subtopic: Subtopic) => { + setEditingId(subtopic.id); + setEditTitle(subtopic.title); + setEditDescription(subtopic.description); + }; + + const saveEdit = () => { + if (!editingId) return; + setSubtopics( + subtopics.map((s) => + s.id === editingId + ? { ...s, title: editTitle.trim() || s.title, description: editDescription.trim() || s.description } + : s + ) + ); + setEditingId(null); + }; + + const cancelEdit = () => { + setEditingId(null); + setEditTitle(""); + setEditDescription(""); + }; + + const isPending = createMutation.isPending || generating; + + return ( + + + + + + {step === "form" && "New Research Session"} + {step === "subtopics" && "Review Subtopics"} + {step === "creating" && "Creating Session..."} + + + {step === "form" && "Enter your research topic and generate subtopics."} + {step === "subtopics" && "Review, edit, or add subtopics for your research."} + {step === "creating" && "Creating your research session and starting research..."} + + + + {error && ( +
+

{error}

+
+ )} + + {generateError && ( +
+
+ +
+

Subtopic generation failed

+

{generateError}

+
+
+
+ )} + + {/* Step 1: Form */} + {step === "form" && ( +
{ + e.preventDefault(); + handleGenerateSubtopics(); + }} + className="space-y-4" + > +
+ + setTitle(e.target.value)} + placeholder="e.g., React Server Components Analysis" + maxLength={200} + disabled={isPending} + /> +
+ +
+ +