From 5890b318c4c547f1bb65da9d9c5819bb6502b04e Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 16 Mar 2026 18:27:20 -0500 Subject: [PATCH] Namespace company skill identities Persist canonical namespaced skill keys, split adapter runtime names from skill keys, and update portability/import flows to carry the canonical identity end-to-end. Co-Authored-By: Paperclip --- packages/adapter-utils/src/server-utils.ts | 24 +- packages/adapter-utils/src/types.ts | 3 +- .../claude-local/src/server/execute.ts | 4 +- .../claude-local/src/server/skills.ts | 20 +- .../codex-local/src/server/execute.ts | 18 +- .../adapters/codex-local/src/server/skills.ts | 39 +- .../cursor-local/src/server/execute.ts | 18 +- .../cursor-local/src/server/skills.ts | 39 +- .../gemini-local/src/server/execute.ts | 14 +- .../gemini-local/src/server/skills.ts | 39 +- .../opencode-local/src/server/execute.ts | 14 +- .../opencode-local/src/server/skills.ts | 39 +- .../adapters/pi-local/src/server/execute.ts | 14 +- .../adapters/pi-local/src/server/skills.ts | 39 +- .../db/src/migrations/0035_colorful_rhino.sql | 27 + .../db/src/migrations/meta/0035_snapshot.json | 9212 +++++++++++++++++ packages/db/src/migrations/meta/_journal.json | 7 + packages/db/src/schema/company_skills.ts | 3 +- packages/shared/src/types/adapter-skills.ts | 3 +- .../shared/src/types/company-portability.ts | 1 + packages/shared/src/types/company-skill.ts | 2 + .../shared/src/validators/adapter-skills.ts | 3 +- .../src/validators/company-portability.ts | 1 + .../shared/src/validators/company-skill.ts | 1 + .../__tests__/claude-local-skill-sync.test.ts | 19 +- .../codex-local-skill-injection.test.ts | 15 +- .../__tests__/codex-local-skill-sync.test.ts | 21 +- .../src/__tests__/company-portability.test.ts | 27 +- .../__tests__/cursor-local-skill-sync.test.ts | 31 +- .../__tests__/gemini-local-skill-sync.test.ts | 21 +- .../opencode-local-skill-sync.test.ts | 21 +- .../__tests__/paperclip-skill-utils.test.ts | 3 +- .../src/__tests__/pi-local-skill-sync.test.ts | 21 +- server/src/routes/agents.ts | 4 +- server/src/services/company-portability.ts | 101 +- server/src/services/company-skills.ts | 271 +- ui/src/pages/AgentDetail.tsx | 47 +- ui/src/pages/CompanyExport.tsx | 16 +- ui/src/pages/CompanySkills.tsx | 9 +- 39 files changed, 9902 insertions(+), 309 deletions(-) create mode 100644 packages/db/src/migrations/0035_colorful_rhino.sql create mode 100644 packages/db/src/migrations/meta/0035_snapshot.json diff --git a/packages/adapter-utils/src/server-utils.ts b/packages/adapter-utils/src/server-utils.ts index 1afe4c3b..3b79636f 100644 --- a/packages/adapter-utils/src/server-utils.ts +++ b/packages/adapter-utils/src/server-utils.ts @@ -38,7 +38,8 @@ const PAPERCLIP_SKILL_ROOT_RELATIVE_CANDIDATES = [ ]; export interface PaperclipSkillEntry { - name: string; + key: string; + runtimeName: string; source: string; required?: boolean; requiredReason?: string | null; @@ -306,7 +307,8 @@ export async function listPaperclipSkillEntries( return entries .filter((entry) => entry.isDirectory()) .map((entry) => ({ - name: entry.name, + key: `paperclipai/paperclip/${entry.name}`, + runtimeName: entry.name, source: path.join(root, entry.name), required: true, requiredReason: "Bundled Paperclip skills are always available for local adapters.", @@ -321,11 +323,13 @@ function normalizeConfiguredPaperclipRuntimeSkills(value: unknown): PaperclipSki const out: PaperclipSkillEntry[] = []; for (const rawEntry of value) { const entry = parseObject(rawEntry); - const name = asString(entry.name, "").trim(); + const key = asString(entry.key, asString(entry.name, "")).trim(); + const runtimeName = asString(entry.runtimeName, asString(entry.name, "")).trim(); const source = asString(entry.source, "").trim(); - if (!name || !source) continue; + if (!key || !runtimeName || !source) continue; out.push({ - name, + key, + runtimeName, source, required: asBoolean(entry.required, false), requiredReason: @@ -349,13 +353,13 @@ export async function readPaperclipRuntimeSkillEntries( export async function readPaperclipSkillMarkdown( moduleDir: string, - skillName: string, + skillKey: string, ): Promise { - const normalized = skillName.trim().toLowerCase(); + const normalized = skillKey.trim().toLowerCase(); if (!normalized) return null; const entries = await listPaperclipSkillEntries(moduleDir); - const match = entries.find((entry) => entry.name === normalized); + const match = entries.find((entry) => entry.key === normalized); if (!match) return null; try { @@ -389,12 +393,12 @@ export function readPaperclipSkillSyncPreference(config: Record export function resolvePaperclipDesiredSkillNames( config: Record, - availableEntries: Array<{ name: string; required?: boolean }>, + availableEntries: Array<{ key: string; required?: boolean }>, ): string[] { const preference = readPaperclipSkillSyncPreference(config); const requiredSkills = availableEntries .filter((entry) => entry.required) - .map((entry) => entry.name); + .map((entry) => entry.key); if (!preference.explicit) { return Array.from(new Set(requiredSkills)); } diff --git a/packages/adapter-utils/src/types.ts b/packages/adapter-utils/src/types.ts index 3fccd1d4..2c43cc39 100644 --- a/packages/adapter-utils/src/types.ts +++ b/packages/adapter-utils/src/types.ts @@ -158,7 +158,8 @@ export type AdapterSkillState = | "external"; export interface AdapterSkillEntry { - name: string; + key: string; + runtimeName: string | null; desired: boolean; managed: boolean; required?: boolean; diff --git a/packages/adapters/claude-local/src/server/execute.ts b/packages/adapters/claude-local/src/server/execute.ts index 6b174dd2..3d60eb9e 100644 --- a/packages/adapters/claude-local/src/server/execute.ts +++ b/packages/adapters/claude-local/src/server/execute.ts @@ -49,10 +49,10 @@ async function buildSkillsDir(config: Record): Promise ), ); for (const entry of availableEntries) { - if (!desiredNames.has(entry.name)) continue; + if (!desiredNames.has(entry.key)) continue; await fs.symlink( entry.source, - path.join(target, entry.name), + path.join(target, entry.runtimeName), ); } return tmp; diff --git a/packages/adapters/claude-local/src/server/skills.ts b/packages/adapters/claude-local/src/server/skills.ts index 194ef324..b6921e0d 100644 --- a/packages/adapters/claude-local/src/server/skills.ts +++ b/packages/adapters/claude-local/src/server/skills.ts @@ -14,17 +14,18 @@ const __moduleDir = path.dirname(fileURLToPath(import.meta.url)); async function buildClaudeSkillSnapshot(config: Record): Promise { const availableEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir); - const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry])); + const availableByKey = new Map(availableEntries.map((entry) => [entry.key, entry])); const desiredSkills = resolvePaperclipDesiredSkillNames(config, availableEntries); const desiredSet = new Set(desiredSkills); const entries: AdapterSkillEntry[] = availableEntries.map((entry) => ({ - name: entry.name, - desired: desiredSet.has(entry.name), + key: entry.key, + runtimeName: entry.runtimeName, + desired: desiredSet.has(entry.key), managed: true, - state: desiredSet.has(entry.name) ? "configured" : "available", + state: desiredSet.has(entry.key) ? "configured" : "available", sourcePath: entry.source, targetPath: null, - detail: desiredSet.has(entry.name) + detail: desiredSet.has(entry.key) ? "Will be mounted into the ephemeral Claude skill directory on the next run." : null, required: Boolean(entry.required), @@ -33,10 +34,11 @@ async function buildClaudeSkillSnapshot(config: Record): Promis const warnings: string[] = []; for (const desiredSkill of desiredSkills) { - if (availableByName.has(desiredSkill)) continue; + if (availableByKey.has(desiredSkill)) continue; warnings.push(`Desired skill "${desiredSkill}" is not available from the Paperclip skills directory.`); entries.push({ - name: desiredSkill, + key: desiredSkill, + runtimeName: null, desired: true, managed: true, state: "missing", @@ -46,7 +48,7 @@ async function buildClaudeSkillSnapshot(config: Record): Promis }); } - entries.sort((left, right) => left.name.localeCompare(right.name)); + entries.sort((left, right) => left.key.localeCompare(right.key)); return { adapterType: "claude_local", @@ -71,7 +73,7 @@ export async function syncClaudeSkills( export function resolveClaudeDesiredSkillNames( config: Record, - availableEntries: Array<{ name: string; required?: boolean }>, + availableEntries: Array<{ key: string; required?: boolean }>, ) { return resolvePaperclipDesiredSkillNames(config, availableEntries); } diff --git a/packages/adapters/codex-local/src/server/execute.ts b/packages/adapters/codex-local/src/server/execute.ts index 6a305f46..31583ab4 100644 --- a/packages/adapters/codex-local/src/server/execute.ts +++ b/packages/adapters/codex-local/src/server/execute.ts @@ -99,7 +99,7 @@ async function isLikelyPaperclipRuntimeSkillSource(candidate: string, skillName: type EnsureCodexSkillsInjectedOptions = { skillsHome?: string; - skillsEntries?: Array<{ name: string; source: string }>; + skillsEntries?: Array<{ key: string; runtimeName: string; source: string }>; desiredSkillNames?: string[]; linkSkill?: (source: string, target: string) => Promise; }; @@ -110,16 +110,16 @@ export async function ensureCodexSkillsInjected( ) { const allSkillsEntries = options.skillsEntries ?? await readPaperclipRuntimeSkillEntries({}, __moduleDir); const desiredSkillNames = - options.desiredSkillNames ?? allSkillsEntries.map((entry) => entry.name); + options.desiredSkillNames ?? allSkillsEntries.map((entry) => entry.key); const desiredSet = new Set(desiredSkillNames); - const skillsEntries = allSkillsEntries.filter((entry) => desiredSet.has(entry.name)); + const skillsEntries = allSkillsEntries.filter((entry) => desiredSet.has(entry.key)); if (skillsEntries.length === 0) return; const skillsHome = options.skillsHome ?? path.join(resolveCodexHomeDir(process.env), "skills"); await fs.mkdir(skillsHome, { recursive: true }); const removedSkills = await removeMaintainerOnlySkillSymlinks( skillsHome, - skillsEntries.map((entry) => entry.name), + skillsEntries.map((entry) => entry.runtimeName), ); for (const skillName of removedSkills) { await onLog( @@ -129,7 +129,7 @@ export async function ensureCodexSkillsInjected( } const linkSkill = options.linkSkill; for (const entry of skillsEntries) { - const target = path.join(skillsHome, entry.name); + const target = path.join(skillsHome, entry.runtimeName); try { const existing = await fs.lstat(target).catch(() => null); @@ -141,7 +141,7 @@ export async function ensureCodexSkillsInjected( if ( resolvedLinkedPath && resolvedLinkedPath !== entry.source && - (await isLikelyPaperclipRuntimeSkillSource(resolvedLinkedPath, entry.name)) + (await isLikelyPaperclipRuntimeSkillSource(resolvedLinkedPath, entry.runtimeName)) ) { await fs.unlink(target); if (linkSkill) { @@ -151,7 +151,7 @@ export async function ensureCodexSkillsInjected( } await onLog( "stderr", - `[paperclip] Repaired Codex skill "${entry.name}" into ${skillsHome}\n`, + `[paperclip] Repaired Codex skill "${entry.key}" into ${skillsHome}\n`, ); continue; } @@ -162,12 +162,12 @@ export async function ensureCodexSkillsInjected( await onLog( "stderr", - `[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Codex skill "${entry.name}" into ${skillsHome}\n`, + `[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Codex skill "${entry.key}" into ${skillsHome}\n`, ); } catch (err) { await onLog( "stderr", - `[paperclip] Failed to inject Codex skill "${entry.name}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`, + `[paperclip] Failed to inject Codex skill "${entry.key}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`, ); } } diff --git a/packages/adapters/codex-local/src/server/skills.ts b/packages/adapters/codex-local/src/server/skills.ts index b2fccab9..b9e04a96 100644 --- a/packages/adapters/codex-local/src/server/skills.ts +++ b/packages/adapters/codex-local/src/server/skills.ts @@ -53,7 +53,7 @@ async function readInstalledSkillTargets(skillsHome: string) { async function buildCodexSkillSnapshot(config: Record): Promise { const availableEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir); - const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry])); + const availableByKey = new Map(availableEntries.map((entry) => [entry.key, entry])); const desiredSkills = resolvePaperclipDesiredSkillNames(config, availableEntries); const desiredSet = new Set(desiredSkills); const skillsHome = resolveCodexSkillsHome(config); @@ -62,8 +62,8 @@ async function buildCodexSkillSnapshot(config: Record): Promise const warnings: string[] = []; for (const available of availableEntries) { - const installedEntry = installed.get(available.name) ?? null; - const desired = desiredSet.has(available.name); + const installedEntry = installed.get(available.runtimeName) ?? null; + const desired = desiredSet.has(available.key); let state: AdapterSkillEntry["state"] = "available"; let managed = false; let detail: string | null = null; @@ -82,12 +82,13 @@ async function buildCodexSkillSnapshot(config: Record): Promise } entries.push({ - name: available.name, + key: available.key, + runtimeName: available.runtimeName, desired, managed, state, sourcePath: available.source, - targetPath: path.join(skillsHome, available.name), + targetPath: path.join(skillsHome, available.runtimeName), detail, required: Boolean(available.required), requiredReason: available.requiredReason ?? null, @@ -95,23 +96,25 @@ async function buildCodexSkillSnapshot(config: Record): Promise } for (const desiredSkill of desiredSkills) { - if (availableByName.has(desiredSkill)) continue; + if (availableByKey.has(desiredSkill)) continue; warnings.push(`Desired skill "${desiredSkill}" is not available from the Paperclip skills directory.`); entries.push({ - name: desiredSkill, + key: desiredSkill, + runtimeName: null, desired: true, managed: true, state: "missing", sourcePath: null, - targetPath: path.join(skillsHome, desiredSkill), + targetPath: null, detail: "Paperclip cannot find this skill in the local runtime skills directory.", }); } for (const [name, installedEntry] of installed.entries()) { - if (availableByName.has(name)) continue; + if (availableEntries.some((entry) => entry.runtimeName === name)) continue; entries.push({ - name, + key: name, + runtimeName: name, desired: false, managed: false, state: "external", @@ -121,7 +124,7 @@ async function buildCodexSkillSnapshot(config: Record): Promise }); } - entries.sort((left, right) => left.name.localeCompare(right.name)); + entries.sort((left, right) => left.key.localeCompare(right.key)); return { adapterType: "codex_local", @@ -144,23 +147,23 @@ export async function syncCodexSkills( const availableEntries = await readPaperclipRuntimeSkillEntries(ctx.config, __moduleDir); const desiredSet = new Set([ ...desiredSkills, - ...availableEntries.filter((entry) => entry.required).map((entry) => entry.name), + ...availableEntries.filter((entry) => entry.required).map((entry) => entry.key), ]); const skillsHome = resolveCodexSkillsHome(ctx.config); await fs.mkdir(skillsHome, { recursive: true }); const installed = await readInstalledSkillTargets(skillsHome); - const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry])); + const availableByRuntimeName = new Map(availableEntries.map((entry) => [entry.runtimeName, entry])); for (const available of availableEntries) { - if (!desiredSet.has(available.name)) continue; - const target = path.join(skillsHome, available.name); + if (!desiredSet.has(available.key)) continue; + const target = path.join(skillsHome, available.runtimeName); await ensurePaperclipSkillSymlink(available.source, target); } for (const [name, installedEntry] of installed.entries()) { - const available = availableByName.get(name); + const available = availableByRuntimeName.get(name); if (!available) continue; - if (desiredSet.has(name)) continue; + if (desiredSet.has(available.key)) continue; if (installedEntry.targetPath !== available.source) continue; await fs.unlink(path.join(skillsHome, name)).catch(() => {}); } @@ -170,7 +173,7 @@ export async function syncCodexSkills( export function resolveCodexDesiredSkillNames( config: Record, - availableEntries: Array<{ name: string; required?: boolean }>, + availableEntries: Array<{ key: string; required?: boolean }>, ) { return resolvePaperclipDesiredSkillNames(config, availableEntries); } diff --git a/packages/adapters/cursor-local/src/server/execute.ts b/packages/adapters/cursor-local/src/server/execute.ts index 02afe4ff..a44e08b1 100644 --- a/packages/adapters/cursor-local/src/server/execute.ts +++ b/packages/adapters/cursor-local/src/server/execute.ts @@ -95,7 +95,7 @@ function cursorSkillsHome(): string { type EnsureCursorSkillsInjectedOptions = { skillsDir?: string | null; - skillsEntries?: Array<{ name: string; source: string }>; + skillsEntries?: Array<{ key: string; runtimeName: string; source: string }>; skillsHome?: string; linkSkill?: (source: string, target: string) => Promise; }; @@ -108,7 +108,11 @@ export async function ensureCursorSkillsInjected( ?? (options.skillsDir ? (await fs.readdir(options.skillsDir, { withFileTypes: true })) .filter((entry) => entry.isDirectory()) - .map((entry) => ({ name: entry.name, source: path.join(options.skillsDir!, entry.name) })) + .map((entry) => ({ + key: entry.name, + runtimeName: entry.name, + source: path.join(options.skillsDir!, entry.name), + })) : await readPaperclipRuntimeSkillEntries({}, __moduleDir)); if (skillsEntries.length === 0) return; @@ -124,7 +128,7 @@ export async function ensureCursorSkillsInjected( } const removedSkills = await removeMaintainerOnlySkillSymlinks( skillsHome, - skillsEntries.map((entry) => entry.name), + skillsEntries.map((entry) => entry.runtimeName), ); for (const skillName of removedSkills) { await onLog( @@ -134,19 +138,19 @@ export async function ensureCursorSkillsInjected( } const linkSkill = options.linkSkill ?? ((source: string, target: string) => fs.symlink(source, target)); for (const entry of skillsEntries) { - const target = path.join(skillsHome, entry.name); + const target = path.join(skillsHome, entry.runtimeName); try { const result = await ensurePaperclipSkillSymlink(entry.source, target, linkSkill); if (result === "skipped") continue; await onLog( "stderr", - `[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Cursor skill "${entry.name}" into ${skillsHome}\n`, + `[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Cursor skill "${entry.key}" into ${skillsHome}\n`, ); } catch (err) { await onLog( "stderr", - `[paperclip] Failed to inject Cursor skill "${entry.name}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`, + `[paperclip] Failed to inject Cursor skill "${entry.key}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`, ); } } @@ -183,7 +187,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise desiredCursorSkillNames.includes(entry.name)), + skillsEntries: cursorSkillEntries.filter((entry) => desiredCursorSkillNames.includes(entry.key)), }); const envConfig = parseObject(config.env); diff --git a/packages/adapters/cursor-local/src/server/skills.ts b/packages/adapters/cursor-local/src/server/skills.ts index c05d1b20..f2cca0a7 100644 --- a/packages/adapters/cursor-local/src/server/skills.ts +++ b/packages/adapters/cursor-local/src/server/skills.ts @@ -53,7 +53,7 @@ async function readInstalledSkillTargets(skillsHome: string) { async function buildCursorSkillSnapshot(config: Record): Promise { const availableEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir); - const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry])); + const availableByKey = new Map(availableEntries.map((entry) => [entry.key, entry])); const desiredSkills = resolvePaperclipDesiredSkillNames(config, availableEntries); const desiredSet = new Set(desiredSkills); const skillsHome = resolveCursorSkillsHome(config); @@ -62,8 +62,8 @@ async function buildCursorSkillSnapshot(config: Record): Promis const warnings: string[] = []; for (const available of availableEntries) { - const installedEntry = installed.get(available.name) ?? null; - const desired = desiredSet.has(available.name); + const installedEntry = installed.get(available.runtimeName) ?? null; + const desired = desiredSet.has(available.key); let state: AdapterSkillEntry["state"] = "available"; let managed = false; let detail: string | null = null; @@ -82,12 +82,13 @@ async function buildCursorSkillSnapshot(config: Record): Promis } entries.push({ - name: available.name, + key: available.key, + runtimeName: available.runtimeName, desired, managed, state, sourcePath: available.source, - targetPath: path.join(skillsHome, available.name), + targetPath: path.join(skillsHome, available.runtimeName), detail, required: Boolean(available.required), requiredReason: available.requiredReason ?? null, @@ -95,23 +96,25 @@ async function buildCursorSkillSnapshot(config: Record): Promis } for (const desiredSkill of desiredSkills) { - if (availableByName.has(desiredSkill)) continue; + if (availableByKey.has(desiredSkill)) continue; warnings.push(`Desired skill "${desiredSkill}" is not available from the Paperclip skills directory.`); entries.push({ - name: desiredSkill, + key: desiredSkill, + runtimeName: null, desired: true, managed: true, state: "missing", sourcePath: null, - targetPath: path.join(skillsHome, desiredSkill), + targetPath: null, detail: "Paperclip cannot find this skill in the local runtime skills directory.", }); } for (const [name, installedEntry] of installed.entries()) { - if (availableByName.has(name)) continue; + if (availableEntries.some((entry) => entry.runtimeName === name)) continue; entries.push({ - name, + key: name, + runtimeName: name, desired: false, managed: false, state: "external", @@ -121,7 +124,7 @@ async function buildCursorSkillSnapshot(config: Record): Promis }); } - entries.sort((left, right) => left.name.localeCompare(right.name)); + entries.sort((left, right) => left.key.localeCompare(right.key)); return { adapterType: "cursor", @@ -144,23 +147,23 @@ export async function syncCursorSkills( const availableEntries = await readPaperclipRuntimeSkillEntries(ctx.config, __moduleDir); const desiredSet = new Set([ ...desiredSkills, - ...availableEntries.filter((entry) => entry.required).map((entry) => entry.name), + ...availableEntries.filter((entry) => entry.required).map((entry) => entry.key), ]); const skillsHome = resolveCursorSkillsHome(ctx.config); await fs.mkdir(skillsHome, { recursive: true }); const installed = await readInstalledSkillTargets(skillsHome); - const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry])); + const availableByRuntimeName = new Map(availableEntries.map((entry) => [entry.runtimeName, entry])); for (const available of availableEntries) { - if (!desiredSet.has(available.name)) continue; - const target = path.join(skillsHome, available.name); + if (!desiredSet.has(available.key)) continue; + const target = path.join(skillsHome, available.runtimeName); await ensurePaperclipSkillSymlink(available.source, target); } for (const [name, installedEntry] of installed.entries()) { - const available = availableByName.get(name); + const available = availableByRuntimeName.get(name); if (!available) continue; - if (desiredSet.has(name)) continue; + if (desiredSet.has(available.key)) continue; if (installedEntry.targetPath !== available.source) continue; await fs.unlink(path.join(skillsHome, name)).catch(() => {}); } @@ -170,7 +173,7 @@ export async function syncCursorSkills( export function resolveCursorDesiredSkillNames( config: Record, - availableEntries: Array<{ name: string; required?: boolean }>, + availableEntries: Array<{ key: string; required?: boolean }>, ) { return resolvePaperclipDesiredSkillNames(config, availableEntries); } diff --git a/packages/adapters/gemini-local/src/server/execute.ts b/packages/adapters/gemini-local/src/server/execute.ts index 32b984e7..d25ec9d1 100644 --- a/packages/adapters/gemini-local/src/server/execute.ts +++ b/packages/adapters/gemini-local/src/server/execute.ts @@ -85,11 +85,11 @@ function geminiSkillsHome(): string { */ async function ensureGeminiSkillsInjected( onLog: AdapterExecutionContext["onLog"], - skillsEntries: Array<{ name: string; source: string }>, + skillsEntries: Array<{ key: string; runtimeName: string; source: string }>, desiredSkillNames?: string[], ): Promise { - const desiredSet = new Set(desiredSkillNames ?? skillsEntries.map((entry) => entry.name)); - const selectedEntries = skillsEntries.filter((entry) => desiredSet.has(entry.name)); + const desiredSet = new Set(desiredSkillNames ?? skillsEntries.map((entry) => entry.key)); + const selectedEntries = skillsEntries.filter((entry) => desiredSet.has(entry.key)); if (selectedEntries.length === 0) return; const skillsHome = geminiSkillsHome(); @@ -104,7 +104,7 @@ async function ensureGeminiSkillsInjected( } const removedSkills = await removeMaintainerOnlySkillSymlinks( skillsHome, - selectedEntries.map((entry) => entry.name), + selectedEntries.map((entry) => entry.runtimeName), ); for (const skillName of removedSkills) { await onLog( @@ -114,19 +114,19 @@ async function ensureGeminiSkillsInjected( } for (const entry of selectedEntries) { - const target = path.join(skillsHome, entry.name); + const target = path.join(skillsHome, entry.runtimeName); try { const result = await ensurePaperclipSkillSymlink(entry.source, target); if (result === "skipped") continue; await onLog( "stderr", - `[paperclip] ${result === "repaired" ? "Repaired" : "Linked"} Gemini skill: ${entry.name}\n`, + `[paperclip] ${result === "repaired" ? "Repaired" : "Linked"} Gemini skill: ${entry.key}\n`, ); } catch (err) { await onLog( "stderr", - `[paperclip] Failed to link Gemini skill "${entry.name}": ${err instanceof Error ? err.message : String(err)}\n`, + `[paperclip] Failed to link Gemini skill "${entry.key}": ${err instanceof Error ? err.message : String(err)}\n`, ); } } diff --git a/packages/adapters/gemini-local/src/server/skills.ts b/packages/adapters/gemini-local/src/server/skills.ts index a2776d83..a7cec2b8 100644 --- a/packages/adapters/gemini-local/src/server/skills.ts +++ b/packages/adapters/gemini-local/src/server/skills.ts @@ -53,7 +53,7 @@ async function readInstalledSkillTargets(skillsHome: string) { async function buildGeminiSkillSnapshot(config: Record): Promise { const availableEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir); - const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry])); + const availableByKey = new Map(availableEntries.map((entry) => [entry.key, entry])); const desiredSkills = resolvePaperclipDesiredSkillNames(config, availableEntries); const desiredSet = new Set(desiredSkills); const skillsHome = resolveGeminiSkillsHome(config); @@ -62,8 +62,8 @@ async function buildGeminiSkillSnapshot(config: Record): Promis const warnings: string[] = []; for (const available of availableEntries) { - const installedEntry = installed.get(available.name) ?? null; - const desired = desiredSet.has(available.name); + const installedEntry = installed.get(available.runtimeName) ?? null; + const desired = desiredSet.has(available.key); let state: AdapterSkillEntry["state"] = "available"; let managed = false; let detail: string | null = null; @@ -82,12 +82,13 @@ async function buildGeminiSkillSnapshot(config: Record): Promis } entries.push({ - name: available.name, + key: available.key, + runtimeName: available.runtimeName, desired, managed, state, sourcePath: available.source, - targetPath: path.join(skillsHome, available.name), + targetPath: path.join(skillsHome, available.runtimeName), detail, required: Boolean(available.required), requiredReason: available.requiredReason ?? null, @@ -95,23 +96,25 @@ async function buildGeminiSkillSnapshot(config: Record): Promis } for (const desiredSkill of desiredSkills) { - if (availableByName.has(desiredSkill)) continue; + if (availableByKey.has(desiredSkill)) continue; warnings.push(`Desired skill "${desiredSkill}" is not available from the Paperclip skills directory.`); entries.push({ - name: desiredSkill, + key: desiredSkill, + runtimeName: null, desired: true, managed: true, state: "missing", sourcePath: null, - targetPath: path.join(skillsHome, desiredSkill), + targetPath: null, detail: "Paperclip cannot find this skill in the local runtime skills directory.", }); } for (const [name, installedEntry] of installed.entries()) { - if (availableByName.has(name)) continue; + if (availableEntries.some((entry) => entry.runtimeName === name)) continue; entries.push({ - name, + key: name, + runtimeName: name, desired: false, managed: false, state: "external", @@ -121,7 +124,7 @@ async function buildGeminiSkillSnapshot(config: Record): Promis }); } - entries.sort((left, right) => left.name.localeCompare(right.name)); + entries.sort((left, right) => left.key.localeCompare(right.key)); return { adapterType: "gemini_local", @@ -144,23 +147,23 @@ export async function syncGeminiSkills( const availableEntries = await readPaperclipRuntimeSkillEntries(ctx.config, __moduleDir); const desiredSet = new Set([ ...desiredSkills, - ...availableEntries.filter((entry) => entry.required).map((entry) => entry.name), + ...availableEntries.filter((entry) => entry.required).map((entry) => entry.key), ]); const skillsHome = resolveGeminiSkillsHome(ctx.config); await fs.mkdir(skillsHome, { recursive: true }); const installed = await readInstalledSkillTargets(skillsHome); - const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry])); + const availableByRuntimeName = new Map(availableEntries.map((entry) => [entry.runtimeName, entry])); for (const available of availableEntries) { - if (!desiredSet.has(available.name)) continue; - const target = path.join(skillsHome, available.name); + if (!desiredSet.has(available.key)) continue; + const target = path.join(skillsHome, available.runtimeName); await ensurePaperclipSkillSymlink(available.source, target); } for (const [name, installedEntry] of installed.entries()) { - const available = availableByName.get(name); + const available = availableByRuntimeName.get(name); if (!available) continue; - if (desiredSet.has(name)) continue; + if (desiredSet.has(available.key)) continue; if (installedEntry.targetPath !== available.source) continue; await fs.unlink(path.join(skillsHome, name)).catch(() => {}); } @@ -170,7 +173,7 @@ export async function syncGeminiSkills( export function resolveGeminiDesiredSkillNames( config: Record, - availableEntries: Array<{ name: string; required?: boolean }>, + availableEntries: Array<{ key: string; required?: boolean }>, ) { return resolvePaperclipDesiredSkillNames(config, availableEntries); } diff --git a/packages/adapters/opencode-local/src/server/execute.ts b/packages/adapters/opencode-local/src/server/execute.ts index 5c60378a..1221ab98 100644 --- a/packages/adapters/opencode-local/src/server/execute.ts +++ b/packages/adapters/opencode-local/src/server/execute.ts @@ -52,16 +52,16 @@ function claudeSkillsHome(): string { async function ensureOpenCodeSkillsInjected( onLog: AdapterExecutionContext["onLog"], - skillsEntries: Array<{ name: string; source: string }>, + skillsEntries: Array<{ key: string; runtimeName: string; source: string }>, desiredSkillNames?: string[], ) { const skillsHome = claudeSkillsHome(); await fs.mkdir(skillsHome, { recursive: true }); - const desiredSet = new Set(desiredSkillNames ?? skillsEntries.map((entry) => entry.name)); - const selectedEntries = skillsEntries.filter((entry) => desiredSet.has(entry.name)); + const desiredSet = new Set(desiredSkillNames ?? skillsEntries.map((entry) => entry.key)); + const selectedEntries = skillsEntries.filter((entry) => desiredSet.has(entry.key)); const removedSkills = await removeMaintainerOnlySkillSymlinks( skillsHome, - selectedEntries.map((entry) => entry.name), + selectedEntries.map((entry) => entry.runtimeName), ); for (const skillName of removedSkills) { await onLog( @@ -70,19 +70,19 @@ async function ensureOpenCodeSkillsInjected( ); } for (const entry of selectedEntries) { - const target = path.join(skillsHome, entry.name); + const target = path.join(skillsHome, entry.runtimeName); try { const result = await ensurePaperclipSkillSymlink(entry.source, target); if (result === "skipped") continue; await onLog( "stderr", - `[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} OpenCode skill "${entry.name}" into ${skillsHome}\n`, + `[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} OpenCode skill "${entry.key}" into ${skillsHome}\n`, ); } catch (err) { await onLog( "stderr", - `[paperclip] Failed to inject OpenCode skill "${entry.name}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`, + `[paperclip] Failed to inject OpenCode skill "${entry.key}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`, ); } } diff --git a/packages/adapters/opencode-local/src/server/skills.ts b/packages/adapters/opencode-local/src/server/skills.ts index 35fe1fdd..e2c1a01f 100644 --- a/packages/adapters/opencode-local/src/server/skills.ts +++ b/packages/adapters/opencode-local/src/server/skills.ts @@ -53,7 +53,7 @@ async function readInstalledSkillTargets(skillsHome: string) { async function buildOpenCodeSkillSnapshot(config: Record): Promise { const availableEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir); - const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry])); + const availableByKey = new Map(availableEntries.map((entry) => [entry.key, entry])); const desiredSkills = resolvePaperclipDesiredSkillNames(config, availableEntries); const desiredSet = new Set(desiredSkills); const skillsHome = resolveOpenCodeSkillsHome(config); @@ -64,8 +64,8 @@ async function buildOpenCodeSkillSnapshot(config: Record): Prom ]; for (const available of availableEntries) { - const installedEntry = installed.get(available.name) ?? null; - const desired = desiredSet.has(available.name); + const installedEntry = installed.get(available.runtimeName) ?? null; + const desired = desiredSet.has(available.key); let state: AdapterSkillEntry["state"] = "available"; let managed = false; let detail: string | null = null; @@ -85,12 +85,13 @@ async function buildOpenCodeSkillSnapshot(config: Record): Prom } entries.push({ - name: available.name, + key: available.key, + runtimeName: available.runtimeName, desired, managed, state, sourcePath: available.source, - targetPath: path.join(skillsHome, available.name), + targetPath: path.join(skillsHome, available.runtimeName), detail, required: Boolean(available.required), requiredReason: available.requiredReason ?? null, @@ -98,23 +99,25 @@ async function buildOpenCodeSkillSnapshot(config: Record): Prom } for (const desiredSkill of desiredSkills) { - if (availableByName.has(desiredSkill)) continue; + if (availableByKey.has(desiredSkill)) continue; warnings.push(`Desired skill "${desiredSkill}" is not available from the Paperclip skills directory.`); entries.push({ - name: desiredSkill, + key: desiredSkill, + runtimeName: null, desired: true, managed: true, state: "missing", sourcePath: null, - targetPath: path.join(skillsHome, desiredSkill), + targetPath: null, detail: "Paperclip cannot find this skill in the local runtime skills directory.", }); } for (const [name, installedEntry] of installed.entries()) { - if (availableByName.has(name)) continue; + if (availableEntries.some((entry) => entry.runtimeName === name)) continue; entries.push({ - name, + key: name, + runtimeName: name, desired: false, managed: false, state: "external", @@ -124,7 +127,7 @@ async function buildOpenCodeSkillSnapshot(config: Record): Prom }); } - entries.sort((left, right) => left.name.localeCompare(right.name)); + entries.sort((left, right) => left.key.localeCompare(right.key)); return { adapterType: "opencode_local", @@ -147,23 +150,23 @@ export async function syncOpenCodeSkills( const availableEntries = await readPaperclipRuntimeSkillEntries(ctx.config, __moduleDir); const desiredSet = new Set([ ...desiredSkills, - ...availableEntries.filter((entry) => entry.required).map((entry) => entry.name), + ...availableEntries.filter((entry) => entry.required).map((entry) => entry.key), ]); const skillsHome = resolveOpenCodeSkillsHome(ctx.config); await fs.mkdir(skillsHome, { recursive: true }); const installed = await readInstalledSkillTargets(skillsHome); - const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry])); + const availableByRuntimeName = new Map(availableEntries.map((entry) => [entry.runtimeName, entry])); for (const available of availableEntries) { - if (!desiredSet.has(available.name)) continue; - const target = path.join(skillsHome, available.name); + if (!desiredSet.has(available.key)) continue; + const target = path.join(skillsHome, available.runtimeName); await ensurePaperclipSkillSymlink(available.source, target); } for (const [name, installedEntry] of installed.entries()) { - const available = availableByName.get(name); + const available = availableByRuntimeName.get(name); if (!available) continue; - if (desiredSet.has(name)) continue; + if (desiredSet.has(available.key)) continue; if (installedEntry.targetPath !== available.source) continue; await fs.unlink(path.join(skillsHome, name)).catch(() => {}); } @@ -173,7 +176,7 @@ export async function syncOpenCodeSkills( export function resolveOpenCodeDesiredSkillNames( config: Record, - availableEntries: Array<{ name: string; required?: boolean }>, + availableEntries: Array<{ key: string; required?: boolean }>, ) { return resolvePaperclipDesiredSkillNames(config, availableEntries); } diff --git a/packages/adapters/pi-local/src/server/execute.ts b/packages/adapters/pi-local/src/server/execute.ts index a91987c3..6f3c883c 100644 --- a/packages/adapters/pi-local/src/server/execute.ts +++ b/packages/adapters/pi-local/src/server/execute.ts @@ -53,17 +53,17 @@ function parseModelId(model: string | null): string | null { async function ensurePiSkillsInjected( onLog: AdapterExecutionContext["onLog"], - skillsEntries: Array<{ name: string; source: string }>, + skillsEntries: Array<{ key: string; runtimeName: string; source: string }>, desiredSkillNames?: string[], ) { - const desiredSet = new Set(desiredSkillNames ?? skillsEntries.map((entry) => entry.name)); - const selectedEntries = skillsEntries.filter((entry) => desiredSet.has(entry.name)); + const desiredSet = new Set(desiredSkillNames ?? skillsEntries.map((entry) => entry.key)); + const selectedEntries = skillsEntries.filter((entry) => desiredSet.has(entry.key)); if (selectedEntries.length === 0) return; const piSkillsHome = path.join(os.homedir(), ".pi", "agent", "skills"); await fs.mkdir(piSkillsHome, { recursive: true }); const removedSkills = await removeMaintainerOnlySkillSymlinks( piSkillsHome, - selectedEntries.map((entry) => entry.name), + selectedEntries.map((entry) => entry.runtimeName), ); for (const skillName of removedSkills) { await onLog( @@ -73,19 +73,19 @@ async function ensurePiSkillsInjected( } for (const entry of selectedEntries) { - const target = path.join(piSkillsHome, entry.name); + const target = path.join(piSkillsHome, entry.runtimeName); try { const result = await ensurePaperclipSkillSymlink(entry.source, target); if (result === "skipped") continue; await onLog( "stderr", - `[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Pi skill "${entry.name}" into ${piSkillsHome}\n`, + `[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Pi skill "${entry.key}" into ${piSkillsHome}\n`, ); } catch (err) { await onLog( "stderr", - `[paperclip] Failed to inject Pi skill "${entry.name}" into ${piSkillsHome}: ${err instanceof Error ? err.message : String(err)}\n`, + `[paperclip] Failed to inject Pi skill "${entry.key}" into ${piSkillsHome}: ${err instanceof Error ? err.message : String(err)}\n`, ); } } diff --git a/packages/adapters/pi-local/src/server/skills.ts b/packages/adapters/pi-local/src/server/skills.ts index b0cb0fe3..f57c1060 100644 --- a/packages/adapters/pi-local/src/server/skills.ts +++ b/packages/adapters/pi-local/src/server/skills.ts @@ -53,7 +53,7 @@ async function readInstalledSkillTargets(skillsHome: string) { async function buildPiSkillSnapshot(config: Record): Promise { const availableEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir); - const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry])); + const availableByKey = new Map(availableEntries.map((entry) => [entry.key, entry])); const desiredSkills = resolvePaperclipDesiredSkillNames(config, availableEntries); const desiredSet = new Set(desiredSkills); const skillsHome = resolvePiSkillsHome(config); @@ -62,8 +62,8 @@ async function buildPiSkillSnapshot(config: Record): Promise): Promise): Promise entry.runtimeName === name)) continue; entries.push({ - name, + key: name, + runtimeName: name, desired: false, managed: false, state: "external", @@ -121,7 +124,7 @@ async function buildPiSkillSnapshot(config: Record): Promise left.name.localeCompare(right.name)); + entries.sort((left, right) => left.key.localeCompare(right.key)); return { adapterType: "pi_local", @@ -144,23 +147,23 @@ export async function syncPiSkills( const availableEntries = await readPaperclipRuntimeSkillEntries(ctx.config, __moduleDir); const desiredSet = new Set([ ...desiredSkills, - ...availableEntries.filter((entry) => entry.required).map((entry) => entry.name), + ...availableEntries.filter((entry) => entry.required).map((entry) => entry.key), ]); const skillsHome = resolvePiSkillsHome(ctx.config); await fs.mkdir(skillsHome, { recursive: true }); const installed = await readInstalledSkillTargets(skillsHome); - const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry])); + const availableByRuntimeName = new Map(availableEntries.map((entry) => [entry.runtimeName, entry])); for (const available of availableEntries) { - if (!desiredSet.has(available.name)) continue; - const target = path.join(skillsHome, available.name); + if (!desiredSet.has(available.key)) continue; + const target = path.join(skillsHome, available.runtimeName); await ensurePaperclipSkillSymlink(available.source, target); } for (const [name, installedEntry] of installed.entries()) { - const available = availableByName.get(name); + const available = availableByRuntimeName.get(name); if (!available) continue; - if (desiredSet.has(name)) continue; + if (desiredSet.has(available.key)) continue; if (installedEntry.targetPath !== available.source) continue; await fs.unlink(path.join(skillsHome, name)).catch(() => {}); } @@ -170,7 +173,7 @@ export async function syncPiSkills( export function resolvePiDesiredSkillNames( config: Record, - availableEntries: Array<{ name: string; required?: boolean }>, + availableEntries: Array<{ key: string; required?: boolean }>, ) { return resolvePaperclipDesiredSkillNames(config, availableEntries); } diff --git a/packages/db/src/migrations/0035_colorful_rhino.sql b/packages/db/src/migrations/0035_colorful_rhino.sql new file mode 100644 index 00000000..870b03ac --- /dev/null +++ b/packages/db/src/migrations/0035_colorful_rhino.sql @@ -0,0 +1,27 @@ +ALTER TABLE "company_skills" ADD COLUMN "key" text;--> statement-breakpoint +UPDATE "company_skills" +SET "key" = CASE + WHEN COALESCE("metadata"->>'sourceKind', '') = 'paperclip_bundled' THEN 'paperclipai/paperclip/' || "slug" + WHEN (COALESCE("metadata"->>'sourceKind', '') = 'github' OR "source_type" = 'github') + AND COALESCE("metadata"->>'owner', '') <> '' + AND COALESCE("metadata"->>'repo', '') <> '' + THEN lower("metadata"->>'owner') || '/' || lower("metadata"->>'repo') || '/' || "slug" + WHEN COALESCE("metadata"->>'sourceKind', '') = 'managed_local' THEN 'company/' || "company_id"::text || '/' || "slug" + WHEN (COALESCE("metadata"->>'sourceKind', '') = 'url' OR "source_type" = 'url') + THEN 'url/' + || COALESCE( + NULLIF(regexp_replace(lower(regexp_replace(COALESCE("source_locator", ''), '^https?://([^/]+).*$','\1')), '[^a-z0-9._-]+', '-', 'g'), ''), + 'unknown' + ) + || '/' + || substr(md5(COALESCE("source_locator", "slug")), 1, 10) + || '/' + || "slug" + WHEN "source_type" = 'local_path' AND COALESCE("source_locator", '') <> '' + THEN 'local/' || substr(md5("source_locator"), 1, 10) || '/' || "slug" + ELSE 'company/' || "company_id"::text || '/' || "slug" +END +WHERE "key" IS NULL;--> statement-breakpoint +ALTER TABLE "company_skills" ALTER COLUMN "key" SET NOT NULL;--> statement-breakpoint +DROP INDEX IF EXISTS "company_skills_company_slug_idx";--> statement-breakpoint +CREATE UNIQUE INDEX "company_skills_company_key_idx" ON "company_skills" USING btree ("company_id","key"); diff --git a/packages/db/src/migrations/meta/0035_snapshot.json b/packages/db/src/migrations/meta/0035_snapshot.json new file mode 100644 index 00000000..be022655 --- /dev/null +++ b/packages/db/src/migrations/meta/0035_snapshot.json @@ -0,0 +1,9212 @@ +{ + "id": "4537f5f6-7522-47f5-84af-e70c7465074e", + "prevId": "53b16771-42c3-41c7-af85-0bfbb9f9013b", + "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_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" + }, + "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": {} + } + }, + "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" + } + }, + "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.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.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 + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "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_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 + }, + "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_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 + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "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 + }, + "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_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": {} + } + }, + "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_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.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 + }, + "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_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" + } + }, + "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 + }, + "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": {} + } + }, + "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" + } + }, + "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_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 + }, + "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": {} + } + }, + "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" + } + }, + "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 + }, + "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": { + "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": {} + } + }, + "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" + } + }, + "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_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.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 + }, + "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'" + }, + "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 + }, + "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_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_identifier_idx": { + "name": "issues_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "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_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" + } + }, + "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": {} + } + }, + "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_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_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_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 + }, + "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 + }, + "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": {} + } + }, + "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 + }, + "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.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 + }, + "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_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_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": {}, + "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 8ad03efe..43dee23d 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -246,6 +246,13 @@ "when": 1773697572188, "tag": "0034_fat_dormammu", "breakpoints": true + }, + { + "idx": 35, + "version": "7", + "when": 1773703213570, + "tag": "0035_colorful_rhino", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/src/schema/company_skills.ts b/packages/db/src/schema/company_skills.ts index c3a8ff2c..aff17c8e 100644 --- a/packages/db/src/schema/company_skills.ts +++ b/packages/db/src/schema/company_skills.ts @@ -14,6 +14,7 @@ export const companySkills = pgTable( { id: uuid("id").primaryKey().defaultRandom(), companyId: uuid("company_id").notNull().references(() => companies.id), + key: text("key").notNull(), slug: text("slug").notNull(), name: text("name").notNull(), description: text("description"), @@ -29,7 +30,7 @@ export const companySkills = pgTable( updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), }, (table) => ({ - companySlugUniqueIdx: uniqueIndex("company_skills_company_slug_idx").on(table.companyId, table.slug), + companyKeyUniqueIdx: uniqueIndex("company_skills_company_key_idx").on(table.companyId, table.key), companyNameIdx: index("company_skills_company_name_idx").on(table.companyId, table.name), }), ); diff --git a/packages/shared/src/types/adapter-skills.ts b/packages/shared/src/types/adapter-skills.ts index c79483b5..e5fcd60d 100644 --- a/packages/shared/src/types/adapter-skills.ts +++ b/packages/shared/src/types/adapter-skills.ts @@ -9,7 +9,8 @@ export type AgentSkillState = | "external"; export interface AgentSkillEntry { - name: string; + key: string; + runtimeName: string | null; desired: boolean; managed: boolean; required?: boolean; diff --git a/packages/shared/src/types/company-portability.ts b/packages/shared/src/types/company-portability.ts index c2526cc3..3a7927e0 100644 --- a/packages/shared/src/types/company-portability.ts +++ b/packages/shared/src/types/company-portability.ts @@ -74,6 +74,7 @@ export interface CompanyPortabilityAgentManifestEntry { } export interface CompanyPortabilitySkillManifestEntry { + key: string; slug: string; name: string; path: string; diff --git a/packages/shared/src/types/company-skill.ts b/packages/shared/src/types/company-skill.ts index c1338e0e..7f0698fd 100644 --- a/packages/shared/src/types/company-skill.ts +++ b/packages/shared/src/types/company-skill.ts @@ -14,6 +14,7 @@ export interface CompanySkillFileInventoryEntry { export interface CompanySkill { id: string; companyId: string; + key: string; slug: string; name: string; description: string | null; @@ -32,6 +33,7 @@ export interface CompanySkill { export interface CompanySkillListItem { id: string; companyId: string; + key: string; slug: string; name: string; description: string | null; diff --git a/packages/shared/src/validators/adapter-skills.ts b/packages/shared/src/validators/adapter-skills.ts index 212043ab..fd81fcb8 100644 --- a/packages/shared/src/validators/adapter-skills.ts +++ b/packages/shared/src/validators/adapter-skills.ts @@ -16,7 +16,8 @@ export const agentSkillSyncModeSchema = z.enum([ ]); export const agentSkillEntrySchema = z.object({ - name: z.string().min(1), + key: z.string().min(1), + runtimeName: z.string().min(1).nullable(), desired: z.boolean(), managed: z.boolean(), required: z.boolean().optional(), diff --git a/packages/shared/src/validators/company-portability.ts b/packages/shared/src/validators/company-portability.ts index c9e387b6..605841f0 100644 --- a/packages/shared/src/validators/company-portability.ts +++ b/packages/shared/src/validators/company-portability.ts @@ -46,6 +46,7 @@ export const portabilityAgentManifestEntrySchema = z.object({ }); export const portabilitySkillManifestEntrySchema = z.object({ + key: z.string().min(1), slug: z.string().min(1), name: z.string().min(1), path: z.string().min(1), diff --git a/packages/shared/src/validators/company-skill.ts b/packages/shared/src/validators/company-skill.ts index a2983982..5d9586a2 100644 --- a/packages/shared/src/validators/company-skill.ts +++ b/packages/shared/src/validators/company-skill.ts @@ -13,6 +13,7 @@ export const companySkillFileInventoryEntrySchema = z.object({ export const companySkillSchema = z.object({ id: z.string().uuid(), companyId: z.string().uuid(), + key: z.string().min(1), slug: z.string().min(1), name: z.string().min(1), description: z.string().nullable(), diff --git a/server/src/__tests__/claude-local-skill-sync.test.ts b/server/src/__tests__/claude-local-skill-sync.test.ts index 4bc58f7d..a103aa7c 100644 --- a/server/src/__tests__/claude-local-skill-sync.test.ts +++ b/server/src/__tests__/claude-local-skill-sync.test.ts @@ -5,6 +5,9 @@ import { } from "@paperclipai/adapter-claude-local/server"; describe("claude local skill sync", () => { + const paperclipKey = "paperclipai/paperclip/paperclip"; + const createAgentKey = "paperclipai/paperclip/paperclip-create-agent"; + it("defaults to mounting all built-in Paperclip skills when no explicit selection exists", async () => { const snapshot = await listClaudeSkills({ agentId: "agent-1", @@ -15,9 +18,9 @@ describe("claude local skill sync", () => { expect(snapshot.mode).toBe("ephemeral"); expect(snapshot.supported).toBe(true); - expect(snapshot.desiredSkills).toContain("paperclip"); - expect(snapshot.entries.find((entry) => entry.name === "paperclip")?.required).toBe(true); - expect(snapshot.entries.find((entry) => entry.name === "paperclip")?.state).toBe("configured"); + expect(snapshot.desiredSkills).toContain(paperclipKey); + expect(snapshot.entries.find((entry) => entry.key === paperclipKey)?.required).toBe(true); + expect(snapshot.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("configured"); }); it("respects an explicit desired skill list without mutating a persistent home", async () => { @@ -27,13 +30,13 @@ describe("claude local skill sync", () => { adapterType: "claude_local", config: { paperclipSkillSync: { - desiredSkills: ["paperclip"], + desiredSkills: [paperclipKey], }, }, - }, ["paperclip"]); + }, [paperclipKey]); - expect(snapshot.desiredSkills).toContain("paperclip"); - expect(snapshot.entries.find((entry) => entry.name === "paperclip")?.state).toBe("configured"); - expect(snapshot.entries.find((entry) => entry.name === "paperclip-create-agent")?.state).toBe("configured"); + expect(snapshot.desiredSkills).toContain(paperclipKey); + expect(snapshot.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("configured"); + expect(snapshot.entries.find((entry) => entry.key === createAgentKey)?.state).toBe("configured"); }); }); diff --git a/server/src/__tests__/codex-local-skill-injection.test.ts b/server/src/__tests__/codex-local-skill-injection.test.ts index bbbaec63..33764e13 100644 --- a/server/src/__tests__/codex-local-skill-injection.test.ts +++ b/server/src/__tests__/codex-local-skill-injection.test.ts @@ -31,6 +31,7 @@ async function createCustomSkill(root: string, skillName: string) { } describe("codex local adapter skill injection", () => { + const paperclipKey = "paperclipai/paperclip/paperclip"; const cleanupDirs = new Set(); afterEach(async () => { @@ -57,14 +58,18 @@ describe("codex local adapter skill injection", () => { }, { skillsHome, - skillsEntries: [{ name: "paperclip", source: path.join(currentRepo, "skills", "paperclip") }], + skillsEntries: [{ + key: paperclipKey, + runtimeName: "paperclip", + source: path.join(currentRepo, "skills", "paperclip"), + }], }, ); expect(await fs.realpath(path.join(skillsHome, "paperclip"))).toBe( await fs.realpath(path.join(currentRepo, "skills", "paperclip")), ); - expect(logs.some((line) => line.includes('Repaired Codex skill "paperclip"'))).toBe(true); + expect(logs.some((line) => line.includes("Repaired Codex skill"))).toBe(true); }); it("preserves a custom Codex skill symlink outside Paperclip repo checkouts", async () => { @@ -81,7 +86,11 @@ describe("codex local adapter skill injection", () => { await ensureCodexSkillsInjected(async () => {}, { skillsHome, - skillsEntries: [{ name: "paperclip", source: path.join(currentRepo, "skills", "paperclip") }], + skillsEntries: [{ + key: paperclipKey, + runtimeName: "paperclip", + source: path.join(currentRepo, "skills", "paperclip"), + }], }); expect(await fs.realpath(path.join(skillsHome, "paperclip"))).toBe( diff --git a/server/src/__tests__/codex-local-skill-sync.test.ts b/server/src/__tests__/codex-local-skill-sync.test.ts index 13be991e..7068e43f 100644 --- a/server/src/__tests__/codex-local-skill-sync.test.ts +++ b/server/src/__tests__/codex-local-skill-sync.test.ts @@ -12,6 +12,7 @@ async function makeTempDir(prefix: string): Promise { } describe("codex local skill sync", () => { + const paperclipKey = "paperclipai/paperclip/paperclip"; const cleanupDirs = new Set(); afterEach(async () => { @@ -32,19 +33,19 @@ describe("codex local skill sync", () => { CODEX_HOME: codexHome, }, paperclipSkillSync: { - desiredSkills: ["paperclip"], + desiredSkills: [paperclipKey], }, }, } as const; const before = await listCodexSkills(ctx); expect(before.mode).toBe("persistent"); - expect(before.desiredSkills).toContain("paperclip"); - expect(before.entries.find((entry) => entry.name === "paperclip")?.required).toBe(true); - expect(before.entries.find((entry) => entry.name === "paperclip")?.state).toBe("missing"); + expect(before.desiredSkills).toContain(paperclipKey); + expect(before.entries.find((entry) => entry.key === paperclipKey)?.required).toBe(true); + expect(before.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("missing"); - const after = await syncCodexSkills(ctx, ["paperclip"]); - expect(after.entries.find((entry) => entry.name === "paperclip")?.state).toBe("installed"); + const after = await syncCodexSkills(ctx, [paperclipKey]); + expect(after.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("installed"); expect((await fs.lstat(path.join(codexHome, "skills", "paperclip"))).isSymbolicLink()).toBe(true); }); @@ -61,12 +62,12 @@ describe("codex local skill sync", () => { CODEX_HOME: codexHome, }, paperclipSkillSync: { - desiredSkills: ["paperclip"], + desiredSkills: [paperclipKey], }, }, } as const; - await syncCodexSkills(configuredCtx, ["paperclip"]); + await syncCodexSkills(configuredCtx, [paperclipKey]); const clearedCtx = { ...configuredCtx, @@ -81,8 +82,8 @@ describe("codex local skill sync", () => { } as const; const after = await syncCodexSkills(clearedCtx, []); - expect(after.desiredSkills).toContain("paperclip"); - expect(after.entries.find((entry) => entry.name === "paperclip")?.state).toBe("installed"); + expect(after.desiredSkills).toContain(paperclipKey); + expect(after.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("installed"); expect((await fs.lstat(path.join(codexHome, "skills", "paperclip"))).isSymbolicLink()).toBe(true); }); }); diff --git a/server/src/__tests__/company-portability.test.ts b/server/src/__tests__/company-portability.test.ts index cdbc4240..8d36dafc 100644 --- a/server/src/__tests__/company-portability.test.ts +++ b/server/src/__tests__/company-portability.test.ts @@ -63,6 +63,9 @@ vi.mock("../services/company-skills.js", () => ({ const { companyPortabilityService } = await import("../services/company-portability.js"); describe("company portability", () => { + const paperclipKey = "paperclipai/paperclip/paperclip"; + const companyPlaybookKey = "company/company-1/company-playbook"; + beforeEach(() => { vi.clearAllMocks(); companySvc.getById.mockResolvedValue({ @@ -86,7 +89,7 @@ describe("company portability", () => { adapterConfig: { promptTemplate: "You are ClaudeCoder.", paperclipSkillSync: { - desiredSkills: ["paperclip"], + desiredSkills: [paperclipKey], }, instructionsFilePath: "/tmp/ignored.md", cwd: "/tmp/ignored", @@ -153,6 +156,7 @@ describe("company portability", () => { { id: "skill-1", companyId: "company-1", + key: paperclipKey, slug: "paperclip", name: "paperclip", description: "Paperclip coordination skill", @@ -178,6 +182,7 @@ describe("company portability", () => { { id: "skill-2", companyId: "company-1", + key: companyPlaybookKey, slug: "company-playbook", name: "company-playbook", description: "Internal company skill", @@ -244,13 +249,13 @@ describe("company portability", () => { expect(exported.files["COMPANY.md"]).toContain('schema: "agentcompanies/v1"'); expect(exported.files["agents/claudecoder/AGENTS.md"]).toContain("You are ClaudeCoder."); expect(exported.files["agents/claudecoder/AGENTS.md"]).toContain("skills:"); - expect(exported.files["agents/claudecoder/AGENTS.md"]).toContain('- "paperclip"'); + expect(exported.files["agents/claudecoder/AGENTS.md"]).toContain(`- "${paperclipKey}"`); expect(exported.files["agents/cmo/AGENTS.md"]).not.toContain("skills:"); - expect(exported.files["skills/paperclip/SKILL.md"]).toContain("metadata:"); - expect(exported.files["skills/paperclip/SKILL.md"]).toContain('kind: "github-dir"'); - expect(exported.files["skills/paperclip/references/api.md"]).toBeUndefined(); - expect(exported.files["skills/company-playbook/SKILL.md"]).toContain("# Company Playbook"); - expect(exported.files["skills/company-playbook/references/checklist.md"]).toContain("# Checklist"); + expect(exported.files[`skills/${paperclipKey}/SKILL.md`]).toContain("metadata:"); + expect(exported.files[`skills/${paperclipKey}/SKILL.md`]).toContain('kind: "github-dir"'); + expect(exported.files[`skills/${paperclipKey}/references/api.md`]).toBeUndefined(); + expect(exported.files[`skills/${companyPlaybookKey}/SKILL.md`]).toContain("# Company Playbook"); + expect(exported.files[`skills/${companyPlaybookKey}/references/checklist.md`]).toContain("# Checklist"); const extension = exported.files[".paperclip.yaml"]; expect(extension).toContain('schema: "paperclip/v1"'); @@ -284,9 +289,9 @@ describe("company portability", () => { expandReferencedSkills: true, }); - expect(exported.files["skills/paperclip/SKILL.md"]).toContain("# Paperclip"); - expect(exported.files["skills/paperclip/SKILL.md"]).toContain("metadata:"); - expect(exported.files["skills/paperclip/references/api.md"]).toContain("# API"); + expect(exported.files[`skills/${paperclipKey}/SKILL.md`]).toContain("# Paperclip"); + expect(exported.files[`skills/${paperclipKey}/SKILL.md`]).toContain("metadata:"); + expect(exported.files[`skills/${paperclipKey}/references/api.md`]).toContain("# API"); }); it("reads env inputs back from .paperclip.yaml during preview import", async () => { @@ -392,7 +397,7 @@ describe("company portability", () => { expect(agentSvc.create).toHaveBeenCalledWith("company-imported", expect.objectContaining({ adapterConfig: expect.objectContaining({ paperclipSkillSync: { - desiredSkills: ["paperclip"], + desiredSkills: [paperclipKey], }, }), })); diff --git a/server/src/__tests__/cursor-local-skill-sync.test.ts b/server/src/__tests__/cursor-local-skill-sync.test.ts index d26c99e4..f0aa23d5 100644 --- a/server/src/__tests__/cursor-local-skill-sync.test.ts +++ b/server/src/__tests__/cursor-local-skill-sync.test.ts @@ -19,6 +19,7 @@ async function createSkillDir(root: string, name: string) { } describe("cursor local skill sync", () => { + const paperclipKey = "paperclipai/paperclip/paperclip"; const cleanupDirs = new Set(); afterEach(async () => { @@ -39,19 +40,19 @@ describe("cursor local skill sync", () => { HOME: home, }, paperclipSkillSync: { - desiredSkills: ["paperclip"], + desiredSkills: [paperclipKey], }, }, } as const; const before = await listCursorSkills(ctx); expect(before.mode).toBe("persistent"); - expect(before.desiredSkills).toContain("paperclip"); - expect(before.entries.find((entry) => entry.name === "paperclip")?.required).toBe(true); - expect(before.entries.find((entry) => entry.name === "paperclip")?.state).toBe("missing"); + expect(before.desiredSkills).toContain(paperclipKey); + expect(before.entries.find((entry) => entry.key === paperclipKey)?.required).toBe(true); + expect(before.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("missing"); - const after = await syncCursorSkills(ctx, ["paperclip"]); - expect(after.entries.find((entry) => entry.name === "paperclip")?.state).toBe("installed"); + const after = await syncCursorSkills(ctx, [paperclipKey]); + expect(after.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("installed"); expect((await fs.lstat(path.join(home, ".cursor", "skills", "paperclip"))).isSymbolicLink()).toBe(true); }); @@ -74,13 +75,15 @@ describe("cursor local skill sync", () => { }, paperclipRuntimeSkills: [ { - name: "paperclip", + key: "paperclip", + runtimeName: "paperclip", source: paperclipDir, required: true, requiredReason: "Bundled Paperclip skills are always available for local adapters.", }, { - name: "ascii-heart", + key: "ascii-heart", + runtimeName: "ascii-heart", source: asciiHeartDir, }, ], @@ -93,11 +96,11 @@ describe("cursor local skill sync", () => { const before = await listCursorSkills(ctx); expect(before.warnings).toEqual([]); expect(before.desiredSkills).toEqual(["paperclip", "ascii-heart"]); - expect(before.entries.find((entry) => entry.name === "ascii-heart")?.state).toBe("missing"); + expect(before.entries.find((entry) => entry.key === "ascii-heart")?.state).toBe("missing"); const after = await syncCursorSkills(ctx, ["ascii-heart"]); expect(after.warnings).toEqual([]); - expect(after.entries.find((entry) => entry.name === "ascii-heart")?.state).toBe("installed"); + expect(after.entries.find((entry) => entry.key === "ascii-heart")?.state).toBe("installed"); expect((await fs.lstat(path.join(home, ".cursor", "skills", "ascii-heart"))).isSymbolicLink()).toBe(true); }); @@ -114,12 +117,12 @@ describe("cursor local skill sync", () => { HOME: home, }, paperclipSkillSync: { - desiredSkills: ["paperclip"], + desiredSkills: [paperclipKey], }, }, } as const; - await syncCursorSkills(configuredCtx, ["paperclip"]); + await syncCursorSkills(configuredCtx, [paperclipKey]); const clearedCtx = { ...configuredCtx, @@ -134,8 +137,8 @@ describe("cursor local skill sync", () => { } as const; const after = await syncCursorSkills(clearedCtx, []); - expect(after.desiredSkills).toContain("paperclip"); - expect(after.entries.find((entry) => entry.name === "paperclip")?.state).toBe("installed"); + expect(after.desiredSkills).toContain(paperclipKey); + expect(after.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("installed"); expect((await fs.lstat(path.join(home, ".cursor", "skills", "paperclip"))).isSymbolicLink()).toBe(true); }); }); diff --git a/server/src/__tests__/gemini-local-skill-sync.test.ts b/server/src/__tests__/gemini-local-skill-sync.test.ts index 8619973b..d11f2eec 100644 --- a/server/src/__tests__/gemini-local-skill-sync.test.ts +++ b/server/src/__tests__/gemini-local-skill-sync.test.ts @@ -12,6 +12,7 @@ async function makeTempDir(prefix: string): Promise { } describe("gemini local skill sync", () => { + const paperclipKey = "paperclipai/paperclip/paperclip"; const cleanupDirs = new Set(); afterEach(async () => { @@ -32,19 +33,19 @@ describe("gemini local skill sync", () => { HOME: home, }, paperclipSkillSync: { - desiredSkills: ["paperclip"], + desiredSkills: [paperclipKey], }, }, } as const; const before = await listGeminiSkills(ctx); expect(before.mode).toBe("persistent"); - expect(before.desiredSkills).toContain("paperclip"); - expect(before.entries.find((entry) => entry.name === "paperclip")?.required).toBe(true); - expect(before.entries.find((entry) => entry.name === "paperclip")?.state).toBe("missing"); + expect(before.desiredSkills).toContain(paperclipKey); + expect(before.entries.find((entry) => entry.key === paperclipKey)?.required).toBe(true); + expect(before.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("missing"); - const after = await syncGeminiSkills(ctx, ["paperclip"]); - expect(after.entries.find((entry) => entry.name === "paperclip")?.state).toBe("installed"); + const after = await syncGeminiSkills(ctx, [paperclipKey]); + expect(after.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("installed"); expect((await fs.lstat(path.join(home, ".gemini", "skills", "paperclip"))).isSymbolicLink()).toBe(true); }); @@ -61,12 +62,12 @@ describe("gemini local skill sync", () => { HOME: home, }, paperclipSkillSync: { - desiredSkills: ["paperclip"], + desiredSkills: [paperclipKey], }, }, } as const; - await syncGeminiSkills(configuredCtx, ["paperclip"]); + await syncGeminiSkills(configuredCtx, [paperclipKey]); const clearedCtx = { ...configuredCtx, @@ -81,8 +82,8 @@ describe("gemini local skill sync", () => { } as const; const after = await syncGeminiSkills(clearedCtx, []); - expect(after.desiredSkills).toContain("paperclip"); - expect(after.entries.find((entry) => entry.name === "paperclip")?.state).toBe("installed"); + expect(after.desiredSkills).toContain(paperclipKey); + expect(after.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("installed"); expect((await fs.lstat(path.join(home, ".gemini", "skills", "paperclip"))).isSymbolicLink()).toBe(true); }); }); diff --git a/server/src/__tests__/opencode-local-skill-sync.test.ts b/server/src/__tests__/opencode-local-skill-sync.test.ts index bf113064..7898a77a 100644 --- a/server/src/__tests__/opencode-local-skill-sync.test.ts +++ b/server/src/__tests__/opencode-local-skill-sync.test.ts @@ -12,6 +12,7 @@ async function makeTempDir(prefix: string): Promise { } describe("opencode local skill sync", () => { + const paperclipKey = "paperclipai/paperclip/paperclip"; const cleanupDirs = new Set(); afterEach(async () => { @@ -32,7 +33,7 @@ describe("opencode local skill sync", () => { HOME: home, }, paperclipSkillSync: { - desiredSkills: ["paperclip"], + desiredSkills: [paperclipKey], }, }, } as const; @@ -40,12 +41,12 @@ describe("opencode local skill sync", () => { const before = await listOpenCodeSkills(ctx); expect(before.mode).toBe("persistent"); expect(before.warnings).toContain("OpenCode currently uses the shared Claude skills home (~/.claude/skills)."); - expect(before.desiredSkills).toContain("paperclip"); - expect(before.entries.find((entry) => entry.name === "paperclip")?.required).toBe(true); - expect(before.entries.find((entry) => entry.name === "paperclip")?.state).toBe("missing"); + expect(before.desiredSkills).toContain(paperclipKey); + expect(before.entries.find((entry) => entry.key === paperclipKey)?.required).toBe(true); + expect(before.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("missing"); - const after = await syncOpenCodeSkills(ctx, ["paperclip"]); - expect(after.entries.find((entry) => entry.name === "paperclip")?.state).toBe("installed"); + const after = await syncOpenCodeSkills(ctx, [paperclipKey]); + expect(after.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("installed"); expect((await fs.lstat(path.join(home, ".claude", "skills", "paperclip"))).isSymbolicLink()).toBe(true); }); @@ -62,12 +63,12 @@ describe("opencode local skill sync", () => { HOME: home, }, paperclipSkillSync: { - desiredSkills: ["paperclip"], + desiredSkills: [paperclipKey], }, }, } as const; - await syncOpenCodeSkills(configuredCtx, ["paperclip"]); + await syncOpenCodeSkills(configuredCtx, [paperclipKey]); const clearedCtx = { ...configuredCtx, @@ -82,8 +83,8 @@ describe("opencode local skill sync", () => { } as const; const after = await syncOpenCodeSkills(clearedCtx, []); - expect(after.desiredSkills).toContain("paperclip"); - expect(after.entries.find((entry) => entry.name === "paperclip")?.state).toBe("installed"); + expect(after.desiredSkills).toContain(paperclipKey); + expect(after.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("installed"); expect((await fs.lstat(path.join(home, ".claude", "skills", "paperclip"))).isSymbolicLink()).toBe(true); }); }); diff --git a/server/src/__tests__/paperclip-skill-utils.test.ts b/server/src/__tests__/paperclip-skill-utils.test.ts index 4344dc17..481ea3a8 100644 --- a/server/src/__tests__/paperclip-skill-utils.test.ts +++ b/server/src/__tests__/paperclip-skill-utils.test.ts @@ -30,7 +30,8 @@ describe("paperclip skill utils", () => { const entries = await listPaperclipSkillEntries(moduleDir); - expect(entries.map((entry) => entry.name)).toEqual(["paperclip"]); + expect(entries.map((entry) => entry.key)).toEqual(["paperclipai/paperclip/paperclip"]); + expect(entries.map((entry) => entry.runtimeName)).toEqual(["paperclip"]); expect(entries[0]?.source).toBe(path.join(root, "skills", "paperclip")); }); diff --git a/server/src/__tests__/pi-local-skill-sync.test.ts b/server/src/__tests__/pi-local-skill-sync.test.ts index 77790c01..def73005 100644 --- a/server/src/__tests__/pi-local-skill-sync.test.ts +++ b/server/src/__tests__/pi-local-skill-sync.test.ts @@ -12,6 +12,7 @@ async function makeTempDir(prefix: string): Promise { } describe("pi local skill sync", () => { + const paperclipKey = "paperclipai/paperclip/paperclip"; const cleanupDirs = new Set(); afterEach(async () => { @@ -32,19 +33,19 @@ describe("pi local skill sync", () => { HOME: home, }, paperclipSkillSync: { - desiredSkills: ["paperclip"], + desiredSkills: [paperclipKey], }, }, } as const; const before = await listPiSkills(ctx); expect(before.mode).toBe("persistent"); - expect(before.desiredSkills).toContain("paperclip"); - expect(before.entries.find((entry) => entry.name === "paperclip")?.required).toBe(true); - expect(before.entries.find((entry) => entry.name === "paperclip")?.state).toBe("missing"); + expect(before.desiredSkills).toContain(paperclipKey); + expect(before.entries.find((entry) => entry.key === paperclipKey)?.required).toBe(true); + expect(before.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("missing"); - const after = await syncPiSkills(ctx, ["paperclip"]); - expect(after.entries.find((entry) => entry.name === "paperclip")?.state).toBe("installed"); + const after = await syncPiSkills(ctx, [paperclipKey]); + expect(after.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("installed"); expect((await fs.lstat(path.join(home, ".pi", "agent", "skills", "paperclip"))).isSymbolicLink()).toBe(true); }); @@ -61,12 +62,12 @@ describe("pi local skill sync", () => { HOME: home, }, paperclipSkillSync: { - desiredSkills: ["paperclip"], + desiredSkills: [paperclipKey], }, }, } as const; - await syncPiSkills(configuredCtx, ["paperclip"]); + await syncPiSkills(configuredCtx, [paperclipKey]); const clearedCtx = { ...configuredCtx, @@ -81,8 +82,8 @@ describe("pi local skill sync", () => { } as const; const after = await syncPiSkills(clearedCtx, []); - expect(after.desiredSkills).toContain("paperclip"); - expect(after.entries.find((entry) => entry.name === "paperclip")?.state).toBe("installed"); + expect(after.desiredSkills).toContain(paperclipKey); + expect(after.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("installed"); expect((await fs.lstat(path.join(home, ".pi", "agent", "skills", "paperclip"))).isSymbolicLink()).toBe(true); }); }); diff --git a/server/src/routes/agents.ts b/server/src/routes/agents.ts index 549cef60..11a2a2bc 100644 --- a/server/src/routes/agents.ts +++ b/server/src/routes/agents.ts @@ -506,7 +506,7 @@ export function agentRoutes(db: Db) { agent.adapterConfig as Record, ); const runtimeSkillEntries = await companySkills.listRuntimeSkillEntries(agent.companyId); - const requiredSkills = runtimeSkillEntries.filter((entry) => entry.required).map((entry) => entry.name); + const requiredSkills = runtimeSkillEntries.filter((entry) => entry.required).map((entry) => entry.key); res.json(buildUnsupportedSkillSnapshot(agent.adapterType, Array.from(new Set([...requiredSkills, ...preference.desiredSkills])))); return; } @@ -545,7 +545,7 @@ export function agentRoutes(db: Db) { ), ); const runtimeSkillEntries = await companySkills.listRuntimeSkillEntries(agent.companyId); - const requiredSkills = runtimeSkillEntries.filter((entry) => entry.required).map((entry) => entry.name); + const requiredSkills = runtimeSkillEntries.filter((entry) => entry.required).map((entry) => entry.key); const desiredSkills = Array.from(new Set([...requiredSkills, ...requestedSkills])); const nextAdapterConfig = writePaperclipSkillSyncPreference( agent.adapterConfig as Record, diff --git a/server/src/services/company-portability.ts b/server/src/services/company-portability.ts index 9d1fc48d..be311c6c 100644 --- a/server/src/services/company-portability.ts +++ b/server/src/services/company-portability.ts @@ -51,6 +51,67 @@ const DEFAULT_COLLISION_STRATEGY: CompanyPortabilityCollisionStrategy = "rename" const execFileAsync = promisify(execFile); let bundledSkillsCommitPromise: Promise | null = null; +function normalizeSkillSlug(value: string | null | undefined) { + return value ? normalizeAgentUrlKey(value) ?? null : null; +} + +function normalizeSkillKey(value: string | null | undefined) { + if (!value) return null; + const segments = value + .split("/") + .map((segment) => normalizeSkillSlug(segment)) + .filter((segment): segment is string => Boolean(segment)); + return segments.length > 0 ? segments.join("/") : null; +} + +function readSkillKey(frontmatter: Record) { + const metadata = isPlainRecord(frontmatter.metadata) ? frontmatter.metadata : null; + const paperclip = isPlainRecord(metadata?.paperclip) ? metadata?.paperclip as Record : null; + return normalizeSkillKey( + asString(frontmatter.key) + ?? asString(frontmatter.skillKey) + ?? asString(metadata?.skillKey) + ?? asString(metadata?.canonicalKey) + ?? asString(metadata?.paperclipSkillKey) + ?? asString(paperclip?.skillKey) + ?? asString(paperclip?.key), + ); +} + +function deriveManifestSkillKey( + frontmatter: Record, + fallbackSlug: string, + metadata: Record | null, + sourceType: string, + sourceLocator: string | null, +) { + const explicit = readSkillKey(frontmatter); + if (explicit) return explicit; + const slug = normalizeSkillSlug(asString(frontmatter.slug) ?? fallbackSlug) ?? "skill"; + const sourceKind = asString(metadata?.sourceKind); + const owner = normalizeSkillSlug(asString(metadata?.owner)); + const repo = normalizeSkillSlug(asString(metadata?.repo)); + if ((sourceType === "github" || sourceKind === "github") && owner && repo) { + return `${owner}/${repo}/${slug}`; + } + if (sourceKind === "paperclip_bundled") { + return `paperclipai/paperclip/${slug}`; + } + if (sourceType === "url" || sourceKind === "url") { + try { + const host = normalizeSkillSlug(sourceLocator ? new URL(sourceLocator).host : null) ?? "url"; + return `url/${host}/${slug}`; + } catch { + return `url/unknown/${slug}`; + } + } + return slug; +} + +function skillPackageDir(key: string) { + return `skills/${key}`; +} + function isSensitiveEnvKey(key: string) { const normalized = key.trim().toLowerCase(); return ( @@ -748,6 +809,8 @@ function shouldReferenceSkillOnExport(skill: CompanySkill, expandReferencedSkill async function buildReferencedSkillMarkdown(skill: CompanySkill) { const sourceEntry = await buildSkillSourceEntry(skill); const frontmatter: Record = { + key: skill.key, + slug: skill.slug, name: skill.name, description: skill.description ?? null, }; @@ -761,7 +824,6 @@ async function buildReferencedSkillMarkdown(skill: CompanySkill) { async function withSkillSourceMetadata(skill: CompanySkill, markdown: string) { const sourceEntry = await buildSkillSourceEntry(skill); - if (!sourceEntry) return markdown; const parsed = parseFrontmatterMarkdown(markdown); const metadata = isPlainRecord(parsed.frontmatter.metadata) ? { ...parsed.frontmatter.metadata } @@ -769,9 +831,20 @@ async function withSkillSourceMetadata(skill: CompanySkill, markdown: string) { const existingSources = Array.isArray(metadata.sources) ? metadata.sources.filter((entry) => isPlainRecord(entry)) : []; - metadata.sources = [...existingSources, sourceEntry]; + if (sourceEntry) { + metadata.sources = [...existingSources, sourceEntry]; + } + metadata.skillKey = skill.key; + metadata.paperclipSkillKey = skill.key; + metadata.paperclip = { + ...(isPlainRecord(metadata.paperclip) ? metadata.paperclip : {}), + skillKey: skill.key, + slug: skill.slug, + }; const frontmatter = { ...parsed.frontmatter, + key: skill.key, + slug: skill.slug, metadata, }; return buildMarkdown(frontmatter, parsed.body); @@ -1043,7 +1116,7 @@ function readAgentSkillRefs(frontmatter: Record) { return Array.from(new Set( skills .filter((entry): entry is string => typeof entry === "string") - .map((entry) => normalizeAgentUrlKey(entry) ?? entry.trim()) + .map((entry) => normalizeSkillKey(entry) ?? entry.trim()) .filter(Boolean), )); } @@ -1256,8 +1329,10 @@ function buildManifestFromPackageFiles( sourceKind: "catalog", }; } + const key = deriveManifestSkillKey(frontmatter, slug, normalizedMetadata, sourceType, sourceLocator); manifest.skills.push({ + key, slug, name: asString(frontmatter.name) ?? slug, path: skillPath, @@ -1688,15 +1763,16 @@ export function companyPortabilityService(db: Db) { const paperclipTasksOut: Record> = {}; for (const skill of companySkillRows) { + const packageDir = skillPackageDir(skill.key); if (shouldReferenceSkillOnExport(skill, Boolean(input.expandReferencedSkills))) { - files[`skills/${skill.slug}/SKILL.md`] = await buildReferencedSkillMarkdown(skill); + files[`${packageDir}/SKILL.md`] = await buildReferencedSkillMarkdown(skill); continue; } for (const inventoryEntry of skill.fileInventory) { const fileDetail = await companySkills.readFile(companyId, skill.id, inventoryEntry.path).catch(() => null); if (!fileDetail) continue; - const filePath = `skills/${skill.slug}/${inventoryEntry.path}`; + const filePath = `${packageDir}/${inventoryEntry.path}`; files[filePath] = inventoryEntry.path === "SKILL.md" ? await withSkillSourceMetadata(skill, fileDetail.content) : fileDetail.content; @@ -1908,7 +1984,13 @@ export function companyPortabilityService(db: Db) { warnings.push("No agents selected for import."); } - const availableSkillSlugs = new Set(source.manifest.skills.map((skill) => skill.slug)); + const availableSkillKeys = new Set(source.manifest.skills.map((skill) => skill.key)); + const availableSkillSlugs = new Map(); + for (const skill of source.manifest.skills) { + const existing = availableSkillSlugs.get(skill.slug) ?? []; + existing.push(skill); + availableSkillSlugs.set(skill.slug, existing); + } for (const agent of selectedAgents) { const filePath = ensureMarkdownPath(agent.path); @@ -1921,9 +2003,10 @@ export function companyPortabilityService(db: Db) { if (parsed.frontmatter.kind && parsed.frontmatter.kind !== "agent") { warnings.push(`Agent markdown ${filePath} does not declare kind: agent in frontmatter.`); } - for (const skillSlug of agent.skills) { - if (!availableSkillSlugs.has(skillSlug)) { - warnings.push(`Agent ${agent.slug} references skill ${skillSlug}, but that skill is not present in the package.`); + for (const skillRef of agent.skills) { + const slugMatches = availableSkillSlugs.get(skillRef) ?? []; + if (!availableSkillKeys.has(skillRef) && slugMatches.length !== 1) { + warnings.push(`Agent ${agent.slug} references skill ${skillRef}, but that skill is not present in the package.`); } } } diff --git a/server/src/services/company-skills.ts b/server/src/services/company-skills.ts index 495a8e79..01234db6 100644 --- a/server/src/services/company-skills.ts +++ b/server/src/services/company-skills.ts @@ -1,3 +1,4 @@ +import { createHash } from "node:crypto"; import { promises as fs } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -31,6 +32,7 @@ import { secretService } from "./secrets.js"; type CompanySkillRow = typeof companySkills.$inferSelect; type ImportedSkill = { + key: string; slug: string; name: string; description: string | null; @@ -52,6 +54,7 @@ type ParsedSkillImportSource = { }; type SkillSourceMeta = { + skillKey?: string; sourceKind?: string; owner?: string; repo?: string; @@ -97,6 +100,86 @@ function normalizeSkillSlug(value: string | null | undefined) { return value ? normalizeAgentUrlKey(value) ?? null : null; } +function normalizeSkillKey(value: string | null | undefined) { + if (!value) return null; + const segments = value + .split("/") + .map((segment) => normalizeSkillSlug(segment)) + .filter((segment): segment is string => Boolean(segment)); + return segments.length > 0 ? segments.join("/") : null; +} + +function hashSkillValue(value: string) { + return createHash("sha256").update(value).digest("hex").slice(0, 10); +} + +function buildSkillRuntimeName(key: string, slug: string) { + if (key.startsWith("paperclipai/paperclip/")) return slug; + return `${slug}--${hashSkillValue(key)}`; +} + +function readCanonicalSkillKey(frontmatter: Record, metadata: Record | null) { + const direct = normalizeSkillKey( + asString(frontmatter.key) + ?? asString(frontmatter.skillKey) + ?? asString(metadata?.skillKey) + ?? asString(metadata?.canonicalKey) + ?? asString(metadata?.paperclipSkillKey), + ); + if (direct) return direct; + const paperclip = isPlainRecord(metadata?.paperclip) ? metadata?.paperclip as Record : null; + return normalizeSkillKey( + asString(paperclip?.skillKey) + ?? asString(paperclip?.key), + ); +} + +function deriveCanonicalSkillKey( + companyId: string, + input: Pick, +) { + const slug = normalizeSkillSlug(input.slug) ?? "skill"; + const metadata = isPlainRecord(input.metadata) ? input.metadata : null; + const explicitKey = readCanonicalSkillKey({}, metadata); + if (explicitKey) return explicitKey; + + const sourceKind = asString(metadata?.sourceKind); + if (sourceKind === "paperclip_bundled") { + return `paperclipai/paperclip/${slug}`; + } + + const owner = normalizeSkillSlug(asString(metadata?.owner)); + const repo = normalizeSkillSlug(asString(metadata?.repo)); + if ((input.sourceType === "github" || sourceKind === "github") && owner && repo) { + return `${owner}/${repo}/${slug}`; + } + + if (input.sourceType === "url" || sourceKind === "url") { + const locator = asString(input.sourceLocator); + if (locator) { + try { + const url = new URL(locator); + const host = normalizeSkillSlug(url.host) ?? "url"; + return `url/${host}/${hashSkillValue(locator)}/${slug}`; + } catch { + return `url/unknown/${hashSkillValue(locator)}/${slug}`; + } + } + } + + if (input.sourceType === "local_path") { + if (sourceKind === "managed_local") { + return `company/${companyId}/${slug}`; + } + const locator = asString(input.sourceLocator); + if (locator) { + return `local/${hashSkillValue(path.resolve(locator))}/${slug}`; + } + } + + return `company/${companyId}/${slug}`; +} + function classifyInventoryKind(relativePath: string): CompanySkillFileInventoryEntry["kind"] { const normalized = normalizePortablePath(relativePath).toLowerCase(); if (normalized.endsWith("/skill.md") || normalized === "skill.md") return "skill"; @@ -417,7 +500,10 @@ function matchesRequestedSkill(relativeSkillPath: string, requestedSkillSlug: st } function deriveImportedSkillSlug(frontmatter: Record, fallback: string) { - return normalizeSkillSlug(asString(frontmatter.name)) ?? normalizeAgentUrlKey(fallback) ?? "skill"; + return normalizeSkillSlug(asString(frontmatter.slug)) + ?? normalizeSkillSlug(asString(frontmatter.name)) + ?? normalizeAgentUrlKey(fallback) + ?? "skill"; } function deriveImportedSkillSource( @@ -425,6 +511,7 @@ function deriveImportedSkillSource( fallbackSlug: string, ): Pick { const metadata = isPlainRecord(frontmatter.metadata) ? frontmatter.metadata : null; + const canonicalKey = readCanonicalSkillKey(frontmatter, metadata); const rawSources = metadata && Array.isArray(metadata.sources) ? metadata.sources : []; const sourceEntry = rawSources.find((entry) => isPlainRecord(entry)) as Record | undefined; const kind = asString(sourceEntry?.kind); @@ -445,6 +532,7 @@ function deriveImportedSkillSource( sourceLocator: url, sourceRef: commit, metadata: { + ...(canonicalKey ? { skillKey: canonicalKey } : {}), sourceKind: "github", owner, repo: repoName, @@ -464,6 +552,7 @@ function deriveImportedSkillSource( sourceLocator: url, sourceRef: null, metadata: { + ...(canonicalKey ? { skillKey: canonicalKey } : {}), sourceKind: "url", }, }; @@ -475,12 +564,13 @@ function deriveImportedSkillSource( sourceLocator: null, sourceRef: null, metadata: { + ...(canonicalKey ? { skillKey: canonicalKey } : {}), sourceKind: "catalog", }, }; } -function readInlineSkillImports(files: Record): ImportedSkill[] { +function readInlineSkillImports(companyId: string, files: Record): ImportedSkill[] { const normalizedFiles = normalizePackageFileMap(files); const skillPaths = Object.keys(normalizedFiles).filter( (entry) => path.posix.basename(entry).toLowerCase() === "skill.md", @@ -507,6 +597,7 @@ function readInlineSkillImports(files: Record): ImportedSkill[] .sort((left, right) => left.path.localeCompare(right.path)); imports.push({ + key: "", slug, name: asString(parsed.frontmatter.name) ?? slug, description: asString(parsed.frontmatter.description), @@ -520,6 +611,7 @@ function readInlineSkillImports(files: Record): ImportedSkill[] fileInventory: inventory, metadata: source.metadata, }); + imports[imports.length - 1]!.key = deriveCanonicalSkillKey(companyId, imports[imports.length - 1]!); } return imports; @@ -539,7 +631,7 @@ async function walkLocalFiles(root: string, current: string, out: string[]) { } } -async function readLocalSkillImports(sourcePath: string): Promise { +async function readLocalSkillImports(companyId: string, sourcePath: string): Promise { const resolvedPath = path.resolve(sourcePath); const stat = await fs.stat(resolvedPath).catch(() => null); if (!stat) { @@ -550,10 +642,24 @@ async function readLocalSkillImports(sourcePath: string): Promise entry === skillPath || entry.startsWith(`${skillDir}/`)) .map((entry) => { @@ -594,6 +708,12 @@ async function readLocalSkillImports(sourcePath: string): Promise left.path.localeCompare(right.path)); imports.push({ + key: deriveCanonicalSkillKey(companyId, { + slug, + sourceType: "local_path", + sourceLocator: path.join(root, skillDir), + metadata, + }), slug, name: asString(parsed.frontmatter.name) ?? slug, description: asString(parsed.frontmatter.description), @@ -605,7 +725,7 @@ async function readLocalSkillImports(sourcePath: string): Promise { @@ -654,9 +775,22 @@ async function readUrlSkillImports( const parsedMarkdown = parseFrontmatterMarkdown(markdown); const skillDir = path.posix.dirname(relativeSkillPath); const slug = deriveImportedSkillSlug(parsedMarkdown.frontmatter, path.posix.basename(skillDir)); + const skillKey = readCanonicalSkillKey( + parsedMarkdown.frontmatter, + isPlainRecord(parsedMarkdown.frontmatter.metadata) ? parsedMarkdown.frontmatter.metadata : null, + ); if (requestedSkillSlug && !matchesRequestedSkill(relativeSkillPath, requestedSkillSlug) && slug !== requestedSkillSlug) { continue; } + const metadata = { + ...(skillKey ? { skillKey } : {}), + sourceKind: "github", + owner: parsed.owner, + repo: parsed.repo, + ref: ref, + trackingRef, + repoSkillDir: basePrefix ? `${basePrefix}${skillDir}` : skillDir, + }; const inventory = filteredPaths .filter((entry) => entry === relativeSkillPath || entry.startsWith(`${skillDir}/`)) .map((entry) => ({ @@ -665,6 +799,12 @@ async function readUrlSkillImports( })) .sort((left, right) => left.path.localeCompare(right.path)); skills.push({ + key: deriveCanonicalSkillKey(companyId, { + slug, + sourceType: "github", + sourceLocator: sourceUrl, + metadata, + }), slug, name: asString(parsedMarkdown.frontmatter.name) ?? slug, description: asString(parsedMarkdown.frontmatter.description), @@ -675,14 +815,7 @@ async function readUrlSkillImports( trustLevel: deriveTrustLevel(inventory), compatibility: "compatible", fileInventory: inventory, - metadata: { - sourceKind: "github", - owner: parsed.owner, - repo: parsed.repo, - ref: ref, - trackingRef, - repoSkillDir: basePrefix ? `${basePrefix}${skillDir}` : skillDir, - }, + metadata, }); } if (skills.length === 0) { @@ -701,9 +834,23 @@ async function readUrlSkillImports( const urlObj = new URL(url); const fileName = path.posix.basename(urlObj.pathname); const slug = deriveImportedSkillSlug(parsedMarkdown.frontmatter, fileName.replace(/\.md$/i, "")); + const skillKey = readCanonicalSkillKey( + parsedMarkdown.frontmatter, + isPlainRecord(parsedMarkdown.frontmatter.metadata) ? parsedMarkdown.frontmatter.metadata : null, + ); + const metadata = { + ...(skillKey ? { skillKey } : {}), + sourceKind: "url", + }; const inventory: CompanySkillFileInventoryEntry[] = [{ path: "SKILL.md", kind: "skill" }]; return { skills: [{ + key: deriveCanonicalSkillKey(companyId, { + slug, + sourceType: "url", + sourceLocator: url, + metadata, + }), slug, name: asString(parsedMarkdown.frontmatter.name) ?? slug, description: asString(parsedMarkdown.frontmatter.description), @@ -714,9 +861,7 @@ async function readUrlSkillImports( trustLevel: deriveTrustLevel(inventory), compatibility: "compatible", fileInventory: inventory, - metadata: { - sourceKind: "url", - }, + metadata, }], warnings, }; @@ -760,6 +905,34 @@ function getSkillMeta(skill: CompanySkill): SkillSourceMeta { return isPlainRecord(skill.metadata) ? skill.metadata as SkillSourceMeta : {}; } +function resolveSkillReference( + skills: CompanySkill[], + reference: string, +): CompanySkill | null { + const normalizedReference = normalizeSkillKey(reference) ?? normalizeSkillSlug(reference); + if (!normalizedReference) return null; + + const byKey = skills.find((skill) => skill.key === normalizedReference); + if (byKey) return byKey; + + const bySlug = skills.filter((skill) => skill.slug === normalizedReference); + if (bySlug.length === 1) return bySlug[0] ?? null; + + return null; +} + +function resolveDesiredSkillKeys( + skills: CompanySkill[], + config: Record, +) { + const preference = readPaperclipSkillSyncPreference(config); + return Array.from(new Set( + preference.desiredSkills + .map((reference) => resolveSkillReference(skills, reference)?.key ?? normalizeSkillKey(reference)) + .filter((value): value is string => Boolean(value)), + )); +} + function normalizeSkillDirectory(skill: CompanySkill) { if ((skill.sourceType !== "local_path" && skill.sourceType !== "catalog") || !skill.sourceLocator) return null; const resolved = path.resolve(skill.sourceLocator); @@ -886,6 +1059,7 @@ function toCompanySkillListItem(skill: CompanySkill, attachedAgentCount: number) return { id: skill.id, companyId: skill.companyId, + key: skill.key, slug: skill.slug, name: skill.name, description: skill.description, @@ -913,9 +1087,16 @@ export function companySkillService(db: Db) { for (const skillsRoot of resolveBundledSkillsRoot()) { const stats = await fs.stat(skillsRoot).catch(() => null); if (!stats?.isDirectory()) continue; - const bundledSkills = await readLocalSkillImports(skillsRoot) + const bundledSkills = await readLocalSkillImports(companyId, skillsRoot) .then((skills) => skills.map((skill) => ({ ...skill, + key: deriveCanonicalSkillKey(companyId, { + ...skill, + metadata: { + ...(skill.metadata ?? {}), + sourceKind: "paperclip_bundled", + }, + }), metadata: { ...(skill.metadata ?? {}), sourceKind: "paperclip_bundled", @@ -933,8 +1114,8 @@ export function companySkillService(db: Db) { const agentRows = await agents.list(companyId); return rows.map((skill) => { const attachedAgentCount = agentRows.filter((agent) => { - const preference = readPaperclipSkillSyncPreference(agent.adapterConfig as Record); - return preference.desiredSkills.includes(skill.slug); + const desiredSkills = resolveDesiredSkillKeys(rows, agent.adapterConfig as Record); + return desiredSkills.includes(skill.key); }).length; return toCompanySkillListItem(skill, attachedAgentCount); }); @@ -946,7 +1127,7 @@ export function companySkillService(db: Db) { .select() .from(companySkills) .where(eq(companySkills.companyId, companyId)) - .orderBy(asc(companySkills.name), asc(companySkills.slug)); + .orderBy(asc(companySkills.name), asc(companySkills.key)); return rows.map((row) => toCompanySkill(row)); } @@ -959,20 +1140,21 @@ export function companySkillService(db: Db) { return row ? toCompanySkill(row) : null; } - async function getBySlug(companyId: string, slug: string) { + async function getByKey(companyId: string, key: string) { const row = await db .select() .from(companySkills) - .where(and(eq(companySkills.companyId, companyId), eq(companySkills.slug, slug))) + .where(and(eq(companySkills.companyId, companyId), eq(companySkills.key, key))) .then((rows) => rows[0] ?? null); return row ? toCompanySkill(row) : null; } - async function usage(companyId: string, slug: string): Promise { + async function usage(companyId: string, key: string): Promise { + const skills = await listFull(companyId); const agentRows = await agents.list(companyId); const desiredAgents = agentRows.filter((agent) => { - const preference = readPaperclipSkillSyncPreference(agent.adapterConfig as Record); - return preference.desiredSkills.includes(slug); + const desiredSkills = resolveDesiredSkillKeys(skills, agent.adapterConfig as Record); + return desiredSkills.includes(key); }); return Promise.all( @@ -998,7 +1180,7 @@ export function companySkillService(db: Db) { paperclipRuntimeSkills: runtimeSkillEntries, }, }); - actualState = snapshot.entries.find((entry) => entry.name === slug)?.state + actualState = snapshot.entries.find((entry) => entry.key === key)?.state ?? (snapshot.supported ? "missing" : "unsupported"); } catch { actualState = "unknown"; @@ -1021,7 +1203,7 @@ export function companySkillService(db: Db) { await ensureBundledSkills(companyId); const skill = await getById(id); if (!skill || skill.companyId !== companyId) return null; - const usedByAgents = await usage(companyId, skill.slug); + const usedByAgents = await usage(companyId, skill.key); return enrichSkill(skill, usedByAgents.length, usedByAgents); } @@ -1147,6 +1329,7 @@ export function companySkillService(db: Db) { const parsed = parseFrontmatterMarkdown(markdown); const imported = await upsertImportedSkills(companyId, [{ + key: `company/${companyId}/${slug}`, slug, name: asString(parsed.frontmatter.name) ?? input.name, description: asString(parsed.frontmatter.description) ?? input.description?.trim() ?? null, @@ -1216,10 +1399,10 @@ export function companySkillService(db: Db) { throw unprocessable("Skill source locator is missing."); } - const result = await readUrlSkillImports(skill.sourceLocator, skill.slug); - const matching = result.skills.find((entry) => entry.slug === skill.slug) ?? result.skills[0] ?? null; + const result = await readUrlSkillImports(companyId, skill.sourceLocator, skill.slug); + const matching = result.skills.find((entry) => entry.key === skill.key) ?? result.skills[0] ?? null; if (!matching) { - throw unprocessable(`Skill ${skill.slug} could not be re-imported from its source.`); + throw unprocessable(`Skill ${skill.key} could not be re-imported from its source.`); } const imported = await upsertImportedSkills(companyId, [matching]); @@ -1234,7 +1417,7 @@ export function companySkillService(db: Db) { const packageDir = skill.packageDir ? normalizePortablePath(skill.packageDir) : null; if (!packageDir) return null; const catalogRoot = path.resolve(resolveManagedSkillsRoot(companyId), "__catalog__"); - const skillDir = path.resolve(catalogRoot, skill.slug); + const skillDir = path.resolve(catalogRoot, buildSkillRuntimeName(skill.key, skill.slug)); await fs.rm(skillDir, { recursive: true, force: true }); await fs.mkdir(skillDir, { recursive: true }); @@ -1254,7 +1437,7 @@ export function companySkillService(db: Db) { async function materializeRuntimeSkillFiles(companyId: string, skill: CompanySkill) { const runtimeRoot = path.resolve(resolveManagedSkillsRoot(companyId), "__runtime__"); - const skillDir = path.resolve(runtimeRoot, skill.slug); + const skillDir = path.resolve(runtimeRoot, buildSkillRuntimeName(skill.key, skill.slug)); await fs.rm(skillDir, { recursive: true, force: true }); await fs.mkdir(skillDir, { recursive: true }); @@ -1275,7 +1458,7 @@ export function companySkillService(db: Db) { .select() .from(companySkills) .where(eq(companySkills.companyId, companyId)) - .orderBy(asc(companySkills.name), asc(companySkills.slug)); + .orderBy(asc(companySkills.name), asc(companySkills.key)); const out: PaperclipSkillEntry[] = []; for (const row of rows) { @@ -1289,7 +1472,8 @@ export function companySkillService(db: Db) { const required = sourceKind === "paperclip_bundled"; out.push({ - name: skill.slug, + key: skill.key, + runtimeName: buildSkillRuntimeName(skill.key, skill.slug), source, required, requiredReason: required @@ -1298,14 +1482,14 @@ export function companySkillService(db: Db) { }); } - out.sort((left, right) => left.name.localeCompare(right.name)); + out.sort((left, right) => left.key.localeCompare(right.key)); return out; } async function importPackageFiles(companyId: string, files: Record): Promise { await ensureBundledSkills(companyId); const normalizedFiles = normalizePackageFileMap(files); - const importedSkills = readInlineSkillImports(normalizedFiles); + const importedSkills = readInlineSkillImports(companyId, normalizedFiles); if (importedSkills.length === 0) return []; for (const skill of importedSkills) { @@ -1322,7 +1506,7 @@ export function companySkillService(db: Db) { async function upsertImportedSkills(companyId: string, imported: ImportedSkill[]): Promise { const out: CompanySkill[] = []; for (const skill of imported) { - const existing = await getBySlug(companyId, skill.slug); + const existing = await getByKey(companyId, skill.key); const existingMeta = existing ? getSkillMeta(existing) : {}; const incomingMeta = skill.metadata && isPlainRecord(skill.metadata) ? skill.metadata : {}; const incomingOwner = asString(incomingMeta.owner); @@ -1339,8 +1523,13 @@ export function companySkillService(db: Db) { continue; } + const metadata = { + ...(skill.metadata ?? {}), + skillKey: skill.key, + }; const values = { companyId, + key: skill.key, slug: skill.slug, name: skill.name, description: skill.description, @@ -1351,7 +1540,7 @@ export function companySkillService(db: Db) { trustLevel: skill.trustLevel, compatibility: skill.compatibility, fileInventory: serializeFileInventory(skill.fileInventory), - metadata: skill.metadata, + metadata, updatedAt: new Date(), }; const row = existing @@ -1378,11 +1567,11 @@ export function companySkillService(db: Db) { const local = !/^https?:\/\//i.test(parsed.resolvedSource); const { skills, warnings } = local ? { - skills: (await readLocalSkillImports(parsed.resolvedSource)) + skills: (await readLocalSkillImports(companyId, parsed.resolvedSource)) .filter((skill) => !parsed.requestedSkillSlug || skill.slug === parsed.requestedSkillSlug), warnings: parsed.warnings, } - : await readUrlSkillImports(parsed.resolvedSource, parsed.requestedSkillSlug) + : await readUrlSkillImports(companyId, parsed.resolvedSource, parsed.requestedSkillSlug) .then((result) => ({ skills: result.skills, warnings: [...parsed.warnings, ...result.warnings], @@ -1405,7 +1594,7 @@ export function companySkillService(db: Db) { list, listFull, getById, - getBySlug, + getByKey, detail, updateStatus, readFile, diff --git a/ui/src/pages/AgentDetail.tsx b/ui/src/pages/AgentDetail.tsx index 1d020136..7e8a912b 100644 --- a/ui/src/pages/AgentDetail.tsx +++ b/ui/src/pages/AgentDetail.tsx @@ -1233,7 +1233,7 @@ function AgentSkillsTab({ }) { type SkillRow = { id: string; - slug: string; + key: string; name: string; description: string | null; detail: string | null; @@ -1316,50 +1316,50 @@ function AgentSkillsTab({ return () => window.clearTimeout(timeout); }, [skillDraft, skillSnapshot, syncSkills.isPending, syncSkills.mutate]); - const companySkillBySlug = useMemo( - () => new Map((companySkills ?? []).map((skill) => [skill.slug, skill])), + const companySkillByKey = useMemo( + () => new Map((companySkills ?? []).map((skill) => [skill.key, skill])), [companySkills], ); - const adapterEntryByName = useMemo( - () => new Map((skillSnapshot?.entries ?? []).map((entry) => [entry.name, entry])), + const adapterEntryByKey = useMemo( + () => new Map((skillSnapshot?.entries ?? []).map((entry) => [entry.key, entry])), [skillSnapshot], ); const optionalSkillRows = useMemo( () => (companySkills ?? []) - .filter((skill) => !adapterEntryByName.get(skill.slug)?.required) + .filter((skill) => !adapterEntryByKey.get(skill.key)?.required) .map((skill) => ({ id: skill.id, - slug: skill.slug, + key: skill.key, name: skill.name, description: skill.description, - detail: adapterEntryByName.get(skill.slug)?.detail ?? null, + detail: adapterEntryByKey.get(skill.key)?.detail ?? null, linkTo: `/skills/${skill.id}`, - adapterEntry: adapterEntryByName.get(skill.slug) ?? null, + adapterEntry: adapterEntryByKey.get(skill.key) ?? null, })), - [adapterEntryByName, companySkills], + [adapterEntryByKey, companySkills], ); const requiredSkillRows = useMemo( () => (skillSnapshot?.entries ?? []) .filter((entry) => entry.required) .map((entry) => { - const companySkill = companySkillBySlug.get(entry.name); + const companySkill = companySkillByKey.get(entry.key); return { - id: companySkill?.id ?? `required:${entry.name}`, - slug: entry.name, - name: companySkill?.name ?? entry.name, + id: companySkill?.id ?? `required:${entry.key}`, + key: entry.key, + name: companySkill?.name ?? entry.key, description: companySkill?.description ?? null, detail: entry.detail ?? null, linkTo: companySkill ? `/skills/${companySkill.id}` : null, adapterEntry: entry, }; }), - [companySkillBySlug, skillSnapshot], + [companySkillByKey, skillSnapshot], ); const desiredOnlyMissingSkills = useMemo( - () => skillDraft.filter((slug) => !companySkillBySlug.has(slug)), - [companySkillBySlug, skillDraft], + () => skillDraft.filter((key) => !companySkillByKey.has(key)), + [companySkillByKey, skillDraft], ); const skillApplicationLabel = useMemo(() => { switch (skillSnapshot?.mode) { @@ -1424,9 +1424,9 @@ function AgentSkillsTab({ <> {(() => { const renderSkillRow = (skill: SkillRow) => { - const adapterEntry = skill.adapterEntry ?? adapterEntryByName.get(skill.slug); + const adapterEntry = skill.adapterEntry ?? adapterEntryByKey.get(skill.key); const required = Boolean(adapterEntry?.required); - const checked = required || skillDraft.includes(skill.slug); + const checked = required || skillDraft.includes(skill.key); const disabled = required || skillSnapshot?.mode === "unsupported"; const checkbox = ( { const next = event.target.checked - ? Array.from(new Set([...skillDraft, skill.slug])) - : skillDraft.filter((value) => value !== skill.slug); + ? Array.from(new Set([...skillDraft, skill.key])) + : skillDraft.filter((value) => value !== skill.key); setSkillDraft(next); }} className="mt-0.5 disabled:cursor-not-allowed disabled:opacity-60" @@ -1468,7 +1468,10 @@ function AgentSkillsTab({ )}
- {skill.name} +
+ {skill.name} +
{skill.key}
+
{skill.linkTo ? ( skill.key === skillKey || skill.slug === skillKey, + ); + const skillPath = manifestSkill?.path ?? `skills/${skillKey}/SKILL.md`; if (!(skillPath in exportData.files)) return; - // Select the file and expand parent dirs setSelectedFile(skillPath); setExpandedDirs((prev) => { const next = new Set(prev); next.add("skills"); - next.add(`skills/${skillSlug}`); + const parts = skillPath.split("/").slice(0, -1); + let current = ""; + for (const part of parts) { + current = current ? `${current}/${part}` : part; + next.add(current); + } return next; }); } diff --git a/ui/src/pages/CompanySkills.tsx b/ui/src/pages/CompanySkills.tsx index 09cde440..1a0d576a 100644 --- a/ui/src/pages/CompanySkills.tsx +++ b/ui/src/pages/CompanySkills.tsx @@ -389,7 +389,7 @@ function SkillList({ onSelectPath: (skillId: string, path: string) => void; }) { const filteredSkills = skills.filter((skill) => { - const haystack = `${skill.name} ${skill.slug} ${skill.sourceLabel ?? ""}`.toLowerCase(); + const haystack = `${skill.name} ${skill.key} ${skill.slug} ${skill.sourceLabel ?? ""}`.toLowerCase(); return haystack.includes(skillFilter.toLowerCase()); }); @@ -435,6 +435,9 @@ function SkillList({ {skill.name} + + {skill.key} +