diff --git a/cli/src/__tests__/company-import-url.test.ts b/cli/src/__tests__/company-import-url.test.ts new file mode 100644 index 00000000..a749d57e --- /dev/null +++ b/cli/src/__tests__/company-import-url.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from "vitest"; +import { isHttpUrl, isGithubUrl } from "../commands/client/company.js"; + +describe("isHttpUrl", () => { + it("matches http URLs", () => { + expect(isHttpUrl("http://example.com/foo")).toBe(true); + }); + + it("matches https URLs", () => { + expect(isHttpUrl("https://example.com/foo")).toBe(true); + }); + + it("rejects local paths", () => { + expect(isHttpUrl("/tmp/my-company")).toBe(false); + expect(isHttpUrl("./relative")).toBe(false); + }); +}); + +describe("isGithubUrl", () => { + it("matches GitHub URLs", () => { + expect(isGithubUrl("https://github.com/org/repo")).toBe(true); + }); + + it("rejects non-GitHub HTTP URLs", () => { + expect(isGithubUrl("https://example.com/foo")).toBe(false); + }); + + it("rejects local paths", () => { + expect(isGithubUrl("/tmp/my-company")).toBe(false); + }); +}); diff --git a/cli/src/commands/client/company.ts b/cli/src/commands/client/company.ts index 05cba06e..01de4548 100644 --- a/cli/src/commands/client/company.ts +++ b/cli/src/commands/client/company.ts @@ -34,6 +34,7 @@ interface CompanyDeleteOptions extends BaseClientOptions { interface CompanyExportOptions extends BaseClientOptions { out?: string; include?: string; + skills?: string; projects?: string; issues?: string; projectIssues?: string; @@ -84,16 +85,17 @@ function normalizeSelector(input: string): string { } function parseInclude(input: string | undefined): CompanyPortabilityInclude { - if (!input || !input.trim()) return { company: true, agents: true, projects: false, issues: false }; + if (!input || !input.trim()) return { company: true, agents: true, projects: false, issues: false, skills: false }; const values = input.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean); const include = { company: values.includes("company"), agents: values.includes("agents"), projects: values.includes("projects"), - issues: values.includes("issues"), + issues: values.includes("issues") || values.includes("tasks"), + skills: values.includes("skills"), }; - if (!include.company && !include.agents && !include.projects && !include.issues) { - throw new Error("Invalid --include value. Use one or more of: company,agents,projects,issues"); + if (!include.company && !include.agents && !include.projects && !include.issues && !include.skills) { + throw new Error("Invalid --include value. Use one or more of: company,agents,projects,issues,tasks,skills"); } return include; } @@ -112,11 +114,11 @@ function parseCsvValues(input: string | undefined): string[] { return Array.from(new Set(input.split(",").map((part) => part.trim()).filter(Boolean))); } -function isHttpUrl(input: string): boolean { +export function isHttpUrl(input: string): boolean { return /^https?:\/\//i.test(input.trim()); } -function isGithubUrl(input: string): boolean { +export function isGithubUrl(input: string): boolean { return /^https?:\/\/github\.com\//i.test(input.trim()); } @@ -336,7 +338,8 @@ export function registerCompanyCommands(program: Command): void { .description("Export a company into a portable markdown package") .argument("", "Company ID") .requiredOption("--out ", "Output directory") - .option("--include ", "Comma-separated include set: company,agents,projects,issues", "company,agents") + .option("--include ", "Comma-separated include set: company,agents,projects,issues,tasks,skills", "company,agents") + .option("--skills ", "Comma-separated skill slugs/keys to export") .option("--projects ", "Comma-separated project shortnames/ids to export") .option("--issues ", "Comma-separated issue identifiers/ids to export") .option("--project-issues ", "Comma-separated project shortnames/ids whose issues should be exported") @@ -349,6 +352,7 @@ export function registerCompanyCommands(program: Command): void { `/api/companies/${companyId}/export`, { include, + skills: parseCsvValues(opts.skills), projects: parseCsvValues(opts.projects), issues: parseCsvValues(opts.issues), projectIssues: parseCsvValues(opts.projectIssues), @@ -387,7 +391,7 @@ export function registerCompanyCommands(program: Command): void { .command("import") .description("Import a portable markdown company package from local path, URL, or GitHub") .requiredOption("--from ", "Source path or URL") - .option("--include ", "Comma-separated include set: company,agents,projects,issues", "company,agents") + .option("--include ", "Comma-separated include set: company,agents,projects,issues,tasks,skills", "company,agents") .option("--target ", "Target mode: new | existing") .option("-C, --company-id ", "Existing target company ID") .option("--new-company-name ", "Name override for --target new") @@ -433,13 +437,16 @@ export function registerCompanyCommands(program: Command): void { let sourcePayload: | { type: "inline"; rootPath?: string | null; files: Record } - | { type: "url"; url: string } | { type: "github"; url: string }; if (isHttpUrl(from)) { - sourcePayload = isGithubUrl(from) - ? { type: "github", url: from } - : { type: "url", url: from }; + if (!isGithubUrl(from)) { + throw new Error( + "Only GitHub URLs and local paths are supported for import. " + + "Generic HTTP URLs are not supported. Use a GitHub URL (https://github.com/...) or a local directory path.", + ); + } + sourcePayload = { type: "github", url: from }; } else { const inline = await resolveInlineSourceFromPath(from); sourcePayload = { diff --git a/doc/AGENTCOMPANIES_SPEC_INVENTORY.md b/doc/AGENTCOMPANIES_SPEC_INVENTORY.md new file mode 100644 index 00000000..0d622890 --- /dev/null +++ b/doc/AGENTCOMPANIES_SPEC_INVENTORY.md @@ -0,0 +1,114 @@ +# Agent Companies Spec Inventory + +This document indexes every part of the Paperclip codebase that touches the [Agent Companies Specification](docs/companies/companies-spec.md) (`agentcompanies/v1-draft`). + +Use it when you need to: + +1. **Update the spec** — know which implementation code must change in lockstep. +2. **Change code that involves the spec** — find all related files quickly. +3. **Keep things aligned** — audit whether implementation matches the spec. + +--- + +## 1. Specification & Design Documents + +| File | Role | +|---|---| +| `docs/companies/companies-spec.md` | **Normative spec** — defines the markdown-first package format (COMPANY.md, TEAM.md, AGENTS.md, PROJECT.md, TASK.md, SKILL.md), reserved files, frontmatter schemas, and vendor extension conventions (`.paperclip.yaml`). | +| `doc/plans/2026-03-13-company-import-export-v2.md` | Implementation plan for the markdown-first package model cutover — phases, API changes, UI plan, and rollout strategy. | +| `doc/SPEC-implementation.md` | V1 implementation contract; references the portability system and `.paperclip.yaml` sidecar format. | +| `docs/specs/cliphub-plan.md` | Earlier blueprint bundle plan; partially superseded by the markdown-first spec (noted in the v2 plan). | +| `doc/plans/2026-02-16-module-system.md` | Module system plan; JSON-only company template sections superseded by the markdown-first model. | +| `doc/plans/2026-03-14-skills-ui-product-plan.md` | Skills UI plan; references portable skill files and `.paperclip.yaml`. | +| `doc/plans/2026-03-14-adapter-skill-sync-rollout.md` | Adapter skill sync rollout; companion to the v2 import/export plan. | + +## 2. Shared Types & Validators + +These define the contract between server, CLI, and UI. + +| File | What it defines | +|---|---| +| `packages/shared/src/types/company-portability.ts` | TypeScript interfaces: `CompanyPortabilityManifest`, `CompanyPortabilityFileEntry`, `CompanyPortabilityEnvInput`, export/import/preview request and result types, manifest entry types for agents, skills, projects, issues, companies. | +| `packages/shared/src/validators/company-portability.ts` | Zod schemas for all portability request/response shapes — used by both server routes and CLI. | +| `packages/shared/src/types/index.ts` | Re-exports portability types. | +| `packages/shared/src/validators/index.ts` | Re-exports portability validators. | + +## 3. Server — Services + +| File | Responsibility | +|---|---| +| `server/src/services/company-portability.ts` | **Core portability service.** Export (manifest generation, markdown file emission, `.paperclip.yaml` sidecars), import (graph resolution, collision handling, entity creation), preview (planned-action summary). Handles skill key derivation, task recurrence parsing, and package README generation. References `agentcompanies/v1` version string. | +| `server/src/services/company-export-readme.ts` | Generates `README.md` and Mermaid org-chart for exported company packages. | +| `server/src/services/index.ts` | Re-exports `companyPortabilityService`. | + +## 4. Server — Routes + +| File | Endpoints | +|---|---| +| `server/src/routes/companies.ts` | `POST /api/companies/:companyId/export` — legacy export bundle
`POST /api/companies/:companyId/exports/preview` — export preview
`POST /api/companies/:companyId/exports` — export package
`POST /api/companies/import/preview` — import preview
`POST /api/companies/import` — perform import | + +Route registration lives in `server/src/app.ts` via `companyRoutes(db, storage)`. + +## 5. Server — Tests + +| File | Coverage | +|---|---| +| `server/src/__tests__/company-portability.test.ts` | Unit tests for the portability service (export, import, preview, manifest shape, `agentcompanies/v1` version). | +| `server/src/__tests__/company-portability-routes.test.ts` | Integration tests for the portability HTTP endpoints. | + +## 6. CLI + +| File | Commands | +|---|---| +| `cli/src/commands/client/company.ts` | `company export` — exports a company package to disk (flags: `--out`, `--include`, `--projects`, `--issues`, `--projectIssues`).
`company import` — imports a company package from a file or folder (flags: `--from`, `--include`, `--target`, `--companyId`, `--newCompanyName`, `--agents`, `--collision`, `--dryRun`).
Reads/writes portable file entries and handles `.paperclip.yaml` filtering. | + +## 7. UI — Pages + +| File | Role | +|---|---| +| `ui/src/pages/CompanyExport.tsx` | Export UI: preview, manifest display, file tree visualization, ZIP archive creation and download. Filters `.paperclip.yaml` based on selection. Shows manifest and README in editor. | +| `ui/src/pages/CompanyImport.tsx` | Import UI: source input (upload/folder/GitHub URL/generic URL), ZIP reading, preview pane with dependency tree, entity selection checkboxes, trust/licensing warnings, secrets requirements, collision strategy, adapter config. | + +## 8. UI — Components + +| File | Role | +|---|---| +| `ui/src/components/PackageFileTree.tsx` | Reusable file tree component for both import and export. Builds tree from `CompanyPortabilityFileEntry` items, parses frontmatter, shows action indicators (create/update/skip), and maps frontmatter field labels. | + +## 9. UI — Libraries + +| File | Role | +|---|---| +| `ui/src/lib/portable-files.ts` | Helpers for portable file entries: `getPortableFileText`, `getPortableFileDataUrl`, `getPortableFileContentType`, `isPortableImageFile`. | +| `ui/src/lib/zip.ts` | ZIP archive creation (`createZipArchive`) and reading (`readZipArchive`) — implements ZIP format from scratch for company packages. CRC32, DOS date/time encoding. | +| `ui/src/lib/zip.test.ts` | Tests for ZIP utilities; exercises round-trip with portability file entries and `.paperclip.yaml` content. | + +## 10. UI — API Client + +| File | Functions | +|---|---| +| `ui/src/api/companies.ts` | `companiesApi.exportBundle`, `companiesApi.exportPreview`, `companiesApi.exportPackage`, `companiesApi.importPreview`, `companiesApi.importBundle` — typed fetch wrappers for the portability endpoints. | + +## 11. Skills & Agent Instructions + +| File | Relevance | +|---|---| +| `skills/paperclip/references/company-skills.md` | Reference doc for company skill library workflow — install, inspect, update, assign. Skill packages are a subset of the agent companies spec. | +| `server/src/services/company-skills.ts` | Company skill management service — handles SKILL.md-based imports and company-level skill library. | +| `server/src/services/agent-instructions.ts` | Agent instructions service — resolves AGENTS.md paths for agent instruction loading. | + +## 12. Quick Cross-Reference by Spec Concept + +| Spec concept | Primary implementation files | +|---|---| +| `COMPANY.md` frontmatter & body | `company-portability.ts` (export emitter + import parser) | +| `AGENTS.md` frontmatter & body | `company-portability.ts`, `agent-instructions.ts` | +| `PROJECT.md` frontmatter & body | `company-portability.ts` | +| `TASK.md` frontmatter & body | `company-portability.ts` | +| `SKILL.md` packages | `company-portability.ts`, `company-skills.ts` | +| `.paperclip.yaml` vendor sidecar | `company-portability.ts`, `CompanyExport.tsx`, `company.ts` (CLI) | +| `manifest.json` | `company-portability.ts` (generation), shared types (schema) | +| ZIP package format | `zip.ts` (UI), `company.ts` (CLI file I/O) | +| Collision resolution | `company-portability.ts` (server), `CompanyImport.tsx` (UI) | +| Env/secrets declarations | shared types (`CompanyPortabilityEnvInput`), `CompanyImport.tsx` (UI) | +| README + org chart | `company-export-readme.ts` | diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index b39839c1..7d98cd6d 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -39,6 +39,8 @@ This starts: `pnpm dev` runs the server in watch mode and restarts on changes from workspace packages (including adapter packages). Use `pnpm dev:once` to run without file watching. +`pnpm dev:once` now tracks backend-relevant file changes and pending migrations. When the current boot is stale, the board UI shows a `Restart required` banner. You can also enable guarded auto-restart in `Instance Settings > Experimental`, which waits for queued/running local agent runs to finish before restarting the dev server. + Tailscale/private-auth dev mode: ```sh @@ -128,6 +130,10 @@ When a local agent run has no resolved project/session workspace, Paperclip fall This path honors `PAPERCLIP_HOME` and `PAPERCLIP_INSTANCE_ID` in non-default setups. +For `codex_local`, Paperclip also manages a per-company Codex home under the instance root and seeds it from the shared Codex login/config home (`$CODEX_HOME` or `~/.codex`): + +- `~/.paperclip/instances/default/companies//codex-home` + ## Worktree-local Instances When developing from multiple git worktrees, do not point two Paperclip servers at the same embedded PostgreSQL data directory. diff --git a/docs/start/quickstart.md b/docs/start/quickstart.md index 9488b3c7..1ad30fcd 100644 --- a/docs/start/quickstart.md +++ b/docs/start/quickstart.md @@ -13,9 +13,19 @@ npx paperclipai onboard --yes This walks you through setup, configures your environment, and gets Paperclip running. +To start Paperclip again later: + +```sh +npx paperclipai run +``` + +> **Note:** If you used `npx` for setup, always use `npx paperclipai` to run commands. The `pnpm paperclipai` form only works inside a cloned copy of the Paperclip repository (see Local Development below). + ## Local Development -Prerequisites: Node.js 20+ and pnpm 9+. +For contributors working on Paperclip itself. Prerequisites: Node.js 20+ and pnpm 9+. + +Clone the repository, then: ```sh pnpm install @@ -26,7 +36,7 @@ This starts the API server and UI at [http://localhost:3100](http://localhost:31 No external database required — Paperclip uses an embedded PostgreSQL instance by default. -## One-Command Bootstrap +When working from the cloned repo, you can also use: ```sh pnpm paperclipai run diff --git a/packages/adapter-utils/src/log-redaction.ts b/packages/adapter-utils/src/log-redaction.ts index 037e279e..6c5554e1 100644 --- a/packages/adapter-utils/src/log-redaction.ts +++ b/packages/adapter-utils/src/log-redaction.ts @@ -1,19 +1,29 @@ import type { TranscriptEntry } from "./types.js"; -export const REDACTED_HOME_PATH_USER = "[]"; +export const REDACTED_HOME_PATH_USER = "*"; + +export interface HomePathRedactionOptions { + enabled?: boolean; +} + +function maskHomePathUserSegment(value: string) { + const trimmed = value.trim(); + if (!trimmed) return REDACTED_HOME_PATH_USER; + return `${trimmed[0]}${"*".repeat(Math.max(1, Array.from(trimmed).length - 1))}`; +} const HOME_PATH_PATTERNS = [ { - regex: /\/Users\/[^/\\\s]+/g, - replace: `/Users/${REDACTED_HOME_PATH_USER}`, + regex: /\/Users\/([^/\\\s]+)/g, + replace: (_match: string, user: string) => `/Users/${maskHomePathUserSegment(user)}`, }, { - regex: /\/home\/[^/\\\s]+/g, - replace: `/home/${REDACTED_HOME_PATH_USER}`, + regex: /\/home\/([^/\\\s]+)/g, + replace: (_match: string, user: string) => `/home/${maskHomePathUserSegment(user)}`, }, { - regex: /([A-Za-z]:\\Users\\)[^\\/\s]+/g, - replace: `$1${REDACTED_HOME_PATH_USER}`, + regex: /([A-Za-z]:\\Users\\)([^\\/\s]+)/g, + replace: (_match: string, prefix: string, user: string) => `${prefix}${maskHomePathUserSegment(user)}`, }, ] as const; @@ -23,7 +33,8 @@ function isPlainObject(value: unknown): value is Record { return proto === Object.prototype || proto === null; } -export function redactHomePathUserSegments(text: string): string { +export function redactHomePathUserSegments(text: string, opts?: HomePathRedactionOptions): string { + if (opts?.enabled === false) return text; let result = text; for (const pattern of HOME_PATH_PATTERNS) { result = result.replace(pattern.regex, pattern.replace); @@ -31,12 +42,12 @@ export function redactHomePathUserSegments(text: string): string { return result; } -export function redactHomePathUserSegmentsInValue(value: T): T { +export function redactHomePathUserSegmentsInValue(value: T, opts?: HomePathRedactionOptions): T { if (typeof value === "string") { - return redactHomePathUserSegments(value) as T; + return redactHomePathUserSegments(value, opts) as T; } if (Array.isArray(value)) { - return value.map((entry) => redactHomePathUserSegmentsInValue(entry)) as T; + return value.map((entry) => redactHomePathUserSegmentsInValue(entry, opts)) as T; } if (!isPlainObject(value)) { return value; @@ -44,12 +55,12 @@ export function redactHomePathUserSegmentsInValue(value: T): T { const redacted: Record = {}; for (const [key, entry] of Object.entries(value)) { - redacted[key] = redactHomePathUserSegmentsInValue(entry); + redacted[key] = redactHomePathUserSegmentsInValue(entry, opts); } return redacted as T; } -export function redactTranscriptEntryPaths(entry: TranscriptEntry): TranscriptEntry { +export function redactTranscriptEntryPaths(entry: TranscriptEntry, opts?: HomePathRedactionOptions): TranscriptEntry { switch (entry.kind) { case "assistant": case "thinking": @@ -57,23 +68,27 @@ export function redactTranscriptEntryPaths(entry: TranscriptEntry): TranscriptEn case "stderr": case "system": case "stdout": - return { ...entry, text: redactHomePathUserSegments(entry.text) }; + return { ...entry, text: redactHomePathUserSegments(entry.text, opts) }; case "tool_call": - return { ...entry, name: redactHomePathUserSegments(entry.name), input: redactHomePathUserSegmentsInValue(entry.input) }; + return { + ...entry, + name: redactHomePathUserSegments(entry.name, opts), + input: redactHomePathUserSegmentsInValue(entry.input, opts), + }; case "tool_result": - return { ...entry, content: redactHomePathUserSegments(entry.content) }; + return { ...entry, content: redactHomePathUserSegments(entry.content, opts) }; case "init": return { ...entry, - model: redactHomePathUserSegments(entry.model), - sessionId: redactHomePathUserSegments(entry.sessionId), + model: redactHomePathUserSegments(entry.model, opts), + sessionId: redactHomePathUserSegments(entry.sessionId, opts), }; case "result": return { ...entry, - text: redactHomePathUserSegments(entry.text), - subtype: redactHomePathUserSegments(entry.subtype), - errors: entry.errors.map((error) => redactHomePathUserSegments(error)), + text: redactHomePathUserSegments(entry.text, opts), + subtype: redactHomePathUserSegments(entry.subtype, opts), + errors: entry.errors.map((error) => redactHomePathUserSegments(error, opts)), }; default: return entry; diff --git a/packages/adapters/codex-local/src/index.ts b/packages/adapters/codex-local/src/index.ts index 7a0aea51..0d881a2b 100644 --- a/packages/adapters/codex-local/src/index.ts +++ b/packages/adapters/codex-local/src/index.ts @@ -41,6 +41,7 @@ Operational fields: Notes: - Prompts are piped via stdin (Codex receives "-" prompt argument). - Paperclip injects desired local skills into the active workspace's ".agents/skills" directory at execution time so Codex can discover "$paperclip" and related skills without coupling them to the user's login home. +- Unless explicitly overridden in adapter config, Paperclip runs Codex with a per-company managed CODEX_HOME under the active Paperclip instance and seeds auth/config from the shared Codex home (the CODEX_HOME env var, when set, or ~/.codex). - Some model/tool combinations reject certain effort levels (for example minimal with web search enabled). - When Paperclip realizes a workspace/runtime for a run, it injects PAPERCLIP_WORKSPACE_* and PAPERCLIP_RUNTIME_* env vars for agent-side tooling. `; diff --git a/packages/adapters/codex-local/src/server/codex-home.ts b/packages/adapters/codex-local/src/server/codex-home.ts index 774a0b35..c032fd24 100644 --- a/packages/adapters/codex-local/src/server/codex-home.ts +++ b/packages/adapters/codex-local/src/server/codex-home.ts @@ -6,6 +6,7 @@ import type { AdapterExecutionContext } from "@paperclipai/adapter-utils"; const TRUTHY_ENV_RE = /^(1|true|yes|on)$/i; const COPIED_SHARED_FILES = ["config.json", "config.toml", "instructions.md"] as const; const SYMLINKED_SHARED_FILES = ["auth.json"] as const; +const DEFAULT_PAPERCLIP_INSTANCE_ID = "default"; function nonEmpty(value: string | undefined): string | null { return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; @@ -15,35 +16,26 @@ export async function pathExists(candidate: string): Promise { return fs.access(candidate).then(() => true).catch(() => false); } -export function resolveCodexHomeDir( +export function resolveSharedCodexHomeDir( env: NodeJS.ProcessEnv = process.env, - companyId?: string, ): string { const fromEnv = nonEmpty(env.CODEX_HOME); - const baseHome = fromEnv ? path.resolve(fromEnv) : path.join(os.homedir(), ".codex"); - return companyId ? path.join(baseHome, "companies", companyId) : baseHome; + return fromEnv ? path.resolve(fromEnv) : path.join(os.homedir(), ".codex"); } function isWorktreeMode(env: NodeJS.ProcessEnv): boolean { return TRUTHY_ENV_RE.test(env.PAPERCLIP_IN_WORKTREE ?? ""); } -function resolveWorktreeCodexHomeDir( +export function resolveManagedCodexHomeDir( env: NodeJS.ProcessEnv, companyId?: string, -): string | null { - if (!isWorktreeMode(env)) return null; - const paperclipHome = nonEmpty(env.PAPERCLIP_HOME); - if (!paperclipHome) return null; - const instanceId = nonEmpty(env.PAPERCLIP_INSTANCE_ID); - if (instanceId) { - return companyId - ? path.resolve(paperclipHome, "instances", instanceId, "companies", companyId, "codex-home") - : path.resolve(paperclipHome, "instances", instanceId, "codex-home"); - } +): string { + const paperclipHome = nonEmpty(env.PAPERCLIP_HOME) ?? path.resolve(os.homedir(), ".paperclip"); + const instanceId = nonEmpty(env.PAPERCLIP_INSTANCE_ID) ?? DEFAULT_PAPERCLIP_INSTANCE_ID; return companyId - ? path.resolve(paperclipHome, "companies", companyId, "codex-home") - : path.resolve(paperclipHome, "codex-home"); + ? path.resolve(paperclipHome, "instances", instanceId, "companies", companyId, "codex-home") + : path.resolve(paperclipHome, "instances", instanceId, "codex-home"); } async function ensureParentDir(target: string): Promise { @@ -79,15 +71,14 @@ async function ensureCopiedFile(target: string, source: string): Promise { await fs.copyFile(source, target); } -export async function prepareWorktreeCodexHome( +export async function prepareManagedCodexHome( env: NodeJS.ProcessEnv, onLog: AdapterExecutionContext["onLog"], companyId?: string, -): Promise { - const targetHome = resolveWorktreeCodexHomeDir(env, companyId); - if (!targetHome) return null; +): Promise { + const targetHome = resolveManagedCodexHomeDir(env, companyId); - const sourceHome = resolveCodexHomeDir(env); + const sourceHome = resolveSharedCodexHomeDir(env); if (path.resolve(sourceHome) === path.resolve(targetHome)) return targetHome; await fs.mkdir(targetHome, { recursive: true }); @@ -106,7 +97,7 @@ export async function prepareWorktreeCodexHome( await onLog( "stdout", - `[paperclip] Using worktree-isolated Codex home "${targetHome}" (seeded from "${sourceHome}").\n`, + `[paperclip] Using ${isWorktreeMode(env) ? "worktree-isolated" : "Paperclip-managed"} Codex home "${targetHome}" (seeded from "${sourceHome}").\n`, ); return targetHome; } diff --git a/packages/adapters/codex-local/src/server/execute.ts b/packages/adapters/codex-local/src/server/execute.ts index 79d5a2ed..b6bda8df 100644 --- a/packages/adapters/codex-local/src/server/execute.ts +++ b/packages/adapters/codex-local/src/server/execute.ts @@ -21,7 +21,7 @@ import { runChildProcess, } from "@paperclipai/adapter-utils/server-utils"; import { parseCodexJsonl, isCodexUnknownSessionError } from "./parse.js"; -import { pathExists, prepareWorktreeCodexHome, resolveCodexHomeDir } from "./codex-home.js"; +import { pathExists, prepareManagedCodexHome, resolveManagedCodexHomeDir } from "./codex-home.js"; import { resolveCodexDesiredSkillNames } from "./skills.js"; const __moduleDir = path.dirname(fileURLToPath(import.meta.url)); @@ -268,10 +268,11 @@ export async function execute(ctx: AdapterExecutionContext): Promise, ts: string): Transcr .filter((change): change is Record => Boolean(change)) .map((change) => { const kind = asString(change.kind, "update"); - const path = redactHomePathUserSegments(asString(change.path, "unknown")); + const path = asString(change.path, "unknown"); return `${kind} ${path}`; }); @@ -131,13 +127,13 @@ function parseCodexItem( if (itemType === "agent_message") { const text = asString(item.text); - if (text) return [{ kind: "assistant", ts, text: redactHomePathUserSegments(text) }]; + if (text) return [{ kind: "assistant", ts, text }]; return []; } if (itemType === "reasoning") { const text = asString(item.text); - if (text) return [{ kind: "thinking", ts, text: redactHomePathUserSegments(text) }]; + if (text) return [{ kind: "thinking", ts, text }]; return [{ kind: "system", ts, text: phase === "started" ? "reasoning started" : "reasoning completed" }]; } @@ -153,9 +149,9 @@ function parseCodexItem( return [{ kind: "tool_call", ts, - name: redactHomePathUserSegments(asString(item.name, "unknown")), + name: asString(item.name, "unknown"), toolUseId: asString(item.id), - input: redactHomePathUserSegmentsInValue(item.input ?? {}), + input: item.input ?? {}, }]; } @@ -167,12 +163,12 @@ function parseCodexItem( asString(item.result) || stringifyUnknown(item.content ?? item.output ?? item.result); const isError = item.is_error === true || asString(item.status) === "error"; - return [{ kind: "tool_result", ts, toolUseId, content: redactHomePathUserSegments(content), isError }]; + return [{ kind: "tool_result", ts, toolUseId, content, isError }]; } if (itemType === "error" && phase === "completed") { const text = errorText(item.message ?? item.error ?? item); - return [{ kind: "stderr", ts, text: redactHomePathUserSegments(text || "error") }]; + return [{ kind: "stderr", ts, text: text || "error" }]; } const id = asString(item.id); @@ -181,14 +177,14 @@ function parseCodexItem( return [{ kind: "system", ts, - text: redactHomePathUserSegments(`item ${phase}: ${itemType || "unknown"}${meta ? ` (${meta})` : ""}`), + text: `item ${phase}: ${itemType || "unknown"}${meta ? ` (${meta})` : ""}`, }]; } export function parseCodexStdoutLine(line: string, ts: string): TranscriptEntry[] { const parsed = asRecord(safeJsonParse(line)); if (!parsed) { - return [{ kind: "stdout", ts, text: redactHomePathUserSegments(line) }]; + return [{ kind: "stdout", ts, text: line }]; } const type = asString(parsed.type); @@ -198,8 +194,8 @@ export function parseCodexStdoutLine(line: string, ts: string): TranscriptEntry[ return [{ kind: "init", ts, - model: redactHomePathUserSegments(asString(parsed.model, "codex")), - sessionId: redactHomePathUserSegments(threadId), + model: asString(parsed.model, "codex"), + sessionId: threadId, }]; } @@ -221,15 +217,15 @@ export function parseCodexStdoutLine(line: string, ts: string): TranscriptEntry[ return [{ kind: "result", ts, - text: redactHomePathUserSegments(asString(parsed.result)), + text: asString(parsed.result), inputTokens, outputTokens, cachedTokens, costUsd: asNumber(parsed.total_cost_usd), - subtype: redactHomePathUserSegments(asString(parsed.subtype)), + subtype: asString(parsed.subtype), isError: parsed.is_error === true, errors: Array.isArray(parsed.errors) - ? parsed.errors.map(errorText).map(redactHomePathUserSegments).filter(Boolean) + ? parsed.errors.map(errorText).filter(Boolean) : [], }]; } @@ -243,21 +239,21 @@ export function parseCodexStdoutLine(line: string, ts: string): TranscriptEntry[ return [{ kind: "result", ts, - text: redactHomePathUserSegments(asString(parsed.result)), + text: asString(parsed.result), inputTokens, outputTokens, cachedTokens, costUsd: asNumber(parsed.total_cost_usd), - subtype: redactHomePathUserSegments(asString(parsed.subtype, "turn.failed")), + subtype: asString(parsed.subtype, "turn.failed"), isError: true, - errors: message ? [redactHomePathUserSegments(message)] : [], + errors: message ? [message] : [], }]; } if (type === "error") { const message = errorText(parsed.message ?? parsed.error ?? parsed); - return [{ kind: "stderr", ts, text: redactHomePathUserSegments(message || line) }]; + return [{ kind: "stderr", ts, text: message || line }]; } - return [{ kind: "stdout", ts, text: redactHomePathUserSegments(line) }]; + return [{ kind: "stdout", ts, text: line }]; } diff --git a/packages/adapters/pi-local/src/server/execute.ts b/packages/adapters/pi-local/src/server/execute.ts index f5b35d8f..9cea8089 100644 --- a/packages/adapters/pi-local/src/server/execute.ts +++ b/packages/adapters/pi-local/src/server/execute.ts @@ -80,12 +80,12 @@ async function ensurePiSkillsInjected( if (result === "skipped") continue; await onLog( "stderr", - `[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Pi skill "${entry.key}" into ${PI_AGENT_SKILLS_DIR}\n`, + `[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Pi skill "${entry.runtimeName}" into ${PI_AGENT_SKILLS_DIR}\n`, ); } catch (err) { await onLog( "stderr", - `[paperclip] Failed to inject Pi skill "${entry.key}" into ${PI_AGENT_SKILLS_DIR}: ${err instanceof Error ? err.message : String(err)}\n`, + `[paperclip] Failed to inject Pi skill "${entry.runtimeName}" into ${PI_AGENT_SKILLS_DIR}: ${err instanceof Error ? err.message : String(err)}\n`, ); } } diff --git a/packages/db/src/migrations/0030_friendly_loners.sql b/packages/db/src/migrations/0030_friendly_loners.sql deleted file mode 100644 index 8064af5f..00000000 --- a/packages/db/src/migrations/0030_friendly_loners.sql +++ /dev/null @@ -1,43 +0,0 @@ -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 - FROM information_schema.tables - WHERE table_schema = 'public' AND table_name = 'company_skills' - ) THEN - CREATE TABLE "company_skills" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "company_id" uuid NOT NULL, - "slug" text NOT NULL, - "name" text NOT NULL, - "description" text, - "markdown" text NOT NULL, - "source_type" text DEFAULT 'local_path' NOT NULL, - "source_locator" text, - "source_ref" text, - "trust_level" text DEFAULT 'markdown_only' NOT NULL, - "compatibility" text DEFAULT 'compatible' NOT NULL, - "file_inventory" jsonb DEFAULT '[]'::jsonb NOT NULL, - "metadata" jsonb, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL - ); - END IF; -END $$; ---> statement-breakpoint -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 - FROM pg_constraint - WHERE conname = 'company_skills_company_id_companies_id_fk' - ) THEN - ALTER TABLE "company_skills" - ADD CONSTRAINT "company_skills_company_id_companies_id_fk" - FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") - ON DELETE no action ON UPDATE no action; - END IF; -END $$; ---> statement-breakpoint -CREATE UNIQUE INDEX IF NOT EXISTS "company_skills_company_slug_idx" ON "company_skills" USING btree ("company_id","slug");--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "company_skills_company_name_idx" ON "company_skills" USING btree ("company_id","name"); diff --git a/packages/db/src/migrations/0035_colorful_rhino.sql b/packages/db/src/migrations/0035_colorful_rhino.sql deleted file mode 100644 index 870b03ac..00000000 --- a/packages/db/src/migrations/0035_colorful_rhino.sql +++ /dev/null @@ -1,27 +0,0 @@ -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/0039_curly_maria_hill.sql b/packages/db/src/migrations/0039_curly_maria_hill.sql new file mode 100644 index 00000000..178f6546 --- /dev/null +++ b/packages/db/src/migrations/0039_curly_maria_hill.sql @@ -0,0 +1 @@ +ALTER TABLE "instance_settings" ADD COLUMN "general" jsonb DEFAULT '{}'::jsonb NOT NULL; \ No newline at end of file diff --git a/packages/db/src/migrations/0040_spotty_the_renegades.sql b/packages/db/src/migrations/0040_spotty_the_renegades.sql new file mode 100644 index 00000000..8c4c0b04 --- /dev/null +++ b/packages/db/src/migrations/0040_spotty_the_renegades.sql @@ -0,0 +1,22 @@ +CREATE TABLE "company_skills" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "key" text NOT NULL, + "slug" text NOT NULL, + "name" text NOT NULL, + "description" text, + "markdown" text NOT NULL, + "source_type" text DEFAULT 'local_path' NOT NULL, + "source_locator" text, + "source_ref" text, + "trust_level" text DEFAULT 'markdown_only' NOT NULL, + "compatibility" text DEFAULT 'compatible' NOT NULL, + "file_inventory" jsonb DEFAULT '[]'::jsonb NOT NULL, + "metadata" jsonb, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "company_skills" ADD CONSTRAINT "company_skills_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "company_skills_company_key_idx" ON "company_skills" USING btree ("company_id","key");--> statement-breakpoint +CREATE INDEX "company_skills_company_name_idx" ON "company_skills" USING btree ("company_id","name"); \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0039_snapshot.json b/packages/db/src/migrations/meta/0039_snapshot.json index 1d092919..084fc881 100644 --- a/packages/db/src/migrations/meta/0039_snapshot.json +++ b/packages/db/src/migrations/meta/0039_snapshot.json @@ -1,6 +1,6 @@ { - "id": "70a51031-63ca-4491-8794-54cae9e07c66", - "prevId": "179f76c5-b1e4-4595-afd2-136cb3c0af18", + "id": "1006727d-476b-474c-932b-51f1ba9626fb", + "prevId": "cb7f5c2d-8be7-4bd7-8adc-6d942a4f2589", "version": "7", "dialect": "postgresql", "tables": { @@ -3253,179 +3253,6 @@ "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": "", @@ -5494,6 +5321,13 @@ "notNull": true, "default": "'default'" }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, "experimental": { "name": "experimental", "type": "jsonb", @@ -7196,25 +7030,6 @@ "primaryKey": false, "notNull": false }, - "origin_kind": { - "name": "origin_kind", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'manual'" - }, - "origin_id": { - "name": "origin_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "origin_run_id": { - "name": "origin_run_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, "request_depth": { "name": "request_depth", "type": "integer", @@ -7409,33 +7224,6 @@ "method": "btree", "with": {} }, - "issues_company_origin_idx": { - "name": "issues_company_origin_idx", - "columns": [ - { - "expression": "company_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "origin_kind", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "origin_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, "issues_company_project_workspace_idx": { "name": "issues_company_project_workspace_idx", "columns": [ @@ -7492,34 +7280,6 @@ "concurrently": false, "method": "btree", "with": {} - }, - "issues_open_routine_execution_uq": { - "name": "issues_open_routine_execution_uq", - "columns": [ - { - "expression": "company_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "origin_kind", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "origin_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"issues\".\"origin_kind\" = 'routine_execution'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" in ('backlog', 'todo', 'in_progress', 'in_review', 'blocked')", - "concurrently": false, - "method": "btree", - "with": {} } }, "foreignKeys": { @@ -9903,851 +9663,6 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.routine_runs": { - "name": "routine_runs", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "company_id": { - "name": "company_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "routine_id": { - "name": "routine_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "trigger_id": { - "name": "trigger_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "source": { - "name": "source", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'received'" - }, - "triggered_at": { - "name": "triggered_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "idempotency_key": { - "name": "idempotency_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "trigger_payload": { - "name": "trigger_payload", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "linked_issue_id": { - "name": "linked_issue_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "coalesced_into_run_id": { - "name": "coalesced_into_run_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "failure_reason": { - "name": "failure_reason", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "completed_at": { - "name": "completed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "routine_runs_company_routine_idx": { - "name": "routine_runs_company_routine_idx", - "columns": [ - { - "expression": "company_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "routine_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "routine_runs_trigger_idx": { - "name": "routine_runs_trigger_idx", - "columns": [ - { - "expression": "trigger_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "routine_runs_linked_issue_idx": { - "name": "routine_runs_linked_issue_idx", - "columns": [ - { - "expression": "linked_issue_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "routine_runs_trigger_idempotency_idx": { - "name": "routine_runs_trigger_idempotency_idx", - "columns": [ - { - "expression": "trigger_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "idempotency_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "routine_runs_company_id_companies_id_fk": { - "name": "routine_runs_company_id_companies_id_fk", - "tableFrom": "routine_runs", - "tableTo": "companies", - "columnsFrom": [ - "company_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "routine_runs_routine_id_routines_id_fk": { - "name": "routine_runs_routine_id_routines_id_fk", - "tableFrom": "routine_runs", - "tableTo": "routines", - "columnsFrom": [ - "routine_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "routine_runs_trigger_id_routine_triggers_id_fk": { - "name": "routine_runs_trigger_id_routine_triggers_id_fk", - "tableFrom": "routine_runs", - "tableTo": "routine_triggers", - "columnsFrom": [ - "trigger_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "routine_runs_linked_issue_id_issues_id_fk": { - "name": "routine_runs_linked_issue_id_issues_id_fk", - "tableFrom": "routine_runs", - "tableTo": "issues", - "columnsFrom": [ - "linked_issue_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.routine_triggers": { - "name": "routine_triggers", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "company_id": { - "name": "company_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "routine_id": { - "name": "routine_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "enabled": { - "name": "enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "cron_expression": { - "name": "cron_expression", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "timezone": { - "name": "timezone", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "next_run_at": { - "name": "next_run_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_fired_at": { - "name": "last_fired_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "public_id": { - "name": "public_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "secret_id": { - "name": "secret_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "signing_mode": { - "name": "signing_mode", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "replay_window_sec": { - "name": "replay_window_sec", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "last_rotated_at": { - "name": "last_rotated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_result": { - "name": "last_result", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_by_agent_id": { - "name": "created_by_agent_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "created_by_user_id": { - "name": "created_by_user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "updated_by_agent_id": { - "name": "updated_by_agent_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "updated_by_user_id": { - "name": "updated_by_user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "routine_triggers_company_routine_idx": { - "name": "routine_triggers_company_routine_idx", - "columns": [ - { - "expression": "company_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "routine_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "routine_triggers_company_kind_idx": { - "name": "routine_triggers_company_kind_idx", - "columns": [ - { - "expression": "company_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "routine_triggers_next_run_idx": { - "name": "routine_triggers_next_run_idx", - "columns": [ - { - "expression": "next_run_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "routine_triggers_public_id_idx": { - "name": "routine_triggers_public_id_idx", - "columns": [ - { - "expression": "public_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "routine_triggers_public_id_uq": { - "name": "routine_triggers_public_id_uq", - "columns": [ - { - "expression": "public_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "routine_triggers_company_id_companies_id_fk": { - "name": "routine_triggers_company_id_companies_id_fk", - "tableFrom": "routine_triggers", - "tableTo": "companies", - "columnsFrom": [ - "company_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "routine_triggers_routine_id_routines_id_fk": { - "name": "routine_triggers_routine_id_routines_id_fk", - "tableFrom": "routine_triggers", - "tableTo": "routines", - "columnsFrom": [ - "routine_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "routine_triggers_secret_id_company_secrets_id_fk": { - "name": "routine_triggers_secret_id_company_secrets_id_fk", - "tableFrom": "routine_triggers", - "tableTo": "company_secrets", - "columnsFrom": [ - "secret_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "routine_triggers_created_by_agent_id_agents_id_fk": { - "name": "routine_triggers_created_by_agent_id_agents_id_fk", - "tableFrom": "routine_triggers", - "tableTo": "agents", - "columnsFrom": [ - "created_by_agent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "routine_triggers_updated_by_agent_id_agents_id_fk": { - "name": "routine_triggers_updated_by_agent_id_agents_id_fk", - "tableFrom": "routine_triggers", - "tableTo": "agents", - "columnsFrom": [ - "updated_by_agent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.routines": { - "name": "routines", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "company_id": { - "name": "company_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "project_id": { - "name": "project_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "goal_id": { - "name": "goal_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "parent_issue_id": { - "name": "parent_issue_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "assignee_agent_id": { - "name": "assignee_agent_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "priority": { - "name": "priority", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'medium'" - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'active'" - }, - "concurrency_policy": { - "name": "concurrency_policy", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'coalesce_if_active'" - }, - "catch_up_policy": { - "name": "catch_up_policy", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'skip_missed'" - }, - "created_by_agent_id": { - "name": "created_by_agent_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "created_by_user_id": { - "name": "created_by_user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "updated_by_agent_id": { - "name": "updated_by_agent_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "updated_by_user_id": { - "name": "updated_by_user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_triggered_at": { - "name": "last_triggered_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_enqueued_at": { - "name": "last_enqueued_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "routines_company_status_idx": { - "name": "routines_company_status_idx", - "columns": [ - { - "expression": "company_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "routines_company_assignee_idx": { - "name": "routines_company_assignee_idx", - "columns": [ - { - "expression": "company_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "assignee_agent_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "routines_company_project_idx": { - "name": "routines_company_project_idx", - "columns": [ - { - "expression": "company_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "project_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "routines_company_id_companies_id_fk": { - "name": "routines_company_id_companies_id_fk", - "tableFrom": "routines", - "tableTo": "companies", - "columnsFrom": [ - "company_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "routines_project_id_projects_id_fk": { - "name": "routines_project_id_projects_id_fk", - "tableFrom": "routines", - "tableTo": "projects", - "columnsFrom": [ - "project_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "routines_goal_id_goals_id_fk": { - "name": "routines_goal_id_goals_id_fk", - "tableFrom": "routines", - "tableTo": "goals", - "columnsFrom": [ - "goal_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "routines_parent_issue_id_issues_id_fk": { - "name": "routines_parent_issue_id_issues_id_fk", - "tableFrom": "routines", - "tableTo": "issues", - "columnsFrom": [ - "parent_issue_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "routines_assignee_agent_id_agents_id_fk": { - "name": "routines_assignee_agent_id_agents_id_fk", - "tableFrom": "routines", - "tableTo": "agents", - "columnsFrom": [ - "assignee_agent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "routines_created_by_agent_id_agents_id_fk": { - "name": "routines_created_by_agent_id_agents_id_fk", - "tableFrom": "routines", - "tableTo": "agents", - "columnsFrom": [ - "created_by_agent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "routines_updated_by_agent_id_agents_id_fk": { - "name": "routines_updated_by_agent_id_agents_id_fk", - "tableFrom": "routines", - "tableTo": "agents", - "columnsFrom": [ - "updated_by_agent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, "public.workspace_operations": { "name": "workspace_operations", "schema": "", diff --git a/packages/db/src/migrations/meta/0040_snapshot.json b/packages/db/src/migrations/meta/0040_snapshot.json new file mode 100644 index 00000000..cde0dddb --- /dev/null +++ b/packages/db/src/migrations/meta/0040_snapshot.json @@ -0,0 +1,10481 @@ +{ + "id": "ff2d3ea8-018e-44ec-9e7d-dfa81b2ef772", + "prevId": "1006727d-476b-474c-932b-51f1ba9626fb", + "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.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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 + }, + "process_pid": { + "name": "process_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_started_at": { + "name": "process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "process_loss_retry_count": { + "name": "process_loss_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "singleton_key": { + "name": "singleton_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "experimental": { + "name": "experimental", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_settings_singleton_key_idx": { + "name": "instance_settings_singleton_key_idx", + "columns": [ + { + "expression": "singleton_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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.issue_work_products": { + "name": "issue_work_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_service_id": { + "name": "runtime_service_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_work_products_company_issue_type_idx": { + "name": "issue_work_products_company_issue_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_execution_workspace_type_idx": { + "name": "issue_work_products_company_execution_workspace_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_provider_external_id_idx": { + "name": "issue_work_products_company_provider_external_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_updated_idx": { + "name": "issue_work_products_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_work_products_company_id_companies_id_fk": { + "name": "issue_work_products_company_id_companies_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_work_products_project_id_projects_id_fk": { + "name": "issue_work_products_project_id_projects_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_issue_id_issues_id_fk": { + "name": "issue_work_products_issue_id_issues_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_work_products_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issue_work_products_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk": { + "name": "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "workspace_runtime_services", + "columnsFrom": [ + "runtime_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_work_products_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issues": { + "name": "issues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "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_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "hidden_at": { + "name": "hidden_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issues_company_status_idx": { + "name": "issues_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_status_idx": { + "name": "issues_company_assignee_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_user_status_idx": { + "name": "issues_company_assignee_user_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_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_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.join_requests": { + "name": "join_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_type": { + "name": "request_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_approval'" + }, + "request_ip": { + "name": "request_ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requesting_user_id": { + "name": "requesting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_email_snapshot": { + "name": "request_email_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_defaults_payload": { + "name": "agent_defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "claim_secret_hash": { + "name": "claim_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_secret_expires_at": { + "name": "claim_secret_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_secret_consumed_at": { + "name": "claim_secret_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_agent_id": { + "name": "created_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_by_user_id": { + "name": "rejected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "join_requests_invite_unique_idx": { + "name": "join_requests_invite_unique_idx", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_company_status_type_created_idx": { + "name": "join_requests_company_status_type_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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 + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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_operations": { + "name": "workspace_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operations_company_run_started_idx": { + "name": "workspace_operations_company_run_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_started_idx": { + "name": "workspace_operations_company_workspace_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operations_company_id_companies_id_fk": { + "name": "workspace_operations_company_id_companies_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_operations_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_operations_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_by_run_id": { + "name": "started_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_policy": { + "name": "stop_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_runtime_services_company_workspace_status_idx": { + "name": "workspace_runtime_services_company_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_execution_workspace_status_idx": { + "name": "workspace_runtime_services_company_execution_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_project_status_idx": { + "name": "workspace_runtime_services_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_run_idx": { + "name": "workspace_runtime_services_run_idx", + "columns": [ + { + "expression": "started_by_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_updated_idx": { + "name": "workspace_runtime_services_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_runtime_services_company_id_companies_id_fk": { + "name": "workspace_runtime_services_company_id_companies_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_id_projects_id_fk": { + "name": "workspace_runtime_services_project_id_projects_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk": { + "name": "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_issue_id_issues_id_fk": { + "name": "workspace_runtime_services_issue_id_issues_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_owner_agent_id_agents_id_fk": { + "name": "workspace_runtime_services_owner_agent_id_agents_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk": { + "name": "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "started_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0041_snapshot.json b/packages/db/src/migrations/meta/0041_snapshot.json index a2b87216..94b233f1 100644 --- a/packages/db/src/migrations/meta/0041_snapshot.json +++ b/packages/db/src/migrations/meta/0041_snapshot.json @@ -1,6 +1,6 @@ { "id": "c49c6ac1-3acd-4a7b-91e5-5ad193b154a5", - "prevId": "70a51031-63ca-4491-8794-54cae9e07c66", + "prevId": "ff2d3ea8-018e-44ec-9e7d-dfa81b2ef772", "version": "7", "dialect": "postgresql", "tables": { @@ -11390,4 +11390,4 @@ "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 3a87c8a1..9ba84be4 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -292,9 +292,23 @@ { "idx": 41, "version": "7", + "when": 1774011294562, + "tag": "0039_curly_maria_hill", + "breakpoints": true + }, + { + "idx": 42, + "version": "7", + "when": 1774031825634, + "tag": "0040_spotty_the_renegades", + "breakpoints": true + }, + { + "idx": 43, + "version": "7", "when": 1774008910991, "tag": "0041_reflective_captain_universe", "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/packages/db/src/schema/instance_settings.ts b/packages/db/src/schema/instance_settings.ts index a9085ea4..002df259 100644 --- a/packages/db/src/schema/instance_settings.ts +++ b/packages/db/src/schema/instance_settings.ts @@ -5,6 +5,7 @@ export const instanceSettings = pgTable( { id: uuid("id").primaryKey().defaultRandom(), singletonKey: text("singleton_key").notNull().default("default"), + general: jsonb("general").$type>().notNull().default({}), experimental: jsonb("experimental").$type>().notNull().default({}), createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 393bef04..a36a24ff 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -162,6 +162,7 @@ export type { AgentSkillSnapshot, AgentSkillSyncRequest, InstanceExperimentalSettings, + InstanceGeneralSettings, InstanceSettings, Agent, AgentAccessState, @@ -310,6 +311,9 @@ export type { } from "./types/index.js"; export { + instanceGeneralSettingsSchema, + patchInstanceGeneralSettingsSchema, + type PatchInstanceGeneralSettings, instanceExperimentalSettingsSchema, patchInstanceExperimentalSettingsSchema, type PatchInstanceExperimentalSettings, diff --git a/packages/shared/src/types/company-portability.ts b/packages/shared/src/types/company-portability.ts index 811c88a6..26088831 100644 --- a/packages/shared/src/types/company-portability.ts +++ b/packages/shared/src/types/company-portability.ts @@ -3,6 +3,7 @@ export interface CompanyPortabilityInclude { agents: boolean; projects: boolean; issues: boolean; + skills: boolean; } export interface CompanyPortabilityEnvInput { diff --git a/packages/shared/src/types/company-skill.ts b/packages/shared/src/types/company-skill.ts index 5d6ed598..12834083 100644 --- a/packages/shared/src/types/company-skill.ts +++ b/packages/shared/src/types/company-skill.ts @@ -1,10 +1,10 @@ -export type CompanySkillSourceType = "local_path" | "github" | "url" | "catalog"; +export type CompanySkillSourceType = "local_path" | "github" | "url" | "catalog" | "skills_sh"; export type CompanySkillTrustLevel = "markdown_only" | "assets" | "scripts_executables"; export type CompanySkillCompatibility = "compatible" | "unknown" | "invalid"; -export type CompanySkillSourceBadge = "paperclip" | "github" | "local" | "url" | "catalog"; +export type CompanySkillSourceBadge = "paperclip" | "github" | "local" | "url" | "catalog" | "skills_sh"; export interface CompanySkillFileInventoryEntry { path: string; diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts index 76742092..e6ae5202 100644 --- a/packages/shared/src/types/index.ts +++ b/packages/shared/src/types/index.ts @@ -1,5 +1,5 @@ export type { Company } from "./company.js"; -export type { InstanceExperimentalSettings, InstanceSettings } from "./instance.js"; +export type { InstanceExperimentalSettings, InstanceGeneralSettings, InstanceSettings } from "./instance.js"; export type { CompanySkillSourceType, CompanySkillTrustLevel, diff --git a/packages/shared/src/types/instance.ts b/packages/shared/src/types/instance.ts index f243ef61..562c55b3 100644 --- a/packages/shared/src/types/instance.ts +++ b/packages/shared/src/types/instance.ts @@ -1,9 +1,15 @@ +export interface InstanceGeneralSettings { + censorUsernameInLogs: boolean; +} + export interface InstanceExperimentalSettings { enableIsolatedWorkspaces: boolean; + autoRestartDevServerWhenIdle: boolean; } export interface InstanceSettings { id: string; + general: InstanceGeneralSettings; experimental: InstanceExperimentalSettings; createdAt: Date; updatedAt: Date; diff --git a/packages/shared/src/validators/company-portability.ts b/packages/shared/src/validators/company-portability.ts index c45eed6a..cae50e89 100644 --- a/packages/shared/src/validators/company-portability.ts +++ b/packages/shared/src/validators/company-portability.ts @@ -6,6 +6,7 @@ export const portabilityIncludeSchema = z agents: z.boolean().optional(), projects: z.boolean().optional(), issues: z.boolean().optional(), + skills: z.boolean().optional(), }) .partial(); @@ -119,6 +120,7 @@ export const portabilityManifestSchema = z.object({ agents: z.boolean(), projects: z.boolean(), issues: z.boolean(), + skills: z.boolean(), }), company: portabilityCompanyManifestEntrySchema.nullable(), agents: z.array(portabilityAgentManifestEntrySchema), diff --git a/packages/shared/src/validators/company-skill.ts b/packages/shared/src/validators/company-skill.ts index 15bd4e2a..7f1df34b 100644 --- a/packages/shared/src/validators/company-skill.ts +++ b/packages/shared/src/validators/company-skill.ts @@ -1,9 +1,9 @@ import { z } from "zod"; -export const companySkillSourceTypeSchema = z.enum(["local_path", "github", "url", "catalog"]); +export const companySkillSourceTypeSchema = z.enum(["local_path", "github", "url", "catalog", "skills_sh"]); export const companySkillTrustLevelSchema = z.enum(["markdown_only", "assets", "scripts_executables"]); export const companySkillCompatibilitySchema = z.enum(["compatible", "unknown", "invalid"]); -export const companySkillSourceBadgeSchema = z.enum(["paperclip", "github", "local", "url", "catalog"]); +export const companySkillSourceBadgeSchema = z.enum(["paperclip", "github", "local", "url", "catalog", "skills_sh"]); export const companySkillFileInventoryEntrySchema = z.object({ path: z.string().min(1), diff --git a/packages/shared/src/validators/company.ts b/packages/shared/src/validators/company.ts index 651f5585..e3a1a208 100644 --- a/packages/shared/src/validators/company.ts +++ b/packages/shared/src/validators/company.ts @@ -33,7 +33,11 @@ export const updateCompanyBrandingSchema = z }) .strict() .refine( - (value) => value.brandColor !== undefined || value.logoAssetId !== undefined, + (value) => + value.name !== undefined + || value.description !== undefined + || value.brandColor !== undefined + || value.logoAssetId !== undefined, "At least one branding field must be provided", ); diff --git a/packages/shared/src/validators/index.ts b/packages/shared/src/validators/index.ts index 1214ac7c..ce6e701a 100644 --- a/packages/shared/src/validators/index.ts +++ b/packages/shared/src/validators/index.ts @@ -1,4 +1,8 @@ export { + instanceGeneralSettingsSchema, + patchInstanceGeneralSettingsSchema, + type InstanceGeneralSettings, + type PatchInstanceGeneralSettings, instanceExperimentalSettingsSchema, patchInstanceExperimentalSettingsSchema, type InstanceExperimentalSettings, diff --git a/packages/shared/src/validators/instance.ts b/packages/shared/src/validators/instance.ts index 955db002..05ee4323 100644 --- a/packages/shared/src/validators/instance.ts +++ b/packages/shared/src/validators/instance.ts @@ -1,10 +1,19 @@ import { z } from "zod"; +export const instanceGeneralSettingsSchema = z.object({ + censorUsernameInLogs: z.boolean().default(false), +}).strict(); + +export const patchInstanceGeneralSettingsSchema = instanceGeneralSettingsSchema.partial(); + export const instanceExperimentalSettingsSchema = z.object({ enableIsolatedWorkspaces: z.boolean().default(false), + autoRestartDevServerWhenIdle: z.boolean().default(false), }).strict(); export const patchInstanceExperimentalSettingsSchema = instanceExperimentalSettingsSchema.partial(); +export type InstanceGeneralSettings = z.infer; +export type PatchInstanceGeneralSettings = z.infer; export type InstanceExperimentalSettings = z.infer; export type PatchInstanceExperimentalSettings = z.infer; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5b3fe946..56a8a46a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -519,6 +519,9 @@ importers: pino-pretty: specifier: ^13.1.3 version: 13.1.3 + sharp: + specifier: ^0.34.5 + version: 0.34.5 ws: specifier: ^8.19.0 version: 8.19.0 @@ -541,6 +544,9 @@ importers: '@types/node': specifier: ^24.6.0 version: 24.12.0 + '@types/sharp': + specifier: ^0.32.0 + version: 0.32.0 '@types/supertest': specifier: ^6.0.2 version: 6.0.3 @@ -1219,6 +1225,9 @@ packages: cpu: [x64] os: [win32] + '@emnapi/runtime@1.9.1': + resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} + '@epic-web/invariant@1.0.0': resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} @@ -1710,6 +1719,143 @@ packages: '@iconify/utils@3.1.0': resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==} + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -3289,6 +3435,10 @@ packages: '@types/serve-static@2.2.0': resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + '@types/sharp@0.32.0': + resolution: {integrity: sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw==} + deprecated: This is a stub types definition. sharp provides its own type definitions, so you do not need this installed. + '@types/superagent@8.1.9': resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} @@ -5261,6 +5411,11 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + send@1.2.1: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} @@ -5275,6 +5430,10 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -6853,6 +7012,11 @@ snapshots: '@embedded-postgres/windows-x64@18.1.0-beta.16': optional: true + '@emnapi/runtime@1.9.1': + dependencies: + tslib: 2.8.1 + optional: true + '@epic-web/invariant@1.0.0': {} '@esbuild-kit/core-utils@3.3.2': @@ -7124,6 +7288,102 @@ snapshots: '@iconify/types': 2.0.0 mlly: 1.8.1 + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.9.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -9018,6 +9278,10 @@ snapshots: '@types/http-errors': 2.0.5 '@types/node': 25.2.3 + '@types/sharp@0.32.0': + dependencies: + sharp: 0.34.5 + '@types/superagent@8.1.9': dependencies: '@types/cookiejar': 2.1.5 @@ -11388,6 +11652,8 @@ snapshots: semver@6.3.1: {} + semver@7.7.4: {} + send@1.2.1: dependencies: debug: 4.4.3 @@ -11417,6 +11683,37 @@ snapshots: setprototypeof@1.2.0: {} + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 diff --git a/scripts/dev-runner.mjs b/scripts/dev-runner.mjs index 391ddb44..a0910430 100644 --- a/scripts/dev-runner.mjs +++ b/scripts/dev-runner.mjs @@ -1,10 +1,54 @@ #!/usr/bin/env node import { spawn } from "node:child_process"; +import { existsSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs"; +import path from "node:path"; import { createInterface } from "node:readline/promises"; import { stdin, stdout } from "node:process"; +import { fileURLToPath } from "node:url"; const mode = process.argv[2] === "watch" ? "watch" : "dev"; const cliArgs = process.argv.slice(3); +const scanIntervalMs = 1500; +const autoRestartPollIntervalMs = 2500; +const gracefulShutdownTimeoutMs = 10_000; +const changedPathSampleLimit = 5; +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const devServerStatusFilePath = path.join(repoRoot, ".paperclip", "dev-server-status.json"); + +const watchedDirectories = [ + ".paperclip", + "cli", + "scripts", + "server", + "packages/adapter-utils", + "packages/adapters", + "packages/db", + "packages/plugins/sdk", + "packages/shared", +].map((relativePath) => path.join(repoRoot, relativePath)); + +const watchedFiles = [ + ".env", + "package.json", + "pnpm-workspace.yaml", + "tsconfig.base.json", + "tsconfig.json", + "vitest.config.ts", +].map((relativePath) => path.join(repoRoot, relativePath)); + +const ignoredDirectoryNames = new Set([ + ".git", + ".turbo", + ".vite", + "coverage", + "dist", + "node_modules", + "ui-dist", +]); + +const ignoredRelativePaths = new Set([ + ".paperclip/dev-server-status.json", +]); const tailscaleAuthFlagNames = new Set([ "--tailscale-auth", @@ -34,6 +78,10 @@ const env = { PAPERCLIP_UI_DEV_MIDDLEWARE: "true", }; +if (mode === "dev") { + env.PAPERCLIP_DEV_SERVER_STATUS_FILE = devServerStatusFilePath; +} + if (mode === "watch") { env.PAPERCLIP_MIGRATION_PROMPT ??= "never"; env.PAPERCLIP_MIGRATION_AUTO_APPLY ??= "true"; @@ -50,6 +98,19 @@ if (tailscaleAuth) { } const pnpmBin = process.platform === "win32" ? "pnpm.cmd" : "pnpm"; +let previousSnapshot = collectWatchedSnapshot(); +let dirtyPaths = new Set(); +let pendingMigrations = []; +let lastChangedAt = null; +let lastRestartAt = null; +let scanInFlight = false; +let restartInFlight = false; +let shuttingDown = false; +let childExitWasExpected = false; +let child = null; +let childExitPromise = null; +let scanTimer = null; +let autoRestartTimer = null; function toError(error, context = "Dev runner command failed") { if (error instanceof Error) return error; @@ -82,9 +143,110 @@ function formatPendingMigrationSummary(migrations) { : migrations.join(", "); } +function exitForSignal(signal) { + if (signal === "SIGINT") { + process.exit(130); + } + if (signal === "SIGTERM") { + process.exit(143); + } + process.exit(1); +} + +function toRelativePath(absolutePath) { + return path.relative(repoRoot, absolutePath).split(path.sep).join("/"); +} + +function readSignature(absolutePath) { + const stats = statSync(absolutePath); + return `${Math.trunc(stats.mtimeMs)}:${stats.size}`; +} + +function addFileToSnapshot(snapshot, absolutePath) { + const relativePath = toRelativePath(absolutePath); + if (ignoredRelativePaths.has(relativePath)) return; + snapshot.set(relativePath, readSignature(absolutePath)); +} + +function walkDirectory(snapshot, absoluteDirectory) { + if (!existsSync(absoluteDirectory)) return; + + for (const entry of readdirSync(absoluteDirectory, { withFileTypes: true })) { + if (ignoredDirectoryNames.has(entry.name)) continue; + + const absolutePath = path.join(absoluteDirectory, entry.name); + if (entry.isDirectory()) { + walkDirectory(snapshot, absolutePath); + continue; + } + if (entry.isFile() || entry.isSymbolicLink()) { + addFileToSnapshot(snapshot, absolutePath); + } + } +} + +function collectWatchedSnapshot() { + const snapshot = new Map(); + + for (const absoluteDirectory of watchedDirectories) { + walkDirectory(snapshot, absoluteDirectory); + } + for (const absoluteFile of watchedFiles) { + if (!existsSync(absoluteFile)) continue; + addFileToSnapshot(snapshot, absoluteFile); + } + + return snapshot; +} + +function diffSnapshots(previous, next) { + const changed = new Set(); + + for (const [relativePath, signature] of next) { + if (previous.get(relativePath) !== signature) { + changed.add(relativePath); + } + } + for (const relativePath of previous.keys()) { + if (!next.has(relativePath)) { + changed.add(relativePath); + } + } + + return [...changed].sort(); +} + +function ensureDevStatusDirectory() { + mkdirSync(path.dirname(devServerStatusFilePath), { recursive: true }); +} + +function writeDevServerStatus() { + if (mode !== "dev") return; + + ensureDevStatusDirectory(); + const changedPaths = [...dirtyPaths].sort(); + writeFileSync( + devServerStatusFilePath, + `${JSON.stringify({ + dirty: changedPaths.length > 0 || pendingMigrations.length > 0, + lastChangedAt, + changedPathCount: changedPaths.length, + changedPathsSample: changedPaths.slice(0, changedPathSampleLimit), + pendingMigrations, + lastRestartAt, + }, null, 2)}\n`, + "utf8", + ); +} + +function clearDevServerStatus() { + if (mode !== "dev") return; + rmSync(devServerStatusFilePath, { force: true }); +} + async function runPnpm(args, options = {}) { return await new Promise((resolve, reject) => { - const child = spawn(pnpmBin, args, { + const spawned = spawn(pnpmBin, args, { stdio: options.stdio ?? ["ignore", "pipe", "pipe"], env: options.env ?? process.env, shell: process.platform === "win32", @@ -93,19 +255,19 @@ async function runPnpm(args, options = {}) { let stdoutBuffer = ""; let stderrBuffer = ""; - if (child.stdout) { - child.stdout.on("data", (chunk) => { + if (spawned.stdout) { + spawned.stdout.on("data", (chunk) => { stdoutBuffer += String(chunk); }); } - if (child.stderr) { - child.stderr.on("data", (chunk) => { + if (spawned.stderr) { + spawned.stderr.on("data", (chunk) => { stderrBuffer += String(chunk); }); } - child.on("error", reject); - child.on("exit", (code, signal) => { + spawned.on("error", reject); + spawned.on("exit", (code, signal) => { resolve({ code: code ?? 0, signal, @@ -116,9 +278,7 @@ async function runPnpm(args, options = {}) { }); } -async function maybePreflightMigrations() { - if (mode !== "watch") return; - +async function getMigrationStatusPayload() { const status = await runPnpm( ["--filter", "@paperclipai/db", "exec", "tsx", "src/migration-status.ts", "--json"], { env }, @@ -132,9 +292,8 @@ async function maybePreflightMigrations() { process.exit(status.code); } - let payload; try { - payload = JSON.parse(status.stdout.trim()); + return JSON.parse(status.stdout.trim()); } catch (error) { process.stderr.write( status.stderr || @@ -143,15 +302,31 @@ async function maybePreflightMigrations() { ); throw toError(error, "Unable to parse migration-status JSON output"); } +} - if (payload.status !== "needsMigrations" || payload.pendingMigrations.length === 0) { +async function refreshPendingMigrations() { + const payload = await getMigrationStatusPayload(); + pendingMigrations = + payload.status === "needsMigrations" && Array.isArray(payload.pendingMigrations) + ? payload.pendingMigrations.filter((entry) => typeof entry === "string" && entry.trim().length > 0) + : []; + writeDevServerStatus(); + return payload; +} + +async function maybePreflightMigrations(options = {}) { + const interactive = options.interactive ?? mode === "watch"; + const autoApply = options.autoApply ?? env.PAPERCLIP_MIGRATION_AUTO_APPLY === "true"; + const exitOnDecline = options.exitOnDecline ?? mode === "watch"; + + const payload = await refreshPendingMigrations(); + if (payload.status !== "needsMigrations" || pendingMigrations.length === 0) { return; } - const autoApply = env.PAPERCLIP_MIGRATION_AUTO_APPLY === "true"; let shouldApply = autoApply; - if (!autoApply) { + if (!autoApply && interactive) { if (!stdin.isTTY || !stdout.isTTY) { shouldApply = true; } else { @@ -159,7 +334,7 @@ async function maybePreflightMigrations() { try { const answer = ( await prompt.question( - `Apply pending migrations (${formatPendingMigrationSummary(payload.pendingMigrations)}) now? (y/N): `, + `Apply pending migrations (${formatPendingMigrationSummary(pendingMigrations)}) now? (y/N): `, ) ) .trim() @@ -172,11 +347,14 @@ async function maybePreflightMigrations() { } if (!shouldApply) { - process.stderr.write( - `[paperclip] Pending migrations detected (${formatPendingMigrationSummary(payload.pendingMigrations)}). ` + - "Refusing to start watch mode against a stale schema.\n", - ); - process.exit(1); + if (exitOnDecline) { + process.stderr.write( + `[paperclip] Pending migrations detected (${formatPendingMigrationSummary(pendingMigrations)}). ` + + "Refusing to start watch mode against a stale schema.\n", + ); + process.exit(1); + } + return; } const migrate = spawn(pnpmBin, ["db:migrate"], { @@ -188,15 +366,15 @@ async function maybePreflightMigrations() { migrate.on("exit", (code, signal) => resolve({ code: code ?? 0, signal })); }); if (exit.signal) { - process.kill(process.pid, exit.signal); + exitForSignal(exit.signal); return; } if (exit.code !== 0) { process.exit(exit.code); } -} -await maybePreflightMigrations(); + await refreshPendingMigrations(); +} async function buildPluginSdk() { console.log("[paperclip] building plugin sdk..."); @@ -205,7 +383,7 @@ async function buildPluginSdk() { { stdio: "inherit" }, ); if (result.signal) { - process.kill(process.pid, result.signal); + exitForSignal(result.signal); return; } if (result.code !== 0) { @@ -214,19 +392,199 @@ async function buildPluginSdk() { } } -await buildPluginSdk(); +async function markChildAsCurrent() { + previousSnapshot = collectWatchedSnapshot(); + dirtyPaths = new Set(); + lastChangedAt = null; + lastRestartAt = new Date().toISOString(); + await refreshPendingMigrations(); +} -const serverScript = mode === "watch" ? "dev:watch" : "dev"; -const child = spawn( - pnpmBin, - ["--filter", "@paperclipai/server", serverScript, ...forwardedArgs], - { stdio: "inherit", env, shell: process.platform === "win32" }, -); +async function scanForBackendChanges() { + if (mode !== "dev" || scanInFlight || restartInFlight) return; + scanInFlight = true; + try { + const nextSnapshot = collectWatchedSnapshot(); + const changed = diffSnapshots(previousSnapshot, nextSnapshot); + previousSnapshot = nextSnapshot; + if (changed.length === 0) return; -child.on("exit", (code, signal) => { - if (signal) { - process.kill(process.pid, signal); + for (const relativePath of changed) { + dirtyPaths.add(relativePath); + } + lastChangedAt = new Date().toISOString(); + await refreshPendingMigrations(); + } finally { + scanInFlight = false; + } +} + +async function getDevHealthPayload() { + const serverPort = env.PORT ?? process.env.PORT ?? "3100"; + const response = await fetch(`http://127.0.0.1:${serverPort}/api/health`); + if (!response.ok) { + throw new Error(`Health request failed (${response.status})`); + } + return await response.json(); +} + +async function waitForChildExit() { + if (!childExitPromise) { + return { code: 0, signal: null }; + } + return await childExitPromise; +} + +async function stopChildForRestart() { + if (!child) return { code: 0, signal: null }; + childExitWasExpected = true; + child.kill("SIGTERM"); + const killTimer = setTimeout(() => { + if (child) { + child.kill("SIGKILL"); + } + }, gracefulShutdownTimeoutMs); + try { + return await waitForChildExit(); + } finally { + clearTimeout(killTimer); + } +} + +async function startServerChild() { + await buildPluginSdk(); + + const serverScript = mode === "watch" ? "dev:watch" : "dev"; + child = spawn( + pnpmBin, + ["--filter", "@paperclipai/server", serverScript, ...forwardedArgs], + { stdio: "inherit", env, shell: process.platform === "win32" }, + ); + + childExitPromise = new Promise((resolve, reject) => { + child.on("error", reject); + child.on("exit", (code, signal) => { + const expected = childExitWasExpected; + childExitWasExpected = false; + child = null; + childExitPromise = null; + resolve({ code: code ?? 0, signal }); + + if (restartInFlight || expected || shuttingDown) { + return; + } + if (signal) { + exitForSignal(signal); + return; + } + process.exit(code ?? 0); + }); + }); + + await markChildAsCurrent(); +} + +async function maybeAutoRestartChild() { + if (mode !== "dev" || restartInFlight || !child) return; + if (dirtyPaths.size === 0 && pendingMigrations.length === 0) return; + + restartInFlight = true; + let health; + try { + health = await getDevHealthPayload(); + } catch { + restartInFlight = false; return; } - process.exit(code ?? 0); + + const devServer = health?.devServer; + if (!devServer?.enabled || devServer.autoRestartEnabled !== true) { + restartInFlight = false; + return; + } + if ((devServer.activeRunCount ?? 0) > 0) { + restartInFlight = false; + return; + } + + try { + await maybePreflightMigrations({ + autoApply: true, + interactive: false, + exitOnDecline: false, + }); + await stopChildForRestart(); + await startServerChild(); + } catch (error) { + const err = toError(error, "Auto-restart failed"); + process.stderr.write(`${err.stack ?? err.message}\n`); + process.exit(1); + } finally { + restartInFlight = false; + } +} + +function installDevIntervals() { + if (mode !== "dev") return; + + scanTimer = setInterval(() => { + void scanForBackendChanges(); + }, scanIntervalMs); + autoRestartTimer = setInterval(() => { + void maybeAutoRestartChild(); + }, autoRestartPollIntervalMs); +} + +function clearDevIntervals() { + if (scanTimer) { + clearInterval(scanTimer); + scanTimer = null; + } + if (autoRestartTimer) { + clearInterval(autoRestartTimer); + autoRestartTimer = null; + } +} + +async function shutdown(signal) { + if (shuttingDown) return; + shuttingDown = true; + clearDevIntervals(); + clearDevServerStatus(); + + if (!child) { + if (signal) { + exitForSignal(signal); + return; + } + process.exit(0); + } + + childExitWasExpected = true; + child.kill(signal); + const exit = await waitForChildExit(); + if (exit.signal) { + exitForSignal(exit.signal); + return; + } + process.exit(exit.code ?? 0); +} + +process.on("SIGINT", () => { + void shutdown("SIGINT"); }); +process.on("SIGTERM", () => { + void shutdown("SIGTERM"); +}); + +await maybePreflightMigrations(); +await startServerChild(); +installDevIntervals(); + +if (mode === "watch") { + const exit = await waitForChildExit(); + if (exit.signal) { + exitForSignal(exit.signal); + } + process.exit(exit.code ?? 0); +} diff --git a/scripts/generate-org-chart-images.ts b/scripts/generate-org-chart-images.ts new file mode 100644 index 00000000..f60e7d1f --- /dev/null +++ b/scripts/generate-org-chart-images.ts @@ -0,0 +1,694 @@ +#!/usr/bin/env npx tsx +/** + * Standalone org chart image generator. + * + * Renders each of the 5 org chart styles to PNG using Playwright (headless Chromium). + * This gives us browser-native emoji rendering, full CSS support, and pixel-perfect output. + * + * Usage: + * npx tsx scripts/generate-org-chart-images.ts + * + * Output: tmp/org-chart-images/ + +
+${tree} +${PAPERCLIP_WATERMARK} +
+`; +} + +// ── Main ─────────────────────────────────────────────────────── + +async function main() { + const outDir = path.resolve("tmp/org-chart-images"); + fs.mkdirSync(outDir, { recursive: true }); + + const browser = await chromium.launch(); + const context = await browser.newContext({ + deviceScaleFactor: 2, // retina quality + }); + + const sizes = ["sm", "med", "lg"] as const; + const results: string[] = []; + + for (const style of STYLES) { + // README sizes + for (const size of sizes) { + const page = await context.newPage(); + const html = buildHtml(style, ORGS[size], false); + await page.setContent(html, { waitUntil: "networkidle" }); + + // Wait for fonts to load + await page.waitForFunction(() => document.fonts.ready); + await page.waitForTimeout(300); + + // Fit to content + const box = await page.evaluate(() => { + const el = document.querySelector(".org-tree")!; + const rect = el.getBoundingClientRect(); + return { + width: Math.ceil(rect.width) + 32, + height: Math.ceil(rect.height) + 32, + }; + }); + + await page.setViewportSize({ + width: Math.max(box.width, 400), + height: Math.max(box.height, 300), + }); + + const filename = `${style.key}-${size}.png`; + await page.screenshot({ + path: path.join(outDir, filename), + clip: { + x: 0, + y: 0, + width: Math.max(box.width, 400), + height: Math.max(box.height, 300), + }, + }); + await page.close(); + results.push(filename); + console.log(` ✓ ${filename}`); + } + + // OG card (1200×630) + { + const page = await context.newPage(); + await page.setViewportSize({ width: 1200, height: 630 }); + const html = buildHtml(style, OG_ORG, true); + // For OG, center the tree in a fixed viewport + const ogHtml = html.replace( + "", + ``, + ); + await page.setContent(ogHtml, { waitUntil: "networkidle" }); + await page.waitForFunction(() => document.fonts.ready); + await page.waitForTimeout(300); + + const filename = `${style.key}-og.png`; + await page.screenshot({ + path: path.join(outDir, filename), + clip: { x: 0, y: 0, width: 1200, height: 630 }, + }); + await page.close(); + results.push(filename); + console.log(` ✓ ${filename}`); + } + } + + await browser.close(); + + // Build an HTML comparison page + let compHtml = ` + + +Org Chart Style Comparison + + +

Org Chart Export — Style Comparison

+

5 styles × 3 org sizes + OG cards. All rendered via Playwright (browser-native emojis, full CSS).

+`; + + for (const style of STYLES) { + compHtml += `
+

${style.name}

+
README — Small / Medium / Large
+
+ + + +
+
OG Card (1200×630)
+
+
`; + } + + compHtml += ``; + fs.writeFileSync(path.join(outDir, "comparison.html"), compHtml); + console.log(`\n✓ All done! ${results.length} images generated.`); + console.log(` Open: tmp/org-chart-images/comparison.html`); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/scripts/generate-org-chart-satori-comparison.ts b/scripts/generate-org-chart-satori-comparison.ts new file mode 100644 index 00000000..0f967d72 --- /dev/null +++ b/scripts/generate-org-chart-satori-comparison.ts @@ -0,0 +1,225 @@ +#!/usr/bin/env npx tsx +/** + * Standalone org chart comparison generator — pure SVG (no Playwright). + * + * Generates SVG files for all 5 styles × 3 org sizes, plus a comparison HTML page. + * Uses the server-side SVG renderer directly — same code that powers the routes. + * + * Usage: + * npx tsx scripts/generate-org-chart-satori-comparison.ts + * + * Output: tmp/org-chart-svg-comparison/ + */ +import * as fs from "fs"; +import * as path from "path"; +import { + renderOrgChartSvg, + renderOrgChartPng, + type OrgNode, + type OrgChartStyle, + ORG_CHART_STYLES, +} from "../server/src/routes/org-chart-svg.js"; + +// ── Sample org data ────────────────────────────────────────────── + +const ORGS: Record = { + sm: { + id: "ceo", + name: "CEO", + role: "Chief Executive", + status: "active", + reports: [ + { id: "eng1", name: "Engineer", role: "Engineering", status: "active", reports: [] }, + { id: "des1", name: "Designer", role: "Design", status: "active", reports: [] }, + ], + }, + med: { + id: "ceo", + name: "CEO", + role: "Chief Executive", + status: "active", + reports: [ + { + id: "cto", + name: "CTO", + role: "Technology", + status: "active", + reports: [ + { id: "eng1", name: "ClaudeCoder", role: "Engineering", status: "active", reports: [] }, + { id: "eng2", name: "CodexCoder", role: "Engineering", status: "active", reports: [] }, + { id: "eng3", name: "SparkCoder", role: "Engineering", status: "active", reports: [] }, + { id: "eng4", name: "CursorCoder", role: "Engineering", status: "active", reports: [] }, + { id: "qa1", name: "QA", role: "Quality", status: "active", reports: [] }, + ], + }, + { + id: "cmo", + name: "CMO", + role: "Marketing", + status: "active", + reports: [ + { id: "des1", name: "Designer", role: "Design", status: "active", reports: [] }, + ], + }, + ], + }, + lg: { + id: "ceo", + name: "CEO", + role: "Chief Executive", + status: "active", + reports: [ + { + id: "cto", + name: "CTO", + role: "Technology", + status: "active", + reports: [ + { id: "eng1", name: "Eng 1", role: "Engineering", status: "active", reports: [] }, + { id: "eng2", name: "Eng 2", role: "Engineering", status: "active", reports: [] }, + { id: "eng3", name: "Eng 3", role: "Engineering", status: "active", reports: [] }, + { id: "qa1", name: "QA", role: "Quality", status: "active", reports: [] }, + ], + }, + { + id: "cmo", + name: "CMO", + role: "Marketing", + status: "active", + reports: [ + { id: "des1", name: "Designer", role: "Design", status: "active", reports: [] }, + { id: "wrt1", name: "Content", role: "Engineering", status: "active", reports: [] }, + ], + }, + { + id: "cfo", + name: "CFO", + role: "Finance", + status: "active", + reports: [ + { id: "fin1", name: "Analyst", role: "Finance", status: "active", reports: [] }, + ], + }, + { + id: "coo", + name: "COO", + role: "Operations", + status: "active", + reports: [ + { id: "ops1", name: "Ops 1", role: "Operations", status: "active", reports: [] }, + { id: "ops2", name: "Ops 2", role: "Operations", status: "active", reports: [] }, + { id: "devops1", name: "DevOps", role: "Operations", status: "active", reports: [] }, + ], + }, + ], + }, +}; + +const STYLE_META: Record = { + monochrome: { name: "Monochrome", vibe: "Vercel — zero color noise, dark", bestFor: "GitHub READMEs, developer docs" }, + nebula: { name: "Nebula", vibe: "Glassmorphism — cosmic gradient", bestFor: "Hero sections, marketing" }, + circuit: { name: "Circuit", vibe: "Linear/Raycast — indigo traces", bestFor: "Product pages, dev tools" }, + warmth: { name: "Warmth", vibe: "Airbnb — light, colored avatars", bestFor: "Light-mode READMEs, presentations" }, + schematic: { name: "Schematic", vibe: "Blueprint — grid bg, monospace", bestFor: "Technical docs, infra diagrams" }, +}; + +// ── Main ───────────────────────────────────────────────────────── + +async function main() { + const outDir = path.resolve("tmp/org-chart-svg-comparison"); + fs.mkdirSync(outDir, { recursive: true }); + + const sizes = ["sm", "med", "lg"] as const; + const results: string[] = []; + + for (const style of ORG_CHART_STYLES) { + for (const size of sizes) { + const svg = renderOrgChartSvg([ORGS[size]], style); + const svgFile = `${style}-${size}.svg`; + fs.writeFileSync(path.join(outDir, svgFile), svg); + results.push(svgFile); + console.log(` ✓ ${svgFile}`); + + // Also generate PNG + try { + const png = await renderOrgChartPng([ORGS[size]], style); + const pngFile = `${style}-${size}.png`; + fs.writeFileSync(path.join(outDir, pngFile), png); + results.push(pngFile); + console.log(` ✓ ${pngFile}`); + } catch (e) { + console.log(` ⚠ PNG failed for ${style}-${size}: ${(e as Error).message}`); + } + } + } + + // Build comparison HTML + let html = ` + + +Org Chart Style Comparison — Pure SVG (No Playwright) + + +

Org Chart Export — Style Comparison

+

5 styles × 3 org sizes. Pure SVG — no Playwright, no Satori, no browser needed.

+
Server-side compatible — works on any route
+`; + + for (const style of ORG_CHART_STYLES) { + const meta = STYLE_META[style]; + html += `
+

${meta.name}

+
${meta.vibe} — Best for: ${meta.bestFor}
+
Small / Medium / Large
+
+
3 agents
+
8 agents
+
14 agents
+
+
`; + } + + html += ` +
+

Why Pure SVG instead of Satori?

+

+ Satori converts JSX → SVG using Yoga (flexbox). It's great for OG cards but has limitations for org charts: + no ::before/::after pseudo-elements, no CSS grid, limited gradient support, + and connector lines between nodes would need post-processing. +

+

+ Pure SVG rendering (what we're using here) gives us full control over layout, connectors, + gradients, filters, and patterns — with zero runtime dependencies beyond sharp for PNG. + It runs on any Node.js route, generates in <10ms, and produces identical output every time. +

+

+ Routes: GET /api/companies/:id/org.svg?style=monochrome and GET /api/companies/:id/org.png?style=circuit +

+
+`; + + fs.writeFileSync(path.join(outDir, "comparison.html"), html); + console.log(`\n✓ All done! ${results.length} files generated.`); + console.log(` Open: tmp/org-chart-svg-comparison/comparison.html`); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/server/package.json b/server/package.json index 57865b2f..843f9ca7 100644 --- a/server/package.json +++ b/server/package.json @@ -35,7 +35,7 @@ "dev": "tsx src/index.ts", "dev:watch": "cross-env PAPERCLIP_MIGRATION_PROMPT=never PAPERCLIP_MIGRATION_AUTO_APPLY=true tsx watch --ignore ../ui/node_modules --ignore ../ui/.vite --ignore ../ui/dist src/index.ts", "prepare:ui-dist": "bash ../scripts/prepare-server-ui-dist.sh", - "build": "tsc", + "build": "tsc && mkdir -p dist/onboarding-assets && cp -R src/onboarding-assets/. dist/onboarding-assets/", "prepack": "pnpm run prepare:ui-dist", "postpack": "rm -rf ui-dist", "clean": "rm -rf dist", @@ -51,7 +51,6 @@ "@paperclipai/adapter-openclaw-gateway": "workspace:*", "@paperclipai/adapter-opencode-local": "workspace:*", "@paperclipai/adapter-pi-local": "workspace:*", - "hermes-paperclip-adapter": "0.1.1", "@paperclipai/adapter-utils": "workspace:*", "@paperclipai/db": "workspace:*", "@paperclipai/plugin-sdk": "workspace:*", @@ -66,12 +65,14 @@ "drizzle-orm": "^0.38.4", "embedded-postgres": "^18.1.0-beta.16", "express": "^5.1.0", + "hermes-paperclip-adapter": "0.1.1", "jsdom": "^28.1.0", "multer": "^2.0.2", "open": "^11.0.0", "pino": "^9.6.0", "pino-http": "^10.4.0", "pino-pretty": "^13.1.3", + "sharp": "^0.34.5", "ws": "^8.19.0", "zod": "^3.24.2" }, @@ -81,6 +82,7 @@ "@types/jsdom": "^28.0.0", "@types/multer": "^2.0.0", "@types/node": "^24.6.0", + "@types/sharp": "^0.32.0", "@types/supertest": "^6.0.2", "@types/ws": "^8.18.1", "cross-env": "^10.1.0", diff --git a/server/src/__tests__/agent-permissions-routes.test.ts b/server/src/__tests__/agent-permissions-routes.test.ts index 9bcc71d5..08941f77 100644 --- a/server/src/__tests__/agent-permissions-routes.test.ts +++ b/server/src/__tests__/agent-permissions-routes.test.ts @@ -76,7 +76,9 @@ const mockSecretService = vi.hoisted(() => ({ resolveAdapterConfigForRuntime: vi.fn(), })); -const mockAgentInstructionsService = vi.hoisted(() => ({})); +const mockAgentInstructionsService = vi.hoisted(() => ({ + materializeManagedBundle: vi.fn(), +})); const mockCompanySkillService = vi.hoisted(() => ({ listRuntimeSkillEntries: vi.fn(), resolveRequestedSkillKeys: vi.fn(), @@ -152,6 +154,23 @@ describe("agent permission routes", () => { mockCompanySkillService.listRuntimeSkillEntries.mockResolvedValue([]); mockCompanySkillService.resolveRequestedSkillKeys.mockImplementation(async (_companyId, requested) => requested); mockBudgetService.upsertPolicy.mockResolvedValue(undefined); + mockAgentInstructionsService.materializeManagedBundle.mockImplementation( + async (agent: Record, files: Record) => ({ + bundle: null, + adapterConfig: { + ...((agent.adapterConfig as Record | undefined) ?? {}), + instructionsBundleMode: "managed", + instructionsRootPath: `/tmp/${String(agent.id)}/instructions`, + instructionsEntryFile: "AGENTS.md", + instructionsFilePath: `/tmp/${String(agent.id)}/instructions/AGENTS.md`, + promptTemplate: files["AGENTS.md"] ?? "", + }, + }), + ); + mockCompanySkillService.listRuntimeSkillEntries.mockResolvedValue([]); + mockCompanySkillService.resolveRequestedSkillKeys.mockImplementation( + async (_companyId: string, requested: string[]) => requested, + ); mockSecretService.normalizeAdapterConfigForPersistence.mockImplementation(async (_companyId, config) => config); mockSecretService.resolveAdapterConfigForRuntime.mockImplementation(async (_companyId, config) => ({ config })); mockLogActivity.mockResolvedValue(undefined); diff --git a/server/src/__tests__/agent-skills-routes.test.ts b/server/src/__tests__/agent-skills-routes.test.ts index a33eafd6..e32894cb 100644 --- a/server/src/__tests__/agent-skills-routes.test.ts +++ b/server/src/__tests__/agent-skills-routes.test.ts @@ -20,7 +20,9 @@ const mockAccessService = vi.hoisted(() => ({ setPrincipalPermission: vi.fn(), })); -const mockApprovalService = vi.hoisted(() => ({})); +const mockApprovalService = vi.hoisted(() => ({ + create: vi.fn(), +})); const mockBudgetService = vi.hoisted(() => ({})); const mockHeartbeatService = vi.hoisted(() => ({})); const mockIssueApprovalService = vi.hoisted(() => ({ @@ -180,13 +182,26 @@ describe("agent skill routes", () => { budgetMonthlyCents: Number(input.budgetMonthlyCents ?? 0), permissions: null, })); - mockApprovalService.create = vi.fn(async (_companyId: string, input: Record) => ({ + mockApprovalService.create.mockImplementation(async (_companyId: string, input: Record) => ({ id: "approval-1", companyId: "company-1", type: "hire_agent", status: "pending", payload: input.payload ?? {}, })); + mockAgentInstructionsService.materializeManagedBundle.mockImplementation( + async (agent: Record, files: Record) => ({ + bundle: null, + adapterConfig: { + ...((agent.adapterConfig as Record | undefined) ?? {}), + instructionsBundleMode: "managed", + instructionsRootPath: `/tmp/${String(agent.id)}/instructions`, + instructionsEntryFile: "AGENTS.md", + instructionsFilePath: `/tmp/${String(agent.id)}/instructions/AGENTS.md`, + promptTemplate: files["AGENTS.md"] ?? "", + }, + }), + ); mockLogActivity.mockResolvedValue(undefined); mockAccessService.canUser.mockResolvedValue(true); mockAccessService.hasPermission.mockResolvedValue(true); @@ -297,6 +312,95 @@ describe("agent skill routes", () => { ); }); + it("materializes a managed AGENTS.md for directly created local agents", async () => { + const res = await request(createApp()) + .post("/api/companies/company-1/agents") + .send({ + name: "QA Agent", + role: "engineer", + adapterType: "claude_local", + adapterConfig: { + promptTemplate: "You are QA.", + }, + }); + + expect(res.status, JSON.stringify(res.body)).toBe(201); + expect(mockAgentInstructionsService.materializeManagedBundle).toHaveBeenCalledWith( + expect.objectContaining({ + id: "11111111-1111-4111-8111-111111111111", + adapterType: "claude_local", + }), + { "AGENTS.md": "You are QA." }, + { entryFile: "AGENTS.md", replaceExisting: false }, + ); + expect(mockAgentService.update).toHaveBeenCalledWith( + "11111111-1111-4111-8111-111111111111", + expect.objectContaining({ + adapterConfig: expect.objectContaining({ + instructionsBundleMode: "managed", + instructionsEntryFile: "AGENTS.md", + instructionsFilePath: "/tmp/11111111-1111-4111-8111-111111111111/instructions/AGENTS.md", + }), + }), + ); + expect(mockAgentService.update.mock.calls.at(-1)?.[1]).not.toMatchObject({ + adapterConfig: expect.objectContaining({ + promptTemplate: expect.anything(), + }), + }); + }); + + it("materializes the bundled CEO instruction set for default CEO agents", async () => { + const res = await request(createApp()) + .post("/api/companies/company-1/agents") + .send({ + name: "CEO", + role: "ceo", + adapterType: "claude_local", + adapterConfig: {}, + }); + + expect(res.status, JSON.stringify(res.body)).toBe(201); + expect(mockAgentInstructionsService.materializeManagedBundle).toHaveBeenCalledWith( + expect.objectContaining({ + id: "11111111-1111-4111-8111-111111111111", + role: "ceo", + adapterType: "claude_local", + }), + expect.objectContaining({ + "AGENTS.md": expect.stringContaining("You are the CEO."), + "HEARTBEAT.md": expect.stringContaining("CEO Heartbeat Checklist"), + "SOUL.md": expect.stringContaining("CEO Persona"), + "TOOLS.md": expect.stringContaining("# Tools"), + }), + { entryFile: "AGENTS.md", replaceExisting: false }, + ); + }); + + it("materializes the bundled default instruction set for non-CEO agents with no prompt template", async () => { + const res = await request(createApp()) + .post("/api/companies/company-1/agents") + .send({ + name: "Engineer", + role: "engineer", + adapterType: "claude_local", + adapterConfig: {}, + }); + + expect(res.status, JSON.stringify(res.body)).toBe(201); + expect(mockAgentInstructionsService.materializeManagedBundle).toHaveBeenCalledWith( + expect.objectContaining({ + id: "11111111-1111-4111-8111-111111111111", + role: "engineer", + adapterType: "claude_local", + }), + expect.objectContaining({ + "AGENTS.md": expect.stringContaining("Keep the work moving until it's done."), + }), + { entryFile: "AGENTS.md", replaceExisting: false }, + ); + }); + it("includes canonical desired skills in hire approvals", async () => { const db = createDb(true); @@ -324,4 +428,35 @@ describe("agent skill routes", () => { }), ); }); + + it("uses managed AGENTS config in hire approval payloads", async () => { + const res = await request(createApp(createDb(true))) + .post("/api/companies/company-1/agent-hires") + .send({ + name: "QA Agent", + role: "engineer", + adapterType: "claude_local", + adapterConfig: { + promptTemplate: "You are QA.", + }, + }); + + expect(res.status, JSON.stringify(res.body)).toBe(201); + expect(mockApprovalService.create).toHaveBeenCalledWith( + "company-1", + expect.objectContaining({ + payload: expect.objectContaining({ + adapterConfig: expect.objectContaining({ + instructionsBundleMode: "managed", + instructionsEntryFile: "AGENTS.md", + instructionsFilePath: "/tmp/11111111-1111-4111-8111-111111111111/instructions/AGENTS.md", + }), + }), + }), + ); + const approvalInput = mockApprovalService.create.mock.calls.at(-1)?.[1] as + | { payload?: { adapterConfig?: Record } } + | undefined; + expect(approvalInput?.payload?.adapterConfig?.promptTemplate).toBeUndefined(); + }); }); diff --git a/server/src/__tests__/codex-local-adapter.test.ts b/server/src/__tests__/codex-local-adapter.test.ts index 18479e43..84c806cd 100644 --- a/server/src/__tests__/codex-local-adapter.test.ts +++ b/server/src/__tests__/codex-local-adapter.test.ts @@ -117,7 +117,7 @@ describe("codex_local ui stdout parser", () => { { kind: "system", ts, - text: "file changes: update /Users/[]/project/ui/src/pages/AgentDetail.tsx", + text: "file changes: update /Users/paperclipuser/project/ui/src/pages/AgentDetail.tsx", }, ]); }); diff --git a/server/src/__tests__/codex-local-execute.test.ts b/server/src/__tests__/codex-local-execute.test.ts index 4c9b7c0e..3f1f15df 100644 --- a/server/src/__tests__/codex-local-execute.test.ts +++ b/server/src/__tests__/codex-local-execute.test.ts @@ -41,6 +41,104 @@ type LogEntry = { }; describe("codex execute", () => { + it("uses a Paperclip-managed CODEX_HOME outside worktree mode while preserving shared auth and config", async () => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-codex-execute-default-")); + const workspace = path.join(root, "workspace"); + const commandPath = path.join(root, "codex"); + const capturePath = path.join(root, "capture.json"); + const sharedCodexHome = path.join(root, "shared-codex-home"); + const paperclipHome = path.join(root, "paperclip-home"); + const managedCodexHome = path.join( + paperclipHome, + "instances", + "default", + "companies", + "company-1", + "codex-home", + ); + await fs.mkdir(workspace, { recursive: true }); + await fs.mkdir(sharedCodexHome, { recursive: true }); + await fs.writeFile(path.join(sharedCodexHome, "auth.json"), '{"token":"shared"}\n', "utf8"); + await fs.writeFile(path.join(sharedCodexHome, "config.toml"), 'model = "codex-mini-latest"\n', "utf8"); + await writeFakeCodexCommand(commandPath); + + const previousHome = process.env.HOME; + const previousPaperclipHome = process.env.PAPERCLIP_HOME; + const previousPaperclipInstanceId = process.env.PAPERCLIP_INSTANCE_ID; + const previousPaperclipInWorktree = process.env.PAPERCLIP_IN_WORKTREE; + const previousCodexHome = process.env.CODEX_HOME; + process.env.HOME = root; + process.env.PAPERCLIP_HOME = paperclipHome; + delete process.env.PAPERCLIP_INSTANCE_ID; + delete process.env.PAPERCLIP_IN_WORKTREE; + process.env.CODEX_HOME = sharedCodexHome; + + try { + const logs: LogEntry[] = []; + const result = await execute({ + runId: "run-default", + agent: { + id: "agent-1", + companyId: "company-1", + name: "Codex Coder", + adapterType: "codex_local", + adapterConfig: {}, + }, + runtime: { + sessionId: null, + sessionParams: null, + sessionDisplayId: null, + taskKey: null, + }, + config: { + command: commandPath, + cwd: workspace, + env: { + PAPERCLIP_TEST_CAPTURE_PATH: capturePath, + }, + promptTemplate: "Follow the paperclip heartbeat.", + }, + context: {}, + authToken: "run-jwt-token", + onLog: async (stream, chunk) => { + logs.push({ stream, chunk }); + }, + }); + + expect(result.exitCode).toBe(0); + expect(result.errorMessage).toBeNull(); + + const capture = JSON.parse(await fs.readFile(capturePath, "utf8")) as CapturePayload; + expect(capture.codexHome).toBe(managedCodexHome); + + const managedAuth = path.join(managedCodexHome, "auth.json"); + const managedConfig = path.join(managedCodexHome, "config.toml"); + expect((await fs.lstat(managedAuth)).isSymbolicLink()).toBe(true); + expect(await fs.realpath(managedAuth)).toBe(await fs.realpath(path.join(sharedCodexHome, "auth.json"))); + expect((await fs.lstat(managedConfig)).isFile()).toBe(true); + expect(await fs.readFile(managedConfig, "utf8")).toBe('model = "codex-mini-latest"\n'); + await expect(fs.lstat(path.join(sharedCodexHome, "companies", "company-1"))).rejects.toThrow(); + expect(logs).toContainEqual( + expect.objectContaining({ + stream: "stdout", + chunk: expect.stringContaining("Using Paperclip-managed Codex home"), + }), + ); + } finally { + if (previousHome === undefined) delete process.env.HOME; + else process.env.HOME = previousHome; + if (previousPaperclipHome === undefined) delete process.env.PAPERCLIP_HOME; + else process.env.PAPERCLIP_HOME = previousPaperclipHome; + if (previousPaperclipInstanceId === undefined) delete process.env.PAPERCLIP_INSTANCE_ID; + else process.env.PAPERCLIP_INSTANCE_ID = previousPaperclipInstanceId; + if (previousPaperclipInWorktree === undefined) delete process.env.PAPERCLIP_IN_WORKTREE; + else process.env.PAPERCLIP_IN_WORKTREE = previousPaperclipInWorktree; + if (previousCodexHome === undefined) delete process.env.CODEX_HOME; + else process.env.CODEX_HOME = previousCodexHome; + await fs.rm(root, { recursive: true, force: true }); + } + }); + it("uses a worktree-isolated CODEX_HOME while preserving shared auth and config", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-codex-execute-")); const workspace = path.join(root, "workspace"); diff --git a/server/src/__tests__/company-portability-routes.test.ts b/server/src/__tests__/company-portability-routes.test.ts index bf1c16d3..9fabef46 100644 --- a/server/src/__tests__/company-portability-routes.test.ts +++ b/server/src/__tests__/company-portability-routes.test.ts @@ -97,7 +97,7 @@ describe("company portability routes", () => { }); mockCompanyPortabilityService.previewExport.mockResolvedValue({ rootPath: "paperclip", - manifest: { agents: [], skills: [], projects: [], issues: [], envInputs: [], includes: { company: true, agents: true, projects: true, issues: false }, company: null, schemaVersion: 1, generatedAt: new Date().toISOString(), source: null }, + manifest: { agents: [], skills: [], projects: [], issues: [], envInputs: [], includes: { company: true, agents: true, projects: true, issues: false, skills: false }, company: null, schemaVersion: 1, generatedAt: new Date().toISOString(), source: null }, files: {}, fileInventory: [], counts: { files: 0, agents: 0, skills: 0, projects: 0, issues: 0 }, diff --git a/server/src/__tests__/company-portability.test.ts b/server/src/__tests__/company-portability.test.ts index fbdab1dd..ff019530 100644 --- a/server/src/__tests__/company-portability.test.ts +++ b/server/src/__tests__/company-portability.test.ts @@ -1,5 +1,6 @@ import { Readable } from "node:stream"; import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { CompanyPortabilityFileEntry } from "@paperclipai/shared"; const companySvc = { getById: vi.fn(), @@ -82,8 +83,17 @@ vi.mock("../services/agent-instructions.js", () => ({ agentInstructionsService: () => agentInstructionsSvc, })); +vi.mock("../routes/org-chart-svg.js", () => ({ + renderOrgChartPng: vi.fn(async () => Buffer.from("png")), +})); + const { companyPortabilityService } = await import("../services/company-portability.js"); +function asTextFile(entry: CompanyPortabilityFileEntry | undefined) { + expect(typeof entry).toBe("string"); + return typeof entry === "string" ? entry : ""; +} + describe("company portability", () => { const paperclipKey = "paperclipai/paperclip/paperclip"; const companyPlaybookKey = "company/company-1/company-playbook"; @@ -303,19 +313,19 @@ describe("company portability", () => { }, }); - expect(exported.files["COMPANY.md"]).toContain('name: "Paperclip"'); - 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(`- "${paperclipKey}"`); - expect(exported.files["agents/cmo/AGENTS.md"]).not.toContain("skills:"); - expect(exported.files["skills/paperclipai/paperclip/paperclip/SKILL.md"]).toContain("metadata:"); - expect(exported.files["skills/paperclipai/paperclip/paperclip/SKILL.md"]).toContain('kind: "github-dir"'); + expect(asTextFile(exported.files["COMPANY.md"])).toContain('name: "Paperclip"'); + expect(asTextFile(exported.files["COMPANY.md"])).toContain('schema: "agentcompanies/v1"'); + expect(asTextFile(exported.files["agents/claudecoder/AGENTS.md"])).toContain("You are ClaudeCoder."); + expect(asTextFile(exported.files["agents/claudecoder/AGENTS.md"])).toContain("skills:"); + expect(asTextFile(exported.files["agents/claudecoder/AGENTS.md"])).toContain(`- "${paperclipKey}"`); + expect(asTextFile(exported.files["agents/cmo/AGENTS.md"])).not.toContain("skills:"); + expect(asTextFile(exported.files["skills/paperclipai/paperclip/paperclip/SKILL.md"])).toContain("metadata:"); + expect(asTextFile(exported.files["skills/paperclipai/paperclip/paperclip/SKILL.md"])).toContain('kind: "github-dir"'); expect(exported.files["skills/paperclipai/paperclip/paperclip/references/api.md"]).toBeUndefined(); - expect(exported.files["skills/company/PAP/company-playbook/SKILL.md"]).toContain("# Company Playbook"); - expect(exported.files["skills/company/PAP/company-playbook/references/checklist.md"]).toContain("# Checklist"); + expect(asTextFile(exported.files["skills/company/PAP/company-playbook/SKILL.md"])).toContain("# Company Playbook"); + expect(asTextFile(exported.files["skills/company/PAP/company-playbook/references/checklist.md"])).toContain("# Checklist"); - const extension = exported.files[".paperclip.yaml"]; + const extension = asTextFile(exported.files[".paperclip.yaml"]); expect(extension).toContain('schema: "paperclip/v1"'); expect(extension).not.toContain("promptTemplate"); expect(extension).not.toContain("instructionsFilePath"); @@ -347,9 +357,45 @@ describe("company portability", () => { expandReferencedSkills: true, }); - expect(exported.files["skills/paperclipai/paperclip/paperclip/SKILL.md"]).toContain("# Paperclip"); - expect(exported.files["skills/paperclipai/paperclip/paperclip/SKILL.md"]).toContain("metadata:"); - expect(exported.files["skills/paperclipai/paperclip/paperclip/references/api.md"]).toContain("# API"); + expect(asTextFile(exported.files["skills/paperclipai/paperclip/paperclip/SKILL.md"])).toContain("# Paperclip"); + expect(asTextFile(exported.files["skills/paperclipai/paperclip/paperclip/SKILL.md"])).toContain("metadata:"); + expect(asTextFile(exported.files["skills/paperclipai/paperclip/paperclip/references/api.md"])).toContain("# API"); + }); + + it("exports only selected skills when skills filter is provided", async () => { + const portability = companyPortabilityService({} as any); + + const exported = await portability.exportBundle("company-1", { + include: { + company: true, + agents: true, + projects: false, + issues: false, + }, + skills: ["company-playbook"], + }); + + expect(exported.files["skills/company/PAP/company-playbook/SKILL.md"]).toBeDefined(); + expect(asTextFile(exported.files["skills/company/PAP/company-playbook/SKILL.md"])).toContain("# Company Playbook"); + expect(exported.files["skills/paperclipai/paperclip/paperclip/SKILL.md"]).toBeUndefined(); + }); + + it("warns and exports all skills when skills filter matches nothing", async () => { + const portability = companyPortabilityService({} as any); + + const exported = await portability.exportBundle("company-1", { + include: { + company: true, + agents: true, + projects: false, + issues: false, + }, + skills: ["nonexistent-skill"], + }); + + expect(exported.warnings).toContainEqual(expect.stringContaining("nonexistent-skill")); + expect(exported.files["skills/company/PAP/company-playbook/SKILL.md"]).toBeDefined(); + expect(exported.files["skills/paperclipai/paperclip/paperclip/SKILL.md"]).toBeDefined(); }); it("exports the company logo into images/ and references it from .paperclip.yaml", async () => { @@ -476,9 +522,9 @@ describe("company portability", () => { }, }); - expect(exported.files["skills/local/release-changelog/SKILL.md"]).toContain("# Local Release Changelog"); - expect(exported.files["skills/paperclipai/paperclip/release-changelog/SKILL.md"]).toContain("metadata:"); - expect(exported.files["skills/paperclipai/paperclip/release-changelog/SKILL.md"]).toContain("paperclipai/paperclip/release-changelog"); + expect(asTextFile(exported.files["skills/local/release-changelog/SKILL.md"])).toContain("# Local Release Changelog"); + expect(asTextFile(exported.files["skills/paperclipai/paperclip/release-changelog/SKILL.md"])).toContain("metadata:"); + expect(asTextFile(exported.files["skills/paperclipai/paperclip/release-changelog/SKILL.md"])).toContain("paperclipai/paperclip/release-changelog"); }); it("builds export previews without tasks by default", async () => { @@ -582,6 +628,181 @@ describe("company portability", () => { ]); }); + it("imports a vendor-neutral package without .paperclip.yaml", async () => { + const portability = companyPortabilityService({} as any); + + companySvc.create.mockResolvedValue({ + id: "company-imported", + name: "Imported Paperclip", + }); + accessSvc.ensureMembership.mockResolvedValue(undefined); + agentSvc.create.mockResolvedValue({ + id: "agent-created", + name: "ClaudeCoder", + }); + + const preview = await portability.previewImport({ + source: { + type: "inline", + rootPath: "paperclip-demo", + files: { + "COMPANY.md": [ + "---", + 'schema: "agentcompanies/v1"', + 'name: "Imported Paperclip"', + 'description: "Portable company package"', + "---", + "", + "# Imported Paperclip", + "", + ].join("\n"), + "agents/claudecoder/AGENTS.md": [ + "---", + 'name: "ClaudeCoder"', + 'title: "Software Engineer"', + "---", + "", + "# ClaudeCoder", + "", + "You write code.", + "", + ].join("\n"), + }, + }, + include: { + company: true, + agents: true, + projects: false, + issues: false, + }, + target: { + mode: "new_company", + newCompanyName: "Imported Paperclip", + }, + agents: "all", + collisionStrategy: "rename", + }); + + expect(preview.errors).toEqual([]); + expect(preview.manifest.company?.name).toBe("Imported Paperclip"); + expect(preview.manifest.agents).toEqual([ + expect.objectContaining({ + slug: "claudecoder", + name: "ClaudeCoder", + adapterType: "process", + }), + ]); + expect(preview.envInputs).toEqual([]); + + await portability.importBundle({ + source: { + type: "inline", + rootPath: "paperclip-demo", + files: { + "COMPANY.md": [ + "---", + 'schema: "agentcompanies/v1"', + 'name: "Imported Paperclip"', + 'description: "Portable company package"', + "---", + "", + "# Imported Paperclip", + "", + ].join("\n"), + "agents/claudecoder/AGENTS.md": [ + "---", + 'name: "ClaudeCoder"', + 'title: "Software Engineer"', + "---", + "", + "# ClaudeCoder", + "", + "You write code.", + "", + ].join("\n"), + }, + }, + include: { + company: true, + agents: true, + projects: false, + issues: false, + }, + target: { + mode: "new_company", + newCompanyName: "Imported Paperclip", + }, + agents: "all", + collisionStrategy: "rename", + }, "user-1"); + + expect(companySvc.create).toHaveBeenCalledWith(expect.objectContaining({ + name: "Imported Paperclip", + description: "Portable company package", + })); + expect(agentSvc.create).toHaveBeenCalledWith("company-imported", expect.objectContaining({ + name: "ClaudeCoder", + adapterType: "process", + })); + }); + + it("treats no-separator auth and api key env names as secrets during export", async () => { + const portability = companyPortabilityService({} as any); + + agentSvc.list.mockResolvedValue([ + { + id: "agent-1", + name: "ClaudeCoder", + status: "idle", + role: "engineer", + title: "Software Engineer", + icon: "code", + reportsTo: null, + capabilities: "Writes code", + adapterType: "claude_local", + adapterConfig: { + promptTemplate: "You are ClaudeCoder.", + env: { + APIKEY: { + type: "plain", + value: "sk-plain-api", + }, + GITHUBAUTH: { + type: "plain", + value: "gh-auth-token", + }, + PRIVATEKEY: { + type: "plain", + value: "private-key-value", + }, + }, + }, + runtimeConfig: {}, + budgetMonthlyCents: 0, + permissions: {}, + metadata: null, + }, + ]); + + const exported = await portability.exportBundle("company-1", { + include: { + company: true, + agents: true, + projects: false, + issues: false, + }, + }); + + const extension = asTextFile(exported.files[".paperclip.yaml"]); + expect(extension).toContain("APIKEY:"); + expect(extension).toContain("GITHUBAUTH:"); + expect(extension).toContain("PRIVATEKEY:"); + expect(extension).not.toContain("sk-plain-api"); + expect(extension).not.toContain("gh-auth-token"); + expect(extension).not.toContain("private-key-value"); + expect(extension).toContain('kind: "secret"'); + }); + it("imports packaged skills and restores desired skill refs on agents", async () => { const portability = companyPortabilityService({} as any); @@ -626,7 +847,8 @@ describe("company portability", () => { collisionStrategy: "rename", }, "user-1"); - expect(companySkillSvc.importPackageFiles).toHaveBeenCalledWith("company-imported", exported.files, { + const textOnlyFiles = Object.fromEntries(Object.entries(exported.files).filter(([, v]) => typeof v === "string")); + expect(companySkillSvc.importPackageFiles).toHaveBeenCalledWith("company-imported", textOnlyFiles, { onConflict: "replace", }); expect(agentSvc.create).toHaveBeenCalledWith("company-imported", expect.objectContaining({ @@ -772,7 +994,8 @@ describe("company portability", () => { expect(accessSvc.listActiveUserMemberships).toHaveBeenCalledWith("company-1"); expect(accessSvc.copyActiveUserMemberships).toHaveBeenCalledWith("company-1", "company-imported"); expect(accessSvc.ensureMembership).not.toHaveBeenCalledWith("company-imported", "user", expect.anything(), "owner", "active"); - expect(companySkillSvc.importPackageFiles).toHaveBeenCalledWith("company-imported", exported.files, { + const textOnlyFiles = Object.fromEntries(Object.entries(exported.files).filter(([, v]) => typeof v === "string")); + expect(companySkillSvc.importPackageFiles).toHaveBeenCalledWith("company-imported", textOnlyFiles, { onConflict: "rename", }); }); diff --git a/server/src/__tests__/company-skills.test.ts b/server/src/__tests__/company-skills.test.ts index 362ae65c..17da7804 100644 --- a/server/src/__tests__/company-skills.test.ts +++ b/server/src/__tests__/company-skills.test.ts @@ -35,14 +35,36 @@ describe("company skill import source parsing", () => { expect(parsed.resolvedSource).toBe("https://github.com/vercel-labs/skills"); expect(parsed.requestedSkillSlug).toBe("find-skills"); + expect(parsed.originalSkillsShUrl).toBeNull(); expect(parsed.warnings).toEqual([]); }); - it("parses owner/repo/skill shorthand as a GitHub repo plus requested skill", () => { + it("parses owner/repo/skill shorthand as skills.sh-managed", () => { const parsed = parseSkillImportSourceInput("vercel-labs/skills/find-skills"); expect(parsed.resolvedSource).toBe("https://github.com/vercel-labs/skills"); expect(parsed.requestedSkillSlug).toBe("find-skills"); + expect(parsed.originalSkillsShUrl).toBe("https://skills.sh/vercel-labs/skills/find-skills"); + }); + + it("resolves skills.sh URL with org/repo/skill to GitHub repo and preserves original URL", () => { + const parsed = parseSkillImportSourceInput( + "https://skills.sh/google-labs-code/stitch-skills/design-md", + ); + + expect(parsed.resolvedSource).toBe("https://github.com/google-labs-code/stitch-skills"); + expect(parsed.requestedSkillSlug).toBe("design-md"); + expect(parsed.originalSkillsShUrl).toBe("https://skills.sh/google-labs-code/stitch-skills/design-md"); + }); + + it("resolves skills.sh URL with org/repo (no skill) to GitHub repo and preserves original URL", () => { + const parsed = parseSkillImportSourceInput( + "https://skills.sh/vercel-labs/skills", + ); + + expect(parsed.resolvedSource).toBe("https://github.com/vercel-labs/skills"); + expect(parsed.requestedSkillSlug).toBeNull(); + expect(parsed.originalSkillsShUrl).toBe("https://skills.sh/vercel-labs/skills"); }); it("parses skills.sh commands whose requested skill differs from the folder name", () => { @@ -52,6 +74,14 @@ describe("company skill import source parsing", () => { expect(parsed.resolvedSource).toBe("https://github.com/remotion-dev/skills"); expect(parsed.requestedSkillSlug).toBe("remotion-best-practices"); + expect(parsed.originalSkillsShUrl).toBeNull(); + }); + + it("does not set originalSkillsShUrl for owner/repo shorthand", () => { + const parsed = parseSkillImportSourceInput("vercel-labs/skills"); + + expect(parsed.resolvedSource).toBe("https://github.com/vercel-labs/skills"); + expect(parsed.originalSkillsShUrl).toBeNull(); }); }); @@ -108,6 +138,45 @@ describe("project workspace skill discovery", () => { expect(imported.fileInventory.map((entry) => entry.kind)).toContain("script"); expect(imported.metadata?.sourceKind).toBe("project_scan"); }); + + it("parses inline object array items in skill frontmatter metadata", async () => { + const workspace = await makeTempDir("paperclip-inline-skill-yaml-"); + await fs.mkdir(workspace, { recursive: true }); + await fs.writeFile( + path.join(workspace, "SKILL.md"), + [ + "---", + "name: Inline Metadata Skill", + "metadata:", + " sources:", + " - kind: github-dir", + " repo: paperclipai/paperclip", + " path: skills/paperclip", + "---", + "", + "# Inline Metadata Skill", + "", + ].join("\n"), + "utf8", + ); + + const imported = await readLocalSkillImportFromDirectory( + "33333333-3333-4333-8333-333333333333", + workspace, + { inventoryMode: "full" }, + ); + + expect(imported.metadata).toMatchObject({ + sourceKind: "local_path", + sources: [ + { + kind: "github-dir", + repo: "paperclipai/paperclip", + path: "skills/paperclip", + }, + ], + }); + }); }); describe("missing local skill reconciliation", () => { diff --git a/server/src/__tests__/dev-server-status.test.ts b/server/src/__tests__/dev-server-status.test.ts new file mode 100644 index 00000000..d178f941 --- /dev/null +++ b/server/src/__tests__/dev-server-status.test.ts @@ -0,0 +1,66 @@ +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { readPersistedDevServerStatus, toDevServerHealthStatus } from "../dev-server-status.js"; + +const tempDirs = []; + +function createTempStatusFile(payload: unknown) { + const dir = mkdtempSync(path.join(os.tmpdir(), "paperclip-dev-status-")); + tempDirs.push(dir); + const filePath = path.join(dir, "dev-server-status.json"); + writeFileSync(filePath, `${JSON.stringify(payload)}\n`, "utf8"); + return filePath; +} + +afterEach(() => { + for (const dir of tempDirs.splice(0)) { + rmSync(dir, { recursive: true, force: true }); + } +}); + +describe("dev server status helpers", () => { + it("reads and normalizes persisted supervisor state", () => { + const filePath = createTempStatusFile({ + dirty: true, + lastChangedAt: "2026-03-20T12:00:00.000Z", + changedPathCount: 4, + changedPathsSample: ["server/src/app.ts", "packages/shared/src/index.ts"], + pendingMigrations: ["0040_restart_banner.sql"], + lastRestartAt: "2026-03-20T11:30:00.000Z", + }); + + expect(readPersistedDevServerStatus({ PAPERCLIP_DEV_SERVER_STATUS_FILE: filePath })).toEqual({ + dirty: true, + lastChangedAt: "2026-03-20T12:00:00.000Z", + changedPathCount: 4, + changedPathsSample: ["server/src/app.ts", "packages/shared/src/index.ts"], + pendingMigrations: ["0040_restart_banner.sql"], + lastRestartAt: "2026-03-20T11:30:00.000Z", + }); + }); + + it("derives waiting-for-idle health state", () => { + const health = toDevServerHealthStatus( + { + dirty: true, + lastChangedAt: "2026-03-20T12:00:00.000Z", + changedPathCount: 2, + changedPathsSample: ["server/src/app.ts"], + pendingMigrations: [], + lastRestartAt: "2026-03-20T11:30:00.000Z", + }, + { autoRestartEnabled: true, activeRunCount: 3 }, + ); + + expect(health).toMatchObject({ + enabled: true, + restartRequired: true, + reason: "backend_changes", + autoRestartEnabled: true, + activeRunCount: 3, + waitingForIdle: true, + }); + }); +}); diff --git a/server/src/__tests__/instance-settings-routes.test.ts b/server/src/__tests__/instance-settings-routes.test.ts index 3014e668..9668d1bf 100644 --- a/server/src/__tests__/instance-settings-routes.test.ts +++ b/server/src/__tests__/instance-settings-routes.test.ts @@ -5,7 +5,9 @@ import { errorHandler } from "../middleware/index.js"; import { instanceSettingsRoutes } from "../routes/instance-settings.js"; const mockInstanceSettingsService = vi.hoisted(() => ({ + getGeneral: vi.fn(), getExperimental: vi.fn(), + updateGeneral: vi.fn(), updateExperimental: vi.fn(), listCompanyIds: vi.fn(), })); @@ -31,13 +33,24 @@ function createApp(actor: any) { describe("instance settings routes", () => { beforeEach(() => { vi.clearAllMocks(); + mockInstanceSettingsService.getGeneral.mockResolvedValue({ + censorUsernameInLogs: false, + }); mockInstanceSettingsService.getExperimental.mockResolvedValue({ enableIsolatedWorkspaces: false, + autoRestartDevServerWhenIdle: false, + }); + mockInstanceSettingsService.updateGeneral.mockResolvedValue({ + id: "instance-settings-1", + general: { + censorUsernameInLogs: true, + }, }); mockInstanceSettingsService.updateExperimental.mockResolvedValue({ id: "instance-settings-1", experimental: { enableIsolatedWorkspaces: true, + autoRestartDevServerWhenIdle: false, }, }); mockInstanceSettingsService.listCompanyIds.mockResolvedValue(["company-1", "company-2"]); @@ -53,7 +66,10 @@ describe("instance settings routes", () => { const getRes = await request(app).get("/api/instance/settings/experimental"); expect(getRes.status).toBe(200); - expect(getRes.body).toEqual({ enableIsolatedWorkspaces: false }); + expect(getRes.body).toEqual({ + enableIsolatedWorkspaces: false, + autoRestartDevServerWhenIdle: false, + }); const patchRes = await request(app) .patch("/api/instance/settings/experimental") @@ -66,6 +82,47 @@ describe("instance settings routes", () => { expect(mockLogActivity).toHaveBeenCalledTimes(2); }); + it("allows local board users to update guarded dev-server auto-restart", async () => { + const app = createApp({ + type: "board", + userId: "local-board", + source: "local_implicit", + isInstanceAdmin: true, + }); + + await request(app) + .patch("/api/instance/settings/experimental") + .send({ autoRestartDevServerWhenIdle: true }) + .expect(200); + + expect(mockInstanceSettingsService.updateExperimental).toHaveBeenCalledWith({ + autoRestartDevServerWhenIdle: true, + }); + }); + + it("allows local board users to read and update general settings", async () => { + const app = createApp({ + type: "board", + userId: "local-board", + source: "local_implicit", + isInstanceAdmin: true, + }); + + const getRes = await request(app).get("/api/instance/settings/general"); + expect(getRes.status).toBe(200); + expect(getRes.body).toEqual({ censorUsernameInLogs: false }); + + const patchRes = await request(app) + .patch("/api/instance/settings/general") + .send({ censorUsernameInLogs: true }); + + expect(patchRes.status).toBe(200); + expect(mockInstanceSettingsService.updateGeneral).toHaveBeenCalledWith({ + censorUsernameInLogs: true, + }); + expect(mockLogActivity).toHaveBeenCalledTimes(2); + }); + it("rejects non-admin board users", async () => { const app = createApp({ type: "board", @@ -75,10 +132,10 @@ describe("instance settings routes", () => { companyIds: ["company-1"], }); - const res = await request(app).get("/api/instance/settings/experimental"); + const res = await request(app).get("/api/instance/settings/general"); expect(res.status).toBe(403); - expect(mockInstanceSettingsService.getExperimental).not.toHaveBeenCalled(); + expect(mockInstanceSettingsService.getGeneral).not.toHaveBeenCalled(); }); it("rejects agent callers", async () => { @@ -90,10 +147,10 @@ describe("instance settings routes", () => { }); const res = await request(app) - .patch("/api/instance/settings/experimental") - .send({ enableIsolatedWorkspaces: true }); + .patch("/api/instance/settings/general") + .send({ censorUsernameInLogs: true }); expect(res.status).toBe(403); - expect(mockInstanceSettingsService.updateExperimental).not.toHaveBeenCalled(); + expect(mockInstanceSettingsService.updateGeneral).not.toHaveBeenCalled(); }); }); diff --git a/server/src/__tests__/log-redaction.test.ts b/server/src/__tests__/log-redaction.test.ts index a1da7a2e..35915bfc 100644 --- a/server/src/__tests__/log-redaction.test.ts +++ b/server/src/__tests__/log-redaction.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import { - CURRENT_USER_REDACTION_TOKEN, + maskUserNameForLogs, redactCurrentUserText, redactCurrentUserValue, } from "../log-redaction.js"; @@ -8,6 +8,7 @@ import { describe("log redaction", () => { it("redacts the active username inside home-directory paths", () => { const userName = "paperclipuser"; + const maskedUserName = maskUserNameForLogs(userName); const input = [ `cwd=/Users/${userName}/paperclip`, `home=/home/${userName}/workspace`, @@ -19,14 +20,15 @@ describe("log redaction", () => { homeDirs: [`/Users/${userName}`, `/home/${userName}`, `C:\\Users\\${userName}`], }); - expect(result).toContain(`cwd=/Users/${CURRENT_USER_REDACTION_TOKEN}/paperclip`); - expect(result).toContain(`home=/home/${CURRENT_USER_REDACTION_TOKEN}/workspace`); - expect(result).toContain(`win=C:\\Users\\${CURRENT_USER_REDACTION_TOKEN}\\paperclip`); + expect(result).toContain(`cwd=/Users/${maskedUserName}/paperclip`); + expect(result).toContain(`home=/home/${maskedUserName}/workspace`); + expect(result).toContain(`win=C:\\Users\\${maskedUserName}\\paperclip`); expect(result).not.toContain(userName); }); it("redacts standalone username mentions without mangling larger tokens", () => { const userName = "paperclipuser"; + const maskedUserName = maskUserNameForLogs(userName); const result = redactCurrentUserText( `user ${userName} said ${userName}/project should stay but apaperclipuserz should not change`, { @@ -36,12 +38,13 @@ describe("log redaction", () => { ); expect(result).toBe( - `user ${CURRENT_USER_REDACTION_TOKEN} said ${CURRENT_USER_REDACTION_TOKEN}/project should stay but apaperclipuserz should not change`, + `user ${maskedUserName} said ${maskedUserName}/project should stay but apaperclipuserz should not change`, ); }); it("recursively redacts nested event payloads", () => { const userName = "paperclipuser"; + const maskedUserName = maskUserNameForLogs(userName); const result = redactCurrentUserValue({ cwd: `/Users/${userName}/paperclip`, prompt: `open /Users/${userName}/paperclip/ui`, @@ -55,12 +58,17 @@ describe("log redaction", () => { }); expect(result).toEqual({ - cwd: `/Users/${CURRENT_USER_REDACTION_TOKEN}/paperclip`, - prompt: `open /Users/${CURRENT_USER_REDACTION_TOKEN}/paperclip/ui`, + cwd: `/Users/${maskedUserName}/paperclip`, + prompt: `open /Users/${maskedUserName}/paperclip/ui`, nested: { - author: CURRENT_USER_REDACTION_TOKEN, + author: maskedUserName, }, - values: [CURRENT_USER_REDACTION_TOKEN, `/home/${CURRENT_USER_REDACTION_TOKEN}/project`], + values: [maskedUserName, `/home/${maskedUserName}/project`], }); }); + + it("skips redaction when disabled", () => { + const input = "cwd=/Users/paperclipuser/paperclip"; + expect(redactCurrentUserText(input, { enabled: false })).toBe(input); + }); }); diff --git a/server/src/dev-server-status.ts b/server/src/dev-server-status.ts new file mode 100644 index 00000000..aecb0fc9 --- /dev/null +++ b/server/src/dev-server-status.ts @@ -0,0 +1,103 @@ +import { existsSync, readFileSync } from "node:fs"; + +export type PersistedDevServerStatus = { + dirty: boolean; + lastChangedAt: string | null; + changedPathCount: number; + changedPathsSample: string[]; + pendingMigrations: string[]; + lastRestartAt: string | null; +}; + +export type DevServerHealthStatus = { + enabled: true; + restartRequired: boolean; + reason: "backend_changes" | "pending_migrations" | "backend_changes_and_pending_migrations" | null; + lastChangedAt: string | null; + changedPathCount: number; + changedPathsSample: string[]; + pendingMigrations: string[]; + autoRestartEnabled: boolean; + activeRunCount: number; + waitingForIdle: boolean; + lastRestartAt: string | null; +}; + +function normalizeStringArray(value: unknown): string[] { + if (!Array.isArray(value)) return []; + return value + .filter((entry): entry is string => typeof entry === "string") + .map((entry) => entry.trim()) + .filter((entry) => entry.length > 0); +} + +function normalizeTimestamp(value: unknown): string | null { + if (typeof value !== "string") return null; + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; +} + +export function readPersistedDevServerStatus( + env: NodeJS.ProcessEnv = process.env, +): PersistedDevServerStatus | null { + const filePath = env.PAPERCLIP_DEV_SERVER_STATUS_FILE?.trim(); + if (!filePath || !existsSync(filePath)) return null; + + try { + const raw = JSON.parse(readFileSync(filePath, "utf8")) as Record; + const changedPathsSample = normalizeStringArray(raw.changedPathsSample).slice(0, 5); + const pendingMigrations = normalizeStringArray(raw.pendingMigrations); + const changedPathCountRaw = raw.changedPathCount; + const changedPathCount = + typeof changedPathCountRaw === "number" && Number.isFinite(changedPathCountRaw) + ? Math.max(0, Math.trunc(changedPathCountRaw)) + : changedPathsSample.length; + const dirtyRaw = raw.dirty; + const dirty = + typeof dirtyRaw === "boolean" + ? dirtyRaw + : changedPathCount > 0 || pendingMigrations.length > 0; + + return { + dirty, + lastChangedAt: normalizeTimestamp(raw.lastChangedAt), + changedPathCount, + changedPathsSample, + pendingMigrations, + lastRestartAt: normalizeTimestamp(raw.lastRestartAt), + }; + } catch { + return null; + } +} + +export function toDevServerHealthStatus( + persisted: PersistedDevServerStatus, + opts: { autoRestartEnabled: boolean; activeRunCount: number }, +): DevServerHealthStatus { + const hasPathChanges = persisted.changedPathCount > 0; + const hasPendingMigrations = persisted.pendingMigrations.length > 0; + const reason = + hasPathChanges && hasPendingMigrations + ? "backend_changes_and_pending_migrations" + : hasPendingMigrations + ? "pending_migrations" + : hasPathChanges + ? "backend_changes" + : null; + const restartRequired = persisted.dirty || reason !== null; + + return { + enabled: true, + restartRequired, + reason, + lastChangedAt: persisted.lastChangedAt, + changedPathCount: persisted.changedPathCount, + changedPathsSample: persisted.changedPathsSample, + pendingMigrations: persisted.pendingMigrations, + autoRestartEnabled: opts.autoRestartEnabled, + activeRunCount: opts.activeRunCount, + waitingForIdle: restartRequired && opts.autoRestartEnabled && opts.activeRunCount > 0, + lastRestartAt: persisted.lastRestartAt, + }; +} diff --git a/server/src/log-redaction.ts b/server/src/log-redaction.ts index 07a28c58..ab59b3e4 100644 --- a/server/src/log-redaction.ts +++ b/server/src/log-redaction.ts @@ -1,8 +1,9 @@ import os from "node:os"; -export const CURRENT_USER_REDACTION_TOKEN = "[]"; +export const CURRENT_USER_REDACTION_TOKEN = "*"; -interface CurrentUserRedactionOptions { +export interface CurrentUserRedactionOptions { + enabled?: boolean; replacement?: string; userNames?: string[]; homeDirs?: string[]; @@ -39,6 +40,12 @@ function replaceLastPathSegment(pathValue: string, replacement: string) { return `${normalized.slice(0, lastSeparator + 1)}${replacement}`; } +export function maskUserNameForLogs(value: string, fallback = CURRENT_USER_REDACTION_TOKEN) { + const trimmed = value.trim(); + if (!trimmed) return fallback; + return `${trimmed[0]}${"*".repeat(Math.max(1, Array.from(trimmed).length - 1))}`; +} + function defaultUserNames() { const candidates = [ process.env.USER, @@ -99,21 +106,22 @@ function resolveCurrentUserCandidates(opts?: CurrentUserRedactionOptions) { export function redactCurrentUserText(input: string, opts?: CurrentUserRedactionOptions) { if (!input) return input; + if (opts?.enabled === false) return input; const { userNames, homeDirs, replacement } = resolveCurrentUserCandidates(opts); let result = input; for (const homeDir of [...homeDirs].sort((a, b) => b.length - a.length)) { const lastSegment = splitPathSegments(homeDir).pop() ?? ""; - const replacementDir = userNames.includes(lastSegment) - ? replaceLastPathSegment(homeDir, replacement) + const replacementDir = lastSegment + ? replaceLastPathSegment(homeDir, maskUserNameForLogs(lastSegment, replacement)) : replacement; result = result.split(homeDir).join(replacementDir); } for (const userName of [...userNames].sort((a, b) => b.length - a.length)) { const pattern = new RegExp(`(? = { @@ -63,7 +69,9 @@ export function agentRoutes(db: Db) { gemini_local: "instructionsFilePath", opencode_local: "instructionsFilePath", cursor: "instructionsFilePath", + pi_local: "instructionsFilePath", }; + const DEFAULT_MANAGED_INSTRUCTIONS_ADAPTER_TYPES = new Set(Object.keys(DEFAULT_INSTRUCTIONS_PATH_KEYS)); const KNOWN_INSTRUCTIONS_PATH_KEYS = new Set(["instructionsFilePath", "agentsMdPath"]); const router = Router(); @@ -77,8 +85,15 @@ export function agentRoutes(db: Db) { const instructions = agentInstructionsService(); const companySkills = companySkillService(db); const workspaceOperations = workspaceOperationService(db); + const instanceSettings = instanceSettingsService(db); const strictSecretsMode = process.env.PAPERCLIP_SECRETS_STRICT_MODE === "true"; + async function getCurrentUserRedactionOptions() { + return { + enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs, + }; + } + function canCreateAgents(agent: { role: string; permissions: Record | null | undefined }) { if (!agent.permissions || typeof agent.permissions !== "object") return false; return Boolean((agent.permissions as Record).canCreateAgents); @@ -402,6 +417,47 @@ export function agentRoutes(db: Db) { return path.resolve(cwd, trimmed); } + async function materializeDefaultInstructionsBundleForNewAgent(agent: T): Promise { + if (!DEFAULT_MANAGED_INSTRUCTIONS_ADAPTER_TYPES.has(agent.adapterType)) { + return agent; + } + + const adapterConfig = asRecord(agent.adapterConfig) ?? {}; + const hasExplicitInstructionsBundle = + Boolean(asNonEmptyString(adapterConfig.instructionsBundleMode)) + || Boolean(asNonEmptyString(adapterConfig.instructionsRootPath)) + || Boolean(asNonEmptyString(adapterConfig.instructionsEntryFile)) + || Boolean(asNonEmptyString(adapterConfig.instructionsFilePath)) + || Boolean(asNonEmptyString(adapterConfig.agentsMdPath)); + if (hasExplicitInstructionsBundle) { + return agent; + } + + const promptTemplate = typeof adapterConfig.promptTemplate === "string" + ? adapterConfig.promptTemplate + : ""; + const files = promptTemplate.trim().length === 0 + ? await loadDefaultAgentInstructionsBundle(resolveDefaultAgentInstructionsBundleRole(agent.role)) + : { "AGENTS.md": promptTemplate }; + const materialized = await instructions.materializeManagedBundle( + agent, + files, + { entryFile: "AGENTS.md", replaceExisting: false }, + ); + const nextAdapterConfig = { ...materialized.adapterConfig }; + delete nextAdapterConfig.promptTemplate; + + const updated = await svc.update(agent.id, { adapterConfig: nextAdapterConfig }); + return (updated as T | null) ?? { ...agent, adapterConfig: nextAdapterConfig }; + } + async function assertCanManageInstructionsPath(req: Request, targetAgent: { id: string; companyId: string }) { assertCompanyAccess(req, targetAgent.companyId); if (req.actor.type === "board") return; @@ -856,6 +912,30 @@ export function agentRoutes(db: Db) { res.json(leanTree); }); + router.get("/companies/:companyId/org.svg", async (req, res) => { + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + const style = (ORG_CHART_STYLES.includes(req.query.style as OrgChartStyle) ? req.query.style : "warmth") as OrgChartStyle; + const tree = await svc.orgForCompany(companyId); + const leanTree = tree.map((node) => toLeanOrgNode(node as Record)); + const svg = renderOrgChartSvg(leanTree as unknown as OrgNode[], style); + res.setHeader("Content-Type", "image/svg+xml"); + res.setHeader("Cache-Control", "no-cache"); + res.send(svg); + }); + + router.get("/companies/:companyId/org.png", async (req, res) => { + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + const style = (ORG_CHART_STYLES.includes(req.query.style as OrgChartStyle) ? req.query.style : "warmth") as OrgChartStyle; + const tree = await svc.orgForCompany(companyId); + const leanTree = tree.map((node) => toLeanOrgNode(node as Record)); + const png = await renderOrgChartPng(leanTree as unknown as OrgNode[], style); + res.setHeader("Content-Type", "image/png"); + res.setHeader("Cache-Control", "no-cache"); + res.send(png); + }); + router.get("/companies/:companyId/agent-configurations", async (req, res) => { const companyId = req.params.companyId as string; await assertCanReadConfigurations(req, companyId); @@ -1106,12 +1186,13 @@ export function agentRoutes(db: Db) { const requiresApproval = company.requireBoardApprovalForNewAgents; const status = requiresApproval ? "pending_approval" : "idle"; - const agent = await svc.create(companyId, { + const createdAgent = await svc.create(companyId, { ...normalizedHireInput, status, spentMonthlyCents: 0, lastHeartbeatAt: null, }); + const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent); let approval: Awaited> | null = null; const actor = getActorInfo(req); @@ -1120,7 +1201,7 @@ export function agentRoutes(db: Db) { const requestedAdapterType = normalizedHireInput.adapterType ?? agent.adapterType; const requestedAdapterConfig = redactEventPayload( - (normalizedHireInput.adapterConfig ?? agent.adapterConfig) as Record, + (agent.adapterConfig ?? normalizedHireInput.adapterConfig) as Record, ) ?? {}; const requestedRuntimeConfig = redactEventPayload( @@ -1249,13 +1330,14 @@ export function agentRoutes(db: Db) { normalizedAdapterConfig, ); - const agent = await svc.create(companyId, { + const createdAgent = await svc.create(companyId, { ...createInput, adapterConfig: normalizedAdapterConfig, status: "idle", spentMonthlyCents: 0, lastHeartbeatAt: null, }); + const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent); const actor = getActorInfo(req); await logActivity(db, { @@ -2010,7 +2092,7 @@ export function agentRoutes(db: Db) { return; } assertCompanyAccess(req, run.companyId); - res.json(redactCurrentUserValue(run)); + res.json(redactCurrentUserValue(run, await getCurrentUserRedactionOptions())); }); router.post("/heartbeat-runs/:runId/cancel", async (req, res) => { @@ -2045,11 +2127,12 @@ export function agentRoutes(db: Db) { const afterSeq = Number(req.query.afterSeq ?? 0); const limit = Number(req.query.limit ?? 200); const events = await heartbeat.listEvents(runId, Number.isFinite(afterSeq) ? afterSeq : 0, Number.isFinite(limit) ? limit : 200); + const currentUserRedactionOptions = await getCurrentUserRedactionOptions(); const redactedEvents = events.map((event) => redactCurrentUserValue({ ...event, payload: redactEventPayload(event.payload), - }), + }, currentUserRedactionOptions), ); res.json(redactedEvents); }); @@ -2085,7 +2168,7 @@ export function agentRoutes(db: Db) { const context = asRecord(run.contextSnapshot); const executionWorkspaceId = asNonEmptyString(context?.executionWorkspaceId); const operations = await workspaceOperations.listForRun(runId, executionWorkspaceId); - res.json(redactCurrentUserValue(operations)); + res.json(redactCurrentUserValue(operations, await getCurrentUserRedactionOptions())); }); router.get("/workspace-operations/:operationId/log", async (req, res) => { @@ -2181,7 +2264,7 @@ export function agentRoutes(db: Db) { } res.json({ - ...redactCurrentUserValue(run), + ...redactCurrentUserValue(run, await getCurrentUserRedactionOptions()), agentId: agent.id, agentName: agent.name, adapterType: agent.adapterType, diff --git a/server/src/routes/company-skills.ts b/server/src/routes/company-skills.ts index c8de035b..7b239832 100644 --- a/server/src/routes/company-skills.ts +++ b/server/src/routes/company-skills.ts @@ -221,6 +221,35 @@ export function companySkillRoutes(db: Db) { }, ); + router.delete("/companies/:companyId/skills/:skillId", async (req, res) => { + const companyId = req.params.companyId as string; + const skillId = req.params.skillId as string; + await assertCanMutateCompanySkills(req, companyId); + const result = await svc.deleteSkill(companyId, skillId); + if (!result) { + res.status(404).json({ error: "Skill not found" }); + return; + } + + const actor = getActorInfo(req); + await logActivity(db, { + companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "company.skill_deleted", + entityType: "company_skill", + entityId: result.id, + details: { + slug: result.slug, + name: result.name, + }, + }); + + res.json(result); + }); + router.post("/companies/:companyId/skills/:skillId/install-update", async (req, res) => { const companyId = req.params.companyId as string; const skillId = req.params.skillId as string; diff --git a/server/src/routes/health.ts b/server/src/routes/health.ts index 59897a89..0bf6e92f 100644 --- a/server/src/routes/health.ts +++ b/server/src/routes/health.ts @@ -1,8 +1,10 @@ import { Router } from "express"; import type { Db } from "@paperclipai/db"; -import { and, count, eq, gt, isNull, sql } from "drizzle-orm"; -import { instanceUserRoles, invites } from "@paperclipai/db"; +import { and, count, eq, gt, inArray, isNull, sql } from "drizzle-orm"; +import { heartbeatRuns, instanceUserRoles, invites } from "@paperclipai/db"; import type { DeploymentExposure, DeploymentMode } from "@paperclipai/shared"; +import { readPersistedDevServerStatus, toDevServerHealthStatus } from "../dev-server-status.js"; +import { instanceSettingsService } from "../services/instance-settings.js"; import { serverVersion } from "../version.js"; export function healthRoutes( @@ -55,6 +57,23 @@ export function healthRoutes( } } + const persistedDevServerStatus = readPersistedDevServerStatus(); + let devServer: ReturnType | undefined; + if (persistedDevServerStatus) { + const instanceSettings = instanceSettingsService(db); + const experimentalSettings = await instanceSettings.getExperimental(); + const activeRunCount = await db + .select({ count: count() }) + .from(heartbeatRuns) + .where(inArray(heartbeatRuns.status, ["queued", "running"])) + .then((rows) => Number(rows[0]?.count ?? 0)); + + devServer = toDevServerHealthStatus(persistedDevServerStatus, { + autoRestartEnabled: experimentalSettings.autoRestartDevServerWhenIdle ?? false, + activeRunCount, + }); + } + res.json({ status: "ok", version: serverVersion, @@ -66,6 +85,7 @@ export function healthRoutes( features: { companyDeletionEnabled: opts.companyDeletionEnabled, }, + ...(devServer ? { devServer } : {}), }); }); diff --git a/server/src/routes/instance-settings.ts b/server/src/routes/instance-settings.ts index 93ae9f6a..1c9493ca 100644 --- a/server/src/routes/instance-settings.ts +++ b/server/src/routes/instance-settings.ts @@ -1,6 +1,6 @@ import { Router, type Request } from "express"; import type { Db } from "@paperclipai/db"; -import { patchInstanceExperimentalSettingsSchema } from "@paperclipai/shared"; +import { patchInstanceExperimentalSettingsSchema, patchInstanceGeneralSettingsSchema } from "@paperclipai/shared"; import { forbidden } from "../errors.js"; import { validate } from "../middleware/validate.js"; import { instanceSettingsService, logActivity } from "../services/index.js"; @@ -20,6 +20,41 @@ export function instanceSettingsRoutes(db: Db) { const router = Router(); const svc = instanceSettingsService(db); + router.get("/instance/settings/general", async (req, res) => { + assertCanManageInstanceSettings(req); + res.json(await svc.getGeneral()); + }); + + router.patch( + "/instance/settings/general", + validate(patchInstanceGeneralSettingsSchema), + async (req, res) => { + assertCanManageInstanceSettings(req); + const updated = await svc.updateGeneral(req.body); + const actor = getActorInfo(req); + const companyIds = await svc.listCompanyIds(); + await Promise.all( + companyIds.map((companyId) => + logActivity(db, { + companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "instance.settings.general_updated", + entityType: "instance_settings", + entityId: updated.id, + details: { + general: updated.general, + changedKeys: Object.keys(req.body).sort(), + }, + }), + ), + ); + res.json(updated.general); + }, + ); + router.get("/instance/settings/experimental", async (req, res) => { assertCanManageInstanceSettings(req); res.json(await svc.getExperimental()); diff --git a/server/src/routes/org-chart-svg.ts b/server/src/routes/org-chart-svg.ts new file mode 100644 index 00000000..cf8d1951 --- /dev/null +++ b/server/src/routes/org-chart-svg.ts @@ -0,0 +1,555 @@ +/** + * Server-side SVG renderer for Paperclip org charts. + * Supports 5 visual styles: monochrome, nebula, circuit, warmth, schematic. + * Pure SVG output — no browser/Playwright needed. PNG via sharp. + */ + +export interface OrgNode { + id: string; + name: string; + role: string; + status: string; + reports: OrgNode[]; +} + +export type OrgChartStyle = "monochrome" | "nebula" | "circuit" | "warmth" | "schematic"; + +export const ORG_CHART_STYLES: OrgChartStyle[] = ["monochrome", "nebula", "circuit", "warmth", "schematic"]; + +interface LayoutNode { + node: OrgNode; + x: number; + y: number; + width: number; + height: number; + children: LayoutNode[]; +} + +// ── Style theme definitions ────────────────────────────────────── + +interface StyleTheme { + bgColor: string; + cardBg: string; + cardBorder: string; + cardRadius: number; + cardShadow: string | null; + lineColor: string; + lineWidth: number; + nameColor: string; + roleColor: string; + font: string; + watermarkColor: string; + /** Extra SVG defs (filters, patterns, gradients) */ + defs: (svgW: number, svgH: number) => string; + /** Extra background elements after the main bg rect */ + bgExtras: (svgW: number, svgH: number) => string; + /** Custom card renderer — if null, uses default avatar+name+role */ + renderCard: ((ln: LayoutNode, theme: StyleTheme) => string) | null; + /** Per-card accent (top bar, border glow, etc.) */ + cardAccent: ((tag: string) => string) | null; +} + +// ── Role config with Twemoji SVG inlines (viewBox 0 0 36 36) ───── +// +// Each `emojiSvg` contains the inner SVG paths from Twemoji (CC-BY 4.0). +// These render as colorful emoji-style icons inside the avatar circle, +// without needing a browser or emoji font. + +const ROLE_ICONS: Record = { + ceo: { + bg: "#fef3c7", roleLabel: "Chief Executive", accentColor: "#f0883e", iconColor: "#92400e", + iconPath: "M8 1l2.2 4.5L15 6.2l-3.5 3.4.8 4.9L8 12.2 3.7 14.5l.8-4.9L1 6.2l4.8-.7z", + // 👑 Crown + emojiSvg: ``, + }, + cto: { + bg: "#dbeafe", roleLabel: "Technology", accentColor: "#58a6ff", iconColor: "#1e40af", + iconPath: "M2 3l5 5-5 5M9 13h5", + // 💻 Laptop + emojiSvg: ``, + }, + cmo: { + bg: "#dcfce7", roleLabel: "Marketing", accentColor: "#3fb950", iconColor: "#166534", + iconPath: "M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zM1 8h14M8 1c-2 2-3 4.5-3 7s1 5 3 7c2-2 3-4.5 3-7s-1-5-3-7z", + // 🌐 Globe with meridians + emojiSvg: ``, + }, + cfo: { + bg: "#fef3c7", roleLabel: "Finance", accentColor: "#f0883e", iconColor: "#92400e", + iconPath: "M8 1v14M5 4.5C5 3.1 6.3 2 8 2s3 1.1 3 2.5S9.7 7 8 7 5 8.1 5 9.5 6.3 12 8 12s3-1.1 3-2.5", + // 📊 Bar chart + emojiSvg: ``, + }, + coo: { + bg: "#e0f2fe", roleLabel: "Operations", accentColor: "#58a6ff", iconColor: "#075985", + iconPath: "M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5z", + // ⚙️ Gear + emojiSvg: ``, + }, + engineer: { + bg: "#f3e8ff", roleLabel: "Engineering", accentColor: "#bc8cff", iconColor: "#6b21a8", + iconPath: "M5 3L1 8l4 5M11 3l4 5-4 5", + // ⌨️ Keyboard + emojiSvg: ``, + }, + quality: { + bg: "#ffe4e6", roleLabel: "Quality", accentColor: "#f778ba", iconColor: "#9f1239", + iconPath: "M4 8l3 3 5-6M8 1L2 4v4c0 3.5 2.6 6.8 6 8 3.4-1.2 6-4.5 6-8V4z", + // 🔬 Microscope + emojiSvg: ``, + }, + design: { + bg: "#fce7f3", roleLabel: "Design", accentColor: "#79c0ff", iconColor: "#9d174d", + iconPath: "M12 2l2 2-9 9H3v-2zM9.5 4.5l2 2", + // 🪄 Magic wand + emojiSvg: ``, + }, + finance: { + bg: "#fef3c7", roleLabel: "Finance", accentColor: "#f0883e", iconColor: "#92400e", + iconPath: "M8 1v14M5 4.5C5 3.1 6.3 2 8 2s3 1.1 3 2.5S9.7 7 8 7 5 8.1 5 9.5 6.3 12 8 12s3-1.1 3-2.5", + // 📊 Bar chart (same as CFO) + emojiSvg: ``, + }, + operations: { + bg: "#e0f2fe", roleLabel: "Operations", accentColor: "#58a6ff", iconColor: "#075985", + iconPath: "M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5z", + // ⚙️ Gear (same as COO) + emojiSvg: ``, + }, + default: { + bg: "#f3e8ff", roleLabel: "Agent", accentColor: "#bc8cff", iconColor: "#6b21a8", + iconPath: "M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM2 14c0-3.3 2.7-4 6-4s6 .7 6 4", + // 👤 Person silhouette + emojiSvg: ``, + }, +}; + +function guessRoleTag(node: OrgNode): string { + const name = node.name.toLowerCase(); + const role = node.role.toLowerCase(); + if (name === "ceo" || role.includes("chief executive")) return "ceo"; + if (name === "cto" || role.includes("chief technology") || role.includes("technology")) return "cto"; + if (name === "cmo" || role.includes("chief marketing") || role.includes("marketing")) return "cmo"; + if (name === "cfo" || role.includes("chief financial")) return "cfo"; + if (name === "coo" || role.includes("chief operating")) return "coo"; + if (role.includes("engineer") || role.includes("eng")) return "engineer"; + if (role.includes("quality") || role.includes("qa")) return "quality"; + if (role.includes("design")) return "design"; + if (role.includes("finance")) return "finance"; + if (role.includes("operations") || role.includes("ops")) return "operations"; + return "default"; +} + +function getRoleInfo(node: OrgNode) { + const tag = guessRoleTag(node); + return { tag, ...(ROLE_ICONS[tag] || ROLE_ICONS.default) }; +} + +// ── Style themes ───────────────────────────────────────────────── + +const THEMES: Record = { + // 01 — Monochrome (Vercel-inspired, dark minimal) + monochrome: { + bgColor: "#18181b", + cardBg: "#18181b", + cardBorder: "#27272a", + cardRadius: 6, + cardShadow: null, + lineColor: "#3f3f46", + lineWidth: 1.5, + nameColor: "#fafafa", + roleColor: "#71717a", + font: "'Inter', system-ui, sans-serif", + watermarkColor: "rgba(255,255,255,0.25)", + defs: () => "", + bgExtras: () => "", + renderCard: null, + cardAccent: null, + }, + + // 02 — Nebula (glassmorphism on cosmic gradient) + nebula: { + bgColor: "#0f0c29", + cardBg: "rgba(255,255,255,0.07)", + cardBorder: "rgba(255,255,255,0.12)", + cardRadius: 6, + cardShadow: null, + lineColor: "rgba(255,255,255,0.25)", + lineWidth: 1.5, + nameColor: "#ffffff", + roleColor: "rgba(255,255,255,0.45)", + font: "'Inter', system-ui, sans-serif", + watermarkColor: "rgba(255,255,255,0.2)", + defs: (_w, _h) => ` + + + + + + + + + + + + + `, + bgExtras: (w, h) => ` + + + `, + renderCard: null, + cardAccent: null, + }, + + // 03 — Circuit (Linear/Raycast — indigo traces, amethyst CEO) + circuit: { + bgColor: "#0c0c0e", + cardBg: "rgba(99,102,241,0.04)", + cardBorder: "rgba(99,102,241,0.18)", + cardRadius: 5, + cardShadow: null, + lineColor: "rgba(99,102,241,0.35)", + lineWidth: 1.5, + nameColor: "#e4e4e7", + roleColor: "#6366f1", + font: "'Inter', system-ui, sans-serif", + watermarkColor: "rgba(99,102,241,0.3)", + defs: () => "", + bgExtras: () => "", + renderCard: (ln: LayoutNode, theme: StyleTheme) => { + const { tag, roleLabel, emojiSvg } = getRoleInfo(ln.node); + const cx = ln.x + ln.width / 2; + const isCeo = tag === "ceo"; + const borderColor = isCeo ? "rgba(168,85,247,0.35)" : theme.cardBorder; + const bgColor = isCeo ? "rgba(168,85,247,0.06)" : theme.cardBg; + + const avatarCY = ln.y + 27; + const nameY = ln.y + 66; + const roleY = ln.y + 82; + + return ` + + ${renderEmojiAvatar(cx, avatarCY, 17, "rgba(99,102,241,0.08)", emojiSvg, "rgba(99,102,241,0.15)")} + ${escapeXml(ln.node.name)} + ${escapeXml(roleLabel).toUpperCase()} + `; + }, + cardAccent: null, + }, + + // 04 — Warmth (Airbnb — light, colored avatars, soft shadows) + warmth: { + bgColor: "#fafaf9", + cardBg: "#ffffff", + cardBorder: "#e7e5e4", + cardRadius: 6, + cardShadow: "rgba(0,0,0,0.05)", + lineColor: "#d6d3d1", + lineWidth: 2, + nameColor: "#1c1917", + roleColor: "#78716c", + font: "'Inter', -apple-system, BlinkMacSystemFont, sans-serif", + watermarkColor: "rgba(0,0,0,0.25)", + defs: () => "", + bgExtras: () => "", + renderCard: null, + cardAccent: null, + }, + + // 05 — Schematic (Blueprint — grid bg, monospace, colored top-bars) + schematic: { + bgColor: "#0d1117", + cardBg: "rgba(13,17,23,0.92)", + cardBorder: "#30363d", + cardRadius: 4, + cardShadow: null, + lineColor: "#30363d", + lineWidth: 1.5, + nameColor: "#c9d1d9", + roleColor: "#8b949e", + font: "'JetBrains Mono', 'SF Mono', monospace", + watermarkColor: "rgba(139,148,158,0.3)", + defs: (w, h) => ` + + + `, + bgExtras: (w, h) => ``, + renderCard: (ln: LayoutNode, theme: StyleTheme) => { + const { tag, accentColor, emojiSvg } = getRoleInfo(ln.node); + const cx = ln.x + ln.width / 2; + + // Schematic uses monospace role labels + const schemaRoles: Record = { + ceo: "chief_executive", cto: "chief_technology", cmo: "chief_marketing", + cfo: "chief_financial", coo: "chief_operating", engineer: "engineer", + quality: "quality_assurance", design: "designer", finance: "finance", + operations: "operations", default: "agent", + }; + const roleText = schemaRoles[tag] || schemaRoles.default; + + const avatarCY = ln.y + 27; + const nameY = ln.y + 66; + const roleY = ln.y + 82; + + return ` + + + ${renderEmojiAvatar(cx, avatarCY, 17, "rgba(48,54,61,0.3)", emojiSvg, theme.cardBorder)} + ${escapeXml(ln.node.name)} + ${escapeXml(roleText)} + `; + }, + cardAccent: null, + }, +}; + +// ── Layout constants ───────────────────────────────────────────── + +const CARD_H = 96; +const CARD_MIN_W = 150; +const CARD_PAD_X = 22; +const AVATAR_SIZE = 34; +const GAP_X = 24; +const GAP_Y = 56; +const PADDING = 48; +const LOGO_PADDING = 16; + +// ── Text measurement ───────────────────────────────────────────── + +function measureText(text: string, fontSize: number): number { + return text.length * fontSize * 0.58; +} + +function cardWidth(node: OrgNode): number { + const { roleLabel } = getRoleInfo(node); + const nameW = measureText(node.name, 14) + CARD_PAD_X * 2; + const roleW = measureText(roleLabel, 11) + CARD_PAD_X * 2; + return Math.max(CARD_MIN_W, Math.max(nameW, roleW)); +} + +// ── Tree layout (top-down, centered) ───────────────────────────── + +function subtreeWidth(node: OrgNode): number { + const cw = cardWidth(node); + if (!node.reports || node.reports.length === 0) return cw; + const childrenW = node.reports.reduce( + (sum, child, i) => sum + subtreeWidth(child) + (i > 0 ? GAP_X : 0), + 0, + ); + return Math.max(cw, childrenW); +} + +function layoutTree(node: OrgNode, x: number, y: number): LayoutNode { + const w = cardWidth(node); + const sw = subtreeWidth(node); + const cardX = x + (sw - w) / 2; + + const layoutNode: LayoutNode = { + node, + x: cardX, + y, + width: w, + height: CARD_H, + children: [], + }; + + if (node.reports && node.reports.length > 0) { + let childX = x; + const childY = y + CARD_H + GAP_Y; + for (let i = 0; i < node.reports.length; i++) { + const child = node.reports[i]; + const childSW = subtreeWidth(child); + layoutNode.children.push(layoutTree(child, childX, childY)); + childX += childSW + GAP_X; + } + } + + return layoutNode; +} + +// ── SVG rendering ──────────────────────────────────────────────── + +function escapeXml(s: string): string { + return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); +} + +/** Render a colorful Twemoji inside a circle at (cx, cy) with given radius */ +function renderEmojiAvatar(cx: number, cy: number, radius: number, bgFill: string, emojiSvg: string, bgStroke?: string): string { + const emojiSize = radius * 1.3; // emoji fills most of the circle + const emojiX = cx - emojiSize / 2; + const emojiY = cy - emojiSize / 2; + const stroke = bgStroke ? `stroke="${bgStroke}" stroke-width="1"` : ""; + return ` + ${emojiSvg}`; +} + +function defaultRenderCard(ln: LayoutNode, theme: StyleTheme): string { + const { roleLabel, bg, emojiSvg } = getRoleInfo(ln.node); + const cx = ln.x + ln.width / 2; + + const avatarCY = ln.y + 27; + const nameY = ln.y + 66; + const roleY = ln.y + 82; + + const filterId = `shadow-${ln.node.id}`; + const shadowFilter = theme.cardShadow + ? `filter="url(#${filterId})"` + : ""; + const shadowDef = theme.cardShadow + ? ` + + + ` + : ""; + + // For dark themes without avatars, use a subtle circle + const isLight = theme.bgColor === "#fafaf9" || theme.bgColor === "#ffffff"; + const avatarBg = isLight ? bg : "rgba(255,255,255,0.06)"; + const avatarStroke = isLight ? undefined : "rgba(255,255,255,0.08)"; + + return ` + ${shadowDef} + + ${renderEmojiAvatar(cx, avatarCY, AVATAR_SIZE / 2, avatarBg, emojiSvg, avatarStroke)} + ${escapeXml(ln.node.name)} + ${escapeXml(roleLabel)} + `; +} + +function renderConnectors(ln: LayoutNode, theme: StyleTheme): string { + if (ln.children.length === 0) return ""; + + const parentCx = ln.x + ln.width / 2; + const parentBottom = ln.y + ln.height; + const midY = parentBottom + GAP_Y / 2; + const lc = theme.lineColor; + const lw = theme.lineWidth; + + let svg = ""; + svg += ``; + + if (ln.children.length === 1) { + const childCx = ln.children[0].x + ln.children[0].width / 2; + svg += ``; + } else { + const leftCx = ln.children[0].x + ln.children[0].width / 2; + const rightCx = ln.children[ln.children.length - 1].x + ln.children[ln.children.length - 1].width / 2; + svg += ``; + + for (const child of ln.children) { + const childCx = child.x + child.width / 2; + svg += ``; + } + } + + for (const child of ln.children) { + svg += renderConnectors(child, theme); + } + return svg; +} + +function renderCards(ln: LayoutNode, theme: StyleTheme): string { + const render = theme.renderCard || defaultRenderCard; + let svg = render(ln, theme); + for (const child of ln.children) { + svg += renderCards(child, theme); + } + return svg; +} + +function treeBounds(ln: LayoutNode): { minX: number; minY: number; maxX: number; maxY: number } { + let minX = ln.x; + let minY = ln.y; + let maxX = ln.x + ln.width; + let maxY = ln.y + ln.height; + for (const child of ln.children) { + const cb = treeBounds(child); + minX = Math.min(minX, cb.minX); + minY = Math.min(minY, cb.minY); + maxX = Math.max(maxX, cb.maxX); + maxY = Math.max(maxY, cb.maxY); + } + return { minX, minY, maxX, maxY }; +} + +// Paperclip logo: scaled icon (~16px) + wordmark (13px), vertically centered +const PAPERCLIP_LOGO_SVG = ` + + + + Paperclip +`; + +// ── Public API ─────────────────────────────────────────────────── + +// GitHub recommended social media preview dimensions +const TARGET_W = 1280; +const TARGET_H = 640; + +export function renderOrgChartSvg(orgTree: OrgNode[], style: OrgChartStyle = "warmth"): string { + const theme = THEMES[style] || THEMES.warmth; + + let root: OrgNode; + if (orgTree.length === 1) { + root = orgTree[0]; + } else { + root = { + id: "virtual-root", + name: "Organization", + role: "Root", + status: "active", + reports: orgTree, + }; + } + + const layout = layoutTree(root, PADDING, PADDING + 24); + const bounds = treeBounds(layout); + + const contentW = bounds.maxX + PADDING; + const contentH = bounds.maxY + PADDING; + + // Scale content to fit within the fixed target dimensions + const scale = Math.min(TARGET_W / contentW, TARGET_H / contentH, 1); + const scaledW = contentW * scale; + const scaledH = contentH * scale; + // Center the scaled content within the target frame + const offsetX = (TARGET_W - scaledW) / 2; + const offsetY = (TARGET_H - scaledH) / 2; + + const logoX = TARGET_W - 110 - LOGO_PADDING; + const logoY = LOGO_PADDING; + + return ` + ${theme.defs(TARGET_W, TARGET_H)} + + ${theme.bgExtras(TARGET_W, TARGET_H)} + + ${PAPERCLIP_LOGO_SVG} + + + ${renderConnectors(layout, theme)} + ${renderCards(layout, theme)} + +`; +} + +export async function renderOrgChartPng(orgTree: OrgNode[], style: OrgChartStyle = "warmth"): Promise { + const svg = renderOrgChartSvg(orgTree, style); + const sharpModule = await import("sharp"); + const sharp = sharpModule.default; + // Render at 2x density for retina quality, resize to exact target dimensions + return sharp(Buffer.from(svg), { density: 144 }) + .resize(TARGET_W, TARGET_H) + .png() + .toBuffer(); +} diff --git a/server/src/services/activity-log.ts b/server/src/services/activity-log.ts index 16758b94..cc608a74 100644 --- a/server/src/services/activity-log.ts +++ b/server/src/services/activity-log.ts @@ -8,6 +8,7 @@ import { redactCurrentUserValue } from "../log-redaction.js"; import { sanitizeRecord } from "../redaction.js"; import { logger } from "../middleware/logger.js"; import type { PluginEventBus } from "./plugin-event-bus.js"; +import { instanceSettingsService } from "./instance-settings.js"; const PLUGIN_EVENT_SET: ReadonlySet = new Set(PLUGIN_EVENT_TYPES); @@ -34,8 +35,13 @@ export interface LogActivityInput { } export async function logActivity(db: Db, input: LogActivityInput) { + const currentUserRedactionOptions = { + enabled: (await instanceSettingsService(db).getGeneral()).censorUsernameInLogs, + }; const sanitizedDetails = input.details ? sanitizeRecord(input.details) : null; - const redactedDetails = sanitizedDetails ? redactCurrentUserValue(sanitizedDetails) : null; + const redactedDetails = sanitizedDetails + ? redactCurrentUserValue(sanitizedDetails, currentUserRedactionOptions) + : null; await db.insert(activityLog).values({ companyId: input.companyId, actorType: input.actorType, diff --git a/server/src/services/approvals.ts b/server/src/services/approvals.ts index f2bdb227..bf101e23 100644 --- a/server/src/services/approvals.ts +++ b/server/src/services/approvals.ts @@ -6,22 +6,24 @@ import { redactCurrentUserText } from "../log-redaction.js"; import { agentService } from "./agents.js"; import { budgetService } from "./budgets.js"; import { notifyHireApproved } from "./hire-hook.js"; - -function redactApprovalComment(comment: T): T { - return { - ...comment, - body: redactCurrentUserText(comment.body), - }; -} +import { instanceSettingsService } from "./instance-settings.js"; export function approvalService(db: Db) { const agentsSvc = agentService(db); const budgets = budgetService(db); + const instanceSettings = instanceSettingsService(db); const canResolveStatuses = new Set(["pending", "revision_requested"]); const resolvableStatuses = Array.from(canResolveStatuses); type ApprovalRecord = typeof approvals.$inferSelect; type ResolutionResult = { approval: ApprovalRecord; applied: boolean }; + function redactApprovalComment(comment: T, censorUsernameInLogs: boolean): T { + return { + ...comment, + body: redactCurrentUserText(comment.body, { enabled: censorUsernameInLogs }), + }; + } + async function getExistingApproval(id: string) { const existing = await db .select() @@ -230,6 +232,7 @@ export function approvalService(db: Db) { listComments: async (approvalId: string) => { const existing = await getExistingApproval(approvalId); + const { censorUsernameInLogs } = await instanceSettings.getGeneral(); return db .select() .from(approvalComments) @@ -240,7 +243,7 @@ export function approvalService(db: Db) { ), ) .orderBy(asc(approvalComments.createdAt)) - .then((comments) => comments.map(redactApprovalComment)); + .then((comments) => comments.map((comment) => redactApprovalComment(comment, censorUsernameInLogs))); }, addComment: async ( @@ -249,7 +252,10 @@ export function approvalService(db: Db) { actor: { agentId?: string; userId?: string }, ) => { const existing = await getExistingApproval(approvalId); - const redactedBody = redactCurrentUserText(body); + const currentUserRedactionOptions = { + enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs, + }; + const redactedBody = redactCurrentUserText(body, currentUserRedactionOptions); return db .insert(approvalComments) .values({ @@ -260,7 +266,7 @@ export function approvalService(db: Db) { body: redactedBody, }) .returning() - .then((rows) => redactApprovalComment(rows[0])); + .then((rows) => redactApprovalComment(rows[0], currentUserRedactionOptions.enabled)); }, }; } diff --git a/server/src/services/company-export-readme.ts b/server/src/services/company-export-readme.ts index 72ebe8b1..df8766e1 100644 --- a/server/src/services/company-export-readme.ts +++ b/server/src/services/company-export-readme.ts @@ -55,6 +55,19 @@ function mermaidEscape(s: string): string { return s.replace(/"/g, """).replace(//g, ">"); } +/** Build a display label for a skill's source, linking to GitHub when available. */ +function skillSourceLabel(skill: CompanyPortabilityManifest["skills"][number]): string { + if (skill.sourceLocator) { + // For GitHub or URL sources, render as a markdown link + if (skill.sourceType === "github" || skill.sourceType === "skills_sh" || skill.sourceType === "url") { + return `[${skill.sourceType}](${skill.sourceLocator})`; + } + return skill.sourceLocator; + } + if (skill.sourceType === "local") return "local"; + return skill.sourceType ?? "\u2014"; +} + /** * Generate the README.md content for a company export. */ @@ -74,17 +87,16 @@ export function generateReadme( lines.push(""); } - // Org chart as Mermaid diagram - const mermaid = generateOrgChartMermaid(manifest.agents); - if (mermaid) { - lines.push(mermaid); + // Org chart image (generated during export as images/org-chart.png) + if (manifest.agents.length > 0) { + lines.push("![Org Chart](images/org-chart.png)"); lines.push(""); } // What's Inside table lines.push("## What's Inside"); lines.push(""); - lines.push("This is an [Agent Company](https://paperclip.ing) package."); + lines.push("> This is an [Agent Company](https://agentcompanies.io) package from [Paperclip](https://paperclip.ing)"); lines.push(""); const counts: Array<[string, number]> = []; @@ -127,6 +139,20 @@ export function generateReadme( lines.push(""); } + // Skills list + if (manifest.skills.length > 0) { + lines.push("### Skills"); + lines.push(""); + lines.push("| Skill | Description | Source |"); + lines.push("|-------|-------------|--------|"); + for (const skill of manifest.skills) { + const desc = skill.description ?? "\u2014"; + const source = skillSourceLabel(skill); + lines.push(`| ${skill.name} | ${desc} | ${source} |`); + } + lines.push(""); + } + // Getting Started lines.push("## Getting Started"); lines.push(""); diff --git a/server/src/services/company-portability.ts b/server/src/services/company-portability.ts index 097c31e3..d063fd36 100644 --- a/server/src/services/company-portability.ts +++ b/server/src/services/company-portability.ts @@ -42,16 +42,63 @@ import { agentService } from "./agents.js"; import { agentInstructionsService } from "./agent-instructions.js"; import { assetService } from "./assets.js"; import { generateReadme } from "./company-export-readme.js"; +import { renderOrgChartPng, type OrgNode } from "../routes/org-chart-svg.js"; import { companySkillService } from "./company-skills.js"; import { companyService } from "./companies.js"; import { issueService } from "./issues.js"; import { projectService } from "./projects.js"; +/** Build OrgNode tree from manifest agent list (slug + reportsToSlug). */ +function buildOrgTreeFromManifest(agents: CompanyPortabilityManifest["agents"]): OrgNode[] { + const ROLE_LABELS: Record = { + ceo: "Chief Executive", cto: "Technology", cmo: "Marketing", + cfo: "Finance", coo: "Operations", vp: "VP", manager: "Manager", + engineer: "Engineer", agent: "Agent", + }; + const bySlug = new Map(agents.map((a) => [a.slug, a])); + const childrenOf = new Map(); + for (const a of agents) { + const parent = a.reportsToSlug ?? null; + const list = childrenOf.get(parent) ?? []; + list.push(a); + childrenOf.set(parent, list); + } + const build = (parentSlug: string | null): OrgNode[] => { + const members = childrenOf.get(parentSlug) ?? []; + return members.map((m) => ({ + id: m.slug, + name: m.name, + role: ROLE_LABELS[m.role] ?? m.role, + status: "active", + reports: build(m.slug), + })); + }; + // Find roots: agents whose reportsToSlug is null or points to a non-existent slug + const roots = agents.filter((a) => !a.reportsToSlug || !bySlug.has(a.reportsToSlug)); + const rootSlugs = new Set(roots.map((r) => r.slug)); + // Start from null parent, but also include orphans + const tree = build(null); + for (const root of roots) { + if (root.reportsToSlug && !bySlug.has(root.reportsToSlug)) { + // Orphan root (parent slug doesn't exist) + tree.push({ + id: root.slug, + name: root.name, + role: ROLE_LABELS[root.role] ?? root.role, + status: "active", + reports: build(root.slug), + }); + } + } + return tree; +} + const DEFAULT_INCLUDE: CompanyPortabilityInclude = { company: true, agents: true, projects: false, issues: false, + skills: false, }; const DEFAULT_COLLISION_STRATEGY: CompanyPortabilityCollisionStrategy = "rename"; @@ -119,7 +166,7 @@ function deriveManifestSkillKey( const sourceKind = asString(metadata?.sourceKind); const owner = normalizeSkillSlug(asString(metadata?.owner)); const repo = normalizeSkillSlug(asString(metadata?.repo)); - if ((sourceType === "github" || sourceKind === "github") && owner && repo) { + if ((sourceType === "github" || sourceType === "skills_sh" || sourceKind === "github" || sourceKind === "skills_sh") && owner && repo) { return `${owner}/${repo}/${slug}`; } if (sourceKind === "paperclip_bundled") { @@ -246,10 +293,10 @@ function deriveSkillExportDirCandidates( pushSuffix("paperclip"); } - if (skill.sourceType === "github") { + if (skill.sourceType === "github" || skill.sourceType === "skills_sh") { pushSuffix(asString(metadata?.repo)); pushSuffix(asString(metadata?.owner)); - pushSuffix("github"); + pushSuffix(skill.sourceType === "skills_sh" ? "skills_sh" : "github"); } else if (skill.sourceType === "url") { try { pushSuffix(skill.sourceLocator ? new URL(skill.sourceLocator).host : null); @@ -304,10 +351,12 @@ function isSensitiveEnvKey(key: string) { normalized === "token" || normalized.endsWith("_token") || normalized.endsWith("-token") || + normalized.includes("apikey") || normalized.includes("api_key") || normalized.includes("api-key") || normalized.includes("access_token") || normalized.includes("access-token") || + normalized.includes("auth") || normalized.includes("auth_token") || normalized.includes("auth-token") || normalized.includes("authorization") || @@ -317,6 +366,7 @@ function isSensitiveEnvKey(key: string) { normalized.includes("password") || normalized.includes("credential") || normalized.includes("jwt") || + normalized.includes("privatekey") || normalized.includes("private_key") || normalized.includes("private-key") || normalized.includes("cookie") || @@ -515,6 +565,7 @@ function normalizeInclude(input?: Partial): CompanyPo agents: input?.agents ?? DEFAULT_INCLUDE.agents, projects: input?.projects ?? DEFAULT_INCLUDE.projects, issues: input?.issues ?? DEFAULT_INCLUDE.issues, + skills: input?.skills ?? DEFAULT_INCLUDE.skills, }; } @@ -826,6 +877,7 @@ function extractPortableEnvInputs( if (isPlainRecord(binding) && binding.type === "plain") { const defaultValue = asString(binding.value); + const isSensitive = isSensitiveEnvKey(key); const portability = defaultValue && isAbsoluteCommand(defaultValue) ? "system_dependent" : "portable"; @@ -836,9 +888,9 @@ function extractPortableEnvInputs( key, description: `Optional default for ${key} on agent ${agentSlug}`, agentSlug, - kind: "plain", + kind: isSensitive ? "secret" : "plain", requirement: "optional", - defaultValue: defaultValue ?? "", + defaultValue: isSensitive ? "" : defaultValue ?? "", portability, }); continue; @@ -1147,6 +1199,7 @@ function applySelectedFilesToSource(source: ResolvedSource, selectedFiles?: stri agents: filtered.manifest.agents.length > 0, projects: filtered.manifest.projects.length > 0, issues: filtered.manifest.issues.length > 0, + skills: filtered.manifest.skills.length > 0, }; return filtered; @@ -1178,7 +1231,7 @@ async function buildSkillSourceEntry(skill: CompanySkill) { }; } - if (skill.sourceType === "github") { + if (skill.sourceType === "github" || skill.sourceType === "skills_sh") { const owner = asString(metadata?.owner); const repo = asString(metadata?.repo); const repoSkillDir = asString(metadata?.repoSkillDir); @@ -1207,7 +1260,7 @@ function shouldReferenceSkillOnExport(skill: CompanySkill, expandReferencedSkill if (expandReferencedSkills) return false; const metadata = isPlainRecord(skill.metadata) ? skill.metadata : null; if (asString(metadata?.sourceKind) === "paperclip_bundled") return true; - return skill.sourceType === "github" || skill.sourceType === "url"; + return skill.sourceType === "github" || skill.sourceType === "skills_sh" || skill.sourceType === "url"; } async function buildReferencedSkillMarkdown(skill: CompanySkill) { @@ -1254,17 +1307,6 @@ async function withSkillSourceMetadata(skill: CompanySkill, markdown: string) { return buildMarkdown(frontmatter, parsed.body); } -function renderCompanyAgentsSection(agentSummaries: Array<{ slug: string; name: string }>) { - const lines = ["# Agents", ""]; - if (agentSummaries.length === 0) { - lines.push("- _none_"); - return lines.join("\n"); - } - for (const agent of agentSummaries) { - lines.push(`- ${agent.slug} - ${agent.name}`); - } - return lines.join("\n"); -} function parseYamlScalar(rawValue: string): unknown { const trimmed = rawValue.trim(); @@ -1610,6 +1652,7 @@ function buildManifestFromPackageFiles( agents: true, projects: projectPaths.length > 0, issues: taskPaths.length > 0, + skills: skillPaths.length > 0, }, company: { path: resolvedCompanyPath, @@ -2005,6 +2048,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { (input.issues && input.issues.length > 0) || (input.projectIssues && input.projectIssues.length > 0) ? true : input.include?.issues, + skills: input.skills && input.skills.length > 0 ? true : input.include?.skills, }); const company = await companies.getById(companyId); if (!company) throw notFound("Company not found"); @@ -2017,7 +2061,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { const allAgentRows = include.agents ? await agents.list(companyId, { includeTerminated: true }) : []; const liveAgentRows = allAgentRows.filter((agent) => agent.status !== "terminated"); - const companySkillRows = await companySkills.listFull(companyId); + const companySkillRows = include.skills || include.agents ? await companySkills.listFull(companyId) : []; if (include.agents) { const skipped = allAgentRows.length - liveAgentRows.length; if (skipped > 0) { @@ -2159,19 +2203,6 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { } const companyPath = "COMPANY.md"; - const companyBodySections: string[] = []; - if (include.agents) { - const companyAgentSummaries = agentRows.map((agent) => ({ - slug: idToSlug.get(agent.id) ?? "agent", - name: agent.name, - })); - companyBodySections.push(renderCompanyAgentsSection(companyAgentSummaries)); - } - if (selectedProjectRows.length > 0) { - companyBodySections.push( - ["# Projects", "", ...selectedProjectRows.map((project) => `- ${projectSlugById.get(project.id) ?? project.id} - ${project.name}`)].join("\n"), - ); - } files[companyPath] = buildMarkdown( { name: company.name, @@ -2179,7 +2210,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { schema: "agentcompanies/v1", slug: rootPath, }, - companyBodySections.join("\n\n").trim(), + "", ); if (include.company && company.logoAssetId) { @@ -2418,10 +2449,22 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { agents: resolved.manifest.agents.length > 0, projects: resolved.manifest.projects.length > 0, issues: resolved.manifest.issues.length > 0, + skills: resolved.manifest.skills.length > 0, }; resolved.manifest.envInputs = dedupeEnvInputs(envInputs); resolved.warnings.unshift(...warnings); + // Generate org chart PNG from manifest agents + if (resolved.manifest.agents.length > 0) { + try { + const orgNodes = buildOrgTreeFromManifest(resolved.manifest.agents); + const pngBuffer = await renderOrgChartPng(orgNodes); + finalFiles["images/org-chart.png"] = bufferToPortableBinaryFile(pngBuffer, "image/png"); + } catch { + // Non-fatal: export still works without the org chart image + } + } + if (!input.selectedFiles || input.selectedFiles.some((entry) => normalizePortablePath(entry) === "README.md")) { finalFiles["README.md"] = generateReadme(resolved.manifest, { companyName: company.name, @@ -2440,6 +2483,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { agents: resolved.manifest.agents.length > 0, projects: resolved.manifest.projects.length > 0, issues: resolved.manifest.issues.length > 0, + skills: resolved.manifest.skills.length > 0, }; resolved.manifest.envInputs = dedupeEnvInputs(envInputs); resolved.warnings.unshift(...warnings); @@ -2502,6 +2546,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { agents: requestedInclude.agents && manifest.agents.length > 0, projects: requestedInclude.projects && manifest.projects.length > 0, issues: requestedInclude.issues && manifest.issues.length > 0, + skills: requestedInclude.skills && manifest.skills.length > 0, }; const collisionStrategy = input.collisionStrategy ?? DEFAULT_COLLISION_STRATEGY; if (mode === "agent_safe" && collisionStrategy === "replace") { @@ -2962,9 +3007,11 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { existingProjectSlugToId.set(existing.urlKey, existing.id); } - const importedSkills = await companySkills.importPackageFiles(targetCompany.id, pickTextFiles(plan.source.files), { - onConflict: resolveSkillConflictStrategy(mode, plan.collisionStrategy), - }); + const importedSkills = include.skills || include.agents + ? await companySkills.importPackageFiles(targetCompany.id, pickTextFiles(plan.source.files), { + onConflict: resolveSkillConflictStrategy(mode, plan.collisionStrategy), + }) + : []; const desiredSkillRefMap = new Map(); for (const importedSkill of importedSkills) { desiredSkillRefMap.set(importedSkill.originalKey, importedSkill.skill.key); diff --git a/server/src/services/company-skills.ts b/server/src/services/company-skills.ts index 87b7c11c..19aeab04 100644 --- a/server/src/services/company-skills.ts +++ b/server/src/services/company-skills.ts @@ -5,7 +5,7 @@ import { fileURLToPath } from "node:url"; import { and, asc, eq } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import { companySkills } from "@paperclipai/db"; -import { readPaperclipSkillSyncPreference } from "@paperclipai/adapter-utils/server-utils"; +import { readPaperclipSkillSyncPreference, writePaperclipSkillSyncPreference } from "@paperclipai/adapter-utils/server-utils"; import type { PaperclipSkillEntry } from "@paperclipai/adapter-utils/server-utils"; import type { CompanySkill, @@ -66,6 +66,7 @@ export type ImportPackageSkillResult = { type ParsedSkillImportSource = { resolvedSource: string; requestedSkillSlug: string | null; + originalSkillsShUrl: string | null; warnings: string[]; }; @@ -251,7 +252,7 @@ function deriveCanonicalSkillKey( const owner = normalizeSkillSlug(asString(metadata?.owner)); const repo = normalizeSkillSlug(asString(metadata?.repo)); - if ((input.sourceType === "github" || sourceKind === "github") && owner && repo) { + if ((input.sourceType === "github" || input.sourceType === "skills_sh" || sourceKind === "github" || sourceKind === "skills_sh") && owner && repo) { return `${owner}/${repo}/${slug}`; } @@ -376,6 +377,28 @@ function parseYamlBlock( index = nested.nextIndex; continue; } + const inlineObjectSeparator = remainder.indexOf(":"); + if ( + inlineObjectSeparator > 0 && + !remainder.startsWith("\"") && + !remainder.startsWith("{") && + !remainder.startsWith("[") + ) { + const key = remainder.slice(0, inlineObjectSeparator).trim(); + const rawValue = remainder.slice(inlineObjectSeparator + 1).trim(); + const nextObject: Record = { + [key]: parseYamlScalar(rawValue), + }; + if (index < lines.length && lines[index]!.indent > indentLevel) { + const nested = parseYamlBlock(lines, index, indentLevel + 2); + if (isPlainRecord(nested.value)) { + Object.assign(nextObject, nested.value); + } + index = nested.nextIndex; + } + values.push(nextObject); + continue; + } values.push(parseYamlScalar(remainder)); } return { value: values, nextIndex: index }; @@ -561,11 +584,13 @@ export function parseSkillImportSourceInput(rawInput: string): ParsedSkillImport throw unprocessable("Skill source is required."); } + // Key-style imports (org/repo/skill) originate from the skills.sh registry if (!/^https?:\/\//i.test(normalizedSource) && /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(normalizedSource)) { const [owner, repo, skillSlugRaw] = normalizedSource.split("/"); return { resolvedSource: `https://github.com/${owner}/${repo}`, requestedSkillSlug: normalizeSkillSlug(skillSlugRaw), + originalSkillsShUrl: `https://skills.sh/${owner}/${repo}/${skillSlugRaw}`, warnings, }; } @@ -574,6 +599,19 @@ export function parseSkillImportSourceInput(rawInput: string): ParsedSkillImport return { resolvedSource: `https://github.com/${normalizedSource}`, requestedSkillSlug, + originalSkillsShUrl: null, + warnings, + }; + } + + // Detect skills.sh URLs and resolve to GitHub: https://skills.sh/org/repo/skill → org/repo/skill key + const skillsShMatch = normalizedSource.match(/^https?:\/\/(?:www\.)?skills\.sh\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)(?:\/([A-Za-z0-9_.-]+))?(?:[?#].*)?$/i); + if (skillsShMatch) { + const [, owner, repo, skillSlugRaw] = skillsShMatch; + return { + resolvedSource: `https://github.com/${owner}/${repo}`, + requestedSkillSlug: skillSlugRaw ? normalizeSkillSlug(skillSlugRaw) : requestedSkillSlug, + originalSkillsShUrl: normalizedSource, warnings, }; } @@ -581,6 +619,7 @@ export function parseSkillImportSourceInput(rawInput: string): ParsedSkillImport return { resolvedSource: normalizedSource, requestedSkillSlug, + originalSkillsShUrl: null, warnings, }; } @@ -787,12 +826,11 @@ export async function readLocalSkillImportFromDirectory( const markdown = await fs.readFile(skillFilePath, "utf8"); const parsed = parseFrontmatterMarkdown(markdown); const slug = deriveImportedSkillSlug(parsed.frontmatter, path.basename(resolvedSkillDir)); - const skillKey = readCanonicalSkillKey( - parsed.frontmatter, - isPlainRecord(parsed.frontmatter.metadata) ? parsed.frontmatter.metadata : null, - ); + const parsedMetadata = isPlainRecord(parsed.frontmatter.metadata) ? parsed.frontmatter.metadata : null; + const skillKey = readCanonicalSkillKey(parsed.frontmatter, parsedMetadata); const metadata = { ...(skillKey ? { skillKey } : {}), + ...(parsedMetadata ?? {}), sourceKind: "local_path", ...(options?.metadata ?? {}), }; @@ -860,12 +898,11 @@ async function readLocalSkillImports(companyId: string, sourcePath: string): Pro const markdown = await fs.readFile(resolvedPath, "utf8"); const parsed = parseFrontmatterMarkdown(markdown); const slug = deriveImportedSkillSlug(parsed.frontmatter, path.basename(path.dirname(resolvedPath))); - const skillKey = readCanonicalSkillKey( - parsed.frontmatter, - isPlainRecord(parsed.frontmatter.metadata) ? parsed.frontmatter.metadata : null, - ); + const parsedMetadata = isPlainRecord(parsed.frontmatter.metadata) ? parsed.frontmatter.metadata : null; + const skillKey = readCanonicalSkillKey(parsed.frontmatter, parsedMetadata); const metadata = { ...(skillKey ? { skillKey } : {}), + ...(parsedMetadata ?? {}), sourceKind: "local_path", }; const inventory: CompanySkillFileInventoryEntry[] = [ @@ -1281,6 +1318,18 @@ function deriveSkillSourceInfo(skill: CompanySkill): { }; } + if (skill.sourceType === "skills_sh") { + const owner = asString(metadata.owner) ?? null; + const repo = asString(metadata.repo) ?? null; + return { + editable: false, + editableReason: "Skills.sh-managed skills are read-only.", + sourceLabel: skill.sourceLocator ?? (owner && repo ? `${owner}/${repo}` : null), + sourceBadge: "skills_sh", + sourcePath: null, + }; + } + if (skill.sourceType === "github") { const owner = asString(metadata.owner) ?? null; const repo = asString(metadata.repo) ?? null; @@ -1532,7 +1581,7 @@ export function companySkillService(db: Db) { const skill = await getById(skillId); if (!skill || skill.companyId !== companyId) return null; - if (skill.sourceType !== "github") { + if (skill.sourceType !== "github" && skill.sourceType !== "skills_sh") { return { supported: false, reason: "Only GitHub-managed skills support update checks.", @@ -1592,7 +1641,7 @@ export function companySkillService(db: Db) { } else { throw notFound("Skill file not found"); } - } else if (skill.sourceType === "github") { + } else if (skill.sourceType === "github" || skill.sourceType === "skills_sh") { const metadata = getSkillMeta(skill); const owner = asString(metadata.owner); const repo = asString(metadata.repo); @@ -2191,10 +2240,63 @@ export function companySkillService(db: Db) { : "No skills were found in the provided source.", ); } + // Override sourceType/sourceLocator for skills imported via skills.sh + if (parsed.originalSkillsShUrl) { + for (const skill of filteredSkills) { + skill.sourceType = "skills_sh"; + skill.sourceLocator = parsed.originalSkillsShUrl; + if (skill.metadata) { + (skill.metadata as Record).sourceKind = "skills_sh"; + } + skill.key = deriveCanonicalSkillKey(companyId, skill); + } + } const imported = await upsertImportedSkills(companyId, filteredSkills); return { imported, warnings }; } + async function deleteSkill(companyId: string, skillId: string): Promise { + const row = await db + .select() + .from(companySkills) + .where(and(eq(companySkills.id, skillId), eq(companySkills.companyId, companyId))) + .then((rows) => rows[0] ?? null); + if (!row) return null; + + const skill = toCompanySkill(row); + + // Remove from any agent desiredSkills that reference this skill + const agentRows = await agents.list(companyId); + const allSkills = await listFull(companyId); + for (const agent of agentRows) { + const config = agent.adapterConfig as Record; + const preference = readPaperclipSkillSyncPreference(config); + const referencesSkill = preference.desiredSkills.some((ref) => { + const resolved = resolveSkillReference(allSkills, ref); + return resolved.skill?.id === skillId; + }); + if (referencesSkill) { + const filtered = preference.desiredSkills.filter((ref) => { + const resolved = resolveSkillReference(allSkills, ref); + return resolved.skill?.id !== skillId; + }); + await agents.update(agent.id, { + adapterConfig: writePaperclipSkillSyncPreference(config, filtered), + }); + } + } + + // Delete DB row + await db + .delete(companySkills) + .where(eq(companySkills.id, skillId)); + + // Clean up materialized runtime files + await fs.rm(resolveRuntimeSkillMaterializedPath(companyId, skill), { recursive: true, force: true }); + + return skill; + } + return { list, listFull, @@ -2209,6 +2311,7 @@ export function companySkillService(db: Db) { readFile, updateFile, createLocalSkill, + deleteSkill, importFromSource, scanProjectWorkspaces, importPackageFiles, diff --git a/server/src/services/default-agent-instructions.ts b/server/src/services/default-agent-instructions.ts new file mode 100644 index 00000000..4278d833 --- /dev/null +++ b/server/src/services/default-agent-instructions.ts @@ -0,0 +1,27 @@ +import fs from "node:fs/promises"; + +const DEFAULT_AGENT_BUNDLE_FILES = { + default: ["AGENTS.md"], + ceo: ["AGENTS.md", "HEARTBEAT.md", "SOUL.md", "TOOLS.md"], +} as const; + +type DefaultAgentBundleRole = keyof typeof DEFAULT_AGENT_BUNDLE_FILES; + +function resolveDefaultAgentBundleUrl(role: DefaultAgentBundleRole, fileName: string) { + return new URL(`../onboarding-assets/${role}/${fileName}`, import.meta.url); +} + +export async function loadDefaultAgentInstructionsBundle(role: DefaultAgentBundleRole): Promise> { + const fileNames = DEFAULT_AGENT_BUNDLE_FILES[role]; + const entries = await Promise.all( + fileNames.map(async (fileName) => { + const content = await fs.readFile(resolveDefaultAgentBundleUrl(role, fileName), "utf8"); + return [fileName, content] as const; + }), + ); + return Object.fromEntries(entries); +} + +export function resolveDefaultAgentInstructionsBundleRole(role: string): DefaultAgentBundleRole { + return role === "ceo" ? "ceo" : "default"; +} diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index 9a351583..0694efed 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -721,6 +721,9 @@ function resolveNextSessionState(input: { export function heartbeatService(db: Db) { const instanceSettings = instanceSettingsService(db); + const getCurrentUserRedactionOptions = async () => ({ + enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs, + }); const runLogStore = getRunLogStore(); const secretsSvc = secretService(db); @@ -1320,8 +1323,13 @@ export function heartbeatService(db: Db) { payload?: Record; }, ) { - const sanitizedMessage = event.message ? redactCurrentUserText(event.message) : event.message; - const sanitizedPayload = event.payload ? redactCurrentUserValue(event.payload) : event.payload; + const currentUserRedactionOptions = await getCurrentUserRedactionOptions(); + const sanitizedMessage = event.message + ? redactCurrentUserText(event.message, currentUserRedactionOptions) + : event.message; + const sanitizedPayload = event.payload + ? redactCurrentUserValue(event.payload, currentUserRedactionOptions) + : event.payload; await db.insert(heartbeatRunEvents).values({ companyId: run.companyId, @@ -2138,7 +2146,11 @@ export function heartbeatService(db: Db) { repoRef: executionWorkspace.repoRef, branchName: executionWorkspace.branchName, worktreePath: executionWorkspace.worktreePath, - agentHome: resolveDefaultAgentWorkspaceDir(agent.id), + agentHome: await (async () => { + const home = resolveDefaultAgentWorkspaceDir(agent.id); + await fs.mkdir(home, { recursive: true }); + return home; + })(), }; context.paperclipWorkspaces = resolvedWorkspace.workspaceHints; const runtimeServiceIntents = (() => { @@ -2259,8 +2271,9 @@ export function heartbeatService(db: Db) { }) .where(eq(heartbeatRuns.id, runId)); + const currentUserRedactionOptions = await getCurrentUserRedactionOptions(); const onLog = async (stream: "stdout" | "stderr", chunk: string) => { - const sanitizedChunk = redactCurrentUserText(chunk); + const sanitizedChunk = redactCurrentUserText(chunk, currentUserRedactionOptions); if (stream === "stdout") stdoutExcerpt = appendExcerpt(stdoutExcerpt, sanitizedChunk); if (stream === "stderr") stderrExcerpt = appendExcerpt(stderrExcerpt, sanitizedChunk); const ts = new Date().toISOString(); @@ -2510,6 +2523,7 @@ export function heartbeatService(db: Db) { ? null : redactCurrentUserText( adapterResult.errorMessage ?? (outcome === "timed_out" ? "Timed out" : "Adapter failed"), + currentUserRedactionOptions, ), errorCode: outcome === "timed_out" @@ -2577,7 +2591,10 @@ export function heartbeatService(db: Db) { } await finalizeAgentStatus(agent.id, outcome); } catch (err) { - const message = redactCurrentUserText(err instanceof Error ? err.message : "Unknown adapter failure"); + const message = redactCurrentUserText( + err instanceof Error ? err.message : "Unknown adapter failure", + await getCurrentUserRedactionOptions(), + ); logger.error({ err, runId }, "heartbeat execution failed"); let logSummary: { bytes: number; sha256?: string; compressed: boolean } | null = null; @@ -3615,7 +3632,7 @@ export function heartbeatService(db: Db) { store: run.logStore, logRef: run.logRef, ...result, - content: redactCurrentUserText(result.content), + content: redactCurrentUserText(result.content, await getCurrentUserRedactionOptions()), }; }, diff --git a/server/src/services/instance-settings.ts b/server/src/services/instance-settings.ts index e60154a4..ccefea7c 100644 --- a/server/src/services/instance-settings.ts +++ b/server/src/services/instance-settings.ts @@ -1,8 +1,11 @@ import type { Db } from "@paperclipai/db"; import { companies, instanceSettings } from "@paperclipai/db"; import { + instanceGeneralSettingsSchema, + type InstanceGeneralSettings, instanceExperimentalSettingsSchema, type InstanceExperimentalSettings, + type PatchInstanceGeneralSettings, type InstanceSettings, type PatchInstanceExperimentalSettings, } from "@paperclipai/shared"; @@ -10,21 +13,36 @@ import { eq } from "drizzle-orm"; const DEFAULT_SINGLETON_KEY = "default"; +function normalizeGeneralSettings(raw: unknown): InstanceGeneralSettings { + const parsed = instanceGeneralSettingsSchema.safeParse(raw ?? {}); + if (parsed.success) { + return { + censorUsernameInLogs: parsed.data.censorUsernameInLogs ?? false, + }; + } + return { + censorUsernameInLogs: false, + }; +} + function normalizeExperimentalSettings(raw: unknown): InstanceExperimentalSettings { const parsed = instanceExperimentalSettingsSchema.safeParse(raw ?? {}); if (parsed.success) { return { enableIsolatedWorkspaces: parsed.data.enableIsolatedWorkspaces ?? false, + autoRestartDevServerWhenIdle: parsed.data.autoRestartDevServerWhenIdle ?? false, }; } return { enableIsolatedWorkspaces: false, + autoRestartDevServerWhenIdle: false, }; } function toInstanceSettings(row: typeof instanceSettings.$inferSelect): InstanceSettings { return { id: row.id, + general: normalizeGeneralSettings(row.general), experimental: normalizeExperimentalSettings(row.experimental), createdAt: row.createdAt, updatedAt: row.updatedAt, @@ -45,6 +63,7 @@ export function instanceSettingsService(db: Db) { .insert(instanceSettings) .values({ singletonKey: DEFAULT_SINGLETON_KEY, + general: {}, experimental: {}, createdAt: now, updatedAt: now, @@ -63,11 +82,34 @@ export function instanceSettingsService(db: Db) { return { get: async (): Promise => toInstanceSettings(await getOrCreateRow()), + getGeneral: async (): Promise => { + const row = await getOrCreateRow(); + return normalizeGeneralSettings(row.general); + }, + getExperimental: async (): Promise => { const row = await getOrCreateRow(); return normalizeExperimentalSettings(row.experimental); }, + updateGeneral: async (patch: PatchInstanceGeneralSettings): Promise => { + const current = await getOrCreateRow(); + const nextGeneral = normalizeGeneralSettings({ + ...normalizeGeneralSettings(current.general), + ...patch, + }); + const now = new Date(); + const [updated] = await db + .update(instanceSettings) + .set({ + general: { ...nextGeneral }, + updatedAt: now, + }) + .where(eq(instanceSettings.id, current.id)) + .returning(); + return toInstanceSettings(updated ?? current); + }, + updateExperimental: async (patch: PatchInstanceExperimentalSettings): Promise => { const current = await getOrCreateRow(); const nextExperimental = normalizeExperimentalSettings({ diff --git a/server/src/services/issues.ts b/server/src/services/issues.ts index f539884e..681da27d 100644 --- a/server/src/services/issues.ts +++ b/server/src/services/issues.ts @@ -100,13 +100,6 @@ type IssueUserContextInput = { updatedAt: Date | string; }; -function redactIssueComment(comment: T): T { - return { - ...comment, - body: redactCurrentUserText(comment.body), - }; -} - function sameRunLock(checkoutRunId: string | null, actorRunId: string | null) { if (actorRunId) return checkoutRunId === actorRunId; return checkoutRunId == null; @@ -323,6 +316,13 @@ function withActiveRuns( export function issueService(db: Db) { const instanceSettings = instanceSettingsService(db); + function redactIssueComment(comment: T, censorUsernameInLogs: boolean): T { + return { + ...comment, + body: redactCurrentUserText(comment.body, { enabled: censorUsernameInLogs }), + }; + } + async function assertAssignableAgent(companyId: string, agentId: string) { const assignee = await db .select({ @@ -1225,7 +1225,8 @@ export function issueService(db: Db) { ); const comments = limit ? await query.limit(limit) : await query; - return comments.map(redactIssueComment); + const { censorUsernameInLogs } = await instanceSettings.getGeneral(); + return comments.map((comment) => redactIssueComment(comment, censorUsernameInLogs)); }, getCommentCursor: async (issueId: string) => { @@ -1257,14 +1258,15 @@ export function issueService(db: Db) { }, getComment: (commentId: string) => - db + instanceSettings.getGeneral().then(({ censorUsernameInLogs }) => + db .select() .from(issueComments) .where(eq(issueComments.id, commentId)) .then((rows) => { const comment = rows[0] ?? null; - return comment ? redactIssueComment(comment) : null; - }), + return comment ? redactIssueComment(comment, censorUsernameInLogs) : null; + })), addComment: async (issueId: string, body: string, actor: { agentId?: string; userId?: string }) => { const issue = await db @@ -1275,7 +1277,10 @@ export function issueService(db: Db) { if (!issue) throw notFound("Issue not found"); - const redactedBody = redactCurrentUserText(body); + const currentUserRedactionOptions = { + enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs, + }; + const redactedBody = redactCurrentUserText(body, currentUserRedactionOptions); const [comment] = await db .insert(issueComments) .values({ @@ -1293,7 +1298,7 @@ export function issueService(db: Db) { .set({ updatedAt: new Date() }) .where(eq(issues.id, issueId)); - return redactIssueComment(comment); + return redactIssueComment(comment, currentUserRedactionOptions.enabled); }, createAttachment: async (input: { diff --git a/server/src/services/workspace-operations.ts b/server/src/services/workspace-operations.ts index 3bbace29..b20a9ed7 100644 --- a/server/src/services/workspace-operations.ts +++ b/server/src/services/workspace-operations.ts @@ -5,6 +5,7 @@ import type { WorkspaceOperation, WorkspaceOperationPhase, WorkspaceOperationSta import { asc, desc, eq, inArray, isNull, or, and } from "drizzle-orm"; import { notFound } from "../errors.js"; import { redactCurrentUserText, redactCurrentUserValue } from "../log-redaction.js"; +import { instanceSettingsService } from "./instance-settings.js"; import { getWorkspaceOperationLogStore } from "./workspace-operation-log-store.js"; type WorkspaceOperationRow = typeof workspaceOperations.$inferSelect; @@ -69,6 +70,7 @@ export interface WorkspaceOperationRecorder { } export function workspaceOperationService(db: Db) { + const instanceSettings = instanceSettingsService(db); const logStore = getWorkspaceOperationLogStore(); async function getById(id: string) { @@ -105,6 +107,9 @@ export function workspaceOperationService(db: Db) { }, async recordOperation(recordInput) { + const currentUserRedactionOptions = { + enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs, + }; const startedAt = new Date(); const id = randomUUID(); const handle = await logStore.begin({ @@ -116,7 +121,7 @@ export function workspaceOperationService(db: Db) { let stderrExcerpt = ""; const append = async (stream: "stdout" | "stderr" | "system", chunk: string | null | undefined) => { if (!chunk) return; - const sanitizedChunk = redactCurrentUserText(chunk); + const sanitizedChunk = redactCurrentUserText(chunk, currentUserRedactionOptions); if (stream === "stdout") stdoutExcerpt = appendExcerpt(stdoutExcerpt, sanitizedChunk); if (stream === "stderr") stderrExcerpt = appendExcerpt(stderrExcerpt, sanitizedChunk); await logStore.append(handle, { @@ -137,7 +142,10 @@ export function workspaceOperationService(db: Db) { status: "running", logStore: handle.store, logRef: handle.logRef, - metadata: redactCurrentUserValue(recordInput.metadata ?? null) as Record | null, + metadata: redactCurrentUserValue( + recordInput.metadata ?? null, + currentUserRedactionOptions, + ) as Record | null, startedAt, }); createdIds.push(id); @@ -162,6 +170,7 @@ export function workspaceOperationService(db: Db) { logCompressed: finalized.compressed, metadata: redactCurrentUserValue( combineMetadata(recordInput.metadata, result.metadata), + currentUserRedactionOptions, ) as Record | null, finishedAt, updatedAt: finishedAt, @@ -241,7 +250,9 @@ export function workspaceOperationService(db: Db) { store: operation.logStore, logRef: operation.logRef, ...result, - content: redactCurrentUserText(result.content), + content: redactCurrentUserText(result.content, { + enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs, + }), }; }, }; diff --git a/tests/e2e/onboarding.spec.ts b/tests/e2e/onboarding.spec.ts index f5f32be8..a89fe114 100644 --- a/tests/e2e/onboarding.spec.ts +++ b/tests/e2e/onboarding.spec.ts @@ -55,14 +55,7 @@ test.describe("Onboarding wizard", () => { ).toBeVisible(); await page.getByRole("button", { name: "More Agent Adapter Types" }).click(); - await page.getByRole("button", { name: "Process" }).click(); - - const commandInput = page.locator('input[placeholder="e.g. node, python"]'); - await commandInput.fill("echo"); - const argsInput = page.locator( - 'input[placeholder="e.g. script.js, --flag"]' - ); - await argsInput.fill("hello"); + await expect(page.getByRole("button", { name: "Process" })).toHaveCount(0); await page.getByRole("button", { name: "Next" }).click(); @@ -110,7 +103,16 @@ test.describe("Onboarding wizard", () => { ); expect(ceoAgent).toBeTruthy(); expect(ceoAgent.role).toBe("ceo"); - expect(ceoAgent.adapterType).toBe("process"); + expect(ceoAgent.adapterType).not.toBe("process"); + + const instructionsBundleRes = await page.request.get( + `${baseUrl}/api/agents/${ceoAgent.id}/instructions-bundle?companyId=${company.id}` + ); + expect(instructionsBundleRes.ok()).toBe(true); + const instructionsBundle = await instructionsBundleRes.json(); + expect( + instructionsBundle.files.map((file: { path: string }) => file.path).sort() + ).toEqual(["AGENTS.md", "HEARTBEAT.md", "SOUL.md", "TOOLS.md"]); const issuesRes = await page.request.get( `${baseUrl}/api/companies/${company.id}/issues` @@ -122,6 +124,10 @@ test.describe("Onboarding wizard", () => { ); expect(task).toBeTruthy(); expect(task.assigneeAgentId).toBe(ceoAgent.id); + expect(task.description).toContain( + "You are the CEO. You set the direction for the company." + ); + expect(task.description).not.toContain("github.com/paperclipai/companies"); if (!SKIP_LLM) { await expect(async () => { diff --git a/tests/release-smoke/docker-auth-onboarding.spec.ts b/tests/release-smoke/docker-auth-onboarding.spec.ts index 068c4234..497d993c 100644 --- a/tests/release-smoke/docker-auth-onboarding.spec.ts +++ b/tests/release-smoke/docker-auth-onboarding.spec.ts @@ -52,11 +52,6 @@ test.describe("Docker authenticated onboarding smoke", () => { ).toBeVisible({ timeout: 10_000 }); await expect(page.locator('input[placeholder="CEO"]')).toHaveValue(AGENT_NAME); - await page.getByRole("button", { name: "Process" }).click(); - await page.locator('input[placeholder="e.g. node, python"]').fill("echo"); - await page - .locator('input[placeholder="e.g. script.js, --flag"]') - .fill("release smoke"); await page.getByRole("button", { name: "Next" }).click(); await expect( @@ -98,7 +93,7 @@ test.describe("Docker authenticated onboarding smoke", () => { const ceoAgent = agents.find((entry) => entry.name === AGENT_NAME); expect(ceoAgent).toBeTruthy(); expect(ceoAgent!.role).toBe("ceo"); - expect(ceoAgent!.adapterType).toBe("process"); + expect(ceoAgent!.adapterType).not.toBe("process"); const issuesRes = await page.request.get( `${baseUrl}/api/companies/${company!.id}/issues` @@ -139,7 +134,7 @@ test.describe("Docker authenticated onboarding smoke", () => { ).toEqual( expect.objectContaining({ invocationSource: "assignment", - status: expect.stringMatching(/^(queued|running|succeeded)$/), + status: expect.stringMatching(/^(queued|running|succeeded|failed)$/), }) ); }); diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 2f7723de..6447a02d 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -28,6 +28,7 @@ import { CompanySkills } from "./pages/CompanySkills"; import { CompanyExport } from "./pages/CompanyExport"; import { CompanyImport } from "./pages/CompanyImport"; import { DesignGuide } from "./pages/DesignGuide"; +import { InstanceGeneralSettings } from "./pages/InstanceGeneralSettings"; import { InstanceSettings } from "./pages/InstanceSettings"; import { InstanceExperimentalSettings } from "./pages/InstanceExperimentalSettings"; import { PluginManager } from "./pages/PluginManager"; @@ -181,7 +182,7 @@ function InboxRootRedirect() { function LegacySettingsRedirect() { const location = useLocation(); - return ; + return ; } function OnboardingRoutePage() { @@ -306,9 +307,10 @@ export function App() { }> } /> } /> - } /> + } /> }> - } /> + } /> + } /> } /> } /> } /> diff --git a/ui/src/adapters/transcript.test.ts b/ui/src/adapters/transcript.test.ts new file mode 100644 index 00000000..8b56163e --- /dev/null +++ b/ui/src/adapters/transcript.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest"; +import { buildTranscript, type RunLogChunk } from "./transcript"; + +describe("buildTranscript", () => { + const ts = "2026-03-20T13:00:00.000Z"; + const chunks: RunLogChunk[] = [ + { ts, stream: "stdout", chunk: "opened /Users/dotta/project\n" }, + { ts, stream: "stderr", chunk: "stderr /Users/dotta/project" }, + ]; + + it("defaults username censoring to off when options are omitted", () => { + const entries = buildTranscript(chunks, (line, entryTs) => [{ kind: "stdout", ts: entryTs, text: line }]); + + expect(entries).toEqual([ + { kind: "stdout", ts, text: "opened /Users/dotta/project" }, + { kind: "stderr", ts, text: "stderr /Users/dotta/project" }, + ]); + }); + + it("still redacts usernames when explicitly enabled", () => { + const entries = buildTranscript(chunks, (line, entryTs) => [{ kind: "stdout", ts: entryTs, text: line }], { + censorUsernameInLogs: true, + }); + + expect(entries).toEqual([ + { kind: "stdout", ts, text: "opened /Users/d****/project" }, + { kind: "stderr", ts, text: "stderr /Users/d****/project" }, + ]); + }); +}); diff --git a/ui/src/adapters/transcript.ts b/ui/src/adapters/transcript.ts index 545c94f4..98b19454 100644 --- a/ui/src/adapters/transcript.ts +++ b/ui/src/adapters/transcript.ts @@ -2,6 +2,7 @@ import { redactHomePathUserSegments, redactTranscriptEntryPaths } from "@papercl import type { TranscriptEntry, StdoutLineParser } from "./types"; export type RunLogChunk = { ts: string; stream: "stdout" | "stderr" | "system"; chunk: string }; +type TranscriptBuildOptions = { censorUsernameInLogs?: boolean }; export function appendTranscriptEntry(entries: TranscriptEntry[], entry: TranscriptEntry) { if ((entry.kind === "thinking" || entry.kind === "assistant") && entry.delta) { @@ -21,17 +22,22 @@ export function appendTranscriptEntries(entries: TranscriptEntry[], incoming: Tr } } -export function buildTranscript(chunks: RunLogChunk[], parser: StdoutLineParser): TranscriptEntry[] { +export function buildTranscript( + chunks: RunLogChunk[], + parser: StdoutLineParser, + opts?: TranscriptBuildOptions, +): TranscriptEntry[] { const entries: TranscriptEntry[] = []; let stdoutBuffer = ""; + const redactionOptions = { enabled: opts?.censorUsernameInLogs ?? false }; for (const chunk of chunks) { if (chunk.stream === "stderr") { - entries.push({ kind: "stderr", ts: chunk.ts, text: redactHomePathUserSegments(chunk.chunk) }); + entries.push({ kind: "stderr", ts: chunk.ts, text: redactHomePathUserSegments(chunk.chunk, redactionOptions) }); continue; } if (chunk.stream === "system") { - entries.push({ kind: "system", ts: chunk.ts, text: redactHomePathUserSegments(chunk.chunk) }); + entries.push({ kind: "system", ts: chunk.ts, text: redactHomePathUserSegments(chunk.chunk, redactionOptions) }); continue; } @@ -41,14 +47,14 @@ export function buildTranscript(chunks: RunLogChunk[], parser: StdoutLineParser) for (const line of lines) { const trimmed = line.trim(); if (!trimmed) continue; - appendTranscriptEntries(entries, parser(trimmed, chunk.ts).map(redactTranscriptEntryPaths)); + appendTranscriptEntries(entries, parser(trimmed, chunk.ts).map((entry) => redactTranscriptEntryPaths(entry, redactionOptions))); } } const trailing = stdoutBuffer.trim(); if (trailing) { const ts = chunks.length > 0 ? chunks[chunks.length - 1]!.ts : new Date().toISOString(); - appendTranscriptEntries(entries, parser(trailing, ts).map(redactTranscriptEntryPaths)); + appendTranscriptEntries(entries, parser(trailing, ts).map((entry) => redactTranscriptEntryPaths(entry, redactionOptions))); } return entries; diff --git a/ui/src/api/health.ts b/ui/src/api/health.ts index b1573805..e2725b20 100644 --- a/ui/src/api/health.ts +++ b/ui/src/api/health.ts @@ -1,3 +1,17 @@ +export type DevServerHealthStatus = { + enabled: true; + restartRequired: boolean; + reason: "backend_changes" | "pending_migrations" | "backend_changes_and_pending_migrations" | null; + lastChangedAt: string | null; + changedPathCount: number; + changedPathsSample: string[]; + pendingMigrations: string[]; + autoRestartEnabled: boolean; + activeRunCount: number; + waitingForIdle: boolean; + lastRestartAt: string | null; +}; + export type HealthStatus = { status: "ok"; version?: string; @@ -9,6 +23,7 @@ export type HealthStatus = { features?: { companyDeletionEnabled?: boolean; }; + devServer?: DevServerHealthStatus; }; export const healthApi = { diff --git a/ui/src/api/instanceSettings.ts b/ui/src/api/instanceSettings.ts index 0b7a8f24..ef50ce47 100644 --- a/ui/src/api/instanceSettings.ts +++ b/ui/src/api/instanceSettings.ts @@ -1,10 +1,16 @@ import type { InstanceExperimentalSettings, + InstanceGeneralSettings, + PatchInstanceGeneralSettings, PatchInstanceExperimentalSettings, } from "@paperclipai/shared"; import { api } from "./client"; export const instanceSettingsApi = { + getGeneral: () => + api.get("/instance/settings/general"), + updateGeneral: (patch: PatchInstanceGeneralSettings) => + api.patch("/instance/settings/general", patch), getExperimental: () => api.get("/instance/settings/experimental"), updateExperimental: (patch: PatchInstanceExperimentalSettings) => diff --git a/ui/src/components/AgentConfigForm.tsx b/ui/src/components/AgentConfigForm.tsx index 086c0c13..0b515dca 100644 --- a/ui/src/components/AgentConfigForm.tsx +++ b/ui/src/components/AgentConfigForm.tsx @@ -44,6 +44,7 @@ import { ClaudeLocalAdvancedFields } from "../adapters/claude-local/config-field import { MarkdownEditor } from "./MarkdownEditor"; import { ChoosePathButton } from "./PathInstructionsModal"; import { OpenCodeLogoIcon } from "./OpenCodeLogoIcon"; +import { shouldShowLegacyWorkingDirectoryField } from "../lib/legacy-agent-config"; /* ---- Create mode values ---- */ @@ -297,6 +298,8 @@ export function AgentConfigForm(props: AgentConfigFormProps) { adapterType === "opencode_local" || adapterType === "pi_local" || adapterType === "cursor"; + const showLegacyWorkingDirectoryField = + isLocal && shouldShowLegacyWorkingDirectoryField({ isCreate, adapterConfig: config }); const uiAdapter = useMemo(() => getUIAdapter(adapterType), [adapterType]); // Fetch adapter models for the effective adapter type @@ -590,8 +593,8 @@ export function AgentConfigForm(props: AgentConfigFormProps) { )} {/* Working directory */} - {isLocal && ( - + {showLegacyWorkingDirectoryField && ( +
+
+
+
+ + Restart Required + {devServer.autoRestartEnabled ? ( + + Auto-Restart On + + ) : null} +
+

+ {describeReason(devServer)} + {changedAt ? ` · updated ${changedAt}` : ""} +

+
+ {sample.length > 0 ? ( + + Changed: {sample.join(", ")} + {devServer.changedPathCount > sample.length ? ` +${devServer.changedPathCount - sample.length} more` : ""} + + ) : null} + {devServer.pendingMigrations.length > 0 ? ( + + Pending migrations: {devServer.pendingMigrations.slice(0, 2).join(", ")} + {devServer.pendingMigrations.length > 2 ? ` +${devServer.pendingMigrations.length - 2} more` : ""} + + ) : null} +
+
+ +
+ {devServer.waitingForIdle ? ( +
+ + Waiting for {devServer.activeRunCount} live run{devServer.activeRunCount === 1 ? "" : "s"} to finish +
+ ) : devServer.autoRestartEnabled ? ( +
+ + Auto-restart will trigger when the instance is idle +
+ ) : ( +
+ + Restart pnpm dev:once after the active work is safe to interrupt +
+ )} +
+
+
+ ); +} diff --git a/ui/src/components/InstanceSidebar.tsx b/ui/src/components/InstanceSidebar.tsx index 451678d1..dbd8381b 100644 --- a/ui/src/components/InstanceSidebar.tsx +++ b/ui/src/components/InstanceSidebar.tsx @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { Clock3, FlaskConical, Puzzle, Settings } from "lucide-react"; +import { Clock3, FlaskConical, Puzzle, Settings, SlidersHorizontal } from "lucide-react"; import { NavLink } from "@/lib/router"; import { pluginsApi } from "@/api/plugins"; import { queryKeys } from "@/lib/queryKeys"; @@ -22,6 +22,7 @@ export function InstanceSidebar() {